[Python-modules-commits] r31311 - in packages (14 files)
bernat at users.alioth.debian.org
bernat at users.alioth.debian.org
Thu Oct 30 10:14:54 UTC 2014
Date: Thursday, October 30, 2014 @ 10:14:54
Author: bernat
Revision: 31311
[svn-inject] Installing original source of python-cs (0.5.7)
Added:
packages/python-cs/
packages/python-cs/branches/
packages/python-cs/branches/upstream/
packages/python-cs/branches/upstream/python-cs/
packages/python-cs/branches/upstream/python-cs/current/
packages/python-cs/branches/upstream/python-cs/current/.gitignore
packages/python-cs/branches/upstream/python-cs/current/.travis.yml
packages/python-cs/branches/upstream/python-cs/current/LICENSE
packages/python-cs/branches/upstream/python-cs/current/README.rst
packages/python-cs/branches/upstream/python-cs/current/cs.py
packages/python-cs/branches/upstream/python-cs/current/setup.cfg
packages/python-cs/branches/upstream/python-cs/current/setup.py
packages/python-cs/branches/upstream/python-cs/current/tests.py
packages/python-cs/branches/upstream/python-cs/current/tox.ini
Added: packages/python-cs/branches/upstream/python-cs/current/.gitignore
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/.gitignore (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/.gitignore 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,4 @@
+cs.egg-info
+dist
+.tox
+build
Added: packages/python-cs/branches/upstream/python-cs/current/.travis.yml
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/.travis.yml (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/.travis.yml 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,14 @@
+language: python
+python: 3.4
+
+env:
+ - TOXENV=py27
+ - TOXENV=py33
+ - TOXENV=py34
+ - TOXENV=lint
+
+install:
+ - pip install tox
+
+script:
+ - tox -e $TOXENV
Added: packages/python-cs/branches/upstream/python-cs/current/LICENSE
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/LICENSE (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/LICENSE 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,27 @@
+Copyright (c) 2014, Bruno Renié and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Added: packages/python-cs/branches/upstream/python-cs/current/README.rst
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/README.rst (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/README.rst 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,80 @@
+CS
+==
+
+.. image:: https://travis-ci.org/exoscale/cs.svg?branch=master
+ :alt: Build Status
+ :target: https://travis-ci.org/exoscale/cs
+
+A simple, yet powerful CloudStack API client for python and the command-line.
+
+* Python 2.7+ and 3.3+ support.
+* All present and future CloudStack API calls and parameters are supported.
+* Syntax highlight in the command-line client if Pygments is installed.
+* BSD license.
+
+Installation
+------------
+
+::
+
+ pip install cs
+
+Usage
+-----
+
+In Python::
+
+ from cs import CloudStack
+
+ cs = CloudStack(endpoint='https://api.exoscale.ch/compute',
+ key='cloudstack api key',
+ secret='cloudstack api secret')
+
+ vms = cs.listVirtualMachines()
+
+ cs.createSecurityGroup(name='web', description='HTTP traffic')
+
+From the command-line, this requires some configuration::
+
+ cat $HOME/.cloudstack.ini
+ [cloudstack]
+ endpoint = https://api.exoscale.ch/compute
+ key = cloudstack api key
+ secret = cloudstack api secret
+
+Then::
+
+ $ cs listVirtualMachines
+ {
+ "count": 1,
+ "virtualmachine": [
+ {
+ "account": "...",
+ ...
+ }
+ ]
+ }
+
+ $ cs authorizeSecurityGroupIngress \
+ cidrlist="0.0.0.0/0" endport=443 startport=443 \
+ securitygroupname="blah blah" protocol=tcp
+
+The command-line client polls when async results are returned. To disable
+polling, use the ``--async`` flag.
+
+Configuration is read from several locations, in the following order:
+
+* The ``CLOUDSTACK_ENDPOINT``, ``CLOUDSTACK_KEY`` and ``CLOUDSTACK_SECRET``
+ environment variables,
+* A ``CLOUDSTACK_CONFIG`` environment variable pointing to an ``.ini`` file,
+* A ``cloudstack.ini`` file in the current working directory,
+* A ``.cloudstack.ini`` file in the home directory.
+
+To use that configuration scheme from your Python code::
+
+ from cs import CloudStack, read_config
+
+ cs = CloudStack(**read_config())
+
+Note that ``read_config()`` can raise ``SystemExit`` if no configuration is
+found.
Added: packages/python-cs/branches/upstream/python-cs/current/cs.py
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/cs.py (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/cs.py 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,225 @@
+#! /usr/bin/env python
+
+import base64
+import hashlib
+import hmac
+import json
+import os
+import requests
+import sys
+import time
+
+from collections import defaultdict
+
+try:
+ from configparser import ConfigParser
+except ImportError: # python 2
+ from ConfigParser import ConfigParser
+
+try:
+ from urllib.parse import quote
+except ImportError: # python 2
+ from urllib import quote
+
+try:
+ import pygments
+ from pygments.lexers import JsonLexer
+ from pygments.formatters import TerminalFormatter
+except ImportError:
+ pygments = None
+
+
+PY2 = sys.version_info < (3, 0)
+
+if PY2:
+ text_type = unicode # noqa
+ string_type = basestring # noqa
+ integer_types = int, long # noqa
+else:
+ text_type = str
+ string_type = str
+ integer_types = int
+
+
+def cs_encode(value):
+ """
+ Try to behave like cloudstack, which uses
+ java.net.URLEncoder.encode(stuff).replace('+', '%20').
+ """
+ if isinstance(value, int):
+ value = str(value)
+ elif PY2 and isinstance(value, text_type):
+ value = value.encode('utf-8')
+ return quote(value, safe=".-*_")
+
+
+def transform(params):
+ for key, value in list(params.items()):
+ if value is None:
+ params.pop(key)
+ continue
+ if isinstance(value, string_type):
+ continue
+ elif isinstance(value, integer_types):
+ params[key] = text_type(value)
+ elif isinstance(value, (list, tuple, set)):
+ if not value:
+ params.pop(key)
+ else:
+ if isinstance(value, set):
+ value = list(value)
+ if not isinstance(value[0], dict):
+ params[key] = ",".join(value)
+ else:
+ params.pop(key)
+ for index, val in enumerate(value):
+ for k, v in val.items():
+ params["%s[%d].%s" % (key, index, k)] = v
+ else:
+ raise ValueError(type(value))
+ return params
+
+
+class CloudStackException(Exception):
+ pass
+
+
+class Unauthorized(CloudStackException):
+ pass
+
+
+class CloudStack(object):
+ def __init__(self, endpoint, key, secret, timeout=10):
+ self.endpoint = endpoint
+ self.key = key
+ self.secret = secret
+ self.timeout = timeout
+
+ def __repr__(self):
+ return '<CloudStack: {0}>'.format(self.endpoint)
+
+ def __getattr__(self, command):
+ def handler(**kwargs):
+ return self._request(command, **kwargs)
+ return handler
+
+ def _request(self, command, json=True, opcode_name='command', **kwargs):
+ kwargs.update({
+ 'apiKey': self.key,
+ opcode_name: command,
+ })
+ if json:
+ kwargs['response'] = 'json'
+ if 'page' in kwargs:
+ kwargs.setdefault('pagesize', 500)
+
+ kwargs = transform(kwargs)
+ kwargs['signature'] = self._sign(kwargs)
+ response = requests.get(self.endpoint, params=kwargs,
+ timeout=self.timeout)
+ data = response.json()
+ key = '{0}response'.format(command.lower())
+ if response.status_code != 200:
+ raise CloudStackException(
+ "HTTP {0} response from CloudStack".format(
+ response.status_code), response,
+ data.get(key, data.get('errorresponse')))
+ return data[key]
+
+ def _sign(self, data):
+ """
+ Computes a signature string according to the CloudStack
+ signature method (hmac/sha1).
+ """
+ params = "&".join(sorted([
+ "=".join((key, cs_encode(value))).lower()
+ for key, value in data.items()
+ ]))
+ digest = hmac.new(
+ self.secret.encode('utf-8'),
+ msg=params.encode('utf-8'),
+ digestmod=hashlib.sha1).digest()
+ return base64.b64encode(digest).decode('utf-8').strip()
+
+
+def read_config():
+ # Try env vars first
+ keys = ['endpoint', 'key', 'secret']
+ for key in keys:
+ if 'CLOUDSTACK_{0}'.format(key.upper()) not in os.environ:
+ break
+ else:
+ return {key: os.environ['CLOUDSTACK_{0}'.format(key.upper())]
+ for key in keys}
+
+ # Config file: $PWD/cloudstack.ini or $HOME/.cloudstack.ini
+ # Last read wins in configparser
+ paths = (
+ os.path.join(os.path.expanduser('~'), '.cloudstack.ini'),
+ os.path.join(os.getcwd(), 'cloudstack.ini'),
+ )
+ # Look at CLOUDSTACK_CONFIG first if present
+ if 'CLOUDSTACK_CONFIG' in os.environ:
+ paths += (os.environ['CLOUDSTACK_CONFIG'],)
+ if not any([os.path.exists(c) for c in paths]):
+ raise SystemExit("Config file not found. Tried {0}".format(
+ ", ".join(paths)))
+ conf = ConfigParser()
+ conf.read(paths)
+ try:
+ return conf['cloudstack']
+ except AttributeError: # python 2
+ return dict(conf.items('cloudstack'))
+
+
+def main():
+ config = read_config()
+ cs = CloudStack(**config)
+
+ usage = "Usage: {0} <command> [option1=value1 " \
+ "[option2=value2] ...] [--async]".format(sys.argv[0])
+
+ if len(sys.argv) == 1:
+ raise SystemExit(usage)
+
+ command = sys.argv[1]
+ kwargs = defaultdict(set)
+ flags = []
+ for option in sys.argv[2:]:
+ if option.startswith('--'):
+ flags.append(option.strip('-'))
+ continue
+ if '=' not in option:
+ raise SystemExit(usage)
+
+ key, value = option.split('=', 1)
+ kwargs[key].add(value.strip(" \"'"))
+
+ try:
+ response = getattr(cs, command)(**kwargs)
+ except CloudStackException as e:
+ response = e.args[2]
+ sys.stderr.write("Cloudstack error:\n")
+
+ if 'Async' not in command and 'jobid' in response and 'async' not in flags:
+ sys.stderr.write("Polling result... ^C to abort\n")
+ while True:
+ try:
+ res = cs.queryAsyncJobResult(**response)
+ if res['jobprocstatus'] == 0:
+ response = res
+ break
+ time.sleep(3)
+ except KeyboardInterrupt:
+ sys.stderr.write("Result not ready yet.\n")
+ break
+
+ data = json.dumps(response, indent=2, sort_keys=True)
+
+ if pygments and sys.stdout.isatty():
+ data = pygments.highlight(data, JsonLexer(), TerminalFormatter())
+ sys.stdout.write(data)
+
+
+if __name__ == '__main__':
+ main()
Added: packages/python-cs/branches/upstream/python-cs/current/setup.cfg
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/setup.cfg (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/setup.cfg 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,2 @@
+[wheel]
+universal = 1
Added: packages/python-cs/branches/upstream/python-cs/current/setup.py
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/setup.py (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/setup.py 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,41 @@
+# coding: utf-8
+from setuptools import setup
+
+with open('README.rst', 'r') as f:
+ long_description = f.read()
+
+setup(
+ name='cs',
+ version='0.5.7',
+ url='https://github.com/exoscale/cs',
+ license='BSD',
+ author=u'Bruno Renié',
+ description=('A simple yet powerful CloudStack API client for '
+ 'Python and the command-line.'),
+ long_description=long_description,
+ py_modules=('cs',),
+ zip_safe=False,
+ include_package_data=True,
+ platforms='any',
+ classifiers=(
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 3',
+ ),
+ install_requires=(
+ 'requests',
+ ),
+ extras_require={
+ 'highlight': ['pygments'],
+ },
+ test_suite='tests',
+ entry_points={
+ 'console_scripts': [
+ 'cs = cs:main',
+ ],
+ },
+)
Added: packages/python-cs/branches/upstream/python-cs/current/tests.py
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/tests.py (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/tests.py 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,123 @@
+# coding: utf-8
+import os
+
+from contextlib import contextmanager
+from functools import partial
+from unittest import TestCase
+
+try:
+ from unittest.mock import patch
+except ImportError:
+ from mock import patch
+
+from cs import read_config, CloudStack
+
+
+ at contextmanager
+def env(**kwargs):
+ old_env = {}
+ for key in kwargs:
+ if key in os.environ:
+ old_env[key] = os.environ[key]
+ os.environ.update(kwargs)
+ try:
+ yield
+ finally:
+ for key in kwargs:
+ if key in old_env:
+ os.environ[key] = old_env[key]
+ else:
+ del os.environ[key]
+
+
+ at contextmanager
+def cwd(path):
+ initial = os.getcwd()
+ os.chdir(path)
+ try:
+ yield
+ finally:
+ os.chdir(initial)
+
+
+class ConfigTest(TestCase):
+ def test_env_vars(self):
+ with env(CLOUDSTACK_KEY='test key from env',
+ CLOUDSTACK_SECRET='test secret from env',
+ CLOUDSTACK_ENDPOINT='https://api.example.com/from-env'):
+ conf = read_config()
+ self.assertEqual(conf, {
+ 'key': 'test key from env',
+ 'secret': 'test secret from env',
+ 'endpoint': 'https://api.example.com/from-env',
+ })
+
+ def test_current_dir_config(self):
+ with open('/tmp/cloudstack.ini', 'w') as f:
+ f.write('[cloudstack]\n'
+ 'endpoint = https://api.example.com/from-file\n'
+ 'key = test key from file\n'
+ 'secret = test secret from file')
+ self.addCleanup(partial(os.remove, '/tmp/cloudstack.ini'))
+
+ with cwd('/tmp'):
+ conf = read_config()
+ self.assertEqual(dict(conf), {
+ 'endpoint': 'https://api.example.com/from-file',
+ 'key': 'test key from file',
+ 'secret': 'test secret from file',
+ })
+
+
+class RequestTest(TestCase):
+ @patch('requests.get')
+ def test_request_params(self, get):
+ cs = CloudStack(endpoint='localhost', key='foo', secret='bar',
+ timeout=20)
+ get.return_value.status_code = 200
+ get.return_value.json.return_value = {
+ 'listvirtualmachinesresponse': {},
+ }
+ machines = cs.listVirtualMachines(listall='true')
+ self.assertEqual(machines, {})
+ get.assert_called_once_with('localhost', timeout=20, params={
+ 'apiKey': 'foo',
+ 'response': 'json',
+ 'command': 'listVirtualMachines',
+ 'listall': 'true',
+ 'signature': 'B0d6hBsZTcFVCiioSxzwKA9Pke8='})
+
+ @patch('requests.get')
+ def test_encoding(self, get):
+ cs = CloudStack(endpoint='localhost', key='foo', secret='bar')
+ get.return_value.status_code = 200
+ get.return_value.json.return_value = {
+ 'listvirtualmachinesresponse': {},
+ }
+ cs.listVirtualMachines(listall=1, unicode_param=u'éèà à û')
+ get.assert_called_once_with('localhost', timeout=10, params={
+ 'apiKey': 'foo',
+ 'response': 'json',
+ 'command': 'listVirtualMachines',
+ 'listall': '1',
+ 'unicode_param': u'éèà à û',
+ 'signature': 'gABU/KFJKD3FLAgKDuxQoryu4sA='})
+
+ @patch("requests.get")
+ def test_transformt(self, get):
+ cs = CloudStack(endpoint='localhost', key='foo', secret='bar')
+ get.return_value.status_code = 200
+ get.return_value.json.return_value = {
+ 'listvirtualmachinesresponse': {},
+ }
+ cs.listVirtualMachines(foo=["foo", "bar"],
+ bar=[{'baz': 'blah', 'foo': 'meh'}])
+ get.assert_called_once_with('localhost', timeout=10, params={
+ 'command': 'listVirtualMachines',
+ 'response': 'json',
+ 'bar[0].foo': 'meh',
+ 'bar[0].baz': 'blah',
+ 'foo': 'foo,bar',
+ 'apiKey': 'foo',
+ 'signature': 'UGUVEfCOfGfOlqoTj1D2m5adr2g=',
+ })
Added: packages/python-cs/branches/upstream/python-cs/current/tox.ini
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/tox.ini (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/tox.ini 2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,24 @@
+[tox]
+envlist =
+ py27,
+ py33,
+ py34,
+ lint
+
+[testenv]
+commands =
+ python setup.py test
+deps =
+ requests
+
+[testenv:py27]
+deps =
+ {[testenv]deps}
+ mock
+
+[testenv:lint]
+deps =
+ flake8
+commands =
+ flake8 cs.py
+ flake8 tests.py
More information about the Python-modules-commits
mailing list