ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Terraform内でGithubのreleaseをダウンロードして利用する

おはようございます、こんにちは、こんばんわ ユニファでインフラみてますすずきです。

緊急事態宣言も解除されましたがリモートワークで引きこもり中です。

そんな中、TerraformでGithubからパッケージをダウンロードしながらLambdaにアップロードできるのか試してみたのでそれを書いてみます。

今回ダウンロードしてみるものはDatadog Forwarderです。
プライベートな話ですが、DatadogでLambdaのEnhanced Metricsを取得する記事を書いていたりするのですが(リンクは載せない)、それに利用するのがこのDatadog Forwarderです。
Datadog ForwarderをTerraformで導入しようとすると、Terraform使ってるのにCloudFormationを呼び出してYAMLを食わせるパターンになってしまいます。それはやりたくないということでダウンロードしてみる方法を試してみました。

※ 今回の最終的なコードGist

Terraform のいろんなProvider

TerraformはProviderを利用することでいろいろな機能を使えます。

AWSのProviderを使えばAWSを、GCPのProviderを使えばGCPを操作、管理でき、 クラウドサービスだけではなく、PostgreSQLなどの操作なども行えます。 変わり種だとJiraやGsuiteなどのProviderが存在しています。

また表題とは異なり TerraformでLambdaをデプロイする際は、Terraformと一緒にコードも保存しておき、 Archive Providerを利用して、コードをアーカイブしてからLambda(AWS Provider)にアップロードします。

利用するProvider

表題のようにTerraformでダウンロードしてLambdaへアップロードするには複数のProviderを組み合わせる必要があります。

利用するプロバイダーは以下

GitHubからDownload URLを取得

今回の場合ではリリースされているパッケージをDownloadしてくる必要があるので、GitHub Providergithub_releaseを利用して情報を取得します。

provider github {
  anonymous = true   # GitHubの認証しませんよみたいな
  individual =  true      # 組織に属さない個人ですよみたいな
}

# リポジトリの情報を記入
data github_release dd_forwarder {
  repository  = "datadog-serverless-functions"
  owner       = "DataDog"
  retrieve_by = "tag"
  release_tag = var.release_tag  # いま時点の最新(aws-dd-forwarder-3.12.0)を指定
}

github_releaseで取得できる情報はいろいろあるのですが、必要なのは asserts_url になります。zipball_url などを利用するとすぐダウンロードできるURLとなっているのですが、 pip install などされてないSource codeが帰ってくるので利用できません。 仮にそれをLambdaにアップロードしてもファイル構造が違うので動いたりしません。

asserts_url の現在の中身 : https://api.github.com/repos/DataDog/datadog-serverless-functions/releases/27002556/assets

# Terraformを実行して確認したい場合これを追加
output datadog_forwarder_asserts_url {
  value = data.github_release.dd_forwarder.asserts_url
}

これをブラウザで見てみると以下のJsonが帰ってきます。

[
  {
    "url": "https://api.github.com/repos/DataDog/datadog-serverless-functions/releases/assets/21152267",
    "id": 21152267,
    "node_id": "MDEyOlJlbGVhc2VBc3NldDIxMTUyMjY3",
    "name": "aws-dd-forwarder-3.12.0.zip",
    "label": "",
    "uploader": {
      "login": "DarcyRaynerDD",
      "id": 50632605,
      "node_id": "MDQ6VXNlcjUwNjMyNjA1",
      "avatar_url": "https://avatars1.githubusercontent.com/u/50632605?v=4",
      "gravatar_id": "",
      "url": "https://api.github.com/users/DarcyRaynerDD",
      "html_url": "https://github.com/DarcyRaynerDD",
      "followers_url": "https://api.github.com/users/DarcyRaynerDD/followers",
      "following_url": "https://api.github.com/users/DarcyRaynerDD/following{/other_user}",
      "gists_url": "https://api.github.com/users/DarcyRaynerDD/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/DarcyRaynerDD/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/DarcyRaynerDD/subscriptions",
      "organizations_url": "https://api.github.com/users/DarcyRaynerDD/orgs",
      "repos_url": "https://api.github.com/users/DarcyRaynerDD/repos",
      "events_url": "https://api.github.com/users/DarcyRaynerDD/events{/privacy}",
      "received_events_url": "https://api.github.com/users/DarcyRaynerDD/received_events",
      "type": "User",
      "site_admin": false
    },
    "content_type": "application/octet-stream",
    "state": "uploaded",
    "size": 12235591,
    "download_count": 104,
    "created_at": "2020-05-28T18:01:46Z",
    "updated_at": "2020-05-28T18:01:47Z",
    "browser_download_url": "https://github.com/DataDog/datadog-serverless-functions/releases/download/aws-dd-forwarder-3.12.0/aws-dd-forwarder-3.12.0.zip"
  }
]

一番下に browser_download_url という値が存在しているのでそれを利用したい、がブラウザで見た結果なのでこの情報をTerraformに取得させる必要があります。

HTTP ProviderでJson取得

JsonをGETしたいということで、HTTP Providerを使ったらいいじゃないか、とういことで以下のようにします。

data http dd_forwarder {
  url = data.github_release.dd_forwarder.asserts_url

  request_headers = {
    Accept = "application/json"
  }
}

# 結果が見たいので追加
output datadog_forwarder_donwload_url {
  value = jsondecode(data.http.dd_forwarder.body)[0]["browser_download_url"] # json textをmapに変換後情報取得
}

実行して帰ってきた結果は https://github.com/DataDog/datadog-serverless-functions/releases/download/aws-dd-forwarder-3.12.0/aws-dd-forwarder-3.12.0.zip となっているので先程のJsonと同じですね。

GitHub から Download する

URLも手に入ったからあとはファイルダウンロードの機能をつかえばいいよねと思うかもしれませんが、Terraformにそんなものはないのです。wget Providerとかwget,download functionなんてものはないのです。

そこで出てくるのがNull Provider、これを利用し空の枠を作ります。
※ Null以外でも動かせるかもしれないけど試してないのでNull、だが実際に利用するならNullを使うのが懸命かと

そこで、local-exec Provisionerを実行し、wgetを実行します。
※ TerraformのDocker Image(0.12.24しか試してませんが)はwgetは存在しているがcurlは存在しない

実際に書いてみたものはこちら

# ローカル変数設定してるだけ
locals {
  ddf_dl_path = format("%s/%s.zip", path.cwd, var.release_tag)
}

resource null_resource dd_forwarder {
  provisioner local-exec {
    command = "wget $dl_url -O $dl_path"

    environment = {
      dl_url = jsondecode(data.http.dd_forwarder.body)[0]["browser_download_url"]
      dl_path = local.ddf_dl_path
    }
  }
}

これを実行すると、頑張ってダウンロードしている結果が見れます。

null_resource.dd_forwarder: Creating...
null_resource.dd_forwarder: Provisioning with 'local-exec'...
null_resource.dd_forwarder (local-exec): Executing: ["/bin/sh" "-c" "wget $dl_url -O $dl_path"]
null_resource.dd_forwarder (local-exec): Connecting to github.com (52.192.72.89:443)
null_resource.dd_forwarder (local-exec): Connecting to github-production-release-asset-2e65be.s3.amazonaws.com (52.216.137.108:443)
null_resource.dd_forwarder (local-exec): saving to '/workdir/aws-dd-forwarder-3.12.0.zip'
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1   3% |*                               |  373k  0:00:30 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1   8% |**                              | 1036k  0:00:21 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  10% |***                             | 1223k  0:00:26 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  11% |***                             | 1410k  0:00:29 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  14% |****                            | 1682k  0:00:30 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  15% |*****                           | 1886k  0:00:32 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  17% |*****                           | 2141k  0:00:32 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  20% |******                          | 2413k  0:00:31 ETA
null_resource.dd_forwarder: Still creating... [10s elapsed]
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  22% |*******                         | 2702k  0:00:30 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  25% |********                        | 3008k  0:00:29 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  28% |********                        | 3348k  0:00:28 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  31% |**********                      | 3772k  0:00:26 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  36% |***********                     | 4366k  0:00:22 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  43% |*************                   | 5167k  0:00:18 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  51% |****************                | 6136k  0:00:14 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  57% |******************              | 6850k  0:00:11 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  63% |********************            | 7598k  0:00:09 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  70% |**********************          | 8380k  0:00:07 ETA
null_resource.dd_forwarder: Still creating... [20s elapsed]
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  76% |************************        | 9196k  0:00:05 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  83% |**************************      | 9961k  0:00:03 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  90% |****************************    | 10.5M  0:00:02 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1  97% |******************************* | 11.3M  0:00:00 ETA
null_resource.dd_forwarder (local-exec): aws-dd-forwarder-3.1 100% |********************************| 11.6M  0:00:00 ETA
null_resource.dd_forwarder (local-exec): '/workdir/aws-dd-forwarder-3.12.0.zip' saved
null_resource.dd_forwarder: Creation complete after 23s [id=5446667541515964428]

Lambda へアップロード

あとは、Lambdaへダウンロードしてきたファイルをアップロードしてやれば完了です。

 provider aws {
   region  = "ap-northeast-1"
 }

resource aws_lambda_function dd_forwarder {
  filename      = local.ddf_dl_path
  function_name = "lambda_function_name"
  handler       = "lambda_function.lambda_handler"
  role          = var.lambda_role

  runtime = "python3.7"

  depends_on = [ null_resource.dd_forwarder ] # ダウンロードが終わってから実行されるように順序調整
}

問題あり

前段ではTerrafomつかえば、ファイルダウンロードしてのアップロードもできるじゃんって思えそうですが課題はあります。

ローカルで手動実行しているぶんにはみえて来ないのですが、GitHub ActionやCircleCIなどでこのようなコードを実行した場合に2回目からは以下のようなエラーが出てしまいます。

aws_lambda_function.dd_forwarder: Creating...

Error: Unable to load "/workdir/aws-dd-forwarder-3.12.0.zip": open /workdir/aws-dd-forwarder-3.12.0.zip: no such file or directory

  on main.tf line 48, in resource "aws_lambda_function" "dd_forwarder":
  48: resource aws_lambda_function dd_forwarder {

これは、以下の要因で発生します。

  • 1度目の実行時のNull リソースの管理情報が保存されてる
    • terraform管理情報と差分がでないと処理が動かないのでwgetは動かない
  • CI環境のストレージはリセットされている
    • 1回目にダウンロードしたファイルは存在しない
  • ダウンロードは終わったつもりでLambda(AWS Provider)がファイル操作しようとした

これを根本から解決するには、結局zipファイルをどこかに保存しておくかキャッシュするなどCI環境側での頑張りが必要だと思われます。

Terrarom側だけでなんとかするには null_resource の処理を以下のように書き換えます。

resource null_resource dd_forwarder {
  triggers = {
    timestamp = timestamp() # 今の時間を返すfunction(randomとかでもよいかも)
  }

  provisioner local-exec {
    command = "wget $dl_url -O $dl_path"
    environment = {
      dl_url = jsondecode(data.http.dd_forwarder.body)[0]["browser_download_url"]
      dl_path = local.ddf_dl_path
    }
  }
}

triggersのmap情報が変更されるとnull_resource は再度実行されるので、時間情報を渡すことで毎回実行するようしてファイルが存在し続けるようにします。

これはこれで、Terraformに差分がないのに毎回実行されて時間の無駄処理になってしまうので課題が残ります…。

課題は残りましたが以上で、TerraformでGitHubからダウンロードしてLambdaにアップロードする処理となります。

こうしたら解決したよとか、こうしたほうが良いとかご意見あればお声掛けいただけたらと思います。

すずき