CLOVER🍀

That was when it all began.

RESTEasy+Vert.x(Handler)で遊ぶ

これは、なにをしたくて書いたもの?

以前、RESTEasyとVert.xでこんなエントリを書きました。

RESTEasy+Vert.x(Embedded Container)で遊ぶ - CLOVER🍀

こちらはVertxJaxrsServerResteasyDeploymentでデプロイするやり方でしたが、今回はVert.xにHandlerを設定する
やり方でやってみようと思います。

ドキュメントでいくと、ここですね。

Vert.x can also embed a RESTEasy deployment, making easy to use Jax-RS annotated controller in Vert.x applications:

Vert.x

環境

今回の環境は、こちら。

$ java --version
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.9.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-54-generic", arch: "amd64", family: "unix"

RESTEasyは、4.5.8.Finalを使います。

お題

足し算をするだけの、すごく単純なJAX-RSリソースを書きます。

前回と変えるのは、Vert.xとのインテグレーション方法と、もう少しVert.xで扱うリソースに踏み込んでみようかなと。

準備

Maven依存関係は、まずはこちら。

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-vertx</artifactId>
            <version>4.5.8.Final</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>4.5.8.Final</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>3.9.4</version>
        </dependency>

JSONも少し扱うので、Jackson2 Providerを足しています。

また、依存関係は後で少し増やします。

vertx-coreについては、自分で依存関係を追加する必要があります。RESTEasyが指している依存関係は3.7.1なのですが、
今回はなにも考えずに最新版を追加…。

https://github.com/resteasy/Resteasy/blob/4.5.8.Final/resteasy-dependencies-bom/pom.xml#L34

アプリケーションの雛形

まずは、ざっくりmainメソッドを持ったクラスを書きます。
src/main/java/org/littlewings/resteasy/vertx/App.java

package org.littlewings.resteasy.vertx;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler;
import org.jboss.resteasy.plugins.server.vertx.VertxResteasyDeployment;
import org.jboss.resteasy.spi.Registry;
import org.jboss.resteasy.spi.ResteasyDeployment;

public class App {
    public static void main(String... args) {
        Logger logger = Logger.getLogger(App.class);

        Vertx vertx = Vertx.vertx();
        HttpServer server = vertx.createHttpServer();

        ResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.start();

        Registry registry = deployment.getRegistry();
        registry.addPerRequestResource(/* JAX-RSリソースクラスを追加 */);

        server.requestHandler(new VertxRequestHandler(vertx, deployment));

        server.listen(8080);

        logger.infof("start server.");
    }
}

VertxRequestHandlerは、VertxJaxrsServerの内部でも使われているものになります。

https://github.com/resteasy/Resteasy/blob/4.5.8.Final/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/VertxJaxrsServer.java#L262

ドキュメント通り、HttpServer#requestHandlerVertxRequestHandlerResteasyDeploymentとともに設定すればOK…
と思いきや、少し落とし穴があって。

Vert.x

ここです。

        ResteasyDeployment deployment = new VertxResteasyDeployment();
        deployment.start();

この形態でやる時には、ResteasyDeployment#startを呼んでいないとRegistry#addPerRequestResourceや、これを使わない方法で
JAX-RSリソースクラスを追加しても起動時に失敗します。

JAX-RSリソースクラスを追加する前に呼んでおくのがポイントみたいです…。VertxJaxrsServerを使っている時にはこんなことは
起こりませんでしたが…?

また、Vert.xのスタイルに従うので、ここで追加するJAX-RSリソースクラスはブロックするコードを書いてはいけません。

        Registry registry = deployment.getRegistry();
        registry.addPerRequestResource(/* JAX-RSリソースクラスを追加 */);

When a resource is called, it is done with the Vert.x Event Loop thread, keep in mind to not block this thread and respect the Vert.x programming model, see the related Vert.x manual page.

シングルトンなJAX-RSリソースクラスとして設定していない場合(=今回のようにaddPerRequestResourceしている場合)は、
イベントループの呼び出しごとにJAX-RSリソースクラスのインスタンスが作られます。

// Create an instance of resource per Event Loop

JAX-RSリソースクラスを書く

では、まずシンプルにJAX-RSリソースクラスを書きます。
src/main/java/org/littlewings/resteasy/vertx/CalcResource.java

package org.littlewings.resteasy.vertx;

import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

@Path("calc")
public class CalcResource {
    @GET
    @Path("query/add")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public int add(@QueryParam("a") int a, @QueryParam("b") int b) {
        return a + b;
    }

    @POST
    @Path("json/add")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> calc(Map<String, Object> body) {
        int a = (Integer) body.get("a");
        int b = (Integer) body.get("b");

        int result = a + b;

        return Map.of("result", result);
    }
}

起動。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.resteasy.vertx.App

...

11月 22, 2020 3:49:49 午後 org.littlewings.resteasy.vertx.App main
INFO: start server.

確認。

### GET
$ curl -i 'localhost:8080/calc/query/add?a=2&b=3'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

5


### POST
$ curl -i -XPOST  -H 'Content-Type: application/json' localhost:8080/calc/json/add -d '{"a": 1, "b": 5}'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json

{"result":6}

OKです。

@ContextでVert.xのクラスを使う

ドキュメントを読むと、Vert.xに関するオブジェクトをインジェクションできるようです。

Vert.x objects can be injected in annotated resources:

Vert.x

以下の4つが使えるようです。

  • io.vertx.core.Context
  • io.vertx.core.http.HttpServerRequest
  • io.vertx.core.http.HttpServerResponse
  • io.vertx.core.Vertx

https://github.com/resteasy/Resteasy/blob/4.5.8.Final/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/RequestDispatcher.java#L90-L93

で、これらを使ってこんなJAX-RSリソースクラスを作ってみました。
src/main/java/org/littlewings/resteasy/vertx/VertxCalcResource.java

package org.littlewings.resteasy.vertx;

import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;

@Path("vertx/calc")
public class VertxCalcResource {
    @GET
    @Path("query/add")
    @Produces(MediaType.TEXT_PLAIN)
    public int queryAdd(@Context HttpServerRequest request) {
        int a = Integer.parseInt(request.getParam("a"));
        int b = Integer.parseInt(request.getParam("b"));

        return a + b;
    }

    @GET
    @Path("query/add2")
    @Produces(MediaType.TEXT_PLAIN)
    public void queryAdd2(@Context HttpServerRequest request, @Context HttpServerResponse response) {
        int a = Integer.parseInt(request.getParam("a"));
        int b = Integer.parseInt(request.getParam("b"));

        int result = a + b;

        response
                .putHeader("Content-Length", Integer.toString(Integer.toString(result).length()))
                .write(Integer.toString(result));
    }

    @POST
    @Path("json/add")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public void jsonAdd(@Context io.vertx.core.Context context, @Context HttpServerResponse response, Map<String, Object> body) {
        int a = (Integer) body.get("a");
        int b = (Integer) body.get("b");

        context.executeBlocking(promise -> {
            int result = a + b;
            promise.complete(result);
        }, res -> response
                .putHeader("Content-Length", Integer.toString(String.valueOf(res.result()).length()))
                .write(String.format("{\"result\": %d", res.result())));
    }
}

まずはこちら。Vert.xのHttpServerRequestを使って、リクエストの内容を受け取ります。

    @GET
    @Path("query/add")
    @Produces(MediaType.TEXT_PLAIN)
    public int queryAdd(@Context HttpServerRequest request) {
        int a = Integer.parseInt(request.getParam("a"));
        int b = Integer.parseInt(request.getParam("b"));

        return a + b;
    }

確認。

$ curl -i 'localhost:8080/vertx/calc/query/add?a=2&b=3'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

5

こちらはOKです。

問題はこちら。

    @GET
    @Path("query/add2")
    @Produces(MediaType.TEXT_PLAIN)
    public void queryAdd2(@Context HttpServerRequest request, @Context HttpServerResponse response) {
        int a = Integer.parseInt(request.getParam("a"));
        int b = Integer.parseInt(request.getParam("b"));

        int result = a + b;

        response
                .putHeader("Content-Length", Integer.toString(Integer.toString(result).length()))
                .write(Integer.toString(result));
    }

一見、動くように見えるんですが

$ curl -i 'localhost:8080/vertx/calc/query/add2?a=2&b=3'
HTTP/1.1 200 OK
Content-Length: 1

5

裏で例外を吐いているのと、よくよく見るとContent-Typeなどが設定されていません。

11月 22, 2020 4:09:43 午後 io.vertx.core.impl.ContextImpl
重大: Unhandled exception
java.lang.IllegalStateException: Response head already sent
    at io.vertx.core.http.impl.HttpServerResponseImpl.checkHeadWritten(HttpServerResponseImpl.java:638)
    at io.vertx.core.http.impl.HttpServerResponseImpl.setStatusCode(HttpServerResponseImpl.java:132)
    at org.jboss.resteasy.plugins.server.vertx.VertxHttpResponse.prepareChunkStream(VertxHttpResponse.java:158)
    at org.jboss.resteasy.plugins.server.vertx.VertxHttpResponse.finish(VertxHttpResponse.java:183)
    at org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler.lambda$handle$0(VertxRequestHandler.java:77)

「すでにヘッダーを送ってしまったよ」と言っているのですが、かといってContent-Lengthのヘッダーを付与しないようにすると

$ curl -i 'localhost:8080/vertx/calc/query/add2?a=2&b=3'
HTTP/1.1 500 Internal Server Error
transfer-encoding: chunked

こうなりますし、Content-Lengthを付けなさいと怒られます。

11月 22, 2020 4:10:31 午後 org.jboss.resteasy.plugins.server.vertx.VertxRequestHandler lambda$handle$0
ERROR: RESTEASY019525: Unexpected
org.jboss.resteasy.spi.UnhandledException: java.lang.IllegalStateException: You must set the Content-Length header to be the total size of the message body BEFORE sending any data if you are not using HTTP chunked encoding.
    at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
    at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:218)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:519)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:261)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:161)

レスポンスの終了時にいろいろと触るようなので、Vert.xのHttpServerResponseで結果を送り出すのとは相性が悪いようです。

https://github.com/resteasy/Resteasy/blob/4.5.8.Final/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/VertxHttpResponse.java

このあたりに関しては、Vert.xのクラスを使うのではなく、素直にJAX-RSのリクエスト、レスポンスの仕組みに乗った方が
よさそうですね。

もうひとつ、仮にブロックするコードを書くならこんなVert.xのContextを使ってこんな感じかな?と。

    @POST
    @Path("json/add")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public void jsonAdd(@Context io.vertx.core.Context context, @Context HttpServerResponse response, Map<String, Object> body) {
        int a = (Integer) body.get("a");
        int b = (Integer) body.get("b");

        context.executeBlocking(promise -> {
            int result = a + b;
            promise.complete(result);
        }, res -> response
                .putHeader("Content-Length", Integer.toString(String.valueOf(res.result()).length()))
                .write(String.format("{\"result\": %d", res.result())));
    }

先ほどの結果とHttpServerResponseを使っていることからうまくいかなさそうなのは想像に難くないのですが、実行すると
こうなります。

$ curl -i -XPOST  -H 'Content-Type: application/json' localhost:8080/vertx/calc/json/add -d '{"a": 1, "b": 5}'
HTTP/1.1 204 No Content

HttpServerResponseが見つからないと…。

11月 22, 2020 4:19:05 午後 io.vertx.core.impl.ContextImpl
重大: Unhandled exception
org.jboss.resteasy.spi.LoggableFailure: RESTEASY003880: Unable to find contextual data of type: io.vertx.core.http.HttpServerResponse
    at org.jboss.resteasy.core.ContextParameterInjector$GenericDelegatingProxy.invoke(ContextParameterInjector.java:124)
    at com.sun.proxy.$Proxy43.putHeader(Unknown Source)
    at org.littlewings.resteasy.vertx.VertxCalcResource.lambda$jsonAdd$1(VertxCalcResource.java:53)
    at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:327)
    at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
    at io.vertx.core.impl.EventLoopContext.lambda$executeAsync$0(EventLoopContext.java:38)
    at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
    at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)

よくよく見ると、ここでResteasyContextに登録している先はThreadLocalなので、そりゃあそうか、と…。

            ResteasyContext.pushContext(Context.class, context);
            ResteasyContext.pushContext(HttpServerRequest.class, req);
            ResteasyContext.pushContext(HttpServerResponse.class, resp);
            ResteasyContext.pushContext(Vertx.class, context.owner());

https://github.com/resteasy/Resteasy/blob/4.5.8.Final/server-adapters/resteasy-vertx/src/main/java/org/jboss/resteasy/plugins/server/vertx/RequestDispatcher.java#L90-L93

とすると、RESTEasy内でVert.xの仕組みを使って「ブロックする処理を書くな」はわかりますが、「書けない」になってるような。

こうなると、RESTEasyと統合するよりもVert.xだけで処理を書いた方が良さげな気がしますね?

Reactorを使う

それならということで、少し視点を変えてReactive Streamsの実装も入れ込むことにしましょう。

今回はReactorを使います。

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-reactor</artifactId>
            <version>4.5.8.Final</version>
        </dependency>

        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-core</artifactId>
            <version>3.4.0</version>
        </dependency>

        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <version>2.0.SP1</version>
        </dependency>

Reactorも、とりあえずRESTEasyが見ているものは置いておいて、最新版を入れました。

https://github.com/resteasy/Resteasy/blob/4.5.8.Final/resteasy-dependencies-bom/pom.xml#L102

どうやらAnnotationLiteralなど一部のクラスに依存があるようで、CDIAPIを依存関係に入れないと起動しませんでした…。

Reactorを使って作成した、JAX-RSリソースクラス。
src/main/java/org/littlewings/resteasy/vertx/ReactorCalcResource.java

package org.littlewings.resteasy.vertx;

import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

import reactor.core.publisher.Mono;

@Path("reactor/calc")
public class ReactorCalcResource {
    @GET
    @Path("query/add")
    @Consumes(MediaType.TEXT_PLAIN)
    @Produces(MediaType.TEXT_PLAIN)
    public Mono<Integer> queryAdd(@QueryParam("a") int a, @QueryParam("b") int b) {
        return Mono.just(a + b);
    }

    @POST
    @Path("json/add")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Mono<Map<String, Object>> jsonAdd(Map<String, Object> body) {
        return Mono
                .just(body)
                .map(b -> Map.of("result", ((Integer) b.get("a")) + ((Integer) b.get("b"))));
    }
}

こちらは問題なく動作します。

$ curl -i 'localhost:8080/reactor/calc/query/add?a=2&b=3'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: text/plain;charset=UTF-8

5


$ curl -i -XPOST  -H 'Content-Type: application/json' localhost:8080/reactor/calc/json/add -d '{"a": 1, "b": 5}'
HTTP/1.1 200 OK
transfer-encoding: chunked
Content-Type: application/json

{"result":6}

動作しますが、これをやるならReactorではなくてMutinyかなぁという気もしますし、そこまでやるならQuarkusに行った方が…
という気も(CDIも使えるし)。

まとめ

RESTEasy+Vert.xということで、Vert.xのHandlerを直接使う形でコードを書きつつ、少し脱線まで。
RESTEasy内で、ムリにVert.xの存在を見せない方がいいなーと思うようにはなりました。

だいぶVert.x側の雰囲気もわかってきたので、ほどほどに使い分けをしつつという感じでいきましょう。

GitHub REAT APIで、releaseやtagを取得する

時々使うのですが、全然覚えられないのでメモすることにしました。

覚えられないし、どこに書いてあるかも見つけられなかったり…。

特定のリポジトリの、releaseやtagを取得するという話です。

Releases | GitHub Developer Guide

Repositories | GitHub Developer Guide

TerraformのGitHubリポジトリをお題にしてみましょう。

GitHub - hashicorp/terraform: Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.

latest releaseを取得する

個人的には、1番使います。latest releaseの取得。

Get the latest release

URLは、こんな感じ。

https://api.github.com/repos/:owner/:repo/releases/latest

Terraformでの例。このエントリの時点では、v0.13.5です。

$ curl https://api.github.com/repos/hashicorp/terraform/releases/latest
{
  "url": "https://api.github.com/repos/hashicorp/terraform/releases/32883332",
  "assets_url": "https://api.github.com/repos/hashicorp/terraform/releases/32883332/assets",
  "upload_url": "https://uploads.github.com/repos/hashicorp/terraform/releases/32883332/assets{?name,label}",
  "html_url": "https://github.com/hashicorp/terraform/releases/tag/v0.13.5",
  "id": 32883332,
  "node_id": "MDc6UmVsZWFzZTMyODgzMzMy",
  "tag_name": "v0.13.5",
  "target_commitish": "master",
  "name": "v0.13.5",
  "draft": false,
  "prerelease": false,
  "created_at": "2020-10-21T18:48:54Z",
  "published_at": "2020-10-21T19:33:54Z",
  "assets": [

  ],
  "tarball_url": "https://api.github.com/repos/hashicorp/terraform/tarball/v0.13.5",
  "zipball_url": "https://api.github.com/repos/hashicorp/terraform/zipball/v0.13.5",
  "author": {
    "login": "hashicorp-ci",
    "id": 37350809,
    "node_id": "MDQ6VXNlcjM3MzUwODA5",
    "avatar_url": "https://avatars1.githubusercontent.com/u/37350809?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/hashicorp-ci",
    "html_url": "https://github.com/hashicorp-ci",
    "followers_url": "https://api.github.com/users/hashicorp-ci/followers",
    "following_url": "https://api.github.com/users/hashicorp-ci/following{/other_user}",
    "gists_url": "https://api.github.com/users/hashicorp-ci/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/hashicorp-ci/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/hashicorp-ci/subscriptions",
    "organizations_url": "https://api.github.com/users/hashicorp-ci/orgs",
    "repos_url": "https://api.github.com/users/hashicorp-ci/repos",
    "events_url": "https://api.github.com/users/hashicorp-ci/events{/privacy}",
    "received_events_url": "https://api.github.com/users/hashicorp-ci/received_events",
    "type": "User",
    "site_admin": false
  },
  "body": "## 0.13.5 (October 21, 2020)\n\nBUG FIXES:\n* terraform: fix issue where the provider configuration was not properly attached to the configured provider source address by localname ([#26567](https://github.com/hashicorp/terraform/issues/26567))\n* core: fix a performance issue when a resource contains a very large and deeply nested schema ([#26577](https://github.com/hashicorp/terraform/issues/26577))\n* backend/azurerm: fix an issue when using the metadata host to lookup environments ([#26463](https://github.com/hashicorp/terraform/issues/26463))\n\n"
}

jqを使って、tag_nameだけ取得。

$ curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | jq .tag_name
"v0.13.5"

クォートも外すとコマンドで使えそうですね。

$ curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | jq -r .tag_name
v0.13.5

特定のreleaseを取得する

特定のreleaseをid指定で取得します。

Get a release

https://api.github.com/repos/:owner/:repo/releases/:release_id

この場合のidは、こんな感じです。これでTerraform 0.13.0を指します。

$ curl https://api.github.com/repos/hashicorp/terraform/releases/29526969
{
  "url": "https://api.github.com/repos/hashicorp/terraform/releases/29526969",
  "assets_url": "https://api.github.com/repos/hashicorp/terraform/releases/29526969/assets",
  "upload_url": "https://uploads.github.com/repos/hashicorp/terraform/releases/29526969/assets{?name,label}",
  "html_url": "https://github.com/hashicorp/terraform/releases/tag/v0.13.0",
  "id": 29526969,
  "node_id": "MDc6UmVsZWFzZTI5NTI2OTY5",
  "tag_name": "v0.13.0",
  "target_commitish": "master",
  "name": "v0.13.0",
  "draft": false,
  "prerelease": false,
  "created_at": "2020-08-10T17:46:08Z",
  "published_at": "2020-08-10T18:03:34Z",
  "assets": [

  ],
  "tarball_url": "https://api.github.com/repos/hashicorp/terraform/tarball/v0.13.0",
  "zipball_url": "https://api.github.com/repos/hashicorp/terraform/zipball/v0.13.0",
  "author": {
    "login": "hashicorp-ci",
    "id": 37350809,
    "node_id": "MDQ6VXNlcjM3MzUwODA5",
    "avatar_url": "https://avatars1.githubusercontent.com/u/37350809?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/hashicorp-ci",
    "html_url": "https://github.com/hashicorp-ci",
    "followers_url": "https://api.github.com/users/hashicorp-ci/followers",
    "following_url": "https://api.github.com/users/hashicorp-ci/following{/other_user}",
    "gists_url": "https://api.github.com/users/hashicorp-ci/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/hashicorp-ci/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/hashicorp-ci/subscriptions",
    "organizations_url": "https://api.github.com/users/hashicorp-ci/orgs",
    "repos_url": "https://api.github.com/users/hashicorp-ci/repos",
    "events_url": "https://api.github.com/users/hashicorp-ci/events{/privacy}",
    "received_events_url": "https://api.github.com/users/hashicorp-ci/received_events",
    "type": "User",
    "site_admin": false
  },
  "body": "...省略..."
}

releaseをtag名で取得する

id指定ではさすがにちょっと…なので、tag名指定で。

Get a release by tag name

https://api.github.com/repos/:owner/:repo/releases/tags/:tag

v0.13.2での例。

$ curl https://api.github.com/repos/hashicorp/terraform/releases/tags/v0.13.2
{
  "url": "https://api.github.com/repos/hashicorp/terraform/releases/30591217",
  "assets_url": "https://api.github.com/repos/hashicorp/terraform/releases/30591217/assets",
  "upload_url": "https://uploads.github.com/repos/hashicorp/terraform/releases/30591217/assets{?name,label}",
  "html_url": "https://github.com/hashicorp/terraform/releases/tag/v0.13.2",
  "id": 30591217,
  "node_id": "MDc6UmVsZWFzZTMwNTkxMjE3",
  "tag_name": "v0.13.2",
  "target_commitish": "master",
  "name": "v0.13.2",
  "draft": false,
  "prerelease": false,
  "created_at": "2020-09-02T14:36:55Z",
  "published_at": "2020-09-02T14:51:01Z",
  "assets": [

  ],
  "tarball_url": "https://api.github.com/repos/hashicorp/terraform/tarball/v0.13.2",
  "zipball_url": "https://api.github.com/repos/hashicorp/terraform/zipball/v0.13.2",
  "author": {
    "login": "hashicorp-ci",
    "id": 37350809,
    "node_id": "MDQ6VXNlcjM3MzUwODA5",
    "avatar_url": "https://avatars1.githubusercontent.com/u/37350809?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/hashicorp-ci",
    "html_url": "https://github.com/hashicorp-ci",
    "followers_url": "https://api.github.com/users/hashicorp-ci/followers",
    "following_url": "https://api.github.com/users/hashicorp-ci/following{/other_user}",
    "gists_url": "https://api.github.com/users/hashicorp-ci/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/hashicorp-ci/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/hashicorp-ci/subscriptions",
    "organizations_url": "https://api.github.com/users/hashicorp-ci/orgs",
    "repos_url": "https://api.github.com/users/hashicorp-ci/repos",
    "events_url": "https://api.github.com/users/hashicorp-ci/events{/privacy}",
    "received_events_url": "https://api.github.com/users/hashicorp-ci/received_events",
    "type": "User",
    "site_admin": false
  },
  "body": "## 0.13.2 (September 02, 2020)\n\nNEW FEATURES:\n\n* **Network-based Mirrors for [Provider Installation](https://www.terraform.io/docs/commands/cli-config.html#provider-installation)**: As an addition to the existing capability of \"mirroring\" providers into the local filesystem, a network mirror allows publishing copies of providers on an HTTP server and using that as an alternative source for provider packages, for situations where directly accessing the origin registries is impossible or undesirable. ([#25999](https://github.com/hashicorp/terraform/issues/25999))\n\nENHANCEMENTS:\n\n* backend/http: add support for configuration by environment variable. ([#25439](https://github.com/hashicorp/terraform/issues/25439))\n* command: Add support for provider redirects to `0.13upgrade`. If a provider in the Terraform Registry has moved to a new namespace, the `0.13upgrade` subcommand now detects this and follows the redirect where possible. ([#26061](https://github.com/hashicorp/terraform/issues/26061))\n* command: Improve `init` error diagnostics when encountering what appears to be an in-house provider required by a pre-0.13 state file. Terraform will now display suggested `terraform state replace-provider` commands which will fix this specific problem. ([#26066](https://github.com/hashicorp/terraform/issues/26066))\n\nBUG FIXES:\n\n* command: Warn instead of error when the `output` subcommand with no arguments results in no outputs. This aligns the UI to match the 0 exit code in this situation, which is notable but not necessarily an error. ([#26036](https://github.com/hashicorp/terraform/issues/26036))\n* terraform: Fix crashing bug when reading data sources during plan with blocks backed by objects, not collections ([#26028](https://github.com/hashicorp/terraform/issues/26028))\n* terraform: Fix bug where variables values were asked for twice on the command line and provider input values were asked for but not saved ([#26063](https://github.com/hashicorp/terraform/issues/26063))\n\n"
}

releaseやtagの一覧を取得

一覧取得。

release。

List releases

https://api.github.com/repos/:owner/:repo/releases
$ curl https://api.github.com/repos/hashicorp/terraform/releases

tags。

List repository tags

https://api.github.com/repos/:owner/:repo/tags
$ curl https://api.github.com/repos/hashicorp/terraform/tags