実行中のシェルスクリプトをchattr(1)を使ってimmutableにするというのはどうか
[追記]
実行中のシェルスクリプトをchattr(1)を使ってimmutableにするというのはどうか - その手の平は尻もつかめるさb.hatena.ne.jp調べてみたけどこれが良さそう <a href="https://stackoverflow.com/a/3399850/1921216" target="_blank" rel="noopener nofollow">https://stackoverflow.com/a/3399850/1921216</a>
2022/01/02 17:02
このブックマークコメントで指摘されましたが、immutableにするまでもなくこのラッパースクリプトを噛ませると良さそう。
#!/bin/bash # usage: # sh-run.sh script-you-want-to-run.sh args... set -ue file="$1" script() { source "$file" } script ${@:2:($#-1)}
[追記ここまで]
sh/bashで実行したシェルスクリプトを実行中に上書きするとシェルスクリプトが再読み込みされ、意図しない挙動をするという現象が昨年末に話題になりました。
bash は、シェルスクリプトの実行中に適時シェルスクリプトを読み込みます。この挙動による副作用を認識できておらず、実行中のスクリプトが存在している状態でスクリプトの上書きによりリリースしてしまったことで、途中から修正したシェルスクリプトの再読み込みが発生し、結果的に未定義の変数を含む find コマンドが実行されてしまいました。
https://www.iimc.kyoto-u.ac.jp/services/comp/pdf/file_loss_insident_20211228.pdf
これは簡単に挙動を確認できます。例えば以下のようなシェルスクリプト script.sh
を用意して、
#!/bin/bash sleep 10 echo "hello"
bash script.sh
として実行します。そしてsleepしている10秒間に
#!/bin/bash sleep 10 echo "yo"
などと書き換えると再読み込みが実施され、sleep終了後にはstdoutに hello
ではなく yo
が表示されることなります。
今回は単純に「echoする内容」を書き換えただけなので再読み込み後の挙動はわかりやすいものでしたが、しかし大規模にスクリプトを書き換えた際にそれがどのように振る舞うかは予測が難しいものになります。
この実行中の再読み込み挙動は多くの場合望ましくないものだと思います。
というわけで実行中にchattr(1)を利用してimmutableにすることで、ファイル内容の変更や削除を防ぐというのはどうだろうか、という提案です。
例えば、
sudo chattr +i script.sh
としてあげると script.sh
はimmutableとなり、ファイルの削除や名前変更、ファイルの書き込みなどが禁止されます。
it cannot be deleted or renamed, no link can be created to this file, most of the file's metadata can not be modified, and the file can not be opened in write mode.
https://man7.org/linux/man-pages/man1/chattr.1.html
すなわち実行したいシェルスクリプトファイルをimmutableにしてあげると、そのファイルに対する変更が禁止されるため意図しない再読み込みも防ぐことが可能となります。安全そうですね。
従って、以下のようなシェルスクリプトを実行するためのラッパースクリプトを噛ませることで「シェルスクリプトに自動的にchattr +i
しつつ実行する」というふうにしてやるとオペミスを防げて安全かつ便利なのではないかと思いました。
使い方の例: sh-run.sh script-you-want-to-run.sh args...
chattrでimmutable属性を付与するには実行ユーザーがrootあるいはCAP_LINUX_IMMUTABLE
capabilityを有している必要があります。なので上記のラッパーではsudoを付けてchattrを実行しています。
自分はシェスクリプトで書かれた長めのバッチ処理を実行することがしばしばあるので、この運用を試しにやってみようかなと思います。
なお、zsh script.sh
などとしてbashあるいはsh以外のシェル処理系で実行する場合はこのような再読み込み挙動は発生しません。zsh等をalternativeとして使うというのも一つの防護策かもしれないですね。
古いaws/aws-lambda-goでAWS Lambdaのcontainer image runtimeを使うと刺さる
具体的に言うと、aws/aws-lambda-go@v1.18.0よりも前のバージョンでAWS Lambdaのcontainer image runtimeを使うとハンドラが呼び出されず、タイムアウトするまで刺さります。
例えば以下のような非常に簡単なLambda Functionをデプロイした時、
package main import ( "context" "fmt" "github.com/aws/aws-lambda-go/events" "github.com/aws/aws-lambda-go/lambda" ) func handler(ctx context.Context, event events.DynamoDBEvent) error { fmt.Println("CALLED") return nil } func main() { lambda.Start(handler) }
古いaws/aws-lambda-goを利用していると、以下のログのように CALLED
は表示されずタイムアウトまで実行を引っぱります。つまり handler()
が呼ばれることなくタイムアウトをしているという挙動であることがわかります。
START RequestId: ...snip... Version: $LATEST END RequestId: ...snip... REPORT RequestId: ...snip... Duration: 3003.52 ms Billed Duration: 3000 ms Memory Size: 128 MB Max Memory Used: 10 MB 2021-12-20T02:49:56.683Z ...snip... Task timed out after 3.00 seconds
このライブラリのバージョンをv1.18.0以降のバージョンにする *1 と、しっかりとハンドラが呼ばれて意図通りの動きをします。
START RequestId: ...snip... Version: $LATEST CALLED END RequestId: ...snip... REPORT RequestId: ...snip... Duration: 9.12 ms Billed Duration: 924 ms Memory Size: 128 MB Max Memory Used: 26 MB Init Duration: 914.31 ms
go1.x
で既に動いているLambda FunctionをそのままContainerに移植してみたところ、この問題で絶妙にハマってしまいました。Lambdaの実行ログには特にそれらしいエラーメッセージも出てこないし、そもそもハンドラ関数が呼ばれないのが謎すぎた……。
それはそうとしてv1.18.0のリリースノートにはそれらしい変更について言及が無いんですが、どれが該当する変更なんでしょう。これなのでしょうか? https://github.com/aws/aws-lambda-go/pull/298。
[追記]
上記の件はやはりこのPull Requestが関連するとのこと https://github.com/aws/aws-lambda-go/pull/298。
container runtimeはcustom runtimeと同じ挙動(自分でRuntime APIにアクセスする)なのでバージョンアップが必要ですね / “古いaws/aws-lambda-goでAWS Lambdaのcontainer image runtimeを使うと刺さる - その手の平は尻もつかめるさ” https://t.co/HkVVTi8wR2
— fujiwara (@fujiwara) 2021年12月20日
[追記ここまで]
さておきライブラリのバージョンは上げないと駄目ですね。ヤル気があるので最新まで上げました。以上です。
*1:つまりv1.18.0は大丈夫
テクノブレーン被害者アドベントカレンダー Day 19
この記事はテクノブレーン被害者アドベントカレンダーの19日目として書かれています。このアドベントカレンダーは今まさに作りましたから、参加者は自分しかいません。他に被害者がいたら続きを書いておいてください。
この記事は特定の企業に対する苦情および批判が含まれます。お前だ、テクノブレーン。
こんなことが横行していては、「リクルーティング」という職業の価値が著しく毀損されてしまうし、ソフトウェアエンジニアリング産業自体がスポイルされていってしまう。
明確に、俺は強く怒っている。お前たちは「駄目」だ。
TL;DR
本題
「トヨタITソリューションズから電話来ましたよ」ということで折り返しの電話をかけたらテクノブレーンだったんだけどコレやばくない? 他社の名前を騙ってリクルーティングの電話を本社にかけてくるのマジで仁義無いと思うんだけど
— moznion (@moznion) 2021年12月17日
こういったことが、自分の把握している限り今年に入ってから5件以上発生しています。
だいたい、以下のパターンです。
- なんらかの有名企業の名前が付いた子会社っぽい社名を名乗ってきて、「用事があるから」として所属企業の代表番号に電話をかけてきて、折り返しの電話を要求してきます。
- 携帯電話の番号で電話をかけてきます。タチが悪いですね。
- 仮に折り返しの電話をかける際、非通知設定、Skype、Twilioなどから電話をかけると取ってくれません。悲しいですね。俺は闘う準備ができているのだぞ。
ヘッドハンターからオフィスに電話かかってきて「折り返してくれ」という連絡がくるのが最近ちょくちょくあり迷惑すぎる。やめろ。しかし俺は慈悲深いので非通知で折り返しているが非通知の電話は取らないらしい。めげないぞ、鬼電してます。
— moznion (@moznion) 2021年12月17日ヘッドハンター、とにかく非通知やSkypeやTwilioの電話を取らない。そんなことで良いのか、お前の仕事に対する熱意はそんなものなのか。リスクを取らずして成功はない、逃げるな、戦え。俺がお前の新しい仕事を探してやる。
— moznion (@moznion) 2021年12月17日
と、こんな感じです。こんな電話は決して取り継がないでください。
もちろん、折り返しの電話は皆さん絶対にかけないでください! しかしあえて電話をかけてみると……*1
相手「もしもし」 (ここで決して自身が何者かは名乗らない)
僕「もしもし、電話を頂いた id:moznion (本当は本名を名乗っている) ですけど」
相手「はい」
僕「どういったご要件ですか、トヨタITソリューションズさんとのことでしたが、あなたは誰ですか」
相手「わたくし、テクノブレーンの……」
僕「アッ、ハイ、もう大丈夫でs」
(ここでブツッと電話が切れる)
という感じで、こちらが真摯に拒否しようとしているのにも関わらず途中で電話をブチ切りしてきます。無礼にも程がありますね。偽名だろうが、ナカガワ、貴様だ。
まとめ
テクノブレーン及び類似の業者に告ぐ。
- 代表電話で取り継いでくれるメンバーも、俺も、お前のように暇ではない。クソみたいな電話をかけてくるんじゃねえ。
- これ以上、同じ手口でリクルーティングをしかけてくるようであれば相応の対応を検討するのでそのつもりで。
- ご提案:
テクノブレーンの悪行に対する制裁について考えていたんだけど、テクノブレーンからかかってきたきた電話番号を全部引っ越し侍に登録するのが良いのではないか、という意見がありました
— moznion (@moznion) 2021年12月18日
- ご提案:
人類全員、誰もテクノブレーン及び類似の業者を、求職、転職、その他あらゆる目的で利用してはなりません。本当にゴミカス。
リクルーティングというのは企業組織を形作るための営みなのであって、重要な経営行為の1つであるように思います。そんな重大なイベントについて、このような詐欺的な行いが横行していては絶対にならない。
以上です。テクノブレーンさん側から何か申し開きがある場合はコメント欄からどうぞ。
*1:無いと思うけど、なにか仕事で関係あったっけ……と思って念のためかけたというのがあります、そういったメンタリティをハックしてきてるんだろうなあ
SORACOM ArcをESP32のArduinoで動かすsoracom-arc-esp32-arduinoのご紹介
こんにちは、株式会社ソラコムでソフトウェア等のエンジニアをやっているmoznionです。
普段ブログには書かない所属を宣言するのはなぜか。それはこれが株式会社ソラコム Advent Calendar 2021 17日目の記事だからです。というわけで記事が書かれます。
前日のアドベントカレンダー16日目は @0x6b さんによる soratun を改造して AWS Lambda から簡単に SORACOM Arc を使ってみました でした。そして本記事もSORACOM Arcの記事になります。連チャンしていて景気が良いですね。
表題の通り、SORACOM ArcをESP32のArduinoで動かすためのライブラリであるsoracom-arc-esp32-arduinoをご紹介します。
SORACOM Arcというサービスはザックリ説明すると「WireGuardを使ってデバイスとSORACOMプラットフォームとの間にトンネルを張り、そのトンネル越しに通信することでSORACOMのサービスを使えるようにするサービス」です。つまり従来は必須であって携帯電話回線を使わなくてもSORACOMサービスが使えるという接続サービスになります。
なお本ライブラリはArduinoのLibrary Registryにも登録してありますので、ArduinoのLibrary Managerから利用が可能です。
使い方は至って簡単で、このライブラリ (SoracomArcESP32.h
) をincludeしてちょっとしたコードを書いてやることですぐにSORACOM Arcを利用することができます。
#include <SoracomArcESP32.h> #include <WiFi.h> #include <HTTPClient.h> #define BAUDRATE 115200 static SoracomArc soracomArc; void setup() { Serial.begin(BAUDRATE); while (!Serial) ; WiFi.begin("__YOUR_AP_SSID__", "__YOUR_AP_PASSWORD__"); while (!WiFi.isConnected()) { delay(1000); } configTime(0, 0, "ntp.nict.jp", "time.google.com", "pool.ntp.org"); std::string soracomAPIAuthKeyID = "keyId-__YOUR_SORACOM_API_KEY_ID__"; std::string soracomAPIAuthKey = "secret-__YOUR_SORACOM_API_AUTH_KEY__"; std::string caCert = "-----BEGIN CERTIFICATE-----\n" "...\n" "__CA_CERTIFICATION_STRING__\n" "...\n" "-----END CERTIFICATE-----\n"; SoracomAPI soracomAPIClient(soracomAPIAuthKeyID, soracomAPIAuthKey, caCert); std::string simID = "__YOUR_SIM_ID__"; while (!soracomArc.activate(soracomAPIClient, simID)) { delay(5000); } log_i("setup finished"); } void loop() { HTTPClient http; http.begin("http://metadata.soracom.io/"); http.setConnectTimeout(1000); http.setTimeout(1000); int statusCode = http.GET(); String payload = http.getString(); log_i("http status: %d", statusCode); log_i("%s", payload.c_str()); http.end(); delay(30000); }
このコードは以下のシークエンスに従って動作します。
1. Wi-Fiに接続する
2. NTPを用いてシステムの時刻合わせをする
3. SORACOM APIのauthKeyIdとauthKeyを組み合わせて認証を行ない、apiKeyとapiTokenを取得する
4. 上記のapiKey/apiTokenを用いて使用するArcのSIMについてセッションの再生成を行う。この再生成時にWireGuardのcredentialsを動的に発行する
5. 生成したArcのセッション情報を引いてくる
6. WireGuardのcredentials情報とセッション情報に基いてWireGuardトンネルを張る
7. Arcが有効に。上記の例では30秒ごとにメタデータサービスにアクセスし、SIMの情報を取得している
という感じです。コード中のCA certについては https://api.soracom.io
に対するものを良い感じに埋め込んでください。
内部的には@cinimlさんのWireGuard-ESP32-Arduinoを使ってWireGuardの接続を行なっています。このライブラリの技術詳細についてはSORACOM Advent Calendar 2021 7日目の@cinimlさんによるWireGuard for ESP32の実装的なところという記事でつまびらかにされているので、併せて是非ご参照ください。
もちろん、WireGuardの設定情報をそのまま利用して利用することも可能です。その例はこちらにございます: https://github.com/soracom-labs/soracom-arc-esp32-arduino/tree/main/examples/boot-static-wg-credentials
既知の課題としては、SORACOM APIを使う方法にせよWireGuardの設定をそのまま使う方法にせよセンシティヴな情報をボード上で取り扱う必要が出てくるため、それらをセキュアな領域に置くあるいは何らかセキュアな方法で操作する必要があります。細心の注意を払っていただければと思います。
というわけでsoracom-arc-esp32-arduinoのご紹介でした。詳しい使い方についてはREADMEやexamplesをご参照ください。
ESP32 ArduinoでSORACOM Arcが動作するとプロトタイピング等で活躍してくれそうで夢が広がりそうです。ぜひご利用ください!
おまけ
PlatformIOを使ってライブラリ開発してたんですけど、これ死ぬほど便利な一方ハマりどころがちらほらあるのでそういった落し穴について記録しておきます。
platform = espressif32
を指定していると古いバージョンが降ってくる
これ新しいバージョンを明示しないと古いのを使い続けてしまいます。結構ドラスティックにプログラミングインターフェイスが変わってたりするんで、バージョン間違えると確実にハマります。
ここから欲しいバージョンを探してきて、 platform = espressif32@3.4.0
みたいな感じで指定してあげると良いです。
Go Genericsを使ってgo-optionalを書いた / Go Generics感想
Go Genericsがどんなもんか試してみたかったので、これを使ってOptionの実装を書いてみました。
基本的な使い方としてはSynopsisを読んでもらえばわかると思いますが、ユーティリティとしては
IsSome()
IsNone()
Take()
TakeOr()
TakeOrElse()
Filter()
Map()
MapOr()
Zip()
ZipWith()
Unzip()
UnzipWith()
あたりを取り揃えております。examplesも併せてご覧いただくとおおよその使い方の雰囲気が掴めると思います。
利用のためにはまだunstableな最新版 (go1.18) を使う必要があるので、gotipとかを使って新しい処理系を引っぱってくる必要があります。
で、GoのGenericsを使ってみた感想としてdefault valueとかconstraintとかどうするんだよと悪戦苦闘していたのですが、id:codehexさんの書かれた記事にほぼほぼ書かれていることを一通り終わった後に気付きました。
で、概ね同じような所感を持ったのですが、それだけだと芸が無いので僕個人としてのGenericsを使ったときの感想について記しておきます。
メソッドに型パラメータが持てない
例えばこのようなメソッドについて考えてみましょう。
type Something struct { } func (s *Something) Echo[V any](v V) V { return v }
なんとなく構文的にはvalidなように見えますが、これをコンパイルすると
./main.go:6:25: methods cannot have type parameters
./main.go:6:26: invalid AST: method must have no type parameters
というエラーが吐かれます。メソッド (つまりレシーバがある) 時にはそのメソッドに対して型パラメータが付けられないということのようですね。
例えば以下のようにメソッドにすることをやめるとコンパイルは通るようになります。
func Echo[V any](s *Something, v V) V { return v }
こういった実挙動のため、go-optionalでは Map()
等の関数実行時の型パラメータに依存するユーティリティについてはメソッドではなく関数として実装しています。
ref: https://github.com/moznion/go-optional/blob/b0ada9baa88672d2f0fc11a1487842da170b5f92/option.go#L83
なんとかジェネリクスのconstraintをtype assertionで分岐させられないか
例えば、go-optionalでは「Option[T]の値が、Tの或る値を持っているか」を確認するメソッドである Contains(v T)
を実装しようと思ったのですが
type Option[T any] struct { value T exists *struct{} } func (o Option[T]) Contains(v T) bool { if o.IsNone() { return false } return v == o.value }
というふうに素朴に実装すると ./option.go:20: invalid operation: cannot compare v == o.value (operator == not defined on T)
という風なコンパイルエラーが出てきます。そりゃそうだ。
これについては上のcodehexさんの記事にもありましたが、現状あるconstraintを満足させるためには別の型 (型パラメータ) を付けて実装する必要があります。
たとえばこれだと通る。
type ComparableOption[T comparable] struct { value T exists *struct{} } func (o ComparableOption[T]) Contains(v T) bool { if o.IsNone() { return false } return v == o.value }
あくまで例えばの話ですが、any
についてtype assertionのようにconstraintのチェックができれば便利だよな〜と思い、例えば以下のように……
// o is a value of Option[T any]
v, ok := o.(Option[T comparable])
とはいえまーこれあんま筋良くない気がするな、コンパイル時のtype erasureとかもあるだろうし……
型パラメータにタプルを持たせられない
例えばこういう書き方はできません。
func Zip[T, U any](opt1 Option[T], opt2 Option[U]) Option[(T, U)] {
...
}
まあそういうもんですよ、と言われたらその通りなのでgo-optionalでは Pair
という構造体を用意して Option[Pair[T, U]]
を返却するようにしました。
ref: https://github.com/moznion/go-optional/blob/b0ada9baa88672d2f0fc11a1487842da170b5f92/option.go#L108
pkg.go.dev がgodocを読み込んでくれないっぽい?
https://pkg.go.dev/github.com/moznion/go-optional
stableなruntime versionではまだ未定義の構文が多く含まれているからか、godocが解釈できないっぽい? pkg.go.dev に一向にドキュメントが出てこなくて地味に困っています……
[追記] バイナリのフットプリントについて
Genericsを使ったときと使わないときでビルドされたバイナリのフットプリントに差が出るのだろうか、という素朴な疑問があったので雑に比較。
環境は go version devel go1.18-4083a6f Wed Nov 17 14:10:29 2021 +0000 darwin/amd64
genericsなし:
package main import "fmt" type SomethingStr struct { v string } type SomethingInt struct { v int } func main() { s := SomethingStr{ v: "foo", } i := SomethingInt{ v: 123, } fmt.Println(s) fmt.Println(i) }
genericsあり:
package main import "fmt" type Something[T any] struct { v T } func main() { s := Something[string] { v: "foo", } i := Something[int] { v: 123, } fmt.Println(s) fmt.Println(i) }
結果:
-rwxr-xr-x 1 user user 1846368 11 18 11:11 generics* -rwxr-xr-x 1 user user 1846368 11 18 11:27 no_generics*
1バイトも変わらぬ結果に。面白いですね。とはいえこれは最適化戦略やコードに依存する気がするのでもっと複雑なコードを書くと変わってくる可能性がある気が……あんま信用しないでください。
だいたいそんな感じでした。手習い的にGoのGenericsを使ってみましたが、けっこうシンプルかつ現時点でも普通に使えるな、という肌触りです。
ここは型の推論が効くだろ (他の言語だと効きそうに見える)、と思って型パラメータの記述をサボると「型パラメータを明示しろや」とコンパイラに怒られたりもするわけですが、まあそこはgoらしさ・シンプルさを優先したということなのでしょう。機能と実装複雑性・コンパイルタイムのトレードオフという見方をしました。
MySQLのJSON Data Typeの値に対し、明示的なキャスト無しに `BETWEEN`, `IN()`, `GREATEST()`, `LEAST()` を使ってはならない
表題の通り、MySQLのJSON Data Typeの値に対しては、明示的なキャスト無しに BETWEEN
, IN()
, GREATEST()
そして LEAST()
を使ってはいけません。
MySQLむずかしい pic.twitter.com/YKoadLbaG2
— すぎゃーん💯 (@sugyan) 2021年9月7日
本記事はこれに係る話題で、id:sugyan さんに Slack で相談を受けて「僕もそれハマったことあるな」と調べたところ以下のドキュメントに辿りつきました。
これはMySQL 8.0のJSON Data Typeに関するドキュメントですが、このドキュメントの Comparison and Ordering of JSON Values
というセクションに
The following comparison operators and functions are not yet supported with JSON values:
- BETWEEN
- IN()
- GREATEST()
- LEAST()
A workaround for the comparison operators and functions just listed is to cast JSON values to a native MySQL numeric or string data type so they have a consistent non-JSON scalar type.
と明記されています。これらの演算子・関数については「未対応」ということみたいですね。
これがどういうことか実例を見てみましょう。
前提として、JSON_EXTRACT()
を使って取り出した値はその値自体がJSON Data Typeとして扱われます。
mysql> SELECT JSON_TYPE(JSON_EXTRACT('{"a":101,"b":99}', '$.a')); +----------------------------------------------------+ | JSON_TYPE(JSON_EXTRACT('{"a":101,"b":99}', '$.a')) | +----------------------------------------------------+ | INTEGER | +----------------------------------------------------+ 1 row in set (0.00 sec)
なおこの反例として、仮にこの値をキャストしてみると JSON_TYPE()
はエラーを返却します。
mysql> SELECT JSON_TYPE(CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.b') AS SIGNED INTEGER)); ERROR 3146 (22032): Invalid data type for JSON data in argument 1 to function json_type; a JSON string or JSON type is required.
さて、このJSON Data Typeの値について単純な比較演算子を使って数値比較をしてみましょう。比較演算子についてドキュメントには
JSON values can be compared using the =, <, <=, >, >=, <>, !=, and <=> operators.
とあるので正常に動きそうです。
mysql> SELECT JSON_EXTRACT('{"a":101,"b":99}', '$.a') > JSON_EXTRACT('{"a":101,"b":99}', '$.b'); +-----------------------------------------------------------------------------------+ | JSON_EXTRACT('{"a":101,"b":99}', '$.a') > JSON_EXTRACT('{"a":101,"b":99}', '$.b') | +-----------------------------------------------------------------------------------+ | 1 | +-----------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
このSQLはJSON Data TypeがINTEGER
の値である 101
と 99
について比較したもの、すなわち 101 > 99
ですが、1
が返却されているので正しく動作していそうです。
一方で同じ値に対し GREATEST()
を利用するとどうなるでしょうか。
mysql> SELECT GREATEST(JSON_EXTRACT('{"a":101,"b":99}', '$.a'), JSON_EXTRACT('{"a":101,"b":99}', '$.b')); +--------------------------------------------------------------------------------------------+ | GREATEST(JSON_EXTRACT('{"a":101,"b":99}', '$.a'), JSON_EXTRACT('{"a":101,"b":99}', '$.b')) | +--------------------------------------------------------------------------------------------+ | 99 | +--------------------------------------------------------------------------------------------+ 1 row in set, 1 warning (0.00 sec)
おやおや、期待した値は 101
なのですが 99
が返却されています。
これは辞書順なのでしょうか……? というところから暗黙的なキャストやその他もろもろを疑っていたのですが、結論としては冒頭の通り「未対応」なので使ってはいけないということのようです。
というわけで、提案されているワークアラウンドを実行してみましょう。JSON INTEGERをSIGNED INTEGERに明示的にキャストして試してみます。
mysql> SELECT GREATEST(CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.a') AS SIGNED INTEGER), CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.b') AS SIGNED INTEGER)); +--------------------------------------------------------------------------------------------------------------------------------------------+ | GREATEST(CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.a') AS SIGNED INTEGER), CAST(JSON_EXTRACT('{"a":101,"b":99}', '$.b') AS SIGNED INTEGER)) | +--------------------------------------------------------------------------------------------------------------------------------------------+ | 101 | +--------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
はい、期待通りの結果になりましたね。良かった良かった。
そしてこの挙動について、実はMySQL側も警告メッセージを残していることがわかります。
mysql> SHOW WARNINGS; +---------+------+----------------------------------------------------------------------------------------------------+ | Level | Code | Message | +---------+------+----------------------------------------------------------------------------------------------------+ | Warning | 1235 | This version of MySQL doesn't yet support 'comparison of JSON in the LEAST and GREATEST operators' | +---------+------+----------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
--show-warnings
を有効にしておくとクエリの実行時に気付くこともできますが、この手のクエリを実行した時に失敗させる方法は見付かりませんでした。strictモードとかを色々いじってはみたのですが。なにか良い方法はあるんでしょうか?
macOSでDocker Desktopをアンインストールしてdocker-cli + docker-machineで動かすようにする
Docker Desktopがここ最近活発に開発されているというか、かなり見た目がオシャレになってきてて「ヤル気あるな〜」と思って眺めていたのですが、なるほど有料化するということなのですね。
Docker Desktop remains free for personal use, education, non-commercial open source projects, and small businesses (fewer than 250 employees AND less than $10M USD in annual revenue).
Commercial use of Docker Desktop in larger enterprises (more than 250 employees OR more than $10 million USD in annual revenue) requires a Docker Pro, Team or Business subscription for as little as $5 per user per month.
実際、こういうところに注力して利益を得ていくというのは良いことだと思います! 頑張れdocker!
それはそうとしてDocker Desktopの機能をあまり使ってこず、実際のところcliでdockerが操作できればよい、つまりdocker engineが動けば良いという感じだったので、docker-machineを使ってローカルでdockerデーモンのためのVMを動かし、ホストからそのVMにcliを繋いでdockerを使うという方法をやっていくこととします。どことなく懐しいですね。podman使うとかでも良かったんですけど、ひとまずdocker cliでやります。
まずDocker Desktopをアンインストールする。
"Troubleshoot" メニューを開きます。この虫マークは "Troubleshoot" というメニューだったのですね。バグレポートボタンだと思ってた……
アンインストールしましょう。これで完了です。
そしてdocker cliとdocker-machineを入れる。
$ brew install docker docker-machine
で、docker-machineを使ってローカルにVMを立ててそこでdockerデーモンを動かすこととする。このとき、ローカルマシンにあらかじめVirtualBoxがインストールされている必要があります。
やりかたとしては右のページに従うと良い: Get started with Docker Machine and a local VM | Docker Documentation
おおざっぱに書くと以下のようなかんじ。
$ docker-machine create --driver virtualbox default $ echo 'eval "$(docker-machine env default)"' >> .bash_profile
そして docker ps
などと打ってみてちゃんとレスポンスが返ってくると成功です。
Docker Desktopで動かしていた時と比較するとなにか制限等があるかもしれませんがそれは遭遇した時に考える、あるいは遭遇したらLinux Desktopに乗り換えるなどを検討していきたいと思います。実際Linux Desktopへ移行していきたい……
[追記]
良くみてみるとdocker-machineの開発が停滞しているように見えるので、そこは懸念ポイントと言えそうですね……
github.com
まあなんかあったら手でVM運用していきましょう。俺たちにはそれができるはずだ。
[追記]
どれくらい実用的かはわかりませんが docker-machine は gitlab が fork してました https://t.co/RQkIQp7JOp
— Hiroshi SHIBATA (@hsbt) 2021年9月1日
Gitlabがforkしたdocker-machineがあるとのこと。