Skip to content

Commit

Permalink
Merge pull request minsu#1 from ziozzang/master
Browse files Browse the repository at this point in the history
Package 반영 / POST 방식 지원 / 인증 부분 수정
  • Loading branch information
LeeSanghoon committed Oct 24, 2013
2 parents 98d0533 + c78da41 commit 2392e28
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 28 deletions.
74 changes: 67 additions & 7 deletions Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,46 @@
#
# Copyright 2012 Netsco Inc.
# Copyright 2012 Minsu Kang
# Copyright 2013 Jioh L. Jung

from urllib import quote_plus as quote
from urllib2 import urlopen, HTTPError
from urllib import quote
from urllib2 import urlopen, Request, HTTPError
from base64 import b64encode

import hmac
import hashlib
import json
import re
import os

UCLOUD_API_KEY = 'YOU_MUST_ENTER_YOUR_API_KEY_HERE_!'
UCLOUD_SECRET = 'YOU_MUST_ENTER_YOUR_SECRET_KEY_HERE_!'
if "UCLOUD_API_KEY" in os.environ:
UCLOUD_API_KEY = os.environ["UCLOUD_API_KEY"]
if "UCLOUD_SECRET" in os.environ:
UCLOUD_SECRET = os.environ["UCLOUD_SECRET"]

UCLOUD_API_URLS = {
# Server/CloudStack http://developer.ucloudbiz.olleh.com/doc/cloudstack/
'server' : 'https://api.ucloudbiz.olleh.com/server/v1/client/api',
# Loadbalancer http://developer.ucloudbiz.olleh.com/doc/loadbalancer/
'lb' : 'https://api.ucloudbiz.olleh.com/loadbalancer/v1/client/api',
# Web Application Firewall http://developer.ucloudbiz.olleh.com/doc/waf/
'waf' : 'https://api.ucloudbiz.olleh.com/waf/v1/client/api',
# Watch http://developer.ucloudbiz.olleh.com/doc/watch/
'watch' : 'https://api.ucloudbiz.olleh.com/watch/v1/client/api',
# Packaging http://developer.ucloudbiz.olleh.com/doc/packaging/
'package': 'https://api.ucloudbiz.olleh.com/packaging/v1/client/api',
# AutoScaling http://developer.ucloudbiz.olleh.com/doc/autoscaling/
'as' : 'https://api.ucloudbiz.olleh.com/autoscaling/v1/client/api',
# CDN http://developer.ucloudbiz.olleh.com/doc/CDN/
'cdn' : 'https://api.ucloudbiz.olleh.com/cdn/v1/client/api',
# Messaging http://developer.ucloudbiz.olleh.com/doc/messaging/
'msg' : 'https://api.ucloudbiz.olleh.com/messaging/v1/client/api',
# NAS Service http://developer.ucloudbiz.olleh.com/doc/nas/
'nas' : 'https://api.ucloudbiz.olleh.com/nas/v1/client/api',
# uCloud DB/RDBAAS http://developer.ucloudbiz.olleh.com/doc/DB/
'db' : 'https://api.ucloudbiz.olleh.com/db/v1/client/api',
}

class Client(object):
Expand All @@ -28,23 +52,35 @@ def __init__(self, api_type = 'server', api_key=UCLOUD_API_KEY, secret=UCLOUD_SE
self.api_key = api_key
self.secret = secret

def request(self, command, args={}):
def request(self, command, args={}, post=None, debug=False, resptype="json"):
if not command:
raise RuntimeError('Command Missing !!')

args['command'] = command
args['response'] = 'json'
args['response'] = resptype
args['apiKey'] = self.api_key

# For safty reason, force Quote some character.
for i in args.keys():
args[i] = args[i].replace("%", "%26")
args[i] = args[i].replace("/", "%2f")

query = '&'.join(
'='.join([k, quote(args[k])]) for k in sorted(args.keys()))
'='.join([k, quote(args[k])]) for k in sorted(args.keys(), key=str.lower))

signature = b64encode(hmac.new(
self.secret,
msg=query.lower(),
digestmod=hashlib.sha1
).digest())

if debug:
print "Server: '%s'" % (self.api_url)
print "Query (for Signiture):"
print query
print "Sigature:"
print signature

#-------------------------------------------------------
# reconstruct : command + params + api_key + signature
#-------------------------------------------------------
Expand All @@ -59,14 +95,38 @@ def request(self, command, args={}):

query += '&' + api_key
query += '&signature=' + quote(signature)

if debug:
print "Query (Reconstructed/LEN: %d):" % len(query)
print query
#-------------------------------------------------------

urls = self.api_url + '?' + query
if post is not None:
post_enc = '&'.join(
'='.join([k, quote(post[k])]) for k in sorted(post.keys()))
req_data = Request(urls, post_enc)
req_data.add_header('Content-type', 'application/x-www-form-urlencoded')
if debug:
print "POST(DICT/LEN: %d): " % (len(post)) , post
print "POST(Encrypted/LEN: %d): " % (len(post_enc)) , post_enc
print "HEADERS: ", req_data.headers
else:
req_data = Request(urls)

try:
response = urlopen(self.api_url + '?' + query)
response = urlopen(req_data)
except HTTPError as e:
# Printing Debugging Indformation.
print e.read()
raise RuntimeError("%s" % e)

decoded = json.loads(response.read())
content = response.read()

if resptype != "json":
return content

decoded = json.loads(content)

# response top node check
response_header = command.lower() + 'response'
Expand Down
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Copyright (c) 2012 Netsco, Inc.
Copyright (c) 2012 Minsu Kang (minsu netsco kr)
Copyright (c) 2012 Jioh L. Jung ([email protected])

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
Expand Down
124 changes: 117 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
UCloud(CloudStack) Python Client
===

* Original Code from https://github.com/minsu/ucloud
* Forked and Revised by Jioh L. Jung ([email protected])

The Python Commandline Tool & library for UCloud.

유클라우드 서버 API를 이용한 파이썬 클라이언트 라이브러리 및 Command Line 유틸리티 프로그램입니다.


### Installation Note

* PIP으로 설치 하는 경우

라이브러리로 사용 할경우에는 pip/PyPI로 설치 하면 됩니다.

```
pip install ucloud
```

* Git 으로 클론 하기.

별도 설치없이 Git 클론한 후에 해당 폴더에서 명령을 실행하거나 해당 파이썬 모듈을 `import UClient` 한 후에 사용합니다. 명령어 실행은 다음의 Example과 같이 Command Line 환경에서 실행 합니다.

python UClient.py server listVirtualMachines
Expand All @@ -15,15 +31,101 @@ UCloud(CloudStack) Python Client
python UClient.py lb listLoadBalancers

python UClient.py waf listWAFs



### 환경 설정

* 파일에 직접 수정
* Client.py 를 열어서 API_KEY 와 SECRET 을 수정 하거나, 환경 변수로 설정 할수 있다.

* 환경 변수로 설정
* API/SECRET Key 설정: ```UCLOUD_API_KEY```, ```UCLOUD_SECRET``` 가 환경 변수로 설정 되어 있으면 해당 값을 읽어서 씁니다. (export 또는 윈도에서 set 으로 설정 하면 됩니다)
* 출력 형식 지정: 기본은 JSON 출력이나, XML 출력을 하고 싶으면 환경변수에 ```UCLOUD_RESP_TYPE``` 를 xml 로 세팅 해주시면 됩니다.

* 코드로 실행 하는 경우, 파라미터로 넘겨 주는 방법
* ``` client = UClient.UClient(api_type="package", api_key=UCLOUD_API_KEY, secret=UCLOUD_SECRET) ``` 와 같이 파라미터로 넘겨줌



### 커맨드 라인으로 실행

```
./UClient.py [api_type] [command] [params1] [param2]...
```
기본 실행 형식입니다.



예를들어 zoneID 를 얻는 API는 다음과 같습니다.

관련 API 문서 http://developer.ucloudbiz.olleh.com/doc/cloudstack/etc/listZones/
```
./UClient.py server listZones
```



예를들어 WAF 생성 API 는 다음과 같습니다.

관련 API 문서 http://developer.ucloudbiz.olleh.com/doc/waf/WAF/createWAF-A/
```
./UClient.py waf name=wafname type=single spec=basic zoneid=9845bd17-d438-4bde-816d-1b12f37d5080 waf1consoleport=5950 waf1SSHport=5951 waf1DBport=5952
```

### 코드로 사용하기

파이썬 모듈로 사용할때에는 다음과 같이 사용하면 됩니다.

```
import json
from ucloud import UClient
client = UClient.UClient(api_type="server", api_key="API_KEY_HERE", secret="SECRET_KEY_HERE")
params = {
"parameter1":"value1",
}
post_data = {
"body1": "longbody1",
}
resp = client.run("Command", params, post=post_data) # POST 로 넘기는 경우
resp = client.run("Command", params) # POST를 쓰지 않는 경우
```


### API 타입 목록
중간에 들어가는 api_type 은 다음을 확인해주시면 됩니다.

종류 | API 타입 | 매뉴얼/가이드 | API 주소
--- | --- | --- | ---
AutoScaling | as | http://developer.ucloudbiz.olleh.com/doc/autoscaling/ | https://api.ucloudbiz.olleh.com/autoscaling/v1/client/api
CDN | cdn | http://developer.ucloudbiz.olleh.com/doc/CDN/ | https://api.ucloudbiz.olleh.com/cdn/v1/client/api
Loadbalancer | lb | http://developer.ucloudbiz.olleh.com/doc/loadbalancer/ | https://api.ucloudbiz.olleh.com/loadbalancer/v1/client/api
Messaging | msg | http://developer.ucloudbiz.olleh.com/doc/messaging/ | https://api.ucloudbiz.olleh.com/messaging/v1/client/api
NAS Service | nas | http://developer.ucloudbiz.olleh.com/doc/nas/ | https://api.ucloudbiz.olleh.com/nas/v1/client/api
Packaging | package | http://developer.ucloudbiz.olleh.com/doc/packaging/ | https://api.ucloudbiz.olleh.com/packaging/v1/client/api
Server/CloudStack | server | http://developer.ucloudbiz.olleh.com/doc/cloudstack/ | https://api.ucloudbiz.olleh.com/server/v1/client/api
uCloud DB/RDBAAS | db | http://developer.ucloudbiz.olleh.com/doc/DB/ | https://api.ucloudbiz.olleh.com/db/v1/client/api
Watch | watch | http://developer.ucloudbiz.olleh.com/doc/watch/ | https://api.ucloudbiz.olleh.com/watch/v1/client/api
Web Application Firewall | waf | http://developer.ucloudbiz.olleh.com/doc/waf/ | https://api.ucloudbiz.olleh.com/waf/v1/client/api


### 디버깅 방법
현재 디버깅은 코드로 작성 하는 경우에만 지원 합니다. 파라미터를 넣어 실행할때에 debug=True 로 실행 해주면 됩니다.

```
client.run(.... , debug=True)
```

### 주의사항

프로그램 사용상 주의할 점들입니다.

- 실행하기 전에 Client.py 파일을 열어 API 키 값과 Secret Key 값을 지정해 주어야 명령들이 동작합니다.
- 일부 명령어의 경우 Command Line 실행시 보기 편한 형태로 출력되지만, 필요한 필드가 빠져있을 수 있습니다. 또한 대부분의 명령은 서버가 회신한 JSON 데이터를 출력합니다.
- 실행하기 전에 API 키 값과 Secret Key 값을 설정 해 주어야 명령들이 동작합니다.
- 일부 명령어의 경우 Command Line 실행시 보기 편한 형태로 출력되지만, 필요한 필드가 빠져있을 수 있습니다. 또한 대부분의 명령은 서버가 회신한 JSON/XML 데이터를 출력합니다.
- 모듈로 사용할 경우 모든 데이터는 JSON 데이터로 처리되어야 합니다.
- 잘못된 명령에 대한 서버의 반응은 별도의 에러메시지 보다는 `500 Server Error`만 나오기 때문에 디버깅 시에는 명령어 파라미터 등에 대한 오탈자를 잘 살펴보아야 합니다.
- 커맨드 라인으로 명령어를 호출할 경우 오직 GET 방식으로 요청 됩니다.
- 잘못된 명령에 대한 서버의 반응은 별도의 에러메시지도 출력되지만 XML포맷으로된 에러 메시지가 출력 됩니다.
- `destroyVirtualMachine`과 같은 명령은 `stopVirtualMachine`이 이루어 진 다음에 실행되어야 정상 동작합니다.

### 지원하는 명령들
Expand All @@ -34,13 +136,18 @@ UCloud(CloudStack) Python Client

유클라우드 웹 방화벽 API 지원이 추가되었습니다. (2013. 02. 20)

유클라우드 Package API 지원이 추가 되었습니다. (2013. 08. 19)

유클라우드 전체 API 지원이 추가 되었습니다. (2013. 08. 23)

PIP/PyPI 에서 설치가 가능합니다. (2013. 09. 09)

### 기본값 지정을 통한 편리한 사용

`commands.py` 에 명시되어 있는 각 명령에는 `default` 라는 Dictionary 데이터가 있습니다. 기본 값으로 지정할 경우 명령창에서 별도로 지정하지 않는 한 해당 `default` 값이 사용됩니다. 현재 `deployVirtualMachine` 명령의 기본값은 kr-1b 존에 `Ubuntu 11.04 32bit, 1vCore, 1GB RAM, 100GB Disk` 시간제 요금이 들어가 있습니다.

### 사용자 포럼
`commands.py` 에 명시 되어 있지 않은 명령어도 실행에는 문제가 없으며 API 문서를 보고 적절한 파라미터를 명시하면 명령어를 사용할수 있습니다.

유클라우드 서버 API 사용자 채널이 [아이언백](http://www.ironbag.net)에 개설되어 있으며 모든 문의는 해당 채널을 통해 받습니다. 또한 관련한 팁이나 사용담 등을 서로 공유하였으면 하는 바램입니다.

### 버전

Expand All @@ -50,5 +157,8 @@ UCloud(CloudStack) Python Client

0.3A : 2013. 02. 20

[채널바로가기]( http://www.ironbag.net/channel/00287799451678010)
===
0.3A-Forked-ziozzang-v1 : 2013. 08. 19

0.3A-Forked-ziozzang-v3 : 2013. 08. 23

1.0 : 2013. 09. 09
40 changes: 26 additions & 14 deletions UClient.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# -*- coding:utf-8 -*-
#
# Copyright 2012 Netsco Inc.
# Copyright 2013 Jioh L. Jung ([email protected])

from Client import *
from commands import COMMANDS
Expand All @@ -10,8 +11,8 @@ class UClient(Client):

""" UCloud Client """

def run(self, command, args={}):
response = self.request(command, args)
def run(self, command, args={}, post=None, debug=False, resptype="json"):
response = self.request(command, args, post=post, debug=debug, resptype=resptype)
if response is None:
raise RuntimeError(
'Response Error : %s' % json.dumps(response, indent=4))
Expand Down Expand Up @@ -68,11 +69,15 @@ def run(self, command, args={}):

def usage_out():
print "usage: python UClient.py api_type command args"
print " api_type : server or lb(loadbalancer) or waf"
print " api_type : server or lb(loadbalancer) or waf or watch or package"

if __name__ == "__main__":

import sys
import os

UCLOUD_RESP_TYPE = "json"
if "UCLOUD_RESP_TYPE" in os.environ:
UCLOUD_RESP_TYPE = os.environ["UCLOUD_RESP_TYPE"]

if len(sys.argv) < 3:
usage_out()
Expand All @@ -87,14 +92,21 @@ def usage_out():
# command validation
command = COMMANDS.get(sys.argv[2], None)
if not command:
raise RuntimeError('invalid command : %s' % sys.argv[2])

# param validation
params = command["default"]
params.update(args)

if not set(command["required"]).issubset(params):
print command["required"]
raise RuntimeError('required parameters missing')
client.run(command['name'], params)
# cannot find Command from command.py
#raise RuntimeError('invalid command : %s' % sys.argv[2])
params = {}
params.update(args)

client.run(sys.argv[2], params, resptype=UCLOUD_RESP_TYPE)

else:
# param validation
params = command["default"]
params.update(args)

if not set(command["required"]).issubset(params):
print command["required"]
raise RuntimeError('required parameters missing')

client.run(command['name'], params, resptype=UCLOUD_RESP_TYPE)
exit(0)
Loading

0 comments on commit 2392e28

Please sign in to comment.