Jenkinsを拡張してファイルI/Fを実現したい

id:kkawaさんからのアドバイスの通りに、ファイルI/FのプログラムをJenkinsのCLIコマンドとして実装
してみようと思い立ちました。
Jenkinsの内部構造などほとんど知らないので、結構時間が掛かるだろうと思いましたが、
結果的に「え、これだけ?」という感じでなんとか動くところまで実装できました。
※まだまだ改善すべき点はありますが。。。。


ファイルI/Fの日記では、送信側用のプログラムと受信側用のプログラムの2つを実装しましたが、
送信用のプログラムは、標準で実装されているBuildCommandで事足りることが分かったので、実装しませんでした。

以前実装してみた送信用プログラム

java -jar hutil.jar -r IFJOB -s SourceJOB -f zzz.csv

は、以下のように実行することで同じ機能になります。
ビルドパラメータにも対応していますし、-sオプションでビルドが終わるまで待ちます。

java -jar jenkins-cli.jar -s http://192.168.0.1:8080/ build IFJOB -s -p HU_JOB=SourceJOB -p HU_FILE=zzz.csv


従って受信用プログラム(ダウンロード用プログラム)だけJenkinsを拡張して作成することにしました。
上で言ったようにとても簡単なプログラムなので、ここに全部載せることにします。

import hudson.Extension;
import hudson.FilePath;
import hudson.cli.CLICommand;
import hudson.model.AbstractProject;
import hudson.model.Hudson;
import hudson.model.Item;
import org.kohsuke.args4j.Argument;

@Extension
public class DownloadCommand extends CLICommand {

    @Override
    public String getShortDescription() {
        return "Download a file.";
    }

    @Argument(metaVar = "JOB", usage = "Name of the job that contain of download file.", index = 0, required = true)
    public AbstractProject<?, ?> job;

    @Argument(metaVar = "FILE", usage = "Name of a file.", index = 1, required = true)
    public String filename;

    @Override
    protected int run() throws Exception {
        Hudson h = Hudson.getInstance();
        h.checkPermission(Item.BUILD);
        FilePath src = null;
        try {
            src = new FilePath(job.getSomeWorkspace(), filename);
            if (!src.exists()) {
                stderr.println("filename : " + filename + " is not exist.");
                return -1;
            }
            new FilePath(channel, filename).copyFrom(src);
        } catch (Exception e) {
            stderr.println(e.getMessage());
            return -1;
        }
        return 0;
    }
}


使い方は以下のようになります。
引数を2つ取ります。
1つ目は、受信したいファイルが含まれるジョブ名(ジョブ配下のworkspaceにファイルがおかれている前提)
2つ目は、受信したいファイル名

java -jar jenkins-cli.jar -s http://192.168.0.1:8080/ download SourceJOB zzz.csv


ファイルは、「java -jar jenkins-cli.jar 〜」を実行した場所に落ちてきます。


Jenkinsのジョブとして定義するときは、以下のようにパラメータ化された引数を渡してやります。

java -jar jenkins-cli.jar -s http://192.168.0.1:8080/ download %HU_JOB% %HU_FILE%


自前で実装した時には、
コマンドラインパラメータを解析するために、getoptライブラリ
・HTTPでダウンロードするために、Apache Http Commonsライブラリ
XMLレスポンスを解析するために、dom4jライブラリ
などなど必要だったのですが、今回はまったく必要ありません。


前回の日記で書いたように、Jenkins拡張用のプロジェクト作成から、デバック環境まで簡単に揃ってしまいますので
とても手軽に拡張用プラグインを作成できちゃいます。
これだけ簡単にできてしまうと、プラグインがたくさんあるのも頷けますね。

Jenkinsを拡張したい

前の日記で書いた内容の機能をJenkinsの拡張プラグインとして
作成するためには、その方法を知らなければなりません。(公開まで遠い道のりになりそうです。。。)
幸い方法は、ここに書かれています。
基本はこの通りに行っていくわけですが、うまく動かなかったところがあったので、ここに残しておきたいと思います。


まず、mavenのsettings.xmlプラグイングループ(org.jenkins-ci.tools)を追加します。

<settings>
  <pluginGroups>
    <pluginGroup>org.jenkins-ci.tools</pluginGroup>
  </pluginGroups>
</settings>

さらに、追加ステップとしてプロファイルの追加も行います。

<settings>
  <profiles>
    <profile>
      <id>jenkins</id>
      <repositories>
        <repository>
          <id>m.g.o-public</id>
          <url>http://maven.glassfish.org/content/groups/public/</url>
        </repository>
      </repositories>
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>jenkins</activeProfile>
  </activeProfiles>
</settings>

この後、mvn -cpu hpi:createを実行するのですが、ここで以下のようなエラーが出てはまりました。

C:\home\jenkins>mvn -cpu hpi:create
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'hpi'.
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] The plugin 'org.apache.maven.plugins:maven-hpi-plugin' does not exist or no valid version could be found

[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: < 1 second
[INFO] Finished at: Fri Mar 11 12:56:29 JST 2011
[INFO] Final Memory: 2M/247M
[INFO] ------------------------------------------------------------------------
C:\home\jenkins>


悩んだ挙句、「pluginRepositoriesを追加しないと検索してくれないのでは?」と思い、プロファイルに追加したリポジトリ
同じURLをpluginRepositoriesとしても登録しました。
が記載されていないので、適宜追加してください。

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

  <pluginGroups>
    <pluginGroup>org.jenkins-ci.tools</pluginGroup>
  </pluginGroups>

  <profiles>
    <profile>
      <id>jenkins</id>
      <repositories>
        <repository>
          <id>m.g.o-public</id>
          <url>http://maven.glassfish.org/content/groups/public/</url>
        </repository>
      </repositories>

      <pluginRepositories>
        <pluginRepository>
          <id>m.g.o-public</id>
          <url>http://maven.glassfish.org/content/groups/public/</url>
        </pluginRepository>
      </pluginRepositories>       
     
    </profile>
  </profiles>
  <activeProfiles>
    <activeProfile>jenkins</activeProfile>
  </activeProfiles>
</settings>

すると今度はうまくいきました。
途中で、groupIdとartifactIdを聞かれますので、適当に入力して進めます。
(ここでは、groupId:sample, artifactId:SampleModuleと入力しています。)


ここから先は特につまるところが無かったです。
作成されたディレクトリに移動し、mvn packageやmvn installも問題なく成功しました。
ちなみに、作成されたディレクトリ(図の例では、SampleModule)の直下にあるpom.xmlの中のタグの中のを書き換えると
Jenkinsのターゲットバージョンを変更できます。
普段eclipseを使用しているので、eclipseのプロジェクトを作成しました。

cd SampleModule
mvn package
mvn install
mvn -DdownloadSources=true -DdownloadJavadocs=true -DoutputDirectory=target/eclipse-classes eclipse:eclipse

次にデバッグですが、これも期待通りの動きで問題ありませんでした。

set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
mvn hpi:run

上のように実行すると、デバックポート8000、Httpアクセス8080でデバック環境が起動します。


eclipseでのリモートデバッグの設定は、デバックの構成からリモートJavaアプリケーションで行います。


mvn -cpu hpi:createで作成したプロジェクトには、HelloWorldBuilderというサンプルがついていますので、
これを利用してデバッグできるか試してみます。

新規ジョブを作成します。ビルドの追加で、HelloWorldBuilderにより実装されている「Say hello world」という
名前のビルダーを指定します。


ビルダーの名前に適当なテキストを入力します。


ビルドの実行時に止めてみたいので、HelloWorldBuilderのperformメソッドで適当にデバッグポイントを設定してみます。


準備ができたら、先ほどのジョブを実行してみます。
実行するとすぐに、eclipseデバッグパースペクティブが起動してきました。


これでJenkinsの拡張開発に必要な最低限の環境が整ったことになります。
あとは実装するのですが、まだまだ勉強しないと着手できそうにありません。
ファイルI/Fの機能を実装できるのはいつの日になるのやら。。。。

Jenkinsへアップグレードしたい

Jenkins自体はもう何度も使用しているのですが、プロジェクトで使用中のHudsonをまだアップグレードしてません。
新しい開発プロジェクトが立ち上がったら、最初からJenkins導入しちゃうんですけど。

アップグレード手順について詳しくは、ここ(http://wiki.jenkins-ci.org/display/JENKINS/Upgrading+from+Hudson+to+Jenkins)の説明に
お任せするのですが、わたしもやってみましたので、作業履歴を載せておきます。


前提としては

  • HudsonがWindows上でサービスとしてインストールされている
  • hudsonというサービス名もjenkinsに変更したい

順番としては

  1. hudsonサービスを停止させる
  2. hudsonサービスを削除する
  3. jenkins.warをダウンロードし、適当な場所でjava -jar jenkins.warとして起動する
  4. Jenkinsの管理から「windowsサービスとしてインストールする」を実行する



アップグレード前の図



サービス停止&サービス削除

hudson.exe stop
hudson.exe uninstall

でOKです。

jenkins.warダウンロード&起動
ここ(http://mirrors.jenkins-ci.org/war/latest/jenkins.war)からダウンロードしました。

java -jar jenkins.war



jenkins起動後
現在、バージョン1.400ですね。


windowsサービスとしてインストールする


特に問題など発生せず、あっさりアップグレードできます。
アップグレード後に定義情報がなくなったとか、履歴が消えたとか、Slave接続がないとか、
そういった類のことは確認できませんでした。(←とはいってもまだ2〜3回しかやってないです)

スレーブ側のサービスも

hudson-slave.exe uninstall

でサービス削除できますので、登録しなおせばサービス名も「jenkins slave」となります。


みなさんも、Jenkinsにアップグレードしてみませんか。

Githubの仕組みを理解したい

Gitは使用し始めたばかりで色々と勉強しなければならないのですが、
本家からforkしたリポジトリの更新について勘違いしていました。



間違いの図1−1。赤い部分は出来ません。


Github上で本家をforkした後、本家の更新がどんどん進んでforkしたリポジトリもそれに
合わせて更新したい場合、本家からforkしたリポジトリへpullできるのかな?
とイメージしていたのですが、どうやらそうではないそうです。



一応正しい図1−2


正しくはローカルのリポジトリにpull(もしくはfetch&merge)した後、forkしたリポジトリにpushするとのことでした。


まず手始めにオリジナルのJenkins-ciのpom.xmlを修正した状態のプロジェクトをGithub上に用意しようと
試みた場合の図が以下になりました。

  • 本家をforkします。
  • ローカルに複製します。
  • 本家の修正が入っていたので、ローカルに反映させます。
  • pom.xmlを修正します。
  • Github上のforkしたプロジェクトに反映させます。


Gitに関して「べし・べからず」的なものがたくさんあって、これって本当に慣れるのかな。。。
私はしばらくpull requestなんてしないつもりですが、とりあえずforkしてしまいました!
Githubチュートリアルに従ってやっていたら、自然とforkボタンをポチッとしていましたが。


forkすると、そこは公開されているので、出先で変更をpushして家でpullして続きをみたいな
ことができると思うのですが、そんな用途にPublicなリポジトリを使用してはいけない気も。。。
そんな用途のためには、Githubのプランをアップグレードするのが本筋なんでしょうね。。。


そういえば、リポジトリサイト(http://maven.glassfish.org/content/groups/public/)が
Service Temporarily Unavailableになってしまいました。
気長に復旧するのを待つことに。

Proxy環境でもGithubを使用したい

Jenkinsのソースをビルドしたいというきっかけから、Githubを使用することになったのですが、
Proxy環境でもなんとか使用できないかなと思いはじめました。
ちょこっと検索すると同じようなことを思っている方々が多数いらっしゃるようで、
すぐにnobeansさんの日記(id:nobeans:20090520)にたどり着くことが出来ました。
私もconnect.cを使用する方法を実践してみました。


connect.cの作者の方()のページと思われるサイトが以下にあります。
http://bent.latency.net/bent/git/goto-san-connect-1.85/src/connect.html
上のほうのリンクにMSVCでコンパイル済みのWindows用バイナリがありましたので、こちらを利用させて頂きました。
.ssh/configの設定などは、すっかりnobeansさんの日記を参考にさせて頂きました。
設定を行うことでGithub上の自分の公開プロジェクトをローカルへ複製(clone)することが可能になりました。


暫くしていると、fork元のリポジトリが更新され、それらをforkした自分のリポジトリに反映させたくなると思います。
Githubのヘルプ(http://help.github.com/fork-a-repo/)にあるように

git remote add upstream git://github.com/jenkinsci/jenkins.git
git fetch upstream

と実行すると、以下のようになり実行できません。

どうやらGithub上にある自分のリポジトリをローカルに複製(clone)する動作と
Github上のupstreamに指定したリポジトリの変更を自分のリポジトリにマージする動作は
違うようで、Proxy環境内にいると難しいようです。


ローカルに複製する処理は、sshを使用するのでconnect.cで対応できました。
git://〜で始まる処理は、gitプロトコルを使用するとのことでport9418を使用するらしいのです。
ところが、GithubのヘルプにSmart Http Supportというページ(https://github.com/blog/642-smart-http-support)があって
そこからPro Git blogに飛べる(http://progit.org/2010/03/04/smart-http.html)んですが、よ〜く読んでみると
gitプロトコルの代わりにhttpプロトコルが使用できるようなことが書いてあります。


試してみました。

git config --global http.proxy "http://XX.XX.XX.XX:YYYY"
git remote add upstream https://github.com/jenkinsci/jenkins.git
git fetch upstream

今度は成功しました!
現在は、普通にhttp/https経由でgitコマンドが発行できるのですね。

Jenkinsをソースからビルドしたい

JenkinsのソースはGithubhttp://github.com/)にお引っ越ししたとのことで、Gitの勉強も兼ねてJenkinsをビルドしてみようと思い立ちました。
Githubにアクセスして、アカウントを取得するまであっという間です。
チュートリアルが用意されていますので、心配無用です。

Githubにログオンして、右上に検索ボックスがあります。

ここにJenkinsと入力して検索すると、

見つかりました!
ウォッチボタンを押下して、さっそくフォークします。
フォークしたらあとはローカルに複製します。
このあたりの手順は、Githubチュートリアルhttp://help.github.com/)にありますので、ここでは省略します。
ローカルに複製が終わったら、mavenにてパッケージングしてみます。
とりあえずビルドが通るか知りたかったので、テストはスキップします。


mvn clean package -Dmaven.test.skip=true

[INFO] 2 errors
[INFO] -------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Compilation failure

\Users\sikakura\jenkins\remoting\src\main\java\hudson\remoting\Launcher.java:[219,50] 警告:sun.misc.BASE64Encoder は Sun
 が所有する API であり、今後のリリースで削除される可能性があります。

\Users\sikakura\jenkins\remoting\src\main\java\hudson\remoting\Engine.java:[152,54] 警告:sun.misc.BASE64Encoder は Sun
が所有する API であり、今後のリリースで削除される可能性があります。


[INFO] ------------------------------------------------------------------------
[INFO] For more information, run Maven with the -e switch
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 35 seconds
[INFO] Finished at: Mon Mar 07 23:46:33 JST 2011
[INFO] Final Memory: 83M/543M
[INFO] ------------------------------------------------------------------------

ん、警告のはずがエラーで落ちてしまっています。。。
ちょっと検索をかけてみると、ssogabeさんのページ(id:ssogabe:20091030)がヒットしました。
kkawaさんのコメントに、バグチケットへのリンク(http://jira.codehaus.org/browse/MCOMPILER-109)があったので見てみます。
なるほど、、、どうやらmaven-compiler-pluginのバージョンが2.3.1だと上のバグチケットの問題があるようです。
チケットによると、2.3.2で修正されているようなので、ローカルに複製したJenkinsのpom.xmlを修正し、
同じコマンドを実行してみます。今度は同じエラーは発生しませんでした。一歩前進です。

さて、次のエラーです。

C:\Users\sikakura\jenkins\maven-plugin\src\main\java\hudson\maven\RedeployPublisher.java:192: 変換できない型
検出値  : hudson.model.AbstractBuild<capture#196 of ?,capture#270 of ?>
期待値  : hudson.maven.MavenModuleSetBuild
        if (!(build instanceof MavenModuleSetBuild)) {
              ^
C:\Users\sikakura\jenkins\maven-plugin\src\main\java\hudson\maven\RedeployPublisher.java:195: 変換できない型
検出値  : hudson.model.AbstractBuild<capture#102 of ?,capture#805 of ?>
期待値  : hudson.maven.MavenModuleSetBuild
        for (Entry<MavenModule, MavenBuild> e : ((MavenModuleSetBuild)build).getModuleLastBuilds().entrySet()) {
                                                                      ^

ソースを確認してみましたが、特に問題なさそうに見えます。。。
もう殆どお手上げ状態だったのですが、最後にJDKを最新にアップデートしてみました。
私が使用していたJDKは、6u17だったので、6u24にしてみました。
すると、上のエラーが発生しなくなりました。

途中でビルドしたアーティファクトmavenリポジトリ上に存在しないという旨のエラーが発生したので、
installゴールを追加することにしました。


mvn clean install package -Dmaven.test.skip=true

すると、以下のようにパッケージングが成功しました!

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Jenkins main module ................................... SUCCESS [6.006s]
[INFO] Jenkins remoting layer ................................ SUCCESS [27.986s]
[INFO] Jenkins CLI ........................................... SUCCESS [9.157s]
[INFO] Jenkins core .......................................... SUCCESS [11:57.039s]
[INFO] Jenkins Maven 2 PluginManager interceptor ............. SUCCESS [7.379s]
[INFO] Jenkins Maven2 CLI agent .............................. SUCCESS [4.493s]
[INFO] Jenkins Maven3 Interceptor ............................ SUCCESS [9.766s]
[INFO] Jenkins Maven3 CLI Agent .............................. SUCCESS [4.618s]
[INFO] Maven Integration plugin .............................. SUCCESS [5:14.481s]
[INFO] Jenkins war ........................................... SUCCESS [1:52.507s]
[INFO] Test harness for Jenkins and plugins .................. SUCCESS [7:05.974s]
[INFO] Jenkins UI sample plugin .............................. SUCCESS [56.098s]
[INFO] ------------------------------------------------------------------------
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 28 minutes 29 seconds
[INFO] Finished at: Tue Mar 08 01:49:39 JST 2011
[INFO] Final Memory: 127M/481M
[INFO] ------------------------------------------------------------------------

Jenkins Coreだけで約12分くらいかかっていますね。


ともあれ、これでビルドする環境が整いました。
次は、CLIの拡張に挑戦してみたいと思います。

Jenkinsでいきたい

言霊って言葉や考えがあるように名前とか、名称というのはとても重要だと思うのです。
Hudsonという名前でスタートし浸透してきただけあって、本人および関係者でなくとも改称は名残惜しいですが、
個人的には、川口さんが発信している内容やHudsonのリリースを通じて伝わってくる発想、方向性が好きなので、
Jenkinsを追い続けたいと思います。もちろんその周辺技術も。


「Hudson? へぇ〜昔はそんな名前だったんだ」


ってなると思うのです。そのうち。
関係者のみなさま、頑張ってください。応援しています。


川口さんの日記[id:kkawa:20110208]
InfoQでのJenkinsサーチ:http://www.infoq.com/jp/search.action;jsessionid=FD8CDB9FCFBD6E2ADC776049C0DEF118?queryString=Jenkins