C アプリケーションから varlink の API を使って Container を操作する

※記事は全て個人の見解です。会社・組織を代表するものではありません。

# はじめに
[varlink](https://varlink.org/) の C 言語用のライブラリ [libvarlink](https://github.com/varlink/libvarlink)を使ってみた、および、libvarlink を使って Container を操作してみたことの紹介です。

* libvarlink を使って、varlink を使用するクライアントアプリケーションを実装します。
* 作成したアプリケーションを使って、Container を操作します。
* Container の操作には [podman](https://github.com/containers/libpod) を使います。
  podman には varlink を使った [API](https://github.com/containers/libpod/blob/master/API.md)が用意されており、API を用いてアプリケーションから Pod や Container の操作等が可能です。

# 環境

* Fedora 29 

# 準備

* 必要なパッケージのインストール
 
```bash:
]# dnf install libvarlink libvarlink-devel varlink-cli podman gcc jq
```

* Podman Remote API Service を start

```bash
]# systemctl start io.podman.service
```
これで、API 通信用の socket が ```/run/podman/io.podman``` に作成されます。
varlink コマンドを実行して以下が確認できれば準備OK。

```bash
]$ varlink call -m unix:/run/podman/io.podman/io.podman.Ping
{
  "ping": {
    "message": "OK"
  }
}
```
ちなみに、この varlink コマンドは Rust で実装されているようです。

# クライアントアプリケーション実装と動作確認
/usr/include/varlink.h に記載されている varlink のライブラリ関数を使ってよろしくコーディング
(サンプルコードは記事の末尾に記載)して、コンパイル。

```bash
]$ gcc -lvarlink -o varlink-c varlink-c.c 

]# ./varlink-c 
Usage: varlink-c -a varlink-address -m varlink-method [-p parameter]
Ex. varlink-c -a unix:/run/podman/io.podman -m io.podman.Ping
]# 
```
できあがったバイナリ varlink-c には、API 通信用の socket、メソッド名、メソッドのパラメータを引数として渡します。

Podman の API は [API.md](https://github.com/containers/libpod/blob/master/API.md) に記載されています。
試しに func Ping を実行してみます。socket 名に unix:/run/podman/io.podman、メソッド名に io.podman.Ping を渡して
実行してみると・・・

```bash
]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.Ping | jq
{
  "ping": {
    "message": "OK"
  }
}
]# 
```
前述の varlink コマンドと同じ結果になり、うまく動作しているようです。
ちなみに 標準出力を jq コマンドに渡しているのは、JSON の処理が面倒くさくて jq 先生に頼っているためです。

# Container 操作

さっそく Container を作ってみます。nginx の Container を func CreateContainer を使って作ります。
メソッド名は io.podman.CreateContainer、パラメータには以下の JSON を渡しています。

```json
{
  "create": {
    "image": "nginx",
    "command": [
      "nginx",
      "-g",
      "daemon off;"
    ]
  }
}
```

```bash
]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.CreateContainer -p "{\"create\": {\"image\" : \"nginx\", \"command\" : [\"nginx\", \"-g\", \"daemon off;\"] }}" | jq
{
  "container": "66adaf4953a61061b99ac94d687a908e6cc72e0884e4301bdbf4eb3bc37b6190"
}
]#
```
ID が 66adaf4953a6 の nginx の Container が作成されました。
つづいて、この Container を動かしてみます。

```bash
]# ./varlink-c -a unix:/run/podman/io.podman -m io.podman.StartContainer -p "{\"name\": \"66adaf4953a6\"}"  | jq
{
  "container": "66adaf4953a61061b99ac94d687a908e6cc72e0884e4301bdbf4eb3bc37b6190"
}
```

podman ps コマンドで確認してみると・・・

```bash
]# podman ps
CONTAINER ID  IMAGE  COMMAND               CREATED         STATUS             PORTS  NAMES
66adaf4953a6  nginx  nginx -g daemon o...  51 seconds ago  Up 32 seconds ago         stoic_jang
]#
```

ちゃんと動いているようです。

# おわりに
私が調べた限りですが、libvarlink を使ったサンプルが見当たらなかったので、本記事が何らかの参考になれば幸いです。
varlink には Go、Rust、Python 用のライブラリがあるので、あえて C で書かなくてもいいよね、という話かも。。。

本記事ではクライアント側を実装してみましたが、libvarlink には API Server を実装するためのライブラリも用意されています。

# サンプルコード

libvarlink のライブラリ関数の説明については、/usr/include/varlink.h のコメントを参照。

```c:varlink-c.c
#include <varlink.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/epoll.h>

#define TIMEOUT 100

const char *progname = "varlink-c";

static long check_event(VarlinkConnection *connection)
{
	struct epoll_event events;
	int epollfd;
	long nfds;
	long ret = 0;

	epollfd = epoll_create1(EPOLL_CLOEXEC);
	if (epollfd == -1) {
		fprintf(stderr, "ERR: epoll_create1: %s (errno: %d)\n", strerror(errno), errno);
		return errno;
	}

	events.events = varlink_connection_get_events(connection);
	events.data.ptr = connection;

	ret = epoll_ctl(epollfd, EPOLL_CTL_ADD,
			varlink_connection_get_fd(connection),
			&events);
	if (ret == -1) {
		fprintf(stderr, "ERR: epoll_ctl: %s (errno: %d)\n", strerror(errno), errno);
		ret = errno;
		goto free_fd;
	}

	for (;;) {
		nfds = epoll_wait(epollfd, &events, 1, TIMEOUT);
		if (nfds == -1) {
			fprintf(stderr, "ERR: epoll_wait: %s (errno: %d)\n", strerror(errno), errno);
			ret = errno;
			break;
		} else if (!nfds)
			continue;
		else {
			ret = varlink_connection_process_events(connection, events.events);
			if (ret < 0)
				fprintf(stderr, "ERR: varlink_connection_process_events: %s\n",
						varlink_error_string(labs(ret)));
			break;
		}
	}

free_fd:
	close(epollfd);

	return ret;
}

static long callback(VarlinkConnection *connection,
			  const char *error,
			  VarlinkObject *parameters,
			  uint64_t flags,
			  void *userdata)
{
	VarlinkObject **out = userdata;
	long ret = 0;

	if (error) {
		fprintf(stderr, "ERR: %s: %s\n", __func__, error);
		ret = -1;
	} else
		*out = varlink_object_ref(parameters);

	return ret;
}

static void usage(void)
{
	fprintf(stderr, "Usage: %s -a varlink-address -m varlink-method [-p parameter]\n"
			"Ex. %s -a unix:/run/podman/io.podman -m io.podman.Ping\n",
			progname, progname);
	exit(EXIT_FAILURE);
}

int main(int argc, char **argv)
{
	VarlinkConnection *connection;
	VarlinkObject *parameters;
	VarlinkObject *out;
	char *varlinkaddr = NULL;
	char *varlinkmethod = NULL;
	char *param;
	int addr_flg = 0, method_flg = 0, param_flg = 0;
	int status = EXIT_FAILURE;
	long ret;
	int c;
	char *result = NULL;

	while ((c = getopt(argc, argv, "a:m:p:")) != EOF)
		switch (c) {
		case 'a':
			varlinkaddr = optarg;
			addr_flg = 1;
			break;
		case 'm':
			varlinkmethod = optarg;
			method_flg = 1;
			break;
		case 'p':
			param = optarg;
			param_flg = 1;
			break;
		default:
			usage();
		}

	if (!addr_flg || !method_flg)
		usage();

	ret = varlink_connection_new(&connection, varlinkaddr);
	if (ret < 0) {
		fprintf(stderr, "ERR: varlink_connection_new: ret: %s\n",
					varlink_error_string(labs(ret)));
		exit(status);
	}

	if (param_flg) {
		ret = varlink_object_new_from_json(&parameters, param);
		if (ret < 0) {
			fprintf(stderr, "ERR: varlink_object_new_from_json: %s\n",
					varlink_error_string(labs(ret)));
			goto free_connection;
		}
	} else {
		ret = varlink_object_new(&parameters);
		if (ret < 0) {
			fprintf(stderr, "ERR: varlink_object_new: %s\n",
					varlink_error_string(labs(ret)));
			goto free_connection;
		}
	}

	ret = varlink_connection_call(connection, varlinkmethod, parameters, 0,
				       callback, &out);
	if (ret < 0) {
		fprintf(stderr, "ERR: varlink_connection_call: %s\n",
					varlink_error_string(labs(ret)));
		goto free_object;
	}

	ret = check_event(connection);
	if (ret)
		goto free_object;

	ret = varlink_object_to_json(out, &result);
	if (ret < 0) {
		varlink_object_unref(out);
		goto free_object;
	}

	fprintf(stdout, "%s\n", result);

	free(result);

	status = EXIT_SUCCESS;

	varlink_object_unref(out);
free_object:
	varlink_object_unref(parameters);
free_connection:
	varlink_connection_free(connection);

	exit(status);
}
```