オープンソースこねこね

Webプログラミングなどについてあれこれ。

シェルスクリプトの代わりにPythonを使う

これまで、開発や運用時に使う、ちょっとしたコマンドラインツール、自動化スクリプトは、主にBashシェルスクリプトで実装していたのですが、最近このような用途にはPythonを使うようにしています。

Bashスクリプトへの不満

Bashスクリプト実装において、以下のような不満がありました。

  • クラスや連想配列がないので、構造化したデータが扱いづらい。
  • JSONをパースできない。jqなどのシステムにデフォルトでインストールされていないコマンドが必要。
  • 基本、コマンドの組み合わせでロジックを書いていくのだが、MacLinuxで挙動が微妙に異なるコマンドがある。そのため思わぬ環境依存でハマることがある。
  • 関数の戻り値が数値しか返せないので、結果を文字列で欲しいときは、標準出力とパイプを使うなど、いろいろ細かいテクニックが必要。

追記: 連想配列bashにもあるとコメントがあったので修正しました。調べたらバージョン4あたりからサポートされたようです。

ちなみにBashスクリプトを書く上でのテクニックは、rbenvとherokuのbuildpackの実装が参考になります。

また、以下は昔まとめたBashチートシートです。

ともあれ、ある程度複雑な実装になってくると、Bashだとプログラミング言語としての機能が貧弱なので、つらいものがあります。

スクリプトの実装方針

私がスクリプトを書く際の、実装の基本方針は以下のようなものです。

  • MacでもLinux(主にCentOS)でも動作する。
  • なるべく、システムに標準でインストールされているコマンド以外に依存しない。
  • なるべく、1ファイルで実装する。
  • -hオプションでヘルプメッセージ表示に対応させて、必要なドキュメントも内包させる。

要は「Mac上で実装、デバックして、Linuxサーバ上で使う場合は、スクリプトファイル一個を置けば動作する」ように作りたいわけです。PythonMacにもCentOSにもデフォルトで入っているし、標準ライブラリも豊富なので、このようなポータビリティ重視のスクリプト記述に向いていると思いました。

ポータビリティ重視のための縛りPythonプログラミング

Pythonスクリプトを実装していく中で、前述のポータビリティを確保するため、いくつか留意すべき制限があります。

  • Pythonのバージョン2系と3系どちらでも、動作するように書く。
  • 外部パッケージを使わない。

現在のPythonの主流はバージョン3系なので、基本は3系のコードを書きます。しかし、MacCentOSにデフォルトインストールされているPythonは2系なので、そちらでも動くように、適宜ワークアラウンドを入れて、書きます。とはいえ、大がかりなアプリケーションを書くわけではないので、そこまで難しいものではありません。外部パッケージを使わないという縛りは、基本的に実装を1ファイルで済ませたいからです。あくまで、シェルスクリプトの代替として使いたい、というのが今回の目的です。

Tips

以上を背景に、実際にPythonスクリプトを書く際に使っているTipsを紹介します。

__future__モジュールでバージョン2、3両方に対応させる

__future__モジュールを以下のようにしてインポートします。個々の仕様の詳細は、もう忘れてしまいましたが、3系のコードを、2系のランタイムでも使用できるようにするものです。今は何も考えず、おまじないのように、スクリプトの最初に必ず記述する感じです。

from __future__ import division, print_function, absolute_import, unicode_literals

バージョン2、3で異なるモジュールを、同じ名前で読み込む

ConfigParserは、iniファイル形式の設定ファイルを扱うための標準モジュールです。 Bashでは難しい、構造化された外部設定ファイルを扱えるので、便利です。ただ2系と3系でモジュールの名前が変わっているので、以下のようにして、最初に3系のモジュールをimport、失敗したら2系をimportするようにしてます。これで両方のバージョンで同じようにモジュールが使用できます。

try:
    import configparser as ConfigParser
except ImportError:
    # fallback for python2
    import ConfigParser

config = ConfigParser.RawConfigParser()
config.read("/path/to/configfile.conf")

バージョン2、3を判別する関数を用意する

どうしても2系と3系で、処理を切り分ける必要がでてくる場合もあります。このため、バージョンを判別できる関数を定義しておくと便利です。以下のようにruntimeクラスのスタティックメソッドとして実装します。

class runtime:
    @staticmethod
    def v3():
        return sys.version_info >= (3,)

    @staticmethod
    def v2():
        return sys.version_info < (3,)

if runtime.v3(): 
    # バージョン3系のときの処理を書く...

よく使うスニペット

コピペして使っているスニペットをいくつか紹介します。全て、標準モジュールのみに依存するコードです。適宜import osimport subprocessなどのように、事前に必要なモジュールをロードして使います。

文字に色をつける

class colors:
    bold = '\033[1m'
    underlined = '\033[4m'

    black = '\033[30m'
    red = '\033[31m'
    green = '\033[32m'
    yellow = '\033[33m'
    blue = '\033[34m'
    magenta = '\033[35m'
    cyan = '\033[36m'
    lightgray = '\033[37m'
    darkgray = '\033[90m'
    lightred = '\033[91m'
    lightgreen = '\033[92m'
    lightyellow = '\033[93m'
    lightblue = '\033[94m'
    lightmagenta = '\033[95m'
    lightcyan = '\033[96m'
    
    background_black = '\033[40m'
    background_red = '\033[41m'
    background_green = '\033[42m'
    background_yellow = '\033[43m'
    background_blue = '\033[44m'
    background_magenta = '\033[45m'
    background_cyan = '\033[46m'

    reset = '\033[0m'

# 使い方
print(colors.red + "red text" + colors.reset)

赤い文字でエラーメッセージを出力して終了する

前述のcolorsクラスを使います。

def abort(s):
    print(colors.red + s + colors.reset, file=sys.stderr)
    sys.exit(1)

abort("error!")

外部コマンド実行する

シェルスクリプトの代替としてのPythonなので、外部コマンド実行は、ほぼ必ず使います。subprocess.check_outputを使うと、簡単にコマンド実行して標準出力を取得できます。 このメソッドの戻り値は2系と3系で異なるので、前述のrumtimeクラスによるバージョン判定を使って、以下のように利用します。

out = subprocess.check_output("ls -la",  shell=True).strip()
if runtime.v3(): out = out.decode('utf-8')

テキストをファイルに出力する

fd = open("/path/to/file", 'w')
fd.write("""#!/usr/bin/env bash
set -e

echo "generated by Python"

""")
fd.close()

ファイルに実行権限をつける

umask = os.umask(0)
os.chmod("/path/to/file", 0o755)
os.umask(umask)

スクリプトの同時、多重起動防止する

try:
    fd = open(__file__, 'r')
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    print("Another process is using: " + os.path.basename(__file__), file=sys.stderr)
    sys.exit(1)

コマンドライン・オプションの解析

argparseモジュールでできます。以下は最小のサンプルです。

import argparse

parser = argparse.ArgumentParser(
    description="cli application description",
    formatter_class=argparse.RawDescriptionHelpFormatter,
    epilog="""
additional description...

"""
    )

parser.add_argument("-V", "--version", dest="version", action="store_true", help="Print the version.")
args = parser.parse_args()
if args.version:
    print("v0.0.1")
    sys.exit(0)

これだけで-hオプションで、ヘルプメッセージを表示に対応したコマンドができます。epilogに使い方などを書けば、ちょっとしたドキュメント代わりになります。 ほかにも、gitのようなサブコマンドの作成にも対応していて、Bashに比べると、とても便利です。

HTTPリクエス

以下は、githubapiから、公開鍵を取得するサンプル。ついでにBashでは難しい、JSONのパースも。

#!/usr/bin/env python
from __future__ import division, print_function, absolute_import, unicode_literals

try:
    from urllib.request import urlopen, Request
    from urllib.error import HTTPError
except ImportError:
    # fallback for python2
    from urllib2 import urlopen, Request, HTTPError
import json
import sys

class runtime:
    @staticmethod
    def v3():
        return sys.version_info >= (3,)

    @staticmethod
    def v2():
        return sys.version_info < (3,)

def main():
    res = urlopen("https://api.github.com/users/kohkimakimoto/keys")
    body = res.read()
    if runtime.v3(): body = body.decode('utf-8')

    keys = json.loads(body)
    for key in keys:
        print(key['key'])

if __name__ == '__main__': main()

まとめ

Bashより格段に便利です。pipなどで外部パッケージを使わなくても、ちょっとした自動化スクリプトなら充分カバーできます。

家庭での小学生向けプログラミング教育についてまとめと、考察

要約と結論

  • 小学生向けプログラミング教育では、実用的なコードが書けるようにはならない。
  • そもそも小学生向けプログラミング教育は、プログラミング「技術」の習得が目的ではない。
  • コンピュータを目的に応じて、制御できることを知る、体験する、ということが目的。

このような性質上、学習効果が客観的判断がしづらい領域なので、あえて義務教育外でやる(家庭で教材を利用したり、スクールに通わせたりする)場合は、子どもに具体的な技能向上を、過度に期待すべきではない。それよりも、子どもが楽しめることを重視し、その上でそれをやらせるか否かの判断は、各家庭が許容できるコストで考えればよい。

注)2017/11現在の私見です。

背景 - プログラミング教育について、考えはじめる

私には小2の娘がいるのですが、先日、奥さんから、とあるプログラミング通信教材を提示され「2020年からプログラミングが必修になるので、娘にコレやらせてみようと考えてる。はじめたら、みて教えてあげてくれない?」と相談されました。中身をざっと見たところ、簡単なロボットを組み立てて、iPad上のビジュアルプログラミング環境でプログラミングして、ロボットの制御を行うというもの。Web系の職業プログラマやっている自分の第一印象としては「おもちゃとしては面白いが、これでプログラミングができるようになるとは、思えない(のであまり意味がない)」でした。

ところで、よくよく考えてみると、私の考えている「プログラミング」も相当限定的で、例えば私の場合、WebサービスiOSアプリを作ることはできるが、PS4のゲームや、電子レンジの制御プログラムは作れないわけです。そのような限定された認識で「プログラミングができるようにはならない」という判断をするのは、早計ではないか。さらには昨今の「プログラミング教育」自体について、よく調べたわけでもないのに、教材の良し悪しを判断するのもどうかな、と思ったわけで、ちょっと調べてみました。

小学生のプログラミング教育とは

私は普段プログラミングを仕事にしているわけですが、その立場で考える「プログラミングの勉強」とは、プログラミング言語の習得、アルゴリズムやネットワークプロトコルの理解、具体的な開発ツールの使用方法の習得、などが挙げられます。当然、これらは私個人のコンテキストに強く依存する考えで「小学生のプログラミング教育」とは別なはずです。では「小学生のプログラミング教育」とは具体的にどのようなものか。文部科学省が公開している資料を調べると、以下のようなものが見つかります。

小学校段階におけるプログラミング教育の在り方について(議論の取りまとめ):文部科学省

いくつか興味深い箇所を抜粋します。

プログラミング教育とは、子供たちに、コンピュータに意図した処理を行うよう指示することができるということを体験させながら、将来どのような職業に就くとしても、時代を超えて普遍的に求められる力としての「プログラミング的思考」などを育むことであり、コーディングを覚えることが目的ではない。

(中略)

子供たちが、情報技術を効果的に活用しながら、論理的・創造的に思考し課題を発見・解決していくためには、コンピュータの働きを理解しながら、それが自らの問題解決にどのように活用できるかをイメージし、意図する処理がどのようにすればコンピュータに伝えられるか、さらに、コンピュータを介してどのように現実世界に働きかけることができるのかを考えることが重要になる。

(中略)

小学校におけるプログラミング教育が目指すのは、前述のように、子供たちが、コンピュータに意図した処理を行うよう指示することができるということを体験しながら、身近な生活でコンピュータが活用されていることや、問題の解決には必要な手順があることに気付くこと、各教科等で育まれる思考力を基盤としながら基礎的な「プログラミング的思考」を身に付けること、コンピュータの働きを自分の生活に生かそうとする態度を身に付けることである。

(中略)

プログラミング教育の実施に当たっては、コーディングを覚えることが目的ではないことを明確に共有していくことが不可欠である。

抑えておきたいポイントは

  • 具体的なプログラミング技術(コーディング)を習得するものではない。
  • コンピュータを制御する体験と、その際の考え方(しばしば「プログラミング的思考」と言及される)を学ぶ。

です。これは算数や国語のように、学習効果がテストによる採点で明確になるものとは、ちょっと性質が違う学習領域です。

体験と考え方の学習

わたしの小2の娘は、オクラを小さな鉢で育てて、収穫するまでを、小学校でやったのですが、これは体験と考え方を学ぶ類の教育だと思っています。つまり、植物が実際にどう育っていくかを体験し、土に植えて、水をあげ、太陽にあてるという手順や、植物栽培の基本的な考え方、を学ぶわけです。

この記事の最初の方に書いた、職業プログラマとして「これでプログラミングができるようになるとは、思えない」というプログラミング教育への第一印象ですが、先程のオクラ栽培に、当てはめて考えてみると「オクラをこんな小さい鉢で育てても、実際の農業ができるようになるとは、思えない」と言っているのに近いわけです。それは当然のことで、初等教育では実践的な農業技術を、習得させる必要なんてないからです。

私たちの生活に、今や当たり前に存在するもの、その成り立ちやしくみを体験を通して知ることは、とても重要です。現代において、その領域にコンピュータシステムが含まれたというのが、プログラミング教育の意義だと理解しました。技能の習得を重視する、より高度な教育は中等、高等教育、あるいは本人がより深く興味をもった時に行えばいいのでしょう。

プログラミング教材、教室など(小学生低学年むけ)

さて、プログラミング教育の意義を認めたところで、プログラミング教室や教材、実践事例などをいくつか調べてみました(私の娘が小2なので高学年向けや中等教育以降を対象にしたものは、基本的に除外しました)。以下は参考にした外部サイトです。

プログラミング教育実践ガイド|学校教育分野|教育の情報化

プログラミング教育ニュースまとめ | リセマム

簡単にまとめると

  • ビジュアルプログラミング環境の利用が多い。
  • 特にScratchの利用がよくみられる。
  • ビジュアルプログラミングの入力端末にタブレットを使用するパターンも多い。
  • 工作や簡単なロボット制作などと合わせて、物理デバイスの制御をプログラミングで体験させるパターンも多い。
  • もっとも簡単な事例では、タブレット上でのビジュアルプログラミングで絵を動かすというもの。

以下が私の雑感となります。

  • 前述した文部科学省のプログラミング教育に基づくのであれば、総じて妥当だと思えるカリキュラムが多い(私が奥さんから提示された教材も、妥当だと思いました)。
  • 特に物理デバイスの操作をプログラミングするのは、体験として良さそうにみえる(私の娘が工作好きなのと、物理的なものを実際に動かせるというのは、興味を引きそう)。
  • プログラミング教育教室の料金(月額1万あたりが多い)は、総じて高額かなと思う。

ちなみに、これを期に簡単な電子工作的なものを自分でやってみて、それを娘に教えてみればどうかな、と思いつき、Raspberry PIを買ってみましたが、1から始めるとなると、他にも初期セットアップ用のモニタやキーボード、マウス、入門書、電子工作用基本パーツセットとかが必要で、すでに2万円ほどかかってしまいました。また自分で、いろいろ調査する時間もかかってしまいます。私の場合は、私自身の趣味と勉強も兼ねているので、いいのですが。

いずれにしろ、前述したように、初等学級の子どもに対するプログラミング教育は、技術習得の場ではなく、体験すること自体が主たる学習内容になるので、そこに明確な成果を求めると、残念な結果になるかもしれません。端的に言うと「プログラミング教室に通ったら、PCが使えるようになって、将来IT系の仕事にこまらなくなる」と考えるのは、間違っているということです。義務教育外でお金を払ってやるのであれば、子どもが楽しんでやれることが、最重要で、そうでなければ、継続してやる必要性はないと思います。本人がつまらないと、体験としては、あまり得るものがないのではないかと思います。

これを踏まえて、あとは金銭コストに親が納得できればよいかなと思います。

それで、どうしたか

結局、私の家ではどうしたかというと、ひとまず今年、2017年内までは保留としてます。私が一度、自分で電子工作してみて、多少なりとも、何か教えられそうなら、まずそれをやらせてみようかな、と思っているためです。それで、うまくいかなかったら(私が教えられそうにないなら)、教材の利用を、わりと前向きに考えています。

ちなみに奥さんに見せてもらった教材はこれです。

www.zkai.co.jp

組み立てキットが2万5千円くらいします。高いとは思うのですが、私がいま揃えているRaspberry PI関連もすでに2万ちかくになっているので、まあ必要な価格と判断。あと専用のテキストやカリキュラムなどがあることを考えると、月5000くらいなら許容範囲かな、と考えてます。自分でなにか教えるとなると、子どもの興味を引くように、この辺もちゃんと考えなければならないのですし。

ただ、外部のプログラミング教室に通うとなると、月々1万〜と高額、親による送り迎えも必要になると思うので、ウチでは今のところ考えていません。

余談

でも小学校は、プログラミング教育の前に「連絡帳」とかいう物理媒体の運用を廃止して、直ちに電子化してほしい。

最新のGit-2.14.1のRPMを提供するyumレポジトリを立てた (CVE-2017-1000117対策)

cve-2017-1000117への対応として、GitをアップデートしたいのだけどCentOSの公式の対応がよくわからない。とりいそぎ自前でgit-2.14.1のRPM作ってyumリポジトリgithub pagesで立てた。

github.com

以下のコマンドでcve-2017-1000117の対応がなされたgit-2.14.1が入る。

$ sudo sh -c 'echo "[kohkimakimoto-git-update-el7-x86_64]
name=kohkimakimoto-git-update-el7-x86_64
baseurl=https://kohkimakimoto.github.io/yumrepo-git-update/el7-x86_64
gpgcheck=0
enabled=0
" > /etc/yum.repos.d/kohkimakimoto-git-update-el7-x86_64.repo'
$ sudo yum update --enablerepo=kohkimakimoto-git-update-el7-x86_64 git

RPMの作り方は

CentOSでGit v2系をRPMで作成する | 本日も乙

を参考にさせていただいた。感謝。

Githubのリポジトリをyumリポジトリとして使う

簡単にできた。手順は後ほど。

背景

普段使っているサーバOSはCentOSなので、自作のOSSツールなどをyumコマンドを使ってさくっとインストールしたい。RPMを作るところまではスクリプト化してあって一発でできるようになっている。yumリポジトリをどうするか。今まではbintrayで個人用のyumリポジトリを立てて使っていました。

bintray.com

ところが、先日このbintrayのyumリポジトリから古くてもう使っていないパッケージを削除しようとしたところ「公開後180日たつと削除できない」とエラーになってしまいました。

結局この後、bintrayのサポートに問い合わせてみたら、わりとすぐに削除してくれたのですが、今後パッケージが要らなくなって消そうとするたびに、問い合わせしなくてはならないのかな、、、と思うとちょっとツライです。

そんなわけで代替となるyumリポジトリをホストしてくれるサービスを探していたりしました。packagecloud.ioなどを見てみましたが、いまひとつ使う気になれず。。。

packagecloud.io

そこで昔yumリポジトリを個人サーバに立てたときのことを思い出してみると、結局必要だったのはただのhttpサーバだけだったはずでは。それならGithub Pagesを使えばGithub上にyumリポジトリも立てられるんじゃないかなと思い至り、ちょっとググったら、かなり前にやっぱり同じことをやっている人がいた。

qiita.com

この記事を参考にして、今はgh-pagesのブランチを切る必要もないし、SSLも対応しているので、その辺をふまえて以下のようにやりました。

作業手順

Githubリポジトリを作る

何でもいいですが、ここではリポジトリ名はyumrepo-exampleとします。作成後リポジトリの設定画面からGithub Pagesの設定をします。Sourceをmasterブランチに設定すれば昔のようにgh-pagesのブランチを切る必要もないです。

f:id:kohkimakimoto:20170721194716p:plain

リポジトリをcloneしてRPMファイルを配置する

上記のリポジトリをローカルにcloneし、サポートするディストリビューションアーキテクチャなどで、適当にサブディレクトリを作成してその中にホストしたいRPMファイルをおきます。試しに拙作であるプロビジョニングツールのcofuのRPM

https://github.com/kohkimakimoto/cofu/releases

からダウンロードしてCentOS6とCentOS7用に配置します

$ mkdir el6-x86_64 el7-x86_64
$ curl -L -o el6-x86_64/cofu-0.6.0-1.el6.x86_64.rpm   https://github.com/kohkimakimoto/cofu/releases/download/v0.6.0/cofu-0.6.0-1.el6.x86_64.rpm
$ curl -L -o el7-x86_64/cofu-0.6.0-1.el7.x86_64.rpm   https://github.com/kohkimakimoto/cofu/releases/download/v0.6.0/cofu-0.6.0-1.el7.x86_64.rpm

createrepoコマンドでリポジトリのメタファイルを作る

createrepoコマンドでyumリポジトリに必要なメタファイルを生成します。Macで直接動作するcreaterepoコマンドはないのですが、dockerを使えばこのあたりも簡単にできます。以下のようにしてbashスクリプトcreaterepoとして作成します。

$ echo '#!/usr/bin/env bash
docker run --rm  -u "$(id -u):$(id -g)" -v $PWD:/build -w /build kohkimakimoto/rpmbuild:el7 createrepo "$@"' > createrepo
$ chmod 755 createrepo

そして、RPMを配置したサブディレクトリを指定して実行します。

$ ./createrepo el6-x86_64
Spawning worker 0 with 1 pkgs
Spawning worker 1 with 0 pkgs
Spawning worker 2 with 0 pkgs
Spawning worker 3 with 0 pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
Sqlite DBs complete

$ ./createrepo el7-x86_64
Spawning worker 0 with 1 pkgs
Spawning worker 1 with 0 pkgs
Spawning worker 2 with 0 pkgs
Spawning worker 3 with 0 pkgs
Workers Finished
Saving Primary metadata
Saving file lists metadata
Saving other metadata
Generating sqlite DBs
Sqlite DBs complete

後はコミットしてpushすれば完了です。なおRPMを更新したら都度同じようにcreaterepoスクリプトを実行してメタファイルも更新します。

yumでインストールしてみる

リポジトリができたのでyumでインストールしてみます。以下の例はCentOS7でやった際のもの。リポジトリのパスなどは適宜読み替えてください。

まずリポジトリの設定をして

$ sudo sh -c 'echo "[github-yumrepo-example-el7-x86_64]
name=github-yumrepo-example-el7-x86_64
baseurl=https://kohkimakimoto.github.io/yumrepo-example/el7-x86_64
gpgcheck=0
enabled=1
" > /etc/yum.repos.d/github-yumrepo-example-el7-x86_64.repo'

あとは普通にyum installする

$ sudo yum install cofu

以上です。サンプルのリポジトリは以下においておきます。

github.com

開発環境用に自己署名SSL証明書をつくるスクリプトを書いた

外部からアクセスできるサーバ環境の場合はLet’s Encryptを使うことで手軽にSSL証明書を設定できるようになったのですが、ローカルマシンにVagrantでたてた開発用VMの場合はそうもいかず、この場合は自己署名のSSL証明書をつかっています。

証明書の発行は普通にopensslコマンドを手作業で叩いてやってたんですが、

  • Chromeだと-extfileオプションで追加のパラメータを指定しないと、Macのキーチェーンにインストールした自己署名証明書がエラーになるので、すこし作業の手間が増えた
  • ローカル環境への名前解決をxip.ioなどのワイルドカードDNSを使うようになって、利用するホスト名がmyapp.192.168.0.10.xip.io,myapp2.192.168.0.10.xip.ioのように多くなった
  • xip.ioで名前解決する構成上、サブドメインへの階層が深くなりワイルドカードの証明書が使えなくなったため、ドメイン名ごとに証明書を発行する必要がある

などの理由で、作業がちょっと煩雑になってきたのでスクリプト化しました。

github.com

Bashスクリプト実装です。引数にドメイン名を指定して(複数指定も可)実行すると

$ selfsigned-crt example.com

以下のように秘密鍵CSR、証明書を吐き出します。有効期限は100年。

example.com-selfsigned.key
example.com-selfsigned.csr
example.com-selfsigned.crt

あとはこの生成した証明書をサーバに配置すればいいのですが、自分の場合、開発環境のプロビジョニングツールに、このスクリプトを実行させるようにレシピを書いておいて、証明書の生成もプロビジョニングツールにやらせてます。自作ツールのcofuを使うと以下のような感じ、

execute "generate-self-signed-ssl" {
    command = [=[
        selfsigned-crt -o /etc/nginx/ssl myapp1.192.168.1.10.xip.io
    ]=],
    not_if = "test -f /etc/nginx/ssl/myapp1.192.168.1.10.xip.io-selfsigned.crt",
}

これで開発環境をセットアップするときに、自動的に証明書も作成できるようなりました。

関連外部リンク

Visual Studio CodeからMacの辞書(Dictionary.app)を開くエクステンションをつくった

kohkimakimoto.hatenablog.com

これのvscode版。LL書く用のエディタをvscodeにしたのでIntelliJatomでやってたように、CTRL+CMD+Kで辞書を起動できるようにしてみました。

marketplace:

marketplace.visualstudio.com

github repository:

github.com

表示名Mac Dictionaryでmarketplaceに公開したので、適当に検索してインストールしてください。vscodeだとCTRL+CMD+Dで簡易辞書がひけるのでそれを使ってもいいのだけど、atomIntelliJで上記プラグインを使ってたせいで、直接辞書をひくのに慣れてしまった。vscodeでもそういうのが欲しくなり、やっぱり作ってみました。

f:id:kohkimakimoto:20170511103121g:plain

SSHラッパーコマンドEsshのv1.0.0をリリースしました

Esshは、zsh補完やLuaスクリプトによる動的なコンフィグレーションなど、便利機能を実装した、sshのラッパーコマンドです。

github.com

ドキュメントをまとめたWebサイトも作成しました。

https://essh.sitespread.net/

上記Webサイトのトップにasciinemaで撮ったターミナルオペレーションのデモを乗せましたので、そちらを見ていただければ、どういったものか大体のことはわかると思います。

開発の経緯

2015/11に以下の記事を書き、Zsshというコマンドを作成しました。

kohkimakimoto.hatenablog.com

その後、CentOSリポジトリzsshというパッケージが存在していたため、コマンド名をzsshからesshに変え、自分の利用にあわせて、適宜、機能の追加や変更を続けていました。そして、自分が使う上で必要十分な機能がそろって、大きな変更もなくなってきたので、この度これをv1.0.0としてリリースすることにしました。なお2015/11時点のものとは仕様が大きく様変わりしており、以下に改めて、機能について説明します。

機能

EsshはGoで書かれたシングルバイナリのCLIツールで多機能sshコマンドとして利用できます。実装的には内部でsshコマンドを実行するラッパーコマンドになっています。標準のsshコマンドでは使えない以下のような機能が利用できます。

  • Luaによる設定ファイル。これによりSSH接続サーバの設定(ssh_config)をより動的に構築することができる。
  • サーバ接続、切断時に発火するフックコマンドを設定できる。
  • サーバにタグをつけられ、一覧をターミナルに出力できる。
  • ZshBash用の強力な補完を組み込みで提供。
  • カレントディレクトリごとに設定ファイルの切り替え。
  • Capistranoのような複数サーバに並行してコマンドを実行できるタスクランナー。
  • 設定用のLuaコードはモジュールとしてGitリポジトリ上で共有できる。

一通りの機能に触れるためのチュートリアルを用意したので、利用の際はここから始めるとよいかと思います。

https://essh.sitespread.net/intro/ja/index.html

利用例

上記のチュートリアルには書いていないが、私個人が使っている機能などを中心にいくつか紹介します。

ログイン時にターミナルの色を変える

接続、切断時のフックコマンドhooks_before_connecthooks_after_disconnectでターミナルの色を変えます。私は本番環境につなぐときは赤にしています。

-- ~/.essh/config.lua

-- 事前にmacのターミナル設定でRedとBlackというプロファイルを作っておく
local red_screen_command    = "osascript -e 'tell application \"Terminal\" to set current settings of first window to settings set \"Red\"'"
local black_screen_command  = "osascript -e 'tell application \"Terminal\" to set current settings of first window to settings set \"Black\"'"

host "webserver-01" {
    HostName = "192.168.56.32",
    Port = "22",
    User = "kohkimakimoto",
    description = "web server-01",
    hooks_before_connect = { red_screen_command },
    hooks_after_disconnect = { black_screen_command },
    tags = {
        "web",
    },
}

host "webserver-02" {
    HostName = "192.168.56.33",
    Port = "22",
    User = "kohkimakimoto",
    description = "web server-02",
    hooks_before_connect = { red_screen_command },
    hooks_after_disconnect = { black_screen_command },
    tags = {
        "web",
    },
}

f:id:kohkimakimoto:20170410083157g:plain

アプリケーションのデプロイ

リモートサーバにコマンドを実行します。以下はただのサンプルです。実環境ではもっと複雑なスクリプトを実行させています。

host "webserver-01" {
    HostName = "192.168.56.32",
    Port = "22",
    User = "kohkimakimoto",
    description = "web server-01",
    tags = {
        "web",
    },
}

host "webserver-02" {
    HostName = "192.168.56.33",
    Port = "22",
    User = "kohkimakimoto",
    description = "web server-02",
    tags = {
        "web",
    },
}

task "deploy" {
    backend = "remote",
    targets = {"web"},
    prefix = true,
    parallel = true,
    script = [=[
        echo "deploy app to $ESSH_HOSTNAME"
        
        # your deploy commands...

        echo "Done."
    ]=],
}

f:id:kohkimakimoto:20170410104414g:plain

以上です。日々のSSHライフのお供に、どうぞお使いください。m( )m