HTMLメールの注意点(Gmail)

システムで Gmail へ HTML メールを送る際の注意点になります。
Yahooなど他のサービスでの動作については確認していません。

例えばですが、PCではフォントサイズ 16px SP ではフォントサイズ 20px みたいな要件のとき

.font {
  font-size: 16px;
}

@media only screen and (max-width:480px) {
  .font {
    font-size: 20px !important;
  }
}

の様に書けば要件を満たせます。

ここで注意しなければいけない部分が2点あります。

1 media query の max-width: の後ろにスペースを入れてはいけない

@media only screen and (max-width: 480px) {

とするだけで、SP 時にフォントサイズが変わらないということが起きます。

2 !important を書かないと上書きされない

font-size: 20px !important;

css は後から書いたスタイルが優先されるはずなのですが、Gmail へ送る HTML メールでは !important を書かなければ上書きされないようでした。

参考

html - Why is Gmail ignoring my media queries? (On iOS) - Stack Overflow

AWS CloudFormation で ECR のライフサイクルを設定する

AWS ECR を使ってコンテナを更新していくと知らないうちにイメージが貯まっていきます。
Lifecycle Policy を設定すれば、古いイメージを自動で削除できます。

CloudFormation で設定する方法を見ると、 LifecyclePolicyTextString で設定すれば、 ECR の Lifecycle Policy を設定できます。

docs.aws.amazon.com

ですが、 Amazon ECR ライフサイクルポリシー を見ると分かるのですが、 JSON で設定するようになっています。

Lifecycle Policy の条件とその JSON は以下の様になります。

  • ルールの優先順位 : 1
  • イメージのステータス : タグ付けなし
  • 一致条件 : 次の数値を超えるイメージ数 10
{
  "rules": [
    {
      "action": {
        "type": "expire"
      },
      "selection": {
        "countType": "imageCountMoreThan",
        "countNumber": 10,
        "tagStatus": "untagged"
      },
      "description": "delete cycle",
      "rulePriority": 1
    }
  ]
}

YAML では > を使うと改行をスペースに置き換えられるのでそのまま JSON を書くことで String として扱うことができます。

        LifecyclePolicyText: >
          {
            "rules": [
              {
                "action": {
                  "type": "expire"
                },
                "selection": {
                  "countType": "imageCountMoreThan",
                  "countNumber": 10,
                  "tagStatus": "untagged"
                },
                "description": "delete cycle",
                "rulePriority": 1
              }
            ]
          }

試してみた CloudFormation のリソースを残しておきます。

Parameters:
  RepositoryName:
    Type: String
  ImageName:
    Type: String

Resources:
  Repository:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Ref RepositoryName
      RepositoryPolicyText:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowPushPull
            Effect: Allow
            Principal:
              AWS:
                - !Sub arn:aws:iam::${AWS::AccountId}:user/${ImageName}
            Action:
              - "ecr:*"
      LifecyclePolicy:
        LifecyclePolicyText: >
          {
            "rules": [
              {
                "action": {
                  "type": "expire"
                },
                "selection": {
                  "countType": "imageCountMoreThan",
                  "countNumber": 10,
                  "tagStatus": "untagged"
                },
                "description": "delete cycle",
                "rulePriority": 1
              }
            ]
          }
        RegistryId: !Ref AWS::AccountId

参考

AWS Lambda のトリガーで CloudWatch の定数(JSONテキスト)を使った場合の起動方法

AWS Lambda が .Net Core をサポートしたと言うけど実際に書くときに引数ってどうするんだろうって思ったので調べてみた。

Lambda のトリガーには CloudWatch を指定できて、 CloudWatch のイベントからルールを設定することでバッチ処理のようなことをさせることができます。

CloudWatch のイベントルール

f:id:naught00:20180831151541p:plain

ここで気になったのが、入力を 定数 (JSON テキスト) にした場合、ソースコードで引数部分をどう書けばいいのかということでした。

f:id:naught00:20180831151614p:plain

イベントごとの書き方の違い

一致したイベント

一致したイベントの場合は CloudWatchLogsEvent で大丈夫です。

public string FunctionHandler(CloudWatchLogsEvent logEvent) { }

定数 (JSON テキスト)

定数 (JSON テキスト) の場合、 JSON テキストは

{ "mode": "test" }

としたとき、 一致したイベント と同じ書き方ではエラーになります。

Object reference not set to an instance of an object.: NullReferenceException

その場合は自分で型を定義してあげることで無事Lambdaを起動することができます。

public class InputParameter
{
    public string mode { get; set; }
}

public string FunctionHandler(InputParameter params) {
    Console.WriteLine("mode : {0}", params.mode);
}

参考

github.com

CircleCI 2.0 で AWS CodeDeploy を使う

まず、 CircleCI 1.0 で CodeDeploy を使っているという前提で話を進めます。
そのため、デプロイ先のインスタンスへの CodeDeploy エージェントインストールなどについては触れません。

先に示すこれは失敗する例です。
成功する例はこの記事の一番最後に書いてあります。

.circleci/config.yml

version: 2
jobs:
  deploy:
    working_directory: ~/repo
    docker:
      - image: naughtldy/circleci-node-awscli:8
    steps:
      - checkout
      - run:
          name: zip
          command: "zip -r master ../repo"
      - run:
          name: zip upload
          command: aws s3 cp master.zip s3://{バケット名}/{ファイル名} --region ap-northeast-1
      - run:
          name: Deploy Staging
          command: |
            aws deploy create-deployment \
              --application-name bot-ryuzu \
              --deployment-group-name ryuzu-prod \
              --s3-location bucket="{バケット名}",bundleType="zip",eTag=`aws s3api head-object --bucket {バケット名} --key {ファイル名} | jq .ETag`,key="{ファイル名}" \
              --region ap-northeast-1

workflows:
  version: 2
  deploy:
    jobs:
      - deploy:
          filters:
            branches:
              only: master

実行結果は、 appspec.yml が無いという理由でデプロイに失敗します。
CircleCI 1.0 で動いていたので、ファイルが不足するなどの理由はないはずなので CircleCI 1.0 のときと比較してみます。

デプロイしたソースコードは、 CodeDeploy によるデプロイ先インスタンス

/opt/codedeploy-agent/deployment-root/{英数字の羅列}/

ここにデプロイIDごとにディレクトリが分かれて入っています。

以前の成功していた時のものを見てみると、デプロイIDのディレクトリの中身は

bundle.tar
deployment-archive
logs

となっています。

CodeDeploy でデプロイするソースコードなどは deployment-archive の中に入ってます。

失敗する場合は

bundle.tar
deployment-archive
repo

となります。

repo はCircleCIでのソースコード展開先のディレクトリ名です。

そうすると、 CodeDeploy のイベント BeforeInstall で、ディレクトdeployment-archive の中に appspec.yml が無いというエラーで CodeDeploy の実行に失敗します。
確認してみると deployment-archive の中身が空で、ソースコードなどは repo に入っています。

こちらが成功する例です。

.circleci/config.yml

version: 2
jobs:
  deploy:
    working_directory: ~/deployment-archive
    docker:
      - image: naughtldy/circleci-node-awscli:8
    steps:
      - checkout
      - run:
          name: zip
          command: "zip -r master ../deployment-archive"
      - run:
          name: zip upload
          command: aws s3 cp master.zip s3://{バケット名}/{ファイル名} --region ap-northeast-1
      - run:
          name: Deploy Staging
          command: |
            aws deploy create-deployment \
              --application-name bot-ryuzu \
              --deployment-group-name ryuzu-prod \
              --s3-location bucket="{バケット名}",bundleType="zip",eTag=`aws s3api head-object --bucket {バケット名} --key {ファイル名} | jq .ETag`,key="{ファイル名}" \
              --region ap-northeast-1

workflows:
  version: 2
  deploy:
    jobs:
      - deploy:
          filters:
            branches:
              only: master

working_directory: ~/deployment-archive で、 CircleCI のソースコード展開先ディレクトリ名を deployment-archive にしたことで、 デプロイ先インスタンスdeployment-archiveソースコードが入り今まで通りにデプロイできるようになりました。

GitHubで二段階認証を有効にしたときに聞かれるパスワード認証の回避方法

GitHubで二段階認証をすると、 http 通信ではなく、 ssh 通信を使う必要があります。
ssh 通信を使用すると、 CLIなどで pullpush などの操作をしたときに秘密鍵のパスワードを聞かれます。

Enter passphrase for key '/path/to/id_rsa':

このパスフレーズを回避する方法のメモです。

いくつか方法があるようです。
全部実施する必要はなくて、どれか一つをすれば大丈夫です。

ssh-add -K {秘密鍵}

CLIで↓のコマンドを入力してPCを再起動するまでの間、秘密鍵のパスワード入力を省略する。

ssh-add -K {秘密鍵}

.netrc を作成する

ユーザーのルートディレクトリに .netrc ファイルを作成する。
.netrc には以下の内容を記述する。

machine github.com
login {username}
password {password}

リポジトリのクローン時にトークンを追加する

GitHubの Settings から Personal access tokens でトークンを生成してリポジトリのURLにtokenを追加する。
https://github.com/settings/tokens

https://{username}:{token}@github.com/{username}/{repository}.git

秘密鍵からパスフレーズを消す

秘密鍵からパスフレーズを消す。

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