type holyshared = Engineer<mixed>

技術的なことなど色々

2021年の靴の着用回数を集計した

f:id:holyshared:20220111190201j:plain

毎年恒例の履いた靴の集計をしてみました。
2021年は追加されたのが7足で、手放したのが3足なので増減があんまりなかったようです。

2020年が17足買っていたので、少なく感じます。
相変わらずレザーの靴は買っているようです。

2021年の特徴として、ジョギング用のBROOCKSの靴が増えています。
これは運動のウォーキングに加えて、5kmくらいジョギングすることが増えたので、購入したものです。

来年としては継続して履いているものが増えたので、購入は控えつつ手放すものを吟味していく予定です。
流石に40足あると置き場に困るのでなるべく減らしていきたいです。

以前から履いている靴

No. モデル ブランド 2021年までの着用回数 2021年の着用回数
1 HunterⅡ Rolling dub trio 27 23
2 ASAHI M020 ASAHI 33 27
3 Black Grain 6 Eye Derby Boot SOLOVAIR 30 25
4 LAW NPS shoes 32 25
5 Harry BROTHER BRIDGE 48 29
6 Jackie Ballband 39 27
7 ASAHI M022 ASAHI 41 27
8 6inch Boots Reverse Black Odessa Chippewa 39 25
9 BOYS KATSUYA TOKUNAGA 39 25
10 HI BASKET W MoonStar 45 28
11 GARRISON TRAIL GORE-TEX HIKING SHOES Timberland 39 25
12 CYPRESS Dry Bones 44 24
13 M-43 SERVICE SHOES JOHN LOFGREN 45 24
14 BERLIN BROTHER BRIDGE 52 25
15 CAMERON ESSENTIAL NPS shoes 44 24
16 CONISTON Crockett&Jones 54 25
17 Morgan BROTHER BRIDGE 53 25
18 1930s Sport Boot Redwing 54 24
19 Spider Scotch Grain 52 24
20 Custom Jobmaster 38LTT Wesco 53 25
21 Military Apron Derby Boots Sanders 57 24
22 Enginier Slip on Tokyo Sandals 197 73
23 442 MILITARY CAP TOE OXFORD RAMSEY 81 24
24 DOBULE MONK SANDAL Tokyo Sandals 269 87
25 ROOTS Rolling dub trio 89 29
26 Norwogian Split Pistorelo 67 24
27 Irish Setter Redwing 84 28
28 OB-8593GT 栃木レザープレーンミッドブーツ SLOW WEAR LION 75 25
29 OB-8593G オイルドレザープレーンMIDブーツ SLOW WEAR LION 73 25
30 6inch Service Boots Chippewa 73 24
31 McCLOUD BROTHER BRIDGE 78 25
32 JAMES BROTHER BRIDGE 83 26
33 6inch HotShot Nicks 74 24

新しく購入した靴

No. モデル ブランド 2021年までの着用回数 2021年の着用回数
1 Ghost14 BROOCKS 13 13
2 Overstone Hi Derby Crown Northampton 27 27
3 Neon Blue 8 Eye Derby Boot SOLOVAIR 20 20
4 6Eye Astronaut Boot SOLOVAIR 21 21
5 “ATHLETIC SHOES”HIGH CUT MODEL BUZZ RICKSON'S 21 21
6 Solar Wave Leather/Fabric Mid Hiker Boots Timberland 31 31
7 飛鳥ホールカット KOTOKA 36 36

手放した靴

No. モデル ブランド 2021年までの着用回数 2021年の着用回数
1 Postal 706V2 New Balance 18 7
2 MEXICO 66 Onitsuka Tiger 19 7
3 Chukka boots Berwick 27 7

OuraRing3が届いた

Gen2でも困らなかったのですが、アプリの画面に通知がずっと出てくるので購入してみました。
リングサイズは、今使っているものと変わらず装着感も違和感がないです。

充電器がGen3から変わってるぽくて、充電が完了するとグリーンに変わるみたいです。

後は前に使っていたモデルは、Donateできるぽくてメールで通知がきてました。
面倒なのでやってはいないです。

ハラ ミュージアム アークに行って来た

前から行きたかったハラ ミュージアム アークに行ってきました 場所は渋川なので、高崎で一泊して午前中に行くことにしました

ホテル周辺

ホテルにチェックイン後は暇だったので本買ったり、近くの高崎市美術館で時間を潰したりして楽しかったです
駅周辺は栄えていてめちゃくちゃ便利そうでした、大宮とか町田の駅前に近い感じです

夕食

夕飯はラーメンを食べたかったので、駅近くのOPAにあるラーメン屋で食べました
ここ数ヶ月全くラーメンを食べてなかったのでめちゃくちゃ上手く感じました

翌日は渋川まで電車でいき、後はタクシーでハラ ミュージアム アークまで行きました
かなり山の麓に近いところで、高原みたいな所で風景がめちゃくちゃ良かったです

帰りは渋川駅まで歩いて帰りましたが、ずっと緩い下り坂が続くのでめちゃくちゃ大変でした
普段7kmぐらい歩くのは平気だったので、いけるかなと思っていましたがバスかタクシーで帰ればよかったです

次来る時は、渋川の付近のホテルに泊まろうかなと思います

Cloudinayの新プラン移行

Cloudinayのプラン上限に達したので、プランを変更してみました。
プランはClassic free planだったのをFree planにしました。

CloudinayのアカウントはHerokuのアドオンで指定していたプランだったので、Cloudinayのアカウントを新規に作成してのプランを変更しました。

作業手順としては下記の通りに進めました。
1. 新規アカウントを作成する
2. 新規アカウントでURLのマッピングを旧アカウントの設定に合わせる
3. S3のバケットの.wellknown/cloudinary/に新アカウントのCloudNameをファイル名にした空のファイルを作成する
4. 新しいURLで画像が変換されるのを確認する
5. herokuの環境変数を変更する
6. アプリケーションで画像が正常に表示されるか確認する
7. S3から旧CloudNameのファイルを消す
8. 旧アカウントを削除する

プラン変更は無事終了しました。
これでしばらく運用できそうです。

css-loaderのバージョンを上げた

webpackのcss-loaderのバージョンを5.xから6.xに変えました。
それに伴いcss内の画像指定が期待した通り変換されなくなったのでwebpackの設定ファイルを変えることで対応しました。

具体的にはwebpackの設定のrulesにasset/resourceを追加する作業です。
この設定によりcss-loaderがcssファイル内で参照している画像を置き換えてくれます。

module.exports = [
  {
    module: {
      rules: [
        {
          test: /\.scss$/,
          use: [
            {
              loader: MiniCssExtractPlugin.loader
            },
            {
              loader: "css-loader",
              options: {
                url: true,
                importLoaders: 2,
              }
            },
            "sass-loader"
          ]
        },
        {
          test: /\.(jpg|jpeg|png|svg|gif|ico)$/,
          type: "asset/resource",
        }
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({}),
    ],
    resolve: {
      extensions: ['.js', '.jsx', '.ts', '.tsx'],
    },
    stats: 'errors-only',
  }
];

asset/resourceをasset/inlineにすると、画像のパスをData URIに置き換えてCSSに画像データを埋め込んでくれます。
ファイルのサイズが小さい場合はこちらでも良いかもしれません。

BigQueryのテーブルに複数のログファイルをロードする

Google Cloud Storageに保存されている複数のファイルをBigQueryのテーブルに読み込もうとして、エラーが発生するので調査してみました。
エラーはテーブルにレコードが挿入できないエラーで、フォーマットが当初おかしいのかと思っていました。

Error while reading data, error message: JSON table encountered too many errors, giving up. Rows: 1; errors: 1. Please look into the errors[] collection for more details.
Error while reading data, error message: JSON processing encountered too many errors, giving up. Rows: 1; errors: 1; max bad: 0; error percent: 0
Error while reading data, error message: JSON parsing error in row starting at position 0: Could not convert value to string. Field: sign_in_count; Value: 12

しかしファイルを調べてみてもおかしいところはありません。
調べた結果、スキーマの構造が最初に処理するファイルによってスキーマが決定されていそうでした。

例えば、次のようなログファイルがあった場合、sign_int_countがNullableであることはわかりますが、INTEGERというデータ型まで特定するのは困難です。
代わりにデータ型はNullableなSTRING扱いになるようです。

{ "id": "a", "name": "demo1",  sign_in_count: null, "createdAt": "2021-02-17 07:33:05 UTC", "updatedAt": null }

次のようだとおそらくNullableなINTEGERであることは推測できます。
なぜなら、sign_in_countに2が含まれていて、数値であることがわかるからです。

{ "id": "a", "name": "demo1",  sign_in_count: null, "createdAt": "2021-02-17 07:33:05 UTC", "updatedAt": null }
{ "id": "b", "name": "demo2",  sign_in_count: 2, "createdAt": "2021-02-17 07:33:05 UTC", "updatedAt": null }

今までだと単一のファイルで読み込むことが多く、問題が出ませんでした。
それは単一のファイルだとフィールドのデータパターンが全部収集できるので、フィールドのデータ型が推測しやすかったからだと思います。

解決方法

この問題を解決する為に、読み込み時にスキーマを明示的に指定するようにしました。
明示的に指定することで、正しく読み込むことができます。
200ファイル近くのファイル読み込んでみましたが特に問題は起きませんでした。

その代わりautodetectのメリットがなくなります。

import { BigQuery } from '@google-cloud/bigquery'
import { Storage, File } from '@google-cloud/storage'

const storage = new Storage({ projectId: process.env.GCP_PROJECT_ID })
const bigQuery = new BigQuery({ projectId: process.env.GCP_PROJECT_ID })

const logFilesOf = async (logDate: string) => {
  const prefix = `logs/${logDate}/`
  const bucket = storage.bucket(process.env.GCP_BACKUP_BUCKET_NAME!)
  const [files] = await bucket.getFiles({ prefix })
  return files
    .filter((file: File) => file.name !== prefix)
    .filter((file: File) => {
      const size = Number(file.metadata.size)
      return size > 0
    })
}

const loadFilesOf = async (tableId: string, logDate: string) => {
  const files = await logFilesOf(logDate)

  if (files.length <= 0) {
    return
  }

  const table = bigQuery.dataset('example_dataset').table(tableId)

  return table.createLoadJob(files as any, {
    sourceFormat: 'NEWLINE_DELIMITED_JSON',
    autodetect: true,
    writeDisposition: 'WRITE_TRUNCATE',
    schema: {
      fields: [
        {
          name: 'id',
          type: 'STRING',
          mode: 'REQUIRED',
        },
        {
          name: 'name',
          type: 'STRING',
          mode: 'REQUIRED',
        },
        {
          name: 'sign_in_count',
          type: 'INTEGER',
        },
        {
          name: 'createdAt',
          type: 'STRING',
          mode: 'REQUIRED',
        },
        {
          name: 'updatedAt',
          type: 'STRING',
        },
      ],
    },
  })
}

loadFilesOf('example', '2021-08-09')
  .then(() => {
    console.log('done')
  })
  .catch((err: Error) => {
    console.log(err.stack)
  })

Cloud CDNでの署名付きCookie、署名付きURL

Cloud Storageに格納したファイルをCDNで配信する時にアクセスを限定したい場合があります
その際に署名付きCookie、署名付きURLが使えます

設定自体はterraformなどで、自動で設定して置けるようにすると楽です

resource "google_storage_bucket" "public-images" {
  project  = var.project_id
  name     = "public-images-${var.project_id}"
  location = "asia-northeast1"

  force_destroy = true

  cors {
    origin          = ["https://${var.domain}"]
    method          = ["GET"]
    response_header = ["*"]
  }
}

resource "google_storage_bucket_iam_member" "signed-url-user" {
  bucket = google_storage_bucket.public-images.name
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:service-${var.project_number}@cloud-cdn-fill.iam.gserviceaccount.com"

  depends_on = [
    google_compute_backend_bucket_signed_url_key.backend_key
  ]
}

resource "google_compute_backend_bucket" "cdn-backend-bucket" {
  project     = var.project_id
  name        = "backend-${google_storage_bucket.public-images.name}"
  bucket_name = "public-images-${var.project_id}"
  enable_cdn  = true
}

resource "random_id" "url_signature" {
  byte_length = 16
}

resource "google_compute_backend_bucket_signed_url_key" "backend_key" {
  project        = var.project_id
  name           = "public-backend-bucket-key"
  key_value      = random_id.url_signature.b64_url
  backend_bucket = google_compute_backend_bucket.cdn-backend-bucket.name
}

resource "google_compute_url_map" "cdn" {
  project         = var.project_id
  name            = "public-images-cdn"
  default_service = google_compute_backend_bucket.cdn-backend-bucket.id
}

resource "google_compute_target_https_proxy" "cdn-proxy" {
  project          = var.project_id
  name             = "proxy-${var.project_id}"
  url_map          = google_compute_url_map.cdn.id
  ssl_certificates = [google_compute_managed_ssl_certificate.tls.id]
}

resource "google_compute_managed_ssl_certificate" "tls" {
  project  = var.project_id
  provider = google-beta
  name     = "public-images-${var.project_id}"
  managed {
    domains = [var.domain]
  }
}

resource "google_compute_global_forwarding_rule" "cdn" {
  project    = var.project_id
  name       = "public-images-${var.project_id}"
  target     = google_compute_target_https_proxy.cdn-proxy.self_link
  port_range = 443
}

生成された鍵はSecretManagerなどに登録しておくといいと思います

署名付きCookie

cloud.google.com

署名付きCookieは署名付きリクエストの鍵を使ってCookieに署名します
生成した値はCloud-CDN-Cookieに指定して利用できます

import crypto from 'crypto';
import dayjs from 'dayjs';

export const ENCODED_URL_PREFIX = (Buffer.from(process.env.URL_PREFIX))
  .toString("base64")
  .replace(/\+/g, "-")
  .replace(/\//g, "_");

export const signedCookie = (seconds: number) => {
  const unixTimestamp = dayjs().add(seconds, 'seconds').unix();
  const input = `URLPrefix=${process.env.ENCODED_URL_PREFIX}:Expires=${unixTimestamp}:KeyName=${process.env.SIGNED_URL_KEY_NAME}`;
  const keyValue = Buffer.from(process.env.SIGNED_URL_KEY_VALUE, 'base64') 
  const signature =  crypto.createHmac('sha1', keyValue)
    .update(input)
    .digest('base64')
    .replace(/\+/g, "-")
    .replace(/\//g, "_");

  return `${input}:Signature=${signature}`;
}

署名付きCookieの場合はCookieのパスの指定ミスなどで期待通りブラウザがCookie送ってくれなかったり、原因の切り分けを早くできた方がいいので、 実装が済んでしまったら先に正しい署名がCookieについているか確認しておくと良いと思います

curl --cookie='Cloud-CDN-Cookie=[生成した値]' \
  -D dump-headers.txt \
  --output=example.jpg \
  https://cdn.example.com/example.jpg

署名付きURL

cloud.google.com

署名付きURLはURLに対して署名を付加できるものです
Cookieと違って対象のURLが多い場合面倒ですが、実装自体は簡単なので導入しやすいです

import crypto from 'crypto';
import dayjs from 'dayjs';

export const signedURL = (url: string, seconds: number) => {
  const unixTimestamp = dayjs().add(seconds, 'seconds').unix();

  const segments = [
    `?Expires=${unixTimestamp}`,
    `&KeyName=${process.env.SIGNED_URL_KEY_NAME}`
  ];

  const signURL = `${url}${segments.join('')}`
  const keyValue = Buffer.from(process.env.SIGNED_URL_KEY_VALUE, 'base64') 
  const signature =  crypto.createHmac('sha1', keyValue)
    .update(signURL)
    .digest('base64')
    .replace(/\+/g, "-")
    .replace(/\//g, "_");

  segments.push(`&Signature=${signature}`);

  const singedQuery = segments.join('');

  return `${url}${singedQuery}`;
}

こちらも実装が済んだら検証しておきます

curl --output=example.jpg [署名付きURL]