Jenkinsの設定などを雑にバックアップする「jenkins-backup-script」

# jenkins-backup-scriptとは?
Jenkinsの設定ファイルやプラグイン一式を全部tar.gzに固めるスクリプトです

https://github.com/sue445/jenkins-backup-script

この辺のファイルをバックアップします

* `$JENKINS_HOME/*.xml`
* `$JENKINS_HOME/jobs/*/*.xml`
* `$JENKINS_HOME/nodes/*`
* `$JENKINS_HOME/plugins/*.jpi`
* `$JENKINS_HOME/secrets/*`
* `$JENKINS_HOME/users/*`

# 使い方
## 単体実行
`./jenkins-backup.sh` にJENKINS_HOMEの場所と作成したいアーカイブファイルの場所を渡すだけです

```shell-session
./jenkins-backup.sh /path/to/jenkins_home archive.tar.gz
```

## Jenkinsのジョブで1日1回動かす
### 1. Exclusive Execution Plugin をインストールする
バックアップ中に他のジョブが実行されないよう排他制御を行うためにExclusive Execution Pluginをインストールしてください

https://wiki.jenkins-ci.org/display/JENKINS/Exclusive+Execution+Plugin

### 2. フリースタイル・プロジェクトで新しいジョブを作る
![image](https://qiita-image-store.s3.amazonaws.com/0/8059/de76d709-345c-806b-25dd-e90399735997.png)

### 3. ジョブの設定
#### ソースコード管理 -> リポジトリ
![image](https://qiita-image-store.s3.amazonaws.com/0/8059/695e7554-7537-0313-c23f-dd217002a971.png)

* Repository URL: `https://github.com/sue445/jenkins-backup-script.git` を設定
* Branch Specifier: masterだと突然挙動が変わることがあるので基本的には最新のtagを入れるのを推奨です
  * https://github.com/sue445/jenkins-backup-script/releases 参照

#### ビルド・トリガ
![image](https://qiita-image-store.s3.amazonaws.com/0/8059/de1fc929-3046-fb26-4a43-776af784a041.png)

「定期的に実行」にチェックを入れて実行したい時間を入力してください。（上のスクショだと午前3時台）

#### ビルド -> シェルの実行
![image](https://qiita-image-store.s3.amazonaws.com/0/8059/b983c161-1659-8c96-af47-69f73cd32157.png)

何でもいいですが僕が実際に使ってるコマンドはこれです

```shell-session
./jenkins-backup.sh $JENKINS_HOME /data/backup/settings/backup_`date +"%Y%m%d_%H%M%S"`.tar.gz

find /data/backup/settings/backup_* -mtime +30 | xargs rm -f
```

`backup_YYYYMMDD_HHMMSS.tar.gz` というような名前でバックアップファイルを作りつつも、バックアップファイルが作られすぎないように直近30日分だけ残してそれより古いのは全部消しています。

## バックアップとった後は
Jenkinsサーバがいつ死んでもいいように物理的に別のサーバ（S3など）に送るのがいいでしょう。

## バックアップから復旧したい
雑ですがこんな感じ。tar.gzを展開してJENKINS_HOMEに上書きするだけです

```shell-session
sudo /etc/init.d/jenkins stop
cd /path/to/backup_dir
tar xzvf backup.tar.gz
sudo cp -R jenkins-backup/* /path/to/jenkins/
sudo chown jenkins:jenkins -R /path/to/jenkins/
sudo /etc/init.d/jenkins start
```

# 【ポエム】 テストの変遷
jenkins-backup-scriptはいくつかテストの変遷があります

## 1. ノーテスト期
雑に作って雑に公開したのでテストはありませんでした。

## 2. test-unitでテストを書いた
しばらくはノーテストでやっていたものの、有難いことにたくさんの人からPullRequestが飛んできてその度に動作確認するのが大変だったので途中でテストコードを書きました。

https://github.com/sue445/jenkins-backup-script/blob/0.1.6/test/test_jenkins-backup.rb

一時ディレクトリに [テストに必要なファイルやディレクトリ一式](https://github.com/sue445/jenkins-backup-script/tree/0.1.6/test/jenkins_home1) をコピーし、 実際に `jenkins-backup.sh` 動かしてテストを実行していました。

rspecではなくtest-unitを使ったのは確か「rspecだとRuby以外の言語の人がPRでテストを修正しづらいだろう」と思ったためです。

## 3. Vagrant + itamaeで環境構築し、Serverspecでテストを実行するようにした
今の形式です

test-unitだとruby以外のセットアップは不要でお手軽に実行できるのですが、テストコードが膨大になりすぎてメンテしづらくなってきたので一念発起して[Vagrant](https://www.vagrantup.com/)で作ったVM内に[itamae](http://itamae.kitchen/)でテストのための環境構築のレシピを流して、[serverspec](http://serverspec.org/)でテストするようにしました。

Vagrantを導入することで複数OSのテストが用意になりました。

テストでやってることはこんな感じ

1. テストの環境構築（JENKINS_HOMEの再現）を行うレシピ
  * https://github.com/sue445/jenkins-backup-script/tree/0.1.8/spec/recipes/jenkins
2. `jenkins-backup.sh` を実行するレシピ
  * https://github.com/sue445/jenkins-backup-script/blob/0.1.8/spec/recipes/backup/default.rb
3. `jenkins-backup.sh` 実行後のアーカイブファイルに期待したファイルが含まれていることを確認するテスト
  * https://github.com/sue445/jenkins-backup-script/blob/0.1.8/spec/jenkins-backup_spec.rb

「テストのための環境構築」というテストとは直接関係ないものを全部itamaeのレシピに押し込めたおかげで、テストコードは割とスッキリしたと思います。

```ruby:spec/jenkins-backup_spec.rb
describe tar_file("/tmp/relative_archive.tar.gz") do
  ARCHIVED_FILES.each do |file|
    it { should include file }
  end
end

describe tar_file("/tmp/absolute_archive.tar.gz") do
  ARCHIVED_FILES.each do |file|
    it { should include file }
  end
end
```

ちなみに `tar_file` というのは、tarの中に含まれているファイルをいい感じに取り出すための独自resourceです。

```ruby:spec/support/serverspec/tar_file.rb
module Serverspec
  module Type
    class TarFile < Base
      def initialize(tar_file)
        # NOTE: @name is used in Serverspec::Type::Base
        @name = tar_file
      end

      def files
        return @files if @files

        command = "tar tfz #{@name}"
        result = Specinfra.backend.run_command(command)
        raise "FAILED: #{command}\n#{result.stderr}" if result.failure?

        @files = result.stdout.split(/[\r\n]+/)
      end

      def include?(file)
        files.include?(file)
      end
    end

    def tar_file(tar_file)
      TarFile.new(tar_file)
    end
  end
end

include Serverspec::Type
```

https://github.com/sue445/jenkins-backup-script/blob/0.1.8/spec/support/serverspec/tar_file.rb