技術メモ

プログラミングとか電子工作とか

AWS LambdaをTypeScriptで開発する

f:id:ysmn_deus:20190318182949p:plain

JavaScriptはオワコン!とまでは言いたくないんですが、やはり普段からTypeScriptを利用していると極力JavaScriptは避けたいところ。
ということで基本JavaScriptで開発されているであろうAWS LambdaのNode環境ですが、TypeScriptで開発を始めるまでをまとめておきます。
Serverless Frameworkを使えば比較的容易にTypeScript環境を構築できるようですが、またそれは別途試してみます。
今回はSAM CLIで対応します。
(デプロイはJavaScriptと変わらないので割愛します)

SAM開発環境を整える

SAM CLIのインストール

まずはSAM CLIをインストールする必要があります。
Dockerのインストールも必要になってきますが、基本的には公式ドキュメント(Installing the AWS SAM CLI)に一通り書いてあるのでそちらを参照して下さい。

最終的に

sam --version

がきちんと動作すれば問題ないと思います。

(補足)IAMユーザー情報の登録

AWS CLIを普段利用していない方などはIAMユーザーの認証情報をPCにセットアップする必要があります。
AWSを利用していてLambdaだけしか使わない人も珍しいと思いますので、詳細は割愛しますが公式ドキュメント(Setting Up AWS Credentials)AWS CLIを利用しない認証情報のセットアップも掲載されておりますのでご参照ください。
個人的にはAWS CLIをセットアップすることをオススメしますが。

SAM CLIでプロジェクトを作成する

SAMでプロジェクトを作成します。
とりあえずはHello World的なものを生成する所まではJavaScriptで対応します。

sam init --runtime nodejs10.x --name hello-sam

上記コマンドのオプションをとりあえず説明しておきますと、
--runtime:Lambdaの実行ランタイムを選択します。(python3.7とか)ホントはv12を使っていきたいんですが、一応安牌をとってv10で進めて行きます。
--name:プロジェクトのディレクトリ名です。Lambdaの関数名などは別途修正する必要があります。

上記コマンドを実行すると対話形式でウィザードが進みます。
とりあえず今回は1, 1をチョイスしました。

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
        1 - Hello World Example
        2 - Quick Start: From Scratch
        3 - Quick Start: Scheduled Events
        4 - Quick Start: S3
        5 - Quick Start: SNS
        6 - Quick Start: SQS
        7 - Quick Start: Web Backend
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: hello-sam
Runtime: nodejs10.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./hello-sam/README.md

S3などの処理を書きたいときはその辺を選んだりするのが良いんでしょう。
とりあえず今回は無難なチョイスを選択しましたが、ウィザードが完了すると--nameで指定したディレクトリにファイルが出力されていると思います。

テストを実行する

SAM CLIでセットアップすると、mocha+chaiがセットアップできるようになっています。とりあえず走らせる為にpackage.jsonの情報でセットアップします。

cd .\hello-sam\
cd .\hello-world\
yarn

ドキュメントではnpmコマンドを利用している例が多いですが普段はyarnを利用しているのでそのまま使います。
基本問題無いのでそのままテストを走らせます。

yarn test

yarn run v1.22.0
$ mocha tests/unit/


  Tests index
    √ verifies successful response


  1 passing (7ms)

Done in 0.34s.

問題無さそうです。

ローカルで関数を実行する

SAMを利用すればローカルで関数を実行するのは非常に簡単です。
一応sam local invokeで関数を実行する方法と、sam local start-apiで実行する2種類の方法がありますが、大体後者かと思いますので後者で動かしてみます。
(Dockerが起動してないと怒られますので起動しておきましょう)

cd ..
sam local start-api

問題が無ければブラウザでhttp://127.0.0.1:3000/helloを入力するか、curlコマンドでGETリクエストを投げると{"message":"hello world"}が返ってきます。
基本的にJavaScriptのファイル群はここで不要となりますので、hello-worldディレクトリはこの段階で削除してしまって構いません。

ちなみに、Dockerをセットアップしただけの状態だとC:\Users以下以外のディレクトリは権限がなくてマウントされないので、プロジェクトのディレクトリがC:\Users以下でない場合はsam local Error: Cannot find module 'app' docker toolboxのようなエラーが発生します。
対処法としましてはDokcer Toolboxでは下記のサイトを参考にさせていただきました。

coffee-nominagara.com

ToolboxでないDokcerでは警告が出てきちんと設定していた気がします。

TypeScript

tsconfig.jsonの作成

まずはプロジェクトのルートディレクトリにtsconfig.jsonを作っておきます。
特にこだわりのない人は

tsc --init

tsconfig.jsonを作成するのが良いと思います。
一応今回用意しているtsconfig.jsonは下記の通りにしました。

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "outDir": "./built",
    "allowJs": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "esModuleInterop": true
  },
  "include": [
    "./src/**/*"
  ],
    "exclude": ["node_modules"]
}

入力対象を./src/以下、出力先を./builtにしています。

パッケージを追加

TypeScriptを使っているので型が必要です。開発用の型をまずは追加していきます。

yarn add -D @types/aws-lambda

最小限のパッケージはこれだけでOKです。

index.tsを編集

index.tsを編集します。基本的にはデフォルトのapp.jsを踏襲して作成してみます。

import {APIGatewayEvent, Context} from "aws-lambda"

const lambdaHandler = async (event: APIGatewayEvent, context: Context) => {
    let response: any
    try {
        response = {
            'statusCode': 200,
            'body': JSON.stringify({
                message: 'hello ts world',
            })
        }
    } catch (err) {
        console.log(err)
        return err
    }

    return response
}

export {lambdaHandler}

とりあえずという意味ではeventcontextanyで受けてしまえばいいんですが、まぁそんぐらいは・・・
これでコンパイルしてみます。tsconfig.jsonを設定してるのでルートディレクトリでtscコマンドを実行します。

tsc

これでbuiltディレクトリが作成され、index.jsが生成されたかと思います。

template.yamlの修正

JavaScriptの時との差異はコンパイル後のファイル名ファイルの場所です。
これをtemplate.yamlに修正していきます。

~
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: built/
      Handler: index.lambdaHandler
~

CodeUriHandlerを修正しました。

ローカルで関数を実行する

これでローカル関数を実行してみます。

sam local start-api

JavaScriptの時と同様にブラウザかcurlコマンドで確認してみて下さい。
返ってくるのが{"message":"hello world"}のままの人はもしかするとsam buildして.aws-samディレクトリに履歴が残ってる可能性がありますので消しちゃって下さい。
無事{"message":"hello ts world"}が返ってきたら最低限度の環境構築はできたのではないでしょうか。

その他

ESLint + Prettier

基本的にはReactの設定と同じなのですが、ReactのJSXなどは不要です。
一応eslintrcなどを掲載しておきます。

パッケージの追加

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -D prettier @types/prettier @types/eslint-plugin-prettier eslint-config-prettier eslint-plugin-prettier

eslintrc.js

module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    sourceType: "module"
  },
  plugins: [
    'prettier',
    '@typescript-eslint'
  ],
  extends: [
    "eslint:recommended",
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint'
  ],
  rules: {
    'prettier/prettier': [
      'error',
      {
        'semi': false,
        'trailingComma': "all"
      }
    ]
  },
};

.eslintignore

node_modules/
*.config.js
built/

webpack

今回はwebpack+ts-loaderで構成します。

パッケージの追加

yarn add -D ts-loader webpack webpack-cli dotenv

今回は.env環境変数を管理する想定でdotenvを利用しています。
直で書いたりする場合はこの辺は必要無いと思います。
とりあえず.envを作っておきます。

NODE_ENV=development

webpack.config.js

const path = require("path")
const dotenv = require("dotenv")
dotenv.config()

const BUILT_PATH = path.resolve(__dirname, "./built")
const BUILD_VARIANT = process.env.NODE_ENV

module.exports = {
  target: "node",
  mode: BUILD_VARIANT === "production" ? "production" : "development",
  resolve: {
    extensions: [".ts", ".js"],
  },
  entry: "./src/index.ts",
  output: {
    filename: `${BUILD_VARIANT}.js`,
    path: BUILT_PATH,
    library: "[name]",
    libraryTarget: "commonjs2",
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: "ts-loader",
      },
    ],
  },
}

これで、ファイルを分割して開発することができます。

React Hook Form入門 その2(基本編)

f:id:ysmn_deus:20200224171811p:plain

前回の続きです。

基本的な使い方

バリデーションなどを無視するなら、基本的にはフォームの要素のref属性にuseFormから得られるregister関数を登録し、formのonSubmithandleSubmitを登録するだけで利用できます。
あえてフォームを作る順序を書くなら

  1. フォームの要素を書き出す
  2. フィールドを登録する
  3. スタイリング(ここでは取り扱わない)

という流れになるかと思います。最初から順にやっていきます。

フォームの要素を書き出す

基本的には普通のHTMLとなんら変わり無い要素をReactで表現します。

import React from "react"

export default function App() {
  return (
    <form>
      <input name="firstName" />
      <select name="gender">
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
      <input type="submit" />
    </form>
  )
}

f:id:ysmn_deus:20200224173934p:plain

非常にシンプルなフォームですが、今回はこれに機能を実装していきます。

フィールドを登録する

ここでは

  • input(name)
  • select(gender)

という2種類のフォームの要素があります。これらをReact Hook Formのフィールドに登録していきます。

import React from "react"
import { useForm } from "react-hook-form" // useFormをreact-hook-formからインポート

export default function App() {
  const { register, handleSubmit } = useForm() // register, handleSubmitをuseFormというHooksから得る
  const onSubmit = (data: any) => console.log(data) // フォームが送信されたときに実行される関数

  return (
    // form要素にhandleSubmitを渡す。なお、引数にはフォーム送信時に実行される関数を渡す。
    <form onSubmit={handleSubmit(onSubmit)}>
      {/* input 要素にref属性を付与、registerを渡す*/}
      <input name="firstName" ref={register} />
      {/* select 要素にref属性を付与、registerを渡す*/}
      <select name="gender" ref={register}>
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
      <input type="submit" />
    </form>
  )
}

基本的にはこれだけでフォームが利用可能です。
フォームのデータを利用してなにがしかしたいときはonSubmitの中に処理を書いていきます。

心がTSに支配されてる方は(data: any)で発狂しそうかと思いますが、後で言及するので我慢して下さい。僕も発狂寸前なんです。

非同期処理

おそらくフォームの送信の後にはAPIと通信したりするので非同期処理が来ると思います。
上記のhandleSubmitの引数にはPromiseも渡せるので、async関数がそのまま渡せます。

import React from "react"
import { useForm } from "react-hook-form"

const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))

export default function App() {
  const { register, handleSubmit } = useForm()
  const onSubmit = async (data: any) => {
    console.log(data)
    await sleep(1000)
    console.log("complete.")
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register} />
      <select name="gender" ref={register}>
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
      <input type="submit" />
    </form>
  )
}

バリデーションを適応する

基本的には上記までで良さそうなものですが、バリデーションぐらいはフロントでもやっておくれ、という気持ちが湧いてきます。
React Hooks FormはHTML 標準のフォームバリデーションのバリデーションには対応しているようです。
対応しているルールは公式サイトを閲覧してもらえればと思いますが、大雑把に言えば

などのルールを適応し、バリデーションに通らない場合は即座にエラーを得ることができるようです。

import React from "react"
import { useForm } from "react-hook-form"

export default function App() {
  const { register, handleSubmit, errors } = useForm()
  const onSubmit = (data: any) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="firstName" ref={register({ required: true })} />
      {errors.firstName && "First name is required"}
      <input name="lastName" ref={register({ pattern: /^[A-Za-z]+$/i })} />
      {errors.lastName && "Last name is not mached [A-Za-z]"}
      <input name="age" type="number" ref={register({ min: 18, max: 99 })} />
      {errors.age && "age is in ragnge 18-99"}
      <input type="submit" />
    </form>
  )
}

errors.の後にname属性で決めたプロパティを参照するとバリデーションのルールを適応した可否が得られます。
上記の例ではboolean &&の形で、もしバリデーションが通ってなければ警告文を表示する様な形式にしています。
バリデーションに適合していなければ、送信してもonSubmitが実行されません。

UIライブラリの利用

一応方法としては

  1. 対応するUIライブラリ(material-uiなど)を利用する
  2. Controllerを利用してカスタム登録処理をする
  3. useEffectを利用する

の3種類ありますが、自分は基本的にmaterial-uiしか利用しないので上記2点にのみ言及します。

対応するUIライブラリを利用する

これが最も簡単です。material-uiを利用している人は、属性を追加するだけで利用できます。
まずはmaterial-uiをインストールします。

yarn add @material-ui/core

もうこれでフォームが作成可能です。
ソースを変更していきます。

import React from "react"
import { useForm } from "react-hook-form"
import TextField from "@material-ui/core/TextField"

export default function App() {
  const { register, handleSubmit } = useForm()
  const onSubmit = (data: any) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField inputRef={register} label="First name" name="firstName" />
      <input type="submit" />
    </form>
  )
}

これで、TextFieldコンポーネントに入力された値がonSubmitdataの中に放り込まれます。

Controllerを利用してカスタム登録処理をする

テキストフィールドやチェックボックスぐらいであれば上記の処理で問題無いんですが、ラジオボタンなどのフォーム要素が制御されたもの等を利用する場合は上記の用にはいきません。
例えば、ラジオボタンの例を出せば

import React, { useState } from "react"
import { useForm } from "react-hook-form"
import {
  TextField,
  FormControl,
  FormLabel,
  FormControlLabel,
  Radio,
  RadioGroup,
} from "@material-ui/core"

export default function App() {
  const { register, handleSubmit } = useForm()
  const onSubmit = (data: any) => console.log(data)
  const radioList: string[] = ["male", "female", "other"]
  const [value, setValue] = useState(radioList[0])
  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue((event.target as HTMLInputElement).value)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField inputRef={register} label="First name" name="firstName" />
      <FormControl>
        <FormLabel>Gender</FormLabel>
        <RadioGroup name="gender1" value={value} onChange={handleChange}>
          {radioList.map(item => (
            <FormControlLabel
              key={item}
              value={item}
              control={<Radio />}
              label={item}
            />
          ))}
        </RadioGroup>
      </FormControl>
      <input type="submit" />
    </form>
  )
}

といった場合にはinputRefregisterを登録すればOKという訳にもいきません。
そこで、React Hook FormにはControllerというものが用意されています。

import React from "react"
import { useForm, Controller } from "react-hook-form"
import {
  TextField,
  FormLabel,
  FormControlLabel,
  Radio,
  RadioGroup,
} from "@material-ui/core"

const radioList: string[] = ["male", "female", "other"]

export default function App() {
  const { register, handleSubmit, control } = useForm()
  const onSubmit = (data: any) => console.log(data)

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <TextField inputRef={register} label="First name" name="firstName" />
      {/* 上記と同じ、Controllerを利用してTextFieldを使い場合の例 */}
      <Controller
        name="firstName2"
        as={TextField}
        control={control}
        defaultValue=""
      />
      {/* FormControlのセクションを<div></div>と想定 */}
      <div>
        <FormLabel>Gender</FormLabel>
        <Controller
          name="radioGroup"
          as={
            <RadioGroup name="gender1">
              {radioList.map(item => (
                <FormControlLabel
                  key={item}
                  value={item}
                  control={<Radio />}
                  label={item}
                />
              ))}
            </RadioGroup>
          }
          control={control}
          defaultValue={radioList[0]}
        />
      </div>

      <input type="submit" />
    </form>
  )
}

やや複雑になりましたが、<FormControl></FormControl>の例と比べても、そこまで記述量が増えていないにもかかわらずフォームデータのやりとりができるのが確認できます。
なお、ここではしれっとdefaultValueを指定していますが、こいつを指定していないとMaterial-UIはコンパイル時にエラーを吐きます。
(たぶんa component is changing an uncontrolled radiogroup to be controlled react hook formみたいなやつ)
フォームの要素を記述する時にdefaultValue={}として値を指定するか、useFormの歳に引数に初期値を入力するかのどちらかで対応して下さい。

TypeScriptの対応

上記までは、onSubmitにany型をたたき込んだりとTypeScriptの良さを出し切れてない使い方でしたが、useFormに型を適応することでuseFormから得られるsetValueerrorsに型を適応することができます。

import * as React from "react"
import { useForm } from "react-hook-form"

// フォームにあるフィールドの型の定義
type FormData = {
  firstName: string
  lastName: string
}

export default function App() {
  const { register, setValue, handleSubmit, errors } = useForm<FormData>() // フィールドの型定義をuseFormに適応する
  // handleSubmitの引数にはフィールドの型定義が適応されている
  const onSubmit = handleSubmit(({ firstName, lastName }) => {
    console.log(firstName, lastName)
  })

  return (
    <form onSubmit={onSubmit}>
      <label>First Name</label>
      <input name="firstName" ref={register} />
      <label>Last Name</label>
      <input name="lastName" ref={register} />
      <button
        type="button"
        onClick={() => {
          // setValueにも型定義が適応されている
          setValue("lastName", "luo") // OK
          setValue("firstName", true) // コンパイルエラー
          errors.bill // errorsにも型定義が適応されているのでコンパイルエラー
        }}
      >
        SetValue
      </button>
    </form>
  )
}

React Hook Form入門 その1(導入編)

f:id:ysmn_deus:20200224171811p:plain

去年はNuxt.jsやNext.jsなど色々触ってたんですが、結局プレーンなReactが扱いやすいんじゃないかと思い色々試行錯誤しています。(Nextは結構良い感じだったんですが、Amplifyとの相性が悪くて不採用となりました)
基本的にはReact + Reduxの組み合わせが便利だと思い利用していたのですが、極力React Hooksを利用した方がシンプルな状態管理ができそうなので(きちんと理解してないのでレンダリングのタイミングでStateを変更してしまったり・・・)フォームはRedux FormからReact Hook Formに移行しようと思います。

この記事ではReact Hook Formに慣れるためにアプリケーションを作成しながら基本的な機能を確認していきます。

初期設定

Reactのプロジェクト作成(create-react-app)

プロジェクトを作成します。この辺は基本的なReactのプロジェクト作成と同じで、create-react-appを利用します。

create-react-app . --template typescript

(コマンドはプロジェクト用のディレクトリ内で実行してます。)

とりあえず実行

まずはプロジェクトとして問題無いことを確認しておきましょう。
基本的にパッケージマネージャーはyarnを利用します。

yarn start

f:id:ysmn_deus:20200224133318p:plain
いつもの

プロジェクトとしてはOKです。
一応ガッツリ開発するわけではないですが、ESLint + Prettierの設定なども個人的によく忘れるので行っておきます。

ESLint + Prettier

ESLintのインストール

まずはESLintをインストールしていきます。
TypeScriptでの開発を想定しているので、@typescript-eslint/parser@typescript-eslint/eslint-pluginも合わせてインストールします。

yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

ESLintの設定

プロジェクトのルートディレクトリ(create-react-appした場所)に.eslintrc.jsを作成します。

module.exports = {
    env: {
        browser: true,
        node: true,
        es6: true
    },
    parser: '@typescript-eslint/parser',
    parserOptions: {
        ecmaFeatures: {
            jsx: true
        },
        sourceType: "module"
    },
    plugins: [
        "react",
        '@typescript-eslint'
    ],
    extends: [
        "eslint:recommended",
        "plugin:react/recommended",
        'plugin:@typescript-eslint/recommended',
    ],
    rules: {
    },
    overrides: [
        {
            'files': ["**/*.tsx"],
            'rules': {
                'react/prop-types': 'off'
            }
        }
    ]
};

とりあえず動けばいいのでこんなもんで。
詳しい説明とかは今回は省きます。

package.jsonにLint用のコマンドを記述します。

~
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "lint": "eslint --ext .ts,.js,.tsx,.jsx --ignore-path .gitignore ."
  },
~

ここまでできたら、試しに実行します。

yarn lint

errorとwarningが色々出てくると思いますが、eslintが動いてる証拠なので問題無いです。

Prettierのインストール

ESLintの設定ができたら次はPrettierをインストールします。
ESLintとの競合などにも対応するためeslint-config-prettiereslint-plugin-prettierも合わせてインストールしておきます。

yarn add -D prettier @types/prettier @types/eslint-plugin-prettier eslint-config-prettier eslint-plugin-prettier

Prettierの設定

基本的にESLintの補助機能として利用するため、ESLintの設定に追記します。

module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true
    },
    sourceType: "module"
  },
  plugins: [
    'prettier',
    "react",
    '@typescript-eslint'
  ],
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    'prettier/@typescript-eslint'
  ],
  rules: {
    'prettier/prettier': [
      'error',
      {
        'semi': false,
        'trailingComma': "all"
      }
    ]
  },
  overrides: [
    {
      'files': ["**/*.tsx"],
      'rules': {
        'react/prop-types': 'off'
      }
    }
  ]
};

基本的に上記の設定で動くとは思います。
試しに実行してみましょう。

yarn lint

errorやwarningがでれば問題ありません。
おそらくこの後保存されれば自動でlintが走るようにすると思いますが、lintの設定で勝手に整形するには--fixオプションを付けます。
試しに付けて実行します。

yarn lint --fix

大部分が勝手に修正されました。
ここで、直らないファイルで、errorのものは

  • App.test.tsx
  • serviceWorker.ts

の二種類かと思います。
今回テストは書かないので、App.test.tsxは削除して対応しますが、testexpectが未定義というエラーなので、おそらくeslint-plugin-jestとか入れれば直ると思います。
serviceWorker.tsに関しては、基本触らないファイルなので、該当箇所のみ無視する様に修正します。

~
        checkValidServiceWorker(swUrl, config) // eslint-disable-line @typescript-eslint/no-use-before-define
~
        registerValidSW(swUrl, config) // eslint-disable-line @typescript-eslint/no-use-before-define
~

対応後にもう一度lintを走らせます。

yarn lint

errorは消えてwarningだけになったかと思います。
warningも気になる人は対応してください。(serviceWorker.tsはファイルごと無視してもいいかもしれません。)

ESLint + Prettierのその他の設定

ファイルウォッチャーに登録する(保存したら勝手に処理する)のは、開発環境によって異なるので省きます。

TypeScript

tsconfig.json

incrementalの設定とexcludeの設定を追記しておきます。

~
    "jsx": "react",
    "baseUrl": "src",
    "incremental": true
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
~

React Hook Form

ようやく本題です。

インストール

インストールは至って簡単です。

yarn add react-hook-form

これで使用可能になります。

基本的な利用方法

試しにクイックスタートのサンプルを利用してみます。
基本的には書いてある通りでいいんですが、TypeScriptだとコンパイル時にエラーが出るので、余計なコメントなどを修正します。

import React from "react"
import { useForm } from "react-hook-form"

export default function App() {
  const { register, handleSubmit, watch, errors } = useForm()
  const onSubmit = (data: any) => {
    console.log(data)
  }

  console.log(watch("example"))

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="example" defaultValue="test" ref={register} />

      <input name="exampleRequired" ref={register({ required: true })} />
      {errors.exampleRequired && <span>This field is required</span>}

      <input type="submit" />
    </form>
  )
}

とりあえず使えるか実行して確認します。

yarn start

超シンプルですが、フォームが出てきました。

f:id:ysmn_deus:20200224144121p:plain

2個目の入力フォームが必須のようなので、適当に値を入れて「送信」を押します。

f:id:ysmn_deus:20200224144425p:plain

基本的にはformのonSubmituseFormというHookを利用して得たhandleSubmitという関数を通じて、handleSubmitに渡した関数がフォームのデータを引数に貰って実行されるようです。
また、watchという関数はフォームの変化を監視しているようで、testと記載されているフォームを編集するとconsole.log(watch('example'))が毎回発火します。

f:id:ysmn_deus:20200224144752p:plain

長くなったのでバリデーションとかはまた次回に。

EC2を使わないAWSでメール送信/受信(転送)(SES + S3 + Lambda)

f:id:ysmn_deus:20200215223446p:plain

Amazon WorkMail使えばええやんって所なんですが、無意味にメアドを増やしたい時なんかは 4.0 USD/Monthはちょい高め。
なので、勉強がてらSES、S3、Lambdaを使ったメール送受信を導入してみることにしました。
とはいえ、SESは本来送信専用なので受信はどこか普段使いのメールに転送する想定になります。

メール受信(転送)のセットアップ

まずはメールの受信から。
基本的にはAWSの公式ドキュメントを参照します。
https://docs.aws.amazon.com/ses/latest/DeveloperGuide/receiving-email-getting-started.html
AWSで痛い目に遭いたくなければ日本語ドキュメントは読まないことです。古事記にもそう書かれている。

Step 1: 初期設定

まずSESの設定とかを始める前にやっておくことが2点あります。

  • AWSのアカウントを作っておく
  • Route53にドメインを登録しておく

基本的にこれらはできてる想定で話を進めます。

Step 2: ドメインの認証

SESでドメインを利用するに当たってドメインの認証が必要になります。
とりあえずSESのコンソールに行きましょう。
https://console.aws.amazon.com/ses/

リージョンは東京がないのでとりあえずバージニア北部(us-east-1)を想定してます。
SESのコンソールを開いたら「Identity Managemen」の「Domains」に行きます。 f:id:ysmn_deus:20200214133008p:plain f:id:ysmn_deus:20200214130405p:plain

Verify a New Domain

なんもないと思うので、左上の「Verify a New Domain」をクリックします。
「Verify a New Domain」というダイアログが出てくると思うので、「Domain」にRoute53に登録したドメインを入力し、「Generate DKIM Setting」にチェックを付けて「Verify This Domain」をクリックします。
DKIMに関しては設定してなくても基本いけると思いますが、送信先によってははじかれたりするので設定しておくのが無難かな、と思います。

「Verify This Domain」をクリックすると、ダイアログの内容が少々変わってなんかでてくると思います。
f:id:ysmn_deus:20200214131648p:plain
Route53を利用しない場合は表示されているレコードをドメインに設定する必要がありますが、AWSに命を捧げている者はAWS様がよしなにしてくれます。
ドメインのレコードをRoute53を利用しない人はここで個別に設定が必要ですが、Route53ユーザーであれば迷わず「Use Route 53」をクリックしましょう。

Use Route 53

次に「Use Route 53」というダイアログが出てきます。
前の段落で設定する筈だった項目をウィザードでやってくれます。
f:id:ysmn_deus:20200214132315p:plain
初めてドメインにレコードを設定する人であれば特に気にせず「Email Receiving Record」の部分もチェック入れて良いと思いますが、もし他にメールアドレスを登録してる人などはMXレコードが上書きされてしまうので要注意です。
全てにチェックを入れて「Create Record Sets」をクリックします。
するとSESのコンソールに戻り、設定していたドメインがpendingになってるのが分かります。
f:id:ysmn_deus:20200214132724p:plain
ここで5分ほど待ちます。待ってる間にRoute53で設定したドメインのレコードに先ほどのウィザードで設定したレコードが追加されているか確認してもいいと思います。

時間がたったらたぶんverifiedになってると思います。
f:id:ysmn_deus:20200214133008p:plain

Step 3: 受信ルールの作成

Step2でドメインの認証を済ませたら、受信設定を作成します。
SESのコンソールの「Email Receiving」の項目から「Rule Sets」をクリックし、「Create a Receipt Rule」をクリックしましょう。
f:id:ysmn_deus:20200214133617p:plain

受信相手の設定

最初に出てくるのは受信対象の設定です。
もし受信するドメインを限定したりする場合はこの辺を設定しましょう。
たぶん設定する人は少ないと思うので、特になにも入力せず「Next Step」をクリックしましょう。
f:id:ysmn_deus:20200214133857p:plain

保存先の設定(Actionの設定)

メールが来たときにそのメールをどうするかの挙動(Action)を設定します。
「Add action」から「S3」を選びます。
f:id:ysmn_deus:20200214134325p:plain
f:id:ysmn_deus:20200214134336p:plain

まずはS3を用意してないと思いますので、「Create S3 bucket」を選択します。(もし既に作成済みなら該当するbucket名を選んでください)
f:id:ysmn_deus:20200214134603p:plain
適当なbucket名を入力します。
f:id:ysmn_deus:20200214140249p:plain
(email-ドメイン名、など分かりやすい名称がいいとは思います。)

prefixなど設定できますがたぶんS3のパフォーマンスを落とすだけなので無視しましょう。
「Next Step」をクリックします。

ルールの詳細設定(名前決めるだけ)

「Rule Details」の項目が出てきます。
基本的に「Rule name」の箇所だけ適当に入力して、「Next Step」で問題無いかと思います。

最終確認

今まで設定してきた項目が一覧で確認できます。
何か間違いがあればここで修正できます。大丈夫であれば右下の「Create Rule」でルールを作成します。
f:id:ysmn_deus:20200214140952p:plain

問題無ければSESのコンソール上で表示されている筈です。
f:id:ysmn_deus:20200214165126p:plain

Step 4: メール送信テスト

まだ設定半ばなのですが、一端ここまでの設定が正しいか確かめてみます。
ドメイン名の前は何でも良いので、「test@設定したドメイン」のように適当なアドレスに向けてメールを送ってみます。
(例えば、example.comというドメインを設定したなら「test@example.com」のような。@の前は何でも良い)

Step 5: 受信したメールを見てみる

設定がうまくいっていれば、先ほどテストで送ったメールがS3に保存されている筈です。
S3のコンソール( https://console.aws.amazon.com/s3/ )から先ほど設定したbucketを見つけて中を見てみましょう。
f:id:ysmn_deus:20200214170821p:plain
bucketの中には - AMAZON_SES_SETUP NOTIFICATION - 謎の英数字の羅列 の2ファイルが存在していると思います。
謎の英数字の羅列が保存されたメールとなります。ダウンロードして中身を確認してみると、メールのソースになっていると思います。
(いわゆる本文だけではなく送信元情報などのデータとしてのメール。)

これでメールの受信が機能している所までは良さそうです。

Step 6: S3にPUTされたらLambdaで転送する様にする

S3にあがるだけではIMAPPOP3形式で受信できないので、どこかのメーラーでやりとりしたいものです。
(例えばGmailThunderbirdなど)
本来設定しているメールアドレスに一時的に転送して、閲覧は普段使いのメーラーでできるようにします。

転送用のLambda関数を作成する

何はともあれLambda関数が必要です。SAM CLIなどでデプロイしてもいいんですがそんなにいじらないし全部ブラウザ上から済ませます。

ローカルにaws-lambda-ses-forwarderをcloneする

まずローカルにGithubで公開されている aws-lambda-ses-forwarder をcloneします。

git clone https://github.com/arithmetric/aws-lambda-ses-forwarder

使い捨てるので適当なディレクトリで大丈夫だと思います。
f:id:ysmn_deus:20200214172950p:plain
必要なのはindex.jsのみです。(なんならコピペでもいいんですが・・・)

コンソールでLambda関数を作成する

ソースが準備できたらLambdaのコンソールに行きます。
https://console.aws.amazon.com/lambda/home
右上の「関数の作成」からLambda関数を作成します。
f:id:ysmn_deus:20200214173333p:plain

「一から作成」で基本的な情報を入力していきます。
関数名は適宜決めていただいて(例えば、「sesForwarder」など)、ランタイムは、現在(2020/02/14)ではNodeのv12が利用可能ですが、念のためv10にしておきました。(GithubのREADME上にはv8 v10と記載があるので、多分v12でも大丈夫だけど)
実行ロールは後で編集するとして、特に触らずに「関数の作成」でLambda関数を作成します。
f:id:ysmn_deus:20200214173938p:plain

とりあえずLambda関数はできました。
f:id:ysmn_deus:20200214174259p:plain

作成した関数をaws-lambda-ses-forwarderに書き換える

先ほどcloneしたaws-lambda-ses-forwarderの中にある「index.js」をアップロードします。
めんどくさい人はエディタで開いてコピーしてからブラウザ上のエディタに貼り付けても良いと思います。
とりあえずzipに固めてアップロードする手法を採ります。「index.js」のみzipに固めます。
f:id:ysmn_deus:20200214174436p:plain

固めたzipをLambdaにアップロードします。
先ほどのコンソールから「関数コード」という項目を探しだし、「コードエントリタイプ」から「zipファイルをアップロード」を選択します。
f:id:ysmn_deus:20200214174611p:plain

「アップロード」という箇所が出てくるので、先ほどのzipをアップロードする。
f:id:ysmn_deus:20200214174744p:plain

「アップロード」の横にzipファイル名が記載されていればアップロードできているので、「保存」をクリックしてアップロードしたzipファイルを関数に適応する。
f:id:ysmn_deus:20200214174934p:plain

Lambdaのコンソール上のエディタにaws-lambda-ses-forwarderが表示されていればOK
f:id:ysmn_deus:20200214175114p:plain

aws-lambda-ses-forwarderの設定を書き換える

これでアップロードは完了しましたが、ちょっとだけ設定を変更します。
30行目のdefaultConfigの値を変更していきます。

var defaultConfig = {
  fromEmail: "noreply@example.com", // ここは設定したドメインの任意のメールアドレス
  subjectPrefix: "[Forwarding]", // 転送するタイトルの頭に何か付けるならここに設定
  emailBucket: "s3-bucket-name", // emailが格納されるS3のbucket名
  emailKeyPrefix: "", // SESの設定でprefixを設定している場合は設定。上記の通りであれば空でOK
  forwardMapping: {
    "info@example.com": [
      "example.john@hoge.com",
      "example.jen@hoge.com"
    ],
    "@example.com": [
      "example.john@hoge.com"
    ]
  }
};

SESは仕様上、転送するメールアドレスの差出人を自分のドメイン以外にすることができません。
つまり、「hogefuga@hoge.com」からメールが来た場合に、「example.john@hoge.com」へメールを転送するのですが、上記の設定では、「example.john@hoge.com」へ届くメールの差出人は「noreply@example.com」に書き換わっています。

f:id:ysmn_deus:20200214182932p:plain
図示するとこんなかんじ

forwardMappingのみ追加で説明しますと、上に記載されているものから順にマッチするか評価され、最初にマッチした宛先に転送されるようです。
つまり上の例では - info@example.comはexample.john@hoge.comとexample.jen@hoge.comに転送される - info2@example.comはexample.john@hoge.comのみに転送される という処理になります。
基本的には1対1か1個のメールアドレスに集約されるように書くのがいいんじゃないでしょうか。

変更した後は右上の「保存」をクリックすることをお忘れ無きよう。

Lambda関数のロールの設定

現状ただのLambda関数ですのでSESはおろかS3にアクセスすらできません。
なので - S3へのアクセス権限 - SESでの送信権限 - ついでにCloudWatchのログ作成権限 を付与します。

コンソール上の下の方にある「実行ロール」という項目を確認します。
f:id:ysmn_deus:20200214184038p:plain

「既存のロール」という項目の下にある「【関数名】-role-(英数字の羅列)ロールを表示」という箇所をクリックします。
するとIAMのコンソールが表示されますので、「インラインポリシーの追加」を押します。
f:id:ysmn_deus:20200214184359p:plain

「ポリシーの作成」というページが表示されますので、編集していきます。
設定する対象がわかりきっている場合はJSONの方が早いです。「JSON」をクリックして下記のように設定しましょう。
f:id:ysmn_deus:20200214184556p:plain

{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
         ],
         "Resource": "arn:aws:logs:*:*:*"
      },
      {
         "Effect": "Allow",
         "Action": "ses:SendRawEmail",
         "Resource": "*"
      },
      {
         "Effect": "Allow",
         "Action": [
            "s3:GetObject",
            "s3:PutObject"
         ],
         "Resource": "arn:aws:s3:::S3-BUCKET-NAME/*"
      }
   ]
}

S3-BUCKET-NAMEの箇所はemailが保存されているS3のbucket名に変更して下さい。
厳密に言えばSESのリソースなども対象を絞った方がセキュアだと思いますが、その辺は割愛します。

JSONの編集が完了したら、右下の「ポリシーの確認」をクリックします。

最後に、設定したポリシーがどのようなものか一覧で表示されます。
ポリシー名を付けないといけないので「名前」の箇所を「LambdaSesForwarder」としておきました。
f:id:ysmn_deus:20200214185106p:plain

問題無ければ「ポリシーの作成」をクリックして完了します。
また別のLambdaに別のメールアドレスを設定する可能性などある場合はポリシーを作成しておくと再び作成しなくていいのですが、どうせS3のbucketも変わると思いますのでインラインポリシーで良いと思います。

Lambda関数のタイムアウト時間の変更

大丈夫な気がするんですが、一応aws-lambda-ses-forwarderのREADMEによると「メモリ制限が128MBで10秒がええんとちゃうか?」のような記述があるので、タイムアウト時間だけ10秒に変更しておきます。
(大きな添付ファイルが想定される場合はメモリを512MBにするか、タイムアウトを30秒にするかした方がいいかも、とも書かれているので心配な方は30秒に設定しておいた方がいいかも)

設定を変える場所は先ほど編集していた「実行ロール」の隣にあると思います。「基本設定」という項目を見つけ「編集」をクリックして下さい。
f:id:ysmn_deus:20200214185949p:plain

タイムアウトを10秒に変更して、「保存」をクリックします。
f:id:ysmn_deus:20200214190059p:plain

SESの設定にLambda関数を追加する

ここまできてようやくSESとLambdaを連携させます。
「保存先の設定(Actionの設定)」で編集していたSESのコンソールの「Email Receiving」→「Rule Sets」を編集します。
今までの通りにやっていれば「default-rule-set」が適応されていると思いますので、「View Active Rule Set」をクリックし、設定したRule(たぶん一番上に表示されてるヤツ)をクリックして編集します。
f:id:ysmn_deus:20200214191042p:plain
f:id:ysmn_deus:20200214191143p:plain

Add actionからLambdaを選択します。
f:id:ysmn_deus:20200214191317p:plain

対象の関数は作成した関数名を入力すれば出てくると思います。
それ以外は基本デフォルトで。
f:id:ysmn_deus:20200214191600p:plain

右下の「Save Rule」を押して編集したルールを保存します。
Missing Permissionsと聞かれますので、「Add permissions」で作成したSESにlambdaを起動する権限を付与します。

SESの設定で転送先のメールアドレスを認証しておく(一時的なもの)

おそらくスパム対策だと思いますが、初期状態ではSES+Lambdaでメールを送信する場合は転送先のメールアドレスを前もって認証しておく必要があります。
(後述する AWSのサポートへ連絡 でどのメールアドレスにも送信できるようになります。)
試験的にメールを送信するために、SESのコンソールから転送対象の認証を済ませます。
コンソールの「Identify Management」→「Email Addresses」をクリックし、「Verify a New Email Address」をクリックします。
f:id:ysmn_deus:20200214194422p:plain

Lambda関数のところで転送先を編集したと思うので、そのアドレスを入力します。
(上記の例では「example.john@hoge.com」と「example.jen@hoge.com」の二つ)
f:id:ysmn_deus:20200214194635p:plain

「Verify This Email Address」をクリックすると、前の画面に戻ってpendingになっているのが確認できます。
おそらく「Amazon Web Services – Email Address Verification Request...」というタイトルのメールが届いていると思いますので、記載されているリンクを24時間以内にクリックします。
「検証に成功しました」的な旨のページが出ればOKです。
f:id:ysmn_deus:20200214195036p:plain

コンソール上で先ほど設定したメールアドレスがpendingからverifiedに変わっていれば転送の準備は完了です。
f:id:ysmn_deus:20200214195239p:plain

転送されるか試しにメールを送ってみる

とりあえずメールを送りましょう。宛先は何でも良いと思いますが、Lambda関数の中でマッピングを複雑にした場合は色々試してみると良いと思います。

EX: S3のライフサイクルポリシー

上記のままだとメールが未来永劫たまり続けます。
基本残したままで困る容量ではないですが、SES+Lambdaで究極のコスパを攻めるならS3には極力データは残しておかない方がいいかもしれません。
S3にはライフサイクルポリシーという生成されてからどの期間残しておくかが設定できますので、そちらを設定しておけば極力容量は抑えられるとは思います。
ここでは割愛しますが、需要があればそのうち追記します。

メール送信のセットアップ

と項目を書いたのですが、受信のセットアップが終わっていればほぼ完了しているも同然です。
とはいえまだSMTPの情報などがないのでセットアップしていきましょう。

SMTPの情報を取得する

SESのコンソールから「Email Sending」→「SMTP Settings」をクリックします。
表示されている「Create My SMTP Credentials」をクリックします。
f:id:ysmn_deus:20200214231439p:plain

「Create User for SMTP」というページが出てくるので、とりあえずIAM User Nameはデフォルトで入ってる適当な名称を利用します。

f:id:ysmn_deus:20200214231952p:plain
特に変更しない

右下の「作成」でSMTPのユーザーを作成します。
f:id:ysmn_deus:20200214232107p:plain

書いてあるとおり、認証情報はこのタイミングでしかダウンロードできないので慎重に保存しておきます。
最悪ユーザーを作り直せば良いだけだと思いますが、くれぐれもこの辺の認証情報を公開しないように注意しましょう。
上記の画像に書いてある「ユーザーのSMTPセキュリティ認証情報を表示」でユーザー名とパスワードを表示して設定してもいいですが、ファイルに保存しておいた方が確実かと思いますので、右下の「認証情報のダウンロード」から認証情報を保存しておきましょう。

試験的に送信してみる

先ほどのSMTPの情報で、試しに認証している(上記までの例では、転送先のメールアドレス)にメールを送信してみて下さい。
おそらくSMTPのポートを587に設定して上記の情報を利用すれば送信できると思います。

Sandbox外への払い出し

SESのサービスを利用し始めたときは、認証したメールアドレスにしかメールを送ることができません。
(転送だけしかしない場合はこれでも問題ないでしょう)
なので、認証してないメールアドレスにも送信できるようにAWSに申請する必要があります。

AWSのサポートへ連絡

サポートに連絡するためにコンソール( https://console.aws.amazon.com/ )を開きます。
右上にある「サポート」から「サポートセンター」を開きます。
f:id:ysmn_deus:20200215091101p:plain

「Create case」をクリックし、「Service limit increase」を選択します。
f:id:ysmn_deus:20200215091414p:plain

すると、「Case classification」というボックスが出てくると思いますので、「Limit type」の「SES 送信制限」を選択します。
f:id:ysmn_deus:20200215095237p:plain
その他の入力項目は適宜合わせて貰えば良いと思いますが、今回は普段使いのメールなので「メールの種類」を「その他」、「ウェブサイトのURL」を空欄、残りの項目を「はい」にしました。
最後の3項目は日本語だとなんのこっちゃ・・・という感じですが、原文は

  • For My email sending complies with the AWS Service Terms and AUP, choose the option that applies to your use case.
  • For I only send to recipients who have specifically requested my mail, choose the option that applies to your use case.
  • For I have a process to handle bounces and complaints, choose the option that applies to your use case.

と記載されていますので

  • AWSサービス条件とAUP(ウェブサイトでリンクがありますのでご確認ください)を満たしているか
  • 受け取る意思がある人にのみメールを送信するか(メールのやりとりなので、よっぽど特殊なケースでない限りはあるはず。不特定多数の営業とかには使っちゃダメですね。)
  • 苦情などが来たときにハンドリングする手順があるか(普通のメールなので基本的には考えなくて良いと思います)

といった感じでしょう。アプリケーションなどにも活用を考えている場合はこのへんは慎重に回答してください。

次に、「Requests」の項目を埋めます。
リージョンを自分のSESのリージョンに合わせ、「Limit」を「希望する一日あたりの送信クォータ」に設定し、「New limit value」を「200」に設定します。この200は公式ドキュメントに書いてあります。 基本的にはドキュメント通りに設定すれば問題無いと思います。1日に200通以上も送る(受信ではなく送信)人は通常の人ではないので僕は知りません。
f:id:ysmn_deus:20200215100127p:plain

次に、「Case description」を記入します。
基本的にはありのままを記載すれば良いと思いますが、「Case classification」ではいと答えている項目には言及しておいた方がいいかもしれません。
自分は普通にメールとして使うよ!ということと、苦情対策としてはCloudWatchでSESの送信制限を監視して、ヤバそうだったら利用を停止するよ!ということを書いておきました。

最後に「Contact options」ですが、基本的にはデフォルトのままで良いと思います。「Preferred contact language」はもしかしたら英語の方がレスポンスが早いのかなぁとも思ったんですが、その後でやりとりが発生する可能性を考慮して一応日本語にしておきました。
「submit」を押して起票完了です。
f:id:ysmn_deus:20200215100956p:plain

たぶん1日ぐらいで返信が来ます。(自分は約1日で来ましたが、2~3日たっても気長に待ちましょう。)
「お客様のアカウントを Amazon SES サンドボックスから移動いたしました。」との記載があれば問題無く利用できる状態になっていると思います。

pythonのpip installで「error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio"」て言われるの対処法

f:id:ysmn_deus:20200215172000p:plain

結論から言えばhttps://visualstudio.microsoft.com/ja/downloads/からビルドツールを取得してインストールすればOKです。
ぐぐれば同様の記事など見かけますが、Visual Studio 2017など若干バージョンが違ってたりするので、その辺バージョン上でも問題無いのか一応確認しました。

f:id:ysmn_deus:20200215171340p:plain

基本的にインストーラーの指示に従ってれば良いですが、Visual C++のBuild Toolsが欲しいので下記の「C++ Build Tools」にチェックを入れてインストールしました。

f:id:ysmn_deus:20200215171351p:plain

再起動したら問題無くpip installが通ると思います。

Ryzenで組んだPCでDockerをインストールするときに詰まったところ

f:id:ysmn_deus:20200131142114p:plain

どうも、靖宗です
RyzenでPCを組んでセットアップしていたのですが、Docker(Toolbox)をインストールするときに何カ所か詰まりました。

AMDのCPUでHyper-Vを有効化するとVMがうまく起動しない

これなんとかして欲しい所ではありますが、今のところあんまり有効な手立てはないようです。
諦めてHyper-Vを無効化してVMを動かすしか無さそうです。Win10Pro買っちまったよ・・・_(:3 」∠ )_

たぶんHyper-Vを意識的に有効化してない人はなにもしなくていいんでしょうが、自分は無意識のうちにHyper-Vを有効化していたようです。
とりあえずHyper-V関連の機能を無効化します。

Windowsの機能のHyper-Vを切る

この操作が必要かあやしいんですが、自分は一応したので書いときます。
まずコントロールパネルの「プログラム」→「プログラムと機能」の「Windowsの機能の有効化または無効化」→「Hyper-V」のチェックを外す
という操作をしておきます。

f:id:ysmn_deus:20200131140918p:plain f:id:ysmn_deus:20200131141644p:plain f:id:ysmn_deus:20200131141654p:plain

たぶん再起動が求められます。

コマンドで機能を無効化する

上記の操作だけではまだ正しくVMが作成できなかったので、下記の操作を行います。
コマンドプロンプトを管理者モードで起動して

dism /Online /Disable-Feature:Microsoft-Hyper-V

というコマンドを実行します。
一応ここで再起動します。再起動後に下記のコマンドを実行します。

bcdedit /set hypervisorlaunchtype off

これでDockerのVMが正しく起動する環境が整ったと思います。
上記のコマンドに関しては下記のStackOverFlowを参考にしました。

stackoverflow.com

インストールするDockerのバージョンをちょっと古いヤツにする

なにやら新しいDocker Toolboxではうまいこといかないそうなのでちょっと古いバージョンにしておきます。
自分は"v18.06.1-ce"を選択しました。下記のDocker Toolboxのリリースからダウンロードします。

github.com

基本的にウィザードに従ってインストールで問題無いかと思います。
バージョンの選定に関しては下記の記事を参考にさせていただきました。

hepokon365.hatenablog.com

AWS SAMでCognito認証のPOSTが通らなかった

f:id:ysmn_deus:20190318182949p:plain

どうも、靖宗です。
docker-lambdaで開発して、コンソールからLambdaをデプロイするというちょっとアレな運用を直すべくSAM CLIを利用し始めました。
そこまでは良かったものの(逆になんで使って無かったんだ?というぐらい快適だしCloudFormation最高)、Cognitoとの連携をやり始めてCORSでPOSTするAPIがうまくいかず悩んでました。

とは言っても基本的には全てリクエストに情報は載っているもの。
調べて見るとPOSTの手前のOPTIONSリクエストでCognito認証がはじかれてる模様。
axiosでPOSTリクエストを送っている環境下だったので、わざわざOPTIONSのリクエストを描いてヘッダーにトークンを・・・というのはなんだか解決方法として間違ってる気がしたので執拗にウェブをあさっておりましたら該当する情報にたどり着きました。ありがたや。

nibral.hateblo.jp

SAMのテンプレートのAPI Gatewayの箇所にAddDefaultAuthorizerToCorsPreflightという項目を追加しfalseに設定すれば良いとのこと。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
  CognitoUserPoolArn:
    Type: String
    Default: arn:aws:cognito-idp:~~~(該当するARNを入力)
  StageName:
    Type: String
    Default: dev

Globals:
  Function:
    Timeout: 3

Resources:
  ApiGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: !Ref StageName
      Auth:
        DefaultAuthorizer: CognitoAuthorizer
        AddDefaultAuthorizerToCorsPreflight: false
        Authorizers:
          CognitoAuthorizer:
            UserPoolArn: !Ref CognitoUserPoolArn
      Cors:
        AllowOrigin: "'*'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowMethods: "'OPTIONS,POST,GET'"
  Function:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../../built(該当する箇所のパス)
      Handler: (該当ハンドラ)
      Runtime: nodejs10.x
      Events:
        Api:
          Type: Api
          Properties:
            Path: /post
            Method: post
            RestApiId: !Ref ApiGateway

Outputs:
  Region:
    Description: "Region"
    Value: !Ref AWS::Region
  ApiId:
    Description: "API ID"
    Value: !Ref ApiGateway
  ApiUrl:
    Description: "API endpoint URL"
    Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/${StageName}/'

これで問題無くAPIが使える筈です。

AmplifyのLambdaでデフォルトでTypeScript使えるようにならんかなぁ・・・(:3 」∠ )