ふるてつのぶろぐ

福岡在住のエンジニアです。

写真提供:福岡市

いまだにJava 8 備忘録 - MyBatis Genarator と LocalDateTime

今日すること

こんにちはふるてつです。
今日もJavaの備忘録になります。

f:id:tetsufuru:20191019103646p:plain

MyBatisとは昔からあるO/Rマッパーの事です。
今日もそのあたりのお話をしたいと思います。

f:id:tetsufuru:20191019103308p:plain
https://mybatis.org/mybatis-3/ja/index.html

以前MyBatis GeneratorMapperやらEntityやらを自動で作りましたが、 datetime型でddl定義されているテーブルカラムから作られるEntityjava.util.Date型になってしまいます。
それではちょっと古いのでやはりLocaldateTime型に変えたいところです。
その変え方をまた備忘録として書いておこうと思います。
(わりとすぐにできます)

普通に作ったEntity

まずは最初に普通に作ったEntityの例です。
内容とは特に関係ないので一番単純そうなEntityを例に書きます(MenuMst.java
下のような感じで日付の項目がJavaDate型で宣言されています。

package com.example.demo.entity.domain;

import java.time.LocalDateTime;
import java.util.Date;

public class MenuMst {

    /**
     * This field was generated by MyBatis Generator. This field corresponds to the database column MENU_MST.MENU_SEQ
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
    private Long menuSeq;
    /**
     * This field was generated by MyBatis Generator. This field corresponds to the database column MENU_MST.MENU_CODE
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
    private String menuCode;

    ~ 途中は省略します ~

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.ENTER_DATE
     * @return  the value of MENU_MST.ENTER_DATE
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
        // ↓ 登録日時 ゲッターの戻り値が Date型になっている
    public Date getEnterDate() {
        return enterDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.ENTER_DATE
     * @param enterDate  the value for MENU_MST.ENTER_DATE
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
        // ↓ 登録日時 セッターの引数も Date型
    public void setEnterDate(Date enterDate) {
        this.enterDate = enterDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.ENTER_USER
     * @return  the value of MENU_MST.ENTER_USER
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
    public String getEnterUser() {
        return enterUser;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.ENTER_USER
     * @param enterUser  the value for MENU_MST.ENTER_USER
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
    public void setEnterUser(String enterUser) {
        this.enterUser = enterUser == null ? null : enterUser.trim();
    }

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.UPDATE_DATE
     * @return  the value of MENU_MST.UPDATE_DATE
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
        // ↓ 更新日時 ゲッターの戻り値が Date型になっている
    public Date getUpdateDate() {
        return updateDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.UPDATE_DATE
     * @param updateDate  the value for MENU_MST.UPDATE_DATE
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
        // ↓ 更新日時 セッターの引数も Date型
    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.UPDATE_USER
     * @return  the value of MENU_MST.UPDATE_USER
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
    public String getUpdateUser() {
        return updateUser;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.UPDATE_USER
     * @param updateUser  the value for MENU_MST.UPDATE_USER
     * @mbg.generated  Sun Jun 07 15:23:08 JST 2020
     */
    public void setUpdateUser(String updateUser) {
        this.updateUser = updateUser == null ? null : updateUser.trim();
    }
}

GeneratorConfigに設定を追加

変え方はMyBatisのリファレンスのこちらに書いてあります。 https://mybatis.org/generator/configreference/javaTypeResolver.html

MyBatis Generatorのコンフィグファイルに下記を追加すればいいようです。
javaTypeResolveruseJSR310Types=trueをセットする感じですね。

<javaTypeResolver>
    <property name="useJSR310Types" value="true"/>
</javaTypeResolver>

余談ですがこれ以外に BigDecimal を使う設定もありました。
その時はforceBigDecimalsを使うようです。

修正後のGeneratorConfig

下が修正後のMyBatis Generatorのコンフィグファイル(generationConfig.xml)です。
わたしの環境そのままを貼り付けています。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
  <context id="handsOnTables" targetRuntime="MyBatis3">

    <!-- Connection Settings -->
    <jdbcConnection driverClass="org.h2.Driver"
        connectionURL="jdbc:h2:../workspace/product-manage-site-service-for-hands-on/product-manage/h2db/sampledb"
        userId="username"
        password="password">
    </jdbcConnection>

    // ↓ ここに追加してます。
    <javaTypeResolver>
        <property name="useJSR310Types" value="true"/>
    </javaTypeResolver>
    // ↑ ここに追加してます。

    <!-- Entity Models -->
    <javaModelGenerator targetPackage="com.example.demo.entity.domain" targetProject="biz/src/main/java">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>

    <!-- Sql Xml files -->
    <sqlMapGenerator targetPackage="com.example.demo.repository" targetProject="biz/src/main/resources">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>

    <!-- Mappers -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.example.demo.repository" targetProject="biz/src/main/java">
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

    <!-- Target tables -->
    <table schema="" tableName="USER_MST">
      <property name="mapUnderscoreToCamelCase" value="true" />
    </table>
    <table schema="" tableName="PRODUCT_MST">
      <property name="mapUnderscoreToCamelCase" value="true" />
    </table>
    <table schema="" tableName="PRODUCT_STOCK_MST">
      <property name="mapUnderscoreToCamelCase" value="true" />
    </table>
    <table schema="" tableName="PRODUCT_PURCHASE_TBL">
      <property name="mapUnderscoreToCamelCase" value="true" />
    </table>
    <table schema="" tableName="PAGE_ROLE_MST">
      <property name="mapUnderscoreToCamelCase" value="true" />
    </table>
    <table schema="" tableName="MENU_MST">
      <property name="mapUnderscoreToCamelCase" value="true" />
    </table>

  </context>
</generatorConfiguration>

修整後のEntity

ではMyBatis Generatorを実行しなおしてみます。
Entityが新しく作り直されました。
上記で上げた例とおなじファイルです(MenuMst.java

package com.example.demo.entity.domain;

import java.time.LocalDateTime;

public class MenuMst {

    /**
     * This field was generated by MyBatis Generator. This field corresponds to the database column MENU_MST.MENU_SEQ
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
    private Long menuSeq;
    /**
     * This field was generated by MyBatis Generator. This field corresponds to the database column MENU_MST.MENU_CODE
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
    private String menuCode;

    ~ 途中は省略します ~

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.ENTER_DATE
     * @return  the value of MENU_MST.ENTER_DATE
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
        // ↓ 登録日時 ゲッターの戻り値が LocalDateTime型に変わりました。
    public LocalDateTime getEnterDate() {
        return enterDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.ENTER_DATE
     * @param enterDate  the value for MENU_MST.ENTER_DATE
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
        // ↓ 登録日時 セッターの引数も LocalDateTime型に変わりました。
    public void setEnterDate(LocalDateTime enterDate) {
        this.enterDate = enterDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.ENTER_USER
     * @return  the value of MENU_MST.ENTER_USER
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
    public String getEnterUser() {
        return enterUser;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.ENTER_USER
     * @param enterUser  the value for MENU_MST.ENTER_USER
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
    public void setEnterUser(String enterUser) {
        this.enterUser = enterUser == null ? null : enterUser.trim();
    }

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.UPDATE_DATE
     * @return  the value of MENU_MST.UPDATE_DATE
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
        // ↓ 更新日時 ゲッターの戻り値が LocalDateTime型に変わりました。
    public LocalDateTime getUpdateDate() {
        return updateDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.UPDATE_DATE
     * @param updateDate  the value for MENU_MST.UPDATE_DATE
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
        // ↓ 更新日時 セッターの引数も LocalDateTime型に変わりました。
    public void setUpdateDate(LocalDateTime updateDate) {
        this.updateDate = updateDate;
    }

    /**
     * This method was generated by MyBatis Generator. This method returns the value of the database column MENU_MST.UPDATE_USER
     * @return  the value of MENU_MST.UPDATE_USER
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
    public String getUpdateUser() {
        return updateUser;
    }

    /**
     * This method was generated by MyBatis Generator. This method sets the value of the database column MENU_MST.UPDATE_USER
     * @param updateUser  the value for MENU_MST.UPDATE_USER
     * @mbg.generated  Sun Jun 07 15:48:11 JST 2020
     */
    public void setUpdateUser(String updateUser) {
        this.updateUser = updateUser == null ? null : updateUser.trim();
    }
}

このように自動生成したEntityの日付型がLocalDateTime型に変わりました。
ちなみに Mapperは特に変わる所はないです。Entityだけが変わる感じです。

感想


今日はまたMyBatis関連でした。
半年か1年位前に試していた内容なので忘れないうちにと書きました。
これですっきり忘れられます~
これはちょっとした断捨離になっているかもしれません…

それではまた😎

いまだにJava 8 備忘録 - MyBatisで更新日付などを自動設定

今日すること

こんにちはふるてつです。

きょうはJava 8 のMyBatisを使ったAOPのおはなしです。
f:id:tetsufuru:20200607114634p:plain
今回書きたかったのは、各テーブルに共通でもつような項目、
例えば「作成者/作成日時」、「更新者/更新日時」を自動で設定する方法です。
Spring BootSprinrg AOPを使います。

実はQiitaにやりたかったことがほぼ書いてあり、真似させていただいてます😊
作者の方ありがとうございます。
https://qiita.com/kenhori/items/9941a159285f9360e877

AOPのインストール

まずSpring AOPをインストールします。
わたしの環境は Gradle を使っているので Maven Repository で Gradle の書き方例を探します。
下記の例がのっていました。
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop/2.3.0.RELEASE


compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop', version: '2.3.0.RELEASE'

こちらをbuild.gradleファイルに書き入れます。
しかしすこし書き方はすこし古いようですね。
compile groupimplementationなどに書き換えた方がいい気がします。
参考までにわたしはいまこのように書いています。
(バージョン名は${springbootVersion}のように外出しにしてます)


implementation "org.springframework.boot:spring-boot-starter-aop:${springbootVersion}"

Interceptorを追加

わたしはMyBatisの Generator で Mapper を自動作成しています。
そのため Mapper の名前は必ず xxMapper となり、AOPで動きをキャッチしやすいですね。
ちなみにわたしのMapper周りは下のような構成になっています。
f:id:tetsufuru:20200606201353p:plain

ではソースを紹介します。
MapperInterceptor.javaというクラスになります。
細かく解説を入れましたのでみなさまの参考になればと思います。

package com.example.demo.interceptor;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Objects;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import com.example.demo.service.AccountService;

import lombok.RequiredArgsConstructor;

@Aspect
@Component
@RequiredArgsConstructor
public class MapperInterceptor {

    private static final String CONST_INSERT = "insert";
    private static final String CONST_UPDATE = "update";
    private static final String CONST_SET_ENTER_USER = "setEnterUser";
    private static final String CONST_SET_ENTER_DATE = "setEnterDate";
    private static final String CONST_SET_UPDATE_USER = "setUpdateUser";
    private static final String CONST_SET_UPDATE_DATE = "setUpdateDate";

    private final AccountService accountService;

    // ↓ ① Beforeなのでメソッドの実行前に呼び出される
    // ↓ ② executionでマッチするメソッドの条件を書いている
    //     (hogeMapper の #insertHoge() メソッドか、#updateHoge() メソッド)
    @Before("execution(* com.example.demo.repository.*Mapper.insert*(..)) || "
            + "execution(* com.example.demo.repository.*Mapper.update*(..))")
    public void setCommonInformations(JoinPoint joinPoint)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        // ③ JoinPoint をもとにいまインターセプトされているメソッドの名前を取得する
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String methodName = method.getName();

        // ④ 登録、更新日時にセットしたい時刻を変数にいれる(UTC基準の現在時刻)
        LocalDateTime localDateTimeNowUtc = LocalDateTime.now(ZoneOffset.UTC);

        // ⑤ JoinPoint をもとにメソッドの1番目の引数を取得する(テーブル更新用のDTOが必ず最初の引数なので)
        Object[] args = joinPoint.getArgs();
        Object dto = args[0];

        if (methodName.startsWith(CONST_INSERT)) {
            // ⑥ こちらが Insert 系のメソッドの時
            //  「作成者・更新者の情報」と、「登録・更新日時にセットしたい時刻」を dto にセットします。
            //  (作成者・更新者情報は アカウントサービスから取得するようにしています)
            setupEnterInformations(accountService.getUserName(), localDateTimeNowUtc, dto);
            setupUpdateInformations(accountService.getUserName(), localDateTimeNowUtc, dto);

        } else if (methodName.startsWith(CONST_UPDATE)) {
            // ⑦ こちらが Update 系のメソッドの時
            //  「更新者の情報」と、「更新日時にセットしたい時刻」を dto にセットします。
            setupUpdateInformations(accountService.getUserName(), localDateTimeNowUtc, dto);
        }

    }

    // ↓ ⑧ 作成者/作成日時を dto に自動で設定するメソッド
    private void setupEnterInformations(String userName, LocalDateTime utcLocalDateTimeNow, Object dto)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        // ↓ ⑧ - 1 リフレクションで dto の #setEnterUser() メソッド(enterUser のセッター)を取得する
        Method setEnterUserMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_ENTER_USER, String.class);
        if (Objects.nonNull(setEnterUserMethod)) {
            // ↓ ⑧ - 2  dto の #setEnterUser() メソッドに userName をセットする
            setEnterUserMethod.invoke(dto, userName);
        }

        // ↓ ⑧ - 3 リフレクションで dto の #setEnterDate() メソッド(enterDateのセッター)を取得する
        Method setEnterDateMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_ENTER_DATE,
                LocalDateTime.class);
        if (Objects.nonNull(setEnterDateMethod)) {
            // ↓ ⑧ - 4  dto の #setEnterDate() メソッドに 現在時刻 をセットする
            setEnterDateMethod.invoke(dto, utcLocalDateTimeNow);
        }

    }

    // ↓ ⑨ 更新者/更新日時を dto に自動で設定するメソッド
    private void setupUpdateInformations(String userName, LocalDateTime utcLocalDateTimeNow, Object dto)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

        Method setUpdateUserMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_UPDATE_USER, String.class);
        if (Objects.nonNull(setUpdateUserMethod)) {
            setUpdateUserMethod.invoke(dto, userName);
        }

        Method setUpdateDateMethod = ReflectionUtils.findMethod(dto.getClass(), CONST_SET_UPDATE_DATE,
                LocalDateTime.class);
        if (Objects.nonNull(setUpdateDateMethod)) {
            setUpdateDateMethod.invoke(dto, utcLocalDateTimeNow);
        }
    }
}

感想


今回はMyBatisAOPについて書きました。
Qiitaの内容をだいぶ参考にしたのでわたしのオリジナルな部分はかなり少なめですが。
しかしこのあたりの内容は忘れてしまいやすいので一度、自分のブログに書きとめておきたかった次第です。

きょうは久しぶりにブログを書きました。
久しぶりすぎてどうやって書いていたか思い出せずちょっと戸惑いました~

それではまた😎

Angular 9 で Web アプリを作ろう - カスタムバリデーション

今日すること

こんにちは、ふるてつです。

今日はカスタムバリデーションについて書きます。
Angular標準のバリデーションは1つの単項目しかチェックできません。 複数の項目にまたがったチェックをしたい場合はカスタムバリデーションを作ります。

例えば画面上の「在庫数」と「購入数」を比較し「在庫数より購入数が上回っている場合」に在庫不足エラーとする。
そんな感じのバリデーションが簡単に書けるので便利です。

今回その「在庫数」と「購入数」を比較するバリデーションを作ってみました。
少しつきなみかもしれませんが、今日はそのお話をします😎

日本語リファレンスの「カスタムバリデータ」~「クロスフィールドバリデーション」を参考にして書きました。
https://angular.jp/guide/form-validation

f:id:tetsufuru:20200304004000p:plain

カスタムバリデーションを書く場所

まずカスタムバリデーションを追加する場所ですが、基本的にはcomponentts中に書きます。
わたしの場合は、リアクティブフォームを使っていますので、下記のようにformBuilder.group()の中になります。
バリデーションは「PurchaseQuantityStockQuantityValidator」という名前です。

registeringForm = this.formBuilder.group(
  {
    productCode: this.productCode,
    productName: this.productName,
 ~ 中略 ~
    validatorLocale: this.validatorLocale
  },
  {
    // ↓ ここです。
    // ↓ formBuilder.group()の validators に追加します。
    validators: [PurchaseQuantityStockQuantityValidator]
    // ↑ ここです。
  }
);

カスタムバリデーションの内容

バリデーションの内容は下記になりました。
長くないので全文を掲載しました。
「在庫数」と「購入数」以外に「ロケール」も必要になったので、追加してます。
(カンマ区切りになっている在庫数や購入数を数値に戻すときに必要になりました)
そしてこのバリデーションを使うフォームにも「ロケール」が必要になりますねー。

import { FormattedNumberPipe } from 'src/app/core/pipes/formatted-number.pipe';

import { FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';

const PRODUCT_STOCK_QUANTITY = 'productStockQuantity';
const PRODUCT_PURCHASE_QUANTITY = 'productPurchaseQuantity';
const VALIDATOR_LOCALE = 'validatorLocale';

export const PurchaseQuantityStockQuantityValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {
  // フォームから必要なコントロールの値を取得する。
  // ↓「在庫数」
  const productStockQuantity: string = control.get(PRODUCT_STOCK_QUANTITY).value;
  // ↓「購入数」
  const productPurchaseQuantity: string = control.get(PRODUCT_PURCHASE_QUANTITY).value;
  // ↓「ロケール」
  const validatorLocale: string = control.get(VALIDATOR_LOCALE).value;

  // どちらかが空の場合は null を返します(エラーなしとします)。  
  if (!productStockQuantity) {
    return;
  }
  if (!productPurchaseQuantity) {
    return;
  }
  
  // 自作のパイプでカンマ区切りの値を数値に戻しています。  
  const formattedNumberPipe: FormattedNumberPipe = new FormattedNumberPipe();
  const numProductStockQuantity = Number(formattedNumberPipe.parse(productStockQuantity, validatorLocale));
  const numProductPurchaseQuantity = Number(formattedNumberPipe.parse(productPurchaseQuantity, validatorLocale));

  //  在庫数が多い場合は正常なので null を返します(エラーなし)。
  if (numProductPurchaseQuantity <= numProductStockQuantity) {
    return;
  }

  // ↓ ここから下はエラーになります。
  const validateError = { exceedStockError: true };
  // ↓ 「購入数」のエラーにしたいので購入数の```control```にエラーをセットします。
  control.get(PRODUCT_PURCHASE_QUANTITY).setErrors(validateError);
  // ↓ 戻り値もエラーを返します。
  return validateError;
};

Unitテスト

Unitテストも念のため掲載します。
こちらも全文載せました。
無駄に長ければすみません。

import { TestBed } from '@angular/core/testing';
import { FormBuilder, FormControl, FormGroup } from '@angular/forms';

import {
    PurchaseQuantityStockQuantityValidator
} from './purchase-quantity-stock-quantity-validator';

const PRODUCT_PURCHASE_QUANTITY = 'productPurchaseQuantity';
describe('PurchaseQuantityStockQuantityValidator', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [PurchaseQuantityStockQuantityValidator]
    });
  });

  describe('#validate', () => {
    // ↓ 「在庫数」がブランクの時にエラーがないことを確認
    it('should not have error | productStockQuantity is blank', () => {
      const formBuilder: FormBuilder = new FormBuilder();
      const testingForm: FormGroup = formBuilder.group({
        productStockQuantity: new FormControl(''),
        productPurchaseQuantity: new FormControl(1),
        validatorLocale: new FormControl('ja-JP')
      });
      PurchaseQuantityStockQuantityValidator(testingForm);
      expect(testingForm.get(PRODUCT_PURCHASE_QUANTITY).getError('exceedStockError')).toBeNull();
    });
    // ↓ 「在庫数」が null の時にエラーがないことを確認(上と同じ内容ですが念のため null の時をテストしておく)
    it('should not have error | productStockQuantity is null', () => {
      const formBuilder: FormBuilder = new FormBuilder();
      const testingForm: FormGroup = formBuilder.group({
        productStockQuantity: new FormControl(null),
        productPurchaseQuantity: new FormControl(1),
        validatorLocale: new FormControl('ja-JP')
      });
      PurchaseQuantityStockQuantityValidator(testingForm);
      expect(testingForm.get(PRODUCT_PURCHASE_QUANTITY).getError('exceedStockError')).toBeNull();
    });

    // ↓ 「購入数」がブランクの時にエラーがないことを確認
    it('should not have error | productPurchaseQuantity is blank', () => {
      const formBuilder: FormBuilder = new FormBuilder();
      const testingForm: FormGroup = formBuilder.group({
        productStockQuantity: new FormControl(1),
        productPurchaseQuantity: new FormControl(''),
        validatorLocale: new FormControl('ja-JP')
      });
      PurchaseQuantityStockQuantityValidator(testingForm);
      expect(testingForm.get(PRODUCT_PURCHASE_QUANTITY).getError('exceedStockError')).toBeNull();
    });

    // ↓ 「購入数」が null の時にエラーがないことを確認
    it('should not have error | productPurchaseQuantity is null', () => {
      const formBuilder: FormBuilder = new FormBuilder();
      const testingForm: FormGroup = formBuilder.group({
        productStockQuantity: new FormControl(1),
        productPurchaseQuantity: new FormControl(null),
        validatorLocale: new FormControl('ja-JP')
      });
      PurchaseQuantityStockQuantityValidator(testingForm);
      expect(testingForm.get(PRODUCT_PURCHASE_QUANTITY).getError('exceedStockError')).toBeNull();
    });

    // ↓ 「購入数」と「在庫数」が等しい時にエラーがないことを確認
    it('should not have error | productPurchaseQuantity equals productStockQuantity', () => {
      const formBuilder: FormBuilder = new FormBuilder();
      const testingForm: FormGroup = formBuilder.group({
        productStockQuantity: new FormControl(1),
        productPurchaseQuantity: new FormControl(1),
        validatorLocale: new FormControl('ja-JP')
      });
      PurchaseQuantityStockQuantityValidator(testingForm);
      expect(testingForm.get(PRODUCT_PURCHASE_QUANTITY).getError('exceedStockError')).toBeNull();
    });

    // ↓ 「購入数」が「在庫数」を超えた時はエラーになることを確認
    it('should have error | productPurchaseQuantity exceeds productStockQuantity', () => {
      const formBuilder: FormBuilder = new FormBuilder();
      const testingForm: FormGroup = formBuilder.group({
        productStockQuantity: new FormControl(1),
        productPurchaseQuantity: new FormControl(2),
        validatorLocale: new FormControl('ja-JP')
      });
      PurchaseQuantityStockQuantityValidator(testingForm);
      expect(testingForm.get(PRODUCT_PURCHASE_QUANTITY).getError('exceedStockError')).toBeTruthy();
    });
  });
});

動かしてみる

今日やりたかったのは下記のような感じです。
f:id:tetsufuru:20200304013527p:plain

今日の感想


今日はカスタムバリデーションについて書いてみました。
コードもさほど多くならないし、テストもあまり難しくならなかったので、練習でさらに一つ二つ書いてみるのもいいかもと思いました🌸

では、今日もお疲れ様でした。

Angular 9 で Web アプリを作ろう - わたしも今日からバージョン 9

今日すること

こんにちは、ふるてつです。 年明けついにAngularがバージョン 9 になりました。
今日は製造中の自分のAngularをバージョンアップしてみました。

バージョンアップ情報の確認

まずはこちらのサイトで手順やその他情報をチェックします。
https://update.angular.io/#8.0:9.0

ここに書いてある手順は実際には行いません。
ng updateコマンドで大体のことができるからです。
困ったときなど用にすこし参考にするだけです。

f:id:tetsufuru:20200215171850p:plain f:id:tetsufuru:20200215172728p:plain

TypeScriptは今回3.7にあがりそうです。
materialもあがりますねー。
@angular/localizeのことが書いてあるようですが、わたしは他言語化ngx-translateを使っているので関係なさそうです。
この感じだと今回もng updateのコマンドの実行だけで済みそうです。

ng updateコマンド

早速ですがAngularcling updateコマンドを実行します。
すると下記のリストがでてきました。
今回はclicorematerialの3つのバージョンアップ作業が必要そうです。

ng update
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
Collecting installed dependencies...
Found 39 dependencies.
    We analyzed your package.json, there are some packages to update:
    
      Name                              Version                  Command to update
     -------------------------------------------------------------------------------
      @angular/cli                      8.3.22 -> 9.0.2          ng update @angular/cli
      @angular/core                     8.2.14 -> 9.0.1          ng update @angular/core
      @angular/material                 8.2.3 -> 9.0.0           ng update @angular/material

実際にバージョンアップするコマンドですが、上のリストの最後右端に書いてあります。
コマンド実行する順番はcoreが最初で、次cli、そしてmaterialがこれまでの経験的に良い気がします。
しかし今回はcliを忘れて飛ばしてしまい、cliが最後になりました。

バージョンアップ実施

angular/coreのバージョンアップ

まず最初にcoreをバージョンアップします。
ng update @angular/core

ng update @angular/core    
The installed Angular CLI version is older than the latest stable version.
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
Collecting installed dependencies...
Found 39 dependencies.
Fetching dependency metadata from registry...
                  Package "@angular-devkit/build-angular" has an incompatible peer dependency to "typescript" (requires ">=3.1 < 3.6", would install "3.7.5").
Incompatible peer dependencies found.
Peer dependency warnings when installing dependencies means that those dependencies might not work correctly together.        
You can use the '--force' option to ignore incompatible peer dependencies and instead address these warnings later.

おや、警告が出ました。
Typescriptがらみのわたしがちょっと苦手なタイプの警告ですね。
Typescriptのバージョンが3.7.5に上がりますが、>=3.1 < 3.6が必要なので上がりすぎます」という旨と思います。
最初にみたサイトでそもそもTypescriptは3.7に上がるようでしたので、ここは無視してバージョンアップを続けます。
(もし動かなければ、Typescriptだけ後でバージョンを下げます)

ではng update @angular/core --forceコマンドを流します(--forceオプションをつけて強制実行)

ng update @angular/core --force
The installed Angular CLI version is older than the latest stable version.
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
Collecting installed dependencies...
Found 39 dependencies.
Fetching dependency metadata from registry...
                  Package "@angular-devkit/build-angular" has an incompatible peer dependency to "typescript" (requires ">=3.1 < 3.6", would install "3.7.5").
    Updating package.json with dependency @angular/core @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/language-service @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/forms @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/platform-browser @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/animations @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/platform-browser-dynamic @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/common @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/router @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/compiler @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency @angular/compiler-cli @ "9.0.1" (was "8.2.14")...
    Updating package.json with dependency zone.js @ "0.10.2" (was "0.9.1")...
    Updating package.json with dependency typescript @ "3.7.5" (was "3.5.3")...
UPDATE package.json (1658 bytes)
√ Packages installed successfully.
** Executing migrations of package '@angular/core' **

> Static flag migration.
  Removes the `static` flag from dynamic queries.
  As of Angular 9, the "static" flag defaults to false and is no longer required for your view and content queries.
  Read more about this here: https://v9.angular.io/guide/migration-dynamic-flag
  Migration completed.

> Missing @Injectable and incomplete provider definition migration.
  In Angular 9, enforcement of @Injectable decorators for DI is a bit stricter and incomplete provider definitions behave differently.
  Read more about this here: https://v9.angular.io/guide/migration-injectable
  Migration completed.

> ModuleWithProviders migration.
  In Angular 9, the ModuleWithProviders type without a generic has been deprecated.
  This migration adds the generic where it is missing.
  Read more about this here: https://v9.angular.io/guide/migration-module-with-providers
  Migration completed.

> Renderer to Renderer2 migration.
  As of Angular 9, the Renderer class is no longer available.
  Renderer2 should be used instead.
  Read more about this here: https://v9.angular.io/guide/migration-renderer
  Migration completed.

> Undecorated classes with decorated fields migration.
  As of Angular 9, it is no longer supported to have Angular field decorators on a class that does not have an Angular decorator.
  Read more about this here: https://v9.angular.io/guide/migration-undecorated-classes
  Migration completed.

> Undecorated classes with DI migration.
  As of Angular 9, it is no longer supported to use Angular DI on a class that does not have an Angular decorator.
  Read more about this here: https://v9.angular.io/guide/migration-undecorated-classes
  Migration completed.


Your project has been updated to Angular version 9!
For more info, please see: https://v9.angular.io/guide/updating-to-version-9

おっ、良い感じのようです。

angular/materialのバージョンアップ

次はmaterialを上げます。 (順番的にはcliだと思うのですが、忘れてて飛ばしました)
ng update @angular/material

ng update @angular/material
The installed Angular CLI version is older than the latest stable version.
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
Collecting installed dependencies...
Found 39 dependencies.
Fetching dependency metadata from registry...
    Updating package.json with dependency @angular/material @ "9.0.0" (was "8.2.3")...
    Updating package.json with dependency @angular/cdk @ "9.0.0" (was "8.2.3")...
UPDATE package.json (1658 bytes)
√ Packages installed successfully.
** Executing migrations of package '@angular/material' **

> Updates Angular Material to v9

    ⚠  General notice: The HammerJS v9 migration for Angular Components is not able to migrate tests. Please manually clean up tests in your project if they rely on HammerJS.
    Read more about migrating tests: https://git.io/ng-material-v9-hammer-migrate-tests

      ✓  Updated Angular Material to version 9

UPDATE src/main.ts (373 bytes)
UPDATE package.json (1632 bytes)
/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site\ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site| Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site/ Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[ .................] | preinstall:product-manage-site-for-hands-on: info lifecycle product-manage-site- Installing packages...[       ...........] | extract:fsevents: verb lock using C:\Users\tetsuji\AppData\Roaming\npm-cache\_l\ Installing packages...[       ...........] | extract:fsevents: verb lock using C:\Users\tetsuji\AppData\Roaming\npm-cache\_l| Installing packages...[       ...........] | extract:fsevents: verb lock using C:\Users\tetsuji\AppData\Roaming\npm-cache\_l/ Installing packages...[       ...........] | extract:fsevents: verb lock using C:\Users\tetsuji\AppData\Roaming\npm-cache\_l- Installing packages...[        ..........] / extract:bindings: sill extract bindings@1.5.0 extracted to C:\papa\00.default\1\ Installing packages...[         .........] \ extract:file-uri-to-path: sill extract file-uri-to-path@1.0.0 extracted to C:\p| Installing packages...[         .........] \ extract:file-uri-to-path: sill extract file-uri-to-path@1.0.0 extracted to C:\p/ Installing packages...[          ........] | extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] | extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] | extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] | extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse| Installing packages...[          ........] \ extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse/ Installing packages...[          ........] | extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse- Installing packages...[          ........] / extract:nan: sill extract nan@2.14.0 extracted to C:\papa\00.default\12.Benesse\ Installing packages...[           .......] - extract:fsevents: sill extract fsevents@1.2.11 extracted to C:\papa\00.default\| Installing packages...[           .......] - extract:fsevents: sill extract fsevents@1.2.11 extracted to C:\papa\00.default\/ Installing packages...[           .......] - extract:fsevents: sill extract fsevents@1.2.11 extracted to C:\papa\00.default\| Installing packages...[            ......] / finalize:code-point-at: sill finalize C:\papa\00.default\12.Benesse\product-man/ Installing packages...[            ......] / finalize:isarray: sill finalize C:\papa\00.default\12.Benesse\product-manage-si- Installing packages...[            ......] \ finalize:is-fullwidth-code-point: sill finalize C:\papa\00.default\12.Benesse\p\ Installing packages...[            ......] \ finalize:osenv: sill finalize C:\papa\00.default\12.Benesse\product-manage-site| Installing packages...[            ......] | finalize:safe-buffer: sill finalize C:\papa\00.default\12.Benesse\product-manag/ Installing packages...[            ......] \ finalize:strip-json-comments: sill finalize C:\papa\00.default\12.Benesse\produ- Installing packages...[            ......] \ finalize:are-we-there-yet: sill finalize C:\papa\00.default\12.Benesse\product-\ Installing packages...[            ......] / finalize:gauge: sill finalize C:\papa\00.default\12.Benesse\product-manage-site| Installing packages...[            ......] - finalize:glob: sill finalize C:\papa\00.default\12.Benesse\product-manage-site-/ Installing packages...[            ......] - finalize:fs-minipass: sill finalize C:\papa\00.default\12.Benesse\product-manag- Installing packages...[            ......] - finalize:nan: sill finalize C:\papa\00.default\12.Benesse\product-manage-site-f\ Installing packages...[            ......] - finalize:deep-extend: sill finalize C:\papa\00.default\12.Benesse\product-manag| Installing packages...[            ......] - finalize:minimatch: sill finalize C:\papa\00.default\12.Benesse\product-manage-/ Installing packages...[            ......] / finalize:object-assign: sill finalize C:\papa\00.default\12.Benesse\product-man- Installing packages...[            ......] \ finalize:process-nextick-args: sill finalize C:\papa\00.default\12.Benesse\prod\ Installing packages...[            ......] | finalize:sax: sill finalize C:\papa\00.default\12.Benesse\product-manage-site-f| Installing packages...[            ......] - finalize:semver: sill finalize C:\papa\00.default\12.Benesse\product-manage-sit/ Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana- Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana\ Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana| Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana/ Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana- Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana\ Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana| Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana/ Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana- Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana\ Installing packages...[            ......] \ finalize:set-blocking: sill finalize C:\papa\00.default\12.Benesse\product-mana| Installing packages...[            ......] | finalize:signal-exit: sill finalize C:\papa\00.default\12.Benesse\product-manag\ Installing packages...[            ......] - finalize:strip-ansi: sill finalize C:\papa\00.default\12.Benesse\product-manage/ Installing packages...[            ......] | finalize:strip-json-comments: sill finalize C:\papa\00.default\12.Benesse\produ- Installing packages...[            ......] | finalize:strip-json-comments: sill finalize C:\papa\00.default\12.Benesse\produ\ Installing packages...[            ......] | finalize:strip-json-comments: sill finalize C:\papa\00.default\12.Benesse\produ| Installing packages...[            ......] - finalize:gauge: sill finalize C:\papa\00.default\12.Benesse\product-manage-site/ Installing packages...[            ......] - finalize:inflight: sill finalize C:\papa\00.default\12.Benesse\product-manage-s- Installing packages...[            ......] \ finalize:fs-minipass: sill finalize C:\papa\00.default\12.Benesse\product-manag\ Installing packages...[            ......] - finalize:node-pre-gyp: sill finalize C:\papa\00.default\12.Benesse\product-mana| Installing packages...[            ......] \ finalize:fsevents: sill finalize C:\papa\00.default\12.Benesse\product-manage-s/ Installing packages...[            ......] / finalize:code-point-at: sill finalize C:\papa\00.default\12.Benesse\product-man- Installing packages...[            ......] - finalize:deep-extend: sill finalize C:\papa\00.default\12.Benesse\product-manag\ Installing packages...[            ......] - finalize:minimatch: sill finalize C:\papa\00.default\12.Benesse\product-manage-| Installing packages...[            ......] / finalize:npm-bundled: sill finalize C:\papa\00.default\12.Benesse\product-manag/ Installing packages...[            ......] / finalize:object-assign: sill finalize C:\papa\00.default\12.Benesse\product-man- Installing packages...[            ......] / finalize:nopt: sill finalize C:\papa\00.default\12.Benesse\product-manage-site-\ Installing packages...[            ......] - finalize:safer-buffer: sill finalize C:\papa\00.default\12.Benesse\product-mana| Installing packages...[            ......] - finalize:semver: sill finalize C:\papa\00.default\12.Benesse\product-manage-sit/ Installing packages...[            ......] | finalize:strip-json-comments: sill finalize C:\papa\00.default\12.Benesse\produ- Installing packages...[            ......] - finalize:util-deprecate: sill finalize C:\papa\00.default\12.Benesse\product-ma\ Installing packages...[            ......] / finalize:once: sill finalize C:\papa\00.default\12.Benesse\product-manage-site-| Installing packages...[            ......] | finalize:rimraf: sill finalize C:\papa\00.default\12.Benesse\product-manage-sit/ Installing packages...[            ......] / finalize:tar: sill finalize C:\papa\00.default\12.Benesse\product-manage-site-f- Installing packages...[            ......] - finalize:node-pre-gyp: sill finalize C:\papa\00.default\12.Benesse\product-mana- Installing packages...[            ......] / refresh-package-json:deep-extend: sill refresh-package-json C:\papa\00.default\\ Installing packages...[            ......] - refresh-package-json:safe-buffer: sill refresh-package-json C:\papa\00.default\| Installing packages...[            ......] - refresh-package-json:safe-buffer: sill refresh-package-json C:\papa\00.default\/ Installing packages...[            ......] - refresh-package-json:safe-buffer: sill refresh-package-json C:\papa\00.default\- Installing packages...[            ......] - refresh-package-json:safe-buffer: sill refresh-package-json C:\papa\00.default\\ Installing packages...[            ......] - refresh-package-json:safe-buffer: sill refresh-package-json C:\papa\00.default\/ Installing packages...[             .....] | postinstall:product-manage-site-for-hands-on: info lifecycle product-manage-sit\ Installing packages...[              ....] - prepare:product-manage-site-for-hands-on: info lifecycle product-manage-site-fo\ Installing packages...[              ....] / prepare:product-manage-site-for-hands-on: info lifecycle product-manage-site-fonpm WARN @angular-devkit/build-angular@0.803.22 requires a peer of @angular/compiler-cli@^8.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN @angular-devkit/build-angular@0.803.22 requires a peer of typescript@>=3.1 < 3.6 but none is installed. You must install peer dependencies yourself.
npm WARN @ngtools/webpack@8.3.22 requires a peer of @angular/compiler-cli@^8.0.0 but none is installed. You must install peer 
dependencies yourself.
npm WARN @ngtools/webpack@8.3.22 requires a peer of typescript@>=3.4 < 3.6 but none is installed. You must install peer dependencies yourself.
npm WARN karma-jasmine-html-reporter@1.5.1 requires a peer of jasmine-core@>=3.5 but none is installed. You must install peer 
dependencies yourself.
npm WARN ngx-translate-testing@3.0.0 requires a peer of @angular/common@^8.0.0-rc.0 || ^8.0.0 but none is installed. You must 
install peer dependencies yourself.
npm WARN ngx-translate-testing@3.0.0 requires a peer of @angular/core@^8.0.0-rc.0 || ^8.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.1.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
/ Installing packages...WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.11 (node_modules\webpack-dev-server\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.11: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.11 (node_modules\watchpack\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.11: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\karma\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

removed 1 package and audited 16850 packages in 54.761s
found 2 high severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details
√ Packages installed successfully.
  Migration completed.

** Executing migrations of package '@angular/cdk' **

> Updates the Angular CDK to v9

      ✓  Updated Angular CDK to version 9

  Migration completed.

こちらも良い感じです。
脆弱性の警告が2件ほど出たもようなので、作業がひとしきり終わったら対応しようと思います(npm audit fix

angular/cliのバージョンアップ

3番目にng update @angular/cli

ng update @angular/cli
The installed Angular CLI version is older than the latest stable version.
Installing a temporary version to perform the update.
Installing packages for tooling via npm.
Installed packages for tooling via npm.
Using package manager: 'npm'
Collecting installed dependencies...
Found 38 dependencies.
Fetching dependency metadata from registry...
    Updating package.json with dependency @angular/cli @ "9.0.2" (was "8.3.22")...
UPDATE package.json (1630 bytes)
√ Packages installed successfully.
** Executing migrations of package '@angular/cli' **

> Angular Workspace migration.
  Update an Angular CLI workspace to version 9.
UPDATE angular.json (3915 bytes)
UPDATE tsconfig.app.json (272 bytes)
UPDATE package.json (1633 bytes)
√ Packages installed successfully.
  Migration completed.

> Lazy loading syntax migration.
  Update lazy loading syntax to use dynamic imports.
  Migration completed.

> Replace deprecated 'styleext' and 'spec' Angular schematic options.
  Migration completed.

こちらも問題なく上がりました。

脆弱性の修正

あとは先ほどの脆弱性をこちらのコマンドで修正します。
npm audit fix

npm audit fix

> core-js@3.6.0 postinstall C:\papa\00.default\12.Benesse\product-manage-site-for-hands-on\node_modules\core-js
> node -e "try{require('./postinstall')}catch(e){}"

Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!

The project needs your help! Please consider supporting of core-js on Open Collective or Patreon: 
> https://opencollective.com/core-js 
> https://www.patreon.com/zloirock 

Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)

npm WARN karma-jasmine-html-reporter@1.5.1 requires a peer of jasmine-core@>=3.5 but none is installed. You must install peer 
dependencies yourself.
npm WARN ngx-translate-testing@3.0.0 requires a peer of @angular/common@^8.0.0-rc.0 || ^8.0.0 but none is installed. You must 
install peer dependencies yourself.
npm WARN ngx-translate-testing@3.0.0 requires a peer of @angular/core@^8.0.0-rc.0 || ^8.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN sass-loader@8.0.0 requires a peer of node-sass@^4.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN sass-loader@8.0.0 requires a peer of fibers@>= 3.1.0 but none is installed. You must install peer dependencies yourself.
npm WARN webpack-subresource-integrity@1.3.4 requires a peer of html-webpack-plugin@^2.21.0 || ~3 || >=4.0.0-alpha.2 <5 but none is installed. You must install peer dependencies yourself.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.11 (node_modules\webpack-dev-server\node_modules\fsevents):      
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.11: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.11 (node_modules\watchpack\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.11: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\karma\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@2.1.2 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@2.1.2: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ @angular-devkit/build-angular@0.900.2
added 196 packages from 108 contributors, removed 31 packages, updated 223 packages and moved 2 packages in 310.024s
fixed 2 of 2 vulnerabilities in 16850 scanned packages

2件の脆弱性は良い感じに修正できた模様です。

バージョン確認

ここでAngularのバージョンを確認してみます。
ng version

ng version
? Would you like to share anonymous usage data about this project with the Angular Team at
Google under Google’s Privacy Policy at https://policies.google.com/privacy? For more     
details and how to change this setting, see http://angular.io/analytics. No

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/
    

Angular CLI: 9.0.2
Node: 12.4.0
OS: win32 x64

Angular: 9.0.1
... animations, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router
Ivy Workspace: Yes

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.900.2
@angular-devkit/build-angular     0.900.2
@angular-devkit/build-optimizer   0.900.2
@angular-devkit/build-webpack     0.900.2
@angular-devkit/core              9.0.2
@angular-devkit/schematics        9.0.2
@angular/cdk                      9.0.0
@angular/cli                      9.0.2
@angular/material                 9.0.0
@ngtools/webpack                  9.0.2
@schematics/angular               9.0.2
@schematics/update                0.900.2
rxjs                              6.5.4
typescript                        3.7.5
webpack                           4.41.2

良いですねぇ、ちゃんと 9 に上がってます。
レンダリングもIvyに変わったようです(Ivy Workspace: Yes)。

動作確認

起動の確認

では動くかどうか起動してみます。
ng serve --o s(起動コマンドです、少し省略してます)

ng serve --o s
0% compiling
Compiling @angular/core : es2015 as esm2015

Compiling @angular/common : es2015 as esm2015

Compiling @angular/platform-browser : es2015 as esm2015

Compiling @angular/platform-browser-dynamic : es2015 as esm2015

Compiling @angular/animations : es2015 as esm2015

Compiling @angular/animations/browser : es2015 as esm2015

Compiling @angular/platform-browser/animations : es2015 as esm2015

Compiling @angular/router : es2015 as esm2015

Compiling @angular/common/http : es2015 as esm2015

Compiling @angular/forms : es2015 as esm2015

Compiling @angular/cdk/keycodes : es2015 as esm2015

Compiling @angular/cdk/platform : es2015 as esm2015

Compiling @angular/cdk/observers : es2015 as esm2015

Compiling @angular/cdk/a11y : es2015 as esm2015

Compiling @angular/cdk/bidi : es2015 as esm2015

Compiling @angular/material/core : es2015 as esm2015

Compiling @angular/material/button : es2015 as esm2015

Compiling @angular/cdk/collections : es2015 as esm2015

Compiling @angular/cdk/scrolling : es2015 as esm2015

Compiling @angular/cdk/portal : es2015 as esm2015

上記のようになにかのCompilingがいっぱい走りましたが、起動は問題なくできました。

Unitテストの確認

Unitテストは最初動かなかったです、下のような感じになりました。

f:id:tetsufuru:20200215200334p:plain f:id:tetsufuru:20200215200351p:plain

このエラーは「hammerjs」によるもので「test.ts」でインポートしていたhammerjsを消すと治まりました。
下記の差分のような感じです。
f:id:tetsufuru:20200215212052p:plain

これまでAngularのUnitテストはhammerjsに依存していましたが、今回のバージョンでそれをなくしたそうです。
そのため手動で「test.ts」からhammerjsのインポート文を消す必要があるようです。
https://github.com/angular/components/blob/master/guides/v9-hammerjs-migration.md

E2Eテストの確認

E2Eテストは問題なく動きました。
これで今日のミッション完了ですな✨

今日の感想


年末から年始にかけてずっとAngularSpring Bootをさわっていたのですが、ブログに書く機会がなく、久しぶりの投稿になりました。
他に書きたいことがだいぶ溜まっているので忘れないうちに書かねばと思いました😎

余談になりますが、Unitテストの時に使う、#TestBed.get()メソッドがDeprecatedになっていました、ブログを書き終えた後に気づきました。
今後は#TestBed.inject()を使いましょうー。

では、本年もよろしくお願いします。

今夜は社内AWSもくもく会2 - Route53でDNSを設定する😊

今日すること

こんにちはふるてつです。

前回勉強したS3に引き続き、今回はRout53を利用してDNSを設定します。
今日はそのお話になります。

ドメインを取得する

本によると作業を進めるためには、まず独自のドメインを取得しないとなりません。
安い所を探そうと思っていましたが、とある勉強会で独自ドメインのクーポン券をいただいたのを思い出しました。
GMOぺパポさんの「MuuMuuDomain」という名前のサービスです。
https://muumuu-domain.com/
下はそのクーポン券です。
今日はこれを使います、1年間だけ無料で登録できます。

f:id:tetsufuru:20191120191841p:plain:w400

f:id:tetsufuru:20191120191949p:plain:w400

ムームーさんのサイトに行ってみます🍉 f:id:tetsufuru:20191120192501p:plain

取得したいドメインを検索します。
取得可能であれば、値段が表示され出てきますので、カートに追加します。
f:id:tetsufuru:20191120193750p:plain

カートに追加したあとそのまま購入します。
f:id:tetsufuru:20191120194037p:plain

f:id:tetsufuru:20191120194546p:plain クーポンを使用するので無料ですが、クレジットカード番号は入れねばなりません。
オプションなどは選択しません。
途中でクーポンコードを登録する箇所があるので忘れずに入力します。

f:id:tetsufuru:20191120195055p:plain

f:id:tetsufuru:20191120195152p:plain 料金を確認して「取得する」ボタンをクリックします。

f:id:tetsufuru:20191120195431p:plain いったんこれで完了🥝

Rout 53の設定

さあそれではAWSの作業に入ります。

f:id:tetsufuru:20191120195605p:plain Rout53をメニューから選択します。

f:id:tetsufuru:20191120195713p:plain なんだかすごそうな画面が出てきました、新しい画面を開くときはいつもすこしワクワクしますねー
では「DNS管理」を選びます。

f:id:tetsufuru:20191120195951p:plain 上記画面で「ホストゾーンの作成」をクリックします。

f:id:tetsufuru:20191120200224p:plain もう1回、「ホストゾーンの作成」をクリックします。

f:id:tetsufuru:20191120200516p:plain 画面右側にホストゾーンの作成欄が表示されますので、「ドメイン名」、「コメント」を入力します。
わたしは外部で独自ドメインを取得していますので「タイプ」はパブリックホストゾーンを選択します。 準備ができたら「作成」ボタンをクリックします。

f:id:tetsufuru:20191120201042p:plain 上記のように、作成できました。
次に「レコードセットの作成」をクリックします。

f:id:tetsufuru:20191120201710p:plain 「レコードセットの作成」をクリックすると上記の画面右側に、レコードセットの作成画面が表示されます。
そこで「名前」欄は"www'にします。「タイプ」は"CNAME"にします(書籍の通り)
「値」欄には前回作成したS3バケットのエンドポイントを入力します。 エンドポイント先頭の "http://" は画面上に書いてあるコメントに合わせて消しました(書籍では消していません)

f:id:tetsufuru:20191120202508p:plain これで作成完了🍊

独自ドメイン側での設定

上記の作業で終わりかと思っていたところ正しく動きませんでした🤢
もくもく仲間の同僚に聞いたところ、独自ドメイン側で設定が必要だそうです。
良く考えるとそれはそうですね。ムームー側がなにも知らないでつながるわけがありません。
そこで下記のサイトを参考に追加で設定しました。
http://webfood.info/muumuu-domain-for-amazon-s3/

もう一度ムームードメインに戻ります。 コントロールパネルを開きます。 f:id:tetsufuru:20191120203312p:plain コントロールパネルの下の方に「ネームサーバ」1~4の欄があります。
そこにRout53側のネームサーバを登録します。
Rout53側のネームサーバは下記画面でレコードセットのタイプ "NS"をクリックすると、画面右側の「値」欄に表示されます。
それらをムームードメインに登録する感じになります。
f:id:tetsufuru:20191120204815p:plain

f:id:tetsufuru:20191120205454p:plain 設定変更が完了しました。
ではS3のエンドポイントではなく、独自のドメインの方から呼び出してみます。
f:id:tetsufuru:20191120205639p:plain S3に置いてある index.html が表示されました。
「ようこそ!!」しか画面には出ませんが、これで繋がるようになりました。

感想


今回は自分独自のドメインを作ってのRout53でした。
こういう勉強をしていなければドメインを作るようなことはなかったと思います。
おじさんになっても知らないことは多いですねぇ。 まあ、あせらずその都度勉強していくしかないかと思う今週でした。

それではまた😎

今度は客先でコンテナ勉強会

今日すること

f:id:tetsufuru:20190806104529p:plain:w100
こんにちはふるてつです。

ただいま客先の勉強会にてDockerを勉強中で、まずは入門としてこちらのサイト「入門Docker」を勉強しております。
https://y-ohgi.com/introduction-docker/
今回は少なめですが、「プロダクションでの活用」 メニューの中から「デバッグ」ページをもくもくとしました。
「設計」と「セキュリティ」のところは読むだけで終わりましたので 今日は「デバッグ」です。 またいつものように勉強した内容を書きます。

1. コンテナのライフサイクル

f:id:tetsufuru:20191010180504p:plain ライフサイクルを意識することは重要だそうです。
コンテナの状態の確認方法をまずは勉強していきます。

1-1. 短命なコンテナ

docker runpythonで"hoge"と出力するコンテナを起動してみます。
"hoge"と表示しただけで終わる、これはたしかに短命ですな。

docker run python python -c "print('hoge')"
hoge

現在のホスト上の全てのコンテナを見ます。

docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
fc4be9b622af        python              "python -c print('ho…"   11 seconds ago      Exited (0) 9 seconds ago                       elastic_hypatia

STATUSが "Exited (0) 9 seconds ago" となっています、もう終わってますね。
もう1回print('hoge')を実行してみます、そしてコンテナを確認します。

docker run python python -c "print('hoge')"
hoge
PS C:\Users\ts-tetsuji.furukawa> docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS               NAMES
20fa58fd83b0        python              "python -c print('ho…"   5 seconds ago       Exited (0) 3 seconds ago                       interesting_heyrovsky
fc4be9b622af        python              "python -c print('ho…"   5 minutes ago       Exited (0) 5 minutes ago                       elastic_hypatia

今度は2つになりました。
実行される度に新しい環境 が立ち上がるようです。
以前立ち上げたコンテナを動かしたいときはdocker start -a [CONTAINER ID]コマンドを実行すると良いそうです、なるほど。

docker start -a 20fa58fd83b0
hoge

1-2. 長命なコンテナ

Webサーバのように起動し続けるタイプのコンテナを見てみます。
docker run -P -d nginx

コンテナの状態を確認してみます。

docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                   NAMES
1a8b6df0862c        nginx               "nginx -g 'daemon of…"   53 seconds ago      Up 52 seconds               0.0.0.0:32768->80/tcp   cocky_cerf
20fa58fd83b0        python              "python -c print('ho…"   20 minutes ago      Exited (0) 10 minutes ago                           interesting_heyrovsky
fc4be9b622af        python              "python -c print('ho…"   26 minutes ago      Exited (0) 26 minutes ago                           elastic_hypatia

STATUSが "Up 52 seconds" となっています、これはまだ動いてますね。
nginxをもう一つ起動します。
nginxが下記のように一つ増えました。

docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS                   NAMES
31035305c436        nginx               "nginx -g 'daemon of…"   5 seconds ago       Up 4 seconds                0.0.0.0:32769->80/tcp   awesome_murdock
1a8b6df0862c        nginx               "nginx -g 'daemon of…"   16 minutes ago      Up 16 minutes               0.0.0.0:32768->80/tcp   cocky_cerf
20fa58fd83b0        python              "python -c print('ho…"   36 minutes ago      Exited (0) 26 minutes ago                           interesting_heyrovsky
fc4be9b622af        python              "python -c print('ho…"   41 minutes ago      Exited (0) 41 minutes ago                           elastic_hypatia

では一旦これまでに起動したコンテナを削除します。
まずコンテナを停止します。

docker stop $(docker container ls -q)

次にコンテナを削除します。

docker rm $(docker container ls -aq)

そしてイメージを削除します。

docker rmi $(docker images -q)

と参考にしたサイトには書いてあります。
しかし全部消すのであれば、わたしとしては下のコマンドで一掃したいですね。
これは使われていないコンテナやイメージを一気に消してくれます。

docker system prune

あとは残ったコンテナやイメージを最初のコマンドで停止・削除します。

2. コンテナの中に入る

起動したコンテナの中に入ることも可能です。
これまでの作業ですべてのコンテナを削除してしまいましたので、もう一度ngnxを起動します。
ホスト側のブラウザからnginxにアクセスしたいのでポートを設定しておきます。
docker run -d -p 8080:80 nginx

run -d -p 8080:80 nginx
fa94b6da6e0e63abc33eca78e061c5921f24f7aee3b07b498a16df0a946874af
PS C:\Users\tetsuji> docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
fa94b6da6e0e        nginx               "nginx -g 'daemon of…"   14 minutes ago      Up 14 minutes       0.0.0.0:8080->80/tcp   xenodochial_merkle

2-1. 起動したコンテナの中に入る

下のコマンドを実行すると先ほど起動したコンテナに入ることができます。
コンテナIDを指定してbashで入ります。
docker exec -it {Container ID} bash

例えばnginxファイルの存在を確認してみます。
たしかにnginxのファイルが見えますね。

docker exec -it 0b3901f01b82 bash
root@0b3901f01b82:/# ls /usr/share/nginx/html/
50x.html  index.html

あとコマンドのインストールなどもしてみます。

root@fa94b6da6e0e:/# apt-get update
root@fa94b6da6e0e:/# apt-get install -y curl procps

実際nginxが起動しているか、 localhost:80 へアクセスしてみます。
動いてますねー
f:id:tetsufuru:20191109004034p:plain

下記のようにnginxのindex.htmlを変えることもできます。

root@fa94b6da6e0e:/# echo "<h1>hello docker</h1>" > /usr/share/nginx/html/index.html
root@fa94b6da6e0e:/# cat /usr/share/nginx/html/index.html
<h1>hello docker</h1>

書き変わりました。
f:id:tetsufuru:20191109005247p:plain

docker logs {Container ID}コマンドでログを見ることも可能です。
起動したプロセスの 標準出力と標準エラーを見ることができるそうです。

docker logs fa94b6da6e0e
172.17.0.1 - - [08/Nov/2019:15:21:57 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" "-"
2019/11/08 15:21:58 [error] 7#7: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: 
"localhost:8080", referrer: "http://localhost:8080/"
172.17.0.1 - - [08/Nov/2019:15:21:58 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://localhost:8080/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" "-"
172.17.0.1 - - [08/Nov/2019:15:52:18 +0000] "GET / HTTP/1.1" 200 22 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36" "-"

2-2. 停止したコンテナの中に入る

最後は停止したコンテナから新たにイメージを作る方法です。
amazonlinux2を起動して/tmp/中にhogeフォルダを作ります。
コマンドが終わればコンテナは停止します。
docker run amazonlinux:2 mkdir /tmp/hoge

docker run amazonlinux:2 mkdir /tmp/hoge
Unable to find image 'amazonlinux:2' locally
2: Pulling from library/amazonlinux
17282fad1a5e: Pull complete
Digest: sha256:5aa0460abffafc6a76590f0070e1b243a93b7bbe7c8035f98c1dee2f9b46f44c
Status: Downloaded newer image for amazonlinux:2
PS C:\Users\tetsuji> docker container ls -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
8f67ae93d41c        amazonlinux:2       "mkdir /tmp/hoge"   18 seconds ago      Exited (0) 17 seconds ago                       youthful_edison

docker commitコマンドで停止したコンテナからexited-containerという名前の新たなイメージを作ります。

docker commit 8f67ae93d41c exited-container
sha256:de46c9f656fcaab46266762d07524ab42a4b68e6e70a928ac2008bd9277fff32

新たなイメージを起動して、コンテナ内で作成したディレクトリが存在するかどうか確認します。

docker run -it exited-container bash
bash-4.2# ls /tmp/
hoge

たしかにありました。

今日の感想


今回もまた「入門Docker」の内容をそのまま、まとめた内容になりました。
基本的にはデバッグの仕方を練習してみた感じでした。
Docker初心者なのでどういったデバッグのシチュエーションがあるのか、あまり想像がついていませんがなんとなく一通りのことはできそうかなと思いました。

それではまた✨

今夜は社内AWSもくもく会2 - やっとS3まできたぞ😊

今日すること

こんにちはふるてつです。

前回はElastic Beanstalkについてでしたが、 今回はS3について書きます。

いままでのおさらいになりますが、わたしは客先の仕事が終わった後定期的に、自社に戻ってAWS環境を勉強しています。
基本的には下記の本のとおりにさわっているだけです。
f:id:tetsufuru:20191106182429p:plain:w400

いま半分を少し過ぎたところで、やっとS3まで来ました。
f:id:tetsufuru:20191106182456p:plain:w400

S3について余談ですが、わたしこれまでに仕事で使ったことはありますね。
例えば、JavaでデータをS3にアップロードしたり、S3から読み込んだり、消したり消さなかったり。
しかし実は空の状態から作ったことがなかったので、ちょうどいい勉強になるかなと思っています。

S3バケットの作成

ではまずS3バケットを作っていきます。
サービスの「S3」から「バケット」で下の画面が表示されます。
バケットを作成する」をクリックします。
f:id:tetsufuru:20191106185252p:plain

f:id:tetsufuru:20191106185428p:plain 上記の画面が出てきます。

f:id:tetsufuru:20191106190021p:plain 必要な項目を入力します。
バケット名」は任意の名前で構いません、しかしS3全体の中で一意なので、 他と被らないような名前にします。
「リージョン」はアジアパシフィック(東京)にして「次へ」をクリック。

f:id:tetsufuru:20191106190854p:plain 上記の画面はそのまま「次へ」をクリックします。

f:id:tetsufuru:20191106191050p:plain 上記はアクセス許可の設定、こちらもデフォルトのまま「次へ」
(アクセス許可をここで設定して構いません、しなかったのは本がデフォルトだったからです)

f:id:tetsufuru:20191106191425p:plain 最後に「バケットを作成」をクリック。
バケットが出来ました。

f:id:tetsufuru:20191106191631p:plain

ブロックパブリックアクセスの設定

それでは次に「アクセス権限」タブ ⇒ 「ブロックパブリックアクセス」で、アクセス権限の設定をおこないます。
f:id:tetsufuru:20191106195553p:plain 現在はすべてのアクセスがブロックされていますので、右横の「編集」をクリックして設定を変更します。

f:id:tetsufuru:20191106195701p:plain 上記画面中の下2つ、「パケットポリシー」に関するブロックをoffにして保存します。

バケットポリシーの設定

次に下記画面にて「パケットポリシー」をクリックします。
f:id:tetsufuru:20191106200320p:plain 画面中央のパケットポリシーエディターにてパケットポリシーを設定します。
設定はjsonで行います、内容は下記です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AddPerm",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::furutest1234567/*"
        }
    ]
}

jsonのサンプルは下記を参考にしましたが、なかなか書くのがめんどくさいですねぇ。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/example-bucket-policies.html
"Effect": "Allow"、"Principal": "*"なので内容的にはすべてのユーザーに許可を与えています。
"Version": "2012-10-17"はこのままにしておきます。
最新でいいかと思って今日の日付に変えたらエラーになりました🤢
f:id:tetsufuru:20191106200526p:plain 上記のようにjsonを入力後「保存」します。

Static website hostingの設定

このバケットを静的サイトとして使用するための設定を行います。
「プロパティ」のタブをクリックします。
f:id:tetsufuru:20191106204452p:plain

「Static website hosting」を選択すると下記の画面になります。 画面の上の方に「エンドポイント」が表示されています。
ユーザーがS3にアクセスする際はこのエンドポイントを指定します。
f:id:tetsufuru:20191106230411p:plain そして上記のように「インデックスドキュメント」に"index.html"、
「エラードキュメント」に"error.html"を入力します。
「インデックスドキュメント」はその名の通り、サイトを訪れたユーザーに最初にアクセスさせたいページです。
「エラードキュメント」はエラー時に表示させたいページになります。

コンテンツのアップロード

上記で登録した"index.html"と"error.html"をアプロードします。 f:id:tetsufuru:20191106205349p:plain S3の画面に戻って「アップロード」ボタンをクリックします。

f:id:tetsufuru:20191106205525p:plain 2ファイルを選択してアップロードします。

f:id:tetsufuru:20191106205804p:plain アップロードが完了しました。
ではエンドポイントにアクセスしてみます。
"index.html"の画面が表示されます。
f:id:tetsufuru:20191106233247p:plain 次は存在しないファイルを指定します。 f:id:tetsufuru:20191106233347p:plain 今度はエラー画面が表示されました、なるほど。

リダイレクトルールの設定

次にリダイレクトルールを設定します。
「Static website hosting」画面の下の方のエリアです。
f:id:tetsufuru:20191106231822p:plain リダイレクトの内容は下記になります。

<RoutingRules>
  <RoutingRule>
    <Condition>
      <KeyPrefixEquals>foo/</KeyPrefixEquals>
    </Condition>
    <Redirect>
      <ReplaceKeyPrefixWith>bar/</ReplaceKeyPrefixWith>
    </Redirect>
  </RoutingRule>
</RoutingRules>

上記で"foo/"フォルダへのアクセスは"bar/"にリダイレクトされます。
S3に"bar"フォルダを作成し、そこにリダイレクトのテスト用のhtmlをアップロードします。
f:id:tetsufuru:20191106234352p:plain

試しにxxxx/foo/redirect_test.htmlにアクセスしてみます。
下記のようにxxxx/bar/redirect_test.htmlが表示されました。
f:id:tetsufuru:20191106234818p:plain

RoutingRuleは複数追加できます。
例えば下記ですが、404エラー時にのみ指定したhtmlを表示するルールを追加しました。

<RoutingRules>
  <RoutingRule>
    <Condition>
      <KeyPrefixEquals>foo/</KeyPrefixEquals>
    </Condition>
    <Redirect>
      <ReplaceKeyPrefixWith>bar/</ReplaceKeyPrefixWith>
    </Redirect>
  </RoutingRule>
  <!-- 追加 -->
  <RoutingRule>
    <Condition>
      <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals>
    </Condition>
    <Redirect>
      <ReplaceKeyWith>404.html</ReplaceKeyWith>
    </Redirect>
  </RoutingRule>
  <!-- 追加 -->
</RoutingRules>

最後にこちらも試してみます。
また存在しないアドレスを指定します。
今度はエラー画面(error.html)ではなく、404用のエラー画面(404.html)が表示されました。
f:id:tetsufuru:20191106235957p:plain

感想


S3を空の状態から作ってみましたが、いざやってみるとわからないことだったり、新たに知ることがあったりでわりと新鮮でした。
リダイレクトの機能がすこし面白かったですねー。
次はS3に加えてRoute53が登場します。
それまでに自分のドメインを取っておいたほうが良いそうですね。どこか安いドメインをさがさないと~。

それではまた😎