メールでJenkinsにコマンドを送りたい(3) 〜Publisher編〜

前日までの実装では、メールの件名(subject)に書かれた内容をCLIコマンドとして認識し、
実行するところまでできました。ですが、このままだと実行結果が分かりません。
もともと思い描いていた構想の2番目は、


2.成功・失敗を問わず、実行結果をコマンド送信元のメールアドレス宛てに送信したい

    • 標準のメール通知だと成功時にメールが飛ばない
    • Publisherを拡張したらできそう?


という感じで、成功や失敗を問わずに結果を知りたいし、コマンド送信を行ったアドレスに対して
結果が通知されてほしいのです。というわけで、Publisherを拡張して専用の通知を作成したいと思います。
今回の設定画面では、チェックだけ付けられればOKなので、以下のような(ほぼ空の)config.jellyを用意しました。

<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
</j:jelly>

また、リソースとしてhelp_ja.htmlファイルを置いておくと、説明として認識されます。
上で設定したチェックボックスの右側に?アイコンがついて、クリックすると説明が展開される仕組みのようです。
help_ja.htmlの内容は、以下のようなものです。

<div>
  Mail Commander実行後、コマンドの送信元にジョブの結果をメールします。
</div>

help_ja.html上は日本語圏向けなので、それ以外用としてhelp.htmlも用意しておきます。


Publisherの書き方は、サンプルに無いので分からないのですが、標準で用意されているメール通知自体が
Publisherを継承している(正確にはPublisherを継承しているNotifierをさらにMailerが継承している)ので、
hudson.tasks.Mailerを参考にして作成しました。

public class MailCommandPublisher extends Publisher {
    @Override
    public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {

        File logfile = build.getLogFile();
        StringBuffer logbuf = new StringBuffer();
        try {
            FileReader in = new FileReader(logfile);
            BufferedReader br = new BufferedReader(in);
            String line;
            while ((line = br.readLine()) != null) {
                logbuf.append(line);
                logbuf.append("\n");
            }
            br.close();
            in.close();
        } catch (IOException e) {
            listener.getLogger().println(e);
            return false;
        }

        File addressFile = new File(build.getRootDir(), "tmp.address");
        String to_address = null;
        if (addressFile.exists()) {
            try {
                FileReader in = new FileReader(addressFile);
                BufferedReader br = new BufferedReader(in);
                String line;
                while ((line = br.readLine()) != null) {
                    to_address = line;
                }
                br.close();
                in.close();
            } catch (IOException e) {
                listener.getLogger().println(e);
                return false;
            }
            if (to_address != null) {
                try {
                    MimeMessage msg = new MimeMessage(Mailer.descriptor().createSession());
                    msg.setRecipients(RecipientType.TO, to_address);
                    msg.setFrom(new InternetAddress(Mailer.descriptor().getAdminAddress()));
                    msg.setSubject("This is a result of mail command");
                    msg.setSentDate(new Date());
                    msg.setText(logbuf.toString());
                    Transport.send(msg);
                } catch (MessagingException mex) {
                    listener.getLogger().println(mex);
                    mex.printStackTrace();
                    return false;
                }
            }
        }
        return true;
    }

    public BuildStepMonitor getRequiredMonitorService() {
        return BuildStepMonitor.NONE;
    }
    public static DescriptorImpl DESCRIPTOR;

    public static DescriptorImpl descriptor() {
        return Hudson.getInstance().getDescriptorByType(MailCommandPublisher.DescriptorImpl.class);
    }

    @Extension
    public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
        public DescriptorImpl() {
            load();
            DESCRIPTOR = this;
        }
        public String getDisplayName() {
            return Messages.MailCommandPublisher_DisplayName();
        }
        @Override
        public Publisher newInstance(StaplerRequest req, JSONObject formData) {
            MailCommandPublisher m = new MailCommandPublisher();
            return m;
        }
        @Override
        public boolean isApplicable(Class<? extends AbstractProject> jobType) {
            return true;
        }
    }
}


<performメソッドについて>
やっていることは大きく2つで、1つ目は、buildのログを取得してStringBufferに溜め込むことです。
2つ目はメール送信元に対して、StringBufferの内容をメールで送信しています。
メールの送信元は、Builderでしか分からないのでBuilderでメールのFromアドレスをファイルに保存し、
そのファイルの内容をPublisherで開いて宛先にセットしています。(←この方法が良いかどうか。。。不明)
JavaMailの送信では(おそらく他の送信プログラムでも)、Fromアドレスは必須となっているため
Jenkinsのシステム設定にある管理者のメールアドレスを利用しました。⇒Mailer.descriptor().getAdminAddress();
ここに適切なアドレスが指定されていないと送信エラーとなります。上のコードではエラーチェックしてませんが。


今回はわざと間違ったコマンドを送信してみたいと思い、件名に「build -s -p aaa=bbb」として送信してみました。

すると、以下のようなメッセージが帰ってきます。



いままで作成したBuilderとPublisherを組み合わせれば、やりたいことが出来ることがわかったのですが、あくまで定期的に実行しないと駄目です。
標準の定期実行でこのジョブを実行すると、該当するメールが無かった場合もビルド履歴として残ってしまいます。
SCMのポーリングというトリガーを利用すると、SCMに変更が無い場合はビルドが実行されず、余計なビルド履歴が残りません。
次回は、Triggerを使用してこの問題をクリアしたいと思います。