読者です 読者をやめる 読者になる 読者になる

crontab -rを使えなくしてみた。

crontabは恐ろしいオプションがあるとそこらじゅうのブログで見ていて、crontab -eもしないようにcrontab file_nameで設定するように心がけていました。
で、いざやっていると、crontab file_nameの後ってうんともすんとも言わんのです。
こんな感じです。

[shiraji ~]% cat test.crontab
0 0 * * * /bin/bash /tmp/test.sh
[shiraji ~]% crontab test.crontab ### ★ここで何も言わない
[shiraji ~]% crontab -l
0 0 * * * /bin/bash /tmp/test.sh
[shiraji ~]%
本当に設定できたの?と毎回crontab -lをするはめに。
結局オプションコマンド必要じゃん!・・・じゃあcrontab file_nameの後に必ずcrontab -lをするようにしようとaliasを作ってみた。

結果、偶然crontab -rを使えなくできた。
crontab -u shiraji -rはできるのでご注意を。-rあればコマンドスルーでできると思うけど、複雑になるし、-uコマンド使わないから、省略。

てかcrontabなんて使わずに、Jenkins使えばいいと思う。

Thorを使いやすくしてみた(Ruby・Railsバッチ編)

以前のエントリー:Thorを使ってみた(Rubyバッチ編)
いくつかRailsのバッチを書いていて、共通な部分が結構あったので、クラスとして出して、使いやすくしてみました。

やっていること:

  • Helpの設定(-hでヘルプ表示)
  • ログレベルの設定(-l FATAL, ERROR, WARN, INFO, DEBUGの5種類)
  • ログファイルの設定(-f 上記ログレベルの設定がされていれば、出力先のログファイルを指定する)
  • 実行環境の設定(-e Railsの実行環境を設定)

■注意点
Railsの環境設定を行っているため、Thor::Invocation.invoke_commandを継承している。
Thorの更新が入った場合、invoke_commandメソッドの呼ぶタイミングが変更されるかも?
Thor.invoke_command
Thor.dispath
Thor.start
などを参照してみてください。

■使い方
前のTestバッチ

class Test < Thor
class_option :help, :type => :boolean, :aliases => '-h', :desc => 'Thor test'
default_task :execute
desc "execute [OPTION]", "Test execute"
option :production, :type => :boolean, :aliases => '-p', :desc => "Run production DB server"
option :number, :type => :numeric, :aliases => '-n', :default => 3, :desc => "test number"

def execute
# DBオプション
if options[:production] then
@mode = 'PRODUCTION'
else
@mode = 'DEVELOPMENT'
end

puts @mode
puts options['number']
end
end

Test.start

MyThorを使ったバッチ。

class Test2 < MyThor
default_task :execute
desc "execute [OPTION]", "Test execute"
option :number, :type => :numeric, :aliases => '-n', :default => 3, :desc => "test number"

def execute
puts Rails.env
puts options['number']
end
end

Test2.start

さらに簡単に。default_taskもMyThorに持っていくといいかもしれない。

===
はてなさんが直すのを待ち続けていたのですが、一向に直らないので、GistのCSSいじりました。多少見やすくなったのではないでしょうか。
コードが横に長いと行番号の横幅が無くなってしまい1行1桁しか表示されなくなってしまうのが問題でした。

デフォルトで!とおもったのですが、さすがに我慢できず。

TextViewのリンクに確認ダイアログをつけてみる。

今回はTextView!
半日悩んで解決したのでメモです。

条件

  1. TextViewに複数のURLが含まれる可能性がある。
  2. そのテキストは自分以外のだれかが入力したもので、HTMLフォーマット化されていない。
  3. そのURLに関して正しいかどうかを判断するロジックはGoodleさんに任せる。
  4. それぞれのURLごとに正しいURLに飛ぶこと。
  5. それぞれのURLをタップしたときに確認ダイアログをつける。

とりあえず簡単なやつから。
まずGoogleさんに判断させる部分だけど、

textView.setAutoLinkMask(Linkify.WEB_URLS);
この一行でOK。あとはGoogleさんがよきに計らってくれる。
複数URLがあっても問題なし。HTMLフォーマットも必要なし。
(ただし、複数あるが、連続してしまうとさすがに判別不能。まぁそこは無視。スプリットでもなんでもすればいい。)

大半の条件はこれで満たせるのだけど、これだけでは確認ダイアログが付けられない。
タップするとアプリから勝手にブラウザに飛んで行ってしまう。

それでは条件満たせないので、それぞれのリンクに確認ダイアログをつけることにする。
そしてここから泥沼へ。

それぞれのリンクはURLSpanというオブジェクトとして保管されている。(それすら今回初めて知りました)
このURLSpanのonClickメソッドを継承し、onClickメソッド内でダイアログ表示→OKならブラウザへで完成するはず。

しかし、残念ながら、TextViewのsetTextメソッドはfinalメソッドのため、継承できず、autoLinkの動きをかえることはできません!何か方法があれば教えてください。

ということで苦肉の策。。。

CharSequence text2 = textView.getText();
if( text2 instanceof Spannable ) {
Spannable spannable = (Spannable) text2;
URLSpan[] spans = spannable.getSpans(0, text2.length(), URLSpan.class);
for (URLSpan urlSpan : spans) {
int start = spannable.getSpanStart(urlSpan);
int end = spannable.getSpanEnd(urlSpan);
spannable.removeSpan(urlSpan);
spannable.setSpan(new MyURLSpan(urlSpan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}

やってること

  1. textView.getText()の中身がSpannableであったら、URLSpanを取得する。
  2. それぞれのURLSpanのスタートとエンドのポジションを取得する。
  3. そのURLSpanを削除する。
  4. 同じ場所にMyURLSpanをぶち込む!

全体
MainActivity.java

package com.example.textviewsample;

import android.app.Activity;
import android.os.Bundle;
import android.text.Spannable;
import android.text.Spanned;
import android.text.style.URLSpan;
import android.text.util.Linkify;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {

private static final String text = "やふる:http://yahoo.co.jp\nぐぐる:http://www.google.co.jp\nびんぐる:http://www.bing.com/";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

TextView textView = (TextView)this.findViewById(R.id.textview);
textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(text);

CharSequence text2 = textView.getText();
if( text2 instanceof Spannable ) {
Spannable spannable = (Spannable) text2;
URLSpan[] spans = spannable.getSpans(0, text2.length(), URLSpan.class);
for (URLSpan urlSpan : spans) {
int start = spannable.getSpanStart(urlSpan);
int end = spannable.getSpanEnd(urlSpan);
spannable.removeSpan(urlSpan);
spannable.setSpan(new MyURLSpan(urlSpan.getURL()), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

}

MyURLSpan.java

package com.example.textviewsample;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Parcel;
import android.text.style.URLSpan;
import android.view.View;

public class MyURLSpan extends URLSpan {

public MyURLSpan(Parcel src) {
super(src);
}

public MyURLSpan(String url) {
super(url);
}

@Override
public void onClick(final View widget) {
AlertDialog.Builder builder = new AlertDialog.Builder(widget.getContext());
builder.setTitle("確認");
builder.setMessage(getURL()+"へ飛びますか?");
builder.setPositiveButton("おっけー", new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onClickSuper(widget);
}
});
builder.setNegativeButton("やだ", null);
builder.show();
}

private void onClickSuper(View widget) {
super.onClick(widget);
}
}

activity_main.xml


実行結果

確認ダイアログ

何が驚いたって、textView.getText()がString以外のもの返すこと。
CharSequenceのことを全く気にせずStringしか使ってこなかった自分が恥ずかしい。

MyURLSpan込みのTextViewを作成してみたい!がんばる。
今日はここまで。

chef-soloでさくらVPSにJenkinsを立ててみる。(詳細編)

途中gdgdになってしまった、chef-soloでさくらVPSにJenkinsを立ててみる。ですが、たぶん今回が最終回。

プライベートに時間がさけるようになったので、chef-solo関連を整理してみた。
ただ、やっぱり独学なので、本当にこれでいいのかわかりません。本を買おうか悩み中です。

ではまず、自分的に綺麗にしたレポジトリは以下
https://github.com/shiraji/chef-repo

よくよく考えてみたら、chefだけで一つレポジトリ持っておけば、Jenkins専用とかにしなくてもいいじゃんと思ったので、chef-repoに改名しました。
これ書き終わったら、jenkins_server_settingsとか消す予定。

変更があったのはinstall_cookbook.shができたこと。これをキックするとBerkshelfを実行する。
Berkshelfは非常に便利で、gemでインストール後、必要なcookbookをOPSCODEのライブラリーから持ってこれる。
基本OPSCODEのものでいいと思うけど、違うものを指定することもできる。
今回必要なものはJavaとJenkins。設定ファイルはBerksfile
https://github.com/shiraji/chef-repo/blob/master/Berksfile

site :opscode
cookbook 'java'
cookbook 'jenkins'

たった三行・・・。理解すれば本当に簡単です。
最初に持ってくるサイトを指定し、必要なcookbookを指定する。
cookbookを引っ張ってくるとBerksfile.lockというファイルができる。これはGit管理する必要がないので、.gitignoreに入れておく。
あと、基本cookbookの中は他のところから引っ張ってきたcookbook、
site-cookbooksには自分が作成したcookbookをというのが流儀っぽいので、cookbookも.gitignoreに入れておく。

次にcookbooks関連。
これは以前説明した通り、サーバの基本的な設定とユーザ作成。

nodes
nodeはインストールさせる各サーバごとに持たせるもの。
今回はjenkinsをインストールするサーバ用なので、jenkins.jsonを作成。
中身はユーザ作って、サーバの設定をして、jenkinsをインストールする。

roles
2つ作成。
1つ目
https://github.com/shiraji/chef-repo/blob/master/roles/adduser_shiraji.json

これは以前作成したaddusersというレシピのデフォルトの値が外出しされているものです。
なぜこのようなことをしたかというと、次にshiraji2というユーザを作成したい場合、roleに同じようなadduser_shiraji2.jsonを作成して、それをnodeで呼べばいいため。
基本、デフォルトの値を使わず、roleで上書きするようにすると毎回変更しなくてもすむ。(デフォルトの値は残しておいても問題ないのだけど。)

2つ目
https://github.com/shiraji/chef-repo/blob/master/roles/jenkins.json

これは以前にもあったものと同じ。Javaのoverride_attributesとJenkinsインストールするという設定が記載されている。

こんな感じでroleとnodeをうまく使って、拡張性を持たせたほうがいいですよという話。(ただ、それが正しいかどうかは知りませんw)

一番難しかったのはroleとnodeの関係。この2つを理解すれば、本当に簡単にセットアップできました。

chef-soloでさくらVPSにJenkinsを立ててみる。(Jenkinsさんが立った編)

この記事は古いです。
jenkins_server_settingsのレポジトリは削除しました。

こちらのURLを確認して下さい。
http://d.hatena.ne.jp/shiraji/20130713/1373679753

===

Jenkinsサーバが立った!のでまとめてみます。
はっきりいって、まだ汚いので、後で自分なりに綺麗にしてみるつもりです。
まずは一つのレポジトリにまとめてみました。

https://github.com/shiraji/jenkins_server_settings

とりあえず、めんどくさいので、chef-soloが動かせるようにprepare_chef_solo_no_yum.shなるものを作成しました。
これは以前公開したものにberkshelfなどを追加したものです。
さくらVPSでOS初期化後すぐに利用するという想定なので、まぁ問題ないはず。

次にキックするのがprepare_jenkins.shです。
この中身が

Berkshelfをキックしてから、chef-soloを実行しています。

nodes/chef.jsonの中身が↓

roleにある、jenkinsという名前がついたroleを利用する。

role/jenkins.jsonの中身が↓

やっていることとして、orcleのjavaをインストールし、shirajiというユーザを作成し、サーバの設定をして、jenkinsをインストールするといったもの。
詳細はhttp://d.hatena.ne.jp/shiraji/20130222/1361547796

Berkshelfがほんとに便利。おかげで、javaとJenkinsのインストールほぼ自分は何もせずに完了した。
細かい設定は後々するとして、とりあえず、Jenkinsおじさんの顔が見れたので満足しました。

各ファイルの説明は後々します。できた!という報告でした。

Jenkinsで変更点をメールする方法

以前書いた、Jenkinsでログの一部分からメールを作成する方法ではログの一部分を送る方法を記載した。
メールで変更点もわかるようなのだが、公式のドキュメントに一切説明がない。

トークンリファレンスにさらっと書かれているが非常にわかりづらいため、メモとして残す。
(このエントリー書いたのに消えてた・・・。)

変更点を送るには3つ方法がある。
${CHANGES, showPaths, showDependencies, format, pathFormat}
直近のビルドからの変更点
${CHANGES_SINCE_LAST_SUCCESS, reverse, format, showPaths, changesFormat, pathFormat}
直近の成功ビルドからの変更点
${CHANGES_SINCE_LAST_UNSTABLE, reverse, format, showPaths, changesFormat, pathFormat}
直近のUNSTABLEか成功ビルドからの変更点



showDependencies - 使ったことないからわからない
showPaths - ファイルパスを表示するかどうか
format - 表示フォーマット
pathFormat - ファイルパスの表示方法のフォーマット

formatとpathFormatで使える変数
%a コミットした人
%d コミットした日付
%m コミットメッセージ
%p ファイルパス
%r リビジョン

ただし、%dと%rは全てのバージョン管理システムでサポートされているか不明。

CHANGES_SINCE_LAST_SUCCESSとCHANGES_SINCE_LAST_UNSTABLEの設定
reverse - true: 最新の変更が上にくる。デフォルトがfalse
format - それぞれのビルドでの表示方法。
%c 変更点
%n ビルド番号

showPaths, changesFormat, pathFormatはCHANGESのshowPaths, format, pathFormatと同じ


それで今回こんな感じで作ってみた。

${CHANGES, format="コミッター: %a\nリビジョン: %r\n変更ファイル: %p\nコメント: %m", pathFormat="\n\t-\ %p"}
結果:
コミッター: shiraji
リビジョン: SHA1のリビジョン
変更ファイル:
- folder/filename
- folder/filename2
コメント: ext-mail test

なぜか、showPaths=trueを設定すると動かない(v2.25)。まぁ問題ないから別にいいやということで放置。

Thorを使ってみた(Rubyバッチ編)

ちょっとJenkinsのことは置いておいて、rubyでバッチを書くことになった。

悩んで、いろいろ試してみた。
まずは、Thor。
Railsにデフォルトで入っているのだけど、Thorだけで利用してみる。

実行結果
ヘルプ1
# ruby thor_test.rb help
Commands:
thor_test.rb execute [OPTION] # Test execute
thor_test.rb help [COMMAND] # Describe available commands or one specific command

Options:
-h, [--help] # Thor test

ヘルプ2
# ruby thor_test.rb help execute
Usage:
thor_test.rb execute [OPTION]

Options:
-p, [--production] # Run production DB server
-n, [--number=N] # test number
# Default: 3
-h, [--help] # Thor test

Test execute

デフォルト
# ruby thor_test.rb
DEVELOPMENT
3

パラメータ設定
# ruby thor_test.rb -p -n=100
PRODUCTION
100

こんな感じで、ソース少ないのに、複雑なことができちゃう。

===
共通部分を抽出して、バッチ作成を簡単にできるようにしてみました。
Thorを使いやすくしてみた(Ruby・Railsバッチ編)