VSCodeのExtensionを端末間で共有する方法

VSCodeでインストールしたExtensionを端末間で共有したり、PCを新しく買ったときにどうやって同じ設定にしようか悩んでました。

どうやら Settings Sync を使うことで簡単に Gist で管理できるみたいです。

marketplace.visualstudio.com

AWS CloudFormationで名前に - が使えなくて困ったこと

AWSの環境ではWAF など、名前に - が使えない物があります。

CloudFormationからWAFの設定をしようとした時に少し困ったのでメモを残しておきます。

何に困ったのか?

CloudFormationのテンプレートで、Parametersから入ってくる変数の値に - が入っていると Name を設定するときにエラーになってしまう。

最初に作成したテンプレートは↓です。

AWSTemplateFormatVersion: '2010-09-09'
Description: WAF CI/CD Pipeline

Parameters:
  ProductName:
    Type: String
  Stage:
    Type: String
    Default: prod

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Product Configuration
        Parameters:
          - ProductName
          - Stage
    ParameterLabels:
      ProductName:
        default: ProductName
      Stage:
        default: Stage

Resources:
  WAF:
    Type: AWS::WAF::WebACL
    Properties:
      Name: !Sub ${ProductName}${Stage}
      MetricName: !Sub ${ProductName}${Stage}
      DefaultAction:
        Type: BLOCK
      Rules:
        - Action:
            Type: ALLOW
          Priority: 1
          RuleId: !Ref WAFRule

  WAFRule:
    Type: AWS::WAF::Rule
    Properties:
      Name: !Sub ${ProductName}MyIp${Stage}
      MetricName: !Sub ${ProductName}MyIp${Stage}
      Predicates:
        - DataId: !Ref WAFIPSet
          Negated: false
          Type: IPMatch

  WAFIPSet:
    Type: AWS::WAF::IPSet
    Properties:
      Name: !Sub ${ProductName}-my-ipset-${Stage}
      IPSetDescriptors:
        - Type: IPV4
          Value: {IPアドレス}/32

モックアプリを作っていたので、 ProductName には hoge-mock という感じで - 付きにしていました。
AWS::WAF::WebACL AWS::WAF::RuleName には - が使えないので、ココでエラーが出てしまいます。

Stage は、環境を表していて、 prod stg dev などの文字が入ってきます。

- を消す方法(テンプレートの変換の流れ)

どうにか - を消せないかとイロイロ試したところ↓の方法で - を消すことができました。

      Name:
        !Join
          - ''
          - - !Join
                - ''
                - !Split
                    - '-'
                    - !Ref ProductName
            - !Ref Stage

!Ref のところを展開します。

      Name:
        !Join
          - ''
          - - !Join
                - ''
                - !Split
                    - '-'
                    - hoge-mock
            - prod

!Splithoge-mock を、 - で分割した配列に変えます。

      Name:
        !Join
          - ''
          - - !Join
                - ''
                - - hoge
                  - mock
            - prod

!Join[hoge, mock] を、文字列に変換します。

      Name:
        !Join
          - ''
          - - hogemock
            - prod

!Join[hogemock, prod] を、文字列に変換します。

      Name:
        hogemockprod

こういう変換の流れで、 - を消すことができました。

完成形

AWSTemplateFormatVersion: '2010-09-09'
Description: WAF CI/CD Pipeline

Parameters:
  ProductName:
    Type: String
  Stage:
    Type: String
    Default: prod

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Product Configuration
        Parameters:
          - ProductName
          - Stage
    ParameterLabels:
      ProductName:
        default: ProductName
      Stage:
        default: Stage

Resources:
  WAF:
    Type: AWS::WAF::WebACL
    Properties:
      Name:
        !Join
          - ''
          - - !Join
                - ''
                - !Split
                    - '-'
                    - !Ref ProductName
            - !Ref Stage
      MetricName:
        !Join
          - ''
          - - !Join
                - ''
                - !Split
                    - '-'
                    - !Ref ProductName
            - !Ref Stage
      DefaultAction:
        Type: BLOCK
      Rules:
        - Action:
            Type: ALLOW
          Priority: 1
          RuleId: !Ref WAFRule

  WAFRule:
    Type: AWS::WAF::Rule
    Properties:
      Name:
        !Join
          - ''
          - - !Join
                - ''
                - !Split
                    - '-'
                    - !Ref ProductName
            - MyIp
            - !Ref Stage
      MetricName:
        !Join
          - ''
          - - !Join
                - ''
                - !Split
                    - '-'
                    - !Ref ProductName
            - MyIp
            - !Ref Stage
      Predicates:
        - DataId: !Ref WAFIPSet
          Negated: false
          Type: IPMatch

  WAFIPSet:
    Type: AWS::WAF::IPSet
    Properties:
      Name: !Sub ${ProductName}-my-ipset-${Stage}
      IPSetDescriptors:
        - Type: IPV4
          Value: {IPアドレス}/32

参考URL

Fn::Join - AWS CloudFormation

Fn::Split - AWS CloudFormation

AWS Lambda@Edgeの設定でハマった話

CloudFront + S3 の構成で静的なWebページを公開したいが、Basic認証を付けたいということがあります。

Lambdaを使うと上記問題は解決できるのですが、Lambdaの設定にいくつか制約があったのでメモとして残しておきます。

  • ビューワーリクエストの場合はLambdaのタイムアウト時間を1秒にしなければいけない
  • バージョニングを使用する必要あり、$Latest は指定できない
  • 環境変数は利用できない

参考URL

AWS Lambda@Edge - AWS Lambda

Amazon CloudFrontとAWS Lambda@EdgeでSPAのBasic認証をやってみる | Developers.IO

Route53の設定だけは手動でした話

AWSでインフラを構築する際は極力Cloud​Formationを使うようにしています。
ですが、Route53の設定はCloud​Formationの設定を使いませんでした。

Route53は最低1つはAレコードを登録しなければいけません。
ですが、CloudFormationの設定からではAレコードにCloudFrontのURLを設定できなかったからです。
AWSコンソールから手動で設定する分にはAレコードにCloudFrontのURLを設定できます。

DNS:
  Type: AWS::Route53::HostedZone
  Properties:
  Name: 'ドメイン名'

Route53RecodeSet:
  Type: AWS::Route53::RecordSet
  Properties:
    Comment: HP Hoge
    HostedZoneId: !Ref DNS
    Name: www.hoge.com.
    ResourceRecords:
      - '設定したいCloudFrontのURL'
    TTL: '300'
    Type: A

Route53のレコードセットでCloudFrontのURLをAレコードに設定しようとすると

Invalid Resource Record: FATAL problem: ARRDATAIllegalIPv4Address (Value is not a valid IPv4 address) encountered with '設定したいCloudFrontのURL'

というエラーが出て設定できないためでした。

create-react-appでTypeScript環境を構築したときにsourcemapを出力しない方法

Reactのテンプレートを作る方法にcreate-react-appがあります。

github.com

これを使うとcreate-react-appコマンドで簡単にテンプレートを作って、初期設定されているnpm scriptsでサーバー起動やビルドといったことが簡単にできるようになっています。

React+TypeScriptの環境を構築したい場合はどうするのかというとプロジェクト作成時のオプションで--scripts-version=react-scripts-tsを指定することでTypeScriptの環境を構築することができます。 github.com

プロジェクトのビルドをするために、初期で用意されているnpm scriptsを実行します。

$ yarn build

buildディレクトリ以下に成果物が作成されます。
jsディレクトリ配下を見るとsourcemapも作成されています。
開発中は便利なのですが、本番環境にデプロイするときはsourcemapは作成しなくていいので、作成しないためにはどうするのか調べてみました。

webpack.config.prod.js にてsourcemapの出力を制御するために、nodeの環境変数を読み込んでいる部分がありました。

const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';

参照:create-react-app-typescript/webpack.config.prod.js at master · wmonk/create-react-app-typescript · GitHub

package.jsonのscriptsでビルド実行時に環境変数を渡してあげることでsourcemapを出力せずにビルドすることができました。

npm scriptsを修正すると↓の用になると思います。

"build": "GENERATE_SOURCEMAP=false react-scripts-ts build"

Reactでシングルクリックとダブルクリックを区別して使いたい

先に今回の話のソースコードです。 github.com

Reactでは シングルクリックイベントとダブルクリックイベントが用意されています。

シングルクリック

<a href="javascript:void(0)"
   onClick={(e) => this.handleOnSingleClick(e)}>
   Single Click
</a>

onClickイベントが用意されています。

ダブルクリック

<a href="javascript:void(0)"
   onDoubleClick={(e) => this.handleOnDoubleClick(e)}>
   Double Click
</a>

onDoubleClickイベントが用意されています。
※ mobile safari だと動きませんでした。

それぞれを別々に使う分には期待した通りに動作します。

問題はSingleClickとDoubleClickを両方設定したときです。

<a href="javascript:void(0)"
   onClick={(e) => this.handleOnSingleClick(e)}
   onDoubleClick={(e) => this.handleOnDoubleClick(e)}>
   Single Click and Double Click
</a>

こんな形で実装して、ダブルクリックしてみると、onClickイベントが2回とonDoubleClickイベントが1回発火します。
シングルクリックのときは onClickイベントだけ、ダブルクリックの時はonDoubleClickイベントだけと区別して使いたいことがあります。

そういったときどうするのかという話です。

ソースコードはTypescriptで書いてます。

import * as React from 'react';

interface IAppProps {
}
interface IAppState {
}

export class App extends React.Component<IAppProps, IAppState> {

  private clickCount: number;

  constructor(props: IAppProps) {
    super(props);

    this.clickCount = 0;

    this.state= {};
  }

  /**
   * Single or Double Click
   */
  handleOnSingleOrDoubleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    this.clickCount++;

    if (this.clickCount < 2) {
      setTimeout(() => {
        if (this.clickCount > 1) {
          console.log('Double Click');
        } else {
          console.log('Single Click');
        }
        this.clickCount = 0;
      }, 200);
    }
  }

  render() {
    return (
      <div>
        <a href="javascript:void(0)"
          onClick={(e) => this.handleOnSingleOrDoubleClick(e)}>
          Single Click or Double Click
        </a>
      </div>
    );
  }
}

の様に書くとシングルクリックとダブルクリックを区別することができます。

キモになるのはこの部分で、onClickイベントのみを使います。

  /**
   * Single or Double Click
   */
  handleOnSingleOrDoubleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    this.clickCount++;

    if (this.clickCount < 2) {
      setTimeout(() => {
        if (this.clickCount > 1) {
          console.log('Double Click');
        } else {
          console.log('Single Click');
        }
        this.clickCount = 0;
      }, 200);
    }
  }

クリックイベント時に、クリック数をインクリメントして、setTimeoutで後からクリック後の動作を実行してあげることでシングルクリックとダブルクリックを別々に認識させることができます。

クリックして動作が見れるページを作ってみたので、興味がある人はクリックしてみて下さい。 React doubleclick sample

AWSで最初からCloudFormationでインフラを管理する方法を考えてみる

AWSで最初からCloudFormationを使ってインフラを管理していく方法について考えてみました。

個人でサービスを作ろうと思い、あまりお金もかけたくなかったのでサービス選定は

サービス名
クラウドサービス AWS
ソースコード管理 Bitbucket
CI/CD CircleCI

といった感じです。

参考にしたサイトです。

dev.classmethod.jp github.com

CloudFormation と CI の組み合わせをする上で必要な物

今回だとCircleCIからAWSへアクセスする為のIAMユーザーが必要になります。
また、CloudFormationのテンプレートを分割して管理する場合は、S3 Bucketも必要になります。

導入手順

先に今回の内容をコード化したものがコチラです。 github.com

導入手順としては

  1. 全サービスにアクセスできるIAMユーザーを作る
  2. 自分が作成したいサービス向けのIAMユーザーを作る

という流れにしようと思いました。

悩ましいのが、どうしても最初はWebのAWSコンソールからIAMユーザーとS3 bucketの作成が必要になることでした。

いろいろ考えて納得できる方法としては

  1. 手作業でAWSコンソールのCloudFormationから setup/cfn.template.yml を指定して awscli 用のIAMとCloudFormationのテンプレートを一時的に置くS3 bucket を作成します。
  2. 作成されたIAMユーザーのkey, secretを作成してAWS CLIにアカウントをセットアップします。
  3. AWS CLI でcloudformationのスタックを作成して新規作成するサービス向けのインフラを構築するための準備をします。

という手順です。

  1. の作成に使うテンプレートだけは別管理にした方がわかりやすいかなと思いました。

CloudFormationのスタックが2つに分かれてしまいますが、管理しやすくて良いかなと思いました。

  • スタック1(setupディレクトリ配下)
    • awscli用のIAMユーザーとテンプレートなどを保存するためのs3 bucketを管理するテンプレート
  • スタック2(srcディレクトリ配下)
    • 各サービスを管理するためのユーザーを管理するテンプレート

の形にCloudFormationのスタックを分けることでができるので良いかなと思いました。

理想としては1つのスタックで完結させたいです。
ディレクトリツリーで見るとこんな感じです。
要は全てのIAMを src/iam.template.yml で管理したいです。

├── src
│   ├── bucket.template.yml
│   ├── cfn.template.yml
│   └── iam.template.yml

ですが、 最初はWebのAWSコンソールからIAMユーザーとS3 bucketの作成 をしないといけないので

├── setup
│   └── cfn.template.yml
├── src
│   ├── bucket.template.yml
│   ├── cfn.template.yml
│   └── iam.template.yml

と分け

├── setup
│   └── cfn.template.yml

ここで、最初のユーザーを管理します。

AWS CLI からのスタック作成について

デプロイするテンプレートは管理しやすくするために分割してます。

├── src
│   ├── cfn.template.yml
│   └── iam.template.yml

実際のところは下の様に実行すればCloudFormationで実行可能なテンプレートを作成することができる。

$ aws cloudformation package \
    --template-file src/cfn.template.yml \
    --s3-bucket artifact \
    --output-template-file .cfn/packaged.yml \
    --s3-prefix template

このコマンドでしていることは、 --s3-bucket で指定したバケット--s3-prefix で指定したディレクトリ配下に src/cfn.template.yml 以外のテンプレートファイルをアップロードします。 --output-template-file .cfn/packaged.yml で指定している部分に出力されるファイルは、 src/cfn.template.yml の参照している他テンプレートファイルを指定するパスが追加されています。

TemplateURL: /path/to/bucket/fc2e4965584e9309b667bf9a6c1eb6f4.template

の様な物が追加されています。

.cfn/packaged.yml が作成されたら、create-stackupdate-stack を使って更新していきます。

ここまでやるとiam.template.ymlで作成したIAMユーザーをCircleCIに設定することで自動更新ができるようになります。

あとは、新しく作りたいサービスがでたらそのサービス向けに権限を絞ったIAMユーザーを作ることができます。 そうすることで、CIなどを使った際に誤操作で別サービスのデータを消してしまうなどが防げるのではないかと思いました。

課題

今回の構成で自動デプロイできるようにはなってますが、課題もあります。

  • update-stack 実行後のCloudFormationの状態を監視していないのでCircleCIでは Success になるが CloudFormation では失敗してロールバックする可能性がある
  • 連続でデプロイしたとき、現在のスタックの状態を確認していないため、スタックが更新中の場合不具合が起きる可能性がある

備考

テンプレートの検証に cfn-lint を使っていて気づいたけど、 Type: AWS::CloudFormation::Stack を使ってテンプレートを分けた場合は、子テンプレートまでチェックしてくれるわけではないみたい。 あくまで引数に渡したテンプレートファイル単体のテストをしてくれるツールみたい