OmniAICreator/Qiita-1.07M huggingface dataset:
Dockerとserveoを使って爆速でプラグイン対応マイクラサーバー構築

# はじめに
プラグイン対応マイクラサーバーの構築って、手順が多くてめんどくさいですよね。
そこで、今回はDockerとserveoを使ってポート解放なしでマイクラサーバーを簡単に構築する方法をご紹介します。
# 環境
- MacOS Ventura 13.3
- Docker 20.10.16
- docker-compose 1.29.2

Dockerはhomebrewでの導入をお勧めします。

https://qiita.com/nemui_/items/ed753f6b2eb9960845f7

# 手順
今回はこのイメージを使ってマイクラサーバーを構築します。

[itzg/minecraft-server](https://hub.docker.com/r/itzg/minecraft-server)

https://github.com/itzg/docker-minecraft-server

## Dockerイメージのpull
Dockerイメージをpullします。
```bash
$ docker pull itzg/minecraft-server
```
## docker-compose.ymlを書く

今回はdocker-composeを使ってコンテナを起動します。

docker-compose.ymlを作成し、以下のように記述します。
```yaml
version: '3'

services:
  mc:
    image: itzg/minecraft-server
    ports:
      - 25565:25565
    environment:
      - EULA=TRUE
      - TYPE=SPIGOT
      - MEMORY=4G
    tty: true
    stdin_open: true
    restart: unless-stopped
    volumes:
      # attach a directory relative to the directory containing this compose file
      - ./minecraft-server:/data

```
## 解説

### version
docker-composeのバージョンを定義しています。
### services
docker-composeではアプリケーションを構成する各要素のことをまとめてservicesと呼んでいます。
### image
起動するdockerイメージを指定しています。
### ports 
サーバーが使用するポートを指定しています。
### environment
コンテナ内で使われる環境変数を設定しています。
このイメージではこの環境変数を変更することでサーバーの種類やメモリ割当量などを変更しています。
このイメージで使えるサーバーの種類やその他の環境変数は

https://github.com/itzg/docker-minecraft-server/blob/master/README.md#server-types

ここから確認できます。
### volumes
ここで指定したホスト側のディレクトリをコンテナ内にマウントしています。
マウントしたフォルダにマイクラサーバーのワールドデータなどが保存されます。

なんとコレだけでサーバー構築の設定が完了しました！
## サーバーを起動する
```bash
docker-compose up -d
```
実行するとサーバーがバックグラウンドで起動します。

マイクラからも接続できました。やったね！
![スクリーンショット 2023-04-04 8.48.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2204837/40fc2766-be72-37f8-2812-66a0cb756ca6.png)

## コンソールにログインする
```bash
docker exec -i サーバーのコンテナ名 rcon-cli
```
rcon-cliを実行してマイクラサーバーのコンソールにログインすることができます。

## serveoで外部からアクセスできるようにする
```
ssh -R hogehoge.serveo.net:25560:localhost:25565 serveo.net
```
sshでポートフォワーディングします。

これで、サーバーが外部からもアクセスできる状態になりました。
hogehogeの部分を変更することで使いたいサブドメインを設定することができます。

# まとめ
docker-composeとserveoを利用すれば手軽にマイクラサーバーを構築することができる。
[TypeScript]trpcでDate型をサーバーとフロントでやり取りする方法

TypeScriptのtrpc使ってますか？　僕はバリバリ使ってます。

サーバー側の処理で、DBから値を取得することがあると思います。
prismaやKnexで「DATETIME型」を取得すると、TypeScript側ではDate型として認識されます。

## 問題
問題は、trpcでサーバー側とフロント側の型を共有する場合です。
DATETIME型の値をサーバーからフロントへ送信した段階で、TypeScriptのDate型ではなくなります。
(```YYYY-MM-DDTHH:mm:ss.sssZ``` の形式の、 ISOフォーマット(ISO 8601拡張フォーマット)の文字列となる)

そうすると、たとえばフロント側でYYYY年MM月DD日に変更したいとき、Date型と思って処理を行うと失敗することがあります。
Day.jsであれば、dayjs(dateValue)でISOStringだろうがDate型だろうがパースできるので大丈夫です。
しかし、date-fnsなどを使っているとDate型とISOString型で挙動が異なります。
そもそも関数の引数、返り値がstringなのかDateなのかは大きく変わってくるので、Date型で統一したいわけです。

例
```typescript
import { format, parseISO } from "date-fns";
import dayjs from "dayjs";
// dayjsでYYYY-MM-DD HH:mm:ssに変換する場合は、stringでもDateでもDayjsにしてくれるのでいけるが
export const dayjsWrapperToYYYYMMDDHHmmss = (date: string | number | Date | dayjs.Dayjs) =>
  dayjs().format("YYYY-MM-DD HH:mm:ss");

// date-fnsでyyyy-MM-dd HH:mm:ssに変換する
// Date型なら成功するが、ISOStringだと失敗する。ISOString型の場合はparseISOが必要。
export const dateFnsWrapperToYYYYMMDDHHmmss = (date: Date) => format(date, "yyyy-MM-dd HH:mm:ss");
```

## どうすれば良いのか

trpcのtransformerという機能を使います。
https://trpc.io/docs/server/data-transformers

事前にsuperjsonというライブラリを追加しておきましょ。
```sh
npm i superjson
```


### サーバー側の最低限の実装

```typescript:server.ts
import { initTRPC } from "@trpc/server";
import superjson from "superjson";

export const t = initTRPC.create({
  transformer: superjson, //ここに足してあげる
});

export const appRouter = t.router({
  getToday: t.procedure.query(() => new Date()),
});

export type AppRouter = typeof appRouter;
```

## フロント側の実装

```tsx
import "./App.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { httpBatchLink } from "@trpc/client";
import { useState } from "react";
import superjson from "superjson";
import { trpc } from "./utils/trpc";
import { IndexPage } from "./pages/IndexPage";

export const App = () => {
  const [queryClient] = useState(() => new QueryClient());
  const [trpcClient] = useState(() =>
    trpc.createClient({
      links: [
        httpBatchLink({
          url: "http://localhost:22004/trpc",
        }),
      ],
      transformer: superjson, //ここに足す！
    })
  );
  return (
    <trpc.Provider client={trpcClient} queryClient={queryClient}>
      <QueryClientProvider client={queryClient}>
        <IndexPage />
      </QueryClientProvider>
    </trpc.Provider>
  );
};
```

これで無事にDate型のデータをやり取りできるようになります。
![スクリーンショット 2023-04-05 22.30.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/679704/b754b65d-11a0-a1af-6046-98e45bad537c.png)
ReactHooksでlocalStorageを使用する

## localStorageとは
**Webブラウザにデータを保存するためのAPI**の一つです。cookieのようにWebサーバーにデータを保存するのではなくブラウザに保存するので、**ブラウザを閉じてもユーザーの入力情報などを保持し続けることができます**。主要なブラウザなら5MBまでデータを保存できます。テキストだと、大まかに250万文字保存可能です。


### 注意点
#### データは永続的に保存される
localStorageのデータを削除する処理を書かないと、データは永続的に残り続けるので削除する処理を書く必要があります。

#### Javascriptから自由にアクセスできる
セキュリティ上、ユーザーの個人情報など、機密性の高い情報には使わないよう注意です。

## 基本的な記述
```javascript
// ローカルストレージに値を保存する
localStorage.setItem("key", "value");

// ローカルストレージから値を取得する
const value = localStorage.getItem("key");

// ローカルストレージから値を削除する
localStorage.removeItem("key");

// ローカルストレージをすべてクリアする
localStorage.clear();
```
**１つのキーに対して１つのスカラー値を読み書き**します。
※スカラー値とは、単一の値で表される量です。1つの値もしくはデータ型（整数、論理値、文字列など）


## ReactHooksを使用した例
ReactHooksを使用するとコンポーネント内で状態を管理でき、また、状態が変化したときに、自動的に再レンダリングを行い、最新の状態を表示することができます。カスタムフックを使用することで、localStorageにアクセスするための共通のロジックを切り出し、再利用できます。
```javascript
import React, { useState, useEffect } from 'react';

// useStateとuseEffectを使用し、localStorageを扱うためのフックを定義
export const useLocalStorage = (key, initialValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      // localStorageから値を取得。値が存在しない場合はinitialValueを返す
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  // useEffectを使用し、stateの値が変更された時にlocalStorageに値を保存
  useEffect(() => {
    try {
      const serializedValue = JSON.stringify(storedValue);
      localStorage.setItem(key, serializedValue);
    } catch (error) {
      console.log(error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
};
```
`useLocalStorage`フックは、渡された`key`に対応する`localStorage`の値を取得し、`useState`で管理します。`initialValue(初期値)`が渡された場合 (`localStorage`に対応する値が無い場合は初期値を使用) `useState`の初期値に関数を渡すことで、初期値の計算を遅延させています。

`useEffect`は、`state`の値が変更された時に`localStorage`に値を保存する処理を実装しています。第2引数に`[key, storedValue]`を指定しているため、`key`か`storedValue`が変更された時に処理が実行されます。

なお、`JSON.parse(item)`は、JSON形式の文字列をパースしてJavaScriptのオブジェクトに変換できるメソッドです。`JSON.stringify()`は、JavaScriptオブジェクトをJSON文字列に変換するメソッドです。
WildFryについて

##　はじめに
実務でWildFryを使用したので、簡単なメモ程度の記事にしていきます。

## WildFryとは
WildFryとはAPサーバーの一つで、Red Hatが開発するマルチプラットフォームで動作するJava EEコンテナです。
WildFlyは、オープンソースのJava EE実装アプリケーションサーバとしてRed Hatによって、今も開発が行われています。
つまり、Javaで記述されたサーバサイドアプリケーションを動作させるためのAPサーバーと覚えておけばOK。

## APサーバーとは
APサーバーとは、アプリケーションを動かすためのサーバーです。WebサーバーとDBサーバーの中間役みたいな役割で、以下の画像のようなイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2613586/00d0646c-0be8-61cb-0f6f-3edd72bbb697.png)


## WildFryの特徴
WildFryの特徴は大きく5つあります。

・動作が軽く、安定している
起動が早く、高度なメモリ管理によりる使用メモリの削減など、軽快で安定した操作を行うことができます。

・マルチプラットフォーム
WildFryはJavaで書かれているため、JDKが対応している様々なプラットフォーム上(Linux,Windowsなど)で動作をさせることが可能です。

・カスタマイズが容易
必要に応じてプラグインサブシステムの追加や削除が行えるようになっており、カスタマイズが容易にすることができます。

・管理機能が強力
WEBベースの管理アプリケーションやCLIによって、強力な管理機能を実現しています。

・Jarのモジュラー化
Jarファイルのモジュラー化により、アプリケーションから必要に応じて使用することで、依存性を明確化することが可能にしています。

## 開発環境について
WildFryはEclipseで動かすことができます。
以下のURLからインストールを使ってみてください。
https://www.wildfly.org/downloads/

以上になります。
詰まったエラーと解決方法

最近遭遇したエラー2つとその解決方法です

【エラー①】
```
Error (Xcode): File not found: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphonesimulator.a
```

上記のエラーが出て、ビルドできず。

`Podfileにconfig.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'`を1行追記したら、ようやくビルドが通りました。

```Podfile
post_install do |installer|
      /// その他の設定
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
　　　 /// その他の設定
      end
    end
  end
```

原因が分からず、同じエラーで詰まった方の助けになれば幸いです。

参考にさせて頂いた記事
https://zenn.dev/ischca/articles/a00c3adf626ea0



【エラー②】
```
1.24.0/lib/src/firebase_app.dart:18:25: Error: Member not found: 'FirebaseAppPlatform.verifyExtends'.
        FirebaseAppPlatform.verifyExtends(_delegate);
```

上記のエラーが出て、ビルドできず。

pubspec.yamlに下記を追加したら、ようやくビルドが通りました。

```pubspec.yaml
dependency_overrides: 
  firebase_auth_platform_interface: 6.7.0
  firebase_core_platform_interface: 4.5.1
```


参考にさせて頂いた記事
https://qiita.com/mogmet/items/4bd48bce39ad8f265c72
CSSの `rgba` はもう古い

さっき[MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb)を読んでてびっくりしたんだけど、 `rgba` の記法はもう古い(legacy)らしい。

> Note: The legacy rgba() syntax is an alias for rgb(), accepting the same parameters and behaving in the same way.

`rgb` 記法でアルファも指定できるから、 `rgba` はもういらないよってことらしい。

さらに驚愕の事実。
`rgb` 記法であっても、カンマ区切りでパラメータを並べるスタイルは古いらしい...。

> Legacy syntax: comma-separated values

例えば、 `rgb(255, 0, 0, 0.5)` ってやつね。これは古い。

イマドキのやり方は `rgb(255 0 0 / 0.5)` のようにスペース区切りにするらしい。
alphaの指定の前には `/` がいるんだって。へ〜。

さらにさらに、rgbの各値には整数以外に、 `50%` などの指定もできるらしい。

よって、まとめると、ナウなヤングは↓のように書く(MDNから引用)。

- `rgb(31 120 50)`
- `rgb(30% 20% 50%)`
- `rgb(255 122 127 / 80%)`
- `rgb(255 122 127 / .2)`

2023-10-07、ちょっとハマったので追記。
整数表記とパーセント表記は混ぜれないらしい。
例えば、 `rgb(0 0 100%)` とかはダメ。

MDNのFormal syntaxの欄をよく読むと、たしかに混ぜれないようになってる↓。

```plaintext
<rgb()> = 
  rgb( [ <percentage> | none ]{3} [ / [ <alpha-value> | none ] ]? )  |
  rgb( [ <number> | none ]{3} [ / [ <alpha-value> | none ] ]? )  |
  rgb( <percentage>#{3} , <alpha-value>? )            |
  rgb( <number>#{3} , <alpha-value>? )                |
  rgb( [ <percentage> | none ]{3} [ / [ <alpha-value> | none ] ]? )  |
  rgb( [ <number> | none ]{3} [ / [ <alpha-value> | none ] ]? )  
```

`[ <percentage> | <number> | none ]{3}` とかにはなってないね。気をつけよう。

## 余談1: `.` から始める数値の書き方

見慣れねえなと思ったけど、[仕様書](https://w3c.github.io/csswg-drafts/css-values-4/#number-value)に記載があった。正式な書き方らしい。

> When written literally, a number is either an integer, or zero or more decimal digits followed by a dot (.) followed by one or more decimal digits;

『zero or more decimal digits followed by a dot』って言ってるので、
`.` の前の数字は0個(省略)でも良いようだ。

## 余談2: Qiitaで `rgb` 記法を書くとカラーマーカが付与される

多分、この記事を見てる人は、 `rgb` 記法の右隣りに色のついた四角(カラーマーカ)が見えてると思う。
これはQiitaがMarkdown中の `rgb` 記法を見つけて勝手につけてくれるらしい。さすが、やるな。
GKEまとめ

- Dockerとかコンテナとかについての記載はせず、GKEについての記載のみする（あくまで自分のメモ程度）
    - 気が向いたらDockerとかk8sについての記事も書こうかな・・・

# GKE
- k8sをGoogleCloudで簡単に使えるようにしたサービス
- 2種類のモードがある
    - 標準モード（standard）
    - Autopilotモード

## standardモード
### 料金
- クラスタの管理料金
- ノードの管理料金

### 特徴
- ワーカーノードのリソース情報を含んだ設定を定義しておく必要がある

## Autopilotモード
### 料金
- vCPUの料金
- ポッドメモリの料金
- エフェメラルストレージ（一時的に利用するストレージ）の料金

### 特徴
- ワーカーノードが自動で管理される
    - コンテナが要求するリソース分のワーカーノードが自動で用意される
    - つまり、**作業者がワーカーノードを管理しなくても自動でオートスケーリングを実現させる**

# ワークロード
- ワークロードとは、Kubernetes上で実行中のアプリケーション
- ワークロードが1つのコンポーネントからなる場合でも、複数のコンポーネントが協調して動作する場合でも、KubernetesではそれらはPodの集合として実行される
- Kubernetesでは、Podはクラスター上で実行中のコンテナの集合として表される
- Podには定義されたライフサイクルがある
    - たとえば、一度Podがクラスター上で実行中になると、そのPodが実行中のノード上で深刻な障害が起こったとき、そのノード上のすべてのPodは停止してしまうことになる
    - Kubernetesではそのようなレベルの障害を最終的なものとして扱うため、たとえノードが後で復元したとしても、ユーザーは新しいPodを作成し直す必要がある

引用：https://kubernetes.io/ja/docs/concepts/workloads/

- ワークロードリソースの種類
    - ReplicaSet
    - Deployment
    - DaemonSet
    - StatefulSet

## ReplicaSet
- 期待するPodの数を定義するためのリソース
- アプリケーションの可用性が向上
- Deploymentで内部的にReplicaSetが使われている

## Deployment
- Podの複数レプリカを管理する
- Podの更新やロールバックを簡単に行えるようにできる
- アプリケーションのバージョンアップやロールバックなどの継続的な展開が容易になる

### 覚えときたい設定値
- MaxUnavailable
    - 停止状態になる最大Pod数
- MaxSurge
    - 宣言したPod数を超えて作れるPod数

## StatefulSet
- 各Podに一意な識別子を割り当て、順序付けられた起動やシャットダウン、永続化されたストレージへのアクセスなど、状態を維持する必要があるアプリケーションに適している
- データベース、メッセージングキュー、キャッシュストアなどのアプリケーションに適している
- Stateful: 状態を維持すること

## DaemonSet
- クラスタ内の全てのノードに対して単一のPodをデプロイするリソース
- 全てのノードに対してデーモンプロセスを実行するためのPodを簡単にデプロイできる
- ログ集約、ネットワークデーモン、ストレージデーモンなど、クラスタ全体で実行する必要があるタスクに適している
- Daemon: コンピューター上で常駐するバックグラウンドプロセスのこと
GitHubのwebフックでCodeBuildを動かす場合に見ているブランチを変更したい

# GitHubのwebフックイベントでCodeBuildを動かす際にぶち当たる壁

CodeBuildでデプロイの仕組みを作成するうえで、
「GitHubのPRマージをトリガーとしてCodeBuildを動かす」
という構成にしている方もいらっしゃると思います。

その際、WEBフックイベントで動き出したCodeBuildが見ているのは***マージ先***のブランチではなく、***マージ元***のブランチになります。

```bash: buildspec.yml
version: 0.2
env:
  shell: bash
phases:
  install:
    commands:
      # 関係ないので略
  build:
    commands:
      - npx cdk deploy #ここでdeployが行われるのはマージ元のブランチのコード
```

この仕様のせいで、修正を行っていたブランチ(下図でいうと`feature2`ブランチ)をマージするより前に、マージ先のブランチ(下図でいうと`develop`ブランチ)に変更が入っていた場合、少し面倒なことになります。

```plantuml
@startuml
skinparam sequence {
LifeLineBorderColor #CCCCCC
}
skinparam ParticipantPadding 10
participant feature2
participant feature1
participant develop
hide footbox


||10||
develop ++ #FEE334
||10||
feature2 o<-o develop ++ #EC54B2
||10||
feature1 o<- develop ++ #EC54B2
||10||
?-->o develop
?-->o feature2
?-->o feature1 
||10||
feature1 o->o develop --
||10||

feature2 o->o develop --

@enduml
```

……お察しの通り、`feature1`をマージすることによって適用されていた変更や、そのまま`develop`ブランチに積み重ねていたコミットが打ち消されてしまうんですね。

# 先に解決策

CodeBuildの中でブランチを切り替えちゃいましょう。
ビルドスペックを以下のように書き換えるとうまくいきました。


```bash: buildspec.yml
version: 0.2
env:
  shell: bash
phases:
  install:
    commands:
      # 関係ないので略
  pre_build:
    commands:
      - git switch develop
  build:
    commands:
      - npx cdk deploy #developブランチのコードがデプロイされます！
```

デプロイコマンドの前に`switch`すればいいいんですね。

# 回り道

以下は解決策にたどりつくための試行錯誤の過程です。

`swicth`する前に`fetch`しないとダメかもなと思っていたので、
```bash: buildspec.yml
      # 関係ないので略
  pre_build:
    commands:
      - git fetch #ここでエラーが起きてビルドが落ちます
      - git switch develop 
  build:
    commands:
      - npx cdk deploy　
```
と書いてたんですが、
こうすると
`PRE_BUILD: COMMAND_EXECUTION_ERROR: Error while executing command: git fetch. Reason: exit status 128`というエラーが出てしまいました。

理由としてはCodeBuildにはGitHubのリポジトリに対してアクセス権限が無いということなんでしょうね。

`fetch`しなくても該当リポジトリのブランチの情報はデフォルトでCodeBuild内に持ってきていることが分かったので、
先ほどのビルドスペックに落ち着きました。
FlutterError: Unable to load asset　解決策

FlutterError: Unable to load asset

pubsepec.yamlファイルはPythonなどのようにインデントを使い階層構造を表現するようです。またこの際タブは使えずスペースでインデントを表現します。
詳しくは<a href="https://qiita.com/laddy/items/2a9526202178fbe8bdc3">こちら<a/>の記事を参照されてください。

```pubspec.yaml
# To add assets to your application, add an assets section, like this:
  assets:
    - images/
```
このようにassetの前にスペース二個分のインデントがないとエラーとなるようでした。
Node.jsの用語についてストックしていきたい

## 概要
職務上でNode.jsを使用する機会が増えてきたが、完全素人の何それ美味しいの状態なので、早い段階から整理していくことにする。
※学習記録用なので随時更新します

.
【参考資料】
・[Node.jsとはなにか？なぜみんな使っているのか？](https://qiita.com/non_cal/items/a8fee0b7ad96e67713eb)


### 1. モジュール
機能ごとに分割された小さなコードの単位で、JavaScriptファイルやファイルをまとめたフォルダを指す。

### 2. 内部モジュール
Node.jsをインストールした時に自動でついてくる標準的なモジュールのこと
（例：fs、http）

### 3. 外部モジュール
npmなどのパッケージ管理ツールを使ってインストールすることができるモジュールのこと（例：gulp）


### 4. パッケージ
外部モジュールのこと。JQueryやGSAPとかのライブラリと似たようなニュアンス

### 5. npm
パッケージ管理（インストールなど）を実施するツールのこと。
（MacにおけるHomebrewと同類）

### 6. アセット
Webアプリケーションで利用されるあらゆるファイルのこと
（HTML、CSS、JavaScript、画像、動画、音声など）

### 7. バンドル
複数のファイルを一つにまとめること。アセットやモジュールをまとめたJavaScriptファイルを意味する。

### 8. タスクランナー
sassのコンパイル、画像の圧縮など、開発タスクを自動化するためのツール。ファイルの変更を監視し、変更があった場合に自動的にタスクを実行するといった動きを実現できる。
オブジェクトのクローンにJSON化を使う時代は終わった！【JavaScript】

# 結論
これからのオブジェクトのディープコピーには[`structuredClone`](https://developer.mozilla.org/ja/docs/Web/API/structuredClone)関数を使おう。

```javascript
const obj = { hoge: [new Date(2023, 3, 12), 1], fuga: 'foo' };

const objClone = structuredClone(obj); // ディープコピー

obj.hoge[1] = 10;
console.log(objClone.hoge[1]); // 1

objClone.hoge[0].setFullYear(2050);
console.log(obj.hoge[0].getFullYear()); // 2023
```

# 背景
JavaScriptのオブジェクトや配列の代入はシャローコピーです。

```javascript
const arr = [0, 1, 2];
const arr2 = arr; // シャローコピー

arr[0] = 10;
console.log(arr2[0]); // 10
```

`arr2`は`arr`のシャローコピーであり実際には同じ配列を指しているので、`arr`に対する変更は`arr2`からも反映されて見えることになります。

上の例をディープコピー、つまり`arr`に対する変更で`arr2`が変化しないようにするには`const arr2 = arr.slice();`とでもすれば良いです。
それなら別に大変では無いですが、しかしそれは上の例が単純なプリミティブ値の配列だったからです。
オブジェクトや配列が多段にネストされた値全体をディープコピーするのは大変です。

```javascript
const arr = [{ hoge: [0] }, { hoge: [1] }, { hoge: [2] }];
const arr2 = arr.map(x => ({ hoge: x.hoge.slice() })); // ディープコピー

arr[0].hoge[0] = 10;
console.log(arr2[0].hoge[0]); // 0
```

そして何より上記のやり方だと値の構造に合わせてディープコピーのコードを変更しなければなりません。

そこでプレーンオブジェクトと配列とプリミティブ値の組み合わせからなる値をディープコピーする汎用的な方法としてJSON化をする方法があります。

# 従来の方法
これまでオブジェクトや配列全体をディープコピーするには一旦JSON化する方法がよく使われていました。

```javascript
const objClone = JSON.parse(JSON.stringify(obj));
```

しかしこの方法には以下のデメリットがありました。
- 数値、文字列、配列、プレーンなオブジェクト以外の値はクローンできない
  - `Date`型や`Map`型等はクローンできない
- 処理時間的にも使用メモリ量的にも非効率
- 再帰的な構造があると使えない

# `structuredClone`
`structuredClone`関数はディープコピーを行うために作られた専用関数です。これを使えば従来のJSON化を使った方法のデメリットが解消できます。

JSON化を使った方法と比べた利点は以下です。
- より多くの型がクローン可能
  - [クローンできる型の一覧](https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#%E5%AF%BE%E5%BF%9C%E6%B8%88%E3%81%BF%E3%81%AE%E5%9E%8B)
- 処理時間的にも使用メモリ量的にも効率的
- 再起的な構造があっても動作する

`structuredClone`関数は主要なブラウザ全てで利用可能です。

https://caniuse.com/mdn-api_structuredclone

使い方は[結論](#結論)のサンプルコードを参照。
転職を契機にQiitaを始めることにします

# 転職しました
 １４年強務めていた会社を退職し、折角なのでと言うことで色々な環境を変えることにしました。
Qiitaもその一環で始めることにしました。

:::note info
 GitHubも使っていくのです。文明開化やで！
:::

# 何を書くの？
　Qiita初心者な私は考えました。
そして出た結論は、 __何書いたら良いか分からないならChatGPTに助けてもらえば良いじゃない__ と言うことです。
　ChatGPTはプログラムですら、えいやで済ませちゃうって噂の**商売敵**ですね。
　~~そんなに頭がいいならワイのQiitaもえいやで書いてくれ。~~

# ネタの検討会
　ChatGPT君と何度か記事の内容について議論しました。
![chatgpt_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/28a84d56-c09e-a648-2229-4763b25f372a.jpeg)
![chatgpt_2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/5372cbd5-2169-bc07-1159-2c89471a3b55.jpeg)
![chatgpt_3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/5d8ce3b7-872d-ab8c-ef6a-3d48ce1e0c91.jpeg)
![chatgpt_4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/4c72ac0d-f463-8284-711f-a5e9f5d9681b.jpeg)
![chatgpt_6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/b272a67f-160a-d68d-6778-1f962a3b9577.jpeg)
![chatgpt_7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3322151/1e31f1c1-e331-4420-758b-b59d8a4cf4b3.jpeg)
　最終的にChatGPT君の提案は私の納得いく、訳のわからない提案となりました。おふざけとリアリティのいい感じな共存はまだまだ難しいのかもしれません。
　使い方的には続けて話しかけることで設定の肉付けを増やしていくイメージですね。
　私が利用したことでChatGPT君が何かしらのユーモアを学んでくれれば幸いです。

# 結局何を選んだのか
　「転職後にわかったこと：職場に謎の猫がいた！」
　職場に段ボールに入った猫がおりましてね。人語を解すのです。
　SlackのスタンプやLINEスタンプにもなっておりましてね。
　スライド資料にも当然のごとく登場しているんですよ。
　いつか私のQiitaに登場させることは出来るんでしょうか。
　~~コンプライアンスｅラーニングを受けたばかりで絶賛警戒中で、ビビっています。~~
　諸先輩方、何か会社のマスコット的な猫っぽいキャラクターをQiitaに載せている人を見かけませんでしたか！？
AWS ECS へのデプロイでコンテナのヘルスチェック ( curl ) が失敗し続ける

# 問題

Terraformでいうとこんなタスク定義
単純に curl しているだけなのだが何故か失敗し続ける

```
  "healthCheck": {
    "command": [
      "CMD-SHELL",
      "curl -f http://127.0.0.1:80/ || exit 1"
    ],
  }
```

# 解決

docker container に curl がインストールされていないのが原因だった
Dockerfile で インストールするようにしたら成功するようになった

コンテナのヘルスチェックはコンテナ本体から実行されるようだ

あとから分かってみれば自明の理なのだがしばらく気づかなかった

まずはlocalでdocker containerに接続して、ヘルスチェックそのままのコマンドを実行して試すと良いようだ

`curl -f http://127.0.0.1:80/ || exit 1`

# 参考

>解決方法
>Amazon ECS のコンテナヘルスチェックの失敗をトラブルシューティングするには、以下のヒントを試してください。
>
>Amazon ECS にプロビジョニングする前に、コンテナをローカルでテストして、コンテナヘルスチェックに合格することを確認します。
>コンテナに渡すコマンドが正しく、[Amazon ECS タスクに正しい構文を使用していること](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html#API_HealthCheck_Contents)を確認します。


https://repost.aws/ja/knowledge-center/ecs-task-container-health-check-failures


# チャットメンバー募集


何か質問、悩み事、相談などあればLINEオープンチャットもご利用ください。

https://line.me/ti/g2/eEPltQ6Tzh3pYAZV8JXKZqc7PJ6L0rpm573dcQ


# Twitter

https://twitter.com/YumaInaura
for...in...は使わない

個人的な考えです。
僕は、for...in...文を使わずに、for...of...文で代替しています。

```js
const obj = { a:1,b:2 };

for(const [key,value] of Object.entries(obj)){
    
    console.log(key,value);

}
```
理由は二つあります。

① 単純に、for...in...文でobj[key]とするのが面倒だから。
② Object.hasOwnPropaty()を使用しないと、プロトタイプチェーン内も対象としてしまうから。

懸念点は、Object.entries(obj)によるパフォーマンス低下ですが、現時点では特に気にせずに使用しています。
【Python自分用メモ】例外処理まとめ

## はじめに
Pythonの例外処理について本で読んだ内容をまとめました。

## try文のいろいろな書き方

try節の中で例外が発生した場合、try節から抜けてexcept節に移る。
発生した例外に一致するexcept節の文を実行後にtry文の外側に移る。
```python:try文(一つの例外処理)
try:
    (例外が発生する可能性のある処理)
except 対象の例外:
    (指定した例外が発生した場合に実行する処理)
```


try文ではexcept節に複数の例外を指定可能。
```python:try文(複数の例外を処理1)
try:
    (例外が発生する可能性のある処理)
except 対象の例外A:
    (例外Aが発生した場合に実行する処理)
except 対象の例外B:
    (例外Bが発生した場合に実行する処理)
```

```python:try文(複数の例外を処理2)
try:
    (例外が発生する可能性のある処理)
except (例外1, 例外2, ...):
    (対象の例外のいずれかが発生した場合に実行する処理)
```
except節に例外を指定しない場合、全ての例外がそのexcept節で処理される。

```python:try文(全ての例外を処理)
try:
    (例外が発生する可能性のある処理)
except:
    (例外が発生した場合に実行する処理)
```

Except節は例外で指定した例外を処理するだけでなく、**指定した例外を基底クラスとする例外**も処理する。

例えば、**Exception**という例外クラスはほぼ全ての例外の基底クラスのため、
except節でExceptionを指定することで、その節でほぼ全ての例外を処理することができる。

```python:try文(Exceptionを処理)
try:
    (例外が発生する可能性のある処理)
except Exception:
    (Exceptionが発生した場合に実行する処理)
```

例外発生時は例外クラスのオブジェクトが生成されており、except節でこのオブジェクトを受け取ることが可能。
```python:try文(except節で例外オブジェクトを取得)
try:
    (例外が発生する可能性のある処理)
except 例外 as 変数:
    (指定した例外が発生した場合に実行する処理)
    (指定した変数に例外オブジェクトが代入されている)
```

except節の中にpass文を書くと、例外が発生したときに何も行わない。
⇨例外の隠蔽になるので注意が必要
```python:try文(例外の隠蔽)
try:
    (例外が発生する可能性のある処理)
except 例外:
    pass
```

finally節に書いた処理は、例外が発生してもしなくても実行される。
```python:try文(finally節の使用)
try:
    (例外が発生する可能性のある処理)
except 例外:
   (指定した例外が発生した場合に実行する処理)
finally:
    (例外が発生してもしなくても実行する処理) 
```

else節に書いた処理は、例外が発生しなかった場合に実行される。
```python:try文(else節の使用)
try:
    (例外が発生する可能性のある処理)
except 例外:
    (指定した例外が発生した場合に実行する処理)
else:
    (例外が発生しなかった場合に実行する処理)
finally:
    (例外が発生してもしなくても実行する処理) 
```

## raise文
raise文を使用すると、例外を発生させることができる。

```python:raise文
raise 例外
```
except節の中でraise文を使用すると次のことができる。
- 例外を他のexcept節に再送出する
- 処理中の例外を原因として他の例外を発生させる

つまりexcept節でraise文を使用することで、あるexcept節内で例外の処理を完結させず、他のexcept節に残りの処理を委ねることができる。
```python:try文(except節内で処理中の例外を再送出)
try:
    (例外が発生する可能性のある処理)
except 例外:
   raise
except:
   (再送出された例外の処理)
```
```python:try文(except節内で例外Aを原因とする例外Bの発生)
try:
    (例外が発生する可能性のある処理)
except 例外A:
   raise 例外B from 例外A
except 例外B:
   (例外Aのexcept節で発生した例外Bの処理)
```

## 参考書籍
『Python[完全]入門』 松浦健一郎 司ゆき 著 (2021年3月29日発行)
ストワで始めるLua 第二回 のコード

動画はこちら
https://www.nicovideo.jp/watch/sm42039290
https://www.nicovideo.jp/watch/sm42039301

if文を使ってタッチパネルのスイッチを作る

```Lua:Lua2.lua

--initialize
selectFlg=false
isTouching=false

function onTick()
	local w,h,x,y
	--input
	battery=input.getNumber(10)*100
	max=100
	
	isTouch=input.getBool(1)
	w=input.getNumber(3)
	h=input.getNumber(4)
	x=input.getNumber(3)
	y=input.getNumber(4)
	
	--button
	if isTouch==true and isTouching==false then
		isTouching=true
		if x>=0 and x<w and y>=0 and y<=6 then
			selectFlg=not(selectFlg)
		end		
	end
	
	if isTouch==false then
		isTouching=false
	end
	--output
end

function onDraw()
	--display
	local w,h,i
	w=screen.getWidth()
	h=screen.getHeight()
	
	--on/off
	if selectFlg then
		screen.setColor(128,128,128)
		screen.drawRectF(0,0,w,6)
	end

	--battery value
	if battery>=30 then
		screen.setColor(0,255,0)
	elseif battery>10 then
		screen.setColor(255,255,0)
	else
		screen.setColor(255,0,0)
	end
	
	screen.drawText(0,0,string.format("%.0f/%.0f",battery,max))
	
	screen.setColor(0,255,0)
	for i=6,h,6 do
		screen.drawLine(0,i,w,i)
	end
end
```
【exe化】Python環境構築無しでPythonスクリプトを実行する方法

## はじめに
会社であるアプリケーションを誰にでも使えるように配布するため、exe化することになりました。
今回は、公式のマニュアルと下記の記事を参考に、
- exe化の簡単な説明
- "Pyinstaller"を使用した実行方法

を記載いたしました。

参考
[Pyinstaller公式マニュアル](https://pyinstaller.org/en/stable/)
[(TECH PLAY)PythonのスクリプトをPyinstallerでexe化する方法](https://techplay.jp/column/1646)

## Pythonスクリプトのexe化
Python環境を構築をしなくてもプログラムを実行可能にするもの。

## 仕組み
Python アプリケーションとそのすべての依存関係を単一のパッケージにバンドルする。
## 注意点
 - 64bit OS環境で作成したexeファイルは32bitOSで利用不可。
 - Python 3.7 以降をサポート
 - 作成したexeファイルは、他のOSで実行できない

## 手順
①Pyinstallerのインストール
> pip install pyinstaller

②作業フォルダに移動
> cd ○○

③実行
> pyinstaller ＜Pythonファイル名＞　--onefile

④distフォルダの中にexeファイルができる（exe化完了）

## おわりに
exe化とても簡単ですね。これだけでアプリケーションを配布できるのは便利です。
余談だけれども、「エグゼ化」って口頭で聞いただけだと、なんかすごくかっこよい。

閲覧ありがとうございました。
WSLでubuntuを使うための準備いろいろ


前の会社にいたイケイケエンジニアは、Macbook片手にAWSでコンテナとか触ってて、「イケてるぅ～」なんて思ったが、いざ家電量販店に行ってMacbookの価格を見るとお高い・・・。買えないわけではない。ただ、これなら自作とか企業向けの型落ちPCを改造したほうが安価で高性能だろ？と、考えてしまう。
特にデザインとかCGを趣味でやるわけでもないので、なかなか買う機会がない。ただ、パッケージマネージャのbrewは素敵だと思う。

というわけで、WindowsでLinuxも触れるようになって久しいので、WSLを使う。

# 環境
以下。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/4b478df2-f336-1be0-f9c7-6e48fb5735fc.png)

# 前提
- Windowsがインターネットに接続できていること

# やり方
## WSL準備
- Powershellを開く
<br>

- powershell：WSL有効化（実施後、OS再起動）
```powershell:Powershell
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
```

- powershell：Win10にWSLインストール
```powershell:Powershell
wsl --install
```


- powershell：WSLインストール可能ディストリビューション確認
```powershell:Powershell
wsl --list --online
```

- powershell：WSLにてUbuntu 20.04をインストール
```powershell:Powershell
wsl --install -d Ubuntu-20.04
```

- WSLコンソールより、Ubuntuへログインしてユーザ名とパスワードを設定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/8cece3db-5777-5319-6cc5-867f7f5ec9d7.png)

- powershell：WSLリスト表示
```powershell:Powershell
wsl -l -v
wsl --list --verbose
```

- 既定の Linux ディストリビューションをUbuntu-20.04に設定する
```powershell:Powershell
wsl --set-default Ubuntu-20.04
```

## Ubuntu準備
- powershell：WSL実行して、Ubuntuのシェルセッションにログイン
```powershell:Powershell
wsl
```

- powershell(Ubuntu)：sudoユーザーへ追加
```shell-session:Powershell(Ubuntu-20.04)
$ sudo gpasswd -a 作成したユーザ名 sudo
```

- powershell(Ubuntu)：Ubuntuアップデート
```shell-session:Powershell(Ubuntu-20.04)
$ sudo apt update && sudo apt upgrade
```

- powershell(Ubuntu)：サインアウト
```shell-session:Powershell(Ubuntu-20.04)
exit
```

## Ubuntuバックアップ
- WSL　Ubuntuバックアップ
```powershell:Powershell
$filetimestamp = Get-Date -Format "yyyy-MMdd-HHmmss"
wsl --export Ubuntu-20.04 C:\Users\USERNAME\Documents\WSL2backup\Ubuntu-20.04\Ubuntu_$filetimestamp.tar
```
`C:\Users\USERNAME\Documents\WSL2backup\Ubuntu-20.04\`は任意ディレクトリに変換する。

# 参考
- WSLのバックアップとインポート

https://blog.takeyuweb.co.jp/entry/2020/04/10/100000

- Windows 10/11でWSL環境を移行する方法

https://techracho.bpsinc.jp/morimorihoge/2021_11_26/113804

# 余談
みなさん、心のジャスティスは持ってますか？
時は選挙の季節。神奈川県民は知事選となります。
そんな中、こんなニュースが。

https://bunshun.jp/articles/-/61913

振り上げた拳、振り上げたジャスティスをどう降ろせばいいのか、わからなくなるニュース。
みんなそれぞれのジャスティスを胸に秘め、選挙に行こう・・・！（他意はないです）。

~~税金返してくれねえかな・・・~~
C＃を初めて触る人

# 初めて触るC# 今までの概念と違いすぎてわからない
#### 躓くところ
今まで、pythonばかり扱ってきたせいで、考えずにやってきたことを、プログラムしないといけなくなって、めっちゃ大変。pythonを一番最初にpyhtonをやったせいで、他の言語が難しすぎると思ってしまう。

#### 備忘録
###### 超基礎
```C#
Console.WriteLine("Hello world");//文字を表示するプログラム
var name = "user";//変数の宣言はvarを使用する
```
###### データ型について
```C#
int number = 100;//数字 intを使う
string text = "テキスト";//テキスト　strを使う
var //型推論なので、データ型がわかりにくい
```
###### if文の書き方
```C#
string text = "Qiita";
if (text == "Qiita"){
    Console.WriteLine("Qiita");
}
else{
    Console.WriteLine("NoQiita");
}
```
###### PCのユーザーフォルダを出力する
```C#
Console.WriteLine("UserName:{0}",Environment.UserName);
```
#### 結論 
###### C#は細かく指定できることでもっと詳しくなれそう！
今まで触ってきたPythonに比べてより一層詳しくなれそうですね！
特に、"Hello world"を出力するのは、めっちゃ簡単なpythonとC#の絶妙さが、たまりません。
まだ勉強初日ですが、これから一生懸命C#を勉強していきたいと思います。

#### 比較

###### Python
``` python
print("Hello World")
```
###### C#
```C#
Console.WriteLine("Hello world");
```
難しさが、全然違いますよねw

# 続き書く気になったら書いていきます。　（公開すんなｗ）
Windows x Pyenv 環境構築

# 環境
OS:Windows11 (10でも同じ手順でOKなはず)

# おおまか手順 （当記事はpipでのやり方）
1.Pythonインストール
2.pyenv-winインストール
3.環境変数の設定
4.pyenvでPythonをインストール
5.global(デフォルト)で使用するPython指定

# 各手順

### 1.Pythonインストール
入れていればスルー

入れていない方は下記手順を順に行う

以下ダウンロードサイト
https://www.python.org/downloads/

「Downloads」⇒ 「Windows」(対象OS選択)

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906162/731f0120-d665-0450-3e78-2ab6f58162a3.png)

バージョンは3.9当たりが安心なのでそこらへん選択
(今回は,3.9.13をインストール)
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906162/1e86eda7-7131-6d17-e01f-e267b6e7ecab.png)

インストールしたexeファイルを起動すると,以下画面。
![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906162/03de3074-e4b1-f656-63ba-de979225e44b.png)

「Add Python 3.9 to PATH」にチェックを入れて進む![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906162/a42ba2f3-c7a7-a658-8728-8b73bcf49d66.png)

ちょっと待てば
インストール完了します

一応コマンドプロンプトからPythonのバージョン確認
```
python -V
```

### 2.pyenv-winインストール
PowerShellを起動
![5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906162/75b08fbb-5f46-99b0-a0a7-c9d23ad4f658.png)

PowerShell内で以下コマンド実行

```
pip install pyenv-win --target $HOME\.pyenv
```

インストール完了

### 3.環境変数の設定
引き続きPowerShell内で以下コマンド実行

```
[System.Environment]::SetEnvironmentVariable('PYENV',$env:USERPROFILE + "\.pyenv\pyenv-win\","User")

[System.Environment]::SetEnvironmentVariable('PYENV_ROOT',$env:USERPROFILE + "\.pyenv\pyenv-win\","User")

[System.Environment]::SetEnvironmentVariable('PYENV_HOME',$env:USERPROFILE + "\.pyenv\pyenv-win\","User")

[System.Environment]::SetEnvironmentVariable('path', $env:USERPROFILE + "\.pyenv\pyenv-win\bin;" + $env:USERPROFILE + "\.pyenv\pyenv-win\shims;" + [System.Environment]::GetEnvironmentVariable('path', "User"),"User")
```

変更を反映させるために
PowerShell落とす

今度はコマンドプロンプトを開く
![6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906162/6e1abff8-82fd-5fec-d888-29b880d1ee98.png)

コマンドプロンプト内で下記コマンド実行して
pyenvコマンドが反応するか確認

```
pyenv --version

# 下記のようなアウトプットでればOK(数字部は変わります)
pyenv 3.1.1
```

### 4.pyenvでPythonをインストール

pyenvからPythonをインストール(今回は3.9.6)
```
pyenv install 3.9.6
```

ちなみに
インストール可能なPythonのバージョンは以下で確認できる
```
pyenv install --list
```

### 5.global(デフォルト)で使用するPython指定

globalで使用するPythonのバージョンを設定
(今回は3.9.6に変えたいので以下)

```
pyenv global 3.9.6
```

Pythonのバージョンが切り替わっていればOK
以下で確認

```
python -V
# 出力結果が以下のようにpyenvでインストールしたバージョンに変わっていればOK
3.9.6
```

# 補足
別仮想環境作成時は
「pyenvからpythonインストール」から行えばOK

# 参考にさせて頂いたサイト

https://qiita.com/probabilityhill/items/9a22f395a1e93206c846
https://zenn.dev/yusen_ran/scraps/5f22b7cc8e57a2
自分がいつもしているエラーが出た時の対応

## 前書き 
プログラムを組んでいるとどうしても対応しないといけないエラー
プログラマーにとっては日常茶飯事のごとく出てきます。
その場合自分はだいたいどうしてるかなーというのを備忘録的に残していこう

ってやつです

## 明示的なエラー文言が出る場合
明示的なエラーのパターンで言えば
- 実行時にFatalErrorがLogで出た
- WarningText（非推奨メソッド使用など）が出ている
- 実機実行時に落ちた。

など

1. IDEのコード補完機能で修正をする
Typoの場合、もしくは引数が足りないなどの場合のエラーなど
XcodeやAndroidStudio、VisualStudioなどの場合などはマウスをオーバーするだけで、修正内容候補をコードヒントとして出してくれることは多いので
それを確認してほしい動作を確認して修正するといいでしょう。

1. ReasonもしくはError内容をそのままコピーして、Google検索などで検索をしてみる
〜〜クラス、〜〜メソッド、エラー文言
で検索すると同様のエラーが出ているような人が居る場合には出てくることが多いです
QiitaやStack Overflowなどで同様のエラーを起こしている人を見つけれたら、それを参考に修正をしてみるといいでしょう。

1. 公式サイトのドキュメントを読む
よくあるのは使うメソッドが間違っている、必要な引数がが足りてないなどが起こり得るのでそれのチェックを行う。
基本的には英語なので頑張って読むか、翻訳にかけたりをして読むことが多いです。

1. 質問をしてみる
公式に質問用のオペレーターが居るなどのSDKであれば聞いたほうが早いことが多いです。
Stack Overflowで聞いてみるのも可、ただしすぐに答えが帰ってくることは稀なので、数週間後とかになることはあります。
日本向けであればteratailなども選択肢かな？

1. ChatGPTに聞いてみる
ChatGPT-4になってから普通に正解のコードもらえたりなどもします。
ただ、コンプライアンスなどの問題でコードの全文を見てもらうなどは難しいので
例えば〜〜クラスの〜〜メソッドを使った時に〜〜のエラーが出たなどの聞き方をすると正解がもらえることもあります。

## 明示的に出なく動作がおかしな場合
明示的ではないパターンは

- ボタンを押した時などに、望んでいる動作をしなかった場合
- 動作が完全に止まってしまった場合
- ビルドした後のアプリで望んだ動きをしていないなど

など

1. Debug.Log()、Console.log()などで動作を1から順に追ってみる
ボタン押した後、その後の処理、処理開始時、処理終了時、エラー時などのすべてにLogを入れて
望んだ動作の進みをしているかの確認＋処理データが正しいのか確認を行う。

1. ErrorLogでエラー内容を確認して、Google検索などで検索をしてみる
アプリもしくはサーバーなどでLog排出先がある場合
エラーのログが出ているところをGoogle検索などで検索してみる
明示的なエラーが出る場合と同様に、QiitaやStack Overflowなどで同様なエラーがあった場合、それを参考に修正などをします。

1. イベントビューアーでError内容を確認してみる(Windows系アプリの場合)
イベントビューアーを起動し該当アプリで絞ったり、エラーが起きた日時でエラー内容を確認する。
これも同様にエラー文言、WARNINGメッセージをGoogle検索などで検索すると答えが出る場合があります。

## あとがき
一旦、思いついた限りの対応を書いてみました
俺は、私はこれするよ！みたいなのあればコメントいただけるとありがたいです。
WindowsMachineFileCopyタスクでFailed to Create PSDrive with Destination: '\\xx.xx.xx.x\D$\xxx', ErrorMessage: 'The network path was not found'

# 概要
Azure DevOpsのPipelinesを利用しWindowsOSのVMへ資産を配置するためにWindowsMachineFileCopy@2タスクを利用したところ、ネットワークパスが見つからないとのエラーが発生しました。

Pipelinesを利用したWindowsVMへの資産の配置方法の詳細については以下参照

https://qiita.com/shingo_kawahara/items/6c7ca38ca7ec32d16a79



今回の構成ではAzure DevOpsでPipelinesを実行する際のエージェント（ジョブを実行するマシン）にAzure Virtual Machine Scale Set（以下、VMSSと記載）を採用していたため通信としては VMSS → VM(os:Windows)となります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293404/08b07760-6baf-1e46-6885-19c6c494020e.png)


NSGやFWなどのネットワーク回りの設定は問題ないことを何度も確認し、なおかつ今回の状況ではProd/Stg/Devの３環境あるうちのDevだけが上記のエラーとなるので環境ごとの差異も確認しましたが解決せずにいました。


# 解決策
原因はまだ判明はしていないのですが、フォルダーのプロパティからフォルダの共有を設定することでエラーが解消できました。

エクスプローラーのディレクトリを右クリックしプロパティを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293404/faee56df-9bc1-a5ed-c4f5-1456c8b5849f.png)


詳細な共有を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293404/e9b2ac00-15c6-9b5f-b865-ca90d9b0031e.png)

このフォルダを共有するにチェックを入れる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293404/608d0b2a-d0ba-ddc6-020a-e48221787341.png)



フォルダの共有設定を継続して設定しておく必要はなく、一度共有設定をONにしてPipelinesを実行。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1293404/5ee3ed1c-82d3-8e58-19e5-902736ddc7b1.png)

PipelinesでのWindowsMachineFileCopyタスクが成功したことを確認できたら、フォルダの共有設定を解除してもその後は問題なくWindowsMachineFileCopyタスクが成功するようになりました。


詳細な原因は不明ですが、本来の設定に戻した上でエラーの回避は出来たためメモとして残しておきます。
[AWS Q&A 365][AppSync]AWSのよくある問題の毎日5選 #24

# 1. AWS AppSyncとは何ですか？
答え：AWS AppSyncは、Amazon Web Servicesが提供するサービスで、さまざまなデータソースに接続してスケーラブルなGraphQL APIを構築することができるものです。

# 2. AWS AppSyncの特徴は何ですか？
答え：AWS AppSyncの特徴には、リアルタイムデータ同期、オフラインデータアクセス、自動スキーマ生成、複数のデータソースのサポートが含まれます。

# 3. AWS AppSyncの価格はいくらですか？
答え：AWS AppSyncの価格は、リクエスト数と転送されたデータ量に依存します。また、新規ユーザー向けに無料のティアがあります。

# 4. AWS AppSyncを使用して最初のGraphQL APIを起動するにはどうすればよいですか？
答え：AWS AppSyncを使用して最初のGraphQL APIを起動するには、開発者ガイドで提供されるクイックスタートガイドに従うことができます。これには、スキーマの作成、データソースの設定、クエリとミューテーションの実行が含まれます。

# 5. AWS AppSyncのアーキテクチャコンセプトは何ですか？
答え：AWS AppSyncのアーキテクチャコンセプトには、GraphQLプロキシ、オペレーション、アクション、データソース、リゾルバ、パイプラインリゾルバ、ユニットリゾルバ、関数、アイデンティティが含まれます。これらのコンセプトは、スケーラブルなGraphQL APIを設計・実装するために使用されます。
【ドラゴンボール風解説】AWS Auto Scalingについて

# はじめに
この記事はAWSの説明は今ひとつよくわからないのでドラゴンボールに例えて説明してほしい。
というAWS初学者向けの記事です。
※投稿記事作成にはchatGPTを使用しております

# AWS Auto Scaling
オッス、オラ悟空！オートスケーリングってのは、魔人ブウのように**自己修復能力を持っているんだぞ**。例えば、戦闘中に攻撃を受けて怪我をしても、すぐに自己修復してまた元気に戦える。それと同じように、オートスケーリングも**自動的にシステムの負荷状況を監視し、必要に応じて自己修復する**んだ。

魔人ブウが体を自由自在に変化させて戦闘に適した形に変えるように、オートスケーリングも**システムの負荷に合わせて自動的に必要なリソースを増減させるんだ**。それによって、システムが滑らかに動き続け、ユーザーに快適な体験を提供することができるんだ。

いいか、オートスケーリングは魔人ブウのように常に最適な状態を保つための秘密兵器だ！
オートスケーリングってすっげー奴だな！ワクワクしてきたぞ！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3304054/27955332-5c6e-eb2f-6faf-9df3301d8fcd.png)
[AWS Q&A 365][AppSync]Daily Five Common Questions #24

# 1. What is AWS AppSync? 
answer: AWS AppSync is a service provided by Amazon Web Services that allows developers to build scalable GraphQL APIs by connecting to various data sources. 

# 2. What are the features of AWS AppSync? 
answer: The features of AWS AppSync include real-time data synchronization, offline data access, automatic schema generation, and support for multiple data sources. 

# 3. How much does AWS AppSync cost? 
answer: The pricing for AWS AppSync depends on the number of requests made and the amount of data transferred. There is also a free tier available for new users. 

# 4. How do I launch my first GraphQL API using AWS AppSync? 
answer: To launch your first GraphQL API using AWS AppSync, you can follow the Quick Start guide provided in the Developer Guide. This involves creating a schema, setting up data sources, and running queries and mutations. 

# 5. What are the architectural concepts of AWS AppSync? 
answer: The architectural concepts of AWS AppSync include GraphQL proxies, operations, actions, data sources, resolvers, pipeline resolvers, unit resolvers, functions, and identities. These concepts are used to design and implement scalable GraphQL APIs.
チャットGDPにチャットGDPの使い方を聞いてみた件について

## チャットGDPにチャットGDPの使い方を聞いてみた結果です。

質問内容は、スクリーンショットで、撮影したものでしたが、最後のこれまでの内容をまとめてくださいという質問を行いました。
結果は、内容は意図したものとは違う的外れなものが出てきてしまいました。

理想：チャットGDPにチャットGDPの使い方を聞いてみた件について
結果チャットGDPにプログラミング初心者向けの学習方法を聞いてみた件について
※まとめられているが、初心者向けのプログラミング学習方法を教えてください。という内容に変わっています。

原因としては、チャットGDPが誤解した可能性もあります。
チャットボットは機械学習に基づいているため、質問内容と回答の関連性を判断することができますが、完璧ではありません。
このことを念頭に置いた上で、利用する必要があると仮定します。



# チャットGDPにチャットGDPの使い方を聞いてみた件について

## **質問**

初心者向けのプログラミング学習方法を教えてください。

## **回答**

初心者向けのプログラミング学習方法には、オンラインの無料学習サイトや書籍、または有料のオンラインコースなどがあります。具体的な方法としては、以下のようなものがあります。

- CodecademyやUdemyなどのオンライン学習サイトで基本的なプログラミング言語を学ぶ
- 初心者向けのプログラミング書籍を読んで学習する
- YouTubeなどの動画サイトでプログラミングのチュートリアル動画を見て学習する
- コミュニティ活動に参加して他のプログラマーと交流する

何を学ぶかによって、学習方法も異なるので、まずはどのプログラミング言語を学びたいかを決めることが重要です。PythonやJavaなどの言語が初心者向けとして人気があります。

また、プログラミング学習は継続的なものであり、毎日少しずつでも学習することが大切です。何かわからないことがあった場合は、オンライン上でのコミュニティ活動やStack OverflowなどのQ&Aサイトを利用して、他のプログラマーに質問することもできます。


# タイトル: 「チャットGDPにプログラミング初心者向けの学習方法を聞いてみた件について」

私は、プログラミング初心者向けの学習方法について知りたいと思い、チャットGDPに質問をしました。チャットGDPからは、以下のような回答が返ってきました。

プログラミング初心者向けの学習方法には、オンラインの無料学習サイトや書籍、または有料のオンラインコースなどがあります。PythonやJavaなどの言語が初心者向けとして人気があります。プログラミング学習は継続的なものであり、毎日少しずつでも学習することが大切です。何かわからないことがあった場合は、他のプログラマーに質問することもできます。

以上が、マークダウンの例となります。初心者から上級者まで、どのレベルの方でも読みやすいフォーマットとなっています。

また、学習方法には、以下のようなものがあります。

CodecademyやUdemyなどのオンライン学習サイトで基本的なプログラミング言語を学ぶ
初心者向けのプログラミング書籍を読んで学習する
YouTubeなどの動画サイトでプログラミングのチュートリアル動画を見て学習する
コミュニティ活動に参加して他のプログラマーと交流する
そして、何を学ぶかによって、学習方法も異なるので、まずは自分が学びたいプログラミング言語を決めることが重要です。そして、学習には時間と努力が必要であるため、継続的に学習することが大切です。

以上が、私がチャットGDPに質問した「プログラミング初心者向けの学習方法について」の内容と、チャットGDPからの回答の要点です。
# 質問
![スクリーンショット 2023-04-05 14.06.16.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3300625/fea03210-91e1-e859-a0e3-68f31a0005a4.jpeg)
![スクリーンショット 2023-04-05 14.06.23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3300625/5c2a3628-a79c-a9d3-f338-553a64a0d847.jpeg)
![スクリーンショット 2023-04-05 14.06.28.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3300625/782aa5ff-20de-3693-55f5-6419a8e61d66.jpeg)
![スクリーンショット 2023-04-05 14.06.36.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3300625/465539ef-b11f-555e-3bb4-5e0aa8274541.jpeg)
カスタムスクリプトでDOMをいじる

# ◆入力したテキストを引用タグ\<blockquote>で囲う
```html:カスタムスクリプト
<script>
document.addEventListener("DOMContentLoaded", async () => {
if (document.body.dataset.hasCompiledHtml) { return; }

MTBlockEditorSetCompiledHtml(`<blockquote>
${document.body.innerHTML}
</blockquote>`);

});
</script>
```

## MTBlockEditorSetCompiledHtml
MTBlockEditorSetCompiledHtml(html)で、出力するhtmlを指定することができます。


## テンプレートリテラルでDOMを作成
入力内容「${document.body.innerHTML}」をそのまま持ってきて、前後に\<blockquote>を。
```javascript
MTBlockEditorSetCompiledHtml(`
<blockquote>
${document.body.innerHTML}
</blockquote>
`);
```

# ◆入力前のテキストをいじる
```html:カスタムスクリプト
<script>
document.addEventListener("DOMContentLoaded", async () => {
    if (!document.body.dataset.hasCompiledHtml) {
        document.body.textContent = "あああああああ";
        Object.assign(document.body.style, {textAlign:"center",paddingTop:"40px"});
    }
});
</script>
```
ClusterAPIによるKubernetesクラスタの操作

執筆日：2023/04/05

# Cluster APIによるKubernetesクラスタの操作

## 目的

- kubernetesクラスタを構築、アップグレード、その他の操作を簡単に行えるようにすることを目的として開発されている[Cluster API](https://cluster-api.sigs.k8s.io/)を検証していきます。
- kubernetesクラスタを構築する手段としてパブリッククラウドのマネージドサービス（GCPのGKE、AWSのEKSなど）を利用する方法、オンプレミス環境で構築ツールを使う方法（kubeadm、Kubesprayなど）がありますが、マルチクラウドや、オンプレミスを含めたハイブリッド環境でkubernetesクラスタを構築・運用する場合、Kubernetesクラスタの土台となるOS以下のプラットフォームはクラウド、VM、ベアメタルなど多岐に渡り、個別に管理する必要があります。
- Cluster APIは[プラットフォーム毎に用意されたProvider](https://cluster-api.sigs.k8s.io/reference/providers.html)という機構を使うことでプラットフォームの差分を吸収し、kubernetesクラスタを構築・運用することができます。
- 本記事では実際にCluster APIをインストールし、Kubernetesクラスタの構築・運用機能を確認します。

## 環境

- ClusterAPI自体もKubernetes上のPodで動作するため、ClusterAPI用のKubernetesクラスタ（Management Clusterと言う）を構築する。
- ClusterAPIを用いてKubernetesクラスタ（Workload Clusterと言う）をマルチノードで構築する。
- Management Clusterはシングルノード構成とする。
- Workload Clusterはマルチノード構成とする。
    - Controller : 3台
    - worker : 2台
- Workload ClusterはManagement ClusterVMと同一セグメント上にあるVMに構築する。
- Management ClusterのVM諸元は以下の通り
  | 諸元 | パラメータ |
  |------|------------|
  | cpu | 2コア |
  | メモリ | 4GB |
  | disk | 20GB |
  | OS | Ubuntu 20.04.6 LTS |
- Workload ClusterのVM諸元は以下の通り
  | 諸元 | パラメータ |
  |------|------------|
  | cpu | 4コア |
  | メモリ | 4GB |
  | disk | 20GB |
  | OS | Ubuntu 20.04.6 LTS |

- 構成図は以下の通り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2633524/20e95ab7-0dd9-2a1f-ef9f-d3b87bf8a7b4.png)
  - 本記事では環境制約によりManagement ClusterとWorkload Clusterを同セグメントに配置するが、両者が同セグメントである必要は無い。複数のパブリッククラウド、オンプレミスクラウドを制御するためL3やインターネットを跨ぐ構成が可能である。

## 目次

確定しているものを記載し随時更新する。

1. [Management Clusterの構築](https://qiita.com/kikutak/items/c581b3e2518477d8631f)
1. [Workload Clusterの構築](https://qiita.com/kikutak/items/709a77d2621c69691583)
カスタムスクリプトをはじめてみる。

MTブロックエディタを1から作ってみたいと思います。
練習練習。

# カスタムスクリプト
カスタムスクリプト内に、\<style>や\<script>を書くことができるので、
ブロックエディタ上でスタイルやDOMを出力状態にして表示することができます。

## スタイルを指定する
```html
<style>
p { color:#cc0000;}
</style>
```

## JavaScriptの基本形
以下が基本形です。
```html
<script>
document.addEventListener("DOMContentLoaded", async () => {
    if (document.body.dataset.hasCompiledHtml) {
        return;
    }
    // ここに処理したいスクリプトを。
});
</script>
```

### DOM構築完了後に実行
addEventListener を DOMContentLoadedイベント（DOMツリーの構築が完了したとき）を取得。
```javascript
// アロー
document.addEventListener("DOMContentLoaded", async () => {

// function
document.addEventListener('DOMContentLoaded', async function() {
```

### DOM構築完了後に実行
「出力されるHTML」で表示された場合には「document.body.dataset.hasCompiledHtml」の値が真になる。
```javascript
if (document.body.dataset.hasCompiledHtml) {
    return;
}
```
git archiveと.gitginoreされているがgit管理下にあるファイル

# 結論

git管理下にあれば`git archive`に拾われる

# 実験手順と結果

```bash
$mkdir git_archive_test1

$cd git_archive_test1/

$git init
Initialized empty Git repository in /home/yumetodo/git_archive_test1/.git/

$echo "arikitari" > a.txt

$nano Makefile

$cat Makefile
ARCHIVE = test

hash := $(firstword $(shell git ls-remote . HEAD))

$(ARCHIVE).tar.gz: $(ARCHIVE)
tar czf $@ $<

$(ARCHIVE):
        rm -rf $@
        mkdir $@
        git archive $(hash) | tar xf - -C $@
        mv $< $@/$<
        echo $(hash) >$@/COMMIT

$git add .

$git commit -m "init commit"
[main (root-commit) dff4133] init commit
 2 files changed, 14 insertions(+)
 create mode 100644 Makefile
 create mode 100644 a.txt

$echo "a.txt" > .gitignore

$git add .

$git commit -m "ignore"
[main e44f0a5] ignore
 1 file changed, 1 insertion(+)
 create mode 100644 .gitignore

$git log --oneline
e44f0a5 (HEAD -> main) ignore
dff4133 init commit

$make
rm -rf test
mkdir test
git archive e44f0a58ff4240a4fe0983e190f7bf7a551baf27 | tar xf - -C test
echo e44f0a58ff4240a4fe0983e190f7bf7a551baf27 >test/COMMIT
tar czf test.tar.gz test

$ls -la
合計 60K
drwxr-xr-x 1 yumetodo なし   0  4月  5 17:38 ./
drwxr-xr-x 1 yumetodo なし   0  4月  5 17:37 ../
drwxr-xr-x 1 yumetodo なし   0  4月  5 17:38 .git/
-rw-r--r-- 1 yumetodo なし   6  4月  5 17:37 .gitignore
-rw-r--r-- 1 yumetodo なし  10  4月  5 17:37 a.txt
-rw-r--r-- 1 yumetodo なし 210  4月  5 17:37 Makefile
drwxr-xr-x 1 yumetodo なし   0  4月  5 17:38 test/
-rw-r--r-- 1 yumetodo なし 450  4月  5 17:38 test.tar.gz

$ls -la test
合計 8.0K
drwxr-xr-x 1 yumetodo なし   0  4月  5 17:38 ./
drwxr-xr-x 1 yumetodo なし   0  4月  5 17:38 ../
-rw-r--r-- 1 yumetodo なし   6  4月  5 17:37 .gitignore
-rw-r--r-- 1 yumetodo なし  10  4月  5 17:37 a.txt
-rw-r--r-- 1 yumetodo なし  41  4月  5 17:38 COMMIT
-rw-r--r-- 1 yumetodo なし 210  4月  5 17:37 Makefile
```

ignoreしている`a.txt`が確認できた

# 考察

git archiveするときには別に`.gitignore`は見られていない。
TypeScript: ChatGPT Speech to text の使い方 (axios)

こちらと同じことを TypeScript で行いました。
[Node.js: ChatGPT Speech to text の使い方 (axios)](https://qiita.com/ekzemplaro/items/65a3dc95929ee7868fb0)
音源も同じです。

## プログラム

```ts:type_text.ts
// ---------------------------------------------------------------
//	type_text.ts
//
//					Apr/05/2023
//
// ---------------------------------------------------------------
import axios from 'axios'
import FormData from 'form-data'
import fs from 'fs'
import dotenv from 'dotenv'

dotenv.config()
const token = `${process.env.API_KEY}`

const file = 'yonendowa1_08_rappa_niimi.mp3'
const model = 'whisper-1'

const form = new FormData()
form.append('file', fs.createReadStream(file))
form.append('model', model)

axios({
  method: 'post',
  url: 'https://api.openai.com/v1/audio/transcriptions',
  data: form,
  headers: {
    'Content-Type': `multipart/form-data boundary=${form.getBoundary()}`,
    'Authorization': `Bearer ${token}`
  }
})
  .then(response => {
    console.log(response.data)
  })
  .catch(error => {
    console.error(error)
  })

// ---------------------------------------------------------------
```

```text:.env
API_KEY="sk-11******************************************"
```

必要なライブラリーのインストール

```bash
npm install axios
npm install form-data
npm install dotenv
```

## 実行結果

```text
$ ts-node type_text.ts
{
  text: 'LibriVox.orgのために録音されました。 ラッパ おじいさんは田舎に一人ぼっちで暮らしていました。 だんだん耳が遠くなったので、 都にいる息子のところへ 耳が遠くなったから よく聞こえる機械を買って送ってくれ と言ってやりました。 息子はいろいろ探したが あまり良い機械がないので ラッパを買って これを逆さまにして耳につけて ラッパの尻から聞きなさい と言ってやりました。 おじいさんはそのラッパを喜んで使っていましたが 耳はどんどん遠くなって とうとうつんぼになりました。 それでもおじいさんは あのラッパでは聞こえなくなったから もっとよく聞こえるのを 送ってくれと息子に手紙をやりました。 息子はいろいろ考えてから また前のラッパと同じラッパを送って このラッパを耳につけると 私の声が聞こえます と言ってやりました。 おじいさんはそのラッパを耳に当ててみました。 そして ああ、聞こえる、聞こえる 懐かしい息子の声が聞こえる と言って喜びました。 けれど、それはおじいさんの耳が聞いたのではありません 心が聞いたのです。 ラッパ終わり この録音はパブリックドメインです。'
}
```

## 確認したバージョン

```text
$ ts-node --version
v10.9.1
```
WordPressを高速化する

とにかく速いWordPressを読む

https://atmarkit.itmedia.co.jp/ait/series/2117/

PHP7の導入（現在はPHP8へ 2023/04/05)

https://atmarkit.itmedia.co.jp/ait/articles/1604/18/news012.html


HHVMの導入　
KUSANAGI9では使用されなくなったので、割愛

https://atmarkit.itmedia.co.jp/ait/articles/1604/25/news003.html

Nginx を導入する

https://atmarkit.itmedia.co.jp/ait/articles/1605/02/news009.html

「WordPress“1000倍”高速化」チューニング 第五弾──プラグイン
「WP SiteManager」で、ページキャッシュを導入する

https://atmarkit.itmedia.co.jp/ait/articles/1605/10/news001.html


ページキャッシュは、一度アクセスされ、WordPressが生成したHTMLなどを
「一定時間、2回目以降のアクセスで再利用する」ことによって高速化を図る仕組みです。
ページキャッシュには、WordPressのプラグインで実現するもの、
ミドルウェアで実現するものなど幾つかの種類があります。

「NginxのFastCGIキャッシュ」を導入して「1127倍」まで高速化する──
「WordPress“1000倍”高速化」チューニング総仕上げ

https://atmarkit.itmedia.co.jp/ait/articles/1605/16/news003.html

“いきなり1000倍高速”になるWordPress高速化チューニング済み仮想マシン「KUSANAGI」とは何か

https://atmarkit.itmedia.co.jp/ait/articles/1606/23/news015.html

チューニング済みWordPress仮想マシン「KUSANAGI」

KUSANAGIは、本連載の第1回目から第10回まで実践してきた高速化チューニングを
「あらかじめ済ませてある」仮想マシンイメージです


「KUSANAGI」応用テクニック　常時SSLとHTTP/2の導入方法（Let's Encrypt編）

https://atmarkit.itmedia.co.jp/ait/articles/1610/04/news015.html
AWS SES v2 の SendBulkEmail を呼び出すと SUCCESS を返すにも関わらず、メールが送信されない

# はじめに

掲題通り。 SendBulkEmail は Python / boto3 であれば以下のドキュメントで示す通りに利用できる。 ドキュメントを読むと、成功時にはレスポンスとして `"Status": "SUCCESS"` を返すことがわかる。 

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sesv2/client/send_bulk_email.html

今回はここで `SUCCESS` を返しているにも関わらず、正常にメールが送信されない状況に遭遇した。 その調査のため、SES の検証済IDに設定可能な通知の「送信」「バウンス」「苦情」にそれぞれ通知用の SNS トピックを設定した後に同様の内容のメールを送信したが、これらの SNS に対して通知は発生しなかった。

この問題の原因と対応方法を以下に示す。


# 原因と対処

原因は「メール送信時に利用したテンプレートのレンダリングに失敗しているため」であった。

SendBulkEmail でメールを送信する際には SES に設定したテンプレートを利用する。 この時、例えば変数として `{{NAME}}` のような変数が定義でき、かつ、これらの変数を `SendBulkEmail` の呼び出し時に宛先個別に設定できるのだが、これらの変数に対して適切な値を渡していないことがある。 この場合、メール送信のデフォルトパラメータ、および、宛先個別のパラメータの中に `NAME` という変数が存在していない場合にレンダリングに失敗する。

この問題を検知するには以下の手順が必要になる。

- SESの設定セット (Configuration Set) を作成する
    - イベント送信先を設定し、「レンダリングの失敗」時に特定の通知先にアラートを飛ばすように設定する
- 以下のいずれかの手順で上記で作成した設定セットを利用する
    - 利用する検証済 ID のデフォルト設定セットとして設定する
    - SendBulkEmail の実行時に利用する設定セットを指定するようにする

今回はレンダリング失敗時に SNS を経由してサブスクリプション済のメールアドレスに通知が飛ぶようにした。 その結果、以下のような SNS 通知結果を得た。

```json:レンダリング失敗時の通知具体例
{
  "eventType":"Rendering Failure",
  "mail":{"timestamp":"2023-04-05T07:44:28.098Z",
  "source":"********",
  "sourceArn":"arn:aws:ses:ap-northeast-1:************:identity/********",
  "sendingAccountId":"************",
  "messageId":"************************************************************************",
  "destination":["************************************"],
  "headersTruncated":false,
  "tags":{"ses:operation":["SendTemplatedEmail"],
  "ses:configuration-set":["template-debug"],
  "ses:source-ip":["************************************"],
  "ses:from-domain":["************************************"],
  "ses:caller-identity":["************************************************"]}},
  "failure":{
    "errorMessage":"Attribute 'NAME' is not present in the rendering data.",
    "templateName":"************************************************************"
   }
}
```

この通知内の `failure.errorMessage` にエラー理由が記載されている。
この場合 `NAME` がレンダリング用のデータ内に存在しないため失敗した、というエラーメッセージが表示されている。

利用しない変数が誤ってテンプレート内に記載されていないかを注意しておかないと、メール送信のAPIは成功しているにも関わらず、実際のメールが送信されていないという問題が発生し得るため注意する必要がある。

# 参考


https://stackoverflow.com/questions/58119798/aws-ses-service-is-not-sending-bulk-templated-emails-on-success-response-using-b

https://dev.to/nabinadhikari/aws-simple-email-service-caveat-5jn