2011年9月22日木曜日

deployを怪しく自動化

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

pip の-rで渡すファイルの中でsourceの取得を使うことができるのだが、それをもっと賢く使えば良
かったかも。sh scriptがかけない人なので、なんでもpythonで書いちゃう、すべてが釘に見える人。しかも無駄(?)にきれい。本人は脳みそのキャパが小さいのでこのくらいきれいじゃないと死んじゃうんです。


いくつかポイント

  • activateのコードを読んだ上で必要だと思われた、環境変数を待避してます。(でもスタックを作るのはやり過ぎだったかも。sub-Targetでなんかbuildするなら必要かもしれない。)
  • subprocessを乱用してソースコードをかき集め、そこに含まれているfreeze.txtをみてpip installを行い、依存関係を解決する。

  • build_procで指定した関数を実行して必要なものを生成。(rstからsphinxをつかってhtmlを生成)


まあ、気の利いたツールだとソースを取ってくるところとか非同期並列なんでしょうけど。あと依存関係と処理済みかどうかを判定して必要な部分だけを作業するとかね。今回の目的は「マニュアルに書くとはまる」ことを回避することにあるので、そういうのは別にどうでもいいのです。いちいちvirtualenvを切り替えたくなかったし。

virtualenvも環境を継承できればいいのになぁ、と思う。よくドキュメントを読んでいないせいかもしれない。ていうか、こういうどのpackageを使うかを管理する機能をpython自体に持たせてほしい。何回もgetされるとだるいので、cacheしてほしいし .i.e packageの実体とpackageがインストールされている事実を分離して(eggのdevとかできてるよな)、インストールされている事実だけをenvごとにもってほしいよね。

import os
import os.path
import subprocess
    
class VirtualEnv(object):
  '''
    VIRTUAL_ENV=/home/ubuntu/virtualenv/ci-proj
    PATH=/home/ubuntu/virtualenv/ci-proj/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
    PS1=(ci-proj)\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$ 
  '''
  _names = ('VIRTUAL_ENV', 'PATH', 'PS1',)

  def __init__(self, **kws):
    for n in self._names:
      if n in kws:
        self.__dict__.update({n: kws[n]})

  def read(self):
    for n in self._names:
      self.__dict__.update({n: os.getenv(n)})

  def __eq__(self, other):
    for n in self._names:
      if getattr(self, n) != getattr(other, n):
        return False
    return True
  
  def activate(self):
    '''
      $ source ~/virtualenv/deploy/bin/activate
    '''
    for n in self._names:
      os.environ[n]=getattr(self, n)
    subprocess.call(['env'])
 
  def deativate(self):
    '''
      $ deactivate
    '''
    # activate some thing else.

  def __str__(self):
    return ''%(id(self), ' '.join(['%s:%s'%(n, getattr(self, n)) for n in self._names]))

class EnvStack(object):
  def __init__(self):
    self._stack = []
    v = self.get_current()
    self._push(v)
    self.check_invariant()

  def check_invariant(self):
    v = self.get_current()
    u = self.top()
    if v != u:
      print v
      print u
      print self._stack
    assert v==u

  def close(self):
    while self._stack > 1:
      self.pop()

  def get_current(self):
    v = VirtualEnv()
    v.read()
    return v

  def top(self):
    return self._stack[-1]

  def push(self, v):
    self.check_invariant()
    v.activate()
    self._push(v)
    self.check_invariant()

  def _push(self, v):
    self._stack.append(v)

  def pop(self):
    self.check_invariant()
    self._stack.pop(-1)
    v = self.top()
    v.activate()
    self.check_invariant()

class Target(object):
  def __init__(self, src, branch, directory, dep=None, entrypoint=None, build_proc=None):
    self.src = src
    self.branch = branch
    self.directory = directory
    self.entrypoint = entrypoint
    self.build_proc = build_proc
    if dep:
      self.dep = dep
    else:
      self.dep = tuple()

  def mypath(self, builddir):
    return os.path.join(builddir, self.directory)

  def start_context(self, builddir):
    self._cwd = os.getcwd()
    os.chdir(self.mypath(builddir))

  def end_context(self):
    os.chdir(self._cwd)

  def get(self, builddir):
    subprocess.call(['git', 'clone', '-n', self.src, self.mypath(builddir)])
    self.start_context(builddir)
    subprocess.call(['git', 'checkout', self.branch])
    self.end_context()

  def pip(self, builddir):
    self.start_context(builddir)
    subprocess.call(['pip', 'install', '-r', 'freeze.txt'])
    self.end_context()

  def resolve(self, builddir):
    for d in self.dep:
      d.prepare(builddir)
      t = os.path.join(os.getcwd(), builddir, d.directory, d.directory) #FIXME
      self.symlink(builddir, t)
    self.pip(builddir)
    
    
  def build(self, builddir):
    if self.build_proc:
      self.start_context(builddir)
      self.build_proc(self)
      self.end_context()

  def prepare(self, builddir):
    self.get(builddir)
    self.resolve(builddir)
    self.build(builddir)

  def symlink(self, builddir, target):
    self.start_context(builddir)
    subprocess.call(['ln', '-s', target])
    self.end_context()

  def run(self, builddir):
    self.start_context(builddir)
    subprocess.call(['python', self.entrypoint,])
    self.end_context()

tonic = Target(
          src='',
          branch='migrate',
          directory='tonic')
bglib = Target(
          src='ひみつ',
          branch='migration',
          directory='bglib')
        
def build_doc(t):
  os.chdir('doc')
  subprocess.call(['make', 'html'])

imageserver = Target(
          src='ひみつ',
          branch='master',
          directory='imageserver',
          dep=(tonic, bglib),
          entrypoint='sample.py',
          build_proc=build_doc)

env = VirtualEnv(
  VIRTUAL_ENV='/home/ubuntu/virtualenv/deploy',
  PATH='/home/ubuntu/virtualenv/deploy/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games',
  PS1='(deploy)\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$',
)
#print env

s = EnvStack()

def deploy(builddir):
  s.push(env)
  os.mkdir(builddir)
  imageserver.prepare(builddir)
  s.pop()

def run(builddir):
  s.push(env)
  imageserver.run(builddir)
  s.pop()
  
def clean(builddir):
  subprocess.call(['rm', '-rf', builddir])
  
if __name__ == '__main__':
  import sys
  cmd = sys.argv[1]
  print repr(cmd)
  builddir = 'build'
  if cmd=='run':
    run(builddir)
  elif cmd=='deploy':
    deploy(builddir)
  elif cmd=='clean':
    clean(builddir)
  else:
    print 'unknown cmd', cmd

0 件のコメント: