HighChartsで1つのグラフに複数の要素を入れる方法

HighChartsってすばらしいですね。

我が家にはパーソナルウェザーステーション(Oregon sientific WNR100N)が設置してあって、なんとかそのデータをウェブで公開してどこからでも我が家の気象情報にアクセスしたいと思っていました。
続きを読む

[chef] chefサーバのバックアップ

一通りchefの構築を済ませたので、この辺でchefサーバのバックアップを仕掛けることにしました。
chefも構築を始めるとノウハウの塊になってきて消失するとかなり痛いです。

node,role,data bagのバックアップ

node,role,data bagのバックアップはドキュメントに書いてあるchef_server_backup.rbを使うことにしました。

これを使えば、node,role,data bagは$cwd/.chef/chef_server_backupというディレクトリにそれぞれのオブジェクトがjson形式でバックアップされます。

chef_server_backup.rbの使い方

使い方ってほどでもないのですが、水増しのため解説します。

まずはchef_server_backup.rbを入手します。

cd /path/to/working_dir
curl -O https://raw.github.com/jtimberman/knife-scripts/master/chef_server_backup.rb

そして、knife execchef_server_backup.rbを実行します。
もちろんknifeが正常に動くことが大前提です。

knife exec /path/to/chef_server_backup.rb

以上!

でも、これではせっかく時間をかけて作ったCookbookがバックアップされません。
Cookbookはこれとは別にバックアップを取る必要があります。

Cookbookのバックアップは自作した

いろしろ探したのですが、Cookbookのバックアップツールは見つかりませんでした。
chef_server_backup.rbと同じような感じでできるのが一番かっこいいのですが、残念ながらそれをするためのRuby言語のスキルが僕には圧倒的に不足しています。

ということで、いろいろ悩んだ結果pythonでknifeを使って、cookbookのリストを取得し、それをknifeコマンドでdownloadするベタな方法にしました。
何で悩んだかというとcookbookのバージョンです。
ご存知の通りCookbookは複数のバージョンを保持することができます。それを全てバックアップを取ろうとするとどうやっても僕のスキルではbashで実装できなかったので、pythonで作りました。

恥をしのんでそのスクリプトを晒します。

#!/usr/bin/python
# -*- coding:utf-8 -*-
import subprocess,sys,os,os.path,shutil
import datetime,locale

import smtplib
from email.MIMEText import MIMEText
from email.Utils import formatdate

backup_dir = '/data/chef-repo/.chef/cookbook'
subject = 'Cookbook Backup ERROR'
mail_from = 'from@domain'
mail_to = 'to@domain'

# 現在時刻を取得する関数
def getnow():
  return datetime.datetime.today().strftime("%Y/%m/%d %H:%M:%S")

# メール構築
def create_message(f,to,subject,body):
  message = MIMEText(body)
  message['Subject'] = subject
  message['From'] = f
  message['To'] = to
  message['Date'] = formatdate()
  return message

# メール送信
def sendMail(f, to, message):
  s = smtplib.SMTP()
  s.connect()
  s.sendmail(f, [to], message.as_string())
  s.close()
 
# 処理開始
print '---START {0} ----'.format(getnow())

# 作業用ディレクトリを消す
if os.path.isdir(backup_dir):
  print "Remove {0}".format(backup_dir)
  try:
    shutil.rmtree(backup_dir)
  except OSError:
    error_message = "ERROR: {0} can not delete.".format(backup_dir)
    print error_message
    sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
    sys.exit(1)

# 作業用ディレクトリの作成
try:
  os.mkdir(backup_dir)
except OSError:
  error_message = "ERROR: {0} can not create.".format(backup_dir)
  print error_message
  sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
  sys.exit(1)

# cookbookリストを取得する
cmdline = ['/usr/bin/knife', 'cookbook', 'list', '-a']
subproc_args = {
  'stdin':subprocess.PIPE,
  'stdout':subprocess.PIPE,
  'stderr':subprocess.STDOUT,
  'close_fds':True,
}

try:
  p = subprocess.Popen(cmdline, **subproc_args)
except OSError:
  error_message = "Failed to execute command. : {0}".format(cmdline.__str__())
  print error_message
  sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
  sys.exit(1)

cmd_args = {
  'stdin':subprocess.PIPE,
  'stdout':subprocess.PIPE,
  'stderr':subprocess.STDOUT,
  'close_fds':True,
}

# Cookbookごとにdownload
while True:
  line = p.stdout.readline().split()
  if not line:
    break
  cookbook_name = line.pop(0)
  print cookbook_name
  pp = ""

  # 複数バージョンがある場合はそれらをdownload
  for version in line:
    cmdline = "/usr/bin/knife cookbook download {0} {1} -d {2}".format(cookbook_name, version, backup_dir)
    print "Downloading {0} {1}: ".format(cookbook_name, version),
    try:
      ret = os.system(cmdline)
    except OSError:
      print "ERROR"
      error_message = "Failed to execute command.: {0}".format(cmdline.__str__())
      print error_message
      sendMail(mail_from,mail_to,create_message(mail_from,mail_to,subject,error_message))
      sys.exit(1)

    print "SUCSESS"
    
print '---END {0} ----'.format(getnow())

このスクリプトで/path/to/.chef/cookbookに全てのcookbookをダウンロードして、chef_server_backup.rbで取得したオブジェクトのjsonをまとめてtarで固めてバックアップを取ることにしました。

おしまい。

pythonでoauth署名付きリクエスト(1)

スクラッチでごりごり

pythonでoauthの署名付きリクエストを送る必要に迫られて、よく調べもせずにスクラッチでごりごり書いてみました。oauth2というモジュールがあることも知らずに・・・。

oauthに必要なパラメータは以下のように設定しました。

from time import time
import random

oauth_consumer_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
oauth_consumer_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
oauth_timestamp = int(time())
oauth_nonce = random.randint(1000000000, 9000000000)
oauth_version = '1.0'
oauth_signature_method = 'HMAC-SHA1'

oauth_nonceのランダム文字列は適当にrandom.randint(1000000000, 9000000000)にしてしまいました。

次に上記のパラメータを配列に入れます。

sig_param = []
sig_param.append("{0}={1}".format('oauth_consumer_key',oauth_consumer_key))
sig_param.append("{0}={1}".format('oauth_signature_method',oauth_signature_method))
sig_param.append("{0}={1}".format('oauth_timestamp',oauth_timestamp))
sig_param.append("{0}={1}".format("oauth_nonce",oauth_nonce))
sig_param.append("{0}={1}".format('oauth_version',oauth_version))

# 最後にsort
sig_param.sort()

そして、signatureを作ります。

import urllib,base64,hmac,hashlib

# signatureのkeyをconsumer_secretとtokenで作ります。
# 今回はtokenはありません。
signature_key = urllib.quote_plus(oauth_consumer_secret) + '&'

# requestを出すmethodを設定
request_method = 'PUT'

# requestを出すURLを設定
request_url = 'http://api.piyopiyo.com/oauth'

# パラメータを設定
oauth_param = ''
for val in sig_param:
  if len(oauth_param) == 0:
    oauth_param += val
  else:
    oauth_param = "{0}".format(oauth_param, val)

# signatureを作る文字列を作る
signature_string = "{0}".format(urllib.quote_plus(request_method),
                                        urllib.quote_plus(request_url),
                                        urllib.quote_plus(auth_param))


# signatureを作る
signature = urllib.quote_plus(base64.b64encode(hmac.new(
                                                signature_key,
                                                signature_string,
                                                hashlib.sha1).digest())

こんな感じでやってみました。
さくっと書いてしまいましたが結構ハマりました。
ハマりポイントは2つ。

quote_plusを使う

urlencodeにはurllib.quoteurllib.quote_plusがあるのですが、quote_plusを使いましょう。
quoteだと「/」(スラッシュ)がencodeされません。これで小一時間ハマりました。

署名はhmacのdigest()を使う

これは僕だけかもしれませんが、最初hmacのhexdigest()を使ってしまってハマりました。
digest()を使いましょう。

それよりもoauth2を使おう!

oauth2モジュールを使ったらさくっとできそうで、軽くショックを受けました。
次回はoauth2モジュールを使って試してみます。

Pythonスタートブック

初めてのPython 第3版