Goで複数バージョンを管理する
(2021.10 更新)
まあここに書いてることそのままなのですが、日本語のメモとして。
go install
でダウンロード用のバイナリを取ってきて、downloadコマンドを打つ
$ go install golang.org/dl/go1.17.1@latest $ go1.17.1 download Downloaded 0.0% ( 16384 / 135929081 bytes) ... Downloaded 0.3% ( 360448 / 135929081 bytes) ... Downloaded 4.2% ( 5685248 / 135929081 bytes) ... Downloaded 9.0% ( 12287984 / 135929081 bytes) ... Downloaded 15.5% ( 21020576 / 135929081 bytes) ... Downloaded 21.0% ( 28524528 / 135929081 bytes) ... Downloaded 28.4% ( 38616896 / 135929081 bytes) ... Downloaded 34.9% ( 47415296 / 135929081 bytes) ... Downloaded 40.7% ( 55312032 / 135929081 bytes) ... Downloaded 47.6% ( 64700416 / 135929081 bytes) ... Downloaded 53.9% ( 73203232 / 135929081 bytes) ... Downloaded 58.9% ( 80035296 / 135929081 bytes) ... Downloaded 65.6% ( 89194304 / 135929081 bytes) ... Downloaded 72.8% ( 98942576 / 135929081 bytes) ... Downloaded 79.3% (107757552 / 135929081 bytes) ... Downloaded 85.9% (116735376 / 135929081 bytes) ... Downloaded 92.8% (126205120 / 135929081 bytes) ... Downloaded 100.0% (135929081 / 135929081 bytes) Unpacking /Users/kamatama41/sdk/go1.17.1/go1.17.1.darwin-amd64.tar.gz ... Success. You may now run 'go1.17.1'
GOROOT
はgo env GOROOT
で分かるので、GoLandとかIntelliJとかで使いたい場合はそこをSDKとして指定する。アンインストールする場合はGOROOT
のディレクトリを消す。
$ go1.17.1 env GOROOT /Users/kamatama41/sdk/go1.17.1
embulk-executor-remoteserver 0.4.0 リリース
このバージョンより、Embulk clientとserver間でTLSでの接続ができるようになりました。
設定方法 (クライアント)
まず、use_tls
オプションをtrueに設定してください。サーバ側が(クライアントにとって)既知のCA証明書でサインされた証明書を使っていれば、これだけでOKです*1。
exec: type: remoteserver hosts: ... use_tls: true
そうでない場合は、CA証明書をca_cert_path
に追加してください
exec: type: remoteserver hosts: ... use_tls: true ca_cert_path: path/to/ca.cert.pem
クライアント認証が必要な場合、クライアント証明書と秘密鍵がセットになったPKCS12ファイルのパスとパスワードをcert_p12_file
で指定してください。
exec: type: remoteserver hosts: ... use_tls: true cert_p12_file: path: path/to/cert/client.p12 password: xxxxx
設定方法 (サーバ)
EmbulkサーバをTLSの終端にする場合*2、以下の環境変数を設定してサーバを起動してください
USE_TLS=true
: TLS接続を有効にするREQUIRE_TLS_CLIENT_AUTH=true
: クライアント認証を有効にするCERT_P12_PATH
,CERT_P12_PASSWORD
: サーバ証明書と秘密鍵のペアのPKCS12ファイルパスとパスワードCA_CERT_PATH
: CA証明書のパス。クライアント証明書が(サーバにとって)未知のCA証明書でサインされてる場合に必要
例えばdocker-composeで設定する場合以下のようになるかと思います
version: '3' services: server: image: kamatama41/embulk-executor-remoteserver ports: - "30001:30001" volumes: - ./certs:/root/certs environment: USE_TLS: "true" REQUIRE_TLS_CLIENT_AUTH: "true" CERT_P12_PATH: /root/certs/embulk-server.local.p12 CERT_P12_PASSWORD: xxxxx CA_CERT_PATH: /root/certs/ca.cert.pem
Gradleで動的にプラグインを適用する
モチベーション
とあるプロジェクトでgradle-release pluginを使っているのですが、DockerでJarをビルドするときにこのプラグインを設定していると .git
ディレクトリをコピーしないと(プロジェクトがGitリポジトリじゃないと)最初のbuild.gradle
の検証で失敗してしまうのですが、無駄なレイヤーが増えるのでDockerコンテナ上に.git
はあまりコピーしたくない。なので、releaseタスクを実行するときのみ上記のプラグインを有効にしたい。
方法
こちらの記事 を参考にさせてもらいました。プラグインを指定するときにapply false
をつけてallprojects
ブロックの中で指定したプロパティがあるかをを使って遅延applyします。
before
plugins { id "net.researchgate.release" version "2.8.0" } release { git { requireBranch = 'master' } }
after
plugins { id "net.researchgate.release" version "2.8.0" apply false } allprojects { if (properties.get("enableReleasePlugin") == "true") { apply plugin: "net.researchgate.release" release { git { requireBranch = 'master' } } } }
これで、実行時に -PenableReleasePlugin=true
をつけたときのみプラグインが適用されます。
$ ./gradlew release -PenableReleasePlugin=true
CicleCIでDockerイメージを再利用する in 2019
CicleCIでDockerイメージを再利用する - かまたま日記3
こちらの記事の最新版です。 現在CircleCIはバージョン2で、Docker Layer Cachingという機能がありますが、残念ながら追加のフィーが必要です。というわけで、会社とかで使っててフィーを払える方はそちらを使うとして、個人のOSS活動などで払うのが厳しい方用に普通にCircleCI 2.0のファイルのキャッシュ機能を使った方法を解説します*1
前回に比べて以下の点が変わっています
- GoのようにMulti stage buildを使って多段ビルドを行う前提 (最初のビルドのステージは
as buider
でbuilderという名前がついてます) - Docker image を完全に再利用はしないで中間イメージを再利用する (
docker build
は毎回やる) docker save
後のtarファイルを更にgzに圧縮してリストアの時間短縮を図る
注意として、自分の環境では、数百メガのキャッシュをload cacheとsave cacheするので2~3分かかるのでキャッシュを使うことによって、それ以上短縮出来なければ、総時間は変わらないか悪くなる可能性もあります。
1. キャッシュ用のディレクトリとキャッシュキーを決める
ここではディレクトリは /home/circleci/docker-cache
、キャッシュキーは毎回変えたいので {{ .Branch }}-{{ .Revision }}
を使います
- restore_cache: keys: - v1-docker-{{ .Branch }}-{{ .Revision }} - v1-docker-{{ .Branch }}- - v1-docker- - save_cache: paths: - "/home/circleci/docker-cache" key: v1-docker-{{ .Branch }}-{{ .Revision }}
2. イメージをビルドする
今回は builder
のイメージを取っておきたいので、builderの部分は別にビルドしてbuilerタグを付けています。
IMAGE_NAME=my_app CACHE_FROM="--cache-from ${IMAGE_NAME}:builder --cache-from ${IMAGE_NAME}" docker build --pull ${CACHE_FROM} --target builder -t ${IMAGE_NAME}:builder . docker build --pull ${CACHE_FROM} -t ${IMAGE_NAME} .
3. イメージを保存する
docker save
で保存するイメージを docker history
コマンドで取得します。 builder
を取ってるのがミソです。
mkdir -p /home/circleci/docker-cache image_ids=$(docker history -q my_app:builder | grep -v '<missing>') docker save ${image_ids} | gzip > ${DOCKER_CACHE_FILE}
4. イメージを展開する
Gzip化しているのでgunzipコマンドで解答して docker load
に渡します
if [ -f /home/circleci/docker-cache/image.tar.gz ]; then gunzip -c /home/circleci/docker-cache/image.tar.gz | docker load fi
全体
キャッシュファイルのパスを変数に入れて、以下のような感じになります。関係ない部分*2は略してます。
version: 2 jobs: build: environment: DOCKER_CACHE_FILE: /home/circleci/docker-cache/image.tar.gz steps: - checkout - restore_cache: keys: - v1-docker-{{ .Branch }}-{{ .Revision }} - v1-docker-{{ .Branch }}- - v1-docker- - run: | name: Load Docker cache command: | if [ -f ${DOCKER_CACHE_FILE} ]; then gunzip -c ${DOCKER_CACHE_FILE} | docker load fi - run: | name: Docker build command: | IMAGE_NAME=my_app CACHE_FROM="--cache-from ${IMAGE_NAME}:builder --cache-from ${IMAGE_NAME}" docker build --pull ${CACHE_FROM} --target builder -t ${IMAGE_NAME}:builder . docker build --pull ${CACHE_FROM} -t ${IMAGE_NAME} . - run: name: Save Docker cache command: | mkdir -p $(dirname ${DOCKER_CACHE_FILE}) image_ids=$(docker history -q my_app:${t} | grep -v '<missing>') docker save ${image_ids} | gzip > ${DOCKER_CACHE_FILE} - save_cache: paths: - ${DOCKER_CACHE_FILE} key: v1-docker-{{ .Branch }}-{{ .Revision }}
embulk-executor-remoteserverを作った #Embulk
Embulkのexecutor pluginの仕組みとネットワークプログラミングを学びたかったので、勉強がてらこのようなプラグインを作ってみました。
できること
- Embulkのタスクの実行を別に立てた専用のサーバ(以下
Embulkサーバ
と呼びます)上で実行できる - 複数サーバにタスクの処理を分散させる
- サーバとの接続が切れたときに再接続する
できないこと (TODOs)
- Maven style pluginを実行する (実行できるのはGemのプラグインのみ) *1
- LocalExecutorのscatter modeのように1つのInputを複数のアウトプットに分ける (inとoutが1:1に紐付く)
- 複数バージョンのEmbulkを使う(サーバに組み込まれているEmbulk0.9.16を固定で使用)
- セキュアな仕組み
使い方
2019/04/06時点の最新は 0.2.1
です
Embulkサーバを起動する
基本的にはDockerコンテナ上で動かすことを想定していて、DockerHubにイメージをホストしているので、それをrunすれば起動できます。クライアント(plugin)と同じバージョンのイメージを使うことをおすすめしますが、多少違ってもたぶん動きます。コンテナ側は 30001
番のポートを開くので*2それを公開します。
$ docker run -p 30001:30001 -it --rm kamatama41/embulk-executor-remoteserver 14:55:48.805 [main] INFO c.g.kamatama41.nsocket.SocketServer - Starting server..
Embulkを起動する
exec
に type: remoteserver
を指定して hosts
に起動中のEmbulkサーバのホストを登録します。
exec: type: remoteserver hosts: - localhost:30001
この状態でEmbulk runをすると、サーバに接続に行き、タスクの実行を依頼します。 もっと詳しいチュートリアルは、こちらのexample を参照してください。
中身の話
このプラグインは、指定されたEmbulkサーバとTCPコネクションを張り、そのコネクションを通じてクライアントとサーバが相互にデータのやり取りを行います。例えばクライアントからはEmbulkEmbedを構築するのに必要なタスクの情報やシステムコンフィグ、OSSのプラグインを実行に必要なgemをZIPファイルにまとめて送ったりしています。逆にサーバからはクライアントに各タスクの進捗状況を送っており、クライアント側は終了の通知が来るまで待受をします。
いわゆるWebSocket的な双方向通信をしているのですが、この仕組みを自作しました。
まあnettyとか使ってWebSocketにしても良かったのですが、あんまり依存ライブラリを増やしたくなかったのとネットワークプログラミングの勉強も兼ねてスクラッチで作ってみたかったという次第です。nsocket
は NIOで作ったノンブロッキングsocketサーバとクライアント
の略です。大変だったけど結構いい勉強になりました。
実用性があるかは微妙なところですが、よかったら使ってみて感想をいただけると喜びます。
gradle-embulk-plugin v0.5.0 リリース
Release 0.5.0 · kamatama41/gradle-embulk-plugin · GitHub
embulk_*
のタスクを実行時の config.yml
output.yml
のオーバーライドを build.gradle
内ではなくプロパティを渡すようにしました。
以前
embulk { configYaml = "myconfig.yml" outputYaml = "myoutput.yml" }
今回以降
$ ./gradlew embulk_guess -PconfigYaml=myconfig.yml -PoutputYaml=myoutput.yml
プラグインの詳しい使い方は以下の記事を参照ください。
Lambdaオブジェクトの型パラメータを取るのは難しい
TL;DR
- ラムダオブジェクトの型パラメータを取得するスマートな方法は今の所見つかっていない
- もし基盤プログラムでそういうことをしたい場合は、ラムダを禁止して、匿名クラスを使う
- いい方法があったら教えてください
本文
Javaで基盤プログラム的なのを作るとき、ジェネリクスの型パラメータを取得したいことがあります。普通のクラスや匿名クラスの場合は以下のようなリフレクションのコードで取得することができます。
import java.lang.reflect.ParameterizedType; import java.util.function.Consumer; public class FooTest { public static void main(String[] args) { System.out.println(getGenericTypeParam(new Foo())); System.out.println(getGenericTypeParam(new Bar())); System.out.println(getGenericTypeParam(Baz)); } private static Class<?> getGenericTypeParam(Consumer consumer) { ParameterizedType type = (ParameterizedType) consumer.getClass().getGenericInterfaces()[0]; return (Class) type.getActualTypeArguments()[0]; } private static class Foo implements Consumer<String> { @Override public void accept(String s) {} } private static class Bar implements Consumer<Integer> { @Override public void accept(Integer s) {} } private static Consumer<Void> Baz = new Consumer<Void>() { @Override public void accept(Void aVoid) { } }; }
実行結果
class java.lang.String class java.lang.Integer class java.lang.Void
しかし、これがラムダになると、getGenericInterfaces
メソッドの結果が ParameterizedType
ではなく単なる java.lang.Object
のクラス型になり、ClassCastExceptionが発生してしまいます。
getGenericTypeParam((Consumer<Byte>) (b -> {}));
結果
Exception in thread "main" java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType
つまり、ラムダ式で生成された関数オブジェクトからは型パラメータの情報が消えているのです。これをどうにかして取得したいといろいろ模索していたのですが、結局ダメでした。一番惜しかったのはこちらのGistのやり方です。
ラムダ式が定義されているクラスで getDeclaredMethods
を使ってメソッド一覧を見ると、そのクラス内で定義されたラムダ式に対応したSyntheticメソッドが生成されています。
import java.lang.reflect.Method; public class FooTest { public static void main(String[] args) { Runnable task1 = () -> task2(); System.out.println(task1.getClass().getName()); task1.run(); System.out.println(); for (Method method : FooTest.class.getDeclaredMethods()) { System.out.println(method); } } private static void task2() { Runnable task2 = () -> {}; System.out.println(task2.getClass().getName()); } }
上記のプログラムを実行すると以下のような結果が出力されます。
FooTest$$Lambda$1/664223387 FooTest$$Lambda$2/666641942 public static void FooTest.main(java.lang.String[]) private static void FooTest.task2() private static void FooTest.lambda$task2$1() private static void FooTest.lambda$main$0()
ラムダ式で生成されたオブジェクトとSyntheticメソッドにはどちらも名前に $1
的なインデックスがついており、それぞれのインデックスが1対1で対応していそうです。そこで、先のGistに習って以下の getGenericTypeParamSmart
を追加します。
private static Class<?> getGenericTypeParamSmart(Consumer consumer) { String functionClassName = consumer.getClass().getName(); int lambdaMarkerIndex = functionClassName.indexOf("$$Lambda$"); if (lambdaMarkerIndex == -1) { // Not a lambda return getGenericTypeParam(consumer); } String declaringClassName = functionClassName.substring(0, lambdaMarkerIndex); int lambdaIndex = Integer.parseInt(functionClassName.substring(lambdaMarkerIndex + 9, functionClassName.lastIndexOf('/'))); Class<?> declaringClass; try { declaringClass = Class.forName(declaringClassName); } catch (ClassNotFoundException e) { throw new IllegalStateException("Unable to find lambda's parent class " + declaringClassName); } for (Method method : declaringClass.getDeclaredMethods()) { if (method.isSynthetic() && method.getName().startsWith("lambda$") && method.getName().endsWith("$" + (lambdaIndex - 1)) && Modifier.isStatic(method.getModifiers())) { return method.getParameterTypes()[0]; } } throw new IllegalStateException("Unable to find lambda's implementation method"); }
その上で、以下のコードを実行するとちゃんと型パラメータが取れてそうです
public static void main(String[] args) { System.out.println(getGenericTypeParamSmart(new Foo())); System.out.println(getGenericTypeParamSmart(new Bar())); System.out.println(getGenericTypeParamSmart(Baz)); System.out.println(getGenericTypeParamSmart((Consumer<Byte>) (b -> {}))); System.out.println(getGenericTypeParamSmart((Consumer<Long>) (l -> {}))); }
class java.lang.String class java.lang.Integer class java.lang.Void class java.lang.Byte class java.lang.Long
ただし、Gistにもコメントしましたが、ラムダ内でラムダを生成した場合、例えば、以下のパターンでは失敗します。
public static void main(String[] args) { Runnable task = () -> { System.out.println(getGenericTypeParamSmart(new Foo())); System.out.println(getGenericTypeParamSmart(new Bar())); System.out.println(getGenericTypeParamSmart(Baz)); System.out.println(getGenericTypeParamSmart((Consumer<Byte>) (b -> {}))); System.out.println(getGenericTypeParamSmart((Consumer<Long>) (b -> {}))); }; task.run(); }
class java.lang.String class java.lang.Integer class java.lang.Void class java.lang.Long Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
なぜでしょうか? ラムダオブジェクトとFooTestクラスに定義されているSyntheticメソッドを比較してみましょう
public static void main(String[] args) { Runnable task = () -> { Consumer<Byte> byteConsumer = b -> {}; Consumer<Long> longConsumer = l -> {}; System.out.println("byteConsumer: " + byteConsumer.getClass().getName()); System.out.println("longConsumer: " + longConsumer.getClass().getName()); }; System.out.println("task: " + task.getClass().getName()); task.run(); System.out.println(); for (Method method : FooTest.class.getDeclaredMethods()) { if (method.isSynthetic()) { System.out.println(method.toString()); } } }
これの実行結果は以下のようになります
task: FooTest$$Lambda$1/664223387 byteConsumer: FooTest$$Lambda$2/1349393271 longConsumer: FooTest$$Lambda$3/159413332 private static void FooTest.lambda$main$2() private static void FooTest.lambda$null$1(java.lang.Long) private static void FooTest.lambda$null$0(java.lang.Byte)
つまり:
task
オブジェクトFooTest$$Lambda$1
に対応するSyntheticメソッドはlambda$main$2
byteConsumer
オブジェクトFooTest$$Lambda$2
に対応するSyntheticメソッドはlambda$null$0
longConsumer
オブジェクトFooTest$$Lambda$3
に対応するSyntheticメソッドはlambda$null$1
に、なるわけです。番号の対応がずれてるので、間違ったメソッドを検索してしまっていたわけです。そういう訳で、この方法は使えませんでした。
そのあといろいろ調べてみましたが、型パラメータをちゃんと取得する方法は見つかりませんでした。というわけで、こういうプログラムを書きたいときは今の所はラムダを禁止したほうが良さそうです。
private static Class<?> getGenericTypeParam(Consumer consumer) { String functionClassName = consumer.getClass().getName(); if (functionClassName.contains("$$Lambda$")) { throw new UnsupportedOperationException("Lambda is not supported"); } ParameterizedType type = (ParameterizedType) consumer.getClass().getGenericInterfaces()[0]; return (Class) type.getActualTypeArguments()[0]; }
最終的なコードは以下のような感じになります。
import java.lang.reflect.ParameterizedType; import java.util.function.Consumer; public class FooTest { public static void main(String[] args) { System.out.println(getGenericTypeParam(new Foo())); System.out.println(getGenericTypeParam(new Bar())); System.out.println(getGenericTypeParam(Baz)); try { System.out.println(getGenericTypeParam((Consumer<Byte>) (b -> {}))); } catch (UnsupportedOperationException e) {} } private static Class<?> getGenericTypeParam(Consumer consumer) { String functionClassName = consumer.getClass().getName(); if (functionClassName.contains("$$Lambda$")) { throw new UnsupportedOperationException("Lambda is not supported"); } ParameterizedType type = (ParameterizedType) consumer.getClass().getGenericInterfaces()[0]; return (Class) type.getActualTypeArguments()[0]; } private static class Foo implements Consumer<String> { @Override public void accept(String s) {} } private static class Bar implements Consumer<Integer> { @Override public void accept(Integer s) {} } private static Consumer<Void> Baz = new Consumer<Void>() { @Override public void accept(Void aVoid) {} }; }