Objective-cでNSArrayのcountではまったこと。

10月くらいからObjective-cを触っている。
結構毎日触っているんだけど、まだまだわからないことが多くて大変です。
さらにプライベートではwindows使っていて、Mac持っていないので、会社でしか触れていなくて。Mac買おうかなー。

ということで初歩的なところではまったのでメモ。

// 配列
NSArray array = @[@"赤",@"青",@"黄",nil];

for (NSIntger index = -1; index < [array count]; index++) {
if (index > 0) {
NSLog(@"%@", [array objectAtIndex:index]);
} else {
NSLog(@"%@", @"以下から選んで下さい。");
}
}

こんな感じで、ループさせてみた。まぁなにゆえ、ループ前にやらないのかとかあるけど、とりあえずそこはおいておいて。
このループ実は実行されない。なぜかというと[array count]の戻り値がNSUInteger。つまり、unsigned integer。
index=-1を比較する場合、indexがunsigned integerとして扱われ、-1はunsigned integerの一番大きな数字となる。

Javaにはないunsigned型があるので注意しましょうということでした。

chef-soloでcookbookを作成する

以前からJenkinsさんをchefで立ち上げるところまで自動化したのですが、
Jenkinsさんの自動起動が設定されていなかった。
ドキュメントを流し読みしてみても、Jenkinsさんの自動起動設定がない。
じゃあそういうレシピ作成してみよかと思ったのですが、数カ月前ということもあり、完全に失念!

ということで、chefのcookbook作成方法のメモです。

まず、環境を作る。

git clone https://github.com/shiraji/chef-repo.git
cd $HOME/chef-repo
bash chef_setup.sh
これで問題なければ、今回使うchefやknifeが入る。
chef-soloなので、knifeの設定(knife configure)をせずに突き進む。

以前にも書いたと思うけど、ざっくり構成についておさらい。

chef-repo/
├── cookbooks --- 人が作ったcookbooksを保持
├── nodes --- 実行される各サーバ固有の設定を保持
├── roles --- cookbook利用時の設定値を保持
└── site-cookbooks --- 自分が作成したcookbookを保持
├── addusers --- 自作ユーザ追加cookbook
└── server_settings --- 自作サーバの基本設定のcookbook
nodesとrolesはかなり重要で、この2つを意識したcookbookを作れば、拡張性が高くなる。

実際にcookbookを作成してみる。

cd $HOME/chef-repo/site-cookbooks/
knife cookbook create autorun_settings -o .
knifeが色々揃えてくれた結果がこれ
site-cookbooks/
└── autorun_settings
├── CHANGELOG.md
├── README.md
├── attributes --- attributeを設定する。default.rbがデフォルトの値
├── definitions
├── files
│ └── default
├── libraries
├── metadata.rb
├── providers
├── recipes
│ └── default.rb --- デフォルトのrecipe
├── resources
└── templates
└── default
Hello worldしてみる。

デフォルトのrecipe(recipes/default.rb)に以下を書き込む。

puts "Hello World"
これだけでほぼ終わりなのだけど、せっかくなので、nodes, rolesを設定する。

まずはrole。$HOME/chef-repo/roles/にjenkins_autorun.jsonを作成する。
本当はここで、knife role createとかするべきなんだけど、knifeの設定してないしーということで簡単なスクリプト作成したので、それを利用して、作成する。

cd $HOME/chef-repo/roles/
bash new_role.sh jenkins_autorun.json
new_role.shはsedでテンプレートファイルの中身を変更し、それをファイルに保存している。
#!/bin/sh

if [ $# -ne 1 ]; then
echo "No role name"
exit 1
fi

_role_name=$1

cat role_template.json | sed -e "s/NAME/${_role_name}/g" > ./${_role_name}.json

role_template.jsonはknife create roleすると出来るべきroleファイル
{
"name": "NAME",
"description": "",
"json_class": "Chef::Role",
"default_attributes": {
},
"override_attributes": {
},
"chef_type": "role",
"run_list": [

],
"env_run_lists": {
}
}

上記のコマンド叩いて、出来るのがこのrole。(コピペするだけでも問題ない。)
{
"name": "jenkins_autorun",
"description": "",
"json_class": "Chef::Role",
"default_attributes": {
},
"override_attributes": {
},
"chef_type": "role",
"run_list": [

],
"env_run_lists": {
}
}

今のところ、attributesなどはないので、設定せず、run_listに先程作成したcookbookを設定する。
{
"name": "jenkins_autorun",
"description": "",
"json_class": "Chef::Role",
"default_attributes": {
},
"override_attributes": {
},
"chef_type": "role",
"run_list": [
"recipe[autorun_settings]"
],
"env_run_lists": {
}
}
次にnode
run_listに上記で作成したroleを指定する。
{
"run_list": [
"role[jenkins_autorun]"
]
}
実行で必要な設定ファイル(chef-repo/solo.rb)はこんな感じ。
le_cache_path           "#{ENV['HOME']}/chef-repo"
data_bag_path "#{ENV['HOME']}/chef-repo/data_bags"
encrypted_data_bag_secret "#{ENV['HOME']}/chef-repo/data_bag_key"
cookbook_path [ "#{ENV['HOME']}/chef-repo/site-cookbooks",
"#{ENV['HOME']}/chef-repo/cookbooks" ]
role_path "#{ENV['HOME']}/chef-repo/roles"
$HOME直下に置いてあると想定されている。違う所に置いてある場合は、このファイルを変更すること。
拡張子で一目瞭然だけど、solo.rbはrubyファイル。$HOMEは使えないので注意。

それでは実行

cd $HOME/chef-repo
chef-solo -c solo.rb -j nodes/jenkins_autorun.json
実行結果
Starting Chef Client, version 11.6.0
Compiling Cookbooks...
Hello World
Converging 0 resources
Chef Client finished, 0 resources updated
これで、Hello Worldはできたので、今回の目的であるjenkinsのサービスの自動起動をしてみる。
chkconfig jenkins onを実行すればいい。
resourcesというものがchefで実行されるコマンド。詳細はここhttp://docs.opscode.com/resource.html
このresourcesとrubyの文法を駆使してrecipeを作成していく。
簡単なlinuxのコマンドのため、resourcesはexecuteを利用する。
chef-repo/site-cookbooks/autorun_settings/recipes/default.rbに以下を記載する
execute "autorun" do
command "chkconfig jenkins on"
action :run
end
ただ、これだと、jenkinsのみのcookbookになってしまう。そこでattributeを設定し、roleで指定できるようにする。
まずはデフォルトのattributeを作成する。
chef-repo/site-cookbooks/autorun_settings/attributesにdefault.rbを作成。中身は以下。
default[:autorun][:ons] = ["jenkins"]
これを使うrecipeはこんな感じ。
node[:autorun][:ons].each do |on|
if !on.empty?
execute "autorun on" do
command "chkconfig #{on} on"
action :run
end
end
end
実際に実行してみると
Starting Chef Client, version 11.6.0
Compiling Cookbooks...
Converging 1 resources
Recipe: autorun_settings::default
* execute[autorun on] action run
- execute chkconfig jenkins on

Chef Client finished, 1 resources updated
これでもまだ、拡張性がないので、autorunの設定のON、OFFができるようにする。
まずデフォルトの設定を以下にする。
default[:autorun][:ons] = []
default[:autorun][:offs] = []
デフォルトのrecipeを以下に
node[:autorun][:ons].each do |on|
if !on.empty?
execute "autorun on" do
command "chkconfig #{on} on"
action :run
end
end
end

node[:autorun][:offs].each do |off|
if !off.empty?
execute "autorun off" do
command "chkconfig #{off} off"
action :run
end
end
end

ここで、roleでoverride_attributesを設定する。chef-repo/roles/jenkins_autorun.jsonを以下のようにする。
{
"name": "jenkins_autorun",
"description": "",
"json_class": "Chef::Role",
"default_attributes": {
},
"override_attributes": {
"autorun": {
"ons": ["jenkins"]
}

},
"chef_type": "role",
"run_list": [
"recipe[autorun_settings]"
],
"env_run_lists": {
}
}
実行結果(上記と同じ)
Starting Chef Client, version 11.6.0
Compiling Cookbooks...
Converging 1 resources
Recipe: autorun_settings::default
* execute[autorun on] action run
- execute chkconfig jenkins on

Chef Client finished, 1 resources updated
これで完了。

■注意点■
私は今CentOSの環境のみしかないので、chkconfigが使えないubuntuとか気にしていない。
必要な場合はOS別のコマンドを作成すればいいと思う。

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おじさんの顔が見れたので満足しました。

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