Symfoware

Symfowareについての考察blog

JenkinsのジョブをPythonでリモートから実行する

処理に時間のかかるバッチプログラムをJenkinsのジョブとして定義。
クライアントからはそのジョブを起動する処理を書いて、すぐに処理を終了させる。
なんてことができないか調べてみました。


最初にうまく行かなかった方法を記載しておきます。


JenkinsのAPI Token



Jenkins 2.11のジョブを実行してみます。
まず、API Tokenを取得する必要があるようです。

ログインした状態で、設定画面を開きます。

698_01.png


画面の中央辺りに、「APIトークンの表示」というボタンがあるのでクリック。

698_02.png


ここにユーザーIDとAPIトークンが表示されます。

698_03.png





ジョブの作成



「test-job」というジョブを作成。
内容は、文字列をechoするだけです。

698_04.png

698_05.png


これを別端末からPythonで実行してみます。




Pythonのサンプルプログラム



job/[jobの名称]/buildにpostすれば動くとのことだったので、こんなプログラムを作成しました。

※このプログラムは動作しません。


  1. # -*- coding:utf-8 -*-
  2. import urllib2
  3. import json
  4. # URLは[jenkins]/[ジョブ名]/build
  5. url = 'http://192.168.1.102:8080/job/test-job/build'
  6. # 設定画面で表示されたAPIトークン
  7. password = 'eb6d158c477b8f8f700d54c2d9c6dc12'
  8. # POSTする値はtoken?
  9. data = {
  10.     'token' : password
  11. }
  12. request = urllib2.Request(url, data=json.dumps(data))
  13. request.get_method = lambda: 'POST'
  14. # job実行
  15. response = urllib2.urlopen(request)
  16. ret = response.read()
  17. print 'Response:', ret
  18. print 'ok'




実行するとこんなエラーが。


Traceback (most recent call last):
File "jenkins.py", line 22, in <module>
    response = urllib2.urlopen(request)
File "/usr/lib/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
File "/usr/lib/python2.7/urllib2.py", line 435, in open
    response = meth(req, response)
File "/usr/lib/python2.7/urllib2.py", line 548, in http_response
    'http', request, response, code, msg, hdrs)
File "/usr/lib/python2.7/urllib2.py", line 473, in error
    return self._call_chain(*args)
File "/usr/lib/python2.7/urllib2.py", line 407, in _call_chain
    result = func(*args)
File "/usr/lib/python2.7/urllib2.py", line 556, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 403: No valid crumb was included in the request








ビルド・トリガのリモートからビルド



諦めかけていた時、改めてジョブの設定画面を見てみると、
「リモートからビルド」の設定を見つけました。

698_06.png



リモートからビルドを実行するには次のURLを使用します:

JENKINS_URL/job/test-job/build?token=TOKEN_NAME
もしくは、
/buildWithParameters?token=TOKEN_NAME

&cause=要因を追加する(オプション)と、ビルドを実行した要因として記録することができます。




認証トークンに適当な文字列を入力して保存します。

698_07.png


ユーザーIDとAPIトークンはベーシック認証で指定します。
Python標準機能だとうまく認証が通過できなかったので、

Python urllib2 Basic Auth Problem
こちらを参考に、自分でヘッダーに情報を書き込みます。


  1. # -*- coding:utf-8 -*-
  2. import urllib2, base64
  3. # URLは[jenkins]/[ジョブ名]/build
  4. url = 'http://192.168.1.102:8080/job/test-job/build?token=sample_token'
  5. # 設定画面で表示されたユーザID、APIトークンでベーシック認証
  6. username = 'symfo'
  7. password = 'eb6d158c477b8f8f700d54c2d9c6dc12'
  8. request = urllib2.Request(url)
  9. # http://stackoverflow.com/questions/2407126/python-urllib2-basic-auth-problem
  10. # You need the replace to handle encodestring adding a trailing newline
  11. # (https://docs.python.org/2/library/base64.html#base64.encodestring)
  12. base64string = base64.encodestring('%s:%s' % (username, password)).replace('\n', '')
  13. request.add_header("Authorization", "Basic %s" % base64string)
  14. result = urllib2.urlopen(request)




プログラムを実行すると、リモートからジョブが実行できました。

698_08.png

698_09.png


テーマ:サーバ - ジャンル:コンピュータ

  1. 2016/06/29(水) 00:19:48|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Python Query Builder「Orator」のインストールと使い方

しっかりしたORMツールではなく、CodeIgniterに搭載されているQuery Builderのような
SQL文の構築を楽にしてくれるライブラリがPythonにないか探していたところ、
「Orator」を見つけました。

https://orator-orm.com/

ORMがメインですが、Query Builderの機能もあるようです。
https://orator-orm.com/docs/0.8/query_builder.html

Debian 8にインストールして使ってみます。



インストール



まず、必要なライブラリをインストール


# apt-get install python-dev python-setuptools g++



orator自身はeasy_installでインストールしました。


# easy_install orator



ここで

[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)


というエラーが発生する場合は、ca-certificatesをインストールすればOKです。
Debian easy_installで[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:581)


# apt-get install ca-certificates






簡単なサンプル



CodeIgniter3 JSONを返すAPIサーバーとして使用する

こちらで使用した郵便番号テーブルを検索してみます。


サーバー:localhost
データベース:sample
ユーザー:root
パスワード:P@ssw0rd




テーブルの構成はこんな感じ。


create table zip (
    zip_code char(7) not null primary key,
    address varchar(200)
);




MariaDBを使用しているので、接続用のライブラリをインストールしておきます。


# apt-get install python-mysqldb




こちらを参考に、データベースに接続。
直接SQL文を実行してみます。
Basic Usage


その前に、0.8.1だとこんなエラーが発生しました。


TypeError: 'autocommit' is an invalid keyword argument for this function



orator/connectors/mysql_connector.pyを編集。
35行目の「config['autocommit'] = True」をコメントします。


# vi /usr/local/lib/python2.7/dist-packages/orator-0.8.1-py2.7.egg/orator/connectors/mysql_connector.py




  1.     def connect(self, config):
  2.         config = dict(config.items())
  3.         for key, value in keys_fix.items():
  4.             config[value] = config[key]
  5.             del config[key]
  6.         #config['autocommit'] = True
  7.         config['cursorclass'] = cursor_class
  8.         return self.get_api().connect(**self.get_config(config))




サンプルを作成して実行。


  1. # -*- coding:utf-8 *-
  2. from orator import DatabaseManager
  3. config = {
  4.     'mysql': {
  5.         'driver': 'mysql',
  6.         'host': 'localhost',
  7.         'database': 'sample',
  8.         'user': 'root',
  9.         'password': 'P@ssw0rd',
  10.         'prefix': ''
  11.     }
  12. }
  13. db = DatabaseManager(config)
  14. results = db.select("select * from zip where zip_code = %s", ['0600000'])
  15. for row in results:
  16.     print row['address']




MySQLの場合、プレースフォルダーは「?」でなく「%s」を使用すれば良いようです。


# python sample.py
北海道札幌市中央区以下に掲載がない場合








Query Builder



上記SQL直接実行のソースをQuery Builderを使用したパターンに変更してみます。


  1. # -*- coding:utf-8 *-
  2. from orator import DatabaseManager
  3. config = {
  4.     'mysql': {
  5.         'driver': 'mysql',
  6.         'host': 'localhost',
  7.         'database': 'sample',
  8.         'user': 'root',
  9.         'password': 'P@ssw0rd',
  10.         'prefix': ''
  11.     }
  12. }
  13. db = DatabaseManager(config)
  14. #results = db.select("select * from zip where zip_code = %s", ['0600000'])
  15. #for row in results:
  16. #    print row['address']
  17. result = db.table('zip').where('zip_code', '0600000').first()
  18. print result['address']




もちろん、同じ実行結果になります。


# python sample.py
北海道札幌市中央区以下に掲載がない場合







もう少し進んだサンプル



郵便番号が0600000,0600001の住所を取得。
郵便番号の逆順で並び替えてみます。


  1. # -*- coding:utf-8 *-
  2. from orator import DatabaseManager
  3. config = {
  4.     'mysql': {
  5.         'driver': 'mysql',
  6.         'host': 'localhost',
  7.         'database': 'sample',
  8.         'user': 'root',
  9.         'password': 'P@ssw0rd',
  10.         'prefix': ''
  11.     }
  12. }
  13. db = DatabaseManager(config)
  14. #results = db.select("select * from zip where zip_code = %s", ['0600000'])
  15. #for row in results:
  16. #    print row['address']
  17. #result = db.table('zip').where('zip_code', '0600000').first()
  18. #print result['address']
  19. results = db.table('zip').where_in('zip_code', ['0600000','0600001']).order_by('zip_code', 'desc').get()
  20. for row in results:
  21.     print row['address']




実行結果


# python sample.py
北海道札幌市中央区北一条西(1~19丁目)
北海道札幌市中央区以下に掲載がない場合




テーマ:プログラミング - ジャンル:コンピュータ

  1. 2016/05/22(日) 22:19:26|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

APIフレームワークFalconをbjoernで動作させる

Falcon
http://falconframework.org/

このフレームワークを高速軽量なwsgi実装「bjoern」で動かしてみます。
https://github.com/jonashaag/bjoern


bjoernのインストール



今回はDebian 8にインストールしてみました。

インストールは、過去Bottleを動作させた時の記事
高速軽量なPython WSGI Server「bjoern」でBottleフレームワークを動かす

これと、インストールのドキュメントを参考にしました。
https://github.com/jonashaag/bjoern/wiki/Installation


まず、必要なライブラリをインストール。


# apt-get install python-dev libev-dev




bjoernはeasy_installでインストールしました。


# easy_install bjoern



バージョン1.4.3がインストールできました。




サンプルプログラム



Python + Falconで高速APIサーバーを作成する
こちらで使用したサンプルを変更して試してみます。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import falcon
  4. class HelloResource(object):
  5.     
  6.     # getされた時の動作
  7.     def on_get(self, req, res):
  8.         msg = {
  9.             "message": "Welcome to the Falcon"
  10.         }
  11.         res.body = json.dumps(msg)
  12. app = falcon.API()
  13. app.add_route("/", HelloResource())
  14. if __name__ == "__main__":
  15.     #from wsgiref import simple_server
  16.     #httpd = simple_server.make_server("0.0.0.0", 8000, app)
  17.     #httpd.serve_forever()
  18.     
  19.     import bjoern
  20.     bjoern.run(app, "0.0.0.0", 8000, reuse_port=True)




動作確認用のプログラムです。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import urllib2
  4. url = 'http://192.168.1.102:8000'
  5. response = urllib2.urlopen(url)
  6. # 戻り値を解析
  7. body = response.read()
  8. response.close()
  9. data = json.loads(body)
  10. print(data['message'])




動いてくれているようです。


$ python api_test.py
Welcome to the Falcon




テーマ:プログラミング - ジャンル:コンピュータ

  1. 2016/05/14(土) 22:22:20|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Falcon JSON形式のAPIとしてのカスタマイズ(リクエスト処理前にデータ加工)

表現が難しいのですが、例えばJSONデータのPOSTのみを受け付けるAPIサーバーを作成する場合、
リクエストの処理で毎回JSON形式のチェックやデータのパース処理を書くのは面倒です。

Falconでは、処理前に共通して行いたい処理をmiddlewareとして定義できます。

こちらのサンプルを参考に試してみます。
https://github.com/falconry/falcon/blob/master/README.rst


middleware



サンプルを参考に、事前にjson形式に変換。
レスポンスも自動的にjson形式に変換するmiddlewareはこんな感じになりました。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import falcon
  4. # 事前に処理を行うクラス
  5. class JSONTranslator(object):
  6.     def process_request(self, req, resp):
  7.         
  8.         # リクエストヘッダーをチェック
  9.         if not req.client_accepts_json:
  10.             raise falcon.HTTPNotAcceptable(
  11.                 'This API only supports responses encoded as JSON.')
  12.         if req.method in ('POST', 'PUT'):
  13.             if 'application/json' not in req.content_type:
  14.                 raise falcon.HTTPUnsupportedMediaType(
  15.                     'This API only supports requests encoded as JSON.')
  16.         
  17.         # req.stream corresponds to the WSGI wsgi.input environ variable,
  18.         # and allows you to read bytes from the request body.
  19.         #
  20.         # See also: PEP 3333
  21.         if req.content_length in (None, 0):
  22.             # Nothing to do
  23.             return
  24.         
  25.         # post値をjson形式に変換
  26.         body = req.stream.read()
  27.         if not body:
  28.             raise falcon.HTTPBadRequest('Empty request body')
  29.         try:
  30.             req.context['doc'] = json.loads(body.decode('utf-8'))
  31.         except (ValueError, UnicodeDecodeError):
  32.             raise falcon.HTTPError(falcon.HTTP_753, 'Malformed JSON')
  33.     def process_response(self, req, resp, resource):
  34.         if 'result' not in req.context:
  35.             return
  36.         
  37.         # resultの値をjson形式に変換して返却
  38.         resp.body = json.dumps(req.context['result'])
  39. # 処理の本体
  40. class HelloResource(object):
  41.     
  42.     # postされた時の動作
  43.     def on_post(self, req, res):
  44.         
  45.         doc = req.context['doc']
  46.         
  47.         req.context['result'] = {'hello': doc['name']}
  48. # 事前処理を行うクラスを指定
  49. app = falcon.API(middleware=[
  50.     JSONTranslator()
  51. ])
  52. # URLパラメータを指定
  53. app.add_route("/", HelloResource())
  54. if __name__ == "__main__":
  55.     from wsgiref import simple_server
  56.     httpd = simple_server.make_server("0.0.0.0", 8000, app)
  57.     httpd.serve_forever()




テスト用のプログラムです。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import urllib2
  4. url = 'http://192.168.1.102:8000'
  5. values = {'name' : 'symfo'}
  6. data = json.dumps(values)
  7. headers = { 'Content-type' : 'application/json' }
  8. req = urllib2.Request(url, data, headers)
  9. response = urllib2.urlopen(req)
  10. # 戻り値を解析
  11. body = response.read()
  12. response.close()
  13. data = json.loads(body)
  14. print(data['hello'])




狙い通りの結果が得られました。


$ python api_test.py
symfo




テーマ:プログラミング - ジャンル:コンピュータ

  1. 2016/05/14(土) 21:35:50|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集

Falcon URLによる分岐と値の取得

PythonのAPIフレームワークFalconを試しています。
Python + Falconで高速APIサーバーを作成する
APIサーバー Falcon post,headerの値の取得方法

URLによるルーティングと値の取得を試してみます。



URLにパラメーターを含める




http://www.example.com/hello/[変動する値]



という形式で値を指定したい場合、add_routeするときにパラメーターを指定します。
on_getやon_postの最後の引数に、指定したパラメーターが渡されます。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import falcon
  4. class HelloResource(object):
  5.     
  6.     # getされた時の動作
  7.     def on_get(self, req, res, name):
  8.         
  9.         # 最後の引数nameにURLの一部が代入される
  10.         data = {'result' : name}
  11.         res.body = json.dumps(data)
  12. app = falcon.API()
  13. # URLパラメータを指定
  14. app.add_route("/hello/{name}", HelloResource())
  15. if __name__ == "__main__":
  16.     from wsgiref import simple_server
  17.     httpd = simple_server.make_server("0.0.0.0", 8000, app)
  18.     httpd.serve_forever()




動作確認のためのプログラム


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import urllib2
  4. url = 'http://192.168.1.102:8000/hello/symfo'
  5. response = urllib2.urlopen(url)
  6. # 戻り値を解析
  7. body = response.read()
  8. response.close()
  9. data = json.loads(body)
  10. print(data['result'])




実行すると、ちゃんとURLの一部がパラメーターとして取り出せています。


$ python api_test.py
symfo





もちろん、複数パラメーターを指定できます。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import falcon
  4. class HelloResource(object):
  5.     
  6.     # getされた時の動作
  7.     def on_get(self, req, res, name, param):
  8.         
  9.         # 最後の引数nameにURLの一部が代入される
  10.         data = {'result' : name + '-' + param}
  11.         res.body = json.dumps(data)
  12. app = falcon.API()
  13. # URLパラメータを指定
  14. app.add_route("/hello/{name}/{param}", HelloResource())
  15. if __name__ == "__main__":
  16.     from wsgiref import simple_server
  17.     httpd = simple_server.make_server("0.0.0.0", 8000, app)
  18.     httpd.serve_forever()




確認用


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import urllib2
  4. url = 'http://192.168.1.102:8000/hello/symfo/ware'
  5. response = urllib2.urlopen(url)
  6. # 戻り値を解析
  7. body = response.read()
  8. response.close()
  9. data = json.loads(body)
  10. print(data['result'])




$ python api_test.py
symfo-ware







任意の文字でマッチさせることもできます。


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import falcon
  4. class HelloResource(object):
  5.     
  6.     # getされた時の動作
  7.     def on_get(self, req, res, a, b, c):
  8.         
  9.         data = {'result' : a + '-' + b + '-' + c}
  10.         res.body = json.dumps(data)
  11. app = falcon.API()
  12. # URLパラメータを指定
  13. app.add_route("/hello/{a}.{b}.{c}", HelloResource())
  14. if __name__ == "__main__":
  15.     from wsgiref import simple_server
  16.     httpd = simple_server.make_server("0.0.0.0", 8000, app)
  17.     httpd.serve_forever()





確認用


  1. # -*- coding:utf-8 -*-
  2. import json
  3. import urllib2
  4. url = 'http://192.168.1.102:8000/hello/t1.t2.t3'
  5. response = urllib2.urlopen(url)
  6. # 戻り値を解析
  7. body = response.read()
  8. response.close()
  9. data = json.loads(body)
  10. print(data['result'])




$ python api_test.py
t1-t2-t3




続きです。
Falcon JSON形式のAPIとしてのカスタマイズ(リクエスト処理前にデータ加工)

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2016/05/14(土) 21:10:47|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
前のページ 次のページ