月別アーカイブ: 2017年4月

DebianにDockerをインストールして基本的なコマンドを使ってみる

前提

以下の環境にDockerを導入する

要素 バージョン
Debian 8.6
Docker 1.13.0

概要

Debian8.6にDocker1.13.0をインストールし、そこでCentOSのコンテナを作成する。コンテナで適当に作業を行い、コミットして再利用するところまでをチュートリアルする。Dockerそのものに関する説明はそんなに無い。Dockerfileによる構築も扱わない。Dockerの雰囲気が味わえれば良いかなぐらい。

Dockerとは

Dockerはサーバ仮想化ソフトウェアの一つ。ただし、VMWareなどの、ハイパーバイザ型における完全な仮想化を提供するのではなく、ホストOSのカーネルを共有し、その上に「コンテナ」と呼ばれるOSのシステムレベルで仮想化されたゲストOSを取り扱う。

つまり、VMWareなどの仮想化と比べると不完全な仮想化で、ゲストOSの独立レベルは低い。Dockerではそのような構成にすることで以下のメリットが得られる。

  • 個別にOSを立ち上げる必要がないので作成や起動、破棄が高速
  • 使用する資源(CPU/メモリ/ストレージ)が少ない
  • オーバーヘッドが少なく性能劣化が殆ど無い

よって、Dockerの使い方としては、永続的に稼働している環境というよりは、頻繁に作成、破棄が行われる使い捨ての環境に用いることが向いていると言える。

個人的には開発環境を手軽に構築、共有するのに使っていけたら良いなと思う

Dockerのインストール

DockerはホストOSのカーネルを共有するので、当然Linux上に構築するのが望ましい。一応WindowsやMacでもDockerを導入することができるが、本記事ではMac上に構築したDebianの上にDocker環境を構築する。

例によってapt-getでインストールしたいところだが、Debianに標準で含まれてるaptリポジトリにdockerが含まれていないので、手作業でリポジトリを追加する必要がある。

/etc/apt/sources.list

が、aptのリポジトリリストなので、これに以下を追記する。ドメイン的に日本のDebian公式リポジトリなんだと思う。

deb http://ftp.jp.debian.org/debian/ sid main
deb-src http://ftp.jp.debian.org/debian/ sid main

で、アップデート

$ sudo apt-get update

dockerをインストール

$ sudo apt-get install docker.io

インストールできたことを確認

docker -v
Docker version 1.13.0, build 49bf474

Dockerイメージのインストール

Dockerでコンテナを作成するためには、元となるイメージが必要になる。

Dockerには、DockerイメージのリポジトリであるDockerHubを通じて、Dockerイメージを共有する機能があるので、今回はそちらからCentOSのDockerイメージをインストールする。

権限周りがやかましいのでここからはsudo権限を持たせる

$ sudo -s
# whoami
root

DockerHubからDockerイメージをダウンロードする場合は、docker pullコマンドを使用する。今回はCentOSをダウンロードする。

# docker pull centos
Using default tag: latest
latest: Pulling from library/centos
93857f76ae30: Already exists
Digest: sha256:4eda692c08e0a065ae91d74e82fff4af3da307b4341ad61fa61771cc4659af60
Status: Downloaded newer image for centos:latest

ダウンロード済みのDockerイメージの一覧の確認は、docker imagesコマンドで行う。centosのDockerイメージがダウンロードされていることが確認できる。

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              a8493f5f50ff        3 weeks ago         192 MB

Dockerイメージが仮想マシンのイメージと同じようなものと考えると、192MBというのは実に小さい。仮想マシンイメージと異なり、カーネルが含まれていないからである。

DockerコンテナによるHello World

DockerコンテナはDockerイメージから生成される仮想環境の実体である。一つのイメージから複数のコンテナを生成できるので、オブジェクト指向におけるクラスとインスタンスの関係のようなものだ。

コンテナ起動は、docker run コマンドを用いる。といっても、コンテナはホストOSのカーネルを用いてプロセスを実行するものなので、どんなプロセスを実行するかを指定する必要がある。

ここでは、”Hello Docker World”という文字列をechoするプロセスを実行するだけのコンテナを立ち上げてみる。

# docker run centos echo "Hello Docker World!"
Hello Docker World!
#

“Hello Docker World!” の文字列が出力され、コマンドの実行は終了する。ではこの時コンテナはどのような状態なのか、docker psコマンドで確認する。psコマンドは基本的にプロセスが動いているコンテナの一覧を表示するが、-aオプションを付与することで終了しているコンテナも表示する。

# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS                          PORTS               NAMES
627496810cd6        centos              "echo 'Hello Docke..."   About a minute ago   Exited (0) About a minute ago                       angry_blackwell

627496810cd6がコンテナのID(ユニーク)で、angry_blackwellがコンテナの名称である。名称はユニークでない可能性もあるが、コンテナ生成時に任意の名称を付けることができる。

このコンテナはコマンド”echo ‘Hello Docker…”を実行完了し、Exited状態に移行している。

コンテナの削除

前項で作成したコンテナは、Hello Docker World!を出力してお役御免なので、docker rmコマンドを用いて削除する。削除の際はコンテナIDがコンテナ名を指定する。特に重複の可能性がないのであれば、可読性のあるコンテナ名を使ったほうが良さそう。

# docker rm angry_blackwell
angry_blackwell

削除されたことを確認

docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

コンテナを作成し、アクセスする

“アクセスする”という言い方には語弊がある気がするが、つまりSSHでアクセスしたときみたいにコンテナ内でシェルを使いたいということである。前項の用に、コンテナを起動する際にプロセスを指定することで、コンテナはプロセスを実行し、実行完了するまで稼働し続ける。

つまり、コンテナで実行するプロセスにシェルを指定してあげればよい。ここではbashを用いてシェルを利用することにする。また、-itオプションを付与することで、現在の標準入出力を引き継ぐことができるので、シェルを利用するためには必須となる。

# docker run -it centos /bin/bash
[root@a3936fcb8f31 /]# cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)

コンテナ内でシェルを利用できるようになり、catコマンドでコンテナ内のOS情報ファイルを出力できていることがわかる。CentOSと書かれているが、実際にはホストOSであるDebianのカーネルを利用している。ややこしい。

コンテナ内でRubyをインストールする

コンテナ内に開発環境を作る、というほど大層なことは本記事では行わないが、その前進としてRubyをインストールして、適当なスクリプトを作成してみる。

カーネルはDebianのものを使ってるが、コンテナはCentOSなので、apt-getではなくyumを用いてRubyをインストールする。ここではRubyのバージョンとかは気にせず、簡単にインストールできる方法を用いる。

[root@a3936fcb8f31 /]# yum install ruby

インストールできたことを確認。どうやらこのコンテナ内のyumで標準でインストールできるRubyは2.0.0らしい。だいぶ古い。

[root@a3936fcb8f31 /]# ruby -v
ruby 2.0.0p648 (2015-12-16) [x86_64-linux]

適当にスクリプトを作成する。と思ったらvimも入っていないので先にvimをインストールする

[root@a3936fcb8f31 /]# yum install vim

インストールしたvimで、/fizzbazz.rb を作成する。フィズバズについてはWikipediaを参照。ここでは、コマンドライン引数Nを取り、1〜Nに対してのフィズバズを行うプログラムを作成する。カッコいいアルゴリズムでも使おうと思ったが、コードの内容は本記事とは関係ないので普通に書く。

N = ARGV[0].to_i
N.times do |m|
  if m % 3 == 0 && m % 5 == 0
    puts "Fizz Buzz"
  elsif m % 3 == 0
    puts "Fizz"
  elsif m % 5 == 0
    puts "Buzz"
  else
    puts m
  end
end

実行してみるとこんな感じ

root@a3936fcb8f31 /]# ruby /fizzbazz.rb 10
Fizz Buzz
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz

フィズバズを持つDockerイメージを作成する

前項では、コンテナ内でRubyとvimのインストール及びフィズバズプログラムの作成を行った。しかし、これからはあくまでコンテナ内に存在するだけで、元となったDockerイメージに存在するものではない。したがって、本コンテナを削除してしまえば前項の作業は水の泡となってしまい、再利用することができない。

そこで、本コンテナを元にしたDockerイメージを新たに作成する。つまり、「CentOSのイメージ」を元に、「CentOSの中にRubyとVimとフィズパズスクリプトが入ったイメージ」を作成するということだ。

コンテナからイメージを作成するには、docker commitコマンドを用いる。以下では、”unruffled_blackwell”(本コンテナの名前)を元に、”fizzbazz”というイメージを作成する。

# docker commit unruffled_blackwell fizzbazz
sha256:7bf2cab2f57325c76c67affd54f7545ecc7ecde41925e7cd2404ba6c602e05a7

イメージが作成されたことを確認。RubyとVimを入れたせいで容量がかなり増えている。それでも348MBだが。

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
fizzbazz            latest              7bf2cab2f573        26 seconds ago      348 MB
centos              latest              a8493f5f50ff        3 weeks ago         192 MB

作成したイメージから新たにコンテナを生成する

とりあえず先程Rubyやvimをインストールしたコンテナはもう不要なので削除する。以下は全てのコンテナを削除するおまじない的なコマンド。といっても、「全てのコンテナ一覧のコンテナIDのみ標準出力し、それらをdocker rmの引数にする」という一連の処理を行っているだけ。

# docker ps -aq | xargs docker rm
a9f5293d88a0
a3936fcb8f31
0357fffe82b2

これでRubyやVimがインストールされたコンテナが削除された。インストール作業は水の泡となってしまった。

# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

ということはもちろん無く、以下のように先程作成したコンテナを用いてフィズバズプログラムを実行できることが確認できる。

# docker run fizzbazz ruby /fizzbazz.rb 10
Fizz Buzz
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz

まとめ

以上より、Dockerを用いることで非常に手軽にCentOSの環境を導入し、そこでRubyの開発環境(の卵)を作成することができた。さらに、Ruby開発環境を持ったCentOSのイメージを作成し、手軽に再利用できるようになった。

また、本記事では割愛しているが、DockerHubを用いて、本記事で作成したイメージをpushすることで、他のユーザがそれを再利用することもできる。逆に他のユーザが作成したイメージからコンテナを作成することもでき、例えばRubyの開発環境に留まらず、Ruby on railsの開発環境一式が揃ったイメージなども数多く出回っている。

つまり、今後何らかの技術を使って開発を行いたくなったら、その開発環境に適合するイメージを探して、docker pullするだけでスグに環境構築を行えるということだ。
完全に適合したイメージが無ければ、近いものをpullし、自分でカスタマイズしてcommit,pushすればさらに特定の目的に特化したイメージを流通させることができる。

今回、本記事を書くために初めてDockerを利用したが、まだまだ使いみちは模索中で、基礎的なコマンドしか抑えられていないので、今後はさらに実用的な使い方ができるように勉強し、実務にも取り入れられたらなと画策している。

GUIでgitを使っている人がCUIに乗り換えるための基礎知識

前提

本記事は以下の環境で動作確認しています

要素 バージョン
debian 8.6
git 2.1.4

また、本記事中ではリモートリポジトリとして、私用のGitHubを用いています。リモートリポジトリは以下のものを用いて、空の状態からスタートするものとします。

git@github.com:Sa2Knight/git_test.git

CUIによるメリット

SourceTreeなどのGUIツールを使う場合と、CUIでgitコマンドを叩いて利用する場合では、どちらもメリット・デメリットが存在すると思います。ここでは、CUIを使う場合のメリットにのみ触れますが、デメリットについてはCUIの利用を躊躇ってる方々の考えるとおりだと思います。(コマンド覚える必要があるなど)

※ 以下のメリット・デメリットは私の個人的な考えが多く、実際は異なる可能性もあります。

1. 作業内容を共有しやすい

これはgitに限らずCUI全般に言えることですが、CUI上での作業は全て正確に言語化することができます。ある作業内容を相手に伝えたい場合、実行したコマンドリストを送ることで全て解決します(もちろん付加情報を自然言語で与えることも重要ですが)

対してGUIでは、操作を言語化する際に曖昧な情報になりやすく、誤解を与えやすい問題があります。

2. 定型的な作業を自動化出来る

こちらもCUI全般の話ですが、GUIでの操作と比べたとき、CUIの操作は自動化を行いやすいです。例えば、pullしてaddしてcommitしてといった一連の作業は、GUIだとマウスカーソルをそれなりに動かして、何度かクリックしたりといった定形作業が発生します。(ショートカットキーを使った場合でも)

対してCUIでは、一連の処理を一つのコマンドで実行できるようにすることで、定形作業をまとめて実行することができます。

3. 使い方を検索しやすい

言うまでもなく、バージョン管理システム”git”は、CUIツールです。SourceTreeなどのGUIツールは、GUIでgitを利用できるようにしてくれる後発のソフトウェアです。

そのため、当然ながらgitの使い方について検索した場合に出てくる情報の大半はCUIのコマンドです。もちろんSourceTreeなど、GUIソフトウェア名を検索ワードに含むことでカバーは効きますが、それでもCUIに関する情報量と比べれば雀の涙程度です。

以上より、gitを利用していて何らかの問題が発生した時に、それを検索して解決できる可能性は確実にCUIのほうが早く見つかると言えます。

4. カッコいい

これに尽きます

gitのインストール

ここでは、debianにapt-getを用いてgitをインストールします。他の環境でのインストール方法については適宜ググってください。gitは特別インストールが難しいツールではないので大丈夫だと思います。

$ sudo apt-get install git

gitがインストールされていることを確認する

$ which git
/usr/bin/git
$ git --version
git version 2.11.0

gitの利用

以降では、実際に以下の手順でgitを利用してみます。
1. ローカルリポジトリの作成
2. 空のファイルを作成してコミット
3. リモートリポジトリを登録し、pushする
4. 既存のファイルを編集する
5. 既存のファイルの削除
6. リモートリポジトリの更新をローカルリポジトリに反映

また、本項では以下のコマンドを利用します

コマンド名 内容
git init ローカルリポジトリを作成
git status リポジトリの状態を確認
git remote add リモートリポジトリを追加
git add ファイルをステージング・エリアに移動
git rm ファイルをリポジトリから削除
git diff 直前のコミットと現在のファイルの差分を確認
git commit リポジトリをコミット
git push ローカルリポジトリの更新をリモートリポジトリに反映
git pull リモートリポジトリの更新をローカルリポジトリに反映

1. ローカルリポジトリの作成

以下のコマンドは、作業ディレクトリ直下で実行してください

$ git init
Initialized empty Git repository in /home/vagrant/gittest/.git/

2. 空のファイルを作成してコミット

適当にファイルを作ります。以下は、”test”と書かれたテキストファイルを作成しています。

$ echo "test" > file1
$ cat file1
test

git statusコマンドを実行すると、リポジトリ内の現在の状態を確認できます

$ git status
On branch master

Initial commit

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	file1

nothing added to commit but untracked files present (use "git add" to track)

Untracked filesは、まだリポジトリに追加されていないファイルのことです。コレに対して、git addコマンドを用いて、file1をリポジトリに追加します。

$ git add file1

もう一度git statusを実行すると、file1がリポジトリに追加され、Changes to be comittedの下にfile1が表示されていることが確認できます。この場所をステージング・エリアと呼びます。

 git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

	new file:   file1

$

コミットすることができるのは、ステージング・エリアにあるファイルのみです。つまり、現状の場合file1のみがコミット対象のファイルです。コミットは、git commitコマンドで行うことができます。

$ git commit -m "初めてのコミット"
[master (root-commit) 32cd62b] 初めてのコミット
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1

これで初めてのコミットが完了です。-mオプションを指定することで、”初めてのコミット”など、コミットコメントを記述することができます。-mを省略した場合、vimなどのエディタが立ち上がり、コミットメッセージの記述を求められます。

コミットが完了したことを、コミットログを閲覧することで確認することができます。コミットログの閲覧には、git logコマンドを用います。

$ git log
commit 32cd62bb013c5f8fe7021789ebb68d6a618fab9e
Author: Sa2Knight <shingo.sasaki.0529@gmail.com>
Date:   Sun Apr 23 20:15:53 2017 +0900

    初めてのコミット

“初めてのコミット”がコミットログに書き込まれていることが確認できます。

commit 32cd62bb013c5f8fe7021789ebb68d6a618fab9e

は、コミットハッシュと呼び、全てのコミットを一意に識別するための文字列です。

3.リモートリポジトリを登録し、pushする

ここでは、空のリモートリポジトリ

git@github.com:Sa2Knight/git_test.git

を登録し、ローカルリポジトリの内容をpushします。

リモートリポジトリの登録にはgit remote addコマンドを用います。

git remote add origin git@github.com:Sa2Knight/git_test.git

originはリモートリポジトリに付けた名前です。特にこだわりがなく、一つしかリモートリポジトリを使わない場合はoriginにする慣習があるっぽいです。

登録されているリモートリポジトリの一覧は、git remote -vコマンドで確認できます。

$ git remote -v
origin	git@github.com:Sa2Knight/git_test.git (fetch)
origin	git@github.com:Sa2Knight/git_test.git (push)

前項でコミットした内容をリモートリポジトリにpushします。git pushコマンドを用いてpushします。

$ git push origin master
Counting objects: 3, done.
Writing objects: 100% (3/3), 239 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:Sa2Knight/git_test.git
 * [new branch]      master -> master

masterはリモートリポジトリのブランチ名です。ブランチについて詳しくない方はとりあえずmasterと入れるものと思ってください。

4.既存のファイルを編集する

既存のfile1の内容を、”test”から”test2″に書き換えます。

$ echo "test2" > file1
$ cat file1
test2

リポジトリの状態を確認すると、file1がChanges not staged for commitの欄に表示されていることが確認できます。

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   file1

no changes added to commit (use "git add" and/or "git commit -a")

modifiedと書かれているとおり、ファイルの内容が前回のコミット時から変更されているが、ステージング・エリアに移動していないファイルを表します。

ここで、前回のコミット時と現在でファイルがどう変わったのか、git diffコマンドで確認することができます。

$ git diff file1
diff --git a/file1 b/file1
index 9daeafb..180cf83 100644
--- a/file1
+++ b/file1
@@ -1 +1 @@
-test
+test2

“- test”となっている部分は”test”という行が削除されたことを表し、逆に”+ test2″となっている部分は、”test2″という行が追加されたことを表します。

前項の手順で、ステージングエリアへの移動、コミット、プッシュを行いますが、やることは同じなので割愛します。

5.既存のファイルの削除

先程から利用していたfile1を削除します。

$ rm file1

リポジトリの状態を確認すると、今度はdeleted: file1と表示されています。

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	deleted:    file1

no changes added to commit (use "git add" and/or "git commit -a")

これは、リポジトリに存在するファイルがディレクトリに存在しない状態を表します。リポジトリからも削除する場合は、git rmコマンドを用いて、ステージング・エリアに移動します。

$ git rm file1
rm 'file1'
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	deleted:    file1

あとはコミット、プッシュを行うだけです。

6. リモートリポジトリの更新をローカルリポジトリに反映

今度は、リモートリポジトリ側が誰かによって更新されたとします。ここでは、リモートリポジトリにfile2というファイルが追加されたことを想定して、git pullコマンドを用いてpullします。

$ git pull origin master
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:Sa2Knight/git_test
 * branch            master     -> FETCH_HEAD
   9cc3229..0d3c678  master     -> origin/master
Updating 9cc3229..0d3c678
Fast-forward
 file2 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 file2

pull後、追加されたfile2が存在することが確認できます。

$ ls file2
file2

その他のコマンド

以下のコマンドは本記事では使用しませんでしたが、覚えると非常に役立つコマンドです。興味があればどんどん調べて使ってみてください。コマンドは私が使うもののみで、実際は他にもう少しあります。

コマンド名 内容
git checkout ファイルを直前のコミット時の状態に戻す
git stash ファイルの変更状態を一時的に保存する
git reset リポジトリの状態を特定のコミット時に戻す
git branch リポジトリのブランチに関する操作を行う
git merge 複数のブランチを統合する
git rebase 複数のコミットを統合する

Bowerを使ってフロントエンドの依存関係を管理する

前提

要素 バージョン
debian 8.7
node 7.4.0
npm 4.1.2
bower 1.8.0

概要

Debianにnpmを用いてBowerをインストールし、Bowerを簡単に使ってみる

Bowerとは

Bower(バウアー)はフロントエンド開発の依存関係(JavaScript/CSS/HTMLなど)を管理してくれるフロントエンド用パッケージ管理システム。

PHPのComposer、RubyのGem、iOSのcocoaPodsなどの仲間だと思えば良い。

ライブラリなどのインストールをコマンドで行えるようになり、インストールした内容及びその依存関係が、bowser.jsonに書き込まれる。

同一のbowser.jsonさえあれば、他環境にライブラリをインストールする際にコマンド一発で解決する。

Bowerのインストール

npmを用いてインストールを行う。パッケージ管理システム(npm)でパッケージ管理システム(bower)をインストールするのがちょっと面白い。ここではver1.8.0がインストールされる。

$ sudo npm install bower -g
/usr/local/bin/bower -> /usr/local/lib/node_modules/bower/bin/bower
/usr/local/lib
└── bower@1.8.0

bower.jsonの作成

初めにパッケージの依存関係などを記述するbower.jsonを作成する。といっても、JSONファイルを直接作成するのでなく、以下のコマンドを実行すると、対話式に入力を促されるのでそれに従っていけば自動的にbower.jsonが作成される

$ bower init

以下のように、名称、説明文、メインファイル(省略可)、キーワード(省略可)、作者、ライセンス、Webサイト(省略可)などを入力することで、bower.jsonが生成される。ここで入力される情報のうちのいくつかは、自分でbowerにライブラリを公開する場合に必要になるものなので、単に開発で試用するライブラリを管理するという場合は省略して良い。

$ bower init
? name qs-test
? description bower test directory
? main file
? keywords
? authors Sa2Knight <shingo.sasaki.0529@gmail.com>
? license MIT
? homepage
? set currently installed components as dependencies? No
? add commonly ignored files to ignore list? No
? would you like to mark this package as private which prevents it from being accidentally published to the registry? No

{
  name: 'qs-test',
  authors: [
    'Sa2Knight <shingo.sasaki.0529@gmail.com>'
  ],
  description: 'bower test directory',
  main: '',
  license: 'MIT',
  homepage: ''
}

? Looks good? Yes

Bowerを用いてjQueryUIパッケージをインストールする

Bowerを用いたパッケージのインストールは、下記のコマンドで行える。 –save を付与することで、インストール結果をbower.jsonに記録してくれるので実質必須。

$ bower install [パッケージ名] –save

jQueryUIのパッケージ名はjquery-uiなので、以下のように、jQueryUIをインストールする

$ bower install jquery-ui --save
bower jquery-ui#*               cached https://github.com/components/jqueryui.git#1.12.1
bower jquery-ui#*             validate 1.12.1 against https://github.com/components/jqueryui.git#*
bower jquery#>=1.6              cached https://github.com/jquery/jquery-dist.git#3.2.1
bower jquery#>=1.6            validate 3.2.1 against https://github.com/jquery/jquery-dist.git#>=1.6
bower jquery-ui#^1.12.1        install jquery-ui#1.12.1
bower jquery#>=1.6             install jquery#3.2.1

bower.jsonの内容を確認すると、jQueryUI1.12.1に依存している状態にあることが追記されている

$ cat bower.json
{
  "name": "qs-test",
  "authors": [
    "Sa2Knight <shingo.sasaki.0529@gmail.com>"
  ],
  "description": "bower test directory",
  "main": "",
  "license": "MIT",
  "homepage": "",
  "dependencies": {
    "jquery-ui": "^1.12.1"
  }
}

インストールされたパッケージは、bower_componentsディレクトリ以下に配置されるのだが、ここを見てみると、jQueryUIが依存しているjQuery本体もインストールされていることがわかる。

$ ls bower_components/
jquery  jquery-ui

このように、Bowerでは、インストール対象のパッケージが依存している他のパッケージについても自動的にインストールしてくれる。(composerやnpm、gemなどに慣れている人にとっては当然だが)

所感

  • npmの影響を大きく受けているらしく、全体的な仕様やコマンドの使い方がnpmに限りなく近い
  • bowerでは、対象パッケージをgitからcloneしてくるだけなので、実際に必要なるファイルはその中の極一部である。それをどのように扱うかはまた別の手段を用いる必要があるようなので、即戦力という感じでは無さそうだ
  • scriptタグでロードする時に依存関係に基づいた順番でロードしなきゃならないって問題は解決できてない
  • と言うかJavaScriptの外部ライブラリってそんな依存管理が必要なほど沢山使うかな
  • bowerの触りをやった程度なのでメリットがあまり掴めてない。詳しい方がいたらご教授願う。
  • bowerのサブコマンドが大量にあるので、余裕があれば色々見てみたい

参考

[Ruby] BacklogをCUIで操作してみる

前提

要素 バージョン
Debian 8.6
Ruby 2.2.2
gem 2.4.5
bundle 1.13.4
Backlog API 2.9.0
BacklogKit 0.15.0

また、Backlogの個人設定より、APIキーを生成していることを前提とする

概要

国産プロジェクト管理ツールであるBacklogを、ブラウザではなく、BacklogAPIを用いて、スクリプト経由で操作する。スクリプトの記述には、APIを間接的に利用するライブラリであるBacklogKitを用いるためにRubyを採用する。

本記事で作成するツールでは、主に以下のことをCUIで実現する

  • ユーザ情報の取得
  • 課題の作成(タイトル/本文のみ指定可能)
  • 課題の操作(ステータスの変更/完了理由の変更/実作業時間の変更)

実用的にするにはさらにカスタマイズする必要があるが、本記事では基本的なAPI利用が出来るようになる部分までにする。また、本課題での動作確認は、個人利用のために開設しているBacklogスペースを用いる。

BacklogAPI利用の準備

今回は、BacklogAPIをRubyで簡易的に利用するためのライブラリである、BacklogKitを利用する。BacklogKitはgemで管理されているので、bundleを用いてインストールする。

※ gem: Rubyのパッケージングシステム
※ Bundle: gemパッケージを管理するツール

Gemfileに以下のように記述

source "https://rubygems.org"
gem 'backlog_kit'

Bundleでインストール

bundle install --path bundle/vendor

スペースIDとAPIキーを記述したファイルを作成

BacklogAPIを利用するためには、対象のスペースIDと、APIキーが必要になる。スクリプト中にハードコーディングしても良いが、本記事で実装するツールは、Githubにてコードを公開しているため、APIキーが漏洩することを避けるために別途JSONファイルに記述することにする(もちろん該当のJSONファイルはリポジトリで共有しない)

ということで、以下のようなsecret.jsonを作成する。もちろんAPI_KEYはダミー

{
  "space_id": "saknight",
  "api_key": "hogehogefugafugafoofoobarbar",
  "project_key": "DEV"
}

とりあえず汎用的なメソッドを持たせることを目的に、Util.rbを作成し、そこにJSONファイルをロードするメソッドを実装する

require 'json'
class Util
  def self.load_secret_file
    File.open('secret.json') do |file|
      JSON.load(file)
    end
  end
end

これでBacklogAPIを利用する下準備は完了

BacklogAPIを使ってみる

Backloger.rbを作成し、ここにBacklogAPIを利用するコードを書いていく。とりあえずBacklogAPIを試しに使ってみるということで、ユーザ情報を取得してみる。

require 'backlog_kit'
require_relative 'Util'

secret = Util.load_secret_file
client = BacklogKit::Client.new(
  space_id: secret['space_id'],
  api_key: secret['api_key']
)

p client.get_space.body

bundleを使っているため、上記ファイルを以下のように実行する

$ bundle exec ruby Backloger.rb

以下のような実行結果が得られた

$ bundle exec ruby Backloger.rb
#<BacklogKit::Resource:0x007f9534806850 @attributes={:spaceKey=>"saknight", :name=>"saknight", :ownerId=>85748, :lang=>"ja", :timezone=>"Asia/Tokyo", :reportSendTime=>"18:00:00", :textFormattingRule=>"backlog", :created=>"2017-03-04T15:47:40Z", :updated=>"2017-03-04T15:47:40Z"}>

どうやらBacklogKitではAPIの実行結果はBacklogKit::Resourceクラスのオブジェクトとして返却されるらしい。詳しい使い方はドキュメントやソースコードを追う必要があるが、以下のようにコードを変更すればスペースIDが取得できそうだ。

p client.get_space.body.spaceKey

実行結果

$ bundle exec ruby Backloger.rb
"saknight"

とりあえずBacklogkitを用いたBacklogAPIの利用が正常にできていることがわかった。あとはドキュメントやソースコードを読んで本ツールの目的を達成するためのコードを作成していく

課題の作成に必要なパラメータの取得

リファレンスによると、課題の追加には以下のパラメータが必須となる

  • 課題の名前
  • プロジェクトID
  • 課題の種別ID
  • 課題の優先度ID

ここで面倒なのが、プロジェクト/種別/優先度が全てIDを要求されていることだ。
例えば優先度の場合「高」「中」「低」といった表示名で指定するのではなく、それに対応するIDが必要になる。面倒なことにIDはBacklog環境のカスタマイズ次第で変わってしまうので、今回はスクリプトから動的に取得できるようにすることにする。

プロジェクトID/種別ID/優先度IDを動的に取得するために、以下のIdentifierクラスを作成した

require 'backlog_kit'

class Identifier

  def initialize(client)
    @client = client
  end

  def project(projectKey)
    project = @client.get_projects.body.find do |pb|
      pb.projectKey == projectKey
    end
    project.id
  end

  def priority(label)
    priority = @client.get_priorities.body.find do |pr|
      pr.name == label
    end
    priority.id
  end

  def issueType(project_key, label)
    type = @client.get_issue_types(project_key).body.find do |it|
      it.name == label
    end
    type.id
  end
end

上記クラスのメソッドは、それぞれプロジェクトキー、優先度、種別を指定すると、それに対応するIDを返却してくれる。
例えば以下のようなコードを実行すると

identifier = Identifier.new(client)
p identifier.projectId("DEV")
p identifier.priorities('中')
p identifier.issueTypeId("DEV", 'タスク')

以下のようにそれぞれIDに変換して出力してくれる

$ bundle exec ruby Backloger.rb
38382
3
172585

もちろんIDは基本的に不変であるため、毎回動的に取得する必要はなく、一度取得したら適当にキャッシュするなどすれば簡単に高速化することができるが、ここでは割愛する。

課題の作成

ようやく本題。前項で、課題の作成に必要なパラメータを動的に取得できるようになったので、create_issueメソッドを用いて、課題を作成する。以下のコードでは、「課題テスト」という、優先度が「中」で、種別が「タスク」の課題を作成する(他の設定項目は未設定状態)

client.create_issue('課題テスト', {
  :projectId   => identifier.project(secret['project_key']),
  :priorityId  => identifier.priority('中'),
  :issueTypeId => identifier.issueType(secret['project_key'], 'タスク'),
})

スクリプト実行後、課題が生成されていることが確認できる

課題の編集に必要なパラメータの取得

今回は、前項で作成した課題に対して、以下の編集を行う。

  • ステータスを「完了」にする
  • 完了理由を「対応済み」にする
  • 実績時間を「1時間」にする
  • 適当なコメントを投稿する

“課題の作成に必要なパラメータの取得”同様、ステータス/完了理由は、対応するIDが必要になるので、同様にIdentifierクラスに以下のメソッドを追加する。

  def status(label)
    status = @client.get_statuses.body.find do |st|
      st.name == label
    end
    status.id
  end

  def resolution(label)
    resolution = @client.get_resolutions.body.find do |res|
      res.name == label
    end
    resolution.id
  end

以下のコードを実行すると

identifier = Identifier.new(client)

p identifier.status('完了')
p identifier.resolution('対応済み')

以下のように完了と対応済みに対応するIDが取得できる

$ bundle exec ruby Backloger.rb
4
0

課題の編集

前項で課題編集のためのIDが取得できたので、update_issueメソッドを用いて、以下のように課題の修正を行う

client.update_issue('DEV-47', {
  :statusId => identifier.status('完了'),
  :resolutionId => identifier.resolution('対応済み'),
})

実行すると、以下のように課題が修正されたことが確認できる

所感

  • Backlogを普段から利用してるが、どうにも定型的な操作が億劫だと思っていたので、色々自動化してみたいと思いBacklogAPIの利用を初めてみた
  • リファレンスを見る限りほぼ全ての操作をAPIで利用できるので、極端に言えばブラウザを一切使わずにBacklogを利用することもできそう
  • 本記事ではBacklogAPI及びBacklogKitについて軽く触れた程度だが、ここから実用的なツールの開発を進め、一段落したところでまた記事を書ければなと思う
  • ここ1ヶ月資格試験に追われてなかなかプログラミング出来てなかったので、リハビリがてら楽しくコーディングできた
  • Rubyを使うのも久しぶりだが、リスト操作の柔軟さはやはりRubyならでは。気持ちよく使える

[Swift3] UIImageから、アスペクト比を維持してサムネイルを生成する

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

概要

既存のUIImageに対して、アスペクト比を維持したままサムネイル(縮小画像)を作成する関数を実装する。
本関数ではWidthを指定し、そのWidthとアスペクト比に合致したHeightを計算してリサイズする。
リサイズは非破壊的に行い、オリジナルのUIImageを維持したまま、リサイズ後のUIImageを新たに生成して返却する。

もっと良い方法がある気がするが、ググってもあまり見つからなかったので自前で実装

コード

/**
 * 横幅を指定してUIImageをリサイズする
 * @params image: 対象の画像
 * @params width: 基準となる横幅
 * @return 横幅をwidthに、縦幅はアスペクト比を保持したサイズにリサイズしたUIImage
*/
func resizeUIImageByWidth(image: UIImage, width: Double) -> UIImage {
  // オリジナル画像のサイズから、アスペクト比を計算
  let aspectRate = image.size.height / image.size.width
  // リサイズ後のWidthをアスペクト比を元に、リサイズ後のサイズを取得
  let resizedSize = CGSize(width: width, height: width * Double(aspectRate))
  // リサイズ後のUIImageを生成して返却
  UIGraphicsBeginImageContext(resizedSize)
  image.draw(in: CGRect(x: 0, y: 0, width: resizedSize.width, height: resizedSize.height))
  let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
  UIGraphicsEndImageContext()
  return resizedImage!
}

備考

  • パラメータと計算式を微修正すれば、Heightをベースにリサイズする関数も作れる
  • 計算式をそれなりに弄れば、現在のサイズからn%リサイズといった関数も作れる
  • 指定したWidthがオリジナルより大きかったらオリジナルを戻すとか加えたほうが良さそう

[Swift3] フォトアルバム名を指定して、アルバムが存在しなければ新規作成

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

また、以下を満たした状態。以下はググればスグ出る内容なので本記事では割愛

  • PhotosFrameworkを利用できる状態にある
  • ライブラリの利用をユーザから許可を取っている

概要

アルバム名を指定し、そのアルバムが端末内に存在するかをチェックし、存在しない場合に新規作成したい。適当にググって出てきた日本語の参考ページ通りにコードを書いても動かなかったので、公式リファレンスを参考に自前で実装。

コード

/**
* 端末に指定した名称のアルバムを作成する
* ただし既に同名のアルバムが存在する場合は作成しない
* @params albumTitle アルバム名
* @params callback   アルバム生成後に呼び出されるコールバック
*/
func createNewAlbum(albumTitle: String, callback: @escaping (Bool) -> Void) {
  if self.checkAlbumExists(albumTitle: albumTitle) {
    callback(true)
  } else {
    PHPhotoLibrary.shared().performChanges({
      PHAssetCollectionChangeRequest.creationRequestForAssetCollection(withTitle: albumTitle)
    }) { (isSuccess, error) in
      callback(isSuccess)
    }
  }
}

/**
* 端末に指定した名称のアルバムが存在するかを戻す
* @params albumTitle アルバム名
* @return 存在する場合True
*/
func checkAlbumExists(albumTitle: String) -> Bool {
  let albums = PHAssetCollection.fetchAssetCollections(with: PHAssetCollectionType.album, subtype: 
  PHAssetCollectionSubtype.albumRegular, options: nil)
  for i in 0 ..< albums.count {
    let album = albums.object(at: i)
    if album.localizedTitle != nil && album.localizedTitle == albumTitle {
      return true
    }
  }
  return false
}

実行例

“グッチ裕三コレクション”アルバムが存在しない場合に作成する。既に存在していたか、作成に成功した場合に”成功”が出力される

createNewAlbum(albumTitle: "グッチ裕三コレクション") { (isSuccess) in
  if isSuccess {  
    print("成功")
  } else {
    print("失敗")
  }
}

参考

[Swift3] UIImageViewにグリッド線を描画する

前提

要素 バージョン
xcode 8.2.1
Swift 3.0.2
iPhone 6s
iOS 10.2.1

概要

UIImageViewに以下の様な3×3のグリッド線を描画する。

コード

UIViewを画面いっぱいに貼り付けて、以下のカスタムクラスを作成する

class GridView: UIView {

  override func draw(_ rect: CGRect) {
    let path = UIBezierPath()
    path.lineWidth = 1.5

    UIColor.white.setStroke()
    path.move(   to: getPoint(rect, x: 1, y: 0))
    path.addLine(to: getPoint(rect, x: 1, y: 3))
    path.move(   to: getPoint(rect, x: 2, y: 0))
    path.addLine(to: getPoint(rect, x: 2, y: 3))
    path.move(   to: getPoint(rect, x: 0, y: 1))
    path.addLine(to: getPoint(rect, x: 3, y: 1))
    path.move(   to: getPoint(rect, x: 0, y: 2))
    path.addLine(to: getPoint(rect, x: 3, y: 2))
    path.stroke()
  }

  /* UIImage上の指定した区画の座標を取得 */
  private func getPoint(_ rect: CGRect, x: CGFloat, y: CGFloat) -> CGPoint {
    let width = rect.width / 3.0
    let height = rect.height / 3.0
    return CGPoint(x: width * x, y: height * y)
  }

}

備考

  • 一部数値を書き換えることで3×3にかぎらず、N×Nの任意のグリッド線を描画できる