Finch の内部実装を見てみた。

## 今 話題になっている Finch

https://qiita.com/yoshii0110/items/692018729b5b2f41b9ac

などを見ていただければ分かりますが、Finch は `ローカルマシン上に仮想環境とコンテナランタイム、ビルドツールなど一式を楽〜に導入` できるツールになっています。
そのコードが 

https://github.com/runfinch/finch

にあります。
コードは Go で書かれていて、Cobra（Go の CLIツール）を使って CLIのコマンドラインを作っています。
簡単に言えば バージョン 0.1.0 の名前から分かるように、Cobra で nerdctl や lima を呼び出しているだけの簡潔な内容に見えます。

それでは、その内容を見てみましょう。

## Cobra のエントリーポイント

Cobra の説明は、

https://zenn.dev/tama8021/articles/22_0627_go_cobra_cli

を参照してみて下さい。

Cobra のエントリーポイントは、`finch/cmd/main.go` にあります（コードは [ここ](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/cmd/main.go) にあります）。
この `main.go` の `newApp` で コマンドラインの登録を行なっていて、それまでは logger や path などの初期化を行なっています。

まず、大枠で説明すると、

```golang
    usage := fmt.Sprintf("%v <command>", finchRootCmd)
	rootCmd := &cobra.Command{
		Use:           usage,
		Short:         "Finch: open-source container development tool",
		SilenceUsage:  true,
		SilenceErrors: true,
		Version:       finchVersion(),
	}
```

で、ルートコマンドの登録をして、
最終的に、

```golang
	rootCmd.AddCommand(allCommands...)
```

部分で、`allCommands` の内容を登録しています。
それまでに出てくるものも説明しましょう。

- `ecc` は、`Create` 関数 を通して、`"os/exec"` からコマンド入力（ls など）を行います（コードは[ここ](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/pkg/command/exec.go#L17)）。
- `lcc` は、`Create` 関数を通して、lima コマンドを 上の ecc から呼び出しています（コードは[ここ](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/pkg/command/lima.go#L121-L134)）。

lima は 

https://blog.amedama.jp/entry/lima-macos-amd64-vm

などを参照にしてみて下さい。

## allCommands の中身

では、`allCommands` の内容を見てみると、

```golang
	allCommands := initializeNerdctlCommands(lcc, logger)
```

のように `initializeNerdctlCommands` を呼び出しています。
ここで出てくる `nerdctl` は docker と同じコマンドライン（`pull` や `run` や `image`など。コードだと [ここ](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/cmd/nerdctl.go#L127)に一覧が載っています。）でコンテナを触れるライブラリになっています。

https://blog.1q77.com/2021/09/lima/

#### initializeNerdctlCommands

`initializeNerdctlCommands` に話を戻すと、その[中身](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/cmd/main.go#L118)は`nerdctlCommandCreator.create` で append する動作を for文で回しているだけなのが分かりますね。

その `nerdctlCommandCreator.create` を見てみると、command の [RunE](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/cmd/nerdctl.go#L40) で `runAdapter` を呼んでいるだけに見えますね。nc.creater には前に出てきた `lcc` が入ります。そして、`runAdapter` の `run` 内の 

```golang
	limaArgs := append([]string{"shell", limaInstanceName, nerdctlCmdName, cmdName}, nerdctlArgs...)
```

で、`lima` の args を指定して、

```golang
	return nc.creator.Create(limaArgs...).Run()
```

で、

```golang
	cmd := lcc.cmdCreator.Create(lcc.limactlPath, args...)
```

で、`limactl shell finch nerdctl [command]` を実行します。この `limactl shell` は [ここ](https://github.com/lima-vm/lima/blob/master/README.ja.md#limactl-shell) に書いているように、Linux 上で command を叩く内容になっていますので、そのまま `nerdctl` を実行する内容になっています。

これらのコマンドは、`RunE` で引数が渡されていませんが、コマンド入力の時に引数が渡されて実行される流れになっています。

ここまでの流れだけでも、`finch` の docker ライクなコマンドの実行が、単純に `limactl shell finch nerdctl コマンド` を叩いているだけなのが分かりました。意外とやっていることは簡単でしたね。

#### virtualMachineCommands

では、`allCommands` の初期化の次の部分である 

```golang
	allCommands = append(allCommands,
		newVersionCommand(),
		virtualMachineCommands(logger, fp, lcc, ecc, fs, fc),
	)
```

を見てみます。
特に重要なのは `virtualMachineCommands` 部分なので、そこから読んでみます。
コードを読めば分かるんですが、[ここ](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/cmd/virtual_machine.go#L39-L44)
の部分で、コマンドを追加しています。

```golang
	virtualMachineCommand.AddCommand(
		newStartVMCommand(limaCmdCreator, logger, optionalDepGroups, lca, nca, fs, fp.LimaSSHPrivateKeyPath()),
		newStopVMCommand(limaCmdCreator, logger),
		newRemoveVMCommand(limaCmdCreator, logger),
		newInitVMCommand(limaCmdCreator, logger, optionalDepGroups, lca, nca, fp.BaseYamlFilePath(), fs, fp.LimaSSHPrivateKeyPath()),
	)
```

ここでは、前に出てきた lima の Linux 仮想環境を開始したり、削除したりの動作を行なっています。仮想環境のセットアップもなしで、`limactl` が動くわけもないですよね。
同じような内容もあるので、ここでは `newStartVMCommand` を中心に見てみます。
この `newStartVMCommand` でも、[RunE](https://github.com/runfinch/finch/blob/52ce4e77aeb679a380f7b580bb13ddd5c8a45b01/cmd/virtual_machine_start.go#L31) がエントリーポイントになり、`runAdapter -> run` で実行されます。
ここでは、

```golang
	limaCmd := sva.creator.CreateWithoutStdio("start", limaInstanceName)
```

から(creator は上で出てきた `lcc` )、`limactl start finch`コマンドを入力しています。これで、lima の Linux 仮想環境が実行されます。
他の、newStopVMCommand や newRemoveVMCommand も同じような感じなので読んでみるといいかもしれませんね。

## 最後に

Finch ってかっこよく宣伝されている割には、中身が `limectl` と `nerdctl` の実行を行なっているだけで特に難しいことはしていないようなラッパーのみに見えるので、今後の Finch の開発に期待ですね。
皆様もこれを機に、`nerdctl` や `Finch` を使うようになれば幸いです。

Go 初めて呼んだ割には、読めてよかったです。
nerdctl も ps だけ読んでみたのですが、containerd を CLI で叩くためのラッパーみたいな感じでしたね。Finch を読めば、結構読みやすいと思うので nerdctl を読むのもオススメです。