<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: nikaera</title>
    <description>The latest articles on Forem by nikaera (@nikaera).</description>
    <link>https://forem.com/nikaera</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F322552%2F453b2639-31e9-4d4d-9db5-4fcb23bc26b4.png</url>
      <title>Forem: nikaera</title>
      <link>https://forem.com/nikaera</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nikaera"/>
    <language>en</language>
    <item>
      <title>[TECH] OpenNext + Drizzle で Cloudflare D1 環境を最も楽に構築する 🌧️</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 02 Aug 2025 06:55:21 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-opennext-drizzle-de-cloudflare-d1-huan-jing-wozui-mole-nigou-zhu-suru-2055</link>
      <guid>https://forem.com/nikaera/tech-opennext-drizzle-de-cloudflare-d1-huan-jing-wozui-mole-nigou-zhu-suru-2055</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;OpenNext + Cloudflare D1 を使った開発で、ローカル環境での開発・テスト・本番への移行を効率的に行いたいと考えたことはありませんか。&lt;/p&gt;

&lt;p&gt;愚直に実現しようとすると、環境ごとに異なる設定ファイルを用意したり、テストデータの管理に手間がかかります。&lt;strong&gt;&lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle&lt;/a&gt; のエコシステム（&lt;code&gt;drizzle-orm&lt;/code&gt;、&lt;code&gt;drizzle-kit&lt;/code&gt;、&lt;code&gt;drizzle-seed&lt;/code&gt;）を活用することで、これらの課題を解決し、シームレスな開発体験を実現する方法を紹介します。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;この記事では、実際の &lt;a href="https://opennext.js.org/cloudflare" rel="noopener noreferrer"&gt;&lt;code&gt;@opennextjs/cloudflare&lt;/code&gt;&lt;/a&gt; プロジェクト構成を参考に、ローカル開発からテスト実装、本番デプロイまでの一連の流れを解説します。&lt;/p&gt;

&lt;h1&gt;
  
  
  動作環境
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@libsql/client"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.15.10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"@opennextjs/cloudflare"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.6.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"drizzle-orm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.44.4"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"devDependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"drizzle-kit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.31.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"drizzle-seed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^0.3.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vitest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^3.2.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"wrangler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.26.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  プロジェクト構成
&lt;/h1&gt;

&lt;p&gt;この記事で構築する最終的な関連したプロジェクト構造は以下のようになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./
├── drizzle/
│   ├── migrations/           &lt;span class="c"&gt;# マイグレーションファイルの格納先&lt;/span&gt;
│   │   └── 0000_init.sql
│   └── schema.ts            &lt;span class="c"&gt;# データベーススキーマの定義&lt;/span&gt;
├── tests/
│   ├── db.test.ts           &lt;span class="c"&gt;# drizzle-seed を使ったテスト&lt;/span&gt;
│   └── db.ts                &lt;span class="c"&gt;# テスト用 DB のユーティリティ&lt;/span&gt;
├── drizzle.config.ts        &lt;span class="c"&gt;# 各種 DB の設定ファイル&lt;/span&gt;
├── wrangler.jsonc           &lt;span class="c"&gt;# Cloudflare Workers 設定 (D1)&lt;/span&gt;
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. 必要なパッケージのインストール
&lt;/h2&gt;

&lt;p&gt;まず、必要なパッケージをインストールします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;drizzle-orm @libsql/client
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; drizzle-kit drizzle-seed vitest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Cloudflare D1 の設定
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;wrangler.jsonc&lt;/code&gt; に D1 データベースの情報を追記します。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;your-database-name&amp;gt;&lt;/code&gt; と &lt;code&gt;&amp;lt;your-database-id&amp;gt;&lt;/code&gt; には、D1 データベースの「データベース名」と「データベースID」を指定します。値の確認・発行方法は後述します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node_modules/wrangler/config-schema.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-app-name&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".open-next/worker.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compatibility_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-03-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compatibility_flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"nodejs_compat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"global_fetch_strictly_public"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Cloudflare&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;D&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;データベースの情報&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"d1_databases"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DB"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"database_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-database-name&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"database_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-database-id&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"migrations_dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drizzle/migrations"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  値の確認・発行方法
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare ダッシュボードから確認&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dash.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Dashboard&lt;/a&gt; にログインし、
対象アカウントの「ストレージとデータベース」→「D1 SQL データベース」へ移動する&lt;/li&gt;
&lt;li&gt;作成済みのデータベース一覧から、該当データベースを選択する&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;your-database-name&amp;gt;&lt;/code&gt; は一覧や詳細画面での表示名

&lt;ul&gt;
&lt;li&gt;例: &lt;code&gt;my-app-dev&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;&amp;lt;your-database-id&amp;gt;&lt;/code&gt; は詳細画面の「UUID」

&lt;ul&gt;
&lt;li&gt;例: &lt;code&gt;e216461a-74c3-40b2-8819-9fa351827304&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://gyazo.com/f01b717bc30e793e7630ca613b364946" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9qnckzif6c4xz7d00nc7.png" alt="ダッシュボード上の  raw `my-app-dev` endraw  の情報" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Wrangler CLI で新規作成する場合&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;下記コマンド成功時に出力される JSON フィールドの値を用いる 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;database_name&lt;/code&gt; が &lt;code&gt;&amp;lt;your-database-name&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;database_id&lt;/code&gt; が &lt;code&gt;&amp;lt;your-database-id&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler d1 create my-app-dev
&lt;span class="c"&gt;# ...&lt;/span&gt;
Successfully created DB &lt;span class="s1"&gt;'my-app-dev'&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;region APAC
Created your new D1 database.

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"d1_databases"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"binding"&lt;/span&gt;: &lt;span class="s2"&gt;"DB"&lt;/span&gt;,
      &lt;span class="s2"&gt;"database_name"&lt;/span&gt;: &lt;span class="s2"&gt;"my-app-dev"&lt;/span&gt;,
      &lt;span class="s2"&gt;"database_id"&lt;/span&gt;: &lt;span class="s2"&gt;"e216461a-74c3-40b2-8819-9fa351827304"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;これらの値を &lt;code&gt;wrangler.jsonc&lt;/code&gt; の該当箇所にコピー&amp;amp;ペーストしてください。&lt;/p&gt;

&lt;h2&gt;
  
  
  3. 環境別のデータベース設定
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;drizzle.config.ts&lt;/code&gt; に&lt;strong&gt;本番環境とローカル環境の設定を記載します。&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;環境の切り分けには &lt;code&gt;NODE_ENV&lt;/code&gt; 環境変数を利用しており、&lt;br&gt;&lt;br&gt;
&lt;code&gt;NODE_ENV=production&lt;/code&gt; の場合は本番用、そうでない場合はローカル用の設定が適用されます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readdirSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isProduction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// `sqliteDirPath` は、wrangler で D1 環境を設定し、`dev` コマンドを実行した際に&lt;/span&gt;
&lt;span class="c1"&gt;// SQLite ファイルが生成されるディレクトリパスを指します。&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqliteDirPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.wrangler/state/v3/d1/miniflare-D1DatabaseObject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sqliteFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sqliteDirPath&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isProduction&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./drizzle/migrations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./drizzle/schema.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dialect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;d1-http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dbCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFLARE_ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;databaseId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFLARE_DATABASE_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CLOUDFLARE_D1_TOKEN&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./drizzle/migrations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./drizzle/schema.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dialect&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sqlite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;dbCredentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sqliteDirPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sqliteFilePath&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;wrangler&lt;/code&gt; のバージョンや環境によっては、SQLite ファイルの生成先ディレクトリが &lt;code&gt;.wrangler/state/v3/d1/miniflare-D1DatabaseObject&lt;/code&gt; ではなく、&lt;code&gt;.wrangler&lt;/code&gt; 配下の異なるパスになる可能性があります。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.wrangler&lt;/code&gt; 配下の SQLite ファイルのパスが不明な場合は、&lt;code&gt;find&lt;/code&gt; コマンドで検索し特定できます。たとえば、以下のように実行すると、&lt;code&gt;.sqlite&lt;/code&gt; 拡張子のファイルを探せます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find .wrangler &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sqlite"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare D1 の本番環境の設定には、以下の環境変数が必要です。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CLOUDFLARE_ACCOUNT_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLOUDFLARE_DATABASE_ID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CLOUDFLARE_D1_TOKEN&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本番環境設定の取得方法に関しては下記を参考にしてください。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://zenn.dev/arafipro/articles/2024-07-24-drizzle-d1-table#drizzle.config.ts-1" rel="noopener noreferrer"&gt;https://zenn.dev/arafipro/articles/2024-07-24-drizzle-d1-table#drizzle.config.ts-1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. データベーススキーマの定義
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;drizzle/schema.ts&lt;/code&gt; で以下の2つのテーブルを定義します。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;conversations テーブル&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
チャットの会話単位を管理するテーブル&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;messages テーブル&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
各会話に紐づくメッセージを管理するテーブル、&lt;br&gt;&lt;br&gt;
&lt;code&gt;conversationId&lt;/code&gt; で &lt;code&gt;conversations&lt;/code&gt; テーブルとリレーションする。&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;integer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm/sqlite-core&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;conversations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`(unixepoch())`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`(unixepoch())`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sqliteTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;conversationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;conversation_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;references&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cascade&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;role&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;timestamp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`(unixepoch())`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. テスト環境の構築
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tests/db.ts&lt;/code&gt; でテスト用のユーティリティ関数を作成します。テスト用データベースの作成・マイグレーションの適用・テストデータの投入などを行う関数をまとめています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@libsql/client/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;drizzle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm/libsql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../drizzle/schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reset&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-seed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;createTestDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:memory:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;drizzle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// `drizzle-kit` で生成された、実際のマイグレーションファイルを読み込む&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;migrationsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle/migrations&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;migrationFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;migrationsDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.sql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 正しい順序で逐次マイグレーションファイルを実行する&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;migrationFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;migrationSQL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;migrationsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;migrationSQL&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--&amp;gt; statement-breakpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;statements&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;statement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;seedTestDataWithDrizzleSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;seedValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

  &lt;span class="c1"&gt;// `drizzle-seed` の `refine` でテスト時に利用するシードデータを生成&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;seedValue&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;refine&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valuesFromArray&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AI System Chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Technical Q&amp;amp;A Session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Creative Writing Help&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Code Review Discussion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product Planning Talk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valuesFromArray&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valuesFromArray&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello! How can you help me today?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;I&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;m an AI system, happy to help with various tasks!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Can you explain how TypeScript interfaces work?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TypeScript interfaces define the shape of objects...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;What are the best practices for React development?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Some key React best practices include using functional components...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resetDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. テストの実装
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tests/db.test.ts&lt;/code&gt; で実際のテストを実装します。このテストで、データベースの接続やクエリ、モデルの動作や挙動などが正しく機能するかどうかを検証します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;afterEach&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vitest&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../drizzle/schema&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;eq&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;drizzle-orm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="nx"&gt;createTestDatabase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;seedTestDataWithDrizzleSeed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nx"&gt;resetDatabase&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Drizzle Seed Test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;testDb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createTestDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;testDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should seed test data using drizzle-seed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;seedTestDataWithDrizzleSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 会話データの検証&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allConversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allConversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// メッセージデータの検証&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 外部キー制約の検証&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;allMessages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;conversation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;allConversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="na"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; 
        &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conversationId&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conversation&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ロール値の検証&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;allMessages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;system&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should reset database correctly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;seedTestDataWithDrizzleSeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;allConversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allConversations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;resetDatabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;allConversations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;conversations&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allConversations&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allMessages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  実際の開発フロー
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;予め &lt;code&gt;package.json&lt;/code&gt; の &lt;code&gt;scripts&lt;/code&gt; へ下記を登録しておきます。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitest run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"db:gen"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drizzle-kit generate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"db:mgn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drizzle-kit migrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"db:mgn:prd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NODE_ENV=production drizzle-kit migrate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"db:client"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drizzle-kit studio"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  1. DB のローカル環境構築
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Wrangler dev でローカル開発環境を起動&lt;/span&gt;
npx wrangler dev

&lt;span class="c"&gt;# 別ターミナルでマイグレーションを実行&lt;/span&gt;
npm run db:gen &lt;span class="c"&gt;# 必要に応じて `-- --name &amp;lt;ファイル名&amp;gt;`&lt;/span&gt;
npm run db:mgn

&lt;span class="c"&gt;# 適宜 DB 絡むテストを実行&lt;/span&gt;
npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drizzle には、&lt;a href="https://orm.drizzle.team/drizzle-studio/overview" rel="noopener noreferrer"&gt;Drizzle Studio&lt;/a&gt; という GUI で DB を確認可能なツールが存在します。&lt;br&gt;&lt;br&gt;
Drizzle Studio は下記コマンドで起動可能です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# DB クライアント (Drizzle Studio) の起動&lt;/span&gt;
npm run db:client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. DB の本番環境への反映
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 本番環境でのマイグレーション&lt;/span&gt;
npm run db:mgn:prd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;正常にコマンド実行が成功すれば、Cloudflare Dashboard の「D1 SQL データベース」から &lt;code&gt;/tables&lt;/code&gt; コマンドで生成されたテーブルが確認できるはずです。&lt;br&gt;
&lt;a href="https://gyazo.com/1ca0a8b4ad54d02a93ae568ca92b8f87" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21xvc8vip7lln5vqi9mh.png" alt="コンソールから  raw `/tables` endraw  でテーブルを確認" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  まとめ
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Drizzle のエコシステムを活用することで、OpenNext + Cloudflare D1 を使った開発・テスト・本番環境をシームレスに構築できました。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenNext を利用したい動機として、複雑な設定や環境構築に時間を取られることなく、迅速かつ効率的にアプリケーションの実装を進めたいというモチベで採用されることが大半だと考えます。&lt;/p&gt;

&lt;p&gt;本記事の手法を使うことで、&lt;strong&gt;データベース周りの煩雑な設定や運用から解放されます。&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
そのため、アプリケーションロジックの実装に集中できます。爆速で開発を進めていきましょう。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://orm.drizzle.team/" rel="noopener noreferrer"&gt;Drizzle ORM - next gen TypeScript ORM.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.cloudflare.com/d1/" rel="noopener noreferrer"&gt;Overview · Cloudflare D1 docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orm.drizzle.team/docs/kit-overview" rel="noopener noreferrer"&gt;Drizzle ORM - Migrations with Drizzle Kit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orm.drizzle.team/docs/seed-overview" rel="noopener noreferrer"&gt;Drizzle ORM - Drizzle Seed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opennext.js.org/cloudflare" rel="noopener noreferrer"&gt;Index - OpenNext&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>drizzleorm</category>
      <category>cloudflare</category>
      <category>d1</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>[IDEA] Flutter で本格的な HTTP キャッシュライブラリ「AeroCache」を作ってみた ☁️</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sun, 13 Jul 2025 06:29:16 +0000</pubDate>
      <link>https://forem.com/nikaera/idea-flutter-deben-ge-de-na-http-kiyatusiyuraiburariaerocache-wozuo-tutemita-477p</link>
      <guid>https://forem.com/nikaera/idea-flutter-deben-ge-de-na-http-kiyatusiyuraiburariaerocache-wozuo-tutemita-477p</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;Flutter は既存のキャッシュライブラリに &lt;a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/Caching" rel="noopener noreferrer"&gt;HTTP のプライベートキャッシュ&lt;/a&gt; に準拠したものが少なく、想定と異なる挙動により意図した形でキャッシュが更新されない状況に遭遇することが多々あります。例えば、&lt;a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Headers/Vary" rel="noopener noreferrer"&gt;Vary&lt;/a&gt;に未対応であったり、キャッシュ再検証周りのロジックが独自実装だったりが該当します。&lt;/p&gt;

&lt;p&gt;そこで、&lt;a href="https://datatracker.ietf.org/doc/html/rfc9111" rel="noopener noreferrer"&gt;RFC 9111&lt;/a&gt; に準拠したキャッシュライブラリ &lt;strong&gt;&lt;a href="https://pub.dev/packages/aero_cache" rel="noopener noreferrer"&gt;AeroCache&lt;/a&gt;&lt;/strong&gt; を開発しました。この記事では、その実装過程で学んだ HTTP のプライベートキャッシュに関する知識を簡潔に解説しつつ AeroCache を紹介します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nikaera.com/aero_cache/" rel="noopener noreferrer"&gt;https://nikaera.com/aero_cache/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  HTTP のプライベートキャッシュの仕組み
&lt;/h1&gt;

&lt;p&gt;主にパフォーマンス向上や帯域節約、細やかなキャッシュ管理を実現するための仕組みとして、活用されるヘッダは以下の通りです。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ヘッダー&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cache-Control&lt;/td&gt;
&lt;td&gt;レスポンスキャッシュの有効期限や再検証ルールを指示するディレクティブ群、&lt;code&gt;max-age&lt;/code&gt; や &lt;code&gt;no-cache&lt;/code&gt; などが代表的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETag&lt;/td&gt;
&lt;td&gt;レスポンスごとに発行される一意の識別子、クライアントは &lt;code&gt;ETag&lt;/code&gt; を使って「内容が変わったか」を効率的に判定可能&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Last-Modified&lt;/td&gt;
&lt;td&gt;レスポンスの最終更新日時、&lt;code&gt;If-Modified-Since&lt;/code&gt; ヘッダーと組み合わせて、キャッシュの更新判定に利用される&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vary&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Accept-Language&lt;/code&gt; や &lt;code&gt;User-Agent&lt;/code&gt; などを指定することで、端末やリクエストごとに異なるキャッシュを保持可能&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;以降では、各要素の具体的な動作について詳しく解説します。&lt;/p&gt;

&lt;h2&gt;
  
  
  主要な &lt;code&gt;Cache-Control&lt;/code&gt; ディレクティブの対応
&lt;/h2&gt;

&lt;p&gt;HTTP レスポンスヘッダーの &lt;code&gt;Cache-Control&lt;/code&gt; は、キャッシュ動作を制御する重要な仕組みです。&lt;br&gt;
プライベートキャッシュの &lt;code&gt;Cache-Control&lt;/code&gt; で用いられる主要なディレクティブは以下です。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ディレクティブ&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max-age=N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;N秒間キャッシュを有効とみなす&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;min-fresh=N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;有効期限よりもN秒以上最新のデータのみ許容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;max-stale[=N]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;有効期限切れN秒以内なら古いキャッシュ許容（N省略時は無制限）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stale-while-revalidate=N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;有効期限切れN秒以内なら古いキャッシュ返却、裏側で更新処理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stale-if-error=N&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;エラー時はN秒間古いキャッシュで代替&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;must-revalidate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;期限切れ時は必ずキャッシュの再検証&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-cache&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用前に必ずキャッシュの再検証&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;キャッシュしない（毎回ダウンロード）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;only-if-cached&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ネットワークアクセスせずキャッシュのみ利用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cache-Control: max-age=600, stale-while-revalidate=3600
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;上記の設定では、以下のような動作になります。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;最初の600秒間はキャッシュを新鮮とみなして即座に返す&lt;/li&gt;
&lt;li&gt;600秒経過後、3600秒までは古いデータを返しつつバックグラウンドで更新&lt;/li&gt;
&lt;li&gt;ユーザーは常に高速なレスポンスを得られる&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;特に &lt;code&gt;stale-while-revalidate&lt;/code&gt; は、ユーザー体験とパフォーマンスの両立を実現する強力なキャッシュ戦略です。新しいデータの取得は裏側で自動的に行われるため、ユーザーは操作を中断することなく最新のデータが利用可能になります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AeroCache はブラウザの挙動を強く意識しており、上記ディレクティブは全てサポートしています。ユーザーや開発者がキャッシュに関する挙動に悩まされないよう、独自ロジックなども極力排除しています。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  ETag や Last-Modified による効率的な再検証
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# 初回レスポンス
&lt;/span&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;ETag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"33a64df551425fcc55e4d42a148795d9f25f89d4"&lt;/span&gt;
&lt;span class="na"&gt;Cache-Control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;max-age=3600&lt;/span&gt;

# 再検証リクエスト
GET /api/data HTTP/1.1
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# 変更なしの場合
HTTP/1.1 304 Not Modified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;ETag や Last-Modified の利点は以下の通りです。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;キャッシュが有効な場合&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;クライアントはキャッシュをそのまま返却&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;キャッシュ期限切れの場合&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;クライアントは ETag/Last-Modified を付与して再検証リクエストを送信&lt;/li&gt;
&lt;li&gt;データに変更無ければ &lt;code&gt;304 Not Modified&lt;/code&gt; を返し、キャッシュを再利用&lt;/li&gt;
&lt;li&gt;データに変更があれば 新しいデータを返却し、キャッシュを更新&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;この仕組みにより、無駄なデータ転送を防ぎつつ常に最新データを効率的に取得できます。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;再検証周りのロジックが独自実装のライブラリは、再起動しない限りキャッシュが更新されなかったりします。AeroCache ではそのような挙動は発生しません。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Vary による条件付きキャッシュ
&lt;/h2&gt;

&lt;p&gt;レスポンスがリクエストヘッダーに依存する場合の重要な仕組みです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="ne"&gt;OK&lt;/span&gt;
&lt;span class="na"&gt;Vary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Accept-Language, User-Agent&lt;/span&gt;
&lt;span class="na"&gt;Content-Language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ja&lt;/span&gt;
&lt;span class="na"&gt;Cache-Control&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;max-age=3600&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;この例では、&lt;code&gt;Accept-Language&lt;/code&gt; と &lt;code&gt;User-Agent&lt;/code&gt; の値が異なるリクエストは、別々にキャッシュされます。&lt;/p&gt;

&lt;p&gt;Vary ヘッダーはキャッシュの粒度を柔軟に制御できる仕組みです。例えば、言語やデバイスごとに異なるレスポンスを返す API では、Vary を使うことで下記のように「リクエストヘッダーごとに別々のキャッシュ」を作成できます。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;多言語対応: &lt;code&gt;Accept-Language&lt;/code&gt; を指定すれば、ユーザーの言語ごとに最適なキャッシュを保持&lt;/li&gt;
&lt;li&gt;デバイス最適化: &lt;code&gt;User-Agent&lt;/code&gt; を使えば、デバイス間で異なるレスポンスをキャッシュ可能&lt;/li&gt;
&lt;li&gt;パーソナライズ: &lt;code&gt;Cookie&lt;/code&gt; や &lt;code&gt;Authorization&lt;/code&gt; など、個別ユーザー向けキャッシュも実現可能&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;AeroCache では Vary を考慮した設計となっており Vary でキャッシュコントロールが可能です。&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;実業務で悩まされてきた問題に終止符を打つべく開発した AeroCache について紹介しました。&lt;/p&gt;

&lt;p&gt;標準仕様に沿ったキャッシュ管理で予期せぬ不具合や運用負荷を減らし、安心して利用できる HTTP キャッシュライブラリを目指しました。今後も実運用で得られた知見やフィードバックをもとに、より使いやすく信頼性の高いライブラリへと改善を続けていきます。&lt;/p&gt;

&lt;p&gt;また、現状は利便性向上のための追加実装として下記を検討しています。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pub.dev/packages/cached_network_image" rel="noopener noreferrer"&gt;cached_network_image&lt;/a&gt; の AeroCache 版&lt;/li&gt;
&lt;li&gt;HTTP クライアントの Interceptor 開発 (dio, http, etc.)&lt;/li&gt;
&lt;li&gt;コンテンツに影響しない URL パラメータのブラックリスト対応&lt;/li&gt;
&lt;li&gt;コンテンツ毎の可逆圧縮有無の設定, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/nikaera/aero_cache" rel="noopener noreferrer"&gt;nikaera/aero_cache: A high-performance HTTP caching library for Dart/Flutter with zstd compression, ETag/Last-Modified revalidation, and full Cache-Control directive support. ☁️&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Guides/Caching" rel="noopener noreferrer"&gt;HTTP キャッシュ - HTTP | MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tex2e.github.io/rfc-translater/html/rfc9111.html" rel="noopener noreferrer"&gt;RFC 9111 - HTTP Caching (RFC 9111) 日本語訳&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>http</category>
      <category>cache</category>
    </item>
    <item>
      <title>[TECH] ECS Fargate のメトリクスを Prometheus Agent 使って AMP に送って Grafana で監視する 🔥</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Mon, 06 Dec 2021 02:05:38 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-ecs-fargate-nometorikusuwo-prometheus-agent-shi-tute-amp-nisong-tute-grafana-dejian-shi-suru-4gb5</link>
      <guid>https://forem.com/nikaera/tech-ecs-fargate-nometorikusuwo-prometheus-agent-shi-tute-amp-nisong-tute-grafana-dejian-shi-suru-4gb5</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;この記事は &lt;a href="https://qiita.com/advent-calendar/2021/aws"&gt;AWS Advent Calendar 2021&lt;/a&gt; の 5 日目の記事です。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/jp/fargate/"&gt;Fargate&lt;/a&gt; で Node.js アプリのメトリクスを &lt;a href="https://prometheus.io/blog/2021/11/16/agent/"&gt;Prometheus Agent&lt;/a&gt; をサイドカーコンテナとして動かして、&lt;a href="https://aws.amazon.com/jp/prometheus/"&gt;Amazon Managed Service for Prometheus (AMP)&lt;/a&gt; に送信して &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt; で見られるようにしてみました。&lt;/p&gt;

&lt;p&gt;ちなみに Promethus Agent はまだ &lt;a href="https://prometheus.io/blog/2021/11/16/agent/#prometheus-agent-mode"&gt;実験的な機能&lt;/a&gt; なため、&lt;strong&gt;実務での利用は推奨しません。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本記事の環境構築には &lt;a href="https://aws.amazon.com/jp/cdk/"&gt;AWS CDK&lt;/a&gt; を利用しています。&lt;/p&gt;

&lt;h1&gt;
  
  
  動作環境
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Node.js v16.13.0&lt;/li&gt;
&lt;li&gt;AWS CDK 2.0.0 (build 4b6ce31)&lt;/li&gt;
&lt;li&gt;Prometheus 2.32.1&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  環境構築
&lt;/h1&gt;

&lt;p&gt;&lt;del&gt;早速環境構築を進めていきます。まだ AMP については CDK から操作できないようでしたので、ワークスペースの作成については AWS コンソールから手動で行います。(2021/12/06)&lt;/del&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/aws-aps-readme.html"&gt;&lt;code&gt;aws-aps&lt;/code&gt;&lt;/a&gt; を利用することで AWS CDK からでも Amazon Managed Service for Prometheus のワークスペースを作成すること確認できましたので、そちらの利用を推奨いたします... 🙇🙇&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="//#ecs-fargate-%E4%B8%8A%E3%81%A7-node.js-%E3%82%A2%E3%83%97%E3%83%AA%E3%81%8A%E3%82%88%E3%81%B3-prometheus-agent-%E3%82%92%E5%8B%95%E4%BD%9C%E3%81%95%E3%81%9B%E3%82%8B"&gt;lib/prometheus-agent-test-stack.ts&lt;/a&gt; のコードも修正済みで AWS CDK で Amazon Managed Service for Prometheus のワークスペースを作成するように編集しました。(2021/12/18 追記)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;まず、&lt;a href="https://ap-northeast-1.console.aws.amazon.com/prometheus/home"&gt;AMP のコンソール画面&lt;/a&gt; に遷移してワークスペースを作成します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3WPPbofs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/bf30611d6bc0f6b93281fd80123b611e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3WPPbofs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/bf30611d6bc0f6b93281fd80123b611e.png" alt="AMP のコンソール画面からワークスペースを作成する" width="880" height="387"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. &lt;a href="https://ap-northeast-1.console.aws.amazon.com/prometheus/home"&gt;AMP のコンソール画面&lt;/a&gt; からワークスペース作成画面に遷移する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Caskjzie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/7199bed8693832b26f4594bcf5541c86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Caskjzie--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/7199bed8693832b26f4594bcf5541c86.png" alt="2. AMP のワークスペースを作成する" width="880" height="449"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. AMP のワークスペースを作成する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qo-XSwGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e32231b46a716aaeb37c8ef88a02e434.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qo-XSwGZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e32231b46a716aaeb37c8ef88a02e434.png" alt="3. AMP のワークスペース作成完了と同時に設定値を控えておく" width="880" height="429"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. AMP のワークスペース作成完了を確認すると同時に設定値を控えておく&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AMP ワークスペースの &lt;code&gt;エンドポイント - リモート書き込み URL&lt;/code&gt; は、&lt;strong&gt;Prometheus Agent で AMP にデータ送信する際や、Grafana でデータソースを登録する際などに必要となるため控えておきます。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  AWS CDK で環境構築する
&lt;/h2&gt;

&lt;p&gt;CDK で構築作業を進めます。まずは下記コマンドで CDK プロジェクトを作成します。使用言語は &lt;code&gt;TypeScript&lt;/code&gt; を選択します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;prometheus-agent-test &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;prometheus-agent-test
cdk init &lt;span class="nt"&gt;--language&lt;/span&gt; typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;まず CDK でインフラ構築を進めていく前に、メトリクス収集テスト用の Node.js アプリを準備します。&lt;/p&gt;

&lt;h3&gt;
  
  
  ECS Fargate で動かす Node.js アプリを準備する
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/siimon/prom-client"&gt;prom-client&lt;/a&gt; を利用して、Node.js のメトリクスが取得できるだけの Node.js アプリを準備します。&lt;code&gt;prometheus-agent-test&lt;/code&gt; フォルダで下記コマンドを実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;metrics-app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;metrics-app
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; prom-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次に &lt;code&gt;metrics-app&lt;/code&gt; フォルダ内に &lt;code&gt;index.js&lt;/code&gt; を作成して下記を編集します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prom-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Registry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// 5秒間隔でメトリクスを取得する&lt;/span&gt;
&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;collectDefaultMetrics&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// /metrics にアクセスしたら、Prometheus のレポートを返す&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/metrics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;node index.js&lt;/code&gt; コマンドを実行して &lt;code&gt;http://localhost:8080/metrics&lt;/code&gt; にアクセスしてみます。下記のように各種メトリクスが出力されている様子が確認できれば OK です。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QVw3M_Np--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a5feae6dba9a9f4eaecae0055dd9be9e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QVw3M_Np--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a5feae6dba9a9f4eaecae0055dd9be9e.png" alt="Prometheus のレポートが正常に出力されている様子" width="880" height="889"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Prometheus のレポートが正常に出力されている様子&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;今回は ECS 上で Node.js アプリを動作させるため、&lt;code&gt;Dockerfile&lt;/code&gt; も作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; public.ecr.aws/docker/library/node:16-alpine3.12 AS builder&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--max-old-space-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4096

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "node", "index.js" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記 &lt;code&gt;Dockerfile&lt;/code&gt; 作成後、再び動作検証のため下記コマンドを実行してから、&lt;code&gt;http://localhost:8080/metrics&lt;/code&gt; にアクセスしてみます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; prometheus-agent-test/metrics-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 prometheus-agent-test/metrics-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;先ほどと同様に &lt;code&gt;http://localhost:8080/metrics&lt;/code&gt; アクセス時に各種メトリクスが出力されている様子を確認できれば OK です。&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js アプリを監視する Prometheus Agent を準備する
&lt;/h3&gt;

&lt;p&gt;まずは Prometheus 関連ファイルを配置するためのフォルダを作成します。&lt;code&gt;prometheus-agent-test&lt;/code&gt; フォルダ内で下記コマンドを実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;prometheus-agent &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;prometheus-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次に Prometheus の設定テンプレートファイルを作成します。テンプレートファイルは &lt;a href="https://atmarkit.itmedia.co.jp/ait/articles/1610/18/news008.html"&gt;&lt;code&gt;sed&lt;/code&gt;&lt;/a&gt; を利用して中身の &lt;code&gt;__TASK_ID__&lt;/code&gt; および &lt;code&gt;__REMOTE_WRITE_URL__&lt;/code&gt; を書き換えて利用します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scrape_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
  &lt;span class="na"&gt;external_labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;monitor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prometheus'&lt;/span&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;prometheus-agent-test'&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;localhost:8080'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# デフォルトの localhost:8080 がインスタンスとして利用されると、&lt;/span&gt;
          &lt;span class="c1"&gt;# メトリクスの判別がしづらくなるため ECS Task の ID を利用する&lt;/span&gt;
          &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__TASK_ID__'&lt;/span&gt;

&lt;span class="na"&gt;remote_write&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# AMP ワークスペース作成時に控えておいた、&lt;/span&gt;
  &lt;span class="c1"&gt;# `エンドポイント - リモート書き込み URL` を設定する箇所&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__REMOTE_WRITE_URL__'&lt;/span&gt;
    &lt;span class="na"&gt;sigv4&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ap-northeast-1&lt;/span&gt;
    &lt;span class="na"&gt;queue_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;max_samples_per_send&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;
      &lt;span class="na"&gt;max_shards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
      &lt;span class="na"&gt;capacity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2500&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;設定ファイルの作成が完了したら、テンプレートファイルを利用して Prometheus の設定ファイルを作成し、Prometheus Agent を起動させるためのシェルスクリプトを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$taskId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c"&gt;# ECS Fargate で起動したタスク ID を取得する&lt;/span&gt;
  &lt;span class="nv"&gt;taskId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ECS_CONTAINER_METADATA_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/task | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.TaskARN | split("/") | .[-1]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"waiting..."&lt;/span&gt;
  &lt;span class="nb"&gt;sleep &lt;/span&gt;1
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"taskId: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;taskId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"remoteWriteUrl: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_WRITE_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# タスク ID `taskId` および、環境変数 `REMOTE_WRITE_URL` で、&lt;/span&gt;
&lt;span class="c"&gt;# Prometheus のテンプレートファイル `prometheus.tmpl.yml` の内容を書き換え、&lt;/span&gt;
&lt;span class="c"&gt;# その結果を `/etc/prometheus/prometheus.yml` に出力する&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/prometheus/prometheus.tmpl.yml | &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/__TASK_ID__/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;taskId&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/g"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s&amp;gt;__REMOTE_WRITE_URL__&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REMOTE_WRITE_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;g"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/prometheus/prometheus.yml

&lt;span class="c"&gt;# --enable-feature=agent で Prometheus を Agent モードで起動する&lt;/span&gt;
&lt;span class="c"&gt;# Prometheus のコンフィグファイルには上記で出力した `/etc/prometheus/prometheus.yml` を利用する&lt;/span&gt;
/usr/local/bin/prometheus &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--enable-feature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;agent &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--config&lt;/span&gt;.file&lt;span class="o"&gt;=&lt;/span&gt;/etc/prometheus/prometheus.yml &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--web&lt;/span&gt;.console.libraries&lt;span class="o"&gt;=&lt;/span&gt;/etc/prometheus/console_libraries &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--web&lt;/span&gt;.console.templates&lt;span class="o"&gt;=&lt;/span&gt;/etc/prometheus/consoles

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで Prometheus Agent 起動のための準備は整ったため、最後に &lt;code&gt;Dockerfile&lt;/code&gt; を準備します。ちなみに Prometheus Agent は &lt;code&gt;v2.32.0&lt;/code&gt; 以降で利用可能です。&lt;strong&gt;本記事では &lt;code&gt;v2.32.1&lt;/code&gt; を利用します。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; --platform=arm64 alpine:3.15&lt;/span&gt;

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; prometheus.tmpl.yml /etc/prometheus/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--update&lt;/span&gt; &lt;span class="nt"&gt;--no-cache&lt;/span&gt; jq &lt;span class="nb"&gt;sed &lt;/span&gt;curl

&lt;span class="c"&gt;# ARM64 で動作する Prometheus v2.32.1 を curl でダウンロード展開する&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-sL&lt;/span&gt; &lt;span class="nt"&gt;-O&lt;/span&gt; https://github.com/prometheus/prometheus/releases/download/v2.32.1/prometheus-2.32.1.linux-arm64.tar.gz
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-zxvf&lt;/span&gt; prometheus-2.32.1.linux-arm64.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm &lt;/span&gt;prometheus-2.32.1.linux-arm64.tar.gz

&lt;span class="c"&gt;# `prometheus` コマンドを `/usr/local/bin/prometheus` に移動する&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mv &lt;/span&gt;prometheus-2.32.1.linux-arm64/prometheus /usr/local/bin/prometheus

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./docker-entrypoint.sh /&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /docker-entrypoint.sh
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/docker-entrypoint.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ここまでで CDK でインフラ整備を進めていくための下準備は完了です。&lt;/p&gt;

&lt;h3&gt;
  
  
  ECS Fargate 上で Node.js アプリおよび Prometheus Agent を動作させる
&lt;/h3&gt;

&lt;p&gt;あとは CDK で ECS Fargate 上で Node.js アプリおよび Prometheus Agent、Grafana を動作させるための環境を整備していきます。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lib/prometheus-agent-test-stack.ts&lt;/code&gt; の内容を書き換えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;constructs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;aws_ecs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;aws_logs&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;aws_aps&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;aps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;aws_ecs_patterns&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ecs_patterns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;aws_iam&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;aws_elasticloadbalancingv2&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;elbv2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-cdk-lib&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PrometheusAgentTestStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Node.js アプリに ecs_patterns.ApplicationLoadBalancedFargateService を利用して ALB 経由でアクセス可能にする&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projectName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prometheus-agent-test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fargateService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs_patterns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApplicationLoadBalancedFargateService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-fargate-service`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-fargate-service`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;desiredCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;listenerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;taskImageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-taskdef`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContainerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metrics-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;logDriver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogDrivers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awsLogs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;streamPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/metrics-app`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;logRetention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_DAY&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-cluster`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;clusterName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-cluster`&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="na"&gt;memoryLimitMiB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;fargateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetGroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configureHealthCheck&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/metrics&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;healthyThresholdCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;unhealthyThresholdCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;healthyHttpCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 本質ではないが、Gravition2 で動作させたいために RuntimePlatform のプロパティを上書きしている&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fargateServiceTaskdef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fargateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultChild&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnTaskDefinition&lt;/span&gt;
    &lt;span class="nx"&gt;fargateServiceTaskdef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addPropertyOverride&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RuntimePlatform&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;CpuArchitecture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ARM64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;OperatingSystemFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LINUX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// AMP への書き込み権限を付与する&lt;/span&gt;
    &lt;span class="nx"&gt;fargateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAwsManagedPolicyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AmazonPrometheusRemoteWriteAccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// (2021/12/18) Amazon Managed Service for Prometheus のワークスペースを作成して、Prometheus の remote-write URL を取得する&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apsWorkspace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;aps&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-prom-workspace`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-prom-workspace`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apsWorkspaceRemoteUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apsWorkspace&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attrPrometheusEndpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;api/v1/remote_write`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// (2021/12/18) 本記事で頻出する "エンドポイント - リモート書き込み URL" をコンソールに出力する&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prom-remote-write-url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apsWorkspaceRemoteUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Prometheus Workspace の remote-write URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;exportName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PromRemoteWriteURL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// AMP へメトリクス情報を送信するための Prometheus Agent コンテナを追加する&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;containerName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-prometheus-agent`&lt;/span&gt;
    &lt;span class="nx"&gt;fargateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;containerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;containerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContainerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prometheus-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;memoryReservationMiB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// (2021/12/18) CDK 経由で作成した Prometheus の remote-write URL を設定する&lt;/span&gt;
        &lt;span class="na"&gt;REMOTE_WRITE_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apsWorkspaceRemoteUrl&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AwsLogDriver&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;streamPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/prometheus-agent`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;logRetention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_DAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Grafana のタスク定義を作成する&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardTaskDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FargateTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-grafana-taskdef`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-grafana-taskdef`&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// Grafana のタスクが Prometheus Query を叩けるように権限付与する&lt;/span&gt;
    &lt;span class="nx"&gt;grafanaDashboardTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ManagedPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromAwsManagedPolicyName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AmazonPrometheusQueryAccess&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Grafana のコンテナを追加する。パスプレフィクスには dashboard を設定する&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardContainerName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-grafana-dashboard`&lt;/span&gt;
    &lt;span class="nx"&gt;grafanaDashboardTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grafanaDashboardContainerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;containerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardContainerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContainerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;public.ecr.aws/ubuntu/grafana&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;AWS_SDK_LOAD_CONFIG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;GF_AUTH_SIGV4_AUTH_ENABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;GF_SERVER_SERVE_FROM_SUB_PATH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;GF_SERVER_ROOT_URL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;%(protocol)s://%(domain)s/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;portMappings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
      &lt;span class="na"&gt;memoryLimitMiB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AwsLogDriver&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;streamPrefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/grafana-dashboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;logRetention&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RetentionDays&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ONE_DAY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardServiceName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-grafana-dashboard-service`&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FargateService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardServiceName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardServiceName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fargateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;taskDefinition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grafanaDashboardTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;desiredCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Grafana のタスクを ALB のターゲットグループに紐づける&lt;/span&gt;
    &lt;span class="nx"&gt;fargateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addTargets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;projectName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-grafana-dashboard-target`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;conditions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;elbv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ListenerCondition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathPatterns&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;healthCheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="na"&gt;healthyThresholdCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;unhealthyThresholdCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;healthyHttpCodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;200&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;elbv2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ApplicationProtocol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;grafanaDashboardService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、&lt;code&gt;cdk deploy&lt;/code&gt; でインフラを構築します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LCXv8-rm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/c7da0f6c6b5a57edee47ae20a8026f8f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LCXv8-rm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/c7da0f6c6b5a57edee47ae20a8026f8f.png" alt="CDK によるインフラ構築が正常に実行された時の様子" width="880" height="212"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;CDK によるインフラ構築が正常に実行された時の様子&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;デプロイが正常に完了したのを確認したら、&lt;code&gt;Outputs&lt;/code&gt; に出力されている &lt;strong&gt;&lt;code&gt;PrometheusAgentTestStack.prometheusagenttestfargateserviceServiceURL&amp;lt;識別子&amp;gt;&lt;/code&gt; の URL 末尾に &lt;code&gt;/metrics&lt;/code&gt; を付与してアクセスしてみます。&lt;/strong&gt; 出力されている URL のフォーマットは &lt;code&gt;http://&amp;lt;識別子&amp;gt;.ap-northeast-1.elb.amazonaws.com&lt;/code&gt; になります。&lt;/p&gt;

&lt;p&gt;つまり、&lt;strong&gt;&lt;code&gt;http://&amp;lt;識別子&amp;gt;.ap-northeast-1.elb.amazonaws.com/metrics&lt;/code&gt; にアクセスします。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n1o8cSy8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/c13a2b0efc3bb96e79b4f1f5a2886a8a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n1o8cSy8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/c13a2b0efc3bb96e79b4f1f5a2886a8a.png" alt="ALB 経由で Node.js アプリにアクセス可能なことを確認する" width="880" height="871"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ALB 経由で Node.js アプリにアクセス可能なことを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;また、&lt;code&gt;Outputs&lt;/code&gt; に出力されている &lt;strong&gt;&lt;code&gt;PrometheusAgentTestStack.promremotewriteurl&lt;/code&gt; は後に利用する &lt;code&gt;エンドポイント - リモート書き込み URL&lt;/code&gt; で使用するので控えておきます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ここまでで AWS CDK でのインフラ構築作業は完了しました。最後に Grafana で AMP のメトリクスを可視化するための作業を進めていきます。&lt;/p&gt;
&lt;h1&gt;
  
  
  Grafana で Prometheus (AMP) のメトリクスを可視化する
&lt;/h1&gt;

&lt;p&gt;先ほどの &lt;code&gt;/metrics&lt;/code&gt; パスへのアクセス同様、&lt;code&gt;Outputs&lt;/code&gt; に出力されている URL の末尾に &lt;code&gt;/dashboard/login&lt;/code&gt; を付与してアクセスします。&lt;strong&gt;Grafana の初期ユーザおよびパスワードは &lt;code&gt;admin&lt;/code&gt; となります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;つまり、&lt;code&gt;http://&amp;lt;識別子&amp;gt;.ap-northeast-1.elb.amazonaws.com/dashboard/login&lt;/code&gt; にアクセスしてみます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--kSNE2YDq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/fe4ea6515f54dec23234e10a93023a36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kSNE2YDq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/fe4ea6515f54dec23234e10a93023a36.png" alt="ログインを行う" width="880" height="494"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ログイン情報が正しければ、新しいパスワードを設定する画面に遷移するので新たなパスワードを入力してログインを終えます。ログイン後は、Prometheus (AMP) をデータソースとして追加するために下記の操作を行います。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--w6eiqHEN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/599d49e4167ccc35c5d44d5df7678036.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--w6eiqHEN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/599d49e4167ccc35c5d44d5df7678036.png" alt="1. 歯車アイコンをクリックして  raw `Data sources` endraw  をクリックする" width="880" height="460"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. 歯車アイコンをクリックして &lt;code&gt;Data sources&lt;/code&gt; をクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DTdEjIFn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e88bfc58a35deaa16a16920a5e551837.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DTdEjIFn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e88bfc58a35deaa16a16920a5e551837.png" alt="2.  raw `Add data source` endraw  ボタンをクリックする" width="880" height="482"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. &lt;code&gt;Add data source&lt;/code&gt; ボタンをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UDZWtyV1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1c8031a3182f03e20194bf0b76e66e5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UDZWtyV1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1c8031a3182f03e20194bf0b76e66e5a.png" alt="3. データソースとして Prometheus を選択する" width="880" height="487"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. データソースとして Prometheus を選択する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JSWEur9A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1684c1fca9decb29e60bd654400fd06b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JSWEur9A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1684c1fca9decb29e60bd654400fd06b.png" alt="4. Prometheus をデータソースとして追加する" width="880" height="538"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Ehdj4QtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2119361eac350044e783ed0c9280580a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Ehdj4QtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2119361eac350044e783ed0c9280580a.png" alt="4. Prometheus をデータソースとして追加する" width="880" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Kexhhb4Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/00cc7edbfc45dbd43ad3b0cccea72c7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Kexhhb4Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/00cc7edbfc45dbd43ad3b0cccea72c7d.png" alt="4. Prometheus をデータソースとして追加する" width="880" height="684"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4. Prometheus をデータソースとして追加する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prometheus (AMP) に送信したメトリクスを Grafana で可視化するための準備が整ったので、実際に Grafana のダッシュボードでメトリクスを可視化してみます。手っ取り早くメトリクスを可視化するため、ダッシュボードには &lt;a href="https://grafana.com/grafana/dashboards/11159"&gt;NodeJS Application Dashboard&lt;/a&gt; を利用します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fc0g0xcS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b36c0281e273e21fc9474666786281c3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fc0g0xcS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b36c0281e273e21fc9474666786281c3.png" alt="1. + アイコンをクリックして、 raw `Import` endraw  をクリックする" width="880" height="476"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. + アイコンをクリックして、&lt;code&gt;Import&lt;/code&gt; をクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vUWyAkaP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/130718dff8b86758acb415764bd37338.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vUWyAkaP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/130718dff8b86758acb415764bd37338.png" alt="2.  raw `NodeJS Application Dashboard` endraw  の ID を入力して  raw `Load` endraw  ボタンをクリックする" width="880" height="620"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. &lt;code&gt;NodeJS Application Dashboard&lt;/code&gt; の ID を入力して &lt;code&gt;Load&lt;/code&gt; ボタンをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Sen5kiwz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2aa8903f3f64d8021699cd5d4bfa9757.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Sen5kiwz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2aa8903f3f64d8021699cd5d4bfa9757.png" alt="3. 必要な情報を入力して  raw `NodeJS Application Dashboard` endraw  のインポートを完了する" width="880" height="487"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. 必要な情報を入力して &lt;code&gt;NodeJS Application Dashboard&lt;/code&gt; のインポートを完了する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xDiUTj1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1378b09c281da28653917ee40494dee8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xDiUTj1e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1378b09c281da28653917ee40494dee8.png" alt="4. ダッシュボードから Prometheus のメトリクスが確認できる" width="880" height="482"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4. ダッシュボードから Node.js アプリのメトリクスが確認できる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ここまでの手順でメトリクスの可視化は完了しましたが、負荷に応じて実際にメトリクスが変化する様子も確認してみます。&lt;a href="https://github.com/tsenart/vegeta"&gt;Vegeta&lt;/a&gt; を利用して、実際に負荷をかけてみます。下記コマンドを実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'GET http://&amp;lt;識別子&amp;gt;.ap-northeast-1.elb.amazonaws.com/metrics'&lt;/span&gt; | vegeta attack &lt;span class="nt"&gt;-duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5s | vegeta report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、再び Grafana のダッシュボードを見にいきます。負荷をかけた時間帯のみグラフに変化があることを確認できるはずです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BjpFNrit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d019d33d3e4bd321ae4d1f4bbecc6ef8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BjpFNrit--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d019d33d3e4bd321ae4d1f4bbecc6ef8.png" alt="ダッシュボードの CPU 使用率のグラフに変化があったことを確認できる" width="880" height="490"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ダッシュボードの CPU 使用率のグラフに変化があったことを確認できる&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;今回は ECS Fargate のメトリクスを Prometheus Agent で Amazon Managed Service for Prometheus (AMP) に送信し、それを Grafana で可視化する方法について紹介しました。&lt;/p&gt;

&lt;p&gt;ECS のサービスでタスクを実行する場合は &lt;a href="https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/service-discovery.html"&gt;サービスディスカバリ&lt;/a&gt; の利用が可能なため、Prometheus の &lt;a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#dns_sd_config"&gt;サービスディスカバリの設定&lt;/a&gt; を行うことで、単一の Prometheus で全てのコンテナのメトリクスを扱うことも可能です。&lt;/p&gt;

&lt;p&gt;また Node.js アプリを作成する際に利用した &lt;code&gt;prom-client&lt;/code&gt; で &lt;a href="https://github.com/siimon/prom-client#custom-metrics"&gt;カスタムメトリクス&lt;/a&gt; を作成することで、監視したい項目を自由に増やすことも可能です。&lt;/p&gt;

&lt;p&gt;本記事が ECS Fargate を監視する際の検討材料の 1 つとなれたら幸いです。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/jp/fargate/"&gt;AWS Fargate（サーバーやクラスターの管理が不要なコンテナの使用）| AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://prometheus.io/blog/2021/11/16/agent/"&gt;Introducing Prometheus Agent Mode, an Efficient and Cloud-Native Way for Metric Forwarding | Prometheus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/jp/prometheus/"&gt;Amazon Managed Service for Prometheus | フルマネージド Prometheus | Amazon Web Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/"&gt;Grafana: The open observability platform | Grafana Labs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/jp/cdk/"&gt;AWS クラウド開発キット – アマゾン ウェブ サービス&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/siimon/prom-client"&gt;siimon/prom-client: Prometheus client for node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tsenart/vegeta"&gt;tsenart/vegeta: HTTP load testing tool and library. It's over 9000!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/grafana/dashboards/11159"&gt;NodeJS Application Dashboard dashboard for Grafana | Grafana Labs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fargate</category>
      <category>prometheus</category>
      <category>grafana</category>
      <category>aws</category>
    </item>
    <item>
      <title>[TECH] Unity で iOS/Android アプリの設定値をセキュアに扱う方法 🔑</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sun, 15 Aug 2021 15:46:39 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-unity-ios-android-2kpi</link>
      <guid>https://forem.com/nikaera/tech-unity-ios-android-2kpi</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;iOS/Android でユーザーの情報をセキュアに扱う必要があったので、調査したところ Android には &lt;a href="https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences"&gt;EncryptedSharedPreferences&lt;/a&gt; が存在することを知りました。iOS には &lt;a href="https://developer.apple.com/documentation/security/keychain_services"&gt;Keychain Services&lt;/a&gt; が存在します。&lt;/p&gt;

&lt;p&gt;今回は Unity の iOS/Android プラットフォーム上で設定値を保存するための実装を行う必要があったので、Unity から扱えるようネイティブプラグインを作成しました。今後もこういった要望はありそうでしたので、記事として手順や内容を書き記しておくことにしました。&lt;/p&gt;

&lt;p&gt;本記事内で紹介しているコードは下記にアップ済みです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nikaera/Unity-iOS-Android-SecretManager-Sample"&gt;https://github.com/nikaera/Unity-iOS-Android-SecretManager-Sample&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  動作環境
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;MacBook Air (M1, 2020)&lt;/li&gt;
&lt;li&gt;Unity 2020.3.15f2&lt;/li&gt;
&lt;li&gt;Android 6.0 以上

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences"&gt;EncryptedSharedPreferences&lt;/a&gt; が使用可能なバージョン&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Android のネイティブプラグインを作成する
&lt;/h1&gt;

&lt;p&gt;Android 環境ではまず &lt;a href="https://github.com/googlesamples/unity-jar-resolver"&gt;External Dependency Manager for Unity&lt;/a&gt; を利用して、Unity の Android ネイティブプラグインで &lt;code&gt;EncryptedSharedPreferences&lt;/code&gt; 利用可能にします。&lt;/p&gt;

&lt;h2&gt;
  
  
  (追記) Gradle を利用したライブラリのインストール方法
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://twitter.com/shiena"&gt;shiena&lt;/a&gt; さんにご教授いただいたのですが、&lt;a href="https://zenn.dev/shiena/articles/unity-sqlcipher#gradle%E3%82%92%E5%88%A9%E7%94%A8"&gt;こちらの記事&lt;/a&gt;のように Gradle を利用することでも簡易にライブラリの取り込みが可能なようでした。&lt;/p&gt;

&lt;p&gt;手順は上記の記事をご参照いただくとして、Gradle を利用する方法で外部ライブラリを取り込む際の &lt;code&gt;Assets/Plugins/Android/mainTemplate.gradle&lt;/code&gt; および &lt;code&gt;Assets/Plugins/Android/gradleTemplate.properties&lt;/code&gt; は下記になります。&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```diff gradle&lt;br&gt;
dependencies {&lt;br&gt;
    implementation fileTree(dir: 'libs', include: ['*.jar'])&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;   implementation 'androidx.security:security-crypto:1.1.0-alpha03'
&lt;strong&gt;DEPS&lt;/strong&gt;}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;android {&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;




```diff properties
org.gradle.jvmargs=-Xmx**JVM_HEAP_SIZE**M
org.gradle.parallel=true
android.enableR8=**MINIFY_WITH_R_EIGHT**
+ android.useAndroidX=true
unityStreamingAssets=.unity3d**STREAMING_ASSETS**
**ADDITIONAL_PROPERTIES**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Gradle を利用した方法でライブラリを利用される際は、次の &lt;code&gt;External Dependency Manager for Unity で必要なパッケージをインストールする&lt;/code&gt; の手順はスキップ可能です。&lt;code&gt;EncryptedSharedPreferences を利用するためのネイティブコードを追加する&lt;/code&gt; のステップから進めてください。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;External Dependency Manager for Unity&lt;/code&gt; を利用する方法だと、取り込み先プロジェクト内でライブラリの競合が発生する恐れがあります。Gradle を利用する方法であれば回避が可能です。&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  External Dependency Manager for Unity で必要なパッケージをインストールする
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;External Dependency Manager for Unity&lt;/code&gt; をインポートするため &lt;a href="https://github.com/googlesamples/unity-jar-resolver/blob/master/external-dependency-manager-latest.unitypackage"&gt;unitypackage&lt;/a&gt; をダウンロードして、&lt;strong&gt;&lt;code&gt;EncryptedSharedPreferences&lt;/code&gt; を導入したい Unity プロジェクトを開いてから &lt;code&gt;unitypackage&lt;/code&gt; をクリックすることで、&lt;code&gt;External Dependency Manager for Unity&lt;/code&gt; を Unity プロジェクトにインポートします。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FrLAr4da--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1af7cdf4d7d5749e59e151eef1ca5493.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FrLAr4da--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1af7cdf4d7d5749e59e151eef1ca5493.png" alt="ダウンロードした  raw `unitypackage` endraw  をクリックして Unity プロジェクトに External Dependency Manager for Unity をインポートする"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unity プロジェクトの &lt;code&gt;Build Settings&lt;/code&gt; からプラットフォームは Android に切り替えておきます。&lt;code&gt;Enable Android Auto-resolution?&lt;/code&gt; というダイアログの選択肢はどちらを選んでも構いません。&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;External Dependency Manager for Unity で各種パッケージを管理する方法は &lt;a href="https://github.com/googlesamples/unity-jar-resolver#android-resolver-usage"&gt;README&lt;/a&gt; に記載がある通り、&lt;strong&gt;&lt;code&gt;*Dependencies.xml&lt;/code&gt; というファイルを &lt;code&gt;Editor&lt;/code&gt; フォルダに配置することで可能になります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;今回は &lt;code&gt;EncryptedSharedPreferences&lt;/code&gt; を導入するため、下記の xml ファイルを &lt;code&gt;Editor&lt;/code&gt; フォルダ内に配置します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;androidPackages&amp;gt;&lt;/span&gt;
        &lt;span class="c"&gt;&amp;lt;!--
            本記事ではバージョン 1.1.0-alpha03 を利用している
        --&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;androidPackage&lt;/span&gt; &lt;span class="na"&gt;spec=&lt;/span&gt;&lt;span class="s"&gt;"androidx.security:security-crypto:1.1.0-alpha03"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;androidSdkPackageIds&amp;gt;&lt;/span&gt;
                &lt;span class="c"&gt;&amp;lt;!--
                    Google の Maven リポジトリからインストールするため、
                    extra-google-m2repository を指定する
                --&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;androidSdkPackageId&amp;gt;&lt;/span&gt;extra-google-m2repository&lt;span class="nt"&gt;&amp;lt;/androidSdkPackageId&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/androidSdkPackageIds&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/androidPackage&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/androidPackages&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、&lt;strong&gt;Unity メニューから &lt;code&gt;Assets -&amp;gt; External Dependency Manager -&amp;gt; Android Resolver -&amp;gt; Force Resolve&lt;/code&gt; を選択して、&lt;code&gt;Assets/Editor/AndroidPluginDependencies.xml&lt;/code&gt; の内容を元に &lt;code&gt;EncryptedSharedPreferences&lt;/code&gt; を利用するのに必要なパッケージを自動で &lt;code&gt;Assets/Plugins/Android&lt;/code&gt; フォルダにダウンロードします。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JqjNUg90--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/df394e15149e54dae3e9a81848512ee9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JqjNUg90--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/df394e15149e54dae3e9a81848512ee9.png" alt="1. Unity メニューから  raw `Assets -&amp;gt; External Dependency Manager -&amp;gt; Android Resolver -&amp;gt; Force Resolve` endraw  を選択する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. Unity メニューから &lt;code&gt;Assets -&amp;gt; External Dependency Manager -&amp;gt; Android Resolver -&amp;gt; Force Resolve&lt;/code&gt; を選択する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6bNAKUTh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/f6d2ec95ef9c2afdc857fecef2b165e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6bNAKUTh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/f6d2ec95ef9c2afdc857fecef2b165e5.png" alt="2. 実行に成功すると EncryptedSharedPreferences を利用するのに必要なライブラリ群が  raw `Assets/Plugins/Android` endraw  フォルダに配置される"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. 実行に成功すると EncryptedSharedPreferences を利用するのに必要なライブラリ群が &lt;code&gt;Assets/Plugins/Android&lt;/code&gt; フォルダに配置される&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ここまで来ればあとは Android ネイティブコードを &lt;code&gt;Assets/Plugins/Android&lt;/code&gt; フォルダ内に配置して Unity 側から叩けるようにするだけです。&lt;/p&gt;
&lt;h2&gt;
  
  
  EncryptedSharedPreferences を利用するためのネイティブコードを追加する
&lt;/h2&gt;

&lt;p&gt;早速下記の Android ネイティブコードを &lt;code&gt;Assets/Plugins/Android/SecretManager.java&lt;/code&gt; に配置します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.nikaera&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.unity3d.player.UnityPlayerActivity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.lang.Exception&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// External Dependency Manager for Unity によって、&lt;/span&gt;
&lt;span class="c1"&gt;// 必要な jar が含まれているため EncryptedSharedPreferences の利用が可能になる&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.security.crypto.EncryptedSharedPreferences&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;androidx.security.crypto.MasterKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.Context&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.SharedPreferences&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.content.SharedPreferences.Editor&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.os.Bundle&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;android.util.Log&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecretManager&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;SharedPreferences&lt;/span&gt; &lt;span class="n"&gt;sharedPreferences&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SecretManager&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Context&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// EncryptedSharedPreferences で設定値を保存する際に用いる、&lt;/span&gt;
        &lt;span class="c1"&gt;// 暗号鍵を扱うためのラッパークラスをデフォルト設定で作成する&lt;/span&gt;
        &lt;span class="nc"&gt;MasterKey&lt;/span&gt; &lt;span class="n"&gt;masterKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MasterKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setKeyScheme&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MasterKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;KeyScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AES256_GCM&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// EncryptedSharedPreferences のインスタンスを生成する&lt;/span&gt;
        &lt;span class="c1"&gt;// コンストラクタで作成した masterKey を指定している&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sharedPreferences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPackageName&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;masterKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nc"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PrefKeyEncryptionScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AES256_SIV&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
          &lt;span class="nc"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PrefValueEncryptionScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;AES256_GCM&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printStackTrace&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * 指定したキーで値を保存する関数
   * @param key 値を保存する際に用いるキー
   * @param value 保存したい値
   * @return boolean 値の保存に成功したかどうか
   */&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Editor&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;edit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;putString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * 指定したキーで保存した値を取得する関数
   * `put` 関数で保存した値を取得するのに利用する
   * @param key 取得したい値のキー
   * @return string キーに紐づく値、存在しなければ空文字が返却される
   */&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * 指定したキーで値を削除する関数
   * @param key 削除したい値のキー
   * @return boolean 値の削除に成功したかどうか
   */&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;SharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Editor&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sharedPreferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;edit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;editor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、上記を Unity スクリプトから実行可能にするための C# クラスを作成します。本記事ではファイルを &lt;code&gt;Assets/Scripts/EncryptedSharedPreferences.cs&lt;/code&gt; に配置します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     利用するネイティブコードは &amp;lt;c&amp;gt;Assets/Plugins/Android/SecretManager.java&amp;lt;/c&amp;gt; に記載&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     &amp;lt;a href="https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences"&amp;gt;EncryptedSharedPreferences&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EncryptedSharedPreferences&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AndroidJavaObject&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// コンストラクタで com.nikaera.SecretManager のインスタンス生成を行う&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AndroidJavaClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.unity3d.player.UnityPlayer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetStatic&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AndroidJavaObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"currentActivity"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AndroidJavaObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"getApplicationContext"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_secretManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AndroidJavaObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.nikaera.SecretManager"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"put"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;あとは用途に応じて下記のようなコードで設定値の保存や取得などを行えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;_sharedPreferences&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// name をキーとして値を nikaera で保存する&lt;/span&gt;
&lt;span class="n"&gt;_sharedPreferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nikaera"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// name をキーとして値を取得する&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_sharedPreferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// "nikaera" が出力される&lt;/span&gt;
&lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//　name をキーとして値を削除する&lt;/span&gt;
&lt;span class="n"&gt;_sharedPreferences&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  iOS のネイティブプラグインを作成する
&lt;/h1&gt;

&lt;p&gt;iOS の場合は外部ライブラリを利用しないため、&lt;code&gt;External Dependency Manager for Unity&lt;/code&gt; は利用しません。&lt;strong&gt;本来であれば Swift で信頼できる外部フレームワークを取り込み利用できると良さそうですが、今回は Objective-C でネイティブプラグインを書いていきます。&lt;/strong&gt;&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keychain Services を利用するためのネイティブコードを追加する
&lt;/h2&gt;

&lt;p&gt;早速下記の iOS ネイティブコードを &lt;code&gt;Assets/Plugins/iOS/KeychainService.mm&lt;/code&gt; に配置します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight objective_c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Keychain Services を利用するために Security フレームワークを利用する&lt;/span&gt;
&lt;span class="cp"&gt;#import &amp;lt;Security/Security.h&amp;gt;
&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="s"&gt;"C"&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 指定したキーで値を保存する関数&lt;/span&gt;
    &lt;span class="c1"&gt;// - param&lt;/span&gt;
    &lt;span class="c1"&gt;//   - dataType:  値を保存する際に用いるキー&lt;/span&gt;
    &lt;span class="c1"&gt;//   - value: 保存したい値&lt;/span&gt;
    &lt;span class="c1"&gt;// - return&lt;/span&gt;
    &lt;span class="c1"&gt;//   - 保存時のステータスコードを返却する (0 以外は失敗)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt; &lt;span class="nf"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="n"&gt;NSData&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="nf"&gt;stringWithCString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="nf"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;dataUsingEncoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClassGenericPassword&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClass&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="nf"&gt;stringWithCString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt; &lt;span class="nf"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrAccount&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="n"&gt;OSStatus&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecItemCopyMatching&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;CFDictionaryRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;noErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt; &lt;span class="nf"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sata&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecValueData&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="n"&gt;NSDate&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrModificationDate&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecItemUpdate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;CFDictionaryRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CFDictionaryRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;errSecItemNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt; &lt;span class="nf"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClassGenericPassword&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClass&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="nf"&gt;stringWithCString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt; &lt;span class="nf"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrAccount&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sata&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecValueData&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="n"&gt;NSDate&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrCreationDate&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="n"&gt;NSDate&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrModificationDate&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecItemAdd&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;CFDictionaryRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 指定したキーで値を取得する関数&lt;/span&gt;
    &lt;span class="c1"&gt;// - param&lt;/span&gt;
    &lt;span class="c1"&gt;//   - dataType: 値を取得する際に用いるキー&lt;/span&gt;
    &lt;span class="c1"&gt;// - return&lt;/span&gt;
    &lt;span class="c1"&gt;//   - キーに紐づく値、存在しなければ空文字が返却される&lt;/span&gt;
    &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt; &lt;span class="nf"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClassGenericPassword&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClass&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="nf"&gt;stringWithCString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt; &lt;span class="nf"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrAccount&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kCFBooleanTrue&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecReturnData&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="n"&gt;CFDataRef&lt;/span&gt; &lt;span class="n"&gt;cfresult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;OSStatus&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecItemCopyMatching&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;CFDictionaryRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CFTypeRef&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cfresult&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;noErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;NSData&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;passwordData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__bridge_transfer&lt;/span&gt; &lt;span class="n"&gt;NSData&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;cfresult&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="nf"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="nf"&gt;initWithData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;passwordData&lt;/span&gt; &lt;span class="nf"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nf"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;UTF8String&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strdup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 指定したキーで値を削除する関数&lt;/span&gt;
    &lt;span class="c1"&gt;// - param&lt;/span&gt;
    &lt;span class="c1"&gt;//   - dataType:  値を削除する際に用いるキー&lt;/span&gt;
    &lt;span class="c1"&gt;// - return&lt;/span&gt;
    &lt;span class="c1"&gt;//   - 保存時のステータスコードを返却する (0 以外は失敗)&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NSMutableDictionary&lt;/span&gt; &lt;span class="nf"&gt;dictionary&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClassGenericPassword&lt;/span&gt; &lt;span class="nf"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecClass&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="nf"&gt;setObject&lt;/span&gt;&lt;span class="p"&gt;:(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;NSString&lt;/span&gt; &lt;span class="nf"&gt;stringWithCString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt; &lt;span class="nf"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NSUTF8StringEncoding&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;kSecAttrAccount&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="n"&gt;OSStatus&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecItemDelete&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;CFDictionaryRef&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;noErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Keychain Services&lt;/code&gt; は &lt;code&gt;Security&lt;/code&gt; フレームワークを利用するため、&lt;strong&gt;&lt;code&gt;KeychainService.mm&lt;/code&gt; に対して &lt;code&gt;Security&lt;/code&gt; フレームワークの依存関係を設定する必要があります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jB9dSkrM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ba82aaced24b83b37bf8c63e1ee7142f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jB9dSkrM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ba82aaced24b83b37bf8c63e1ee7142f.png" alt=" raw `KeychainService.mm` endraw  で  raw `Security` endraw  フレームワークの利用を可能にする"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;KeychainService.mm&lt;/code&gt; で &lt;code&gt;Security&lt;/code&gt; フレームワークの利用を可能にする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;その後、上記を Unity スクリプトから実行可能にするための C# クラスを作成します。本記事ではファイルを &lt;code&gt;Assets/Scripts/KeychainService.cs&lt;/code&gt; に配置します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Runtime.InteropServices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     実装は &amp;lt;c&amp;gt;Assets/Plugins/iOS/KeychainService.mm&amp;lt;/c&amp;gt; に記載&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     &amp;lt;a href="https://developer.apple.com/documentation/security/keychain_services"&amp;gt;Keychain Services&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KeychainService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;        &lt;span class="c1"&gt;// 返却されるステータスが 0 なら成功&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;        &lt;span class="c1"&gt;// 返却されるステータスが 0 なら成功&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;あとは用途に応じて下記のようなコードで設定値の保存や取得などを行えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;_keychainService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KeychainService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// name をキーとして値を nikaera で保存する&lt;/span&gt;
&lt;span class="n"&gt;_keychainService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nikaera"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// name をキーとして値を取得する&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_keychainService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// "nikaera" が出力される&lt;/span&gt;
&lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//　name をキーとして値を削除する&lt;/span&gt;
&lt;span class="n"&gt;_keychainService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  (余談) インターフェースで iOS/Android のふるまいを共通化する
&lt;/h1&gt;

&lt;p&gt;このままだとプラットフォームを切り替える毎にコードを書き直さないとならないので、インターフェースを利用して共通化を行います。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ISecretManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// 指定したキーで値を保存する関数&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;param name="key"&amp;gt;キー&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;param name="value"&amp;gt;値&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;保存に成功したかどうか&amp;lt;/returns&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// 指定したキーの値を取得する関数&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;param name="key"&amp;gt;キー&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;指定したキーで設定された値、無ければ null&amp;lt;/returns&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// 指定したキーの値を削除する関数&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;param name="key"&amp;gt;キー&amp;lt;/param&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;returns&amp;gt;削除に成功したかどうか&amp;lt;/returns&amp;gt;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、&lt;code&gt;Assets/Scripts/EncryptedSharedPreferences.cs&lt;/code&gt; および &lt;code&gt;Assets/Scripts/KeychainService.cs&lt;/code&gt; を下記の通り &lt;code&gt;ISecretManager&lt;/code&gt; の実装に紐付けます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     利用するネイティブコードは &amp;lt;c&amp;gt;Assets/Plugins/Android/SecretManager.java&amp;lt;/c&amp;gt; に記載&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     &amp;lt;a href="https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences"&amp;gt;EncryptedSharedPreferences&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ISecretManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;AndroidJavaObject&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AndroidJavaClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.unity3d.player.UnityPlayer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetStatic&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AndroidJavaObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"currentActivity"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AndroidJavaObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"getApplicationContext"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_secretManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AndroidJavaObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.nikaera.SecretManager"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="n"&gt;ISecretManager&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"put"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_secretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;endregion&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Runtime.InteropServices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     実装は &amp;lt;c&amp;gt;Assets/Plugins/iOS/KeychainService.mm&amp;lt;/c&amp;gt; に記載&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     &amp;lt;a href="https://developer.apple.com/documentation/security/keychain_services"&amp;gt;Keychain Services&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KeychainService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ISecretManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"__Internal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
    &lt;span class="c1"&gt;// KeychainService.mm に定義した関数を呼び出す&lt;/span&gt;
    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="n"&gt;ISecretManager&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;addItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_IOS
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;deleteItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="n"&gt;endregion&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;あとは上記をよしなに利用可能な &lt;code&gt;SecretManager&lt;/code&gt; クラスを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;///     &amp;lt;em&amp;gt;Editor 利用時のみ PlayerPrefs を利用する&amp;lt;/em&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;span class="c1"&gt;/// &amp;lt;remarks&amp;gt;&amp;lt;see cref="KeychainService" /&amp;gt;, &amp;lt;see cref="EncryptedSharedPreferences" /&amp;gt;&amp;lt;/remarks&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecretManager&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_EDITOR
#elif UNITY_ANDROID
&lt;/span&gt;        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ISecretManager&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;EncryptedSharedPreferences&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#elif UNITY_IOS
&lt;/span&gt;        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ISecretManager&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;KeychainService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_EDITOR
&lt;/span&gt;                &lt;span class="n"&gt;PlayerPrefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;PlayerPrefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#elif UNITY_IOS || UNITY_ANDROID
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;                &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not Implemented."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_EDITOR
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PlayerPrefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#elif UNITY_IOS || UNITY_ANDROID
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;            &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not Implemented."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#if UNITY_EDITOR
&lt;/span&gt;            &lt;span class="n"&gt;PlayerPrefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeleteKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;PlayerPrefs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#elif UNITY_IOS || UNITY_ANDROID
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="cp"&gt;#else
&lt;/span&gt;            &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not Implemented."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;#endif
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これでプラットフォーム間の実装差異を気にすることなく、下記のような記述で設定値の保存や取得などを行えます。&lt;strong&gt;iOS/Android 以外のプラットフォームで追加実装したい場合は &lt;a href="https://docs.unity3d.com/ja/2021.1/Manual/PlatformDependentCompilation.html"&gt;プラットフォーム依存コンパイル&lt;/a&gt; と &lt;code&gt;ISecretManager&lt;/code&gt; の実装クラスを新たに作成することで簡単に追加できます。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="c1"&gt;// name をキーとして値を nikaera で保存する&lt;/span&gt;
&lt;span class="n"&gt;SecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"nikaera"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// name をキーとして値を取得する&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// "nikaera" が出力される&lt;/span&gt;
&lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//　name をキーとして値を削除する&lt;/span&gt;
&lt;span class="n"&gt;SecretManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;今回は iOS/Android で設定値をセキュアに扱うための方法についてまとめてみました。実際は &lt;code&gt;Keychain Services&lt;/code&gt; 周りは実装が大変なので、&lt;code&gt;External Dependency Manager for Unity&lt;/code&gt; とか使って &lt;a href="https://github.com/kishikawakatsumi/KeychainAccess"&gt;KeychainAccess&lt;/a&gt; のような外部ライブラリを利用する構成のほうが良いと思われます。&lt;/p&gt;

&lt;p&gt;本記事の内容に誤りがあったり、実際にはセキュアな実装ができていない等々あれば是非コメントでご指摘いただけますと幸いです。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/topic/security/data?hl=ja"&gt;Android デベロッパー  |  Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences?hl=ja"&gt;EncryptedSharedPreferences  |  Android デベロッパー  |  Android Developers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/security/keychain_services"&gt;Keychain Services | Apple Developer Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/masaki_shoji/items/6c512c7ebb30a13cda1d"&gt;SharedPreferencesを自前で難読化するのはもう古い?これからはEncryptedSharedPrefenrecesを使おう - Qiita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/sachiko-kame/items/261d42c57207e4b7002a"&gt;iOSのキーチェーンについて - Qiita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/nyhk-oi/items/189236d0627d43e7d658"&gt;UnityでIOSにセキュアに値を保存するにはKeyChainを使おう - Qiita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/googlesamples/unity-jar-resolver"&gt;googlesamples/unity-jar-resolver: Unity plugin which resolves Android &amp;amp; iOS dependencies and performs version management&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;逆に &lt;code&gt;External Dependency Manager for Unity&lt;/code&gt; を利用する方法のメリットは、UnityPackage などでライブラリとして配布する際に、ライブラリを動作させるのに必要な外部パッケージも同梱した状態で配布が可能になるなどがあります。(当然ライセンスには気を付ける必要がありますが...) ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;パッケージの依存関係を自動で解決するかどうかという選択肢になります。本記事では明示的に Resolve を実行するため &lt;code&gt;Disable&lt;/code&gt; でも &lt;code&gt;Enable&lt;/code&gt; でも進行上の問題はありません。 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://cocoapods.org/"&gt;CocoaPods&lt;/a&gt; もサポートされているようなので、iOS でも Android 同様、外部ライブラリを取り込むのは簡単にできそうでした。例えば &lt;a href="https://github.com/kishikawakatsumi/KeychainAccess"&gt;KeychainAccess&lt;/a&gt; とか使いたい。 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>unity3d</category>
      <category>ios</category>
      <category>android</category>
    </item>
    <item>
      <title>[TECH] GameCI で Unity の CI 環境を GitHub Actions で構築する 🎮</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Wed, 26 May 2021 12:52:59 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-gameci-unity-ci-github-actions-4p35</link>
      <guid>https://forem.com/nikaera/tech-gameci-unity-ci-github-actions-4p35</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;先日同僚が Unity の CI 環境を構築するためのライブラリである &lt;a href="https://game.ci/"&gt;GameCI&lt;/a&gt; について教えてくれました。早速 GameCI の GitHub Actions を利用して、サンプルプロジェクトで色々動作検証してみたところ、Unity の CI 環境を楽に構築できることが分かりました。&lt;/p&gt;

&lt;p&gt;もちろん、&lt;a href="https://unity3d.com/jp/unity/features/cloud-build"&gt;Unity Cloud Build&lt;/a&gt; を利用すれば CI 環境の構築は以前から楽にできました。しかし、選択肢の 1 つとして GameCI を持っておくことで、&lt;strong&gt;サクッと GitHub Actions に統合する形で Unity の CI 環境を導入できるのは他には無いメリットを感じました。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本記事で紹介しているソースコード、及び検証時に利用したプロジェクトは GitHub にアップ済みですので、手っ取り早く内容を把握されたい方は下記をご参照くださいませ。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nikaera/Unity-GameCI-Sample"&gt;https://github.com/nikaera/Unity-GameCI-Sample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;業務でも利用できそうなので、GameCI を利用して CI 環境を構築する手順を記事でまとめました。&lt;/p&gt;

&lt;h1&gt;
  
  
  GameCI に備わっている機能紹介
&lt;/h1&gt;

&lt;p&gt;GameCI には現状下記の GitHub Actions が用意されているようです。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;機能&lt;/th&gt;
&lt;th&gt;概要&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://game.ci/docs/github/activation"&gt;Activation&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Unity ライセンスを任意の Unity バージョンで発行する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://game.ci/docs/github/test-runner"&gt;Test runner&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Unity の PlayMode 及び EditMode のテストを実行する (テスト結果の出力にも対応)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://game.ci/docs/github/builder"&gt;Builder&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;任意の Platform ビルドを実行する (&lt;a href="https://docs.github.com/ja/actions/guides/storing-workflow-data-as-artifacts"&gt;アーティファクト&lt;/a&gt; 利用でダウンロードも可能)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://game.ci/docs/github/returning-a-license"&gt;Returning a license&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Unity ライセンスの返却ができる (Professional License のみ対応)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://game.ci/docs/github/remote-builder"&gt;Remote builder&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;GitHub Actions のスペックでは満足のいくビルドができない際に AWS 環境でハイスペックなマシンを用意してビルドできる。ビルドのためのインフラ構築には &lt;a href="https://aws.amazon.com/jp/cloudformation/"&gt;AWS CloudFormation&lt;/a&gt; を使用している (現在は AWS のみ対応。今後 GCP, Azure にも対応予定とのこと)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://game.ci/docs/github/deployment/android"&gt;Deployment&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Unity ビルドを各種 Platform 向けにデプロイする (iOS 及び Android のみ記載あり。厳密に言うと &lt;code&gt;Builder&lt;/code&gt; でビルド出力した内容を &lt;a href="https://fastlane.tools/"&gt;&lt;code&gt;fastlane&lt;/code&gt;&lt;/a&gt; を用いてデプロイするためのワークフロー紹介になっている)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;上記を見ると既に &lt;strong&gt;GameCI には開発者として Unity CI に欲しい機能は最低限揃っているように見受けられました。&lt;/strong&gt; また本記事では、今後機会があれば試してみたいと考えていますが &lt;strong&gt;Remote builder 及び Deployment&lt;/strong&gt; については言及していません。&lt;/p&gt;

&lt;p&gt;今回は実例を交えながら &lt;strong&gt;Activation 及び Test runner、Builder、Returning a license&lt;/strong&gt; の使用方法について紹介していきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  Activation: GameCI で必要となる Unity License のアクティベーションを行う
&lt;/h2&gt;

&lt;p&gt;GameCI で Unity ライセンスをアクティベートするには &lt;a href="https://game.ci/docs/github/activation"&gt;Activation&lt;/a&gt; を利用します。早速ドキュメントの手順に沿って作業を進めていきます。&lt;/p&gt;

&lt;p&gt;まず CI を導入したい GitHub 上の Unity プロジェクトの &lt;code&gt;.github/workflows&lt;/code&gt; 内に Unity ライセンスアクティベート用のワークフローファイルを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Acquire activation file&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;activation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Request manual activation file 🔑&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# GameCI の Activation を利用して alf ファイルを発行する&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Request manual activation file&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;getManualLicenseFile&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-ci/unity-request-activation-file@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Unity プロジェクトのバージョンを指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# ProjectSettings/ProjectVersion.txt に記載されているバージョンを入力すれば OK&lt;/span&gt;
          &lt;span class="na"&gt;unityVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2020.3.5f1&lt;/span&gt;
      &lt;span class="c1"&gt;# Upload artifact (Unity_v20XX.X.XXXX.alf)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Expose as artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.getManualLicenseFile.outputs.filePath }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.getManualLicenseFile.outputs.filePath }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;その後、&lt;a href="https://docs.github.com/ja/github/setting-up-and-managing-your-github-user-account/managing-user-account-settings/managing-the-default-branch-name-for-your-repositories#about-management-of-the-default-branch-name"&gt;デフォルトブランチ&lt;/a&gt; にプッシュして GitHub Actions で実行可能にしたら、下記手順に従い Unity ライセンスファイルのアクティベート及びダウンロードを行います。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zoKq49M7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/bd276ca6dcf6a2c12ce9ff9569e08ce3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zoKq49M7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/bd276ca6dcf6a2c12ce9ff9569e08ce3.png" alt="1. ブラウザから GitHub リポジトリにアクセスして、Unity ライセンスアクティベート用のワークフローを実行して  raw `alf` endraw  ファイルを生成する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. ブラウザから GitHub リポジトリにアクセスして、Unity ライセンスアクティベート用のワークフローを実行して &lt;code&gt;alf&lt;/code&gt; ファイルを生成する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dK3H8LtP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2271b3cb35efc7f1c9d51702662cdac9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dK3H8LtP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2271b3cb35efc7f1c9d51702662cdac9.png" alt="2. ワークフローの実行に成功したら、該当項目をクリックして詳細画面に遷移する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. ワークフローの実行に成功したら、該当項目をクリックして詳細画面に遷移する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uM1DZ-c5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/71b9dff8266c9bc990e1b709a5191535.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uM1DZ-c5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/71b9dff8266c9bc990e1b709a5191535.png" alt="3.  raw `Artifacts` endraw  の項目から  raw `alf` endraw  ファイルをダウンロードする"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. &lt;code&gt;Artifacts&lt;/code&gt; の項目から &lt;code&gt;alf&lt;/code&gt; ファイルをダウンロードする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nn-GPnsL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/cfec48fc58f2a17560ea2e7d0f71cc41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nn-GPnsL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/cfec48fc58f2a17560ea2e7d0f71cc41.png" alt="4. [Unity license manual activation webpage](https://license.unity3d.com) からログインして  raw `alf` endraw  ファイルをアップロードする"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4. &lt;a href="https://license.unity3d.com"&gt;Unity license manual activation webpage&lt;/a&gt; からログインして &lt;code&gt;alf&lt;/code&gt; ファイルをアップロードする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--N9VEGBkl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/72239a40ef5b2474f34c814a68f8c61d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--N9VEGBkl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/72239a40ef5b2474f34c814a68f8c61d.png" alt="5. Unity ライセンスの用途に応じて適切な選択肢を入力する (本記事では Personal ライセンスを選択)"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;5. Unity ライセンスの利用用途に応じて適切な選択肢を入力する (本記事では Personal ライセンスを選択)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mgjHkGEZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9ddc63dc321a68986bfedfb8a97c8f00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mgjHkGEZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9ddc63dc321a68986bfedfb8a97c8f00.png" alt="6.  raw `Download license` endraw  ボタンをクリックして  raw `ulf` endraw  ファイルをダウンロードする"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;6. &lt;code&gt;Download license&lt;/code&gt; ボタンをクリックして &lt;code&gt;ulf&lt;/code&gt; ファイルをダウンロードする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;これで Unity ライセンスファイルのアクティベートは完了です。&lt;strong&gt;次にアクティベートしたライセンスファイルを GitHub リポジトリの &lt;a href="https://docs.github.com/ja/actions/reference/encrypted-secrets"&gt;Secrets&lt;/a&gt; に登録して、GameCI で PlayMode 及び EditMode のテストが実行できるようにしていきます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;alf&lt;/code&gt; 拡張子のファイルがライセンスリクエストファイルを指していて、Unity ライセンスの発行に必要となるファイルです。&lt;code&gt;ulf&lt;/code&gt; 拡張子のファイルが Unity ライセンスのファイルです。&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Test runner: PlayMode 及び EditMode テストを実行して結果を参照する
&lt;/h2&gt;

&lt;p&gt;GitHub Actions 上でテストを実行するために、&lt;strong&gt;先ほどアクティベートした Unity ライセンスの情報を ワークフロー上で扱えるようにする必要があります。そのため、まずは Secrets に &lt;code&gt;ulf&lt;/code&gt; ファイルの内容を登録することから始めます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lSh0Hv3B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e126ae5e2fe9339d56047b8497808100.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lSh0Hv3B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e126ae5e2fe9339d56047b8497808100.png" alt="1. Unity ライセンスの情報登録のため、Github リポジトリの  raw `Secrets` endraw  登録画面に遷移する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. Unity ライセンスの情報登録のため、Github リポジトリの &lt;code&gt;Secrets&lt;/code&gt; 登録画面に遷移する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NaSyP5_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/f52356a229caa4e31e3ae8268d53a4e6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NaSyP5_v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/f52356a229caa4e31e3ae8268d53a4e6.png" alt="2. GameCI はライセンス情報参照のため  raw `Secrets` endraw  の  raw `UNITY_LICENSE` endraw  を参照する。そのため、 raw `Name` endraw  を  raw `UNITY_LICENSE` endraw  で  raw `Value` endraw  に  raw `ulf` endraw  ファイルの中身をコピー &amp;amp; ペーストしておく"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. GameCI はライセンス情報参照のため、デフォルト設定では &lt;code&gt;Secrets&lt;/code&gt; の &lt;code&gt;UNITY_LICENSE&lt;/code&gt; を参照する。そのため、&lt;code&gt;Name&lt;/code&gt; が &lt;code&gt;UNITY_LICENSE&lt;/code&gt;、&lt;code&gt;Value&lt;/code&gt; には &lt;code&gt;ulf&lt;/code&gt; ファイルの中身を登録する&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;上記作業で GameCI でテストやビルド実行を行える環境が整ったので、動作検証のためテスト実行用のワークフローファイルを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run EditMode and PlayMode Test&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run EditMode and PlayMode Test&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# actions/checkout@v2 を利用して作業ディレクトリに&lt;/span&gt;
      &lt;span class="c1"&gt;# Unity プロジェクトの中身をダウンロードしてくる&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out my unity project.&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="c1"&gt;# GameCI の Test runner を利用して&lt;/span&gt;
      &lt;span class="c1"&gt;# EditMode 及び PlayMode のテストを実行する&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run EditMode and PlayMode Test&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-ci/unity-test-runner@v2&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# 2. の手順で Secrets に登録した Unity ライセンスの情報を指定する&lt;/span&gt;
          &lt;span class="na"&gt;UNITY_LICENSE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_LICENSE }}&lt;/span&gt;

          &lt;span class="c1"&gt;# もし Professional license を使いたい場合は、&lt;/span&gt;
          &lt;span class="c1"&gt;# メールアドレス、パスワード、シリアルナンバーを入力する必要がある&lt;/span&gt;
          &lt;span class="c1"&gt;# ref: https://game.ci/docs/github/test-runner#professional-license&lt;/span&gt;
          &lt;span class="c1"&gt;# UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}&lt;/span&gt;
          &lt;span class="c1"&gt;# UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}&lt;/span&gt;
          &lt;span class="c1"&gt;# UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;projectPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="c1"&gt;# テストの実行結果もみたい場合は githubToken を指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# secrets.GITHUB_TOKEN は Secrets 未登録でも利用可能&lt;/span&gt;
          &lt;span class="na"&gt;githubToken&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;

          &lt;span class="c1"&gt;# Unity プロジェクトのバージョンを指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# ProjectSettings/ProjectVersion.txt に記載されているバージョンを入力すれば OK&lt;/span&gt;
          &lt;span class="na"&gt;unityVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2020.3.5f1&lt;/span&gt;

          &lt;span class="c1"&gt;# 実行したいテストの種類を指定できる&lt;/span&gt;
          &lt;span class="c1"&gt;# 指定可能な値は All, PlayMode, EditMode&lt;/span&gt;
          &lt;span class="c1"&gt;# testMode: All&lt;/span&gt;

          &lt;span class="c1"&gt;# テスト実行時に利用したい Docker イメージを明示的に指定できる&lt;/span&gt;
          &lt;span class="c1"&gt;# customImage: 'unityci/editor:2020.1.14f1-base-0'&lt;/span&gt;

      &lt;span class="c1"&gt;# テストの実行結果をアーティファクトにアップロードしてダウンロードして参照できるようにする&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test results&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;artifacts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記のワークフローファイルを GitHub Actions 上で動作検証する際の手順は下記になります。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Fw1shGJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9bace70734f1e99955f5b9aa068b31de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Fw1shGJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9bace70734f1e99955f5b9aa068b31de.png" alt="1. Unity のテストを実行するためのワークフローを選択して実行する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. Unity のテストを実行するためのワークフローを選択して実行する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rWGstX8o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ffc31b5919431fedb516f1932a13ce62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rWGstX8o--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ffc31b5919431fedb516f1932a13ce62.png" alt="2. ワークフローの実行が成功したら、詳細画面に遷移した後、 raw `Test Results` endraw  の項目からテストの実行結果を確認する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. ワークフローの実行が成功したら、詳細画面に遷移した後、&lt;code&gt;Test Results&lt;/code&gt; の項目からテストの実行結果を確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;テスト実行用のワークフローファイルでは &lt;a href="https://docs.github.com/ja/actions/reference/events-that-trigger-workflows#manual-events"&gt;&lt;code&gt;workflow_dispatch&lt;/code&gt;&lt;/a&gt; で実行可能にしていますが、&lt;code&gt;pull_request&lt;/code&gt; を利用すればプルリク時にテストを実行させることが可能になります。&lt;/p&gt;
&lt;h2&gt;
  
  
  Builder: プロジェクトのビルドを実行して出力結果を確認する
&lt;/h2&gt;

&lt;p&gt;GameCI にはプロジェクトのビルドを行うための GitHub Actions も用意されています。&lt;strong&gt;実際に GameCI で WebGL ビルドを行いその内容を GitHub Pages で確認できるようにして動作検証していきます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;早速 WebGL ビルドを行うためのワークフローファイルを作成していきます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run the WebGL build&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run the WebGL build&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# actions/checkout@v2 を利用して作業ディレクトリに&lt;/span&gt;
      &lt;span class="c1"&gt;# Unity プロジェクトの中身をダウンロードしてくる&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check out my unity project.&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class="c1"&gt;# GameCI の Builder を利用して、&lt;/span&gt;
      &lt;span class="c1"&gt;# Unity プロジェクトのビルドを実行する&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run the WebGL build&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-ci/unity-builder@v2&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;UNITY_LICENSE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_LICENSE }}&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# 今回は WebGL ビルドを行いたいため WebGL を指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# WebGL 以外の指定可能な値は下記に記載の値が利用可能&lt;/span&gt;
          &lt;span class="c1"&gt;# ref: https://docs.unity3d.com/ScriptReference/BuildTarget.html&lt;/span&gt;
          &lt;span class="na"&gt;targetPlatform&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WebGL&lt;/span&gt;

          &lt;span class="na"&gt;unityVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2020.3.5f1&lt;/span&gt;
      &lt;span class="c1"&gt;# Builder で出力した WebGL ビルドを GitHub Pages にデプロイする&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to GitHub Pages&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JamesIves/github-pages-deploy-action@4.1.3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# GitHub Pages デプロイ用の Orphan ブランチ名を指定する&lt;/span&gt;
          &lt;span class="na"&gt;branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh-pages&lt;/span&gt;

          &lt;span class="c1"&gt;# デプロイ用ビルドフォルダパスを指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# GameCI の Builder はデフォルトでは build フォルダにビルド内容を出力する&lt;/span&gt;
          &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;

      &lt;span class="c1"&gt;# Builder で出力した WebGL ビルドをアーティファクトでダウンロード可能にする&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload the WebGL Build&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記のワークフローファイルを GitHub Actions 上で動作検証する際の手順は下記になります。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TxtioYH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b8f43baef2e2edbf8d5510ce8f58172d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TxtioYH0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b8f43baef2e2edbf8d5510ce8f58172d.png" alt="1. Unity の WebGL ビルドを実行するためのワークフローを実行する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. Unity の WebGL ビルドを実行するためのワークフローを実行する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---0HBzYcI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a102d1f5cb4d0581746770d1d9cad965.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---0HBzYcI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a102d1f5cb4d0581746770d1d9cad965.png" alt="2. ワークフローの実行が成功したら、詳細画面に遷移した後、ビルド内容が正常か確認する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. ワークフローの実行が成功したら、詳細画面に遷移した後、ビルド内容が正常そうか確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Hinc9Orz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/7d39c035b52ba3862d5dad18213a2041.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Hinc9Orz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/7d39c035b52ba3862d5dad18213a2041.png" alt="3. ビルド内容を確認するための GitHub Pages の設定を Settings から行う"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. ビルド内容を確認するための GitHub Pages の設定を Settings から行う&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JYuE0yL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b2e1bd86ca9d3b3f207a5ddbb1de3ba7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JYuE0yL5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b2e1bd86ca9d3b3f207a5ddbb1de3ba7.png" alt="4. GitHub Pages でブラウザから WebGL ビルドの動作確認をする"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4. GitHub Pages でブラウザから WebGL ビルドの動作確認をする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;上記のように Builder を利用することで WebGL ビルドの成否及び、最新のビルド内容を常に GitHub Pages で見られるようにできます。&lt;/strong&gt; すると WebGL ビルドが正常かどうかの確認が常に GitHub Pages を見れば把握できるようになるため、&lt;a href="https://unityroom.com/unity1weeks"&gt;Unity1 週間ゲームジャム&lt;/a&gt; などに参加する際で便利に活用できそうです。&lt;/p&gt;

&lt;p&gt;WebGL ビルドを行う際、Unity バージョンやアセットの対応状況によっては正しくブラウザ上で動作しないビルドが出力されます。&lt;strong&gt;ただし、ブラウザで発生するエラー内容によっては WebGL のビルド設定を見直すだけで解決できる場合があります。&lt;/strong&gt; 例えば &lt;code&gt;unityframework is not defined&lt;/code&gt; というエラーが発生した際は、&lt;a href="https://qiita.com/aguroshou0413/items/1451a6779a92acb96b78"&gt;この記事&lt;/a&gt; のように WebGL の &lt;code&gt;Build Settings&lt;/code&gt; を見直すことで解決できる場合があります。&lt;/p&gt;

&lt;p&gt;私の環境では &lt;a href="https://github.com/JamesIves/github-pages-deploy-action"&gt;&lt;code&gt;JamesIves/github-pages-deploy-action&lt;/code&gt;&lt;/a&gt; で GitHub Pages へのデプロイを行った際、&lt;strong&gt;デフォルトでは &lt;code&gt;/WebGL/WebGL&lt;/code&gt; フォルダにビルド内容が出力されました。そのため、ブラウザから WebGL ビルドにアクセスする際、&lt;code&gt;&amp;lt;GitHub Pages の URL&amp;gt;/WebGL/WebGL&lt;/code&gt; のような URL にアクセスする必要がありました。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Returning a license: GameCI で利用している Unity License を返却する
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;通常利用することは無いと&lt;a href="https://game.ci/docs/github/returning-a-license"&gt;公式サイト&lt;/a&gt;にも書かれていますが、Professional License の返却も GameCI で行うことが可能です。&lt;/strong&gt; 今回は Personal License を利用したため使用しませんでしたが、下記をワークフローのステップに組み込むことで Professional License を返却できるようです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="c1"&gt;# どこかのタイミングでライセンスのアクティベートを行う&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Activate Unity&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-ci/unity-activate@v1&lt;/span&gt;
  &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;UNITY_LICENSE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.UNITY_LICENSE }}&lt;/span&gt;

&lt;span class="c1"&gt;#...&lt;/span&gt;
&lt;span class="c1"&gt;# ステップの最後などに game-ci/unity-return-license@v1 を呼び出して&lt;/span&gt;
&lt;span class="c1"&gt;# アクティベート済みのライセンスを返却する&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Return license&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;game-ci/unity-return-license@v1&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;以前 Unity コマンドを駆使して自分で CI 環境を構築した経験があるのですが、&lt;br&gt;
GameCI を利用した方が全然楽に Unity CI 環境構築を GitHub Actions 上で行えました。&lt;/p&gt;

&lt;p&gt;ちなみに &lt;a href="https://game.ci/docs/docker/docker-images"&gt;GameCI で利用されている Docker イメージ&lt;/a&gt; は以前からよく使われていた &lt;a href="https://hub.docker.com/r/gableroux/unity3d/"&gt;gableroux/unity3d&lt;/a&gt; が元になっているようでした。ってか &lt;a href="https://gableroux.com/about/"&gt;GabLeRoux さんのホームページ&lt;/a&gt; を見たら、GameCI の開発を始めた方のようでした。すごい。&lt;/p&gt;

&lt;p&gt;本記事が GitHub Actions で Unity CI 環境構築を始めようとしている方の助けになれれば幸いです。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://game.ci/"&gt;GameCI - The fastest and easiest way to automatically test and build your game projects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unity3d.com/jp/unity/features/cloud-build"&gt;Services - Cloud Build - Unity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/jp/cloudformation/"&gt;AWS CloudFormation（テンプレートを使ったリソースのモデル化と管理）| AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fastlane.tools/"&gt;fastlane - App automation done right&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/github/setting-up-and-managing-your-github-user-account/managing-user-account-settings/managing-the-default-branch-name-for-your-repositories#about-management-of-the-default-branch-name"&gt;リポジトリのデフォルトブランチ名を管理する - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.yucchiy.com/2019/01/08/how-to-get-unity-free-license/"&gt;Unity でパーソナルライセンスのシリアルナンバーを発行する | Yucchiy's Note&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://license.unity3d.com"&gt;Unity license manual activation webpage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/actions/reference/encrypted-secrets"&gt;暗号化されたシークレット - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.unity3d.com/ScriptReference/BuildTarget.html"&gt;Unity - Scripting API: BuildTarget&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unityroom.com/unity1weeks"&gt;Unity 1 週間ゲームジャム | フリーゲーム投稿サイト unityroom&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/aguroshou0413/items/1451a6779a92acb96b78"&gt;Unity2020 WebGL 9 割まで読み込めるがアプリが起動しない不具合の解決方法 - Qiita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/marketplace/actions/deploy-to-github-pages"&gt;Deploy to GitHub Pages · Actions · GitHub Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;code&gt;alf&lt;/code&gt; ファイル及び &lt;code&gt;ulf&lt;/code&gt; ファイルの実態は XML ファイルです。 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;適当なテキストエディタで &lt;code&gt;ulf&lt;/code&gt; ファイルを開き全文をコピー &amp;amp; ペーストします。 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>unity3d</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>[TECH] Zenn の記事を DEV に自動的に同期させる GitHub Actions 作ってみた 📌</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Mon, 22 Mar 2021 03:12:11 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-zenn-dev-github-action-1ojm</link>
      <guid>https://forem.com/nikaera/tech-zenn-dev-github-action-1ojm</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;去年 &lt;a href="https://dev.to/"&gt;DEV&lt;/a&gt; のアカウントを作成したものの、今まで全く有効活用出来ていませんでした。&lt;/p&gt;

&lt;p&gt;DEV には &lt;a href="https://dev.to/michaelburrows/comment/125j0"&gt;カノニカル URL&lt;/a&gt; を設定出来るので、常々 Zenn の記事を投稿する際にクロスポストしたいなと考えておりました。そこで、&lt;strong&gt;Zenn に記事を投稿したら、自動的に DEV にも記事を投稿 &amp;amp; 同期する GitHub Actions を作ってみました。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/nikaera/sync-zenn-with-dev-action"&gt;https://github.com/nikaera/sync-zenn-with-dev-action&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;今回初めて GitHub Actions を自作したのですが、その中で得た知見を残す形で記事を書くことにしました。また、GitHub Actions は TypeScript で作成しました。&lt;/p&gt;

&lt;h1&gt;
  
  
  開発した GitHub Actions の概要
&lt;/h1&gt;

&lt;p&gt;まずはザッとどのような GitHub Actions を作成したのか、概要について説明いたします。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub リポジトリで管理している Zenn の記事を DEV に同期して投稿する GitHub Actions を作成しました。&lt;/strong&gt; その際に DEV へ投稿する記事には Zenn の該当記事へのカノニカル URL も自動で設定できます。これにより DEV と Zenn へ記事をシームレスにクロスポストすることが可能となります。&lt;/p&gt;

&lt;p&gt;今回作成した GitHub Actions を利用するワークフローファイルの一例は下記となります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sync&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout my project&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.to action step&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nikaera/sync-zenn-with-dev-action@v1&lt;/span&gt;
        &lt;span class="c1"&gt;# id を設定することで、後のジョブで Output で指定した値が参照可能になる&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev-to&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# DEV の API キーを指定する&lt;/span&gt;
          &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.api_key }}&lt;/span&gt;
          &lt;span class="c1"&gt;# (オプション) DEV に記事を投稿した際に Zenn のカノニカル URL を設定したい場合に指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# username: nikaera&lt;/span&gt;
          &lt;span class="c1"&gt;# (オプション) 改行区切りで指定した articles フォルダ内のファイルパスを記載した txt ファイルを指定することで、記載された記事のみを同期するようになる。&lt;/span&gt;
          &lt;span class="c1"&gt;# 他プラグインと組み合わせることで差分のみを txt ファイルに載せることが可能。詳細については後述の Outputs の項目に記載。&lt;/span&gt;
          &lt;span class="c1"&gt;# added_modified_filepath: ./added_modified.txt&lt;/span&gt;
          &lt;span class="c1"&gt;# (オプション) Zenn の articles 以下全ての記事を常に DEV に同期するか指定する&lt;/span&gt;
          &lt;span class="c1"&gt;# update_all が true のときは added_modified_filepath は無視される。&lt;/span&gt;
          &lt;span class="c1"&gt;# update_all: false&lt;/span&gt;
        &lt;span class="c1"&gt;# 上記アクション実行時に DEV に新規で同期する記事に関しては Zenn のマークダウンヘッダに&lt;/span&gt;
        &lt;span class="c1"&gt;# 該当する DEV の記事の ID が dev_article_id として記載されるようになる。&lt;/span&gt;
        &lt;span class="c1"&gt;# 今後はその ID を元に同期するようになるため、該当する Zenn の記事をコミットする。&lt;/span&gt;
        &lt;span class="c1"&gt;# 新規で同期する記事が無ければ、このジョブは実行しない。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write article id of DEV to articles of Zenn.&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name github-actions&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email github-actions@github.com&lt;/span&gt;
          &lt;span class="s"&gt;git add ${{ steps.dev-to.outputs.newly-sync-articles }}&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "sync: Zenn with DEV [skip ci]"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.dev-to.outputs.newly-sync-articles&lt;/span&gt;
        &lt;span class="c1"&gt;# Outputs には DEV の記事情報 (title, url) が含まれるようになるため、&lt;/span&gt;
        &lt;span class="c1"&gt;# 最後に出力して実行結果の内容を確認することもできる&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the output articles.&lt;/span&gt;
        &lt;span class="c1"&gt;# dev-to という id が紐付いたジョブの Outputs を取得して echo で内容を出力する&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "${{ steps.dev-to.outputs.articles }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;簡単に &lt;code&gt;nikaera/sync-zenn-with-dev-action@v1&lt;/code&gt; というジョブの内部処理について説明いたします。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inputs の &lt;code&gt;update_all&lt;/code&gt; 及び &lt;code&gt;added_modified_filepath&lt;/code&gt; で受け取った情報を元に、
どの記事を DEV に同期させるか判定する&lt;/li&gt;
&lt;li&gt;DEV に同期する記事のファイルパス一覧を取得して、
それぞれの記事のヘッダに &lt;code&gt;dev_article_id&lt;/code&gt; の記載があるか判定する&lt;/li&gt;
&lt;li&gt;Inputs の &lt;code&gt;api_key&lt;/code&gt; を利用して、Zenn の記事に &lt;code&gt;dev_article_id&lt;/code&gt; が含まれていれば、
該当する DEV の記事を更新する。含まれていなければ DEV に新規で記事を作成する&lt;/li&gt;
&lt;li&gt;Inputs の &lt;code&gt;username&lt;/code&gt; を利用して、
DEV の記事に該当する Zenn 記事のカノニカル URL を設定する&lt;/li&gt;
&lt;li&gt;DEV に新規で記事を作成した場合は、
Zenn の該当記事のヘッダに &lt;code&gt;dev_article_id&lt;/code&gt; を書き込む&lt;/li&gt;
&lt;li&gt;DEV に記事を投稿する際、Zenn は対応しているが、
DEV では対応していない記述は削除する (`&lt;/li&gt;
&lt;li&gt;記事の公開ステータス及びタグなどについても DEV の記事に反映する&lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;新規で DEV に記事を同期した Zenn 記事のファイルパスを、
Outputs の &lt;code&gt;newly-sync-articles&lt;/code&gt; に設定する
(後のジョブで &lt;code&gt;dev_article_id&lt;/code&gt; の含まれた記事をコミットしたいため)&lt;/li&gt;
&lt;li&gt;ワークフローで同期された記事情報は Outouts の &lt;code&gt;articles&lt;/code&gt; に設定する&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inputs と Outputs の内容一覧については下記になります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inputs&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;キー&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;th&gt;必須&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;api_key&lt;/td&gt;
&lt;td&gt;DEV の &lt;a href="https://docs.forem.com/api/#section/Authentication"&gt;API Key&lt;/a&gt; を設定する&lt;/td&gt;
&lt;td&gt;o&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;username&lt;/td&gt;
&lt;td&gt;Zenn の &lt;strong&gt;自分のアカウント名&lt;/strong&gt; を設定する (DEV に同期する記事に Zenn のカノニカル URL を設定したい場合のみ)&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;added_modified_filepath&lt;/td&gt;
&lt;td&gt;改行区切りで指定した articles フォルダ内のファイルパスを記載した txt ファイルを指定することで、記載された記事のみを同期するようになる。&lt;strong&gt;PR やコミット差分のファイルのみを取得するための GitHub Actions &lt;a href="https://github.com/jitterbit/get-changed-files"&gt;jitterbit/get-changed-files@v1&lt;/a&gt; と組み合わせることで、更新差分のあった記事のみを随時同期することも可能。&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/strong&gt; 更新差分のあった記事のみを随時同期するための&lt;a href="https://github.com/nikaera/zenn.dev/blob/main/.github/workflows/sync-zenn-with-dev.yml"&gt;実際のワークフローファイルはこちら&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;update_all&lt;/td&gt;
&lt;td&gt;Zenn の全ての記事をどうきするかどうかを設定する。GitHub Actions 初回実行時のみ true にする使い方を想定している。デフォルトは true。&lt;strong&gt;&lt;code&gt;added_modified_filepath&lt;/code&gt; よりも &lt;code&gt;update_all&lt;/code&gt; が優先されるため &lt;code&gt;added_modified_filepath&lt;/code&gt; を設定する場合は false を設定する必要あり`&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Outputs&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;キー&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;articles&lt;/td&gt;
&lt;td&gt;同期された DEV の記事のタイトル及び URL が格納された配列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;newly-sync-articles&lt;/td&gt;
&lt;td&gt;DEV で新たに新規作成された Zenn 記事のファイルパスが格納された配列。&lt;strong&gt;&lt;a href="https://github.com/nikaera/sync-zenn-with-dev-action/blob/main/.github/workflows/test.yml#L31-L38"&gt;実際のワークフローファイルの該当する記述&lt;/a&gt;のように、必ずコミットに含めるようにする必要がある (理由は後述)&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Inputs 及び Outputs については&lt;a href="https://docs.github.com/ja/actions/creating-actions/metadata-syntax-for-github-actions#"&gt;公式サイトの説明&lt;/a&gt;をご参照ください。&lt;/p&gt;

&lt;h2&gt;
  
  
  Zenn の記事を DEV に同期するための仕組み
&lt;/h2&gt;

&lt;p&gt;Zenn の記事を新規で DEV に同期する際は、DEV に記事を新規作成する必要があります。&lt;strong&gt;その際に Zenn の記事と DEV の記事を紐付けるための何らかの仕組みが必要となります。そうしないと、今後 Zenn の記事内容を更新した際に、DEV のどの記事に内容を同期させればよいかが不明なためです。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;そこで、記事を同期するための仕組みとして、&lt;strong&gt;&lt;code&gt;dev_article_id&lt;/code&gt; というフィールドを Zenn のマークダウンヘッダに追記することで DEV の同期すべき記事との紐付けを行うことにしました。&lt;/strong&gt;&lt;code&gt;dev_article_id&lt;/code&gt; には &lt;a href="https://docs.forem.com/api/#operation/createArticle"&gt;DEV の記事作成 API&lt;/a&gt; 実行時の返り値である &lt;code&gt;id&lt;/code&gt; を設定します。&lt;/p&gt;

&lt;p&gt;一度 &lt;code&gt;id&lt;/code&gt; を &lt;code&gt;dev_article_id&lt;/code&gt; として Zenn の記事に紐付けてしまえば、次回以降に記事の同期を行う際は &lt;a href="https://docs.forem.com/api/#operation/updateArticle"&gt;DEV の記事更新 API&lt;/a&gt; を利用できます。&lt;/p&gt;

&lt;p&gt;また、&lt;strong&gt;Outputs の &lt;code&gt;newly-sync-articles&lt;/code&gt; には新規で作成された DEV 記事の &lt;code&gt;id&lt;/code&gt; である &lt;code&gt;dev_article_id&lt;/code&gt; が追記された Zenn 記事のファイルパスが格納されています。そのため、&lt;code&gt;nikaera/sync-zenn-with-dev-action@v1&lt;/code&gt; 実行後は、下記のように &lt;code&gt;steps.dev-to.outputs.newly-sync-articles&lt;/code&gt; 内に格納されたファイル群をコミットに反映させる必要があります。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# `nikaera/sync-zenn-with-dev-action@v1` 実行後に必ず定義すべきジョブ&lt;/span&gt;
&lt;span class="c1"&gt;# DEV に新規に作成した記事がなければ実行しない (if: steps.dev-to.outputs.newly-sync-articles)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write article id of DEV to articles of Zenn.&lt;/span&gt;
    &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;git config user.name github-actions&lt;/span&gt;
        &lt;span class="s"&gt;git config user.email github-actions@github.com&lt;/span&gt;
        &lt;span class="s"&gt;git add ${{ steps.dev-to.outputs.newly-sync-articles }}&lt;/span&gt;
        &lt;span class="s"&gt;git commit -m "sync: Zenn with DEV [skip ci]"&lt;/span&gt;
        &lt;span class="s"&gt;git push&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.dev-to.outputs.newly-sync-articles&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記のジョブで &lt;code&gt;newly-sync-articles&lt;/code&gt; に格納された &lt;code&gt;dev_article_id&lt;/code&gt; が追記された Zenn 記事は随時コミットに反映しないと、&lt;strong&gt;Zenn の全ての記事が同期毎 DEV に新規作成され続けるという不具合を引き起こしてしまうので、ご注意ください&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub Actions を開発する手順
&lt;/h1&gt;

&lt;p&gt;サクッと開発に取り組みたかったため、&lt;a href="https://docs.github.com/ja/actions/creating-actions/creating-a-docker-container-action"&gt;Docker コンテナを利用する方法&lt;/a&gt; ではなく、&lt;a href="https://docs.github.com/ja/actions/creating-actions/creating-a-javascript-action"&gt;JavaScript を利用する方法&lt;/a&gt; で開発を進めていくことにしました。&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript で GitHub Actions を作る
&lt;/h2&gt;

&lt;p&gt;GitHub 公式が TypeScript で GitHub Actions を作るための &lt;a href="https://github.com/actions/typescript-action"&gt;テンプレートプロジェクト&lt;/a&gt; を用意してくれています。今回はこのテンプレートプロジェクトを利用する形でプロジェクトを作成しました。&lt;/p&gt;

&lt;p&gt;(余談) GitHub Actions では &lt;a href="https://docs.github.com/ja/actions/creating-actions/creating-a-docker-container-action"&gt;Docker コンテナ&lt;/a&gt; を用いてワークフローを実行可能です。&lt;strong&gt;そのため、実行環境は自由に設定出来ます。(Go, Python, Ruby, etc.)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;早速 TypeScript のテンプレートプロジェクトを元に自分の GitHub Actions プロジェクトを作成します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--stJZ9wdm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/359f6f795bab9807c9f480c0f922973a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--stJZ9wdm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/359f6f795bab9807c9f480c0f922973a.png" alt="スクリーンショット 2021-03-21 13.25.54.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. テンプレートプロジェクトを元に GitHub Actions の TypeScript プロジェクトを作成する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jgTYiKNQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/76aecd3d63db95d34c086774ded122d4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jgTYiKNQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/76aecd3d63db95d34c086774ded122d4.png" alt="スクリーンショット 2021-03-21 13.29.43.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. プロジェクトの作成後 &lt;code&gt;git clone&lt;/code&gt; してきて開発する準備を整える&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Actions プロジェクトの開発を進めるための準備を行う
&lt;/h2&gt;

&lt;p&gt;テンプレートプロジェクトを &lt;code&gt;git clone&lt;/code&gt; したら、まずは &lt;code&gt;action.yml&lt;/code&gt; の内容を変更します。&lt;br&gt;
今回作成した GitHub Actions の &lt;code&gt;action.yml&lt;/code&gt; は下記となっております。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# GitHub Actions のプロジェクト名&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sync&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV'&lt;/span&gt;
&lt;span class="c1"&gt;# GitHub Actions のプロジェクト説明文&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Just&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sync&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV.'&lt;/span&gt;
&lt;span class="c1"&gt;# GitHub Actions の作者&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;nikaera'&lt;/span&gt;
&lt;span class="c1"&gt;# GitHub Actions に渡せる引数の値定義&lt;/span&gt;
&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# フィールドの指定が必須であれば true、必須でなければ false を設定する&lt;/span&gt;
    &lt;span class="c1"&gt;# DEV の API キーは同期を行う際に必須なため、true を設定している&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;# フィールドの説明文&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;api_key&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;use&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;API&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(https://docs.forem.com/api/#section/Authentication)'&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user's&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;account&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;name.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(Fields&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;filled&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;if&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;canonical&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set.)"&lt;/span&gt;
  &lt;span class="na"&gt;articles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;where&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stored."&lt;/span&gt;
    &lt;span class="c1"&gt;# フィールドにはデフォルト値を指定することも可能&lt;/span&gt;
    &lt;span class="c1"&gt;# Zenn の記事がデフォで格納されているフォルダ名を指定している&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;articles&lt;/span&gt;
  &lt;span class="na"&gt;update_all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;require&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Whether&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;synchronize&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles."&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;added_modified_filepath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;Synchronize only the articles in the file path divided by line breaks.&lt;/span&gt;
      &lt;span class="s"&gt;You can use jitterbit/get-changed-files@v1 to get only the file paths of articles that have changed in the correct format.&lt;/span&gt;
      &lt;span class="s"&gt;(https://github.com/jitterbit/get-changed-files)&lt;/span&gt;
&lt;span class="c1"&gt;# GitHub Actions 実行後に参照可能になる値定義&lt;/span&gt;
&lt;span class="na"&gt;outputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;articles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;URLs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dev.to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;that&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;have&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;been&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;updated'&lt;/span&gt;
  &lt;span class="na"&gt;newly-sync-articles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;File&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;newly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;synchronized&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;articles.'&lt;/span&gt;
&lt;span class="c1"&gt;# GitHub Actions の実行環境&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;node12'&lt;/span&gt;
  &lt;span class="c1"&gt;# テンプレートプロジェクトでは コンパイル先が dist になるため `dist/index.js` を指定している&lt;/span&gt;
  &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dist/index.js'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TypeScript のテンプレートプロジェクトでは、バンドルツールとして &lt;a href="https://github.com/vercel/ncc"&gt;&lt;code&gt;ncc&lt;/code&gt;&lt;/a&gt; が採用されています。&lt;strong&gt;GitHub Actions 実行時に使用されるのは ncc によりコンパイルされた単一の JavaScript ファイル (&lt;code&gt;dist/index.js&lt;/code&gt;) になります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;あとは &lt;code&gt;src&lt;/code&gt; フォルダ内でプログラムを書いて、&lt;code&gt;npm run all &amp;amp;&amp;amp; node dist/index.js&lt;/code&gt; のようにコマンド実行しながら開発を進めていくだけです。&lt;/p&gt;

&lt;p&gt;(余談) GitHub Actions の開発ツールとして Docker を利用した &lt;a href="https://github.com/nektos/act"&gt;&lt;code&gt;act&lt;/code&gt;&lt;/a&gt; というものが存在するようです。ローカル環境で検証する際は &lt;a href="https://github.com/nektos/act#known-issues"&gt;既知の問題&lt;/a&gt; に対応する必要がありそうですが、GitHub Actions の開発で非常に有効活用できそうで気になっております。&lt;/p&gt;

&lt;p&gt;今回の開発では利用しなかったのですが、今後開発を進めていく中で利用する機会も出てきそうなので、その際は本記事内容を更新する形で知見を追記したいと考えております。&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Actions を実装する際に利用した機能
&lt;/h2&gt;

&lt;p&gt;GitHub Actions を実装する際に利用した機能を、実際のコード内容を抜粋して簡単に説明していきます。下記で紹介する内容は &lt;a href="https://github.com/actions/toolkit"&gt;GitHub Actions Toolkit&lt;/a&gt; の機能です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
下記の yml の with で指定した値は core.getInput で受け取ることが可能。

- name: dev.to action step
    uses: nikaera/sync-zenn-with-dev-action@v1
    id: dev-to
    with:
        # DEV の API キーを指定する
        api_key: ${{ secrets.api_key }}
*/&lt;/span&gt;
&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;api_key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;update_all&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="cm"&gt;/**
下記の yml の steps.&amp;lt;ジョブで指定した id&amp;gt;.outputs で参照可能な値をセットすることが可能。
セットする内容は文字列である必要がある。

- name: dev.to action step
    uses: nikaera/sync-zenn-with-dev-action@v1
    id: dev-to
- name: Get the output articles.
    run: echo "${{ steps.dev-to.outputs.articles }}"
*/&lt;/span&gt;

&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;devtoArticles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;newly-sync-articles&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newlySyncedArticles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="cm"&gt;/**
GitHub Actions 実行時に出力されるログをレベルごとに出力することが可能
core.debug はローカル実行時のみに出力内容を確認することができる
*/&lt;/span&gt;
&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;debug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`update_all: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;updateAll&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記だけ把握してれば GitHub Actions の開発は問題なく行うことができました。&lt;/p&gt;

&lt;h1&gt;
  
  
  作成した GitHub Actions を実際に GitHub 上で実行可能にする
&lt;/h1&gt;

&lt;p&gt;ローカル環境で一通り開発が完了したら、GitHub リポジトリに push した後タグ付けを行います。&lt;strong&gt;GitHub Actions はタグを設定しないと実行できないため必要な作業となります。&lt;/strong&gt; 今回タグ付けは GitHub 上で行いました。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f3p3t2MM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/252514cca20470154475db75b133e9b3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f3p3t2MM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/252514cca20470154475db75b133e9b3.png" alt="スクリーンショット 2021-03-22 8.11.58.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. タグの項目をクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--swlyLMDe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a8c145f8d6ae7b1f6253eb4866bcbb51.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--swlyLMDe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a8c145f8d6ae7b1f6253eb4866bcbb51.png" alt="スクリーンショット 2021-03-22 8.14.47.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. &lt;code&gt;Create a new release&lt;/code&gt; ボタンをクリックしてタグ作成画面に遷移する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n1rsy3n9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/25e019e42227ad7e0b0b634e68bb1873.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n1rsy3n9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/25e019e42227ad7e0b0b634e68bb1873.png" alt="スクリーンショット 2021-03-22 8.20.00.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. &lt;code&gt;Publish release&lt;/code&gt; ボタンをクリックしてタグの作成を完了する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;上記の例では &lt;code&gt;v1&lt;/code&gt; というタグを作成したので &lt;code&gt;nikaera/sync-zenn-with-dev-action@v1&lt;/code&gt; のような記述で GitHub Actions を利用可能になりました。&lt;/strong&gt; 私は Zenn の記事を &lt;a href="https://github.com/nikaera/zenn.dev/"&gt;&lt;code&gt;zenn.dev&lt;/code&gt;&lt;/a&gt; というリポジトリで管理しているため、早速このリポジトリに GitHub Actions を導入してみます。&lt;/p&gt;
&lt;h2&gt;
  
  
  Zenn の全ての記事を DEV に同期するためのワークフロー
&lt;/h2&gt;

&lt;p&gt;本記事の GitHub Actions では DEV の API キーを使用するため、&lt;strong&gt;事前にシークレットへ &lt;code&gt;API_KEY&lt;/code&gt; という名称で値を登録しておきます。&lt;/strong&gt;&lt;a href="https://docs.github.com/ja/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;公式サイトの手順&lt;/a&gt; に従い シークレットの登録が完了したら、該当リポジトリに &lt;code&gt;.github/workflows/sync-zenn-with-dev-all.yml&lt;/code&gt; というワークフローファイルを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sync-All&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains(github.event.head_commit.message, '[skip ci]') == &lt;/span&gt;&lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup node project&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.to action step&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nikaera/sync-zenn-with-dev-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev-to&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.api_key }}&lt;/span&gt;
          &lt;span class="c1"&gt;# Zenn の自分のアカウント名を指定すると&lt;/span&gt;
          &lt;span class="c1"&gt;# DEV 記事のカノニカル URL に Zenn 記事の URL を指定できる&lt;/span&gt;
          &lt;span class="c1"&gt;# username: nikaera&lt;/span&gt;
          &lt;span class="na"&gt;update_all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write article id of DEV to articles of Zenn.&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name github-actions&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email github-actions@github.com&lt;/span&gt;
          &lt;span class="s"&gt;git add ${{ steps.dev-to.outputs.newly-sync-articles }}&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "sync: Zenn with DEV [skip ci]"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.dev-to.outputs.newly-sync-articles&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the output articles.&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "${{ steps.dev-to.outputs.articles }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記は手動実行が可能な Zenn の記事を全て DEV に同期するためのワークフローファイルになります。作成が完了したらワークフローファイルを実行して、記事が正常に同期できているか確認してみます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cF8LnZSw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e0740fe369752db67514e0ab50e88772.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cF8LnZSw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e0740fe369752db67514e0ab50e88772.png" alt="スクリーンショット 2021-03-22 8.35.38.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. &lt;code&gt;Actions&lt;/code&gt; タブをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RE2nPIsS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2aeadddccf08c0dddc54dddab464edee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RE2nPIsS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/2aeadddccf08c0dddc54dddab464edee.png" alt="スクリーンショット 2021-03-22 8.37.24.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. 作成した &lt;code&gt;Sync-All Zenn with DEV&lt;/code&gt; ワークフローファイルを選択する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BzQxzfGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d9e138de1f6e0baac8b0c3bc6742f4f2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BzQxzfGl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d9e138de1f6e0baac8b0c3bc6742f4f2.png" alt="スクリーンショット 2021-03-22 8.40.11.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. &lt;code&gt;Run workflow&lt;/code&gt; ボタンを実行して、ワークフローを実行する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pQhEG9S2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e67df0f6e33d1f57a46000d20ef2c824.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pQhEG9S2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e67df0f6e33d1f57a46000d20ef2c824.png" alt="スクリーンショット 2021-03-22 8.45.23.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4. ワークフローの実行が正常に完了していれば、ログに DEV の記事情報が出力される&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qZ65dxvW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/4acc0b0de3acd6731918a9347e7baea8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qZ65dxvW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/4acc0b0de3acd6731918a9347e7baea8.png" alt="スクリーンショット 2021-03-22 8.50.37.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;5. &lt;code&gt;dev.to&lt;/code&gt; にアクセスして Zenn の記事情報が正常に同期されていることを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;正常に Zenn の全ての記事が DEV に同期されていることが確認できたら、次は Zenn の記事が新規作成されたり、更新されたときのみに DEV に同期するためのワークフローファイルを作成します。&lt;/p&gt;
&lt;h2&gt;
  
  
  更新差分のあった Zenn 記事のみを DEV に同期するためのワークフロー
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;main&lt;/code&gt; ブランチが更新されたときのみ更新された記事のみを DEV に同期するためのワークフローファイルを作成します。該当リポジトリに &lt;code&gt;.github/workflows/sync-zenn-with-dev.yml&lt;/code&gt; というワークフローファイルを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sync&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Zenn&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DEV"&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains(github.event.head_commit.message, '[skip ci]') == &lt;/span&gt;&lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;setup node project&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get modified files&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;files&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jitterbit/get-changed-files@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;output modified files to text&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;for changed_file in ${{ steps.files.outputs.added_modified }}; do&lt;/span&gt;
            &lt;span class="s"&gt;echo "${changed_file}" &amp;gt;&amp;gt; added_modified.txt&lt;/span&gt;
          &lt;span class="s"&gt;done&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev.to action step&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nikaera/sync-zenn-with-dev-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dev-to&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.api_key }}&lt;/span&gt;
          &lt;span class="c1"&gt;# Zenn の自分のアカウント名を指定すると&lt;/span&gt;
          &lt;span class="c1"&gt;# DEV 記事のカノニカル URL に Zenn 記事の URL を指定できる&lt;/span&gt;
          &lt;span class="c1"&gt;# username: nikaera&lt;/span&gt;
          &lt;span class="na"&gt;added_modified_filepath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./added_modified.txt&lt;/span&gt;
          &lt;span class="na"&gt;update_all&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write article id of DEV to articles of Zenn.&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config user.name github-actions&lt;/span&gt;
          &lt;span class="s"&gt;git config user.email github-actions@github.com&lt;/span&gt;
          &lt;span class="s"&gt;git add ${{ steps.dev-to.outputs.newly-sync-articles }}&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "sync: Zenn with DEV [skip ci]"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.dev-to.outputs.newly-sync-articles&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the output articles.&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "${{ steps.dev-to.outputs.articles }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記ワークフローファイルの作成が完了したら、早速動作確認のために、&lt;strong&gt;まさに今執筆中の本記事内容をリポジトリに push してみます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--28ApH1Z6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/36512e7874b49edc1e48f0ef88af5d89.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--28ApH1Z6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/36512e7874b49edc1e48f0ef88af5d89.png" alt="スクリーンショット 2021-03-22 9.07.55.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;1. &lt;code&gt;git push&lt;/code&gt; 後に該当するワークフローの実行結果を確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uIH3k4VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/cbb0a1cf7b94605680e39630dded0096.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uIH3k4VO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/cbb0a1cf7b94605680e39630dded0096.png" alt="Image from Gyazo"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;2. &lt;code&gt;dev.to&lt;/code&gt; に執筆途中の内容で記事が同期されていることを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;これまで説明してきた 2 つのワークフローを利用することで、大抵のユースケースはカバーできるはずです。&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;GitHub Actions の勉強のために取り組んだプロジェクトですが、思いの外楽しくて他にも色々な機能のアイデアがあるので随時実装していきたいと考えています。(英訳, タイトルフォーマット変更, etc.)&lt;/p&gt;

&lt;p&gt;DEV に Zenn の記事をクロスポストする GitHub Actions を公開することで、いつもお世話になっている Zenn というプラットフォームを海外の方に認知していただける機会を創出できたのかもと考えたらテンションが上がってきました。&lt;/p&gt;

&lt;p&gt;それはさておき、Zenn の記事を他でも有効活用するための GitHub Actions を開発する際には、恐らく本記事で紹介した GitHub Actions のコードが参考になるはずです。&lt;/p&gt;

&lt;p&gt;また、&lt;a href="https://docs.github.com/ja/actions/creating-actions/publishing-actions-in-github-marketplace"&gt;GitHub Actions の Marketplace&lt;/a&gt; というものが用意されているようなので、開発がある程度完了次第、こちらに申請するのも試してみたいと考えております。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/"&gt;DEV Community 👩‍💻👨‍💻&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/michaelburrows/comment/125j0"&gt;dev.to supports canonical URLs so you can share content without impacting SEO... - DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nikaera/sync-zenn-with-dev-action"&gt;nikaera/sync-zenn-with-dev-action: Just sync Zenn articles to DEV.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.forem.com/api/#section/Authentication"&gt;DEV API (beta)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jitterbit/get-changed-files"&gt;jitterbit/get-changed-files: Get all of the files changed/modified in a pull request or push's commits.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/actions/creating-actions/creating-a-docker-container-action"&gt;Docker コンテナのアクションを作成する - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/actions/creating-actions/creating-a-javascript-action"&gt;JavaScript アクションを作成する - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/typescript-action"&gt;actions/typescript-action: Create a TypeScript Action with tests, linting, workflow, publishing, and versioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/ncc"&gt;vercel/ncc: Compile a Node.js project into a single file. Supports TypeScript, binary addons, dynamic requires.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nektos/act"&gt;nektos/act: Run your GitHub Actions locally 🚀&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/toolkit"&gt;actions/toolkit: The GitHub ToolKit for developing GitHub Actions.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository"&gt;暗号化されたシークレット - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.deepl.com/ja/pro#developer"&gt;DeepL Pro：テキストの他、Word などの文書をセキュアに翻訳&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://qiita.com/"&gt;Qiita&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://crieit.net/advent-calendars/2020/crieit"&gt;Crieit - プログラマー、クリエイターが何でも気軽に書けるコミュニティ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;DEV (Forem) の仕様上、&lt;a href="https://dev.to/p/editor_guide#front-matter"&gt;タグは最大でも 4 つまで&lt;/a&gt;しか設定できないため、Zenn で設定したタグの先頭 4 つまで DEV の記事には設定しています ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;当然といえば当然ですが &lt;a href="https://github.com/jitterbit/get-changed-files"&gt;jitterbit/get-changed-files@v1&lt;/a&gt; は &lt;code&gt;workflow_dispatch&lt;/code&gt; でワークフローを手動実行した際や、&lt;code&gt;force push&lt;/code&gt; 等でファイル差分を正確に特定できない操作には対応しておりませんので、その場合はエラーが発生します ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>typescript</category>
      <category>githubactions</category>
      <category>forem</category>
    </item>
    <item>
      <title>[TECH] AWS Lightsail Containers に Actix web をデプロイする ⛵</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 20 Mar 2021 18:54:43 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-aws-lightsail-containers-actix-web-l9o</link>
      <guid>https://forem.com/nikaera/tech-aws-lightsail-containers-actix-web-l9o</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/actix/actix-web"&gt;Actix web&lt;/a&gt; で Web アプリケーションを作ったのですが、技術勉強も兼ねていたので、デプロイ先も今まで試したことがないものを試そうとしていました。そこで、日頃業務でも AWS を利用しているということもあり、去年末に発表された &lt;a href="https://aws.amazon.com/jp/about-aws/whats-new/2020/11/announcing-amazon-lightsail-containers/"&gt;AWS Lightsail Containers&lt;/a&gt; をデプロイ先に採用しました。&lt;/p&gt;

&lt;p&gt;AWS Lightsail Containers へのデプロイ自体は非常に簡単でした。また、デプロイにあたり Rust の Docker イメージ作成のやり方も学べました。今回はそのあたりの手順をまとめる形で記事として書き残しておくことにしました。&lt;/p&gt;

&lt;h1&gt;
  
  
  Actix web の Docker イメージを作成する
&lt;/h1&gt;

&lt;p&gt;開発したアプリケーションでは React でフロントエンド開発をしていて、ビルドしたものを Actix web の public フォルダに配置する形で公開しています。そのため、下記の Dockerfile ではマルチステージビルドを利用しておりますが、本質的には &lt;code&gt;FROM rust:1.49&lt;/code&gt; 以降の記述が Actix web に関するものとなります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# React ビルド用のイメージ&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:14.15.4-alpine3.10 as client_builder&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_API_URL&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_GYAZO_AUTH_URL&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; REACT_APP_GA_UNIVERSAL_ID&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /client&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./client/package*.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; ./client .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;yarn build

&lt;span class="c"&gt;# Actix web ビルド用のイメージ&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; rust:1.49&lt;/span&gt;

&lt;span class="c"&gt;# Actix web にアクセスするためのポートを公開する&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="c"&gt;# Actix web プロジェクトのフォルダをイメージに追加する&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /server&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; ./server .&lt;/span&gt;

&lt;span class="c"&gt;# プロジェクトフォルダ内で `cargo install` してビルドを生成する&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; .

&lt;span class="c"&gt;# 不要になったファイル群を削除する&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'templates'&lt;/span&gt; | xargs &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;

&lt;span class="c"&gt;# React ビルド用のイメージでビルドした内容を Actix web ビルド用イメージに追加する&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=client_builder /client/build ./build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;tmp

&lt;span class="c"&gt;# `cargo install` コマンドで生成したビルドを実行して Actix web を起動する&lt;/span&gt;
&lt;span class="c"&gt;# 下記のコマンド名称は Cargo.toml 内の [package.name] に準ずる&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["bloggimg-server"]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;また、&lt;strong&gt;Docker ビルド時のオプション管理を楽にするため、Docker Compose を利用しました。単一の Docker イメージをビルドする際にも利用しておくことで、後々コンテナを追加して連携させたいときにも即座に対応できたりでオススメです。&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# context に Actix web プロジェクトのパスを指定する&lt;/span&gt;
&lt;span class="c1"&gt;# args に Docker ビルド時に利用したい ARGS の値を環境変数で設定する&lt;/span&gt;
&lt;span class="c1"&gt;# image に Docker の &amp;lt;イメージ名:タグ名&amp;gt; を指定する (今回は Docker Hub にデプロイする想定)&lt;/span&gt;
&lt;span class="c1"&gt;# env_file に開発/動作検証時に利用したい dotenv ファイルを指定する&lt;/span&gt;
&lt;span class="c1"&gt;# ports にポートマッピングの設定を書く&lt;/span&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./&lt;/span&gt;
      &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REACT_APP_API_URL=${REACT_APP_API_URL}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REACT_APP_GYAZO_AUTH_URL=${REACT_APP_GYAZO_AUTH_URL}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REACT_APP_GA_UNIVERSAL_ID=${REACT_APP_GA_UNIVERSAL_ID}&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n1kaera/bloggimg:v1.0.0&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./server/.env&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:8080&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記を自分の Actix web プロジェクトに応じて改変し &lt;code&gt;docker-compose up&lt;/code&gt; して動作検証します。動作検証ができ次第、&lt;code&gt;docker-compose build&lt;/code&gt; を実行して Docker イメージをビルドします。ビルドに成功したら次は Docker Hub にイメージを push します。&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker Hub にビルドしたイメージを push する
&lt;/h1&gt;

&lt;p&gt;今回は AWS Lightsail Containers で使用するイメージの管理に &lt;a href="https://hub.docker.com/"&gt;Docker Hub&lt;/a&gt; を利用します。Docker Hub へ push する前に &lt;code&gt;docker login --username=&amp;lt;Docker Hub のユーザ名&amp;gt;&lt;/code&gt; コマンドで Docker Hub へのログインを済ませておきます。&lt;/p&gt;

&lt;p&gt;その後 &lt;code&gt;docker-compose push&lt;/code&gt; コマンドで Docker イメージを Docker Hub に push します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--52NFfNiM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/06ce4ca43a26c73c227b9eb768f65685.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--52NFfNiM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/06ce4ca43a26c73c227b9eb768f65685.png" alt="スクリーンショット 2021-01-23 20.37.39.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docker Hub のページから、正常に Docker イメージが push できていそうか確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Docker イメージの push が成功していることを確認できたら、残りは AWS Console 上での作業になります。&lt;/p&gt;

&lt;h1&gt;
  
  
  AWS Console から Lightsail Containers Service を作成する
&lt;/h1&gt;

&lt;p&gt;AWS Console にログイン後、&lt;a href="https://lightsail.aws.amazon.com/ls/webapp/home/containers"&gt;Lightsail サービス&lt;/a&gt; を選択して Lightsail サービスのトップページへ遷移します。遷移したら Containers タブを選択し、&lt;code&gt;Create container services&lt;/code&gt; ボタンから Container Service を作成します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9TixwiYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/42a8e6fa6224a6a52212cdb7461a8515.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9TixwiYV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/42a8e6fa6224a6a52212cdb7461a8515.png" alt="スクリーンショット 2021-01-23 18.55.16.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;AWS Console へログイン後 Lightsail のページに遷移して、Containers タブを選択する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oB7J4cme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/25c1b55863c4d77249d3105ba7a97afd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oB7J4cme--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/25c1b55863c4d77249d3105ba7a97afd.png" alt="スクリーンショット 2021-01-23 18.57.06.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Containers タブを選択すると出てくる、&lt;code&gt;Create container services&lt;/code&gt; ボタンをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Create container services&lt;/code&gt; ボタンをクリックした遷移先の画面で、リージョンやキャパシティ (Micro であれば 3 ヶ月間のみ無料で利用可能) 等を選択して、名称を入力します。今回は最初にデプロイのための準備をすでに済ませているので、Container Service を作成するついでにデプロイ設定も行います。&lt;/p&gt;

&lt;p&gt;デプロイ設定は &lt;code&gt;Set up your first deployment&lt;/code&gt; の項目から行うことが可能です。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TVhZJ7Gx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e8e4ab4e3b29afaf6298f7eb75355c34.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TVhZJ7Gx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/e8e4ab4e3b29afaf6298f7eb75355c34.png" alt="スクリーンショット 2021-01-23 19.07.53.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;Set up deployment&lt;/code&gt; の部分をクリックして、デプロイの設定項目を表示する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---6e33aVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/da0fd3ce440f441082a367a0dfe350b6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---6e33aVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/da0fd3ce440f441082a367a0dfe350b6.png" alt="スクリーンショット 2021-01-23 19.12.14.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Docker Hub イメージを利用してデプロイする際に必要な設定項目を入力する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--e8Yk5I0R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/eefec162ec66adab937d5139f70763cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--e8Yk5I0R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/eefec162ec66adab937d5139f70763cc.png" alt="スクリーンショット 2021-01-23 19.16.16.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;コンテナのヘルスチェックのための情報を入力する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KQR3Wm1S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/0c532eb818754d554a61028f6a177dde.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KQR3Wm1S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/0c532eb818754d554a61028f6a177dde.png" alt="スクリーンショット 2021-01-23 19.19.17.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;すべての情報入力が完了したら &lt;code&gt;Create container services&lt;/code&gt; ボタンをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nfl1gT6e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/102978a025c8babbba40886e1b551ae6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nfl1gT6e--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/102978a025c8babbba40886e1b551ae6.png" alt="スクリーンショット 2021-01-23 19.22.48.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;遷移後の画面下部の &lt;code&gt;Deployment versions&lt;/code&gt; からデプロイ状況の確認が行える&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tKgeVl8Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/eaaf0b1f1d2b2d37807d49764cafa681.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tKgeVl8Y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/eaaf0b1f1d2b2d37807d49764cafa681.png" alt="スクリーンショット 2021-01-23 19.39.55.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;正常にデプロイできていれば &lt;code&gt;Deployment versions&lt;/code&gt; の項目が Active になる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;デプロイが完了したら &lt;code&gt;Public domain&lt;/code&gt; が発行されているはずなので、正常にアクセスして Web アプリケーションが利用できそうか確認します。&lt;code&gt;Public domain&lt;/code&gt; は該当する Container Service のトップページから確認できます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UKC2QhHc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9ee36e7f1018c47a9c12e51ebe6df6d0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UKC2QhHc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9ee36e7f1018c47a9c12e51ebe6df6d0.png" alt="スクリーンショット 2021-01-23 19.48.23.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;AWS Lightsail Containers のトップページにある &lt;code&gt;Public domain&lt;/code&gt; から動作検証する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XK_MavnZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a381f52745c002ce47addf7721b7ec0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XK_MavnZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a381f52745c002ce47addf7721b7ec0b.png" alt="スクリーンショット 2021-01-23 19.51.27.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;一通りの動作検証を行い、正常にデプロイできていそうか確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;これで作業は完了です。新しい Docker イメージでデプロイし直したい場合は、&lt;code&gt;Deployments&lt;/code&gt; タブの &lt;code&gt;Modify your deployment&lt;/code&gt; リンクをクリックすれば可能です。&lt;/p&gt;

&lt;h1&gt;
  
  
  (おまけ) 独自ドメインで Container Service へアクセス可能にする
&lt;/h1&gt;

&lt;p&gt;AWS Lightsail Containers では独自ドメインの紐付け及び、HTTPS 化も簡単に設定できます。&lt;code&gt;Custom domains&lt;/code&gt; タブを選択した後、画面下部にある &lt;code&gt;Create certificate&lt;/code&gt; リンクをクリックすることで設定画面を表示します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hwuNBVXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/93019714418209872be82c396931beee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hwuNBVXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/93019714418209872be82c396931beee.png" alt="スクリーンショット 2021-01-23 20.07.22.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;Custom domain&lt;/code&gt; タブをクリックしてから、&lt;code&gt;Create certificate&lt;/code&gt; リンクをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PXk9fOIv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b6cc81261f5d3bec4b236e04c0409372.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PXk9fOIv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b6cc81261f5d3bec4b236e04c0409372.png" alt="スクリーンショット 2021-01-23 20.10.22.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;各種設定項目の入力が完了したら &lt;code&gt;Create&lt;/code&gt; ボタンをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--u7jB5WBC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d8075a0e1eedeb1e518e7f0ae22554a1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--u7jB5WBC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d8075a0e1eedeb1e518e7f0ae22554a1.png" alt="スクリーンショット 2021-01-23 20.16.02.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ドメイン検証のために、CNAME レコードの設定を求められるので各自で設定作業を行う&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g-XH7qHC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/3aa4608c22ab83193d75b3e968d47b77.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g-XH7qHC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/3aa4608c22ab83193d75b3e968d47b77.png" alt="スクリーンショット 2021-01-23 20.20.26.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;正常に CNAME レコードを設定した後、しばらく経つと Status が Valid になる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;上記まで確認したら、&lt;code&gt;Create certificate&lt;/code&gt; で設定したドメインの CNAME レコードに &lt;code&gt;Public domain&lt;/code&gt; の値を設定しておきます。設定内容が反映され次第、独自ドメインへアクセスすることで HTTPS 経由で Container Service へアクセスできるようになります。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vyiy4cX4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/21d69cbc2370b588e01cfae43afa50ca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vyiy4cX4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/21d69cbc2370b588e01cfae43afa50ca.png" alt="スクリーンショット 2021-01-23 20.27.26.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;Custom domains&lt;/code&gt; で Container Service で起動しているサービスにアクセスできることを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;AWS Lightsail Containers を利用して Actix web プロジェクトをデプロイする手順について簡単にまとめてみました。便利ではあるものの、個人開発で利用する分には価格面及び性能面で Lightsail Instance のほうが良いなと現時点では感じてしまいました。&lt;/p&gt;

&lt;p&gt;しかし、日本リージョンが用意されていたりロードバランサーを備えていたり、簡単にスケールさせやすくかつ定額で利用可能なサービスであるというメリットを活かせる場面があれば有効活用できそうだなと感じました。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/jp/blogs/news/lightsail-containers-an-easy-way-to-run-your-containers-in-the-cloud/"&gt;Lightsail コンテナ: クラウドでコンテナを実行する簡単な方法 | Amazon Web Services ブログ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/_/rust"&gt;rust - Docker Hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-enabling-distribution-custom-domains"&gt;Enabling custom domains for your Amazon Lightsail distributions | Lightsail Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>aws</category>
      <category>lightsail</category>
    </item>
    <item>
      <title>[TECH] MediaPackage 用の CloudFront ディストリビューションを AWS SDK で作成する 🎥</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 20 Mar 2021 18:54:35 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-mediapackage-cloudfront-aws-sdk-47pf</link>
      <guid>https://forem.com/nikaera/tech-mediapackage-cloudfront-aws-sdk-47pf</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;とある事情で MediaPackage のエンドポイント用の CloudFront ディストリビューションを AWS SDK で作成する機会がありました。その際得た知見をソースコードを交えながら備忘録として記事に残しておきます。&lt;/p&gt;

&lt;p&gt;本記事内容で紹介しているソースコードは &lt;a href="https://gist.github.com/nikaera/d9d616a089998ff2d23b210faf3a85c3"&gt;Gist&lt;/a&gt; にも同じ内容でアップしてあります。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ちなみに MediaLive + MediaPackage + CloudFront の構成でインフラ構築したい場合は、&lt;a href="https://dev.classmethod.jp/articles/update-aws-elemental-mediapackage-cloudformation/"&gt;CloudFormation が MediaPackage にも対応した&lt;/a&gt;ので CloudFormation の利用を推奨します。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本記事内容はあくまでも何らかの事情で、&lt;strong&gt;後から CloudFront ディストリビューションを MediaPackage エンドポイントに紐づけたいケース等で参考になると思われます。&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  実装内容
&lt;/h1&gt;

&lt;p&gt;作成したソースコードの内容は下記になります。&lt;br&gt;
最下部の &lt;code&gt;createDistributionForMediaPackage&lt;/code&gt; が本記事タイトルに該当する関数です。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CloudFront&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;CreateDistributionWithTagsResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;GetDistributionResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;UpdateDistributionResult&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aws-sdk/clients/cloudfront&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CloudFrontClientForMediaPackage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;cloudFront&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CloudFront&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CloudFront&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ap-northeast-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2020-05-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * CloudFront ディストリビューションの情報を取得するために利用する
 * @param id CloudFront ディストリビューションの ID
 * @return ディストリビューションの情報を取得する
 */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;GetDistributionResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * CloudFront ディストリビューションの設定内容を取得するために利用する
   * @param id CloudFront ディストリビューションの ID
   * @return ディストリビューションの設定内容を取得する
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;getDistributionConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CloudFront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DistributionConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDistributionConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * CloudFront ディストリビューションを削除する
   * @param id 削除したい CloudFront ディストリビューションの ID
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;deleteDistribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteDistribution&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;IfMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ETag&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * CloudFront ディストリビューションを無効化する
   * @param id 無効化したい CloudFront ディストリビューションの ID
   * @return 無効化した CloudFront ディストリビューションの情報
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;disableDistribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UpdateDistributionResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateDistribution&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;IfMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ETag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;
      &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * MediaPackage のエンドポイント用の CloudFront ディストリビューションを作成する
   * @param id CloudFront ディストリビューションを判別するための ID
   * @param mediaPackageArn MediaPackage チャンネルの ARN
   * @param mediaPackageUrl MediaPackage エンドポイントの URL
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;createDistributionForMediaPackage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;mediaPackageArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;mediaPackageUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CreateDistributionWithTagsResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. url モジュールを用いて URL 文字列をパースする&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaPackageEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediaPackageUrl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="cm"&gt;/**
    2. MediaPackage のエンドポイント URL から FQDN を取得する。
    後述する CloudFront ディストリビューションのオリジンのドメイン名としても利用する
    */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediaPackageHostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mediaPackageEndpoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
    3. MediaPackage のエンドポイント URL のフォーマットは
    https://&amp;lt;AccountID&amp;gt;.mediapackage.&amp;lt;Region&amp;gt;.amazonaws.com/**** となっているので、
    FQDN の先頭部分を文字列分割で取り出すとアカウント ID が取得できる
    */&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mediaPackageHostname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. 後述する CloudFront ディストリビューションのオリジン ID として、アカウント ID を利用する&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetOriginId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`MP-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

    &lt;span class="cm"&gt;/**
    5. createDistribution ではなく、createDistributionWithTags 関数で、
    CloudFront ディストリビューションを作成する。MediaPackage との紐付けにタグを利用するため。
    */&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createDistributionWithTags&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;DistributionConfigWithTags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="cm"&gt;/**
                    !!!!!重要!!!!!

                    6. CloudFront ディストリビューションに紐付けたい
                    MediaPackage エンドポイントのチャンネル ARN を
                    mediapackage:cloudfront_assoc で定義する。

                    mediapackage:cloudfront_assoc を定義することで、
                    CloudFront ディストリビューションと
                    MediaPackage チャンネルを紐付けることが可能となる。
                    */&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mediapackage:cloudfront_assoc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mediaPackageArn&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Stage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="na"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;CallerReference&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="na"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Managed by MediaPackage - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="cm"&gt;/**
                7. CloudFront ディストリビューションのオリジンには 2つ設定します。
                1つが MediaPackage のエンドポイントに対するものと、
                もう 1つが MediaPacakge サービスに対するものです。

                基本的には MediaPackage のエンドポイントに対するオリジンを利用します。
                例外時に向けるオリジンが MediaPacakge サービスに対するものになります。
                */&lt;/span&gt;
                &lt;span class="na"&gt;Origins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;DomainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mediaPackageHostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetOriginId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;CustomOriginConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;HTTPPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;HTTPSPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;OriginProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;match-viewer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;DomainName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mediapackage.amazonaws.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TEMP_ORIGIN_ID/channel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;CustomOriginConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;HTTPPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;HTTPSPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;OriginProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;match-viewer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="cm"&gt;/**
                8. CacheBehaviors のいずれにも当てはまらなかった場合の
                キャッシュの振る舞いを定義します。

                MediaPackage は タイムシフト表示機能を使用する際等で、クエリ文字列に start, m, end を利用しています。
                そのため、それらの文字列は WhitelistedNames に含め QueryString には true を指定しておきます。

                DefaultCacheBehavior に引っかかる挙動は例外的扱いなので、
                使用するオリジンは MediaPackage サービスのものを設定します。
                */&lt;/span&gt;
                &lt;span class="na"&gt;DefaultCacheBehavior&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;ForwardedValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Forward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whitelist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;WhitelistedNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                                &lt;span class="p"&gt;]&lt;/span&gt;
                            &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;QueryString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;QueryStringCacheKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="na"&gt;MinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;TargetOriginId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TEMP_ORIGIN_ID/channel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;TrustedSigners&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="na"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect-to-https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;AllowedMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                            &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                        &lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                    &lt;span class="na"&gt;MaxTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="cm"&gt;/**
                9. CloudFront のエラーコード全ての TTL に 1sec を設定します。
                MediaPackage のエラーのキャッシュが長時間持続してしまうと、
                その間は MediaPackage で正常に配信できているとしても、
                復旧できない状態となるからです。
                */&lt;/span&gt;
                &lt;span class="na"&gt;CustomErrorResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                    &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;414&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;416&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;501&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ErrorCachingMinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="cm"&gt;/**
                10. CloudFront ディストリビューションのキャッシュの振る舞いを 2つ定義します。

                それぞれの設定内容は基本的に DefaultCacheBehavior で定義したものと同様です。
                しかし、利用するオリジンは MediaPackage エンドポイントに向けたものを利用します。

                1つは Microsoft Smooth Streaming での配信時に利用する
                index.ism に対するもので Smooth Streaming を true に設定しています。

                もう 1つは上記 Microsoft Smooth Streaming 以外の
                全てに当てはまるストリーミングに適用されるものになります。
                */&lt;/span&gt;
                &lt;span class="na"&gt;CacheBehaviors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
                        &lt;span class="na"&gt;MinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;PathPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.ism/*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;TargetOriginId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetOriginId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect-to-https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;AllowedMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                            &lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;ForwardedValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Forward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whitelist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;WhitelistedNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                    &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                                    &lt;span class="p"&gt;]&lt;/span&gt;
                                &lt;span class="p"&gt;}&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                            &lt;span class="na"&gt;QueryString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                            &lt;span class="na"&gt;QueryStringCacheKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;SmoothStreaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;MinTTL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;PathPattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;TargetOriginId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;targetOriginId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;ViewerProtocolPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirect-to-https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="na"&gt;AllowedMethods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;HEAD&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                            &lt;span class="p"&gt;],&lt;/span&gt;
                            &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="na"&gt;ForwardedValues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="na"&gt;Cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Forward&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;whitelist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;WhitelistedNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="na"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                                    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;m&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                                &lt;span class="p"&gt;]&lt;/span&gt;
                                &lt;span class="p"&gt;}&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                            &lt;span class="na"&gt;QueryString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="na"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                            &lt;span class="na"&gt;QueryStringCacheKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="na"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                            &lt;span class="p"&gt;},&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}]&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="na"&gt;PriceClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PriceClass_All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;createDistributionForMediaPackage&lt;/code&gt; で作成したディストリビューションは、&lt;a href="https://docs.aws.amazon.com/ja_jp/mediapackage/latest/ug/cdns-cf.html"&gt;公式ページに記載された手順&lt;/a&gt; で作成した CloudFront ディストリビューションと同等のものになります。&lt;/p&gt;

&lt;p&gt;詳細な説明はインラインコメントにて書きましたが、一応補足説明を少し付け加えておきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  随所に出てくる &lt;code&gt;Quantity&lt;/code&gt; について
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;Quantity&lt;/code&gt; には &lt;code&gt;Items&lt;/code&gt; で指定する項目の数を入力します。&lt;/strong&gt; 例えば &lt;code&gt;Headers&lt;/code&gt; や &lt;code&gt;QueryStringCacheKeys&lt;/code&gt; には &lt;code&gt;Items&lt;/code&gt; に何も指定していないため、&lt;code&gt;Quantity&lt;/code&gt; に &lt;code&gt;0&lt;/code&gt; を指定します。&lt;/p&gt;

&lt;p&gt;しかし、&lt;code&gt;AllowedMethods&lt;/code&gt; や &lt;code&gt;WhitelistedNames&lt;/code&gt; には &lt;code&gt;Items&lt;/code&gt; に指定した項目数である &lt;code&gt;2&lt;/code&gt; や &lt;code&gt;3&lt;/code&gt; を &lt;code&gt;Quantity&lt;/code&gt; に入力しています。&lt;strong&gt;&lt;code&gt;Quantity&lt;/code&gt; の数と &lt;code&gt;Items&lt;/code&gt; の項目数が合わないと、エラーが発生するため、注意が必要です。&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;mediapackage:cloudfront_assoc&lt;/code&gt; を定義する意味
&lt;/h2&gt;

&lt;p&gt;CloudFront ディストリビューションのタグに &lt;strong&gt;&lt;code&gt;mediapackage:cloudfront_assoc&lt;/code&gt; で紐付ける MediaPackage のチャンネル ARN を指定することで、MediaPackage コンソールから紐付けられた CloudFront ディストリビューション情報を参照できるようになります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;試しに紐づけられた MediaPackage のチャンネルのエンドポイント詳細ページに遷移すると、&lt;br&gt;
下記のような画面が確認できるはずです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YA8wvf1k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/93ec5b42f4e550e38a7b06a945e0102b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YA8wvf1k--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/93ec5b42f4e550e38a7b06a945e0102b.png" alt="mediapackage:cloudfront_assoc で紐付いた CloudFront ディストリビューションが確認できる"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;mediapackage:cloudfront_assoc&lt;/code&gt; で紐付いた CloudFront ディストリビューションが確認できる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本記事内のソースコードでは他にも &lt;code&gt;Id&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;, &lt;code&gt;Stage&lt;/code&gt; といったタグを定義していますが、MediaPackage とは関係無いものなので削除して問題ありません。&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;updateDistribution&lt;/code&gt; を実行する際の注意点
&lt;/h2&gt;

&lt;p&gt;これは今回の記事内容とは直接関係ないのですが、地味にハマったので載せておきます。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudFront では &lt;code&gt;createDistribution&lt;/code&gt; の時に要求されるパラメータよりも &lt;code&gt;updateDistribution&lt;/code&gt; で要求されるパラメータのほうが多いです。&lt;/strong&gt; &lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-overview-required-fields.html"&gt;AWS 公式ページの比較表&lt;/a&gt;にある通りです。&lt;/p&gt;

&lt;p&gt;そのため、&lt;code&gt;updateDistribution&lt;/code&gt; で設定を一部更新したいだけなのに、とても多くのパラメータを指定する必要があり非常に面倒です。例えば CloudFront ディストリビューションの Enable/Disable を切り替えるだけでも 30 個近いパラメータを指定する必要あります。&lt;/p&gt;

&lt;p&gt;上記の入力の手間を省くのには &lt;strong&gt;&lt;code&gt;getDistribution&lt;/code&gt; で取得した既存のディストリビューション情報を改変する形で &lt;code&gt;updateDistribution&lt;/code&gt; のパラメータを作成すると楽でした。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;今回のソースコードの内容を参照すると &lt;code&gt;disableDistribution&lt;/code&gt; が該当します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. getDistribution を実行して CloudFront ディストリビューションの情報を取得する&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getDistribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. CloudFront ディストリビューションの設定内容を取得する&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 3. CloudFront ディストリビューションの Enabled/Disabled を切り替えるオプションを改変する&lt;/span&gt;
&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// 4. 3. で改変した内容を updateDistribution で CloudFront ディストリビューションに反映する&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudFront&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;updateDistribution&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;IfMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;distribution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ETag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;DistributionConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;ニッチな内容なので、本記事内容を今後利用するかどうかは分かりませんが、一応得た知見を記事として残しておきました。同様のことを行う必要が出てきた方の参考になれれば幸いです。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudFormation.html"&gt;Class: AWS.CloudFormation — AWS SDK for JavaScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/ja_jp/mediapackage/latest/ug/cdns-cf.html"&gt;Amazon CloudFront を MediaPackage に使用する - AWS Elemental MediaPackage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/distribution-overview-required-fields.html"&gt;Required fields for creating and updating distributions - Amazon CloudFront&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/live-streaming.html"&gt;CloudFront と AWS Media Services によるライブストリーミングビデオの配信&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>node</category>
      <category>mediapackage</category>
      <category>cloudfront</category>
    </item>
    <item>
      <title>[TECH] Actix web で HttpOnly な Cookie を設定する 🍪</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 20 Mar 2021 18:54:26 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-actix-web-httponly-cookie-182</link>
      <guid>https://forem.com/nikaera/tech-actix-web-httponly-cookie-182</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;最近 Rust を勉強するため、&lt;a href="https://github.com/actix/actix-web"&gt;Actix web&lt;/a&gt; で &lt;a href="https://github.com/nikaera/bloggimg"&gt;Bloggimg&lt;/a&gt; という Web アプリケーションを作りました。その際、セッション管理のために Cookie を利用したのですが、その際の手順及び設定方法についてまとめておきます。&lt;/p&gt;

&lt;p&gt;本記事では Rust や Actix web のインストール方法については説明しません。Mac であれば &lt;code&gt;brew install rustup&lt;/code&gt; して &lt;code&gt;rustup-init&lt;/code&gt; した後、&lt;code&gt;PATH&lt;/code&gt; に &lt;code&gt;$HOME/.cargo/bin&lt;/code&gt; を追加するだけで大丈夫なはずです。詳細なインストール手順については &lt;a href="https://www.rust-lang.org/tools/install"&gt;公式サイト&lt;/a&gt; をご参照ください。&lt;/p&gt;

&lt;p&gt;開発環境については &lt;a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust"&gt;VSCode の Rust Plugin&lt;/a&gt; がオススメです。Rustup で Rust をインストールしている場合、設定から Rustup の PATH を &lt;code&gt;$HOME/.cargo/bin/rustup&lt;/code&gt; にするだけで利用可能です。設定手順の詳細は&lt;a href="https://takoyaking.hatenablog.com/entry/2020/01/05/180000"&gt;こちら&lt;/a&gt;をご参照ください。&lt;/p&gt;

&lt;h1&gt;
  
  
  動作環境
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Mac mini (M1, 2020)

&lt;ul&gt;
&lt;li&gt;Rust 1.49&lt;/li&gt;
&lt;li&gt;Actix web 3&lt;/li&gt;
&lt;li&gt;Serde 1.0
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[package]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cookie_test"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;authors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["nikaera"]&lt;/span&gt;
&lt;span class="py"&gt;edition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2018"&lt;/span&gt;

&lt;span class="c"&gt;# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html&lt;/span&gt;

&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="py"&gt;actix-web&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;
&lt;span class="nn"&gt;serde&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["derive"]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Actix web で Cookie をセットする
&lt;/h1&gt;

&lt;p&gt;サーバー側で Cookie を設定するため、HTTP レスポンスヘッダーに &lt;a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie"&gt;Set-Cookie&lt;/a&gt; を含める形でセッション情報をクライアントへ渡します。その際、最低でも Cookie の属性に &lt;code&gt;HttpOnly&lt;/code&gt; と &lt;code&gt;Secure&lt;/code&gt;、&lt;code&gt;SameSite=Strict&lt;/code&gt; は設定します。実際の Cookie を設定するための Actix web でのサンプルコードは下記になります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix_web&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix_web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SameSite&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix_web&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c"&gt;/// Cookie に設定するキー&lt;/span&gt;
&lt;span class="c"&gt;/// 今回は cookie_test をキーとして使用する&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"cookie_test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;/// 存在していれば、HTTP Request ヘッダーから Cookie 文字列を取得する関数&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Arguments&lt;/span&gt;
&lt;span class="c"&gt;/// * `req` - actix_web::HttpRequest&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Return value&lt;/span&gt;
&lt;span class="c"&gt;/// * Option&amp;lt;String&amp;gt; - key=value; key1=value1;~ のような Cookie の文字列&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_cookie_string_from_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cookie_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="nf"&gt;.headers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cookie"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie_header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cookie_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.to_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookie_string&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/// 存在していれば、特定のキーで Cookie に設定された値を取得するための関数&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Arguments&lt;/span&gt;
&lt;span class="c"&gt;/// * `key` - Cookie から取り出したい値のキー&lt;/span&gt;
&lt;span class="c"&gt;/// * `cookie_string` - get_cookie_string_from_header 関数で取得した Cookie の文字列&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Return value&lt;/span&gt;
&lt;span class="c"&gt;/// * Option&amp;lt;String&amp;gt; - Cookie に設定されている値を取得する&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_cookie_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookie_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// 取得した Cookie 文字列を ; で分割してループで回す&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie_string&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;';'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Cookie 文字列をパースして key で指定した値とマッチしたキーが存在するかチェックする&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="nf"&gt;.name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c"&gt;// key で指定した値とマッチしたキーが存在していたら、その値を取得する&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="nf"&gt;.value&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cookie parse error. -&amp;gt; {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/// 特定のキーで環境変数から値を取得するための関数&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Arguments&lt;/span&gt;
&lt;span class="c"&gt;/// * `key` - 環境変数から取り出したい値のキー&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Return value&lt;/span&gt;
&lt;span class="c"&gt;/// * String - 環境変数の値を文字列として取得する&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ENV: ERR {:?}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/// 環境変数に設定された HTTPS の値が 1 か判定する&lt;/span&gt;
&lt;span class="c"&gt;/// Cookie の属性に Secure を付与するか判定するのに使用する&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Return value&lt;/span&gt;
&lt;span class="c"&gt;/// * bool - Secure 属性を付与するか判定するための真偽値&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;is_https&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get_env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTPS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/// Cookie に設定する値を扱う HTTP Query の定義&lt;/span&gt;
&lt;span class="nd"&gt;#[derive(Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CookieQuery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/// Cookie を設定するために用意したルート&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// # Example&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="c"&gt;/// 例えば GET /cookie?value=test にアクセスした場合、&lt;/span&gt;
&lt;span class="c"&gt;/// Cookie に cookie_test=test が設定されるようになる&lt;/span&gt;
&lt;span class="c"&gt;///&lt;/span&gt;
&lt;span class="nd"&gt;#[get(&lt;/span&gt;&lt;span class="s"&gt;"/cookie"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;set_cookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CookieQuery&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// 設定したい Cookie を作成する&lt;/span&gt;
    &lt;span class="c"&gt;// その際に Secure, HttpOnly, SameSite=Strict 属性を付与する&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Cookie&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="py"&gt;.value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.secure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;is_https&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nf"&gt;.http_only&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.same_site&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;SameSite&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Strict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.finish&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c"&gt;// 作成した Cookie を HTTP Response の Set-Cookie ヘッダーに含めることで、&lt;/span&gt;
    &lt;span class="c"&gt;// HTTP Response を受け取ったクライアントに Cookie をセットさせる&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Set-Cookie"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/// KEY で指定した Cookie が存在すれば、その値を返却する&lt;/span&gt;
&lt;span class="c"&gt;/// KEY で指定した Cookie が存在しなければ、空の文字列を返却する&lt;/span&gt;
&lt;span class="nd"&gt;#[get(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="nd"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cookie_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_cookie_string_from_header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie_string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_cookie_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[actix_web::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_cookie&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;.bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0:8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
    &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ザッとインラインコメントで説明していますが、&lt;br&gt;&lt;br&gt;
最も重要な &lt;code&gt;set_cookie&lt;/code&gt; 関数について簡単に説明します。&lt;/p&gt;

&lt;p&gt;Actix web には &lt;a href="https://docs.rs/actix-web/3.3.2/actix_web/http/struct.Cookie.html"&gt;&lt;code&gt;Cookie&lt;/code&gt; クラス&lt;/a&gt;が存在します。この &lt;code&gt;Cookie&lt;/code&gt; クラスは Cookie 文字列を生成したり、パースしたりするのに役立ちます。&lt;code&gt;set_cookie&lt;/code&gt; 関数では、Cookie を生成するための関数 &lt;a href="https://docs.rs/actix-web/3.3.2/actix_web/http/struct.Cookie.html#method.build"&gt;&lt;code&gt;Cookie::build&lt;/code&gt;&lt;/a&gt; を利用しています。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Cookie::build&lt;/code&gt; 関数を利用することで、メソッドチェインで Cookie の値や属性を設定できます。&lt;strong&gt;作成した Cookie は &lt;code&gt;to_string&lt;/code&gt; 関数を使用することで文字列として出力できます。出力した Cookie 文字列を HTTP レスポンスヘッダーに &lt;code&gt;Set-Cookie&lt;/code&gt; として設定すれば Cookie を設定できます。&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  動作検証
&lt;/h1&gt;

&lt;p&gt;今回用意した Actix web のサンプルコードには 2 つのエンドポイントを用意しました。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;URI&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /cookie&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;value&lt;/code&gt; クエリで HttpOnly な Cookie を設定する&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GET /&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GET /cookie&lt;/code&gt; で設定した Cookie を確認する&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;cargo run&lt;/code&gt; で Actix web のサンプルを起動した後に、ブラウザで &lt;code&gt;http://localhost:8080/cookie?value=sample&lt;/code&gt; にアクセスしてみます。またその際に HTTP レスポンスヘッダーを確認したいため、&lt;a href="https://developer.mozilla.org/ja/docs/Learn/Common_questions/What_are_browser_developer_tools"&gt;開発者ツール&lt;/a&gt;を開いておきます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CJvxFGm4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9a1cb0cf73aa001b9ad3fdf7d8ae9966.png%2520%3D450x" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CJvxFGm4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9a1cb0cf73aa001b9ad3fdf7d8ae9966.png%2520%3D450x" alt="スクリーンショット 2021-01-23 13.12.27.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;HTTP レスポンスヘッダーに Set-Cookie が含まれていることを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Set-Cookie&lt;/code&gt; が含まれていることが確認できたら正常に Cookie が設定されているか確認します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XFDiT84T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a6963e1d7ec82c57b3ae8d4978e1c116.png%2520%3D450x" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XFDiT84T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/a6963e1d7ec82c57b3ae8d4978e1c116.png%2520%3D450x" alt="スクリーンショット 2021-01-23 13.26.52.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;HTTP リクエストヘッダーの Cookie に &lt;code&gt;cookie_test=sample&lt;/code&gt; が存在していることを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CCm7kZIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/282473c4c235d92233929f95c25ca899.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CCm7kZIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/282473c4c235d92233929f95c25ca899.png" alt="スクリーンショット 2021-01-23 13.32.00.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;実際にブラウザーにも Cookie が正しく設定されているか、開発者ツールで確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;正常に Cookie がセットされていることが確認できれば作業完了です。Cookie の属性に &lt;code&gt;Secure&lt;/code&gt; を設定した場合の動作検証は、環境変数に &lt;code&gt;HTTPS=1&lt;/code&gt; をセットして &lt;code&gt;cargo run&lt;/code&gt; で可能です。&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;Actix web で割と汎用的に使えそうな知識として Cookie の設定方法について、メモ的な記事を書いてみました。引き続き、Rust への理解を深めるために &lt;a href="https://github.com/nikaera/bloggimg"&gt;Bloggimg&lt;/a&gt; の開発を進めながら学習を進めていきます 🧑‍🎓&lt;/p&gt;

&lt;p&gt;本記事の内容がセキュリティの観点から適切でない場合等はコメントでご指摘いただけますと幸いです。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rust-lang.org/tools/install"&gt;Install Rust - Rust Programming Language&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust"&gt;Rust - Visual Studio Marketplace&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://takoyaking.hatenablog.com/entry/2020/01/05/180000"&gt;VSCode で Rust インストールしたのに「Rustup not available」が出るとき (備忘録) - TAKOYAKING’s blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie"&gt;Set-Cookie - HTTP | MDN&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actix/actix-web"&gt;actix/actix-web: Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/actix-web/3.3.2/actix_web/http/struct.Cookie.html"&gt;actix_web::http::Cookie - Rust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/ja/docs/Learn/Common_questions/What_are_browser_developer_tools"&gt;ブラウザー開発者ツールとは？ - ウェブ開発を学ぶ | MDN&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>rust</category>
      <category>actixweb</category>
      <category>cookie</category>
      <category>authentication</category>
    </item>
    <item>
      <title>[TECH] Gatling で複数ユーザ認証した情報を元に負荷テストする 🔫</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 20 Mar 2021 18:54:16 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-gatling-55gg</link>
      <guid>https://forem.com/nikaera/tech-gatling-55gg</guid>
      <description>&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;今までは &lt;a href="https://jmeter.apache.org/"&gt;JMeter&lt;/a&gt; でしか負荷テストを行ったことなかったのですが、最近 PlayFab で CloudFunction の負荷テストを行う際に &lt;a href="https://gatling.io/"&gt;Gatling&lt;/a&gt; を初めて利用しました。&lt;/p&gt;

&lt;p&gt;今回の負荷テストでは、各ユーザ毎のレートリミットの制限等も考慮した実利用時を想定した形で行うことが要求されたため、単一ユーザの認証情報を使い回すことは望ましくないと考えました。そこで、複数の認証済みユーザの情報を元に &lt;a href="https://docs.microsoft.com/ja-jp/gaming/playfab/features/automation/cloudscript-af/"&gt;PlayFab の CloudFunction&lt;/a&gt; の負荷テストを実施したのですが、若干実装に苦戦したため手順について記事として残しておくことにしました。&lt;/p&gt;

&lt;p&gt;また、本記事では Gatling のセットアップから記載していますが、該当コードやその説明を早く見たいという方は &lt;code&gt;複数ユーザ認証を行うテストシナリオを実装する&lt;/code&gt; 項目をご参照ください。&lt;/p&gt;

&lt;h1&gt;
  
  
  動作環境
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;macOS Big Sur&lt;/li&gt;
&lt;li&gt;Java OpenJDK 12.0.1

&lt;ul&gt;
&lt;li&gt;未インストールの方は事前に &lt;a href="https://jdk.java.net/"&gt;公式サイトから&lt;/a&gt; OpenJDK をインストールしてください&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Gatling の環境を整える
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Gatling には 2 種類のセットアップ方法が用意されています。&lt;/strong&gt; スタンドアローンなツールを直接公式サイトからダウンロードするか、&lt;a href="https://maven.apache.org/"&gt;Maven&lt;/a&gt; や &lt;a href="https://www.scala-sbt.org/"&gt;sbt&lt;/a&gt; といったツール経由でダウンロードするか選択できます。&lt;/p&gt;

&lt;p&gt;どちらの方法でセットアップするかについてですが、&lt;strong&gt;新規でテストケースを Gatling で書いていく用途だと前者になり、既存のプロジェクトに Gatling を取り込む用途だと後者になるかと存じます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本記事では、前者のスタンドアローンなツールを直接公式サイトからダウンロードする方法で Gatling の環境をセットアップします。&lt;/p&gt;

&lt;h2&gt;
  
  
  公式サイトから Gatling をダウンロードする
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gatling.io/open-source/"&gt;Gatling のトップページ&lt;/a&gt; に遷移して、ページを &lt;code&gt;2 Ways to use Gatling&lt;/code&gt; の項目までスクロールした後、ダウンロードボタンをクリックします。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zrUHNlXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/0bbfb36fa7ea0704d4ebc50682f836ef.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zrUHNlXb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/0bbfb36fa7ea0704d4ebc50682f836ef.png" alt="スクリーンショット 2021-03-14 21.57.35.png"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;DOWNLOAD GATLING'S BUNDLE&lt;/code&gt; にある &lt;code&gt;DOWNLOAD NOW&lt;/code&gt; ボタンをクリックする&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ファイルダウンロード後はダウンロードした zip ファイルを適当なフォルダに展開して配置します。&lt;br&gt;
早速ターミナルで展開したフォルダ内にある &lt;code&gt;./bin/gatling.sh&lt;/code&gt; を実行して、正常にコマンドが実行できるか確認してみます。&lt;/p&gt;

&lt;p&gt;OS が Windows の場合は &lt;code&gt;./bin/gatling.bat&lt;/code&gt; を実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⊨ ./bin/gatling.sh                   ~/D/gatling-charts-highcharts-bundle-3.5.1
GATLING_HOME is &lt;span class="nb"&gt;set &lt;/span&gt;to /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1
Choose a simulation number:
     &lt;span class="o"&gt;[&lt;/span&gt;0] computerdatabase.BasicSimulation
     &lt;span class="o"&gt;[&lt;/span&gt;1] computerdatabase.advanced.AdvancedSimulationStep01
     &lt;span class="o"&gt;[&lt;/span&gt;2] computerdatabase.advanced.AdvancedSimulationStep02
     &lt;span class="o"&gt;[&lt;/span&gt;3] computerdatabase.advanced.AdvancedSimulationStep03
     &lt;span class="o"&gt;[&lt;/span&gt;4] computerdatabase.advanced.AdvancedSimulationStep04
     &lt;span class="o"&gt;[&lt;/span&gt;5] computerdatabase.advanced.AdvancedSimulationStep05
0 &lt;span class="c"&gt;# 実行したいテストの番号を入力する、今回は適当に 0 を指定&lt;/span&gt;
Select run description &lt;span class="o"&gt;(&lt;/span&gt;optional&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# 実行するテストに関する説明文を入力する。何も入力する内容が無い or&lt;/span&gt;
&lt;span class="c"&gt;# 説明文の入力が完了したら Enter を入力して、実際にテストを実行する&lt;/span&gt;

&lt;span class="c"&gt;# 選択したテストの実行が開始する&lt;/span&gt;
&lt;span class="c"&gt;# (0 を入力したので computerdatabase.BasicSimulation が実行される)&lt;/span&gt;
Simulation computerdatabase.BasicSimulation started...

&lt;span class="c"&gt;#...&lt;/span&gt;

Simulation computerdatabase.BasicSimulation completed &lt;span class="k"&gt;in &lt;/span&gt;26 seconds
Parsing log file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;...
Parsing log file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;done
&lt;/span&gt;Generating reports...

&lt;span class="c"&gt;# テストの実行が無事完了すると、結果が表示されレポートファイルが生成される。&lt;/span&gt;
&lt;span class="c"&gt;# レポート生成先のファイルパスは実行結果の末尾に表示される。&lt;/span&gt;
&lt;span class="c"&gt;# レポートファイルは HTML で生成されるため、適当なブラウザで開くことで内容を確認することが出来る。&lt;/span&gt;

&lt;span class="o"&gt;================================================================================&lt;/span&gt;
&lt;span class="nt"&gt;----&lt;/span&gt; Global Information &lt;span class="nt"&gt;--------------------------------------------------------&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; request count                                         13 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;13     &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; min response &lt;span class="nb"&gt;time                                    &lt;/span&gt;230 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;230    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; max response &lt;span class="nb"&gt;time                                    &lt;/span&gt;483 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;483    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mean response &lt;span class="nb"&gt;time                                   &lt;/span&gt;324 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;324    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; std deviation                                         98 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;98     &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;50th percentile                        259 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;259    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;75th percentile                        415 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;415    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;95th percentile                        476 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;476    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;99th percentile                        482 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;482    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mean requests/sec                                  0.481 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.481  &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;----&lt;/span&gt; Response Time Distribution &lt;span class="nt"&gt;------------------------------------------------&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; t &amp;lt; 800 ms                                            13 &lt;span class="o"&gt;(&lt;/span&gt;100%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 800 ms &amp;lt; t &amp;lt; 1200 ms                                   0 &lt;span class="o"&gt;(&lt;/span&gt;  0%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; t &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 1200 ms                                            0 &lt;span class="o"&gt;(&lt;/span&gt;  0%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; failed                                                 0 &lt;span class="o"&gt;(&lt;/span&gt;  0%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;================================================================================&lt;/span&gt;

Reports generated &lt;span class="k"&gt;in &lt;/span&gt;0s.
Please open the following file: /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1/results/basicsimulation-20210314133324259/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;./bin/gatling.sh&lt;/code&gt; を実行した後、上記のような出力が確認できれば、問題なくスタンドアローン版の Gatling 環境のセットアップが完了しています。&lt;/p&gt;

&lt;p&gt;また、&lt;strong&gt;負荷テストのレポートを確認したい場合は、出力結果にある &lt;code&gt;Please open the following file: &amp;lt;レポートのファイルパス&amp;gt;&lt;/code&gt; に記載されているファイルをブラウザで開きます。&lt;/strong&gt;&lt;br&gt;
html 拡張子を開くアプリのデフォルトが何らかのブラウザになっているのであれば、ターミナルから &lt;code&gt;open &amp;lt;レポートのファイルパス&amp;gt;&lt;/code&gt; を実行するのでも構いません。&lt;/p&gt;
&lt;h1&gt;
  
  
  Gatling でテストシナリオを実装する
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Gatling のテストシナリオを書く場所は &lt;code&gt;./user-files/simulations&lt;/code&gt; フォルダ内になります。&lt;/strong&gt; テストシナリオを Scala で書いた後、ファイルを &lt;code&gt;./user-files/simulations&lt;/code&gt; フォルダに配置します。すると、&lt;code&gt;./bin/gatling.sh&lt;/code&gt; を実行した際の実行するテストシナリオのリストに出てくるようになります。&lt;/p&gt;
&lt;h2&gt;
  
  
  簡単なテストシナリオを試しに書いてみる
&lt;/h2&gt;

&lt;p&gt;まずは試しに私のブログに対してのアクセス負荷を計測するためのテストを実装していきます。&lt;code&gt;./user-files/simulations&lt;/code&gt; フォルダ内に &lt;code&gt;nikaera.com&lt;/code&gt; フォルダを作成し、&lt;code&gt;AccessSimulation.scala&lt;/code&gt; という負荷テストのシナリオファイルを作成します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.nikaera&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.concurrent.duration._&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.gatling.core.Predef._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.gatling.http.Predef._&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.collection.mutable.ListBuffer&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.gatling.core.structure.PopulationBuilder&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Simulation クラスを継承してテストシナリオを実行するクラスを定義する&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccessSimulation&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Simulation&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. HTTP アクセスする際の設定値を入力する。&lt;/span&gt;
  &lt;span class="c1"&gt;// 今回はアクセス先のベース URL を定義するための baseUrl のみ指定&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;httpConf&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://nikaera.com"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Scala の ListBuffer を用いて複数シナリオを格納する scenarios 変数を用意する&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;scenarios&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListBuffer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;PopulationBuilder&lt;/span&gt;&lt;span class="o"&gt;]()&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. httpConf で設定した情報を元に / (https://nikaera.com) 及び&lt;/span&gt;
  &lt;span class="c1"&gt;// /tech/ (https://nikaera.com/tech/) へ同時 10 アクセスするのを、&lt;/span&gt;
  &lt;span class="c1"&gt;// 5秒毎に 3回実行するシナリオを作成して、scenarios 変数に格納する&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;pollingApiScn&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scenario&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Polling Simulation"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Top Page"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tech Page"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/tech/"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;scenarios&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pollingApiScn&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;inject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;atOnceUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;nothingFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;atOnceUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;nothingFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
      &lt;span class="nf"&gt;atOnceUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;protocols&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpConf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 5. 4. で定義したシナリオを実行して https://nikaera.com のアクセス負荷を計測する&lt;/span&gt;
  &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再度 &lt;code&gt;./bin/gatling.sh&lt;/code&gt; を実行して、実際に負荷テストを行ってみます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;⊨ ./bin/gatling.sh                   ~/D/gatling-charts-highcharts-bundle-3.5.1
GATLING_HOME is &lt;span class="nb"&gt;set &lt;/span&gt;to /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1
Choose a simulation number:
     &lt;span class="o"&gt;[&lt;/span&gt;0] com.nikaera.AccessSimulation
     &lt;span class="o"&gt;[&lt;/span&gt;1] computerdatabase.BasicSimulation
     &lt;span class="o"&gt;[&lt;/span&gt;2] computerdatabase.advanced.AdvancedSimulationStep01
     &lt;span class="o"&gt;[&lt;/span&gt;3] computerdatabase.advanced.AdvancedSimulationStep02
     &lt;span class="o"&gt;[&lt;/span&gt;4] computerdatabase.advanced.AdvancedSimulationStep03
     &lt;span class="o"&gt;[&lt;/span&gt;5] computerdatabase.advanced.AdvancedSimulationStep04
     &lt;span class="o"&gt;[&lt;/span&gt;6] computerdatabase.advanced.AdvancedSimulationStep05
0 &lt;span class="c"&gt;# 作成したテストシナリオである com.nikaera.AccessSimulation を選択して実行します。&lt;/span&gt;
Select run description &lt;span class="o"&gt;(&lt;/span&gt;optional&lt;span class="o"&gt;)&lt;/span&gt;

Simulation com.nikaera.AccessSimulation started...

&lt;span class="c"&gt;#...&lt;/span&gt;

Simulation com.nikaera.AccessSimulation completed &lt;span class="k"&gt;in &lt;/span&gt;10 seconds
Parsing log file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;...
Parsing log file&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;done
&lt;/span&gt;Generating reports...

&lt;span class="o"&gt;================================================================================&lt;/span&gt;
&lt;span class="nt"&gt;---------&lt;/span&gt; Global Information &lt;span class="nt"&gt;--------------------------------------------------------&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; request count                                         60 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;60     &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; min response &lt;span class="nb"&gt;time                                     &lt;/span&gt;11 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;11     &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; max response &lt;span class="nb"&gt;time                                    &lt;/span&gt;372 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;372    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mean response &lt;span class="nb"&gt;time                                   &lt;/span&gt;104 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;104    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; std deviation                                         99 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;99     &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;50th percentile                         53 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;53     &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;75th percentile                        212 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;212    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;95th percentile                        259 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;259    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; response &lt;span class="nb"&gt;time &lt;/span&gt;99th percentile                        307 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;307    &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mean requests/sec                                  5.455 &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;OK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5.455  &lt;span class="nv"&gt;KO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;-     &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;---------&lt;/span&gt; Response Time Distribution &lt;span class="nt"&gt;------------------------------------------------&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; t &amp;lt; 800 ms                                            60 &lt;span class="o"&gt;(&lt;/span&gt;100%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 800 ms &amp;lt; t &amp;lt; 1200 ms                                   0 &lt;span class="o"&gt;(&lt;/span&gt;  0%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; t &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 1200 ms                                            0 &lt;span class="o"&gt;(&lt;/span&gt;  0%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; failed                                                 0 &lt;span class="o"&gt;(&lt;/span&gt;  0%&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;================================================================================&lt;/span&gt;

Reports generated &lt;span class="k"&gt;in &lt;/span&gt;0s.
Please open the following file: /Users/nika/Desktop/gatling-charts-highcharts-bundle-3.5.1/results/accesssimulation-20210314144205502/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;テストシナリオの実行に成功していることが確認できたら、複数ユーザ認証した情報を元に行うテストシナリオを書いていきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  複数ユーザ認証を行うテストシナリオを実装する
&lt;/h2&gt;

&lt;p&gt;複数ユーザ認証するテストシナリオを実装していきます。PlayFab で複数ユーザの認証情報を元に CloudFunction の負荷テストを行うことを想定しています。&lt;sup id="fnref1"&gt;1&lt;/sup&gt; テストシナリオを作成するにあたり、&lt;code&gt;./user-files/simulations/&lt;/code&gt; フォルダに新たに &lt;code&gt;playfab.com&lt;/code&gt; フォルダを作成して、その中に &lt;code&gt;TestCloudFunctionSimulation.scala&lt;/code&gt; ファイルを生成します。&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight scala"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.playfab&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.io._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.net.&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;HttpURLConnection&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.gatling.core.Predef._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.gatling.http.Predef._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.concurrent.duration._&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.util.Random&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.util.parsing.json.JSON&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;scala.collection.mutable.ListBuffer&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.gatling.core.structure.PopulationBuilder&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestCloudFunctionSimulation&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Simulation&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// PlayFab に登録されたユーザの ID 群&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;playfabUsers&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;](&lt;/span&gt;
    &lt;span class="s"&gt;"user1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user3"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user4"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user5"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user6"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user7"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user8"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user9"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"user10"&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// PlayFab の TitleId 及び Secret を変数に保持しておく&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;playfabId&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"XXXXX"&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;playfabSecret&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"&lt;/span&gt;
  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;playfabApiUrl&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"https://${playfabId}.playfabapi.com"&lt;/span&gt;

&lt;span class="err"&gt;　&lt;/span&gt;&lt;span class="c1"&gt;// PlayFab の Login With Server Custom Id を利用して、&lt;/span&gt;
  &lt;span class="c1"&gt;// ユーザの認証情報を取得するために用いる関数&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getPlayfabAuth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serverCustomId&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"${playfabApiUrl}/Server/LoginWithServerCustomId"&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;con&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;openConnection&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;HttpURLConnection&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
    &lt;span class="nv"&gt;HttpURLConnection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setFollowRedirects&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setRequestMethod&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setRequestProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setRequestProperty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-SecretKey"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;playfabSecret&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;setDoOutput&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;out&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OutputStreamWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getOutputStream&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"""{
        "CreateAccount": false,
        "ServerCustomId": "${serverCustomId}"
    }"""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nv"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="nv"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;connect&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getInputStream&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;br&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BufferedReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InputStreamReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;json&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;br&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;readLine&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

    &lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;close&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="nv"&gt;con&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;disconnect&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;parseFull&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;httpConf&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;playfabApiUrl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// CloudFunction の Request Body を作成するために利用する関数&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cloudScriptDto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;funcName&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;funcArgs&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"""{
        "FunctionName": "${funcName}",
        "FunctionParameter": ${funcArgs}
    }"""&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;scenarios&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ListBuffer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;PopulationBuilder&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ListBuffer&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;PopulationBuilder&lt;/span&gt;&lt;span class="o"&gt;]()&lt;/span&gt;

  &lt;span class="nv"&gt;playfabUsers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;foreach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// playfabUsers で指定したユーザ ID 情報を元に、&lt;/span&gt;
    &lt;span class="c1"&gt;// PlayFab の認証情報を取得利用することで、&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;playfab&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;getPlayfabAuth&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;map&lt;/span&gt;&lt;span class="k"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;, &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;playfab&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;, &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]]];&lt;/span&gt;

    &lt;span class="c1"&gt;// 愚直に JSON オブジェクトのパースを行い、必要な情報を変数に取り出す。&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;map&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;, &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]]];&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;entityTokenInfo&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"EntityToken"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;, &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]]];&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;entityToken&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;entityTokenInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"EntityToken"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;entity&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;entityTokenInfo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entity"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;, &lt;span class="kt"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="o"&gt;]]];&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;entityId&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt;
      &lt;span class="nv"&gt;entity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Id"&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="py"&gt;get&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;asInstanceOf&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// Test2 API については CloudFunction 実行時のパラメタを、&lt;/span&gt;
    &lt;span class="c1"&gt;// ランダム指定したいため、Random を用いてパラメタを散らすようにする&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;rand&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Random&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;values&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="s"&gt;"value1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"value2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"value3"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"value4"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"value5"&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;values_length&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;length&lt;/span&gt;

    &lt;span class="c1"&gt;// アクセス負荷を調査したい CloudFunction API 群を実行する。&lt;/span&gt;
    &lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="nv"&gt;pollingApiScn&lt;/span&gt; &lt;span class="k"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;scenario&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"PollingSimulation: ${entityId}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test1 Api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/CloudScript/ExecuteFunction"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-EntityToken"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entityToken&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;StringBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cloudScriptDto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test2 Api"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;post&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/CloudScript/ExecuteFunction"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;header&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-EntityToken"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entityToken&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;body&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;StringBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
              &lt;span class="nf"&gt;cloudScriptDto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Test2"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="s"&gt;"""{"value": "${values(rand.nextInt(values_length))}"}"""&lt;/span&gt;
              &lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;check&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;is&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
      &lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 1 ユーザあたり 3秒毎に 100回ずつ API 群を実行した際の&lt;/span&gt;
    &lt;span class="c1"&gt;// 負荷テストのシナリオを scenarios 変数に格納する&lt;/span&gt;
    &lt;span class="n"&gt;scenarios&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;pollingApiScn&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;inject&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;atOnceUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;nothingFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;atOnceUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;nothingFor&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
        &lt;span class="nf"&gt;atOnceUsers&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="py"&gt;protocols&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpConf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// scenarios 変数に格納されたテストシナリオを並列実行する&lt;/span&gt;
  &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="nf"&gt;scenarios&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ザッとインラインコメントで説明を書きましたが、重要な点についてのみ補足します。&lt;br&gt;
&lt;code&gt;def getPlayfabAuth&lt;/code&gt; は PlayFab 認証するための関数となっていますが、&lt;strong&gt;適宜認証に用いるサービス毎で関数の内容を変更することで、他サービスで認証するための関数として利用可能です。&lt;/strong&gt;&lt;br&gt;
また &lt;code&gt;playfabUsers.foreach&lt;/code&gt; 内で各ユーザが実行するテストシナリオを生成しつつ、それらを &lt;code&gt;scenarios&lt;/code&gt; 変数に保持しています。そうすることで、最後に &lt;code&gt;setUp&lt;/code&gt; 関数でシナリオをまとめてセットできるようになります。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;playfabUsers.foreach&lt;/code&gt; で値を指定するのではなく &lt;a href="http://www.ajisaba.net/develop/gatling/test_case_csv_feeder.html"&gt;CSV でテストに与えるフィードデータを定義する&lt;/a&gt; する方法もあります。認証部分も含めてテストシナリオを書きたい場合にも便利に利用できます。またアカウント情報を CSV ファイルに定義しておくと、テストユーザの情報を新規追加したい場合で管理が楽になります。&lt;/p&gt;

&lt;p&gt;上記テストシナリオのソースコードを応用することで、様々なサービスで複数ユーザ認証した情報を元に負荷テストを行うためのシナリオを作成することが可能となります。&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;今回は Gatling で複数ユーザ認証した情報を元に負荷テストする手順について書きました。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gatling で生成されるレポートは見やすく、改善点を洗い出してコードの改善作業をするのにとても有用でした。&lt;/strong&gt; また、JMeter と比べて動作が軽いため気軽に実行しやすく、テストシナリオが全てコードで管理されているためシナリオの改変も素早く行うことが出来ました。&lt;/p&gt;

&lt;p&gt;個人的にはテストシナリオを全てコードで記述できる Gatling が気に入ったので今後も有効活用していきたいと感じました。ただ、&lt;strong&gt;Gatling 以外にも Python で書ける &lt;a href="https://github.com/locustio/locust"&gt;locust&lt;/a&gt; や JavaScript で書ける &lt;a href="https://github.com/loadimpact/k6"&gt;k6&lt;/a&gt; など、他にも気になるツールがいくつかあったので今後試してみたいなと考えています。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;勝手に負荷テストは JMeter 一択だと思っていたのですが、負荷テストツールには色々あるようなのでプロジェクトや自分にあったものを選定していけるよう随時キャッチアップしていきたいと感じました。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jmeter.apache.org/"&gt;Apache JMeter - Apache JMeter™&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gatling.io/"&gt;Gatling Open-Source Load Testing – For DevOps and CI/CD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/ja-jp/gaming/playfab/features/automation/cloudscript-af/"&gt;Azure 関数を使用した PlayFab CloudScript - PlayFab | Microsoft Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jdk.java.net/"&gt;JDK Builds from Oracle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maven.apache.org/"&gt;Maven – Welcome to Apache Maven&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.scala-sbt.org/"&gt;sbt - The interactive build tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gatling.io/open-source/"&gt;Open Source – Gatling Open-Source Load Testing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.ajisaba.net/develop/gatling/test_case_csv_feeder.html"&gt;負荷試験ツール・Gatling・CSV ファイルと Feeder を使ったテストケース&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://locust.io/"&gt;Locust - A modern load testing framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k6.io/"&gt;Load testing for engineering teams | k6&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;コードの &lt;code&gt;setUp&lt;/code&gt; でシナリオを複数指定する箇所についてですが、より良いやり方があれば是非ご教授いただけますと幸いです... ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>gatling</category>
      <category>scala</category>
      <category>test</category>
      <category>web</category>
    </item>
    <item>
      <title>[TECH] Hugo + GitHub Pages + GitHub Actions で独自ドメインのウェブサイトを構築する 😊</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 20 Mar 2021 18:54:06 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-hugo-github-pages-github-actions-596</link>
      <guid>https://forem.com/nikaera/tech-hugo-github-pages-github-actions-596</guid>
      <description>&lt;p&gt;この記事は &lt;a href="https://qiita.com/advent-calendar/2020/static-site-generator"&gt;Static Site Generator Advent Calendar 2020&lt;/a&gt; 10 日目の記事です。&lt;/p&gt;

&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--mRz2aqq8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ffe8fe276b9d008461880581002430ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mRz2aqq8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ffe8fe276b9d008461880581002430ec.png" alt="Hugo のトップページ"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Zenn や Qiita, note など様々なウェブサービスで記事を書くにつれて、ふと雑多な内容で自分の好き勝手に記事を書いて公開できる自分のブログが欲しくなりました。そこで、自分のブログを作ろうと思い調査したところ、SSG で作るのが手っ取り早そうだったのと、その中でも一番ラクにウェブサイトが構築できそうな &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; を採用しました。&lt;/p&gt;

&lt;p&gt;また、デプロイは簡単に行いたかったので、デプロイ先として &lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/about-github-pages"&gt;GitHub Pages&lt;/a&gt; を採用しました。&lt;br&gt;
独自ドメインの割り当てから HTTPS 対応まで無料でできるかつ、使い慣れている GitHub をデプロイ先に使えることが決め手でした。&lt;/p&gt;

&lt;p&gt;Hugo で自分のブログを構築して GitHub Pages で公開できるようになったのですが、ブログ内容を更新したり記事を書くたびにビルドしてデプロイをするのが、意外と面倒なことに気づきました。そこで、&lt;a href="https://github.com/marketplace/actions/github-pages-action"&gt;GitHub Pages action&lt;/a&gt; を用いて、ビルドしてデプロイするという作業は自動化しました。&lt;/p&gt;

&lt;p&gt;上記までの作業をすることで、自分のブログを書いたり更新することだけに集中できる環境を整えることができました。ウェブサイトを作りこむ以外は、簡単ないくつかの作業をするだけで Hugo で満足のいく自分のブログを書く環境が整えられたので、その手順についてまとめてみました。&lt;/p&gt;

&lt;p&gt;ちなみに本記事の手順で実際に作成した私のブログは下記です。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://nikaera.com/"&gt;https://nikaera.com/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Hugo を PC にインストールする
&lt;/h1&gt;

&lt;p&gt;私は Windows には &lt;a href="https://chocolatey.org/"&gt;Chocolatey&lt;/a&gt;、Mac では &lt;a href="https://brew.sh/index_ja"&gt;Homebrew&lt;/a&gt; で Hugo をインストールしました。Chocolatey や Homebrew を利用したインストール方法については &lt;a href="https://gohugo.io/getting-started/installing/"&gt;公式サイトの手順&lt;/a&gt; で公開されています。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Mac で Homebrew を使って Hugo をインストールする&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;hugo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Windows で Chocolatey を使って Hugo をインストールする&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hugo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-confirm&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Sass/SCSS を用いてウェブサイトのデザイン改修を行いたい場合は&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 下記で Chocolatey を使って Hugo をインストールする&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;choco&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;hugo-extended&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-confirm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Hugo で自分のウェブサイトを構築する
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Hugo プロジェクトを作成する
&lt;/h2&gt;

&lt;p&gt;下記コマンドで Hugo のプロジェクトフォルダを生成できます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo new site &amp;lt;プロジェクトフォルダのパス&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hugo のコンフィグファイルのデフォルトフォーマットは &lt;a href="https://github.com/toml-lang/toml.io/blob/main/specs/ja/v1.0.0-rc.2.md"&gt;TOML&lt;/a&gt; 形式なのですが、&lt;code&gt;hugo new site &amp;lt;プロジェクトフォルダへのパス&amp;gt; -f json&lt;/code&gt; のように &lt;code&gt;-f&lt;/code&gt; オプションを付与することで JSON フォーマットでもコンフィグファイルを生成可能です。&lt;/p&gt;

&lt;p&gt;後述しますが、一部の Hugo のテーマはコンフィグファイルのサンプルが JSON ファイルで書かれています。その場合は、新規で設定するコンフィグファイルのフォーマットも JSON で統一しておくと各種設定項目の調整が楽になりそうです。&lt;/p&gt;

&lt;p&gt;もしくは&lt;a href="https://github.com/sclevine/yj"&gt;こちら&lt;/a&gt;のようなコンバーターを使用したり、&lt;a href="https://pseitz.github.io/toml-to-json-online-converter/"&gt;こちら&lt;/a&gt;のようなウェブのコンバーターを使用して、設定ファイルを JSON から TOML フォーマットに変更しても良さそうです。&lt;/p&gt;

&lt;h2&gt;
  
  
  ウェブサイトのテーマを探す
&lt;/h2&gt;

&lt;p&gt;テーマとは、ウェブサイトのデザインテンプレートのことです。テーマは &lt;a href="https://themes.gohugo.io/"&gt;Hugo Themes&lt;/a&gt; で公開されているので、この中から自分の好みを選びます。Hugo ではテーマの内容をカスタマイズしたり差し替えることもできるので、あとから一部デザインを自分好みに修正できます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XTTLMmKL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/294b076125052fe8bd9574c5bd0d1f0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XTTLMmKL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/294b076125052fe8bd9574c5bd0d1f0b.png" alt="Hugo Themes にアクセスした時のページ"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://themes.gohugo.io/"&gt;Hugo Themes&lt;/a&gt; にアクセスした時のページ&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;お気に入りのテーマが見つかったら、&lt;code&gt;Download&lt;/code&gt; ボタンをクリックしてテーマの GitHub URL を控えておきます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qy-iV15W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/36ce79358e416ff6c92e7ad405c2abfa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qy-iV15W--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/36ce79358e416ff6c92e7ad405c2abfa.png" alt="Kiera というテーマのページ"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;a href="https://themes.gohugo.io/hugo-kiera/"&gt;Kiera&lt;/a&gt; というテーマのページ&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Hugo プロジェクトにテーマを適用する
&lt;/h2&gt;

&lt;p&gt;テーマの適用方法については &lt;a href="https://gohugo.io/getting-started/quick-start/#step-3-add-a-theme"&gt;公式サイトの手順&lt;/a&gt; で紹介されていますが、Hugo のプロジェクトフォルダのルートで下記コマンドを実行して、コンフィグファイルに &lt;code&gt;theme&lt;/code&gt; を追記するだけです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;テーマの GitHub URL&amp;gt; themes/&amp;lt;テーマ名&amp;gt; &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'theme = "&amp;lt;テーマ名&amp;gt;"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;どのテーマも基本的に適用方法は同じで、例えば私が採用したテーマである &lt;a href="https://themes.gohugo.io/hugo-papermod/"&gt;PaperMod&lt;/a&gt; を適用する場合は下記コマンドを実行することになります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/adityatelange/hugo-PaperMod themes/hugo-PaperMod &lt;span class="nt"&gt;--depth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'theme = "hugo-PaperMod"'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで自分のウェブサイトを作り込んでいくための準備が整いました。&lt;/p&gt;

&lt;h2&gt;
  
  
  ウェブサイトを作り込む
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;各テーマには &lt;code&gt;exampleSite&lt;/code&gt; というウェブサイト作成の参考になる Hugo のプロジェクトフォルダが存在します。&lt;/strong&gt; 最初は &lt;code&gt;exampleSite&lt;/code&gt; フォルダ内に存在する各種ファイルを自分のプロジェクトにコピー&amp;amp;ペーストして、改変しながらウェブサイトを作り込んでいくとスムーズに作業が進められます。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;exampleSite&lt;/code&gt; フォルダは大抵 GitHub プロジェクトのルートに存在しているのですが、&lt;strong&gt;GitHub のブランチで管理しているものもあります。&lt;/strong&gt; 私の採用した PaperMod は GitHub の &lt;code&gt;exampleSite&lt;/code&gt; ブランチで管理していました。&lt;/p&gt;

&lt;p&gt;上記のような詳細は Hugo のテーマページに記載があるはずなので、事前に &lt;code&gt;exampleSite&lt;/code&gt; フォルダが配置されている場所や設定可能な項目等をチェックしておくことをオススメします。実際のウェブサイトの見た目を確認するには、Hugo のプロジェクトフォルダのルートで下記コマンドを実行します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hugo server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;hugo server&lt;/code&gt; 実行中に、ウェブサイトの設定を変更したり記事を追加すると、自動的にビルドが実行されるので常に最新のウェブサイトの見た目を確認できます。また、その際にエラーもコンソールに出力されるので、適宜修正しながらウェブサイトの作成を進めていくことが可能です。&lt;/p&gt;

&lt;h1&gt;
  
  
  GitHub にデプロイ環境を整える
&lt;/h1&gt;

&lt;p&gt;ウェブサイトの構築が出来ればあとはデプロイするだけです。今回は GitHub Actions でデプロイするため、残りの作業は GitHub 上で進めていきます。一応 GitHub Actions を使わずに手動でデプロイする手順については、Hugo の &lt;a href="https://gohugo.io/hosting-and-deployment/hosting-on-github/"&gt;公式サイトの手順&lt;/a&gt; にて紹介されています。&lt;/p&gt;

&lt;p&gt;GitHub Actions を用いてデプロイできるようにした際の利点としては下記があります。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;デプロイ時に毎回手動で複数コマンド実行する手間が省ける&lt;/li&gt;
&lt;li&gt;自動化することでデプロイ時のコマンドミスを防ぐことが可能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常に統一した Hugo のビルド環境が利用可能&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最初のうちは私も公式サイトの手順通りに Mac で手動デプロイしていました。しかし、Windows 環境でデプロイ作業した際、本番環境デプロイ時に &lt;a href="https://gohugo.io/hugo-pipes/fingerprint/"&gt;SRI&lt;/a&gt; 関連のエラーが発生してしまい、ウェブサイトに stylesheet が適用されないバグが発生してしまいました。&lt;/p&gt;

&lt;p&gt;結局原因はよく分からなかったのですが、GitHub Actions 経由でデプロイするようにしたところ直りました。&lt;strong&gt;CI 経由でデプロイできるようになると、こういった実行環境の違いによる挙動も気にする必要が無くなります。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;一応 Windows 環境で発生した SRI 関連のバグは Hugo で該当するテンプレートファイルを &lt;code&gt;layouts&lt;/code&gt; フォルダを利用して差し替えて、integrity の設定内容を空にすることで、本番環境でも stylesheet が適用できるようになったことは確認しました。&lt;a href="https://stackoverflow.com/a/65052963"&gt;詳細はこちら&lt;/a&gt;。&lt;/p&gt;

&lt;h2&gt;
  
  
  自分のウェブサイトをデプロイするためのリポジトリを作成する
&lt;/h2&gt;

&lt;p&gt;GitHub の &lt;a href="https://pages.github.com/"&gt;公式サイトの手順&lt;/a&gt; に従って、自身のウェブサイトをデプロイするためのリポジトリを作成します。また本記事では、&lt;strong&gt;Hugo プロジェクトのリポジトリはデプロイ用リポジトリとは別で扱うため、Hugo プロジェクトのリポジトリも新たに作成します。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;本記事では Hugo プロジェクトのリポジトリ名は &lt;code&gt;hugo-blog&lt;/code&gt; という名前に設定した前提で進めていきます。また作成したリモートリポジトリの情報は、下記コマンドで Hugo プロジェクトに登録しておきます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hugo プロジェクトフォルダのルートで Git リポジトリを作成する&lt;/span&gt;
git init

&lt;span class="c"&gt;# Hugo プロジェクトのリポジトリ情報を登録しておく (hugo-blog の GitHub URL)&lt;/span&gt;
git remote add origin &amp;lt;Hugo プロジェクトのリポジトリの URL&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Actions で Hugo のビルドからデプロイまでを自動化するための環境を整える
&lt;/h2&gt;

&lt;p&gt;今回は &lt;code&gt;hugo-blog&lt;/code&gt; という Hugo プロジェクトのリポジトリから、デプロイ用リポジトリへデプロイしたいため、まずはデプロイ用リポジトリでデプロイキーを登録します。&lt;a href="https://docs.github.com/ja/free-pro-team@latest/developers/overview/managing-deploy-keys#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%82%AD%E3%83%BC"&gt;公式サイトの手順&lt;/a&gt; に従いデプロイキーを登録したら、秘密鍵を &lt;code&gt;hugo-blog&lt;/code&gt; リポジトリのシークレットに登録します。&lt;/p&gt;

&lt;p&gt;シークレットは &lt;a href="https://docs.github.com/ja/free-pro-team@latest/actions/reference/encrypted-secrets#%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%95%E3%82%8C%E3%81%9F%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90"&gt;公式サイトの手順&lt;/a&gt; に従って登録します。今回は秘密鍵をシークレットに登録する際の名前に &lt;code&gt;ACTIONS_DEPLOY_KEY&lt;/code&gt; を設定した前提で進めていきます。&lt;/p&gt;

&lt;p&gt;次に &lt;a href="https://github.com/marketplace/actions/github-pages-action"&gt;GitHub Pages action&lt;/a&gt; を導入して Hugo のビルドから公開までを自動化する環境をセットアップします。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;hugo-blog&lt;/code&gt; リポジトリに GitHub Pages action の &lt;a href="https://github.com/marketplace/actions/github-pages-action#getting-started"&gt;Getting started&lt;/a&gt; を参考にワークフローファイルを追加します。&lt;code&gt;- name: Deploy&lt;/code&gt; の項目のみデプロイ用リポジトリへデプロイするために設定内容を変更します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github pages&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;  &lt;span class="c1"&gt;# Set a branch name to trigger deployment&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-18.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;submodules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Fetch Hugo themes (true OR recursive)&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# Fetch all history for .GitInfo and .Lastmod&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Hugo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaceiris/actions-hugo@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;hugo-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.78.2'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hugo --minify&lt;/span&gt;

    &lt;span class="c1"&gt;#   - name: Deploy&lt;/span&gt;
    &lt;span class="c1"&gt;#     uses: peaceiris/actions-gh-pages@v3&lt;/span&gt;
    &lt;span class="c1"&gt;#     with:&lt;/span&gt;
    &lt;span class="c1"&gt;#       github_token: ${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="c1"&gt;#       publish_dir: ./public&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaceiris/actions-gh-pages@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deploy_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ACTIONS_DEPLOY_KEY }}&lt;/span&gt;
          &lt;span class="na"&gt;external_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nikaera/nikaera.github.io&lt;/span&gt;
          &lt;span class="na"&gt;publish_branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
          &lt;span class="na"&gt;cname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nikaera.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;- name: Deploy&lt;/code&gt; の項目で設定した各種パラメータは下記になります。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;キー&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;deploy_key&lt;/td&gt;
&lt;td&gt;デプロイ時に使用する秘密鍵&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;external_repository&lt;/td&gt;
&lt;td&gt;デプロイ先のリモートリポジトリ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;publish_branch&lt;/td&gt;
&lt;td&gt;デプロイ先のリモートリポジトリのブランチ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cname&lt;/td&gt;
&lt;td&gt;設定するカスタムドメイン名&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;deploy_key&lt;/code&gt; にはシークレットに登録した秘密鍵を設定します。&lt;code&gt;external_repository&lt;/code&gt; には Hugo をビルドした際のデプロイ先リポジトリを &lt;code&gt;&amp;lt;ユーザ名&amp;gt;/&amp;lt;リポジトリ名&amp;gt;&lt;/code&gt; のフォーマットで指定します。&lt;code&gt;publish_branch&lt;/code&gt; はデプロイ先として使用するブランチ名になります。&lt;code&gt;cname&lt;/code&gt; には自分が設定したいドメイン名を指定します。&lt;code&gt;cname&lt;/code&gt; の &lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site"&gt;詳細&lt;/a&gt; はこちらからご確認いただけます。&lt;/p&gt;

&lt;h2&gt;
  
  
  カスタムドメインで設定した内容で DNS 設定を書き換える
&lt;/h2&gt;

&lt;p&gt;GitHub Pages にカスタムドメインを利用する際は、該当するドメインの DNS レコードの設定で CNAME レコードもしくは A レコードを設定する必要があります。&lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site"&gt;公式サイトの手順&lt;/a&gt; に従って設定します。&lt;/p&gt;

&lt;p&gt;また、カスタムドメインの設定後は特別な理由がない限りは、デプロイ用リポジトリで &lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/securing-your-github-pages-site-with-https"&gt;HTTPS 強制の設定&lt;/a&gt; をしておくことオススメします。&lt;/p&gt;

&lt;p&gt;ちなみに GitHub Pages で利用している証明書は &lt;a href="https://github.blog/2018-05-01-github-pages-custom-domains-https/"&gt;Let's Encrypt&lt;/a&gt; のものになります。&lt;/p&gt;

&lt;p&gt;設定作業はこれで完了です。あとは実際に Hugo プロジェクトを更新後、&lt;code&gt;hugo-blog&lt;/code&gt; リポジトリに push することでビルドからデプロイまで GitHub Actions で行われるようになったかを確認していきます。&lt;/p&gt;

&lt;h2&gt;
  
  
  Hugo プロジェクトの更新時に自動でデプロイが行われるか確認する
&lt;/h2&gt;

&lt;p&gt;Hugo プロジェクトには既に &lt;code&gt;hugo-blog&lt;/code&gt; リポジトリの GitHub URL が &lt;code&gt;origin&lt;/code&gt; として設定されているはずなので、下記で実際に &lt;code&gt;hugo-blog&lt;/code&gt; リポジトリへ push してみます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Hugo プロジェクトを hugo-blog リポジトリに push する&lt;/span&gt;
git add &lt;span class="nt"&gt;-A&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"initial commit"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次は実際に GitHub リポジトリの &lt;code&gt;Actions&lt;/code&gt; タブから、GitHub Pages action のワークフローが実行されているか確認します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Q4qP5TuQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d4c3c5477c0f149ae84464fcc63c593b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q4qP5TuQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/d4c3c5477c0f149ae84464fcc63c593b.png" alt="GitHub Pages actions の実行に成功した様子"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub Pages actions の実行に成功した様子&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;無事ワークフローの実行に成功したことを確認したら、デプロイ用リポジトリの様子を確認します。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--QNpc64bL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/3a0eb5ca58701a39bb922ea14477488c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--QNpc64bL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/3a0eb5ca58701a39bb922ea14477488c.png" alt="デプロイ用リポジトリに Hugo の最新ビルドが push されていることを確認する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;デプロイ用リポジトリに Hugo の最新ビルドが push されていることを確認する&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;最新コミットのメッセージに &lt;code&gt;deploy: &amp;lt;ユーザ名&amp;gt;/hugo-blog@&amp;lt;コミットハッシュ&amp;gt;&lt;/code&gt; のコメントが表示されているはずです。コメントのリンクをクリックすると、&lt;code&gt;hugo-blog&lt;/code&gt; リポジトリの最新コミットの確認画面に遷移するはずです。&lt;/p&gt;

&lt;p&gt;ここまで確認できれば、あとはローカルで &lt;code&gt;hugo server&lt;/code&gt; して確認していたウェブサイトと同じように、GitHub Pages 上でもウェブサイトが見えるか実際に確認してみます。&lt;/p&gt;

&lt;p&gt;デプロイ用リポジトリ名が &lt;code&gt;&amp;lt;ユーザ名&amp;gt;/&amp;lt;ユーザ名&amp;gt;.github.io&lt;/code&gt; であった場合、&lt;code&gt;https://&amp;lt;ユーザ名&amp;gt;.github.io&lt;/code&gt; でウェブサイトにアクセスできます。私の場合は &lt;code&gt;https://nikaera.github.io&lt;/code&gt; になります。その際、カスタムドメインにリダイレクトすることも確認できれば、カスタムドメインの設定も正常に反映されています。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gA9-jFym--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b3c061569dae33a1dd58f4741c6d77cc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gA9-jFym--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/b3c061569dae33a1dd58f4741c6d77cc.png" alt="ページが正常に表示できカスタムドメインでもアクセスできている様子"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ページが正常に表示されていてカスタムドメインでアクセスできた様子&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;アクセス時に意図したページが表示されていることが確認できれば大丈夫です。これで自分のウェブサイトを更新したら &lt;code&gt;hugo-blog&lt;/code&gt; リポジトリに Hugo プロジェクトを push するだけで自分のウェブサイトを自動更新できるようになりました。&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;今回は Hugo を例にしましたが、本記事内で紹介した &lt;a href="https://github.com/marketplace/actions/github-pages-action"&gt;GitHub Pages action&lt;/a&gt; は &lt;a href="https://jamstack.org/generators/"&gt;様々な SSG&lt;/a&gt; に対応しています。そのため Hugo ではウェブサイトのカスタマイズに限界が来たなと感じたら &lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; や &lt;a href="https://www.gatsbyjs.com/"&gt;Gatsby&lt;/a&gt; に乗り換えるといったことも可能です。&lt;/p&gt;

&lt;p&gt;また Hugo では何も考えずとも、マークダウンで記事が書けてビルドも高速なので、手っ取り早く自分のウェブサイトを構築してみたいという用途にはピッタリだと感じました。&lt;/p&gt;

&lt;p&gt;関係ないですが、Hugo でウェブサイト構築する際の知見も記事に含めてしまったせいで、文章量が意図した倍近い量になってしまいました。。簡潔で分かりやすい文章が書けるようにならなきゃ。。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/"&gt;The world’s fastest framework for building websites | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://themes.gohugo.io/"&gt;Complete List | Hugo Themes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/about-github-pages"&gt;GitHub Pages について - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/marketplace/actions/github-pages-action"&gt;GitHub Pages action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/getting-started/installing/"&gt;Install Hugo | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/getting-started/quick-start/#step-3-add-a-theme"&gt;Quick Start | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/hosting-and-deployment/hosting-on-github/"&gt;Host on GitHub | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pages.github.com/"&gt;GitHub Pages | Websites for you and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/free-pro-team@latest/developers/overview/managing-deploy-keys#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%82%AD%E3%83%BC"&gt;デプロイキーの管理 - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/free-pro-team@latest/actions/reference/encrypted-secrets#%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%81%AE%E6%9A%97%E5%8F%B7%E5%8C%96%E3%81%95%E3%82%8C%E3%81%9F%E3%82%B7%E3%83%BC%E3%82%AF%E3%83%AC%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90"&gt;暗号化されたシークレット - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site"&gt;GitHub Pages サイトのカスタムドメインを管理する - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/ja/free-pro-team@latest/github/working-with-github-pages/securing-your-github-pages-site-with-https"&gt;HTTPS で GitHub Pages サイトを保護する - GitHub Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>hugo</category>
      <category>githubactions</category>
      <category>githubpages</category>
    </item>
    <item>
      <title>[TECH] Hugo で React + TypeScript を利用してサクッとウェブサイトに RSS リーダーを追加する ⛳</title>
      <dc:creator>nikaera</dc:creator>
      <pubDate>Sat, 20 Mar 2021 18:53:55 +0000</pubDate>
      <link>https://forem.com/nikaera/tech-hugo-react-typescript-rss-3hl5</link>
      <guid>https://forem.com/nikaera/tech-hugo-react-typescript-rss-3hl5</guid>
      <description>&lt;p&gt;この記事は &lt;a href="https://qiita.com/advent-calendar/2020/static-site-generator"&gt;Static Site Generator Advent Calendar 2020&lt;/a&gt; 22 日目の記事です。&lt;/p&gt;

&lt;h1&gt;
  
  
  はじめに
&lt;/h1&gt;

&lt;p&gt;Hugo のウェブサイトに組み込む RSS リーダーを TypeScript で開発してみたいと思い調査したところ、Hugo の最新版には &lt;a href="https://github.com/evanw/esbuild"&gt;ESBuild&lt;/a&gt; が組み込まれていて、&lt;strong&gt;非常に手厚く JavaScript の開発環境がサポートされていることが分かりました。&lt;/strong&gt; 本記事では紹介していませんが &lt;a href="https://gohugo.io/hugo-pipes/babel/"&gt;Babel&lt;/a&gt; も利用できるようです。&lt;/p&gt;

&lt;p&gt;また、NPM パッケージも利用できるため、普段のウェブ開発と同様の流れで開発ができ、各種ライブラリを用いた開発も非常に楽でした。&lt;br&gt;
今回は Hugo で JavaScript 開発する方法を RSS リーダーの開発を例に上げ、そこで得た知見についても交える形で記事として残しておくことにしました。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ちなみに本記事内容は Hugo で JavaScript 開発する方法に焦点を絞ったものなのですが、ウェブサイトに RSS リーダーを組み込むことに焦点を絞って見たい方は &lt;code&gt;RSS リーダーを Hugo の Data Templates で実装する&lt;/code&gt; から見ていただくことをオススメします。&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Hugo で JavaScript (React + TypeScript) の開発環境を整える
&lt;/h1&gt;

&lt;p&gt;まず、&lt;strong&gt;TypeScript のビルドは ESBuild に任せることができるため何も行う必要はありません。&lt;/strong&gt; そのため React 開発用パッケージのインストールのみ行えば大丈夫です。&lt;/p&gt;

&lt;p&gt;Hugo プロジェクトのルートディレクトリで下記コマンドを実行し、&lt;code&gt;package.json&lt;/code&gt; を作成してから、React の開発に必要なパッケージをインストールします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; react react-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;無事パッケージのインストールが完了したら、早速 TSX ファイルを &lt;code&gt;assets/js/App.tsx&lt;/code&gt; に作成してしまいます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;Hello&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記のコードを見てもらえば分かる通り、レンダリング先に &lt;code&gt;id&lt;/code&gt; が &lt;code&gt;react&lt;/code&gt; の DOM ノードを指定しています。そのため Hugo 側で該当する DOM ノードを用意する必要があります。その際の HTML テンプレートは下記になります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- 利用するリソースを指定する --&amp;gt;&lt;/span&gt;
{{ with resources.Get "js/App.tsx" }}

&lt;span class="c"&gt;&amp;lt;!-- id が react の div 要素を用意する --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"react"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- TSX を ESBuild でビルドする際の Hugo のオプションを指定する --&amp;gt;&lt;/span&gt;
{{ $options := dict "targetPath" "js/app.js" "minify" true "defines" (dict
"process.env.NODE_ENV" "\"development\"") }}

&lt;span class="c"&gt;&amp;lt;!-- TSX のビルドを Hugo のオプションで指定した内容で実行する --&amp;gt;&lt;/span&gt;
{{ $js := resources.Get . | js.Build $options }}

&lt;span class="c"&gt;&amp;lt;!-- 一応 SRI を有効化した状態でビルドした JS を読み込む --&amp;gt;&lt;/span&gt;
{{ $secureJS := $js | resources.Fingerprint "sha512" }}
&lt;span class="nt"&gt;&amp;lt;script
  &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"{{ $secureJS.Permalink }}"&lt;/span&gt;
  &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"{{ $secureJS.Data.Integrity }}"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

{{ end }}

&lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ちなみに &lt;code&gt;$options&lt;/code&gt; で指定している ESBuild でビルド時に指定可能なオプションは &lt;a href="https://gohugo.io/hugo-pipes/js/"&gt;Hugo の公式ページ&lt;/a&gt; に記載されています。&lt;/p&gt;

&lt;p&gt;上記 HTML の記述を RSS リーダーを埋め込みたいページに追加します。&lt;br&gt;
この状態で該当ページにアクセスすると下記のような表示が確認できるはずです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s1eYZeAd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/7e196a2a52f492771deb5dd6913bbe60.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s1eYZeAd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/7e196a2a52f492771deb5dd6913bbe60.png" alt="Hello React! と画面に表示される"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;App.tsx で定義した内容が画面に表示される&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;これで React + TypeScript の開発環境が整いました。&lt;/p&gt;
&lt;h1&gt;
  
  
  RSS リーダーを実装する
&lt;/h1&gt;

&lt;p&gt;あとは一般的な Web フロントエンド開発の流れで RSS リーダーの開発を進めていくだけです。&lt;/p&gt;
&lt;h2&gt;
  
  
  ウェブサイトで読み込みたい RSS フィードを準備する
&lt;/h2&gt;

&lt;p&gt;RSS フィードを利用する際は必ず提供しているサービスの利用規約をご確認ください。&lt;br&gt;
&lt;a href="https://qiita.com/terms"&gt;Qiita&lt;/a&gt; 及び &lt;a href="https://zenn.dev/terms"&gt;Zenn&lt;/a&gt; については個人利用かつ自分の情報のみを扱う範囲内であれば利用が許可されているように見受けられました。&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;下準備としてウェブサイトで読み込みたい RSS フィードを事前にダウンロードするためのバッチを作成します。バッチは NPM を利用して作成していきます。&lt;strong&gt;NPM を導入したので Hugo で利用する簡易なバッチは JavaScript でサクッと作成していきます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;まずはスクリプト作成の際に必要となるパッケージを事前にいくつかインストールします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# html をテキスト変換にするパッケージと RSS フィードのパーサーをインストールする&lt;/span&gt;
npm i &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; html-to-text rss-parser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;実際のコードは下記になります。ファイル名末尾が &lt;code&gt;.mjs&lt;/code&gt; なのは &lt;a href="https://dev.to/mikeesto/top-level-await-in-node-2jad"&gt;Top-Level Await&lt;/a&gt; を使用したいからです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html-to-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;htmlToText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Parser&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rss-parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 自ブログで読み込みたい RSS フィードの情報を設定する&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rssFeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Zenn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://zenn.dev/nikaera/feed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://zenn.dev/nikaera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;Qiita&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://qiita.com/nikaera/feed.atom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://qiita.com/nikaera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonFeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="c1"&gt;// RSS フィード内の description を 73字で切り取り末尾に ... を付与する関数&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spliceContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;htmlToText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;...`&lt;/span&gt;

    &lt;span class="c1"&gt;// rssFeed 変数で定義されてる情報を繰り返し処理する&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rssFeed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// RSS フィードの URL から必要な情報を取得する&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// RSS フィードに登録されている項目で必要な情報のみを取得する&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;spliceContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubDate&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c1"&gt;// 取得内容は jsonFeed に格納する&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;
        &lt;span class="nx"&gt;jsonFeed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 最後に jsonFeed に格納された内容を JSON 文字列として static/rss.json に出力する&lt;/span&gt;
    &lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./static/rss.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonFeed&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次に &lt;code&gt;package.json&lt;/code&gt; の &lt;code&gt;scripts&lt;/code&gt; に登録してコマンドとして実行可能にします。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"update-rss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node ./scripts/update-rss.mjs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで &lt;code&gt;npm run update-rss&lt;/code&gt; を実行すれば自ブログで表示する際に用いる JSON ファイルとして RSS フィードの内容を &lt;code&gt;static/rss.json&lt;/code&gt; に出力できます。また、JSON ファイルは &lt;code&gt;static&lt;/code&gt; フォルダに出力しているため &lt;code&gt;http://localhost:1313/rss.json&lt;/code&gt; でアクセスできます。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Set0iHRo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/508ba87c41f1c1e410b89ff1bb56be4e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Set0iHRo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/508ba87c41f1c1e410b89ff1bb56be4e.png" alt="npm run update-rss を実行して出力した rss.json"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;npm run update-rss を実行して出力した rss.json&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfjr-QYY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9b7ebeedce1cb69b6b3ab8acacb0b1d1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfjr-QYY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/9b7ebeedce1cb69b6b3ab8acacb0b1d1.png" alt="npm run update-rss を実行して出力した rss.json にブラウザからアクセスする"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;&lt;code&gt;http://localhost:1313/rss.json&lt;/code&gt; にアクセスして出力した rss.json が参照可能なことを確認する&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  RSS リーダーを React + TypeScript で実装する
&lt;/h2&gt;

&lt;p&gt;準備が整ったので、早速 RSS リーダーを作成していきます。&lt;/p&gt;

&lt;p&gt;下記は Hugo のテーマの 1 つである &lt;a href="https://themes.gohugo.io/hugo-papermod/"&gt;hugo-PaperMod&lt;/a&gt; の &lt;code&gt;archives&lt;/code&gt; テンプレートを利用してページに埋め込むことを想定した RSS リーダーのコードです。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;superagent&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;superagent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Rss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nx"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;superagent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/rss.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="nx"&gt;setFeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})()&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-month&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-month-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&amp;gt; - &amp;lt;a href={feed.rss_url} target="_blank" rel="noopener noreferrer"&amp;gt;RSS&amp;lt;/&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-posts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-entry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-entry-title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-meta&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;entry-link&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;_blank&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;noopener noreferrer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;nbsp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/a&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="p"&gt;})}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Rss&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;次に &lt;code&gt;assets/js/App.tsx&lt;/code&gt; で &lt;code&gt;assets/js/Rss.tsx&lt;/code&gt; を読み込み画面に表示できるよう改修します。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Rss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./Rss&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ReactDOM&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;archive-year-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nx"&gt;Tech&lt;/span&gt; &lt;span class="err"&gt;🦾&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rss&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Zenn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Rss&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Qiita&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;ReactDOM&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで RSS リーダーを埋め込んだページを閲覧すると下記のような画面が表示されるはずです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Vt6t1QsG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/0a6b8923d141ae70f5e298637f5acc69.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Vt6t1QsG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/0a6b8923d141ae70f5e298637f5acc69.png" alt="hugo-PaperMod で archives テンプレートを用いて RSS リーダーを表示する"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;hugo-PaperMod で &lt;code&gt;archives&lt;/code&gt; テンプレートを用いて RSS リーダーを表示したときの画面&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;もし他の RSS フィードを追加したい場合は &lt;code&gt;scripts/update-rss.mjs&lt;/code&gt; の &lt;code&gt;rssFeed&lt;/code&gt; 変数に情報を追加して、&lt;code&gt;App.tsx&lt;/code&gt; に &lt;code&gt;&amp;lt;Rss name="&amp;lt;rssFeed 変数で定義した RSS Feed 名&amp;gt;" /&amp;gt;&lt;/code&gt; を定義することで対応できます。&lt;/p&gt;
&lt;h1&gt;
  
  
  RSS フィードの内容を自動で更新する
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;npm run update-rss&lt;/code&gt; を手元で実行して &lt;code&gt;static/rss.json&lt;/code&gt; を更新して公開すれば、最新の RSS フィードの内容をページに反映できる状態ですが、都度手動で更新するのは面倒な作業です。&lt;/p&gt;

&lt;p&gt;そこで今回は GitHub Actions の &lt;code&gt;schedule&lt;/code&gt; を用いて &lt;code&gt;static/rss.json&lt;/code&gt; の更新を自動化します。&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Actions のワークフローファイルを作成する
&lt;/h2&gt;

&lt;p&gt;実際のワークフローファイルは下記になります。&lt;code&gt;schedule&lt;/code&gt; の項目で設定している内容がワークフローの実行スケジュールになります。今回は半日毎に更新が走るようにしました。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update rss json file&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;  &lt;span class="c1"&gt;# Set a branch name to trigger deployment&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*/12&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt; &lt;span class="c1"&gt;# 今回は半日に 1回のタイミングで更新するようにした&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-18.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
          &lt;span class="na"&gt;submodules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Fetch Hugo themes (true OR recursive)&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# Fetch all history for .GitInfo and .Lastmod&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js 14.10.1&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;14.10.1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update RSS Feeds&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run update-rss&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit files&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config --local user.email "action@github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git config --local user.name "GitHub Action"&lt;/span&gt;
          &lt;span class="s"&gt;git add static/rss.json&lt;/span&gt;
          &lt;span class="s"&gt;STATUS=$(git status -s)&lt;/span&gt;
          &lt;span class="s"&gt;if [ -n "$STATUS" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;git commit -m "Update rss.json `date +'%Y-%m-%d %H:%M:%S'`" -a&lt;/span&gt;
            &lt;span class="s"&gt;git push origin main&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記ワークフローファイルをプロジェクトに追加して、リモートリポジトリにプッシュした後は、ワークフローが実行されるタイミングを待ちます。&lt;/p&gt;

&lt;p&gt;無事にワークフローの実行が完了すると下記のようなコミットが追加されているはずです。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2_JAf2OP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ebb7cb2e64b13e4a1e1a592836f511f5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2_JAf2OP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/ebb7cb2e64b13e4a1e1a592836f511f5.png" alt="GitHub Actions が JSON ファイルを更新してコミットしている"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;GitHub Actions が JSON ファイルを更新してコミットしている&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jbVTJZF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1a74399a3cf1053e0480a01590086fbe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jbVTJZF6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/1a74399a3cf1053e0480a01590086fbe.png" alt="コミットの詳細を見ると正常に JSON ファイルが更新されていることを確認できる"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;コミットの詳細を見ると正常に JSON ファイルが更新されていることが確認できる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--G-LdkQ0i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/bf86668d5fc32ca09b6d2cfcf71262ce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--G-LdkQ0i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i.gyazo.com/bf86668d5fc32ca09b6d2cfcf71262ce.png" alt="コミット後 Hugo をビルド &amp;amp; デプロイするとページが更新されていることを確認できる"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;コミット後 Hugo をビルド &amp;amp; デプロイするとページが更新されていることを確認できる&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;これで Zenn や Qiita 等に記事を書いた際に、都度手動で &lt;code&gt;static/rss.json&lt;/code&gt; を更新してページに最新の内容を反映させる作業は必要なくなりました。&lt;/p&gt;
&lt;h1&gt;
  
  
  (余談) RSS リーダーを Hugo の Data Templates で実装する
&lt;/h1&gt;

&lt;p&gt;ちなみに Hugo には &lt;a href="https://gohugo.io/templates/data-templates/"&gt;Data Templates&lt;/a&gt; という仕組みがあり、これを用いることで実は JavaScript を利用しなくても HTML テンプレートで RSS リーダーを実現できるということを後から知りました。&lt;/p&gt;

&lt;p&gt;そこで最後に Data Template での RSS リーダーの実装方法について記載します。&lt;/p&gt;

&lt;p&gt;まずは、&lt;code&gt;scripts/update-rss.mjs&lt;/code&gt; の内容を書き換えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;html-to-text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;htmlToText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Parser&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rss-parser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rssFeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;Zenn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://zenn.dev/nikaera/feed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://zenn.dev/nikaera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;Qiita&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://qiita.com/nikaera/feed.atom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://qiita.com/nikaera&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonFeed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spliceContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;htmlToText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;...`&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rssFeed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parseURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;spliceContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pubDate&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;
        &lt;span class="nx"&gt;jsonFeed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rss_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;profile_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="cm"&gt;/*
        最終的な JSON ファイルの出力先は data フォルダとなり、RSS フィード毎に出力する
        例: ./data/Qiita.json, ./data/Zenn.json, etc.
        */&lt;/span&gt;
        &lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./data/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonFeed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上記を実行することで &lt;code&gt;data/Qiita.json&lt;/code&gt; や &lt;code&gt;data/Zenn.json&lt;/code&gt; にファイルが出力されます。&lt;/p&gt;

&lt;p&gt;Hugo の Data Template を用いると &lt;code&gt;data&lt;/code&gt; フォルダ内に配置した &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;yaml&lt;/code&gt;, &lt;code&gt;toml&lt;/code&gt; 形式のファイルは Go の HTML テンプレートで読み込めるようになります。&lt;/p&gt;

&lt;p&gt;例えば、&lt;strong&gt;&lt;code&gt;data/Qiita.json&lt;/code&gt; に配置された JSON ファイルを読み込みたい場合は Go のテンプレートで &lt;code&gt;$Qiita := $.Site.Data.Qiita&lt;/code&gt; のような記述でできます。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;次に RSS リーダーを埋め込んでいたページを下記のように書き換えます。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- React 関連の記述を全て削除する --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!--
{{ with resources.Get "js/App.tsx" }}
&amp;lt;div id="react"&amp;gt;&amp;lt;/div&amp;gt;
{{ $options := dict "targetPath" "js/app.js" "minify" true "defines" (dict "process.env.NODE_ENV" "\"development\"") }}
{{ $js := resources.Get . | js.Build $options }}
{{ $secureJS := $js | resources.Fingerprint "sha512" }}
&amp;lt;script src="{{ $secureJS.Permalink }}" integrity="{{ $secureJS.Data.Integrity }}"&amp;gt;&amp;lt;/script&amp;gt;
{{ end }}
--&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-year"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-year-header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tech 🦾&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-month"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- data/Zenn.json の内容を読み込む --&amp;gt;&lt;/span&gt;
    {{ $Zenn := $.Site.Data.Zenn }}
    &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-month-header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
        &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $Zenn.profile_url }}"&lt;/span&gt;
        &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;
        &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Zenn&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      -
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $Zenn.rss_url }}"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;RSS&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-posts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- 配列で格納されている記事情報を繰り返し処理で取得する --&amp;gt;&lt;/span&gt;
      {{- range $Zenn.items }}
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-entry"&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"{{ .url }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-entry-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ .title }}&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-meta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ .date }} - {{ .content }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"entry-link"&lt;/span&gt;
          &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"{{ .content }}"&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ .url }}"&lt;/span&gt;
          &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;" _blank"&lt;/span&gt;
          &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      {{- end }}
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-month"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- data/Qiita.json の内容を読み込む --&amp;gt;&lt;/span&gt;
    {{ $Qiita := $.Site.Data.Qiita }}
    &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-month-header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
        &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $Qiita.profile_url }}"&lt;/span&gt;
        &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;
        &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Qiita&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      -
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ $Qiita.rss_url }}"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;RSS&lt;span class="nt"&gt;&amp;lt;/a&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-posts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;&amp;lt;!-- 配列で格納されている記事情報を繰り返し処理で取得する --&amp;gt;&lt;/span&gt;
      {{- range $Qiita.items }}
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-entry"&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;"{{ .url }}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-entry-title"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ .title }}&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"archive-meta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{ .date }} - {{ .content }}&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt;
          &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"entry-link"&lt;/span&gt;
          &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"{{ .content }}"&lt;/span&gt;
          &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"{{ .url }}"&lt;/span&gt;
          &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;" _blank"&lt;/span&gt;
          &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"noopener noreferrer"&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
      {{- end }}
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;また GitHub Actions のワークフローを用いて RSS フィードの情報を更新していた場合は、&lt;code&gt;.github/workflows/update-rss.yml&lt;/code&gt; ファイルの更新も必要になります。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;update rss json file&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;  &lt;span class="c1"&gt;# Set a branch name to trigger deployment&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*/12&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-18.04&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
          &lt;span class="na"&gt;submodules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;  &lt;span class="c1"&gt;# Fetch Hugo themes (true OR recursive)&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;    &lt;span class="c1"&gt;# Fetch all history for .GitInfo and .Lastmod&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use Node.js 14.10.1&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;14.10.1&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm install&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Update RSS Feeds&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run update-rss&lt;/span&gt;

        &lt;span class="c1"&gt;# Git で追加する内容を data フォルダに変更する&lt;/span&gt;
        &lt;span class="c1"&gt;# git add static/rss.json -&amp;gt; git add data/&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit files&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git config --local user.email "action@github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git config --local user.name "GitHub Action"&lt;/span&gt;
          &lt;span class="s"&gt;git add data/&lt;/span&gt;
          &lt;span class="s"&gt;STATUS=$(git status -s)&lt;/span&gt;
          &lt;span class="s"&gt;if [ -n "$STATUS" ]; then&lt;/span&gt;
            &lt;span class="s"&gt;git commit -m "Update data folder `date +'%Y-%m-%d %H:%M:%S'`" -a&lt;/span&gt;
            &lt;span class="s"&gt;git push origin main&lt;/span&gt;
          &lt;span class="s"&gt;fi&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;これで JavaScript で作成した RSS リーダーから、Hugo の Data Templates を用いて作成した RSS リーダーへ移行できました。&lt;/p&gt;

&lt;h1&gt;
  
  
  おわりに
&lt;/h1&gt;

&lt;p&gt;Hugo で React + TypeScript 開発を楽にできそうなことが分かり、テンションが上がってしまい、そのままのノリで実際に RSS リーダーを自ブログ向けに作成してみました。&lt;/p&gt;

&lt;p&gt;しかし、本記事内容で RSS リーダーを実装するのであれば、Hugo の Data Templates を利用することがベストなことに後から気づきました。ただ Hugo での JavaScript を用いた開発手法が理解でき勉強になったので結果ヨシとしました。&lt;/p&gt;

&lt;p&gt;Hugo での JavaScript 開発環境は相当充実していることが分かったので、また何かアイデアを思いついたら気軽に作って自ブログに取り込んでいきます。今はザックリ WebGL/WebVR とかで何か面白いもの作れそうだなと考えています。&lt;/p&gt;

&lt;h1&gt;
  
  
  参考リンク
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://esbuild.github.io/"&gt;esbuild - An extremely fast JavaScript bundler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/templates/data-templates/"&gt;Data Templates | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/functions/"&gt;Functions Quick Reference | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/hugo-pipes/js/"&gt;JavaScript Building | Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-intro.html"&gt;Introducing Hooks – React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rbren/rss-parser"&gt;rbren/rss-parser: A lightweight RSS parser, for Node and the browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/html-to-text/node-html-to-text"&gt;html-to-text/node-html-to-text: Advanced html to text converter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;もし認識に誤りがあればコメント欄等でご教授いただけますと幸いです。 ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>hugo</category>
      <category>react</category>
      <category>rss</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
