pushの権限を持っていないリモートリポジトリに更新内容を反映する

pushの権限を持っていない環境(ユーザ)で適用した更新を、リモートリポジトリに反映したい時がある。
例えば、本番環境でのhotfixなど。

今のところ以下のような手順で行っている。
(localとproductionという2つのホスト上で作業をしているとする。localが自分の環境。productionが本番環境)

大まかには

  1. productionでformat-patchでpatchの作成
  2. localでpatchを取り込んでリモートリポジトリにpush
  3. productionでリモートリポジトリの変更をpull

実際の内容

production
git add . #変更をaddして
git commit -m "this-is-hot-fix" #commit
git format-patch HEAD~ ## 0001-*.patchが作成される
local
scp production:/srv/this/is/production/env/<0001-*.patch> .
git am <0001-*.patch>
git push origin
production
git reset --hard HEAD~
git pull origin

template lookup errorを見つけるスクリプト

pyramidは、template lookup errorが起き得る状態の設定でもアプリケーションが動作する。
もちろん、動的言語的な性質を考えるとruntime errorというのが正しいという気もしないではないけれど。

デプロイ前後にtemplate lookup errorが発生することが分かると結構へこむ。
直接アクセスして調べるのも手間がかかるので事前に検知したい。

use introspector

pyramid.1.3からintrospectionの機能が強化されている。http://pyramid.readthedocs.org/en/latest/narr/introspector.html
内部的には、add_viewやadd_routeなどで、config.action()が呼ばれる際に登録される。

introspectorは以下のようにして取得することが可能

request.registry.introspector

registryへの参照さえあればconfigからでもrequestからでもアクセスできる。

introspectorは

  • viewの情報
  • templateの情報
  • tweenの情報

など色々な情報を持っている。

introspectorの持っているtemplateの情報を利用することでtemplate lookup errorを検知することができるかもしれない。

実際にやってみた。

以下のようなスクリプトを書けば良かった。

from pyramid.paster import bootstrap
import sys

def detect(renderer):
    return renderer.lookup.get_template(renderer.path)

config_uri = sys.argv[1]
env = bootstrap(config_uri)
registry = env["registry"]


for t in registry.introspector.get_category("templates"):
    try:
        renderer = t["introspectable"]["renderer"]
        if renderer.type == ".mako":
            detect(renderer.renderer)
    except Exception as e:
        print str(e)

bootstrapは"develop.ini"や"production.ini"などの設定ファイルのパスを引数として取り、
渡された設定ファイルで立ち上がったアプリケーション(あるいは環境)を返す関数。
viewなどが登録された状態で返ってくるので、これを利用する。

動作チェック

hello0.mako,hello1.makoは存在していないテンプレート。

$ python detect.py development.ini 
Cant locate template for uri 'hello1.mako'
Cant locate template for uri 'hello0.mako'
Cant locate template for uri 'hello1.mako'
Cant locate template for uri 'hello0.mako'

上のスクリプトは、対応しているのがmakoのテンプレートだけだけれど。
真面目にやればtemplate lookup errorを事前にチェックすることができそう。

動作確認に使ったファイルたち


これらは単独では動かない。今日の別記事を参考に
http://d.hatena.ne.jp/podhmo/20120908/1347106193

1ファイルと1プロジェクトの中間

想定読者

  • pyramidの機能を色々試してみたい人
  • 特に、設定ファイル(development.ini)を要求するコマンドの作成などをしてみたい人

発端

pyramidの機能を試す際に、1ファイルでアプリケーションが作れるのは便利なのだけれど。
pyramidで提供されているpから始まるコマンド群*1を使おうとすると設定ファイルを要求される。

1ファイルアプリは設定ファイルを書かないので、このレイヤーの機能の実験をしようと思った場合には、
1プロジェクト作る必要があってめんどうだった。(ここでいう1プロジェクトとはsetup.pyを持つpython packageのこと)

ちょっとした機能を試してみる度に、setup.pyを定義していくのは面倒なのでどうすれば良いのか考えてみた。

ポイントは

  • 機能の実験の度にsetup.pyを作成するのが面倒くさい
  • それに付随して、管理対象のpackageが増えていくのが邪魔。
  • 少なくともsetup.pyを書くのは1回だけにしたい。

実際の方法

1. てきとうに親となるプロジェクト(experiment)を作成する
2. 親となるプロジェクトのトップレベルのモジュールのディレクトリの中にサブモジュールを切って作業

以下のようなイメージ

.
├── development.ini
├── experiment
│   ├── __init__.py
│   ├── hello
│   │   ├── __init__.py
│   │   └── hello.ini
│   ├── tests.py
│   └── views.py
├── production.ini
├── setup.cfg
└── setup.py

experimentというパッケージを作成して、実験用のモジュールはexperiment以下に作成する。
これで"python setup.py develop"は初回の一回だけで済むようになる。

1ファイルアプリケーションと実際のウェブアプリケーション開発の中間位の大きさの作業をする時この構成でやると良い気がする。

実際の利用方法

proutesを使って見るために、helloというアプリを作ることにする。
設定ファイルはhello.ini(development.iniという名前でも構わないけれど)

1ファイルアプリケーション(http://docs.pylonsproject.org/projects/pyramid_tutorials/en/latest/humans/creatingux/step01/index.html)との違いは

  • 直接httpserverを起動しないこと
  • 設定ファイル(.ini)から設定情報を取得する形式なこと

hello/__init__.py

from wsgiref.simple_server import make_server

from pyramid.config import Configurator
from pyramid.response import Response

# This acts as the view function
def hello_world(request):
    return Response('hello!')

def main(global_config, **settings):
    # Grab the config, add a view, and make a WSGI app
    config = Configurator(settings=settings)

    config.add_route("index", "/")
    config.add_view(hello_world, route_name="index")
    app = config.make_wsgi_app()
    return app

hello/hello.ini

[app:main]
paste.app_factory = experiment.hello:main

[server:main]
use = egg:waitress
port = 6543
host = 0.0.0.0
起動
# pwd => experiment/hello
$ pserve hello.ini
# Starting server in PID 12014.
# serving on http://0.0.0.0:6543

proutesを使ってみる。

$ proutes hello.ini
Name            Pattern                        View                     
----            -------                        ----                     
index           /                              <function hello_world at 0x1de28c0>

動いている。

注意

  • ここに書かれるものは基本的に使い捨て
  • 実際のwebアプリケーションとか作るのは止めましょう。

*1:pserve,pviews,proutes,pshell,..など

templerのコマンドのbash completion作ってみる

はじめに

templer使おうとしたけれどコマンド入力するのが面倒になったのでbashの補完を作成してみる。

step0 about complete

completeという関数を利用して補完候補を設定するらしい。

bash: complete: -h: invalid option
complete: usage: complete [-abcdefgjksuv] [-pr] [-DE] [-o option] [-A action] [-G globpat] [-W wordlist]  [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...]

ubuntuでは/etc/bash_completion.d以下に現在のbashの環境で補完に使われている設定が置かれてい模様。
覗いてて見たところ、補完の候補を表示する関数を定義し、complete -Fを使っているものが多かった。

completeの引数に渡せるのに以下のようなものがある。(詳しい情報はman bash)

-W 直接指定した文字列の中から絞り込み
-F 指定した関数を実行した結果から絞り込み

ためしに補完を作ってみる。

以下は、wheeというコマンドを定義し、その補完候補に[foo,bar,boo]を設定する場合の例。

touch ~/bin/whee #~/binに$PATHが通っている
chmod -c u+x ~/bin/whee 
complete -W "foo bar boo" whee

このようにすると、シェル上でwheeとタブを押下した際に、foo,bar,booを補完候補として表示してくれる。

step1 全候補表示

templerの候補は以下のようにして絞り込める

templer --list | grep ":" | cut -d ":" -f 1

というわけで、以下のようにしてtemplerの補完を追加できる

_templer(){
  COMPREPLY=($(templer --list | grep ":" | cut -d ":" -f 1 ))
}
complete -F _templer templer

関数の中でCOMPREPLYという変数に代入された値が補完候補として扱われるらしい。
ただ、現状では、補完候補として表示されるものの補完はしてくれない

step2 実際に補完してもらう。(compgen)

補完はcompgenがやってくれるらしい。以下のように変える。

_templer(){
  local cur
  cur=${COMP_WORDS[COMP_CWORD]}	
  COMPREPLY=($( compgen -W "`templer --list | grep ':' | cut -d ':' -f 1`" -- $cur))
}
complete -F _templer templer

ただ、現状では、-付きのオプションなどを補完してくれない。

step3 -の引数に対応

あとはbashのプログラミング

_templer(){
  local cur opts templates
  cur=${COMP_WORDS[COMP_CWORD]}	
  opts="--help --make-config-file --list --version"
  templates="`templer --list | grep ':' | cut -d ':' -f 1`"

  if [[ ${cur} == "" ]] ; then
    COMPREPLY=($( compgen -W "${opts} ${templates}" -- $cur))
  elif [[ ${cur} == -* ]] ; then
    COMPREPLY=($( compgen -W "${opts}" -- $cur))
  else   
    COMPREPLY=($( compgen -W "${templates}" -- $cur))
  fi
}
complete -F _templer templer

リモートの閉じられた環境のサーバにアクセスする。

web applicationなどを作成しているとき、公開前の段階であるにもかかわらずwebブラウザなどでアクセスして
実際の動作をチェックしたいということがある。こんな時にトンネルを掘って作業する。

ちょっと調べたりして時間がかかったりしてしまったのでメモ。

だいたいひつような作業

大体必要な作業は以下のとおり

  1. sshでlogin(鍵の設定)
  2. アクセスできるか確認コマンドライン
  3. ブラウザの設定

sshでlogin

sshでloginしよう。同じ鍵を持ち回る時ssh-agentを使っても良い。
また、以下のような設定ファイルをかきトンネルを掘っても良い。

対象とするネットワーク

[local] --> [humidai] ---> [target.app]

humidaiは外部へ公開されていて、local(自分)からsshでアクセスすることは可能。
ここで、sshでトンネルを掘って直接target.appにつなげられるようにしてみる。*1

HOST target.app.net
     user podhmo
     HostName ap2
     ProxyCommand ssh podhmo@humidai.net -W %h:%p 
     IdentityFile ~/.ssh/id_rsa_your_key

HOST humidai.net
     User podhmo
     IdentityFile ~/.ssh/id_rsa_your_key

ProxyCommandは、sshでログインしようとした際に間で実行されるコマンド。%h,%pにはホスト名とポート番号が入る。

Dynamic forwarding

sshにはdynamic forwardingの機能がある。これを使うと簡単にsocksサーバとして動作するようになる。
これを使って、上図のtarget.appのような外部から閉じた環境へブラウザでアクセスするといったことができるようになる。

アクセスできるか確認コマンドライン

実際にログインできるかどうかただsshでアクセスしてみる。

ssh podhmo@target.app.net

これでログインできなかったら設定を見直す。(-v)とかつけて実行してみるとエラーの原因が分かるかもしれない。

sshでログインができるようになったら、前述したsocksサーバを立てる。

ssh -D 1080 -N podhmo@target.app.net

Dに渡したポート番号(省略すると1080)でsocksサーバが立ち上がる。(Nつけて良いと思う。-fはおこのみで)

これが実際に動いているかどうか確かめたい。GUIベースのブラウザは確認が面倒なのでコマンドラインで調べる。curlを使う。

## ちなみにこのオプションの指定方法は古い。あとsocks4ではなく、socks5かもしれない。
curl --socks4 localhost:1080 localhost:8000
curl --socks4a localhost:1080 localhost:8000 

200っぽいメッセージが返ってくるか調べてみる。
socks4aはホスト名の解決がリモート先のホストで行われるし。aなしのsock4はホスト名の解決がアクセス元(local)のホストで行われる
(というわけで、上のsocks4の例はあまり意味がない。)

curlが動いたことを確認したらブラウザで確認する

ブラウザでの確認作業

firefoxchromeでproxy設定(socks)を選び、socks関係の選択を追加する。
firefoxは、

  • network.proxy.socks_remote_dns
  • network.proxy.socks_version

などを確認してみる。remote_dnsがtrueならば、curlのsocks4aと同様の動きをするはず*2

*1:昔はnetcat(nc)とか使ってました

*2:手元の環境では動かなかった。

ssh-agent

ssh-agent

鍵をリモートホストを渡り歩く時に、鍵を持ち回ってくれる

local -> x -> y -> z

というサーバがある時

localで

$ ssh-add <key>
$ ssh <yourname>@<xのadresss>

とすると。
x から y, y から zへつなぐとき. localでaddしたkeyをssh loginに利用してくれるという理解。

ログインした x 上で

$ ssh-add -l

とすると。localでaddしたkeyが見えていれば正常。
x から yのときは-Aオプションいらない。

$ ssh <yourname>@<yのaddress>

でつなげることができる。