tokuhirom's blog

OSS版 Ansible Tower alternative である Ansible Semaphore を試したぞ!

Ansible Tower の OSS Alternative であるところの Ansible Semaphore を試した。 Ansible を利用してデプロイするのの web ui である。

installation guide 通りにやれば利用可能。

docker run -d --name=mysql -p 127.0.0.1:3306:3306 -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql

などとして mysqld を立ち上げておく(もちろん、別に docker じゃなくてもいい)。

https://github.com/ansible-semaphore/semaphore/releases から最新版のバイナリを取得(golang なので single binary)。

semaphore -setup

として、起動。セットアップが始まる。セットアップは以下の様な感じ。

$ ~/Downloads/semaphore_darwin_amd64 -setup

 Hello! You will now be guided through a setup to:

 1. Set up configuration for a MySQL/MariaDB database
 2. Set up a path for your playbooks (auto-created)
 3. Run database Migrations
 4. Set up initial seamphore user & password

 > DB Hostname (default 127.0.0.1:3306):
 > DB User (default root):
 > DB Password: my-secret-pw
 > DB Name (default semaphore):
 > Playbook path:

 Generated configuration:
 {
        "mysql": {
                "host": "127.0.0.1:3306",
                "user": "root",
                "pass": "my-secret-pw",
                "name": "semaphore"
        },
        "port": "",
        "bugsnag_key": "",
        "tmp_path": "/tmp/semaphore",
        "cookie_hash": "3aIKJIwVCvXH6vLGb8xhQHMr90OeVZ1SimYMNJBWD+A=",
        "cookie_encryption": "TDFElfq2JZN5PfTfPedRjgMZ+MZUY9VgNTZCS/sGJTY="
 }

 > Is this correct? (yes/no): yes
 Running: mkdir -p /tmp/semaphore..
 Configuration written to /tmp/semaphore/semaphore_config.json..
 Pinging database..

 Running DB Migrations..
Creating migrations table
Executing migration v0.0.0 (at 2016-06-25 08:44:13.784368138 +0900 JST)...
 [11/11]
Executing migration v1.0.0 (at 2016-06-25 08:44:14.340635482 +0900 JST)...
 [7/7]
Executing migration v1.1.0 (at 2016-06-25 08:44:14.752282549 +0900 JST)...
 [1/1]
Executing migration v1.2.0 (at 2016-06-25 08:44:14.861796122 +0900 JST)...
 [1/1]
Executing migration v1.3.0 (at 2016-06-25 08:44:14.918184638 +0900 JST)...
 [3/3]
Executing migration v1.4.0 (at 2016-06-25 08:44:15.103055625 +0900 JST)...
 [2/2]
Executing migration v1.5.0 (at 2016-06-25 08:44:15.220915164 +0900 JST)...
 [1/1]
Executing migration v0.1.0 (at 2016-06-25 08:44:15.279912624 +0900 JST)...
 [6/6]

 > Username: foobar
 > Email: foobar@example.com
 > Your name: Foo Bar
 > Password: *********

 You are all setup Foo Bar!
 Re-launch this program pointing to the configuration file

./semaphore -config /tmp/semaphore/semaphore_config.json

 To run as daemon:

nohup ./semaphore -config /tmp/semaphore/semaphore_config.json &

 You can login with foobar@example.com or foobar.

設定ファイルが生成されるんで、メッセージどおりにそれを使って起動すれば OK.

ここまでは極めて簡単にできる。

現時点の最新版である v2.0.2 には致命的な問題がある(全くデプロイできない)ので、困ったなーって思って git log 見たら HEAD では治ってたので、リリースしてよ、って言ったら音速でリリースされた。

が、v2.0.2 でエンバグしていたので、パッチを送ってみたが、そうじゃないって言われたので様子を見ている。たぶんためそうと思ってる人はv2.0.3まで待つのが良い。 https://github.com/ansible-semaphore/semaphore/issues/142

開発

patch 送ったりするにはどーすりゃいいか。golang の開発環境はすでにあるとして、、

circle.yml の以下の部分を参考に、手でインストールする。。 https://github.com/ansible-semaphore/semaphore/blob/master/circle.yml#L7-L17

そしたらば、./make.sh watch とすると、ファイル変更すると自動で更新がかかる感じで、プロセスが起動する。

ってだけ。

利用手順

まず、Dashboard を見て、プロジェクトを追加します。 https://gyazo.com/b696178e7abea2ed4297ba65197f4698

次に、Key Store に ssh key を登録します。ここに登録した ssh key は、ログインと git の clone の両方に利用されます。 https://gyazo.com/8eaefe8f15641469d535517226c81d5b

playbook 登録してるレポジトリを登録しまうす。git clone されるだけなので、file:// でもなんでもOK. https://gyazo.com/e2e5d675faa41fbaffd2b0b5636075ca

インベントリを登録します。これは ansible コマンドでいうところの ansible-playbook -i www playbook.yml の www にあたる部分ですね。 https://gyazo.com/adb05fbf1c02e19057635dd3ed3110a4

Environment を登録します。ansible 内で variable として利用可能になる。のかな?よくわかってない。 https://gyazo.com/27cc47602aed92ce743cb438ab936649

最後にこれらの要素を利用して、タスクを登録。 https://gyazo.com/7d49c11134e177bbb8f98f0471dfb97c

run ボタン押すと、タスク実行画面が起動する。ここで、変数をいじったりとかできるので便利。 https://gyazo.com/6c3ab1ea2f72261161533e67258e208f

実行すると、普通に動く。 https://gyazo.com/7046532dee6aab89dab50290a9759b97

まとめ

v2 が出たばかりで v2 は荒削りだが、アーキテクチャは綺麗なので良さそう。 go+ansible みたいな構成になってていじりやすい。コードは綺麗。インストールも楽。 今後に期待がモテる。

Created: 2016-06-25 01:06:38 +0000
Updated: 2016-06-25 01:06:38 +0000

circleci_ikachan を書いた

https://github.com/tokuhirom/circleci_ikachan

circle ci の webhook を受け取って、ikachan に forward するやつです。 ちょっと必要だったので書きました。

今回は、go で書いてみました。

Created: 2016-06-24 23:33:06 +0000
Updated: 2016-06-24 23:33:06 +0000

Promgen talk - Prometheus casual talks

I promoted promgen, a prometheus configuration management console web app at Prometheus Casual Talks.

I'll be release it as OSS application.

Created: 2016-06-17 03:13:52 +0000
Updated: 2016-06-17 03:13:52 +0000

How do I display custom element with IncrementalDOM?

Incremental DOM is great library to build dynamic DOM tree.

If you want to build DOM tree contains custom tags, you need to call IncrementalDOM.skip() to skip patching DOM elements inside custom element.

<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="node_modules/incremental-dom/dist/incremental-dom.js"></script>
  </head>
<body>

<div id="container"></div>

<script>
document.registerElement('x-bar', (function () {
  var proto = Object.create(HTMLElement.prototype);
  proto.createdCallback = function () {
    IncrementalDOM.patch(this, function () {
      IncrementalDOM.elementOpen('div');
      IncrementalDOM.text('hello');
      IncrementalDOM.elementClose('div');
    });
  };
  return {prototype:proto};
})());

const container = document.getElementById('container');
for (let i=0; i<3; ++i) {
  IncrementalDOM.patch(container, function () {
    IncrementalDOM.elementOpen('x-bar');
    IncrementalDOM.skip();
    IncrementalDOM.elementClose('x-bar');
  });
}

</script>

</body>
</html>
Created: 2016-06-14 06:25:16 +0000
Updated: 2016-06-14 06:25:16 +0000

x-tag で delegate しているときに root element を取得する方法

http://stackoverflow.com/questions/30895067/x-tag-event-delegation-accessing-the-root-element

xtag.register('x-foo', {
  content: '<input /><span></span>',
  events: {
    focus: function(e){
      // e.currentTarget === your x-foo element
    },
    'tap:delegate(span)': function(e){
      // e.currentTarget still === your x-foo element
      // 'this' reference set to matched span element
    }
  }
});

のように e.currentTarget を見れば取得可能。

Created: 2016-06-06 23:28:25 +0000
Updated: 2016-06-06 23:28:25 +0000

spring-data-elasticsearch について

ES 2 が出てから半年以上経過しているにもかかわらず 2 対応がされていない。 https://www.elastic.co/blog/elasticsearch-2-0-0-released https://github.com/spring-projects/spring-boot/issues/4339

クエリをすべてラップしているために 2 対応がなかなか難しいのだと思う。

Elasticsearch の場合、1系用のクライアントライブラリで2系にアクセスすると接続拒否されるし、なかなか厳しい。。

_score の取得が一筋縄ではいかない。 http://stackoverflow.com/questions/35175319/spring-data-elasticsearch-metadata-score

もともと elasticsearch 自体の java client は良く出来ているので、あまりラッパをかます必要がないのになぜかましてしまったのか。。

「楽になっている部分 < 負担になっている部分」という感じで、採用メリットが薄いし、後々負債になる可能性が高いと思う。 (この手の「便利なラッパークラス」にありがちだが)

Created: 2016-06-03 22:52:45 +0000
Updated: 2016-06-03 22:52:45 +0000

[prometheus][java] Added Spring Boot Metrics integration to Prometheus' simpleclient_java

https://github.com/prometheus/client_java/pull/114

I sent p-r for clientjava repository ... I want to export spring boot metrics to simpleclientservlet. The p-r was merged in master branch. The patch will include in next release.

Created: 2016-05-26 05:02:30 +0000
Updated: 2016-05-26 05:02:30 +0000

[golang] json_path_scanner 書いた

https://github.com/tokuhirom/json_path_scanner

JSON を読み取ったデータ構造を食わせると、「JSON Path」と「値」のペアのリストを得られるというやつ。 すでにありそうだったけど見当たらなかったので書いた。

Created: 2016-05-22 00:19:58 +0000
Updated: 2016-05-22 00:19:58 +0000

[prometheus] apache_exporter なおした

https://github.com/neezgee/apache_exporter Prometheus で apache を監視するための agent として apache_exporter があるが、これが全く動いてなかったので、動くようになおしておきました。

Created: 2016-05-18 00:18:37 +0000
Updated: 2016-05-18 00:18:37 +0000

[ruby] 10s 10m みたいな文字列から秒数を求めるには chronic_duration を使う

https://github.com/hpoydar/chronic_duration

なにかの実行間隔のような設定がある場合、chronicduration を利用してパースすることができる。 実行例は以下の通り。integer をそのまま渡すとエラーになるので、integer が渡ってきそうなケースでは tos して渡すようにするのがよさそう。 パースできない場合には nil が返ってくるので、nil もケアしてあげる必要があります。

[1] pry(main)> require 'chronic_duration'
=> true
[2] pry(main)> ChronicDuration.parse(3)
NoMethodError: undefined method `downcase' for 3:Fixnum
from /Users/tokuhirom/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/chronic_duration-0.10.6/lib/chronic_duration.rb:181:in `cleanup'
[3] pry(main)> ChronicDuration.parse(3.to_s)                                                                                                  
=> 3
[4] pry(main)> ChronicDuration.parse('3')                                                                                                      
=> 3
[5] pry(main)> ChronicDuration.parse('3s')
=> 3
[6] pry(main)> ChronicDuration.parse('3m')                                                                                                     
=> 180
[7] pry(main)> ChronicDuration.parse('3h')                                                                                                     
=> 10800
[8] pry(main)> 3*60*60
=> 10800
[9] pry(main)> ChronicDuration.parse("carrot")
=> nil
Created: 2016-05-17 01:21:38 +0000
Updated: 2016-05-17 01:21:38 +0000

sequel の migration に関するメモ

Sequel には migration 機能がついている。これを利用するには以下のようなファイルを作成する。

Sequel.migration do
  change do
    create_table(:artists) do
      primary_key :id
      String :name, :null=>false
    end
  end
end

ファイル名は db/migrations/001_init.rb とかにする。

rakefile に以下のように書く。DATABASE_URL=sqlite://hoge rake db:migrate とかすればマイグレーションされる。 namespace :db do desc "Run migrations" task :migrate, [:version] do |t, args| require "sequel" Sequel.extension :migration db = Sequel.connect(ENV.fetch("DATABASE_URL")) if args[:version] puts "Migrating to version #{args[:version]}" Sequel::Migrator.run(db, "db/migrations", target: args[:version].to_i) else puts "Migrating to latest" Sequel::Migrator.run(db, "db/migrations") end end end

と、公式のマニュアルに書いてあった。 https://github.com/jeremyevans/sequel/blob/master/doc/migration.rdoc

Created: 2016-05-15 09:32:54 +0000
Updated: 2016-05-15 09:32:54 +0000

個人的な grafana に対する不満トップ3

Editable をオフにすると戻せない

https://github.com/grafana/grafana/issues/2554

You can make it editable again using the Save As... feature and enter the same name. I agree though, there should be an easier way to make it editable.

えぇ~ そんなことってある? ッて感じのISSUE。

This is done and available in nightly and soon grafana 3.0 とのことなので 3.0 を正座して待つしかない

ダッシュボードの設定変更履歴が把握できないし、誰かがいじっても戻せない

https://github.com/grafana/grafana/issues/4638 誰かがいじっちゃったとしてそれを把握したり復元したりできないと辛い。。

4.0 まででなさそうで辛い。

Home Dashboard のロゴ変えられない

変えたい。

Created: 2016-05-10 10:21:23 +0000
Updated: 2016-05-10 10:21:23 +0000

CPU の system が妙に高いぞって時に犯人さがすには pidstat 使えば良い。

pidstat -h -u | sort -nr -k 5,5 | head すれば良い。

sudo yum install -y sysstat してインストール。

簡単に誰が system 消費してるかわかる [tokuhirom@centos-1gb-sgp1-01 ~]$ pidstat -h -u | sort -nr -k 5,5 | head 1462874412 0 27 0.00 0.22 0.00 0.22 0 kswapd0 1462874412 0 19281 0.16 0.09 0.00 0.25 0 cadvisor 1462874412 0 342 0.01 0.02 0.00 0.03 0 systemd-journal 1462874412 0 377 0.00 0.01 0.00 0.01 0 auditd 1462874412 0 262 0.00 0.01 0.00 0.01 0 jbd2/vda1-8 1462874412 0 16958 0.02 0.01 0.00 0.02 0 tuned 1462874412 0 13775 0.00 0.01 0.00 0.01 0 xfsaild/dm-9 1462874412 0 11 0.00 0.01 0.00 0.01 0 rcuos/0 1462874412 0 10 0.00 0.01 0.00 0.01 0 rcu_sched

Created: 2016-05-10 10:01:11 +0000
Updated: 2016-05-10 10:01:11 +0000

blog のバグを直した

ブログシステムをうっかり書き直したらちょっとバグってたので直した。

  • ctime/mtime を timestamp で保持していたが git なので保持されてなくて死んでたのでファイルの中に ctime/mtime を入れるようにした
  • entry の個別ページの title タグがちゃんと入ってなかったので入れた
Created: 2016-05-09 23:40:25 +0900
Updated: 2016-05-09 23:40:25 +0900

[java][linux] Java の各プロセスのスレッド数を得るワンライナー

jps -l | ruby -ne 'pid, cmd = $_.chomp.split(/ /); puts pid + "\t" + File.read("/proc/#{pid}/stat").split(/ /)[19] + "\t" + cmd'

linux の procfs から取得していくスタイル

Created: 2016-05-09 10:08:27 +0000
Updated: 2016-05-09 10:08:27 +0000

javaagent として動く java のサンプラを書いた

java アプリケーションのサンプリングを簡単にやるための簡単なライブラリを書いた。javaagent の習作的なやつです。

https://github.com/tokuhirom/java-samples/tree/master/nanojsampler

たんに、定期的に Thread.getAllStackTraces() して、よしなに集計した結果を jmx に流し込んでいる。

package me.geso.nanojsampler;

import javax.management.*;
import java.lang.instrument.Instrumentation;
import java.lang.management.ManagementFactory;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class Nanojsampler {
    private final Map<StackTraceElement, Integer> map = new ConcurrentHashMap<>();

    public static void premain(String agentArgs, Instrumentation inst) throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        int sleepMillisec = Integer.parseInt(agentArgs);
        Nanojsampler nanojsampler = new Nanojsampler();
        nanojsampler.initJmx();

        Thread thread = new Thread(() -> {
            try {
                while (true) {
                    nanojsampler.poll();
                    Thread.sleep(sleepMillisec);
                }
            } catch (InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
        });
        thread.setName("nanojsampler");
        thread.start();

        System.out.println("done");
    }

    public void initJmx() throws MalformedObjectNameException, NotCompliantMBeanException, InstanceAlreadyExistsException, MBeanRegistrationException {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName objectName = new ObjectName("nanojsampler:type=report");
        NanojsamplerReport nanojsamplerMBean = new NanojsamplerReport(map);
        mbs.registerMBean(nanojsamplerMBean, objectName);
    }

    public void poll() {
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> threadEntry : allStackTraces.entrySet()) {
            process(threadEntry);
        }
    }

    private void process(Map.Entry<Thread, StackTraceElement[]> threadEntry) {
        Thread thread = threadEntry.getKey();
        StackTraceElement[] traceElements = threadEntry.getValue();

        // ignore waiting threads
        if (thread.getState() == Thread.State.TIMED_WAITING || thread.getState() == Thread.State.WAITING) {
            return;
        }

        for (StackTraceElement traceElement : traceElements) {
            map.compute(traceElement,
                    (key2, oldValue) -> oldValue == null ? 1 : oldValue + 1);
        }
    }

    public interface NanojsamplerReportMBean {
        List<String> getReport();
    }

    public static class NanojsamplerReport implements NanojsamplerReportMBean {
        private final Map<StackTraceElement, Integer> map;

        public NanojsamplerReport(Map<StackTraceElement, Integer> map) {
            this.map = map;
        }

        @Override
        public List<String> getReport() {
            return this.map.entrySet().stream()
                    .sorted(Comparator.comparing(Map.Entry::getValue, Comparator.reverseOrder()))
                    .map(NanojsamplerReport::convertEntry)
                    .collect(Collectors.toList());
        }

        private static String convertEntry(Map.Entry<StackTraceElement, Integer> stackTraceElementIntegerEntry) {
            StackTraceElement element = stackTraceElementIntegerEntry.getKey();
            Integer value = stackTraceElementIntegerEntry.getValue();

            return value + "\t" + element.getClassName() + "." + element.getMethodName() + ":" + element.getFileName() + ":" + element.getLineNumber();
        }
    }
}

build.gradle に以下のように記載して、Premain-class を定義する。src/main/resources/META-INF/MANIFEST.MF に直接ファイル置いても上書きされるので注意。 jar { manifest { attributes( "Agent-Class": "me.geso.nanojsampler.Nanojsampler", "Premain-Class": "me.geso.nanojsampler.Nanojsampler") } }

集計処理をインプロセスでやっているので重くなりがちかもしれない。が、現実的にはそれほど重くはならないか。 いずれにせよサンプルコードなのでまあ良し。

Created: 2016-05-09 00:18:59 +0000
Updated: 2016-05-09 00:18:59 +0000

[docker] alpine linux を使うなら gosu じゃなくて su-exec で良い

gosu という setuidgid みたいなツールがあるが、よく README を読むと

When using Alpine, it's probably also worth checking out su-exec (apk add --no-cache su-exec), which since version 0.2 is fully gosu-compatible in a fraction of the file size.

と記載されており、su-exec を使えば良いとのこと(su-exec のほうが C なので読みやすいし)。

Created: 2016-05-08 21:10:30 +0000
Updated: 2016-05-08 21:10:30 +0000

Java mixed-mode flame graphsについてのメモ

http://www.brendangregg.com/blog/2015-11-06/java-mixed-mode-flame-graphs.html

Java mixed-mode flame graphs ってエントリがどんな問題を解決しているか

  • perf を用いた軽量なプロファイリングを java でも
    • perf-map-agent を利用することで java のメソッドもプロファイリング可能
    • Java のプロファイリングと kernel/libc などの system profiling をいっぺんにできる
    • この2つのものを同時にプロファイリングできることを mixed-mode といっているようだ
  • flame graphs で見やすいグラフを
    • NYTProf とかで出てる奴ね

といった問題を解決しているのだが、解決している問題が多岐に渡っていて理解しにくい。

必要な環境

  • jdk8u60+
  • linux
  • perf

とりあえず試す

sudo yum install -y perf

で perf を install

sudo yum install -y java-1.8.0-openjdk-devel

openjdk なら -devel パッケージもインストールしないと perf-map-agent をビルドできない(oraclejdk でどうなのかは未検証)。

perf のメトリクス取るのの基本

sudo perf record -F 99 -ag -- sleep 30 で、99Hz で 30 sec のプロファイリング。

sudo perf report -f でレポートを curses で見れる

perf-map-agent を使う

https://github.com/jrudolph/perf-map-agent

[tokuhirom@develop perf-map-agent]$ git clone https://github.com/jrudolph/perf-map-agent.git                                                                                                        
Cloning into 'perf-map-agent'...
remote: Counting objects: 336, done.
remote: Total 336 (delta 0), reused 0 (delta 0), pack-reused 336
Receiving objects: 100% (336/336), 95.16 KiB | 0 bytes/s, done.
Resolving deltas: 100% (165/165), done.
[tokuhirom@develop perf-map-agent]$ cmake .
-- Found JNI: /usr/lib/jvm/jre/lib/amd64/libjawt.so  
-- JNI_INCLUDE_DIRS=/usr/lib/jvm/java/include;/usr/lib/jvm/java/include/linux;/usr/lib/jvm/java/include
-- JNI_LIBRARIES=/usr/lib/jvm/jre/lib/amd64/libjawt.so;/usr/lib/jvm/jre/lib/amd64/server/libjvm.so
-- JAVA_INCLUDE_PATH=/usr/lib/jvm/java/include
-- JAVA_INCLUDE_PATH2=/usr/lib/jvm/java/include/linux
-- Found Java: /bin/java (found version "1.8.0.91") 
-- Configuring done
-- Generating done
-- Build files have been written to: /home/tokuhirom/perf-map-agent

perf-map-agent というコマンドラインツールを用意する。これが特定の PID の java process に attach して、JVM TI (JVM Tool Inteface) で情報を集めて perf 用の形式で出力してくれる。このツールを利用するには -XX:+PreserveFramePointer をターゲットの jvm の起動時につける必要がある(このオプションは java 8u60+ で利用可能)。

create-java-perf-map.sh

./bin/perf-java-top

localdev-provisioning%20%E2%80%94%20tokuhirom@develop:~%20%E2%80%94%20ssh%20%E2%97%82%20vagrant%20ssh%20and%20blog%20admin

net.virtualvoid.perf.AttachOnce 実行して map を取得したあと、perf top している。↑↑のスクショを見ると、java method の情報も取得できていることがわかる。

まとめ

  • perf-map-agent 便利
    • 得に、Atlassian など導入しているときにボトルネックさっくり知りたいとかそういうケースでは恩恵がおおきそう
    • あるいは norikra とか
  • 通常の web app の場合、適用範囲は限定的かもしれない
    • しかし、perf ほど気軽に使えるものもあんまないのでこの方法は有りかもしれない
  • perf-map-agent はjvmti を利用するCコードをターゲットVMにねじ込む
    • バグがあるとこれ起因でクラッシュするかもしれない
Created: 2016-05-08 14:37:03 +0000
Updated: 2016-05-09 03:38:10 +0000

ansistrano について

https://github.com/ansistrano/deploy/

Ansible 上で capistrano 的にデプロイできるようにするというプロジェクトがある。 これは一見良さそうだが、設定を真面目にやっていくと、同等のことを自分で手で書くのと同程度に大量の設定を書くことになるのが気になる。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900

Alfred のワークフローを Perl で書きたい!

https://github.com/tokuhirom/p5-Alfred

なんか Alfred2 を久々に使い始めている。

最近は IDE で生活する時間が長いので、terminal を開いてそこから peco を開いてみたいなことをするよりは、Alfred2 からサーバー選んで、そこから open してターミナル開いたほうがいいなぁと思ってみたという感じです。

で、perl で workflow を書いたらいいんだけど、perl 用のライブラリがなかったので書いた。

要するに、定義にしたがって、XML を吐くスクリプトがありゃいいんで、吐くようにしたってだけ。

ご利用ください。

これを使って、社内のデプロイツールからサーバーリスト撮ってきて、ターミナル開く機能と、サーバーリストからサーバーグループをいっきに csshX で開くってなツールを書きました。

あと、kurado を開くやつを作ろうと思ったけど、kurado には service list を取得する API がないのであきらめた。

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-05 13:28:37 +0900

rxjava で peek() みたいなことしたい

java8 stream api でいうところの peek をするには doOnNext を利用すればよい。

http://stackoverflow.com/questions/30760768/rxjava-performing-a-peek-or-void-operation-within-an-observable-chain

Created: 2016-05-02 14:24:54 +0900
Updated: 2016-05-02 14:24:54 +0900
Next page