Docker Engine API試用

前に少し気になっていたDocker Engine APiを使ってみたので,それについて.

特に複雑なことはせず,引数に与えたイメージを削除するというプログラムを作ってみた.
ただ単純に削除するだけではつまらないので,何世代分保存しておくか,というのをオプションで指定できるようにした.

コードは mas9612/docker-tools/image-remove に置いてある.
あまりきれいなコードではないのでご注意ください.

Client.ImageList() メソッドでローカルにあるイメージの一覧が取得できるが, filter でイメージ名を指定できなさそうだったので,愚直にfor文で1つ1つ確認している.
アルゴリズムは得意ではないので,良い方法があれば教えてください…

images, err := client.ImageList(ctx, types.ImageListOptions{})
if err != nil {
    log.Fatalf("[ERROR] client.ImageList(): %s\n", err)
}

for _, image := range images {
    for _, repotag := range image.RepoTags {
        repository := strings.Split(repotag, ":")
        if repository[0] == *imageName {
            imageInfos = append(imageInfos, imageInfo{
                ID:      image.ID,
                Created: image.Created,
                Name:    repotag,
            })
        }
    }
}

削除対象のイメージをリスト出来たら,それを作成日時でソートし,指定した世代分は残してそれ以外を Client.ImageRemove() メソッドで削除している.
デフォルトでは,イメージ名にマッチしたもの全てを削除するようになっているのでお気をつけください.

if *generation > len(imageInfos) {
    *generation = len(imageInfos)
}
removeOptions := types.ImageRemoveOptions{
    Force: *force,
}
for _, image := range imageInfos[*generation:] {
    _, err := client.ImageRemove(ctx, image.ID, removeOptions)
    if err != nil {
        log.Fatalf("[ERROR] client.ImageRemove(): %s\n", err)
    }
    fmt.Printf("Image %s was deleted.\n", image.Name)
}

やっている事自体は簡単なので,ドキュメントと見比べて頂ければわかると思います.

GoでMySQLを使う – database/sql package

GoからMySQLを使う方法について調べた.
O/Rマッパーを使う方法も気になったが,まずGo標準パッケージで用意されている機能を使い,SQLを地道に実行していく方法を試した.

ソースコードは以下.

実行

Dockerを使って簡単にローカルにMySQLを準備する.

$ docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=mypass -e MYSQL_DATABASE=testdb -e MYSQL_USER=mysql -e MYSQL_PASSWORD=mypass -d --name mysql mysql

DBの準備後,作成したソースコードを実行する.

$ go run mysql_example.go
ID: 1, Name: Tom
ID: 2, Name: Bob
ID: 3, Name: Alice

INSERTしたデータが正しく取得できていそうである.
念のため,MySQLに入って確認してみる.

$ docker exec -it mysql mysql -u root -p
Enter password:

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| testdb             |
+--------------------+
5 rows in set (0.01 sec)

mysql> use testdb;
Database changed

mysql> show tables;
+------------------+
| Tables_in_testdb |
+------------------+
| test_tbl         |
+------------------+
1 row in set (0.00 sec)

mysql> select * from test_tbl;
+------+-------+
| id   | name  |
+------+-------+
|    1 | Tom   |
|    2 | Bob   |
|    3 | Alice |
+------+-------+
3 rows in set (0.00 sec)

上記の通り,正常にテーブルの作成とデータの追加が行えていることが確認できた.

解説

準備

Goの database/sql パッケージを使うと,色々なDBを扱うことができる.
しかし, database/sql パッケージとは別に,ここから使いたいDBのdriverを探してインストールしておく必要がある.
今回はMySQLを使いたいので,go-sql-driver/mysqlを利用した.
下記コマンドで go-sql-driver/mysql をインストールする.

$ go get -u github.com/go-sql-driver/mysql

データベースへの接続

データベースへ接続するには, sql.Open() メソッドを使用する.
第1引数に使用したいdriver名,第2引数に接続先を指定する.

db, err := sql.Open("mysql", "mysql:mypass@/testdb")

接続確認を行いたい場合は, sql.Open() の後に DB.Ping() メソッドを呼び出すことでできる.

if err = db.Ping(); err != nil {
    log.Fatalf("db.Ping(): %s\n", err)
}

SQLの実行

SQLの実行は, DB.Exec() 及び DB.Query() メソッドで行うことができる.
CREATE文やINSERT文など,DBからデータが返ってこないものに関しては DB.Exec() メソッドを用い,SELECT文などDBからデータを取得するのが目的であるものに関しては DB.Query() メソッドを用いる.

Exec()

_, err = db.Exec("create table test_tbl (id int, name varchar(32))")
if err != nil {
    log.Fatalf("db.Exec(): %s\n", err)
}

Query()

DBからの結果は sql.Rows に入っている.
Rows.Scan() メソッドで,1レコードの中から値(今回であればSELECT文での取得対象に * をしているため,全てのカラム = idname )を取得することができる.
Rows.Scan() メソッドの引数にはポインタを渡すことに注意する.

1レコード分の処理が終了し,次のレコードに移るためには Rows.Next() メソッドを呼び出す.

var rows *sql.Rows
rows, err = db.Query("select * from test_tbl")
if err != nil {
    log.Fatalf("db.Query(): %s\n", err)
}
defer rows.Close()

for rows.Next() {
    var (
        id   int
        name string
    )
    err = rows.Scan(&id, &name)
    if err != nil {
        log.Fatalf("rows.Scan(): %s\n", err)
    }

    fmt.Printf("ID: %d, Name: %s\n", id, name)
}
if err = rows.Err(); err != nil {
    log.Fatalf("rows.Err(): %s\n", err)
}

Go net package – Goでソケット通信

Goのnetパッケージについて軽く勉強した.
簡単なソケット通信についてまとめる.

ソケット通信

ソースコードは以下.

package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    host := "localhost"
    port := "8000"
    address := net.JoinHostPort(host, port)
    conn, err := net.Dial("tcp", address)
    if err != nil {
        log.Fatalf("net.Dial(): %s\n", err)
    }
    defer conn.Close()

    request := "GET / HTTP/1.1\n\n"
    fmt.Println([]byte(request))
    _, err = conn.Write([]byte(request))
    if err != nil {
        log.Fatalf("Conn.Write(): %s\n", err)
    }

    buffer := make([]byte, 1024)
    var n int
    for {
        n, err = conn.Read(buffer)
        if n == 0 {
            break
    }
    if err != nil {
        log.Fatalf("Conn.Read(): %s\n", err)
    }
    fmt.Print(string(buffer))
    }
}

以下に簡単な解説を.

Goでのソケット通信は, Conn オブジェクトを作成するところから始まる.
まず, net.Dial()Conn オブジェクトを作成する.

conn, err := net.Dial("tcp", address)

第1引数にはネットワークの種類を,第2引数には接続したい先のアドレスを指定する.
今回はサンプルとしてローカルに立てたHTTPサーバへ接続してみるので,ネットワークの種類は TCP を指定しておく.

Conn オブジェクトを作成できたら,後は Conn.Read()Conn.Write() でデータの読み書きができる.

_, err = conn.Write([]byte(request))
...
...
n, err = conn.Read(buffer)

簡単なソケット通信は以上の3つのメソッドを使うことで簡単にできる.

Pythonで簡単にローカルHTTPサーバをたて,作成したプログラムを実行してみる.

$ python -m http.server
$ go run main.go
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.6.2
Date: Sun, 04 Feb 2018 12:47:14 GMT
Content-type: text/html; charset=utf-8
Content-Length: 336

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="main.go">main.go</a></li>
</ul>
<hr>
</body>
</html>

JoinHostPort()/SplitHostPort()

上の例でも使っているが,これらのメソッドを使うことでアドレスとポート番号の結合・分離が簡単にできる.

address := net.JoinHostPort("localhost", "8000")
fmt.Println(address) // localhost:8000

host, port, err := net.SplitHostPort("localhost:3000")
if err != nil {
log.Fatalf("net.SplitHostPort(): %s\n", err)
}
fmt.Printf("Host: %s, Port: %s\n", host, port) // Host: localhost, Port: 3000

C++のstd::vectorについて

std::vectorについて勉強しなおした.

STLコンテナの一種.実行時に動的にサイズを変更できる.vectorを使用するためには, #include <vector> を記述する必要がある.

vectorの生成

vectorには複数のコンストラクタが存在する.

const int data[] = {1, 2, 3, 4, 5};

std::vector<int> empty_vector;                  // 空のvector
std::vector<int> int_vector(10);                // 要素数10のvector
std::vector<double> double_vector(10, 3.2);     // 要素数10,各要素は3.2で初期化されたvector
std::vector<double> copy_vector(double_vector); // double_vectorのコピー
std::vector<int> iter_vector(data, data + 5);   // dataからdata+5の要素をもつvector

vectorのサイズ・容量

vectorでは,実際に確保されている動的配列の要素数と,実際に使用されている動的配列の要素数は異なる.これらの値を調べたい時には,それぞれ capacity() 関数, size() 関数を使用する.これらの関数の戻り値は, std::vector::size_type 型である.

vectorで使用できるサイズの最大値は max_size() 関数で得ることができる.

std::vector<int> v;

std::cout << v.capacity()   // 確保されている要素数
<< v.size()       // 使用されている要素数
<< v.max_size()   // std::vector<int>で使用できる最大要素数
<< std::endl;

vectorの容量を拡張したい時には, reserve() 関数を使用する.

std::vector<int> v;

v.reserve(100);

vectorの操作

要素の追加・代入

vectorの末尾に値を追加するには, push_back() 関数を使用する.要素追加時には,容量の拡張は自動的に行われる.
また,任意の位置に値を挿入するには, insert() 関数を使用する.

std::vector<int> v(5, 1);

v.push_back(3);     // vの末尾に3を追加
v.(v.begin(), 10);  // vの先頭に10を挿入

vectorへの値の代入には, = 演算子が使用できる.さらに, assign() 関数を使用することもできる.

std::vector<int> v(10);

v.assign(10, 1);    // 10個の1を代入
v[5] = 0;           // v[5]に0を代入

const int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
v.assign(a, a + 10);    // aからa+10の要素を代入

要素の取得

要素にアクセスするには,通常の配列のように [] を使用することができる.添字アクセスの際には容量の拡張は行われないため,サイズ以上の値を指定した場合は範囲外アクセスになる.

範囲外アクセスに対応したい場合には, at() 関数を使用する.この関数を使用している際に範囲外アクセスが起こると, std::out_of_range 例外が送出される.

std::vector<int> v(10, 3);
try {
cout << v[3] << '\n';   // 3が表示される
v.at(15) = 13;          // std::out_of_range例外の送出
} catch (const std::out_of_range& e) {
std::cerr << e.what() << std::endl;
}

また,先頭の要素は front() 関数,末尾の要素は back() 関数で取得できる.

std::vector<int> v(10);
std::cout << v.front() << '\n';
std::cout << v.back()  << '\n';

要素の削除

要素の削除には, pop_back() 関数,もしくは erase() 関数を使用する.なお,要素を削除しても,その領域は残ったままになる.そのため, new で確保した領域を削除した場合でも,自動的に delete されない.

std::vector<int> v(10, 1);

v.pop_back();                       // 末尾の要素を削除
v.erase(v.begin());                 // 先頭要素を削除
v.erase(v.begin() + 2, v.end());    // 3番めの要素から最後まで削除

要素をすべて削除したい場合は, clear() 関数を使用する.

std::vector<int> v(5, 5);

v.clear();

PythonからMySQLを使う

DjangoでデータベースにMySQLを使用するときはmysqlclientを使用することが推奨されている.Djangoが勝手にデータベースに接続などの処理をしてくれるのでモジュールの使い方は知らなくても使うことは可能だが,せっかくなら使い方もわかるほうが良いので調べてみた.

基本的に,Python標準ライブラリのsqlite3と使い方は同じ.
まずコネクションオブジェクトを作成し,そこからカーソルオブジェクトを作る.できたカーソルオブジェクトを使って様々なクエリを実行する.

インストール

$ pip install mysqlclient

MySQLに接続する

conn = MySQLdb.connect(
    user='username',
    passwd='password',
    host='host',
    db='dbname'
)

返り値はコネクションオブジェクト.userpasswd は名前の通り.MySQLに登録されているユーザー情報を記述する. host はデータベースの置いてある場所を指定する.ローカルのMySQLに接続する場合は localhost を指定する. db には使用するデータベース名を指定する.

カーソルオブジェクトの作成

c = conn.cursor()

MySQLdb.connect で作成したオブジェクトを使ってカーソルオブジェクトを作成する.

クエリの実行

c.execute(query)

query に指定したクエリを実行する.

プレースホルダ

クエリ中に %s を記述すると,プレースホルダとして扱える.ここに値を埋め込む場合は,与えたい値を execute() の第2引数にタプルで渡す.

c.execute('select * from test where id = %s', (2,))

レコードの取得

execute() でselect文を実行した後,レコードを得るためには以下のいずれかを使用する.

  • fetchone() : レコードを1件取得
  • fetchmany(n) : レコードをn件取得
  • fetchall() : レコードをすべて取得

データベースへの変更を保存

conn.commit()

このメソッドを呼び出すことで,変更を保存できる. これを呼び出し忘れると,追加・削除などの変更が破棄される ので注意.

このメソッドはカーソルオブジェクトではなく,コネクションオブジェクトが持っていることにも注意.

サンプルコード

# coding: utf-8

import MySQLdb


def main():
    conn = MySQLdb.connect(
        user='testuser',
        passwd='testuser',
        host='192.168.33.3',
        db='testdb'
    )
    c = conn.cursor()

    # テーブルの作成
    sql = 'create table test (id int, content varchar(32))'
    c.execute(sql)
    print('* testテーブルを作成\n')

    # テーブル一覧の取得
    sql = 'show tables'
    c.execute(sql)
    print('===== テーブル一覧 =====')
    print(c.fetchone())

    # レコードの登録
    sql = 'insert into test values (%s, %s)'
    c.execute(sql, (1, 'hoge'))  # 1件のみ
    datas = [
        (2, 'foo'),
        (3, 'bar')
    ]
    c.executemany(sql, datas)    # 複数件
    print('\n* レコードを3件登録\n')

    # レコードの取得
    sql = 'select * from test'
    c.execute(sql)
    print('===== レコード =====')
    for row in c.fetchall():
        print('Id:', row[0], 'Content:', row[1])
    
    # レコードの削除
    sql = 'delete from test where id=%s'
    c.execute(sql, (2,))
    print('\n* idが2のレコードを削除\n')

    # レコードの取得
    sql = 'select * from test'
    c.execute(sql)
    print('===== レコード =====')
    for row in c.fetchall():
        print('Id:', row[0], 'Content:', row[1])

    # データベースへの変更を保存
    conn.commit()
    
    c.close()
    conn.close()


if __name__ == '__main__':
    main()

実行結果

* testテーブルを作成

===== テーブル一覧 =====
('test',)

* レコードを3件登録

===== レコード =====
Id: 1 Content: hoge
Id: 2 Content: foo
Id: 3 Content: bar

* idが2のレコードを削除

===== レコード =====
Id: 1 Content: hoge
Id: 3 Content: bar