nikkie-ftnextの日記

イベントレポートや読書メモを発信

S3にオブジェクト(ファイル)が存在するか否かシェルスクリプトで確認する 〜提案実装と、行き着くまでの回り道〜

はじめに

YAPC::Kyotoのチケット、2/3(金)までの販売に延長1 nikkieです。

S3にオブジェクトがあるかどうかをシェルスクリプトで確認する方法を考えてみました。
現時点の暫定解としてアウトプットします。
シェルスクリプトAWSに関しては伸びしろ豊富と思いますので、フィードバックは大歓迎です。

目次

実現したいこと

S3のオブジェクト(今回はファイルに限定します)のURIが手元にあります(s3://で始まるURIです)。
このURIが指すオブジェクト(ファイル)があるかどうかシェルスクリプトで知りたいという状況です。
ここでは詳しく立ち入りませんが、存在有無に応じて後く処理を分岐させることを考えていました。

素振り準備

LocalStackで環境構築

Dockerを使ってローカル環境にS3を用意します。
以下の記事のPythonスクリプトを使って、s3://awesome/foo/bar.txtを置きました。

docker runの出力には以下のバージョン記載がありました。

LocalStack version: 1.3.2.dev
LocalStack build date: 2023-01-28
LocalStack build git hash: 63e357fb

AWS CLIの設定

コマンドラインからAWSのサービスを操作するのにAWS CLIを使います。
インストールはこちらを参考にしました。

aws configureでLocalStack用のprofileを用意します2

高速セットアップ - AWS Command Line Interface

$ aws configure --profile localstack
AWS Access Key ID [None]: dummy
AWS Secret Access Key [None]: dummy
Default region name [None]: us-east-1
Default output format [None]: json

使ったCLIのバージョンは以下です。

$ aws --version
aws-cli/2.1.30 Python/3.8.8 Darwin/18.7.0 exe/x86_64 prompt/off

準備できました!

$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/
2023-01-31 19:53:33         11 bar.txt

参考実装とその読み解き

以下を参考にしました。

  1. aws s3 ls --sumの結果を取得
  2. grepでオブジェクトの数を確認
    • grep -sq 'Total Objects: 0'URIで指定したディレクトリにオブジェクトがないことを検出
    • -qは見つかった行を標準出力に書き出さない指定3
    • -sはエラーメッセージを表示しない指定

提案実装(S3にオブジェクトがあるかどうかを確認する方法 暫定解)

exist_or_not.sh

#!/usr/bin/env bash
set -euo pipefail

file_uri=$1
set +e
aws --profile localstack --endpoint-url http://localhost:4566 s3 ls ${file_uri} > /dev/null
ls_code=$?
set -e

if [ ${ls_code} -eq 0 ]; then
  echo "${file_uri} はあります"
else
  echo "${file_uri} はありません"
fi

オブジェクトが存在するURIs3://awesome/foo/bar.txtと、オブジェクトが存在しないURIs3://awesome/foo/baz.txtを渡して実行してみます。

$ ./exist_or_not.sh s3://awesome/foo/bar.txt
s3://awesome/foo/bar.txt はあります

$ ./exist_or_not.sh s3://awesome/foo/baz.txt
s3://awesome/foo/baz.txt はありません

aws s3 lsの終了コード

ドキュメントで見つけられなかったのですが、URIで指定したオブジェクトがない場合、aws s3 lsの終了コードは1になるようです4

$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/bar.txt
2023-01-31 19:53:33         11 bar.txt
$ echo $?
0

$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/baz.txt
$ echo $?
1

これを利用して、aws s3 ls終了コードで処理を分岐しました。

終了コードの扱い(set +eの理由)

単にaws s3 lsだけを使って実装したスクリプトは、ファイルが存在するときは期待通り動きますが、ファイルが存在しないときにはechoされません。
set -eにより、コマンドの終了コードが0以外だと、そこでスクリプトが終了する5ためです。

aws s3 lsスクリプトが終了しないよう、set +eと書いて、set -eを一時的に無効にしています。

供養したい実装

実はこのエントリを書き始めた時点でのexist_or_not.shは以下の実装でした。
diffのシンタックスハイライトを利用して、大きく違うところを強調しています。

#!/usr/bin/env bash
set -euo pipefail

file_uri=$1
set +e
+ls_output=$(aws --profile localstack --endpoint-url http://localhost:4566 s3 ls ${file_uri} --sum)
+echo "${ls_output}" | grep -sq 'Total Objects: 1'
+grep_code=$?
set -e

if [ ${grep_code} -eq 0 ]; then
  echo "${file_uri} はあります"
else
  echo "${file_uri} はありません"
fi

エントリを書いているうちにaws s3 lsの終了コードだけで判定できることに気付き、その方が実装が簡潔になるので差し替えました。
この実装に至るまでに調べたことを以下にアウトプットしておきます。

aws s3 ls --sum

--sum--summarizeオプションです。

Displays summary information (number of objects, total size). (aws s3 lsのドキュメントより引用)

summary information(要約情報。「Total Objects」「Total Size」のこと)が表示されるようになります。

$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/bar.txt --sum
2023-01-31 19:53:33         11 bar.txt

Total Objects: 1
   Total Size: 11

ドキュメントの「Example 5: Summarizing all prefixes and objects in a bucket」も参考になりました。

grep

aws s3 ls --sumの結果の文字列をgrepで検索します。
存在しないオブジェクトのURIを指定したときも--sumオプションにより要約情報は表示されます。

$ aws --profile localstack --endpoint-url http://localhost:4566 s3 ls s3://awesome/foo/baz.txt --sum

Total Objects: 0
   Total Size: 0

そこで(ここではオブジェクトはファイルの想定なので)、aws s3 ls --sumの結果に

  • 「Total Objects: 1」があればURIで指定したオブジェクト(ファイル)が存在する
  • なければ、URIで指定したオブジェクトが存在しない

と判定することにしました。

set +e

ここまでの要素で実装したスクリプトは、ファイルが存在するときは期待通り動きますが、ファイルが存在しないときにはechoされませんでした。
これはaws s3 lsgrepの終了コードによります。

URIに対応するオブジェクトが存在しないときaws s3 lsは終了コード1となります。
set -eによりエラーが送出されたらスクリプトを終了します。

また、grepも検索語句が見つからなかったときに終了コードが1となります。

選択される行が見つかったときの終了ステータスは 0 であり、 見つからなかったときは 1 であり、エラーが起きた場合は 2 です。

Man page of GREP

grepの終了コードは行が見つかったか否かを表すことを知り、これを判定ロジックで使いたいと考えました。

終了コードをまとめると以下のようになります6

ファイル有無 aws s3 lsの終了コード grepの終了コード
あり 0 0
なし 1 1

ファイルがない場合、aws s3 lsgrepも終了コードは0でありません。
aws s3 lsgrepスクリプトが終了しないよう、set +eを使っています。

終わりに

S3にオブジェクト(ファイル)が存在するか否か確認するスクリプトについてアウトプットしました。
aws s3 lsの終了コードを使う考えです。
またこの実装に至るまでにaws s3 ls --sumgrepを調べた回り道もアウトプットしました。


  1. リンク先にありますが、売り切れも近いそうです。お早めに!
  2. LocalStackを使ってローカル環境でS3を立ち上げ、Lambdaでファイルをアップロードしてみた - Qiita を参考にしました
  3. マスタリングLinuxシェルスクリプト 第2版』8.1によると> /dev/nullと同じです
  4. ドキュメントに記載がない情報を使うのは反対という立場も全然あると思います。私は「lsコマンドの終了コードと似ているし、突然実装も変わりづらいだろうから利用しちゃえ」と考えました
  5. set -eエラーが送出されたらそれをハンドルしてから終了するシェルスクリプトの書き方を理解しました(set -e, trap, exit) - nikkie-ftnextの日記で完全に理解しました
  6. この表を書いたのが簡潔な実装に気付くきっかけとなりました

LocalStackでローカル環境に簡単にAWSのサービスを用意! Dockerで動かし、boto3でS3を操作する例

はじめに

YAPC::Kyoto 2023のチケットを買ってくれ!1(本日1/31(火)まで) nikkieです。

直近でLocalStackという技術を触る機会がありました。
LocalStackはAWSのサービス(に相当するAPI群)をローカル開発環境で動かせます
環境構築からPythonスクリプトでローカルのS3を操作するまでをログとして残します。

目次

LocalStack

フューチャーさんのブログで存在を知りました。

フューチャーさんの記事は、AWS Lambdaの開発環境をLocalStackで動かす例を示しています。

LocalStack自体はPythonで実装されていて2PyPIにパッケージも登録されています

DockerでLocalStackを動かす

今回はDockerで動かすことにしました(pip install localstackは今後の素振り材料です)。

https://docs.localstack.cloud/getting-started/installation/#docker

$ docker run --rm -it \
    -p 4566:4566 \
    -p 4510-4559:4510-4559 \
    localstack/localstack

起動中のログより、以下のバージョンを動かしていました。

LocalStack version: 1.3.2.dev
LocalStack build date: 2023-01-28
LocalStack build git hash: 63e357fb

「Ready.」と出たら準備完了です。
curl -s http://localhost:4566/health | jq .で各種サービスがavailableになったことが確認できます。

ローカルのS3でboto3を素振り!

AWSのサービスの操作を提供するライブラリboto3、LocalStackに向けても使えます。

pip install boto3でインストールできます。
以下のバージョンで動かしています。

$ python -V
Python 3.10.2
$ pip list | grep boto
boto3           1.26.59
botocore        1.29.59

試しにS3にバケットを作り、ファイルを置いてみます(s3://awesome/foo/bar.txtが置かれます)。

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://localhost:4566",
    aws_access_key_id="dummy",
    aws_secret_access_key="dummy",
)

BUCKET_NAME = "awesome"
s3.create_bucket(Bucket=BUCKET_NAME)

FILE_PATH = "foo/bar.txt"
s3.put_object(Bucket=BUCKET_NAME, Key=FILE_PATH, Body=b"LocalStack!")

client3はLocalStackのエンドポイントを指定し、aws_access_key_idaws_secret_access_keyにはダミーの文字列を指定します。

boto3でS3のバケット操作

バケットの作成はcreate_bucketメソッドです。
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.create_bucket
Parametersのうち、Bucketバケット名を指定)のみが必須です。

パケットにファイルを置くのはput_objectメソッドです。
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.put_object
必須のパラメータはBucketKey(S3に置くオブジェクトのキー)です。
オブジェクトのデータはBodybytesオブジェクト、またはfile-likeオブジェクト4として渡せます。

LocalStackのS3バケットに置いたファイルを確認

上で示したスクリプトs3_practice.py)を-iオプションを付けて実行します。
スクリプト実行後に立ち上がるPythonの対話モードで、s3://awesome/foo/bar.txtの内容を確認します。

>>> response = s3.get_object(Bucket=BUCKET_NAME, Key=FILE_PATH)
>>> response["Body"].read()
b'LocalStack!'

get_objectメソッドでオブジェクトを取得します。 https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3.html#S3.Client.get_object
Parametersのうち、BucketKeyが必須です。

Responseの辞書では、キー"Body"がデータを指しています。
readメソッドで読み込みます5
書き込んだバイト列と同じですね!🙌

終わりに

ローカル環境にAWSのサービスを立てられるLocalStackを触りました。
Dockerで動かし、boto3を使ってS3を操作しています。
バケットを作り、オブジェクトを置き、その内容を読み出しました。

ローカル環境に簡単にAWSを用意できるのは開発が捗りそうな印象です。
AWS Lambdaで動かしているBot6がありますが、他のAWSサービスと連携させる開発はまずサービスの用意が必要ですし、一発で正しく構築できないことが多いので数回作り直すイメージがあります。
ですが、LocalStackでローカル環境に用意すれば、試行錯誤に必要な準備が劇的に簡単になります!
素振り中も、例えばバケットができたあとに実装ミスでエラーが送出されたら、LocalStackを止めて再度docker runして操作をリセット。
修正したスクリプトを最初から流し直しました。

ブログにまとめるにあたって、boto3のドキュメントの読み方もなんとなく分かりました。
すごく文字がありますが、ParametersのREQUIREDさえ確認すれば使い出せます。
ResponseやExampleも参考になりますね。

参考記事

P.S. boto3のClient API

ここで示したスクリプトboto3.clientとClient APIを使うものです。
boto3にはもう1つResource APIがあります(boto3.resource)。

  • Client APIは低レベル
  • Resource APIは高レベル

Resource APIが廃止という情報を見かけ、今回のスクリプトはClient APIを使って実装しています。

2023/01/19 追記:「Resource API」廃止についての情報

3/19(日)にハイブリッド開催のYAPC::Kyoto 2023、たくさんの「話します!」ブログを読んで、私すごく楽しみなんです!(📣チケットが1/31(火)まで!!) #yapcjapan

タイトルの1/31の曜日を日曜と間違えていたのを修正しました (2023/01/31)

はじめに

氷菓音楽会、めっちゃよかった…1 nikkieです。

来る3/19(日)、2023年のYet Another Perl Conferenceが京都とオンラインの地でハイブリッド開催されます!

1/27(金)に採択発表があり、「YAPC::Kyoto 2023で話します! そしてチケットを今すぐに購入しましょう!!」エントリがたくさん世に放たれました2
それを読む中で面白そうなトークをいくつか見つけたので、見返せるようここにログとして残します。

目次

YAPC::Kyoto 2023で聴けるトーク

採択発表ブログはこちらです。
数え間違えているかもしれませんが、27本ありました。

さらにゲストスピーカーも5名いらっしゃいます!

トークは(キーノート含め)全部で32本。
さらに例年のLTもあると思われますが、タイムテーブルはいったいどうなるんでしょう(わくわく、わくわく

YAPC::Kyoto 2023で話します! そしてチケットを今すぐに購入しましょう!!」エントリラッシュ

はじまりの

スピーカーのうち半分くらいがこのエントリのオマージュを書かれていました。
「始まる前からのお祭り感いいな〜」と見守っていましたが、気付いたらまとめてました。

エントリに目を通す中で面白そうなトークを見つけ、当日が非常に楽しみになってきました!

🙏チケットを買ってくれ

今月中にチケットを買わないと参加ができないのです! 今、まさにこの瞬間、すぐに買いましょう!!!!!

私も言えたよ!(便乗)

これらのトークが楽しみなんです

(※今後情報は増えていくと思うので、当日までに追加があると思います)

Pasta-Kさん「JavaScriptのASTと戯れる」

AST(抽象構文木)の話題だ!
関心のあるトピックなので非常に楽しみです。
このトークの理解度を上げるために、締切設定して積ん読消化もありですね(思案中)

Songmuさん「小さく始め、長く続けるOSS開発と貢献」

今回YAPCに行きたいと思った理由の一つはOSSのメンテナの先輩方と話をしたいと思ったからなんですよ。
SpeechRecognitionのメンテナ活動始めました3が、ノウハウが分からず、ゲームで言う「わからん殺し4」を決められている感じが拭えません。
2022年の参加を経て、Songmuさんをはじめとして、長期のメンテナ経験のある方の存在に気付き、「これはもう行っちゃえ」と決めました。
Songmuさんのこのトークは楽しみです。

Konboi さん「様々な環境へコマンドラインツールを提供する上での苦労とその対策」

Python製のコマンドラインツールの話だ!
Pythonということで、これは聞きたい!
メンテナ活動のヒントにもなるんじゃないかな(わくわく

ソースコードをざっと確認して臨んでみよう!

終わりに

YAPC::Kyoto 2023で話します! そしてチケットを今すぐに購入しましょう!!」エントリ群のおかげで、半分くらいのトークの内容が分かり、楽しみになってきました。
1日1エントリと絡めてちょっとだけでも予習して当日を迎えたいと思います。

私にはPerlの経験はほとんどないですが、YAPCは「Perlだけにとどまらない技術者たちが、好きな技術の話をし交流するカンファレンス」なので、自分の技術のバックグラウンドに合わせて楽しめそうという印象です。
なので、ご自身のPerlの経験は棚に上げて、発表されるトークの中に「これは聴きたい!」というものがあったら、ぜひチケットを買ってみてはいかがでしょうか?

今すぐに購入しましょう!! つまり、買ってくれということです。


  1. アーカイブもありますよ〜
  2. チェーンメール」という表現もあって、面白かったです。こういうノリのよさはPythonコミュニティに入り浸っている私の視点からは新鮮に映ります
  3. 一例です。メンテナ記 | GitHubでリリースを作るのは、怖くない - nikkie-ftnextの日記
  4. ミノ駆動さんの過去の登壇から。言い得て妙な言い回しと感じています(掘り下げは別エントリかな)

WireMockは簡単に使えてすごい!JSONファイルで設定してDockerで動かす例 〜ちさたきを添えて🐟〜

はじめに

もしもしもしもし〜1、nikkieです。

HTTPサーバをモックできるWireMockを最近触りました。
この技術、「非常に簡単に間違えずに使える!」と感動したので、今回アウトプットします。

目次

WireMockとは

HTTPサーバをモックできます2
2つの用途で使えるそうです3

  • stubbing(スタブ)
    • 特定のリクエストに対して、前もって指定されたレスポンスを返す
  • verification(検証)
    • 後でチェックできるよう、入ってくるリクエストをキャプチャする

REST APIJavaAPIも提供されていますが、今回はJSONファイルからスタブの設定4を触りました。
これが非常に簡単だったんです!

WireMockをJSONファイルで設定してDockerで動かす

Configuring and running WireMock in Docker | WireMock

ドキュメントの「Mounting stub mapping files」を参照します。

  • 作業ディレクトリに、JSONを配置するための2つのディレクトを用意する
    • mappings
    • __files
  • mappings__filesがあるディレクトリをDockerコンテナの/home/wiremockにマウントする
    • 👉-v $PWD:/home/wiremock

新しい概念がたくさん出てきましたが、1つ1つ見ていきましょう。
モックするサーバは『リコリス・リコイル』の「さかなー」「ちんあなごー」を模します5

GET /chinanago で「さかなー」と返すように設定する

mappingsJSONファイルtakina.jsonを作ります。

{
    "request": {
        "urlPath": "/chinanago",
        "method": "GET"
    },
    "response": {
        "status": 200,
        "body": "さかなー🐟"
    }
}

パス/chinanago6GETメソッドでリクエストされたときのスタブを設定しています(requestの部分)。
レスポンスは200 OKを返し、ボディは「さかなー🐟」と設定しました(responseの部分)。

設定値について詳しくは Matching and filtering HTTP requests in WireMock | WireMock (1) に記載されています。

mappings/takina.jsonを置いた状態でWireMockのDockerイメージをrunしましょう!7

docker run -it --rm \
  -p 8080:8080 \
  --name chisataki-mock \
  -v $PWD:/home/wiremock \
  wiremock/wiremock:2.35.0

別のシェル8でリクエストを送ってみましょう。

$ curl http://localhost:8080/chinanago
さかなー🐟

さかなー🐟🐟🐟🐟!!!!

POST /takina にchinanagoのJSONが送られたら「さかなー」と返すように設定する

次はPOSTリクエスをモックしてみましょう。
mappings/takina.jsonrequestを変えていきます。

⚒変える箇所

  • urlPath(完全に好みで/takinacurlしたかったので変えました)
  • method:POSTリクエストを指定
  • headersを追加
    • Content-Typeがapplication/jsonの場合のスタブです9
  • bodyPatternsを追加
    • {"lines": "chinanago"}というJSONに一致するボディのリクエストに対するスタブです
{
    "request": {
-        "urlPath": "/chinanago",
-        "method": "GET"
+        "urlPath": "/takina",
+        "method": "POST",
+        "headers": {
+            "Content-Type": {
+                "equalTo": "application/json"
+            }
+        },
+        "bodyPatterns": [
+            {
+                "equalToJson": {
+                    "lines": "chinanago"
+                }
+            }
+        ]
    },
    "response": {
        "status": 200,
        "body": "さかなー🐟"
    }
}

設定を変えたのでWireMockのイメージは一度止めて、再度docker run(コマンドはこれまでと同じです)。
別のシェルでcurlします。

$ curl http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}'
さかなー🐟

さかなー🐟🐟🐟🐟🐟🐟🐟🐟!!!!!!!!

.request.bodyPatternsequalToJsonJSONで指定しましたが、ドキュメント(1)によると文字列リテラルとしても指定できます(「JSON with string literal」でページ内を検索すると見つかります)

        "bodyPatterns": [
            {
                "equalToJson": "{\"lines\": \"chinanago\"}"
            }
        ]

POST /takina の「さかなー」のレスポンスをJSONファイルを使って設定する

Mock API Response Templating | WireMock (2)

mappings/takina.json.response.bodyJSON形式の文字列リテラルも指定できます。

{
    "request": {
        "urlPath": "/takina",
        "method": "POST",
        "headers": {
            "Content-Type": {
                "equalTo": "application/json"
            }
        },
        "bodyPatterns": [
            {
                "equalToJson": "{\"lines\": \"chinanago\"}"
            }
        ]
    },
    "response": {
        "status": 200,
-        "body": "さかなー🐟"
+        "body": "{\"lines\": \"さかなー🐟\"}"
    }
}
$ curl -s http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}' | jq .
{
  "lines": "さかなー🐟"
}

JSONの、さかなー🐟🐟🐟🐟!!!!

ドキュメント(2)によると、responsebodyは代わりにbodyFileNameを使って__files下に配置したJSONファイルの内容をレスポンスとして返せます(「Templated body file」)10

__files/takina.jsonを置きましょう。

{
    "lines": "さかなー🐟"
}

mappings/takina.jsonからこのファイルを使うように変更します。
bodyFileNameには__files以下の相対パスを書くようです。

{
    "request": {
        "urlPath": "/takina",
        "method": "POST",
        "headers": {
            "Content-Type": {
                "equalTo": "application/json"
            }
        },
        "bodyPatterns": [
            {
                "equalToJson": "{\"lines\": \"chinanago\"}"
            }
        ]
    },
    "response": {
        "status": 200,
-        "body": "{\"lines\": \"さかなー🐟\"}"
+        "bodyFileName": "takina.json"
    }
}
$ curl -s http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}' | jq .
{
  "lines": "さかなー🐟"
}

__filesを使って設定したJSONの、さかなー🐟🐟🐟🐟🐟🐟🐟🐟!!!!!!!!

WireMockのDockerイメージの挙動は以下のようです。
-vでマウントしているので、ホスト側のファイルを編集すると、コンテナ内のファイルの内容も変わります

  • mappings側は起動時にメモリに読み込むようでdocker run中にファイルの内容が変わっても再度docker runするまで反映されない
  • __filesはリクエストが来るたびに参照される(メモリに読み込まれていない)ようで、docker runしたままでも返されるレスポンスが変わる

コンテナを止めずに__files/takina.jsonを変えてみます。

{
-    "lines": "さかなー🐟"
+    "lines": "さかなー🐟🐟🐟🐟"
}
$ curl -s http://localhost:8080/takina -H 'Content-Type: application/json' -d '{"lines": "chinanago"}' | jq .
{
  "lines": "さかなー🐟🐟🐟🐟"
}

まとめ:WireMockをJSONファイルで設定する

WireMockを設定するJSONファイルの配置

  • mappingsディレクト
    • スタブするリクエストを設定するJSONファイルを置く
      • レスポンスも設定可能(__filesに切り出すこともできる)
    • requestでスタブするリクエストを設定
  • __filesディレクト
    • レスポンスとするJSONファイルを置ける
    • .response.bodyFileName相対パスで指定できる

docker runでは「mappings__filesがあるディレクトリ」を/home/wiremockにマウントする。

この記事ではJSONファイルは1つずつしか置いていませんが、複数置くこともできます。

終わりに

JSONファイルでのWireMockの設定が簡単!」という感動からこの記事にアウトプットしました。
思うに「正しい使い方を簡単に、誤った使い方を困難に」の好例だと思います。
ルールに沿ってJSONファイルを置いてdocker runするだけでモックサーバが立ち上がる、簡単!
JSONの書き方を間違えたときはdocker runで落ちてパースに失敗したエラーメッセージが表示されます。
jqコマンド素振り11など、読み解くために必要な知識があったというバイアスがかかっているかもしれませんが、間違えても正しい道に戻りやすかったです。

また、WireMockには「80/20ルール」12も該当するように思われます。
Gitなどと同様、WireMockも20%を知ることで、日常的なニーズの80%はまかなえそうです。

今回はJSONファイルを使いましたが、REST APIからの設定に使える(?)Pythonライブラリもあるみたいでした。
素振り材料ですね。

GitHub - wiremock/python-wiremock: Python WireMock Admin API Client

今回のサンプルコードはこちらです(たきなをGET、千束をPOSTで設定しました)


  1. リコリス・リコイル6話で千束が言ってました。
  2. ドキュメントより「WireMock is an HTTP mock server.
  3. ドキュメントより「At its core it is web server that can be primed to serve canned responses to particular requests (stubbing) and that captures incoming requests so that they can be checked later (verification).
  4. ドキュメントより「Additionally, stubs can be configured via JSON files.
  5. 例に使った理由は、好みです!
  6. takina.json.request.urlPathですが、ドキュメント(1)の「URL matching」を参照すると他にもやり方があります。正規表現も使えるそうです
  7. この時点では__filesディレクトリは使っていませんが、__filesディレクトリがない場合はdocker run -vのマウントにより__filesディレクトリができました(権限はrootではなく、ホスト側の現在のユーザでした)
  8. docker run-dを指定していないので、WireMockの出力が表示された状態です。-dを指定して同じシェルでcurlしてもいいと思います!
  9. Content-TypeのようなHTTPヘッダー名は「大文字小文字を区別しない」んですよね(contenT-typEなどのように変えてお試しください)。ヘッダーの値については、大文字小文字を区別しないでマッチするように指定する方法("caseInsensitive": true)がドキュメント(1)の「Case-insensitive equality」に載っています
  10. この記事の設定例だとあまり嬉しさを感じないかもしれませんが、「Templated body file」には"bodyFileName": "files/{{request.pathSegments.[1]}}"リクエストに応じたファイルを返す設定例があります
  11. JSON Linesをjqコマンドで扱う方法、『jqハンドブック』で完全に理解した! - nikkie-ftnextの日記
  12. Clean Agile』にあります。過去に言及したエントリはこちら:はてなスター、好きです!〜引用スターを知ってますます好きに〜 - nikkie-ftnextの日記

旅の準備:3月はNLP2023 OKINAWAとYAPC::Kyoto 2023

はじめに

クリーチャーを最大2体まで対象とし、それらの上に+1/+1カウンターをそれぞれ1個置く。1 nikkieです。

オフラインイベントも戻り始め、この3月には興味深い技術イベントがあります!
1日1エントリと絡めて、技術イベントへの遠征旅行準備をしました。

目次

3月はNLP2023 OKINAWAとYAPC::Kyoto 2023

3/13(月)〜17(金)まで沖縄(とオンライン)で言語処理学会第29回年次大会(NLP2023)があります!

NLP2022クロージングでの発表から楽しみにしていました。

その週の週末、3/19(日)には京都でYet Another Perl Conferenceがあります!

2022年はOnlineで楽しみました2

旅の準備

NLP2023の2ヶ月前は過ぎた状況で、「ホテルとか飛行機とかそろそろ用意したほうがいいよな」という気持ちが頭をもたげてきました3

ただ旅の準備って正直めんどくさいんですよね。
一瞬で完了したらすごくいいんですが、ホテルや飛行機など選択肢がいくつかあって、ちょっとした意思決定の連続になります。
何時の便にするか、どこに何泊するか、などなど。

「動き出しが重い…」という気持ちに対して、1日1エントリと絡めて立ち向かいました。
動いたことをネタにして、自分用バックアップにすればいいじゃん!と。

今回手配したものメモ

  • 飛行機
    • 羽田〜沖縄
    • 沖縄〜伊丹(京都へは伊丹空港がよさそうでした)
  • ホテル
    • 沖縄
    • 京都

NLP2023 OKINAWAは2/13(月)までの参加登録がオススメ!

上で示した大会サイトより

言語処理学会第 29 回年次大会(NLP2023)は,2023 年 3 月 13~17 日の期間,5 日間の日程で開催いたします.
チュートリアルは 3 月 13 日午後 1 時頃に開始,本会議は 3 月 13 日午後 4 時頃から 16 日午後 7 時頃までの 4 日間です.

ハイブリッド開催です!

現在,現地とオンラインのハイブリッド開催の形態で準備を進めています.
現地とオンラインの両方から参加し,発表・聴講・議論をすることができます.

現在「事前参加登録」期間です。

事前参加登録締切 2023 年 02 月 13 日(月)

3/1(水)から直前受付が開始されますが、参加費がちょっとお高くなります。

会場は過去に訪れたことがありました。
イベントレポート | Tech BASE Okinawaに行ってきました! #TechBASEOkinawa - nikkie-ftnextの日記

最新情報は公式アカウントで確認するようにしています。

YAPC::Kyoto 2023はチケットが1/31(火)まで!お買い逃しなく📣

上で示した公式サイトより

Perlだけにとどまらない技術者たちが、好きな技術の話をし交流するカンファレンスで、技術者であれば誰でも楽しめるお祭りです!
今回のYAPC::Japanのテーマは「try/catch」。

YAPCハイブリッド開催です!

YAPC::Japanとして初めての試みとなる「会場と配信のハイブリッド形式」で開催されるこのカンファレンス (略)

トークのラインナップは先日発表されました。

めっちゃ濃くないですか? これは楽しみ!4
あとOSSメンテナの方とお話ししたいなと思ってます5

チケットは以下で買えますよ!(1/31(火)まで)
京都に行ける方、迷ったら買ってみてはいかがでしょうか。

公式アカウントさんによると、前日3/18(土)にもイベントが予定されているそうですよ

3/19(日)当日の懇親会はなしとのこと。

YAPCは公式ブログと公式Twitterを追いかけるといい感じに情報が入ってきそうですね。

終わりに

1日1エントリと絡めて、3月の技術イベント旅行の準備をガッと進めました!
めんどくさいものの手配は終わった… これ以上後回しにしなかった、グッジョブ、俺!

移動できる状態にはなったので、次はコンテンツ面での予習ですね。
これも1日1エントリと絡めてちょっとずつ進められるかも!

最後にどうしてもこれだけは。
📣YAPC::Kyoto 2023はチケットが1/31(火)まで、NLP2023は事前参加登録が2/13(月)までです!!

P.S. 最終責任時点

お気づきでしょうか、実は京都から東京に戻る手段については後回しにしていることに。

これは『アート・オブ・アジャイル デベロップメント』で知った「最終責任時点」の考え方を採用しています。
最終責任時点とは

決定を下しそこねると、重要な代替案がなくなる時点 (p.42)

です。

飛行機やホテルは日が近づくほど値上がりするので、予算に制限がある場合、ある時点をすぎると予算面で代替案がなくなりえます。
この「ある時点」が最終責任時点という理解です。

京都から東京への移動はおそらく新幹線なのですが、新幹線は今予約しても直前に予約しても、私にとっては代替案の量は同じくらいです。
事前にガチガチに計画するよりは、判断を遅らせられるものは遅らせて、選択肢が多く残った状態が私にとってはしっくり来ます。

『アート・オブ・アジャイル デベロップメント』にはよりエクストリームに旅行に適用する実践記がありますよ。
自由度高くてめっちゃいいなと思っています。


  1. ズバリなカードがあるのです! 旅の準備|カードギャラリー|マジック:ザ・ギャザリング 日本公式ウェブサイト
  2. その様子は イベントレポート | YAPC::Japan::Online 2022に参加、優勝超えのカンファレンスでした #yapcjapan - nikkie-ftnextの日記(全部で3本書きました)
  3. VSCodeConJPの登壇が終わって一息ついたのもあります。また、日が近づいてくるとお値段が上がりますからね。
  4. いまのところ一番目を引いたのは「JavaScriptのASTと戯れる」です。Perl以外の言語の話も豊富にあって、めっちゃ面白そう(語りたい。うずうず)
  5. Songmuさん「小さく始め、長く続けるOSS開発と貢献」めっちゃ聞きたい!

GitHub Actionsはね、後続ステップで参照できるようにあるステップで出力を定義する方法が、ワークフローコマンドから環境ファイルに変わったんだよ

はじめに

そこもムーンプリンセスなの?1、nikkieです。

今回はGitHub Actionsネタです。
自作したActionを微調整していたのですが、出力の設定方法が変わったことを知りました。

目次

GitHub Actions、簡単に用語の整理

「ステップ」に関連する用語を整理します。

GitHub Actionsは「ワークフロー」から構築されます。
.github/workflowsYAMLファイルを書いて定義するので、ワークフローと呼ばれるのかなと理解しています。

ワークフローは「ジョブ」から構成されます。
1つのジョブからも複数のジョブからも構成できます。
ワークフロー定義のYAMLファイルにはjobsというセクションがありますね。

ジョブは1つ以上の「ステップ」からなります。
YAMLjobsにはstepsを定義しますね。
1つのジョブの実行環境は同じで、ファイルシステムも共通です。
なのであるジョブでライブラリをインストールして、続くステップでライブラリのコマンドを実行できます。

つまり、ステップはジョブを構成するものです。
あるステップから後続のステップへはファイルシステムを通して情報をやり取りできますが、プログラミングでいう"変数を定義"っぽいこともできます。
それが、エントリタイトルの「後続ステップで参照できるようにあるステップで出力を定義」です。
あるコマンドの実行結果を別のステップで参照したいときに、私は使っています。

結論:ステップの出力の定義には、「環境ファイル」を使おう!

2022年10月の以下記事で、一部のワークフローコマンドの廃止がアナウンスされています(私は当時見落としました)。

これまではワークフローコマンドのset-outputでしたが、今後は環境ファイルの$GITHUB_OUTPUTとなります。

We are monitoring telemetry for the usage of these commands and plan to fully disable them on 31st May 2023.

(後半部分)2023年5月末でset-outputを無効化する計画、とのことです。

背景としては

To avoid untrusted logged data to use save-state and set-output workflow commands without the intention of the workflow author we have introduced a new set of environment files to manage state and output.

(前半部分)ワークフロー作成者が意図せずにset-outputワークフローコマンドを使って信頼できないデータが記録されるのを防ぐため、だそうです

定義の仕方が変わるだけで、参照の仕方は変わりません

廃止されるset-outputワークフローコマンド

シェルのechoコマンド & 特定の形式の出力でワークフローコマンドが使えます。
https://docs.github.com/ja/actions/using-workflows/workflow-commands-for-github-actions#about-workflow-commands

書式は以下のようになります。

echo "::workflow-command parameter1={data},parameter2={data}::{command value}"

「後続ステップで参照できるようにあるステップで出力を定義」するset-outputコマンドは以下の書式となります(廃止される書き方です)。

echo "::set-output name={key}::{value}"
  • set-outputコマンドのnameパラメタにkeyを渡す
  • set-outputコマンドの出力はvalueとする

set-outputコマンドを実行するステップにはIDを付与します。
後続のステップではsteps.<id>.outputs.<key>で参照できます!

# stepsのイメージです
- id: awesome
  run: echo "::set-output name=character::shion"
- run: |
    # あとに続くステップでは ${{ steps.awesome.outputs.character }} でshionを参照できます

こちらを使おう!環境ファイル$GITHUB_OUTPUT

https://docs.github.com/ja/actions/using-workflows/workflow-commands-for-github-actions#environment-files

ワークフローの実行中に、ランナーは特定のアクションを実行する際に使用できる一時ファイルを生成します。
これらのファイルへのパスは、環境変数を介して公開されます。

$GITHUB_OUTPUTは「ステップの出力パラメータの設定」に使う環境ファイルです。
環境ファイルには追記のリダイレクトを使って書き込みます。
書式は以下のようになります。

echo "{key}={value}" >> $GITHUB_OUTPUT

set-outputワークフローコマンドを使った例を書き換えてみます。

# stepsのイメージです
- id: awesome
  run: echo "character=shion" >> $GITHUB_OUTPUT
- run: |
    # あとに続くステップでは ${{ steps.awesome.outputs.character }} でshionを参照できます

終わりに

見落としていたset-outputコマンドの廃止に気付き、環境ファイルを使った書き方を確認しました。
知った時点では「廃止、つまり、不可逆な変更なのか!」と驚いたのですが、書き換えてみると環境ファイルを使ったほうが簡単な書式になるので納得感もあります。

この記事でset-outputワークフローコマンドの廃止を知った方、お早めに環境ファイル$GITHUB_OUTPUTに切り替えましょう。
私もいくつかのリポジトリで作業します!

参考文献

GitHub Actionsの用語とset-outputワークフローコマンドの書式は、『Software Design 2020年10月号』第2特集「3ステップでマスターするGitHub Actions」を参照しました。
非常に分かりやすく、GitHub Actionsを自作する上で参考にしている記事です。

最初に知った超頼れる文献ですが、今だともっと新しいものがあるかもしれません(ご存じの方ぜひ教えてください🙏)


  1. 『アイの歌声を聴かせて』「秘密はね、最後に明かされるんだよ」オマージュです。ここ尊すぎる…

読書ログ | #ミノ駆動本 11章「コメント」と、コメントつながりで思い出した書籍たち

はじめに

リンクスタート! nikkieです。

『良いコード/悪いコードで学ぶ設計入門』(通称ミノ駆動本1)の読書ログです。
11章「コメント」のログを残します。

目次

ミノ駆動本 11章「コメント」

公開されている目次を引用します。

11 コメント ―保守と変更の正確性を高める書き方―

11.1 退化コメント
11.2 コメントで命名をごまかす
11.3 意図や仕様変更時の注意点を読み手に伝えること
11.4 コメントのルール まとめ
11.5 ドキュメントコメント

読んでみて

  • 11.1〜11.2がコメントにまつわる悪魔の解説
  • 11.3〜11.4がコメントに関するミノ駆動さんの主張
  • 11.5がドキュメントコメントの話題(Pythonではdocstring)

と理解しました。

コメントと言えば、t_wadaさんのこのツイート

コードコメントには Why not を書こう

「なぜしないのか」「あえてやっていないこと2」を書く、ミノ駆動本と重なる部分もあるように思われます。
また、この中では「コミットログには Why」は結構意識するのですが、この4つは重ねていくほど効果がどんどん(=指数関数的に)高くなりそうという印象です。
このツイートがどの程度普遍的なのか今の私には判断が難しいのですが、私の中にはこちらを言ってくれるt_wadaさんを置いています🦁

リファクタリング』より、コメントもCode Smell

リファクタリングの必要性を示す不吉な兆候」がCode Smell(=コードの不吉な臭い)。
リファクタリング』の3章で解説されています。
その中の1つにコメントがあるんです!

Code Smellとなるコメントは、消臭剤として(Code Smellを消す意図で使う)のコメントです。
ミノ駆動本の「11.2 コメントで命名をごまかす」は、消臭剤として使う一例と思われます。

リファクタリング』ではCode Smellのコメントへの対処(どうリファクタリングするか)も解説されます。
例えば、関数の処理の一部を説明するコメントがある場合、「関数の抽出」で小さい関数に抽出し、説明コメントを消す(=関数名として表す3)ことができます!

3章の以下の一節は、私としては非常に賛成できるものでした。
コメントは最小限にしたい派です(過激派)

コメントの必要性を感じたときにはリファクタリングを行って、コメントを書かなくとも内容がわかるようなコードを目指すこと。(Kindle の位置No.2542-2543)

コメントと言ったらこの本!『リーダブルコード』

『リーダブルコード』にはコメントを扱った章が2章あります。

5章 コメントすべきことを知る
6章 コメントは正確で簡潔に

駆け出した頃に読んだ部分ですが、色々な本に出会って立ち戻ってくるとまた印象が変わってくるものですね。
5章には「監督のコメンタリー」としてコメントを入れるのをオススメする部分があります。
ミノ駆動本の「11.3 意図や仕様変更時の注意点を読み手に伝えること」やt_wadaさんの「Why notを書こう」と重なる部分かと思います。
最初に読んだ頃は素直に試してうまくいったりいかなかったりしたのですが、いろんなコードを見てくる中で、いまの私には『リファクタリング』にある「コメントは最小限」という考え方がしっくりくるな〜と感じています。

終わりに

ミノ駆動本 11章とコメントつながりで思い出した書籍の読書ログをアウトプットしました。
コメントを扱った書籍は他にも浮かびます。
一度「コメント」という切り口で徹底的に情報収集しても面白いかもですね〜

現段階の感触ですが、今回紹介した中でもコメントについての共通理解といえる部分は見出せそうですが、それぞれのコンテキストにおける主張(バイアス)もいくらか載っているように思われます(結構バラバラですからね)。
銀の弾丸はコメントにもないのだと思いますが、自分が置かれているのとは別の文脈においては、コメントはどんなふうに捉えられるんだろうというのが気になるところです。
私の場合も、文脈が変わると(例:仕事で書くコードとOSSのコード)、コメントの使い方を僅かですが変えているように思うのです。

P.S. ミノ駆動本_読書pyの予習

1/27(金)改め2/10(金)開催、ミノ駆動本_読書py事前もくもくの一環として13章を読んだのでした〜

短い章なのでさっと読めますよ〜



  1. 過去にも何度か取り上げています。読書ログ | #ミノ駆動本 13章「モデリング」とDeveloper eXperience Day 2021 - nikkie-ftnextの日記
  2. リファクタリング』で目指す「コードの自己文書化」の1つの状態と理解しています