Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 1 addition & 253 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,256 +2,4 @@
Proxmoxer: A wrapper for Proxmox REST API
=========================================

master branch: |master_build_status| |master_coverage_status| |pypi_version| |pypi_downloads|

develop branch: |develop_build_status| |develop_coverage_status|


What does it do and what's different?
-------------------------------------

Proxmoxer is a wrapper around the `Proxmox REST API v2 <https://pve.proxmox.com/wiki/Proxmox_VE_API>`_.

It was inspired by slumber, but it dedicated only to Proxmox. It allows to use not only REST API over HTTPS, but
the same api over ssh and pvesh utility.

Like `Proxmoxia <https://github.com/baseblack/Proxmoxia>`_ it dynamically creates attributes which responds to the
attributes you've attempted to reach.

Installation
------------

.. code-block:: bash

pip install proxmoxer

For 'https' backend install requests

.. code-block:: bash

pip install requests

For 'ssh_paramiko' backend install paramiko

.. code-block:: bash

pip install paramiko


Short usage information
-----------------------

The first thing to do is import the proxmoxer library and create ProxmoxAPI instance.

.. code-block:: python

from proxmoxer import ProxmoxAPI
proxmox = ProxmoxAPI('proxmox_host', user='admin@pam',
password='secret_word', verify_ssl=False)

This will connect by default through the 'https' backend.

It is possible to use already prepared public/private key authentication. It is possible to use ssh-agent also.

.. code-block:: python

from proxmoxer import ProxmoxAPI
proxmox = ProxmoxAPI('proxmox_host', user='proxmox_admin', backend='ssh_paramiko')

**Please note, https-backend needs 'requests' library, ssh_paramiko-backend needs 'paramiko' library,
openssh-backend needs 'openssh_wrapper' library installed.**

Queries are exposed via the access methods **get**, **post**, **put** and **delete**. For convenience added two
synonyms: **create** for **post**, and **set** for **put**.

.. code-block:: python

for node in proxmox.nodes.get():
for vm in proxmox.nodes(node['node']).openvz.get():
print "{0}. {1} => {2}" .format(vm['vmid'], vm['name'], vm['status'])

>>> 141. puppet-2.london.baseblack.com => running
101. munki.london.baseblack.com => running
102. redmine.london.baseblack.com => running
140. dns-1.london.baseblack.com => running
126. ns-3.london.baseblack.com => running
113. rabbitmq.london.baseblack.com => running

same code can be rewritten in the next way:

.. code-block:: python

for node in proxmox.get('nodes'):
for vm in proxmox.get('nodes/%s/openvz' % node['node']):
print "%s. %s => %s" % (vm['vmid'], vm['name'], vm['status'])


for example next lines do the same job:

.. code-block:: python

proxmox.nodes(node['node']).openvz.get()
proxmox.nodes(node['node']).get('openvz')
proxmox.get('nodes/%s/openvz' % node['node'])
proxmox.get('nodes', node['node'], 'openvz')


Some more examples:

.. code-block:: python

for vm in proxmox.cluster.resources.get(type='vm'):
print("{0}. {1} => {2}" .format(vm['vmid'], vm['name'], vm['status']))


.. code-block:: python

node = proxmox.nodes('proxmox_node')
pprint(node.storage('local').content.get())

or the with same results

.. code-block:: python

node = proxmox.nodes.proxmox_node()
pprint(node.storage.local.content.get())


Example of creation of lxc container:

.. code-block:: python

node = proxmox.nodes('proxmox_node')
node.lxc.create(vmid=202,
ostemplate='local:vztmpl/debian-9.0-standard_20170530_amd64.tar.gz',
hostname='debian-stretch',
storage='local',
memory=512,
swap=512,
cores=1,
password='secret',
net0='name=eth0,bridge=vmbr0,ip=192.168.22.1/20,gw=192.168.16.1')

Example of template upload:

.. code-block:: python

local_storage = proxmox.nodes('proxmox_node').storage('local')
local_storage.upload.create(content='vztmpl',
filename=open(os.path.expanduser('~/templates/debian-6-my-core_1.0-1_i386.tar.gz'))))


Example of rrd download:

.. code-block:: python

response = proxmox.nodes('proxmox').rrd.get(ds='cpu', timeframe='hour')
with open('cpu.png', 'wb') as f:
f.write(response['image'].encode('raw_unicode_escape'))

Example of usage of logging:

.. code-block:: python

# now logging debug info will be written to stdout
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s:%(name)s: %(message)s')


Roadmap
-------

* write tests
* support other actual python versions
* add optional validation of requests
* add some shortcuts for convenience

History
-------

1.0.2 (2017-12-02)
..................
* Tarball repackaged with tests

1.0.1 (2017-12-02)
..................
* LICENSE file now included in tarball
* Added verify_ssl parameter to ProxmoxHTTPAuth (`Walter Doekes <https://github.com/wdoekes>`_)

1.0.0 (2017-11-12)
..................
* Update Proxmoxer readme (`Emmanuel Kasper <https://github.com/EmmanuelKasper>`_)
* Display the reason of API calls errors (`Emmanuel Kasper <https://github.com/EmmanuelKasper>`_, `kantsdog <https://github.com/kantsdog>`_)
* Filter for ssh response code (`Chris Plock <https://github.com/chrisplo>`_)

0.2.5 (2017-02-12)
..................
* Adding sudo to execute CLI with paramiko ssh backend (`Jason Meridth <https://github.com/jmeridth>`_)
* Proxmoxer/backends/ssh_paramiko: improve file upload (`Jérôme Schneider <https://github.com/merinos>`_)

0.2.4 (2016-05-02)
..................
* Removed newline in tmp_filename string (`Jérôme Schneider <https://github.com/merinos>`_)
* Fix to avoid module reloading (`jklang <https://github.com/jklang>`_)

0.2.3 (2016-01-20)
..................
* Minor typo fix (`Srinivas Sakhamuri <https://github.com/srsakhamuri>`_)

0.2.2 (2016-01-19)
..................
* Adding sudo to execute pvesh CLI in openssh backend (`Wei Tie <https://github.com/TieWei>`_, `Srinivas Sakhamuri <https://github.com/srsakhamuri>`_)
* Add support to specify an identity file for ssh connections (`Srinivas Sakhamuri <https://github.com/srsakhamuri>`_)

0.2.1 (2015-05-02)
..................
* fix for python 3.4 (`kokuev <https://github.com/kokuev>`_)

0.2.0 (2015-03-21)
..................
* Https will now raise AuthenticationError when appropriate. (`scap1784 <https://github.com/scap1784>`_)
* Preliminary python 3 compatibility. (`wdoekes <https://github.com/wdoekes>`_)
* Additional example. (`wdoekes <https://github.com/wdoekes>`_)

0.1.7 (2014-11-16)
..................
* Added ignore of "InsecureRequestWarning: Unverified HTTPS request is being made..." warning while using https (requests) backend.

0.1.4 (2013-06-01)
..................
* Added logging
* Added openssh backend
* Tests are reorganized

0.1.3 (2013-05-30)
..................
* Added next tests
* Bugfixes

0.1.2 (2013-05-27)
..................
* Added first tests
* Added support for travis and coveralls
* Bugfixes

0.1.1 (2013-05-13)
..................
* Initial try.

.. |master_build_status| image:: https://travis-ci.org/swayf/proxmoxer.png?branch=master
:target: https://travis-ci.org/swayf/proxmoxer

.. |master_coverage_status| image:: https://coveralls.io/repos/swayf/proxmoxer/badge.png?branch=master
:target: https://coveralls.io/r/swayf/proxmoxer

.. |develop_build_status| image:: https://travis-ci.org/swayf/proxmoxer.png?branch=develop
:target: https://travis-ci.org/swayf/proxmoxer

.. |develop_coverage_status| image:: https://coveralls.io/repos/swayf/proxmoxer/badge.png?branch=develop
:target: https://coveralls.io/r/swayf/proxmoxer

.. |pypi_version| image:: https://img.shields.io/pypi/v/proxmoxer.svg
:target: https://pypi.python.org/pypi/proxmoxer

.. |pypi_downloads| image:: https://img.shields.io/pypi/dm/proxmoxer.svg
:target: https://pypi.python.org/pypi/proxmoxer

repository is moved to https://github.com/proxmoxer/proxmoxer
2 changes: 1 addition & 1 deletion proxmoxer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
__author__ = 'Oleg Butovich'
__copyright__ = '(c) Oleg Butovich 2013-2017'
__version__ = '1.0.2'
__version__ = '1.0.3'
__licence__ = 'MIT'

from .core import *
7 changes: 5 additions & 2 deletions proxmoxer/backends/base_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def request(self, method, url, data=None, params=None, headers=None):
data['filename'] = data['filename'].name
data['tmpfilename'] = tmp_filename

translated_data = ' '.join(["-{0} {1}".format(k, v) for k, v in chain(data.items(), params.items())])
translated_data = ' '.join(["-{0} {1}".format(k, v if not isinstance(v, str) or " " not in v else '"{}"'.format(v)) for k, v in chain(data.items(), params.items())])
full_cmd = 'pvesh {0}'.format(' '.join(filter(None, (cmd, url, translated_data))))

stdout, stderr = self._exec(full_cmd)
Expand All @@ -49,7 +49,10 @@ def request(self, method, url, data=None, params=None, headers=None):
status_code = next(
(int(s.split()[0]) for s in stderr.splitlines() if match(s)),
500)
return Response(stdout, status_code)
if stdout:
return Response(stdout, status_code)
else:
return Response(stderr, status_code)

def upload_file_obj(self, file_obj, remote_path):
raise NotImplementedError()
Expand Down
9 changes: 9 additions & 0 deletions proxmoxer/backends/https.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ def request(self, method, url, params=None, data=None, headers=None, cookies=Non
timeout=None, allow_redirects=True, proxies=None, hooks=None, stream=None, verify=None, cert=None,
serializer=None):

# take set verify flag from session request does not have this parameter explicitly
if verify is None:
verify = self.verify

#filter out streams
files = files or {}
data = data or {}
Expand All @@ -109,7 +113,12 @@ def request(self, method, url, params=None, data=None, headers=None, cookies=Non
class Backend(object):
def __init__(self, host, user, password, port=8006, verify_ssl=True,
mode='json', timeout=5, auth_token=None, csrf_token=None):
if ':' in host:
host, host_port = host.split(':')
port = host_port if host_port.isdigit() else port

self.base_url = "https://{0}:{1}/api2/{2}".format(host, port, mode)

if auth_token is not None:
self.auth = ProxmoxHTTPTokenAuth(auth_token, csrf_token)
else:
Expand Down
4 changes: 2 additions & 2 deletions proxmoxer/backends/ssh_paramiko.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def _exec(self, cmd):
cmd = 'sudo ' + cmd
session = self.ssh_client.get_transport().open_session()
session.exec_command(cmd)
stdout = ''.join(session.makefile('rb', -1))
stderr = ''.join(session.makefile_stderr('rb', -1))
stdout = session.makefile('rb', -1).read().decode()
stderr = session.makefile_stderr('rb', -1).read().decode()
return stdout, stderr

def upload_file_obj(self, file_obj, remote_path):
Expand Down
14 changes: 11 additions & 3 deletions proxmoxer/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,16 @@ def url_join(self, base, *args):


class ResourceException(Exception):
pass
def __init__(self, status_code, status_message, content, reason):
self.status_code = status_code
self.status_message = status_message
self.content = content
self.reason = reason.strip()
super(ResourceException, self).__init__(self.__repr__())

def __repr__(self):
return "{0} {1}: {2}, Content:{3}".format(
self.status_code, self.status_message, self.reason, self.content)


class ProxmoxResource(ProxmoxResourceBase):
Expand Down Expand Up @@ -75,8 +84,7 @@ def _request(self, method, data=None, params=None):
logger.debug('Status code: %s, output: %s', resp.status_code, resp.content)

if resp.status_code >= 400:
raise ResourceException("{0} {1}: {2}".format(resp.status_code, httplib.responses[resp.status_code],
resp.content))
raise ResourceException(resp.status_code, httplib.responses[resp.status_code], resp.content, resp.reason)
elif 200 <= resp.status_code <= 299:
return self._store["serializer"].loads(resp)

Expand Down
6 changes: 6 additions & 0 deletions tests/base/base_ssh_suite.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,15 @@ def test_get(self):
def test_delete(self):
self.proxmox.nodes('proxmox').openvz(100).delete()
eq_(self._get_called_cmd(), self._called_cmd('pvesh delete /nodes/proxmox/openvz/100'))
self._set_stderr("200 OK")
self.proxmox.nodes('proxmox').openvz('101').delete()
eq_(self._get_called_cmd(), self._called_cmd('pvesh delete /nodes/proxmox/openvz/101'))
self._set_stderr("200 OK")
self.proxmox.nodes('proxmox').openvz.delete('102')
eq_(self._get_called_cmd(), self._called_cmd('pvesh delete /nodes/proxmox/openvz/102'))

def test_post(self):
self._set_stderr("200 OK")
node = self.proxmox.nodes('proxmox')
node.openvz.create(vmid=800,
ostemplate='local:vztmpl/debian-6-turnkey-core_12.0-1_i386.tar.gz',
Expand All @@ -102,6 +105,7 @@ def test_post(self):
ok_('-swap 512' in options)
ok_('-vmid 800' in options)

self._set_stderr("200 OK")
node = self.proxmox.nodes('proxmox1')
node.openvz.post(vmid=900,
ostemplate='local:vztmpl/debian-7-turnkey-core_12.0-1_i386.tar.gz',
Expand All @@ -127,6 +131,7 @@ def test_post(self):
ok_('-vmid 900' in options)

def test_put(self):
self._set_stderr("200 OK")
node = self.proxmox.nodes('proxmox')
node.openvz(101).config.set(cpus=4, memory=1024, ip_address='10.0.100.100', onboot=True)
cmd, options = self._split_cmd(self._get_called_cmd())
Expand All @@ -136,6 +141,7 @@ def test_put(self):
ok_('-onboot True' in options)
ok_('-cpus 4' in options)

self._set_stderr("200 OK")
node = self.proxmox.nodes('proxmox1')
node.openvz('102').config.put(cpus=2, memory=512, ip_address='10.0.100.200', onboot=False)
cmd, options = self._split_cmd(self._get_called_cmd())
Expand Down
Loading