概要
- RPMパッケージ管理システムにはC言語のAPIと、そのPython Binding APIがある。
- 今後Linuxを管理する上でGo言語からもパッケージ管理させていただきたい
- かんたんな機能を実現するGo Binding APIを作りました。
これまでの経緯
以前投稿した記事で、RHEL・CentOS以外の環境(RPMがない環境)でRPMパッケージの中身を見たり、パッケージのVerifyをやったりするために、Go言語でかんたんなアプリを作ってみていた。
当然だが、上記したアプリではRHEL・CentOS環境にインストールされたパッケージの一覧を見たりすることはできないし、もちろん、パッケージのインストールを行うこともできない。だが今のところはこれだけの機能があれば十分良いと考えられる。
将来的な用途
昨今のGo界隈を見るに、他の言語と同等か、それ以上の機能を持つサードパーティ製パッケージが次々とリリースされているところを見ると、また、Go言語の特性から言って、Goによって実装された統合監視システム、あるいは構成管理ツールが出てきてもおかしくはない(と勝手に)思っている。
つまりどういうことか
純正RPMユーティリティをexecで呼び出したりすることなく、Go言語から直接パッケージマネージメントをするAPIが欲しい。
RPMパッケージ管理システムのGo言語 API
RPMでは純正の実装済みユーティリティ(rpm, rpm2cpio等)以外にC言語用APIとPython言語バインディングされたAPI(主にyum等で利用)が提供されている。
このファミリーにGo言語用のバインディングAPIを追加したい。
しかし、上記したGo言語向けのAPIをRPMメンテナーの皆さんは提供してくれそうな気配はない。欲しいなら作れ、言い出しっぺの法則である。
できあがったもの
- go-rpmlib
- Go言語用RPM APIのバインディング モジュール
- rpm-4.8.0 に対応。CentOS6系列はOKなはず
- https://github.com/necomeshi/rpmlib
利用方法
コンパイルのためにはRPMの開発ライブラリが必要となっている。デフォルトではインストールされていないのでパッケージをインストールする必要がある。RHEL環境なら、以下のコマンドで取得ができる。
$ su -
$ yum -y install rpm-devel
モジュールはいつものようにgithubにアップロードしている。取得方法は以下
$ export GOROOT=<Your Go installation path>
$ export GOPATH=<Your Go Project Workspace>
$ go get github.com/necomeshi/rpmlib
利用方法を以下に示す。
以下は、インストール済みのパッケージのパッケージ名を頭から順に表示するプログラムのソースコード例である。
/*
* Search installed package sequencially
*/
package main
import (
"os"
"fmt"
"io"
"github.com/necomeshi/rpmlib"
)
func main() {
ts, err := rpmlib.NewTransactionSet()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot get transaction set: %s\n", err)
os.Exit(1)
}
defer ts.Free()
iter, err := ts.SequencialIterator()
if err != nil {
fmt.Fprintf(os.Stderr, "Cannot get iterator: %s\n", err)
os.Exit(1)
}
defer iter.Free()
for {
h, itr_err := iter.Next()
if itr_err == io.EOF {
break
}
if itr_err != nil {
fmt.Fprintf(os.Stderr, "Cannot get next iterator: %s\n", itr_err)
break
}
defer h.Free()
name, h_err := h.GetString(rpmlib.RPMTAG_NAME)
if h_err != nil {
fmt.Fprintf(os.Stderr, "Cannot get name from rpm header: %s\n", h_err)
break
}
fmt.Println(name)
}
}
今回のつまりどころ
RPMのAPI
RPMのAPIは、RHEL・CentOS環境にてC言語ライブラリ、およびPython APIにバインディングして提供されている。
6系の現状の最新版であるCentOS6.8でのRPMバージョンは4.8.0である
RPM APIの利用方法は親切なことに**ここ**に記載されている。
お分かりいただけただろうか
上記を見ただけでお分かりいただけた方々は、恐らく紙にパンチ穴開けていた頃から戦ってきていた歴戦のエンジニアか、もしくはRedHatメンテナーの中の人か、以前何かの拍子に触ったことがある御仁だろう。
悪いがおじさんにはさっぱりわからない。
だがわからないなりに調べたところ、コード上は以下のような流れで処理されているようだ。
-
トランザクションオブジェクトを作る
rpmtsCreate()
を使ってトランザクションオブジェクトを作成する。 -
DBに接続し、Cursor(イテレータ)を取得する
rpmdbXXX
側のAPIを使用しても良いが、トランザクションオブジェクトからアクセスするrpmtsInitIterator()
から取得したほうが、内部でDBのクローズ処理や重複オープンに対処してくれるのでこちらを使う。 -
イテレータからRPMヘッダ情報を取得
rpmdbIteratorNext
を使ってNULLが返るまでループを回す。ここらあたりはsqlite3と同じような感覚。 -
RPMヘッダ情報から各種パッケージ情報を得る
3で返されるヘッダ構造体に対してheaderGetXXX
等を使って情報を得る。この時パッケージのサマリを祝したい場合は、headerGetString()
の引数にRPMTAG_SUMMARYを指定する。 -
3に戻る
cgoによるC言語ライブラリの利用
Python等の他の言語と同じように、GoにもC言語のライブラリを呼び出す仕組みとしてcgoというものがデフォルトで備わっている。
たとえば、Goで記述したモジュールからC言語のprintfを利用してHello Worldするプログラムは以下のように記述することができる
/*
# include <stdio.h>
*/
import "C"
import "unsafe"
def main() {
go_strHello := "Hello World"
// Convert Go strings to C lang strings
fmt := C.CString("%s\n")
c_strHello := C.CString(g_strHello)
// Hello World!!
C.printf(fmt, c_strHello)
// We must free C lang strings
C.free(unsafe.Pointer(fmt))
C.free(unsafe.Pointer(c_strHello)
}
import ”C"
より上方、コメントアウトした部分にC言語コードを記述すると、Go言語のモジュール側からそのコードを呼び出すことができる。ヘッダファイルでなくとも、直接関数を記述することもでき、どちらの場合もプレフィックスとして C.
を付け、 C.Foo()
のようにして利用することができる。
もし、数学系ライブラリ等、デフォルトでリンクされないライブラリを使用したい場合は、コンパイラフラグを指定する必要がある。以下のように#cgo
ディレクティブを用いるとコンパイラフラグを操作し、リンクの指定ができる。
/*
# cgo LDFLAGS: -lm
# include <math.h>
*/
import "C"
これらの機能を用いて、RPMの先程のC言語APIをGo言語のコードから呼び出すことができる。
なお、CとGoとの間でのデータのやり取り、例えばC言語関数の引数等はGoからCのデータ型に変換しなければならず、また、関数からの返り値もC言語型からGo言語型に変換する必要がある。
このC言語型←→Go言語型間の変換の一覧を作成してらっしゃるかたがいらしたのでここで紹介しておきたい。
Appendix of Cgo and Go Type Mappings