DDG - Develop a Digital Garden

develop a digital garden

AWS CDKを使ってほぼ一撃で静的サイトを構築する

AWS CDKとは?

AWS において Infrastructure as Code (以下 IaC) を実現するためのツールです。 CDK 登場以前も CloudFormation を利用して JSONYAML での IaC は実現可能でしたが、

  • 複数のスタックに共通する構成を定義しづらい
  • ほぼ同一構成なリソースを複数作成するのが面倒

という課題がありました。 CDK は プログラミング言語を使って定義できるので、 IF文によるパラメータ切り替えや、ループ処理による複数リソース定義が可能です!

利用できるプログラミング言語は、

です。

今回は、サンプルコードが充実している TypeScript を使用して、CloudFront + S3 の静的サイトを構築してみます。

前提

AWS CLIが利用できる状態。 ※AWS CDKは内部的に AWS-CLI と CloudFormation を利用して、リソースを操作しています。

構築手順

CDKのインストール

$ yarn global add aws-cdk

## バージョン確認
$ cdk --version
1.18.0 (build bc924bc)

CDKを書いていく

CDKプロジェクトを作成していきましょう。

$ cdk init --app cdk-static-site --language=typescript

必要なライブラリをインストールします。

$ yarn add @aws-cdk/aws-iam @aws-cdk/aws-s3 @aws-cdk/aws-cloudfront

CloudFront と S3 を定義します。

import cdk = require('@aws-cdk/core');
import s3 = require("@aws-cdk/aws-s3");
import cloudfront = require("@aws-cdk/aws-cloudfront");
import iam = require("@aws-cdk/aws-iam");

type Stage = 'prod' | 'stg' | 'test';

export class CdkStaticSiteStack extends cdk.Stack {

  readonly AppDomain: string = 'example.com';
  readonly AppName: string = 'example';
  readonly Stage: Stage = 'prod';

  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 独自ドメインを設定する場合に使用する(証明書ARNチェック)
    // const acmArn: string = this.node.tryGetContext('acmArn');
    // if (!acmArn) {
    //   throw new Error('CloudFrontに設定する証明書のARNをContextに設定してください。');
    // }

    // CloudFront オリジン用のS3バケットを作成する
    const originBucket = new s3.Bucket(this, this.prefix() + '-bucket', {
      // バケット名
      bucketName: this.prefix() + '-' + this.account,
      // バージョニング(無効)
      versioned: false,
      // CDKスタック削除時の挙動(スタック削除時にバケットも削除する)
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // CloudFront で設定する オリジンアクセスアイデンティティ を作成する
    const oai = new cloudfront.CfnCloudFrontOriginAccessIdentity(this, this.prefix() + '-oai', {
      cloudFrontOriginAccessIdentityConfig: {
        comment: 's3 access.',
      }
    });

    // S3バケットポリシーで、CloudFrontのオリジンアクセスアイデンティティを許可する
    const policy = new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      actions: ['s3:GetObject'],
      principals: [new iam.CanonicalUserPrincipal(oai.attrS3CanonicalUserId)],
      resources: [
        originBucket.bucketArn + '/*'
      ]
    });
    originBucket.addToResourcePolicy(policy);

    // CloudFrontディストリビューションを作成する
    const distribution = new cloudfront.CloudFrontWebDistribution(this, this.prefix() + '-cloudfront', {
      defaultRootObject: '/index.html',
      viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      httpVersion: cloudfront.HttpVersion.HTTP2,
      priceClass: cloudfront.PriceClass.PRICE_CLASS_200,
      originConfigs: [
        {
          s3OriginSource: {
            s3BucketSource: originBucket,
            originAccessIdentityId: oai.ref
          },
          behaviors: [
            {
              isDefaultBehavior: true,
              compress: true,
              minTtl: cdk.Duration.seconds(0),
              maxTtl: cdk.Duration.days(365),
              defaultTtl: cdk.Duration.days(1),
            }
          ]
        }
      ],
      // 独自ドメインを設定する場合に使用する
      // viewerCertificate: {
      //   aliases: [this.AppDomain],
      //   props: {
      //     acmCertificateArn: acmArn,
      //     minimumProtocolVersion: "TLSv1.2_2018",
      //     sslSupportMethod: "sni-only",
      //   }
      // },
      errorConfigurations: [
        {
          errorCode: 403,
          errorCachingMinTtl: 300,
          responseCode: 200,
          responsePagePath: '/index.html'
        }
      ]
    });

    cdk.Tag.add(this, 'App', this.AppName);
    cdk.Tag.add(this, 'Stage', this.Stage);
  }

  private prefix(): string {
    return this.AppName.toLowerCase() + '-' + this.Stage.toLowerCase();
  }
}

デプロイしてみます。

# TypeScriptをコンパイルする
$ yarn build

# CloudFomationテンプレートを生成する
$ cdk synth

# デプロイする
# しばらく待ちます。CloudFrontディストリビューションの作成に10分程度かかります。
$ cdk deploy

$ cdk deploy が終わり次第、S3にindex.htmlをアップロードし、 CloudFrontエンドポイントにアクセスするとindex.htmlの内容が表示されるはずです!

まとめ

このように、100行未満のコードを書くだけで、静的サイトが構築できます。 プログラミング言語を利用するので、柔軟に書けて、パラメータ切り替えや構成の再利用が行いやすいです。 今回は単純な構成例ですが、実務での冗長化構成や監視定義など、大規模になればなるほど、 プログラミング言語で定義できるというのが活きてくるかと思います。

まだまだ発展途上で、AWSJのSAの方にも「まだ手を出すのは早い」と言われたこともありますが、 どんどんCDKで構築していきたいと思います!

補足: 独自ドメインを設定する場合

上記コードの// 独自ドメインを設定する場合に使用するとコメントしている処理を利用してください。 CDKを実行する前に、ACMで証明書を発行しておき、その証明書のARNをcdk.context.jsonに追記してください。

{
  "@aws-cdk/core:enableStackNameDuplicates": "true",
  "acmArn": ""
}

AWS CDKのArgument of type 'this' is not assignable to parameter of type 'Construct'エラーの対応方法

AWS CDK 1.15.0 がリリースされました!
https://github.com/aws/aws-cdk/releases/tag/v1.15.0

アップデート前に作業していたCDKプロジェクトで、新しくリソースを追加したところ、以下のエラーが発生しました。

Argument of type 'this' is not assignable to parameter of type 'Construct'.

コード

import cdk = require('@aws-cdk/core');
import sns = require('@aws-cdk/aws-sns');
import subs = require('@aws-cdk/aws-sns-subscriptions');
// 1.15.0 リリース後に追加
import lambda = require('@aws-cdk/aws-lambda');
import events = require('@aws-cdk/aws-events');
import targets = require('@aws-cdk/aws-events-targets');

export class HogeStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // SNSトピック
    const topic = new sns.Topic(this, 'HogeTopic');
    topic.addSubscription(new subs.EmailSubscription('hoge@hoge.hoge'));

    // Lambda関数 (第1引数のthisで今回のエラーが発生している)
    const lambdaFn = new lambda.Function(this, 'HogeFunction', {
      code: lambda.Code.asset('lambda/dist'),
      handler: 'lambda_function.lambda_handler',
      runtime: lambda.Runtime.PYTHON_3_7,
      timeout: cdk.Duration.seconds(300),
      environment: {
        topic: topic.topicArn
      }
    });

    // Cloud Watch Events (第1引数のthisで今回のエラーが発生している)
    const rule = new events.Rule(this, 'Rule', {
      schedule: events.Schedule.expression('cron(30 6 * * ? *)'),
    });
    rule.addTarget(new targets.LambdaFunction(lambdaFn));
  }
}

対応方法

package.jsonのバージョン情報を修正します。

{
  ...
  "dependencies": {
    "@aws-cdk/aws-events": "^1.15.0",
    "@aws-cdk/aws-events-targets": "^1.15.0",
    "@aws-cdk/aws-lambda": "^1.15.0",
    # 新しく追加したCDKリソースのバージョンに合わせる(1.14.0 -> 1.15.0)
    "@aws-cdk/aws-sns": "^1.15.0",
    "@aws-cdk/aws-sns-subscriptions": "^1.15.0",
    "@aws-cdk/core": "^1.15.0",
    "source-map-support": "^0.5.9"
  }
  ...
}

node_modulesディレクトリを削除し、再度インストールします。

rm -rf node_modules
npm run install

参考

プロフェッショナルになるためのClean Coderまとめ

Clean Coder は、プログラマーという職業における 「プロとしての意識・考え方・振る舞い・仕事への取り組み方」が書かれています。 具体的な技術知識はほとんど出てきませんが、「プロとしての振る舞い方」がわかる素晴らしい本です。

この記事の注意点

私が印象に残った意識に関する部分噛み砕いて紹介しています。 紹介している部分以外が気になる方・そのままの表現を知りたい方は、ぜひ本を購入して読んでみてください。

この記事の目次

  1. プロフェッショナルの意識まとめ
  2. この本書を実際に読むメリット
  3. なぜ、この本をまとめたか?

プロフェッショナルの意識 まとめ

プロは 責任 をとる

プロは自分の行動に責任を持ちます。 自分の修正によって

  • どんな影響がでるのか?
  • 動作に問題は無いのか?

それらを全て把握しておかなければなりません。 では、どうやって把握するのでしょうか? それはテストしかありません。自動化されたユニットテストや受け入れテストをつくります。 テストを充実させても、バグが存在しないソフトウェアをつくることは現実的に不可能です。 ですが、責任はプロである自分にあります。 間違えたり、スケジュールに遅れることもありますが、その責任をとるのがプロです。 上司や他人・環境のせいにするのは、プロではありません。

プロは 継続的に学習 する

最新の医学雑誌を読まず、古いやり方しか知らない医者に診てもらいたいですか? それと同じで、最新の技術を学ばないプログラマーを会社は雇いたいでしょうか? プロは成長するために学習し続けなければいけません。

プロは 仕事を本番 だと思っている

プロは本番で成果を出すために練習をします。 プロ野球選手は試合が本番で素振りは練習です。 素早く質が高い仕事をするために、会社以外で勉強し効率の良い作業方法や質の高い設計方法を身につけなければなりません。

プロは No と言う

プロは 間に合いそうにない・できない ことを できると言いません。 プロは自分の能力を把握しています。

プロは Yes と言う

Yesと言うことは、約束したということです。 プロは Yes と言うときに、以下のような言葉を使いません。

  • 〜する必要がある、〜しなければいけない
  • 〜するといい、〜したい
  • 〜やりましょう

その代わりにこの言葉を使います。

  • 私が、〜までに、〜をやります

プロは 完了を定義 する

完了していないのに完了したと報告する人がたまにいます。 完了と言えるのは受け入れテストに合格した時だけです。 ビジネスの要求をクリアした時に初めて完了となるのです。

プロは 時間と集中力を管理 できる

プロは睡眠や運動が良い仕事に繋がることを理解しています。 無謀な残業が生産性を下げることを知っているので、自分の絶好調のペースを保てるように管理します。

プロは 規律 を守る

プロは緊急時でも安心して守れる規律を常に持っています。 例えば、テスト駆動開発(TDD)です。 緊急時にTDDをしないのであれば、TDDの効果を信じていないことになります。

プロは 協力 する

チームの中で孤立するのはプロではありません。 プロの第一の責任は、顧客の要求を満たすことです。 ビジネスサイドの人にも積極的に協力するのがプロです。 ビジネスが破綻しているのに、技術を追い求めても意味がありません。 ビジネスを成長させる・沈ませないことがプロの仕事です。

P.161 プロのプログラマとして最悪なのは、ビジネスが破綻しているのに、満足気に技術の墓場に没頭することだ。ビジネスを沈めさせないのが仕事だろ!

プロは 伝染 する

プロとしての振る舞いを強制することは難しいですが、プロの振る舞いは周囲に伝染します。 誰か1人、あなたがプロとして振る舞うことで他の人もプロになっていくはずです。

本書を実際に読むメリット

今回、紹介したのは 本に書かれている ごく一部 です。 この本は各章のはじまりで、著者の実体験が書かれ、その後にそこから学べることが紹介されている、という形式になっています。 著者の実体験の部分から学べることは、読む人によって変わると思いますので、もっと知りたい方は実際に読んでみてください。 そして感想を共有していただければ、ありがたいです。

なぜ、この本をまとめたか?

私は技術書やドキュメントを読むことや、勉強会に参加することが苦ではありません。 むしろ、新しい学びを得て成長に繋がることを確信しているので、楽しんで取り組んでいます。 ですが、プログラマーとして働いている人の中には、技術書を読むことが苦手な人もいます。 以前であれば、新しく何かを学ぼうとしないなんてありえないと思っていましたが、得意・不得意は誰にでもあります。 得意な人が不得意な人を助けるのが当たり前なのではないか、と今では思うようになりました。 そこで、Qiitaのような記事は読めるけど分厚い技術書は読めない・読みたくない、 という方に 少しでも読んでみたいと思ってもらえればいいな、と考えてこの記事を書きました。 何らかの形で誰かの役に立てば幸いです。

手書きでメモすることについて考える

普段、あなたはノートやメモを手書きでとっていますか?
それともパソコンでとっていますか?
最近、わたしは手書きするようにしてみました。
手書きにしてみて感じたことを書いていきます。

なぜ、手書きを始めてみたか?

『アウトプット大全』という本の中で、手書きでメモをとると記憶に定着しやすい、ということが書かれていました。 それを、とりあえず試してみようと思い、手書きを始めました。

今まではどうやっていたか?

今までは Dyanlist というアプリを使ってメモをとっていました。
Webエンジニアという仕事上、常にパソコンに向かって作業しているのでモニターから視線を動かさずにメモをとれることもあり、特に不自由はありませんでした。

手書きしてみてどうか?

手書きで不便と感じた点は

  • 時間がかかる
  • スペースが限られてしまう

の2つで、良いなと思った点は

  • 図や矢印で関連性を表現しやすい
  • モニターが1つ増えたように扱える

です。

気になった点の2つはデメリットと捉えていましたが、
ちょっと考えてみると逆にメリットなのではないか、と思うようになりました。

手書きのデメリットは、むしろメリット?

まずは、時間がかかる、という点についてです。
書くのに時間がかかるということは、その内容について考えている時間も長い、ということです。
パソコンの場合は2,3秒で書き終わるようなメモでも、手書きなら10秒程度かかります。 ペンを動かしている最中はそのことについて考えているわけです。
時間がかかっている時間をかけている分、手書きの方が深く考えられるようになり、メリットになると考えています。

次に、スペースが限られている、点についてです。
私はA5ノートを使用しているので、わりとすぐにページが埋まってしまいます。
書くスペースが少ないということは、書くことを厳選しなければならない ということです。
ノートには重要なことがだけが残るので、見直しやすくなります。

時間がかかる、スペースが限られている、両点に共通することですが、
時間とスペースに制限があるので自然と重要な部分だけを書くようになり、情報の取捨選択が行われます。
パソコンの場合は、素早く書ける、かつスペースが無制限なので、とりえあずメモとして残せてしまいます。
すると見返した際に情報が多すぎて本当に重要な部分が分かりづらくなる、ということが起きます。

手書き特有の制限がデメリットではなくメリットとなり、メモとしての効果が高くなるのではないでしょうか。

まとめ

手書きのデメリットになりそうな部分が、自分にとってはむしろメリットなのではないかと考えているので、
もうしばらく手書きをメインにしてみるつもりです。

冒頭で紹介した『アウトプット大全』にも書かれていることですが、

抽象のアナログ、具象のデジタル

という使い分けは、個人的にかなりしっくりきています。

アナログな手書きとデジタルなアプリ、どちらが良いというわけではなく状況に合わせて最適な方法で取り組むのが一番効率が良いと思います。

今までの方法に固執するのではなく、より良いものを試して柔軟に吸収することが大事ですね。

PHP標準機能でディレクトリ毎にPHPの設定変更を行う方法

PHP標準機能 .user.iniを利用して、ディレクトリ毎にPHP設定を変更する方法をご紹介します。

WebサーバーにApacheを利用している場合は .htaccessファイル でディレクトリ毎にPHP設定を変更することができますが、 Nginx + php-fpm を利用している場合 .htaccessファイル は使用できません。
なので.user.iniを利用します。

PHP公式ドキュメント - .user.ini ファイル

検証用のNginx + php-fpm 環境をDockerで構築しています。
※ファイル内容は最後にまとめて記載していますので、ご活用してください。

例として、使用メモリ最大値memory_limitを変更してみます。

デフォルトのPHP設定を確認する

デフォルトでは、.user.iniというファイルを探すようになっていますが、変更することもできます。

Image from Gyazo

.user.iniを使用していない状態の設定を確認しておきます。 左側の数値がディレクトリ毎の設定、右側の数値がphp.iniで設定している値です。

phpinfoのローカル・マスター列

user.iniのファイル名設定 Before

.user.iniで設定を変更する

PHP設定を変更したいディレクトリ直下に.user.iniファイルを作成します。

; .user.ini
memory_limit = 256M

再度、phpinfoを確認してみると256Mに変更されていることがわかります。

user.iniのファイル名設定 After

まとめ

上記のように.user.iniを利用することで、Nginx + php-fpm環境でもディレクトリ毎にPHP設定を変更することができます。

私が実際に行った例としては、
AWS上に構築した EC2 AutoScalingGroup + CodeDeploy(Blue/Green) という環境に対してCakePHPプロジェクトをデプロイする必要があり、 デプロイソースに.user.iniを含めることで、ディレクトリ毎のPHP設定変更を実現しました。

PHPファイル内でini_set()を行うよりも.user.iniを使うほうが設定の見通しがよくなるので、.user.iniを積極的に使うべきだと思います。


検証で利用したDocker構成

ディレクトリ構成

$ tree -a
.
├── code
│   ├── .user.ini
│   └── index.php
├── docker-compose.yml
└── nginx.conf

ファイル内容

./code/.user.ini

memory_limit = 256M

./code/index.php

<?php
phpinfo();

./docker-compose.yml

version: '3'
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - "./nginx.conf:/etc/nginx/conf.d/default.conf"
      - ./code:/code
    depends_on:
      - php
    links:
      - php
  php:
    image: php:7-fpm-alpine
    volumes:
      - ./code:/code

./nginx.conf

server {
    listen       80;
    root         /code;
    index  index.php index.html index.htm;

    location ~ \.php$ {
       fastcgi_pass   php:9000;
       fastcgi_index  index.php;
       include        fastcgi_params;
       fastcgi_param  SCRIPT_FILENAME  $document_root/$fastcgi_script_name;
    }
}

Docker起動方法

$ docker-compose up
# localhsot:8080でアクセスできます。
# ctrl + C で終了できます。

CloudFormationで作成したEC2 Auto Scaling Groupスタックが削除されない?

CloudFormationスタックとして作成したEC2 Auto Scaling Groupが削除されなかった際に調べたことを記載します。

CFn(CloudFormation)でEC2 Auto Scaling Groupのスタックを作成して、スケーリングの検証を行った後、
作成したスタックを削除してもEC2 Auto Scaling GroupとEC2インスタンスの削除がされなかったことがありました。

結論

何もしなくても10分程度待てば削除されました。

確認したこと

  • スタック削除の操作後、マネジメントコンソールでEC2 Auto Scaling Groupを確認しても削除中の表示はない
  • 維持インスタンス数、最小インスタンス数、最大インスタンス数が 0 になっていた。
  • EC2 Auto Scaling Groupに紐付いているEC2インスタンスの起動ステータスは動作中のまま
  • 手動でEC2 Auto Scaling GroupとEC2インスタンスを削除すると、スタックも削除される

調査したこと

公式ドキュメントの『自動スケーリンググループの削除』では下記のように記載されていました。

Auto Scalingグループを削除する前にすべてのインスタンスを終了するには、UpdateAutoScalingGroupを呼び出して、Auto Scalingグループの最小サイズと必要な容量をゼロに設定します。 To terminate all instances before deleting the Auto Scaling group, call UpdateAutoScalingGroup and set the minimum size and desired capacity of the Auto Scaling group to zero.

今回のCFnスタック削除でのAutoScalingGroup削除では、上記の方法が取られているようです。

CFnで構成管理するまでは、

  1. マネジメントコンソールからAuto Scaling Groupを作成する
  2. 検証
  3. マネジメントコンソールからAutoScalingGroupを削除する
  4. 紐付いているEC2インスタンスが削除されることを確認

という手順を行っており、AutoScalingGroup削除操作の数秒後にはEC2インスタンスの削除が始まっていたので、
AutoScalingGroupを削除するとほぼ同時にEC2インスタンスも削除されるものだと思っていました。

課題や知らなかったこと

  • 利用するサービスのドキュメントは大雑把でもいいので一通りは読んでおくべき。
  • 設定次第で挙動は異なるし、使用するサービス毎にデフォルトの挙動が異なるのを念頭においておく必要がある。

参考

Goの標準ライブラリでWebサーバーを立ててHelloWorldする

Goの標準ライブラリ(net/http)でWebサーバーを立ててHello, Worldを出力する手順です。

Goの標準ライブラリ net/http を利用すれば、簡単にWebサーバーを実装することができます。

# goファイルを作成する
$ touch server.go

以下のようなコードを書くことでWebサーバーを実装できます。

package main

import (
    "fmt"
    "net/http"
)

func main() {
    // ルーティングを設定する
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, World")
    })
    // ポート8080番でサーバーを起動する
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

以下のコマンドを実行後、ブラウザなどで
http://localhost:8080
にアクセスすると、Hello, Worldが出力されます。

$ go run server.go

上記コマンドを実行した際とは別のターミナルセッションで、curlコマンドでアクセスしてみます

$ curl localhost:8080
Hello, World

以上、Goの標準ライブラリ net/httpでWebサーバーを建てて、Hello, Worldする手順でした。

CodeDeployでAutoScalingGroupに対してデプロイした際のロールエラー

CodeDeployでAutoScalingGroupに対してデプロイした際に発生したロールエラーを解決する手順を記載します。

エラー内容

The IAM role arn:~~~ does not give you permission to perform operations in the following AWS service: AmazonAutoScaling.

解決方法

現在 CodeDeployに割り当てている IAMロールのポリシーに、以下のポリシーを追加するだけです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:PassRole",
                "ec2:CreateTags",
                "ec2:RunInstances"
            ],
            "Resource": "*"
        }
    ]
}

参考

Denoのソースビルドを試してみる

ちょっとしたツールをつくる必要があった際に、 TypeScriptで書きたいなー、そういえばDenoっていうのがあったなー、 と思い出しました。
公式サイトを見ているとソースビルドの手順が書かれていて、気になったので試してみました。

基本的に公式サイトの手順に沿うだけですが、数箇所エラーが発生したので、その際の対応を書いていきます。

Deno とは?

こちらの記事で概要を把握できると思います。
Deno について知っていることと、今後への期待 v1811

環境

手順

https://deno.land/manual.html#buildfromsource

ソースダウンロード

mkdir ~/github.com/denoland
cd ~/github.com/denoland
git clone --recurse-submodules https://github.com/denoland/deno.git
cd deno

セットアップスクリプト

$ ./tools/setup.py
(省略...)
Done. Made 431 targets from 90 files in 1120ms
(省略...)
Done. Made 431 targets from 90 files in 1051ms

問題なく実行できました。

ビルド

エラーが発生しました。 Rustファイルで発生しており、dynというものが非推奨になっているため、発生しているみたいです。

$ ./tools/setup.py
error: trait objects without an explicit `dyn` are deprecated
  --> ../../core/isolate.rs:55:19
   |
55 | type DispatchFn = Fn(&[u8], Option<PinnedBuf>) -> Op;
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `dyn`: `dyn Fn(&[u8], Option<PinnedBuf>) -> Op`
   |
   = note: `-D bare-trait-objects` implied by `-D warnings`

error: trait objects without an explicit `dyn` are deprecated
  --> ../../core/isolate.rs:58:20
   |
58 | type DynImportFn = Fn(&str, &str) -> DynImportFuture;
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `dyn`: `dyn Fn(&str, &str) -> DynImportFuture`

error: trait objects without an explicit `dyn` are deprecated
   --> ../../core/modules.rs:593:32
    |
593 |     fn cause(&self) -> Option<&Error> {
    |                                ^^^^^ help: use `dyn`: `dyn Error`

deprecatedというのは、バージョン互換性関連でよく見る単語なので、 Rustのバージョンを1.37.0-nightlyから下げてみます。

Denoのドキュメントには、

Rust >= 1.34.1

と書かれているので、1.34.1まで下げたほうがよさそうですが、 なるべく新しいバージョンで進めたいので 1.35.0 で試してみます。

Rustでは、プロジェクトディレクトリに、rust-toolchainというバージョン情報を記載したファイルを用意しておくと、そのバージョンを使用してくれます。
プロジェクトで使用するRustツールチェインのバージョンをチームで共有する

# rust-toolchain作成前
$ rustup show
Default host: x86_64-apple-darwin

installed toolchains
--------------------

stable-x86_64-apple-darwin
nightly-x86_64-apple-darwin (default)
1.35.0-x86_64-apple-darwin

active toolchain
----------------

nightly-x86_64-apple-darwin (default)
rustc 1.37.0-nightly (5f3656ce9 2019-06-11)
# rust-toolchain作成後
$ cat rust-toolchain
1.35.0

# 未インストールのバージョンの場合、rustup showのタイミングでダウンロードしてくれる
$ rustup show
Default host: x86_64-apple-darwin

installed toolchains
--------------------

stable-x86_64-apple-darwin
nightly-x86_64-apple-darwin (default)
1.35.0-x86_64-apple-darwin

active toolchain
----------------

1.35.0-x86_64-apple-darwin (overridden by '/Users/shootacean/github.com/denoland/deno/rust-toolchain')
rustc 1.35.0 (3c235d560 2019-05-20)

再度、ビルドスクリプトを実行する!

$ ./tools/build.py
ninja: Entering directory `/Users/shootacean/github.com/denoland/deno/target/debug'
[17/264] ACTION //build_extra/rust:log_rustc(//build/toolchain/mac:clang_x64)
FAILED: rust_crates/liblog.rlib 
python ../../build_extra/rust/run.py /Users/shootacean/github.com/denoland/deno/prebuilt/mac/sccache rustc ../../third_party/rust_crates/registry/src/github.com-1ecc6299db9ec823/log-0.4.6/src/lib.rs --crate-name=log --crate-type=rlib --emit=link,dep-info --edition=2015 --out-dir=rust_crates -Cextra-filename= -Cmetadata=\"_rustc\ 1.37.0-nightly\ \(5f3656ce9\ 2019-06-11\)\" -L dependency=rust_crates --color=always -g -Dwarnings --cap-lints allow --extern cfg_if=rust_crates/libcfg_if.rlib
error[E0514]: found crate `cfg_if` compiled by an incompatible version of rustc
   --> ../../third_party/rust_crates/registry/src/github.com-1ecc6299db9ec823/log-0.4.6/src/lib.rs:287:1
    |
287 | extern crate cfg_if;
    | ^^^^^^^^^^^^^^^^^^^^
    |
    = help: please recompile that crate using this compiler (rustc 1.35.0 (3c235d560 2019-05-20))
    = note: the following crate versions were found:
            crate `cfg_if` compiled by rustc 1.37.0-nightly (5f3656ce9 2019-06-11): /Users/shootacean/github.com/denoland/deno/target/debug/rust_crates/libcfg_if.rlib

error: aborting due to previous error

For more information about this error, try `rustc --explain E0514`.
(以降省略...)

また別のエラーが発生しました:frowning2:

= note: the following crate versions were found: crate cfg_if compiled by rustc 1.37.0-nightly (5f3656ce9 2019-06-11): /Users/shootacean/github.com/denoland/deno/target/debug/rust_crates/libcfg_if.rlib

エラーメッセージを見た感じ、ビルド失敗時の情報が残っていることが原因っぽいです。 なので、./target/debug/rust_cratesディレクトリを削除して、再度ビルドスクリプトを実行します!! (./targetディレクトリ配下をまるごと消してしまった場合は、再度セットアップスクリプトを実行してください。)

$ ./tools/build.py
ninja: Entering directory `/Users/shootacean/github.com/denoland/deno/target/debug'
[698/698] STAMP obj/default.stamp
$  

問題なく、ビルドできました!:grinning:

$ ./target/debug/deno run tests/002_hello.ts
[1/1] Compiling file:///Users/shootacean/github.com/denoland/deno/tests/002_hello.ts
Hello World

Hello World も問題なく実行できました!:grinning:

テストスクリプト

./tools/test.py
gn_string (setup_test.TestSetup) ... ok
read_gn_args (setup_test.TestSetup) ... ok
write_gn_args (setup_test.TestSetup) ... ok
...省略
----------------------------------------------------------------------
Ran 163 tests in 367.339s

OK (skipped=1)

問題なし!:grinning:

リリースビルド

リリースビルドも試してみます。

$ ./tools/build.py --release deno
ninja: Entering directory `/Users/shootacean/github.com/denoland/deno/target/release'
[656/656] LINK ./deno

ビルドしたバイナリで、Hello Worldを実行します!

$ ./target/release/deno tests/002_hello.ts
Hello World

実行できました!:ok_hand:

感想

言語処理系周りを学ぶのは初めてハードルが高そうですが、 TypeScriptは好きな言語ですし、Rustも興味あるので、Denoをチェックしていこうと思います!

Cloud Functions for Firebaseデプロイ時のChange of function trigger type or event provider is not allowedを解決する

HTTPSリクエストトリガーとして定義していた関数を、PubSubスケジュールトリガーとしてデプロイしようとした際に発生しました。

// before
export const helloWorld = functions
  .https
  .onRequest((request, response) => {
    response.send("Hello from Firebase!");
  });
// after
export const helloWorld = functions
  .pubsub
  .schedule('every day 03:00')
  .timezone('Asia/Tokyo')
  .onCall((context) => {
    console.log("Hello from Firebase!");
  });
$ firebase deploy --only functions
...
HTTP Error: 400, Change of function trigger type or event provider is not allowed
...

解決方法

対象の関数を削除し、再度デプロイすると、問題なくデプロイできます。

# 関数の削除 ( HTTPSリクエストトリガー )
$ firebase functions:delete helloWorld
# 関数のデプロイ ( PubSubスケジュールトリガー )
$ firebase deploy --only functions:helloWorld