[Pkg-puppet-devel] [python-pypuppetdb] 01/01: Imported Upstream version 0.1.1
Jonas Genannt
jonas at brachium-system.net
Fri Jul 25 12:18:19 UTC 2014
This is an automated email from the git hooks/post-receive script.
hggh-guest pushed a commit to branch master
in repository python-pypuppetdb.
commit 7d02475132ec086ce138ca53c9e7e982845c90ae
Author: Jonas Genannt <jonas at brachium-system.net>
Date: Fri Jul 25 14:18:09 2014 +0200
Imported Upstream version 0.1.1
---
CHANGELOG.rst | 115 +++++++++
LICENSE | 191 +++++++++++++++
MANIFEST.in | 4 +
PKG-INFO | 378 ++++++++++++++++++++++++++++
README.rst | 240 ++++++++++++++++++
pypuppetdb.egg-info/PKG-INFO | 378 ++++++++++++++++++++++++++++
pypuppetdb.egg-info/SOURCES.txt | 19 ++
pypuppetdb.egg-info/dependency_links.txt | 1 +
pypuppetdb.egg-info/requires.txt | 1 +
pypuppetdb.egg-info/top_level.txt | 1 +
pypuppetdb/__init__.py | 114 +++++++++
pypuppetdb/api/__init__.py | 321 ++++++++++++++++++++++++
pypuppetdb/api/v2.py | 122 +++++++++
pypuppetdb/api/v3.py | 254 +++++++++++++++++++
pypuppetdb/errors.py | 30 +++
pypuppetdb/package.py | 9 +
pypuppetdb/types.py | 408 +++++++++++++++++++++++++++++++
pypuppetdb/utils.py | 43 ++++
setup.cfg | 15 ++
setup.py | 62 +++++
20 files changed, 2706 insertions(+)
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
new file mode 100644
index 0000000..1e17d93
--- /dev/null
+++ b/CHANGELOG.rst
@@ -0,0 +1,115 @@
+#########
+Changelog
+#########
+
+0.1.1
+=====
+
+* Fix the license in our ``setup.py``. The license shouldn't be longer than
+ 200 characters. We were including the full license tripping up tools like
+ bdist_rpm.
+
+0.1.0
+=====
+Significant changes have been made in this release. The complete v3 API is
+now supported except for query pagination.
+
+Most changes are backwards compatible except for a change in the SSL
+configuration. The previous behaviour was buggy and slightly misleading in
+the names the options took:
+
+* ``ssl`` has been renamed to ``ssl_verify`` and now defaults to ``True``.
+* Automatically use HTTPS if ``ssl_key`` and ``ssl_cert`` are provided.
+
+For additional instructions about getting SSL to work see the Quickstart
+in the documentation.
+
+Deprecation
+------------
+Support for API v2 will be dropped in the 0.2.x release series.
+
+New features
+------------
+
+The following features are **only** supported for **API v3**.
+
+The ``node()`` and ``nodes()`` function have gained the following options:
+
+ * ``with_status=False``
+ * ``unreported=2``
+
+When ``with_status`` is set to ``True`` an additional query will be made using
+the ``events-count`` endpoint scoped to the latest report. This will result in
+an additional ``events`` and ``status`` keys on the node object. ``status``
+will be either of ``changed``, ``unchanged`` or ``failed`` depending on if
+``events`` contains ``successes`` or ``failures`` or none.
+
+By default ``unreported`` is set to ``2``. This is only in effect when
+``with_status`` is set to ``True``. It means that if a node hasn't checked in
+for two hours it will get a ``status`` of ``unreported`` instead.
+
+New endpoints:
+
+ * ``events-count``: ``events_count()``
+ * ``aggregate-event-counts``: ``aggregate_event_counts()``
+ * ``server-time``: ``server_time()``
+ * ``version``: ``current_version()``
+ * ``catalog``: ``catalog()``
+
+New types:
+
+ * ``pypuppetdb.types.Catalog``
+ * ``pypuppetdb.types.Edge``
+
+Changes to types:
+
+ * ``pypuppetdb.types.Node`` now has:
+ * ``status`` defaulting to ``None``
+ * ``events`` defaulting to ``None``
+ * ``unreported_time`` defaulting to ``None``
+
+0.0.4
+=====
+
+Due to a fairly serious bug 0.0.3 was pulled from PyPi minutes after release.
+
+When a bug was fixed to be able to query for all facts we accidentally
+introduced a different bug that caused the ``facts()`` call on a node to
+query for all facts because we were resetting the query.
+
+* Fix a bug where ``node.facts()`` was causing us to query all facts because
+ the query to scope our request was being reset.
+
+0.0.3
+=====
+
+With the introduction of PuppetDB 1.5 a new API version, v3, was also
+introduced. In that same release the old ``/experimental`` endpoints
+were removed, meaning that as of PuppetDB 1.5 with the v2 API you can
+no longer get access to reports or events.
+
+In light of this the support for the experimental endpoints has been
+completely removed from pypuppetdb. As of this release you can only get
+to reports and/or events through v3 of the API.
+
+This release includes preliminary support for the v3 API. Everything that
+could be done with v2 plus the experimental endpoints is now possible on
+v3. However, more advanced funtionality has not yet been implemented. That
+will be the focus of the next release.
+
+* Removed dependency on pytz.
+* Fixed the behaviour of ``facts()`` and ``resources()``. We can now
+ correctly query for all facts or resources.
+* Fixed an issue with catalog timestampless nodes.
+* Pass along the ``timeout`` option to ``connect()``.
+* Added preliminary PuppetDB API v3 support.
+* Removed support for the experimental endpoints.
+* The ``connect()`` method defaults to API v3 now.
+
+0.0.2
+=====
+* Fix a bug in ``setup.py`` preventing successful installation.
+
+0.0.1
+=====
+Initial release. Implements most of the v2 API.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..685b7b0
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include README.rst
+include CHANGELOG.rst
+include LICENSE
+include requirements.txt
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..4e78d5b
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,378 @@
+Metadata-Version: 1.1
+Name: pypuppetdb
+Version: 0.1.1
+Summary: Library for working with the PuppetDB REST API.
+Home-page: https://github.com/nedap/pypuppetdb
+Author: Daniele Sluijters
+Author-email: daniele.sluijters+pypi at gmail.com
+License: Apache License 2.0
+Description: ##########
+ pypuppetdb
+ ##########
+
+ .. image:: https://api.travis-ci.org/nedap/pypuppetdb.png
+ :target: https://travis-ci.org/nedap/pypuppetdb
+
+ .. image:: https://coveralls.io/repos/nedap/pypuppetdb/badge.png
+ :target: https://coeralls.io/r/nedap/pypuppetdb
+
+ .. image:: https://pypip.in/d/pypuppetdb/badge.png
+ :target: https://crate.io/packages/pypuppetdb
+
+ .. image:: https://pypip.in/v/pypuppetdb/badge.png
+ :target: https://crate.io/packages/pypuppetdb
+
+ pypuppetdtb is a library to work with PuppetDB's REST API. It is implemented
+ using the `requests`_ library.
+
+ .. _requests: http://docs.python-requests.org/en/latest/
+
+ This library is a thin wrapper around the REST API providing some convinience
+ functions and objects to request and hold data from PuppetDB.
+
+ To use this library you will need:
+ * Python 2.6 or 2.7
+ * Python 3.3
+
+ Installation
+ ============
+
+ You can install this package from source or from PyPi.
+
+ .. code-block:: bash
+
+ $ pip install pypuppetdb
+
+ .. code-block:: bash
+
+ $ git clone https://github.com/nedap/pypuppetdb
+ $ python setup.py install
+
+ If you wish to hack on it clone the repository but after that run:
+
+ .. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+ This will install all the runtime requirements of pypuppetdb and the
+ dependencies for the test suite and generation of documentation.
+
+ Usage
+ =====
+
+ Once you have pypuppetdb installed you can configure it to connect to PuppetDB
+ and take it from there.
+
+ Connecting
+ ----------
+
+ The first thing you need to do is to connect with PuppetDB:
+
+ .. code-block:: python
+
+ >>> from pypuppetdb import connect
+ >>> db = connect()
+
+ Nodes
+ -----
+
+ The following will return a generator object yielding Node objects for every
+ returned node from PuppetDB.
+
+ .. code-block:: python
+
+ >>> nodes = db.nodes()
+ >>> for node in nodes:
+ >>> print(node)
+ host1
+ host2
+ ...
+
+ To query a single node the singular `node()` can be used:
+
+ .. code-block:: python
+
+ >>> node = db.node('hostname')
+ >>> print(node)
+ hostname
+
+ Node scope
+ ~~~~~~~~~~
+
+ The Node objects are a bit more special in that they can query for facts and
+ resources themselves. Using those methods from a node object will automatically
+ add a query to the request scoping the request to the node.
+
+ .. code-block:: python
+
+ >>> node = db.node('hostname')
+ >>> print(node.fact('osfamily'))
+ osfamily/hostname
+
+ Facts
+ -----
+
+ .. code-block:: python
+
+ >>> facts = db.facts('osfamily')
+ >>> for fact in facts:
+ >>> print(fact)
+ osfamily/host1
+ osfamily/host2
+
+ That queries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+ one per node this fact is known for.
+
+ Resources
+ ---------
+
+ .. code-block:: python
+
+ >>> resources = db.resources('file')
+
+ Will return a generator object containing all file resources you're managing
+ across your infrastructure. This is probably a bad idea if you have a big
+ number of nodes as the response will be huge.
+
+ Catalogs
+ ---------
+
+ .. code-block:: python
+
+ >>> catalog = db.catalog('hostname')
+ >>> for res in catalog.get_resources():
+ >>> print(res)
+
+ Will return a Catalog object with the latest Catalog of the definded host. This
+ catalog contains the defined Resources and Edges.
+
+ Getting Help
+ ============
+ This project is still very new so it's not inconceivable you'll run into
+ issues.
+
+ For bug reports you can file an `issue`_. If you need help with something
+ feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
+ be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
+
+ There's now also the #puppetboard channel on `Freenode`_ where we hang out
+ and answer questions related to pypuppetdb and Puppetboard.
+
+ .. _issue: https://github.com/nedap/pypuppetdb/issues
+ .. _ at daenney: https://github.com/daenney
+ .. _IRCnet: http://www.ircnet.org
+ .. _Freenode: http://freenode.net
+
+ Documentation
+ =============
+ API documentation is automatically generated from the docstrings using
+ Sphinx's autodoc feature.
+
+ Documentation will automatically be rebuilt on every push thanks to the
+ Read The Docs webhook. You can `find it here`_.
+
+ .. _find it here: https://pypuppetdb.readthedocs.org/en/latest/
+
+ You can build the documentation manually by doing:
+
+ .. code-block:: bash
+
+ $ cd docs
+ $ make html
+
+ Doing so will only work if you have Sphinx installed, which you can acheive
+ through:
+
+ .. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+ Contributing
+ ============
+
+ We welcome contributions to this library. However, there are a few ground
+ rules contributors should be aware of.
+
+ License
+ -------
+ This project is licensed under the Apache v2.0 License. As such, your
+ contributions, once accepted, are automatically covered by this license.
+
+ Commit messages
+ ---------------
+ Write decent commit messages. Don't use swear words and refrain from
+ uninformative commit messages as 'fixed typo'.
+
+ The preferred format of a commit message:
+
+ ::
+
+ docs/quickstart: Fixed a typo in the Nodes section.
+
+ If needed, elaborate further on this commit. Feel free to write a
+ complete blog post here if that helps us understand what this is
+ all about.
+
+ Fixes #4 and resolves #2.
+
+ If you'd like a more elaborate guide on how to write and format your commit
+ messages have a look at this post by `Tim Pope`_.
+
+ .. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+
+ Tests
+ -----
+ Commits are expected to contain tests or updates to tests if they add to or
+ modify the current behaviour.
+
+ The test suite is powered by `pytest`_ and requires `pytest`_, `pytest-pep8`_,
+ `httpretty`_ and `pytest-httpretty`_ which will be installed for you if you
+ run:
+
+ .. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+ .. _pytest: http://pytest.org/latest/
+ .. _pytest-pep8: https://pypi.python.org/pypi/pytest-pep8
+ .. _httpretty: https://pypi.python.org/pypi/httpretty/
+ .. _pytest-httpretty: https://github.com/papaeye/pytest-httpretty
+
+ To run the unit tests (the ones that don't require a live PuppetDB):
+
+ .. code-block:: bash
+
+ $ py.test -v -m unit
+
+ If the tests pass, you're golden. If not we'll have to figure out why and
+ fix that. Feel free to ask for help on this.
+
+ #########
+ Changelog
+ #########
+
+ 0.1.1
+ =====
+
+ * Fix the license in our ``setup.py``. The license shouldn't be longer than
+ 200 characters. We were including the full license tripping up tools like
+ bdist_rpm.
+
+ 0.1.0
+ =====
+ Significant changes have been made in this release. The complete v3 API is
+ now supported except for query pagination.
+
+ Most changes are backwards compatible except for a change in the SSL
+ configuration. The previous behaviour was buggy and slightly misleading in
+ the names the options took:
+
+ * ``ssl`` has been renamed to ``ssl_verify`` and now defaults to ``True``.
+ * Automatically use HTTPS if ``ssl_key`` and ``ssl_cert`` are provided.
+
+ For additional instructions about getting SSL to work see the Quickstart
+ in the documentation.
+
+ Deprecation
+ ------------
+ Support for API v2 will be dropped in the 0.2.x release series.
+
+ New features
+ ------------
+
+ The following features are **only** supported for **API v3**.
+
+ The ``node()`` and ``nodes()`` function have gained the following options:
+
+ * ``with_status=False``
+ * ``unreported=2``
+
+ When ``with_status`` is set to ``True`` an additional query will be made using
+ the ``events-count`` endpoint scoped to the latest report. This will result in
+ an additional ``events`` and ``status`` keys on the node object. ``status``
+ will be either of ``changed``, ``unchanged`` or ``failed`` depending on if
+ ``events`` contains ``successes`` or ``failures`` or none.
+
+ By default ``unreported`` is set to ``2``. This is only in effect when
+ ``with_status`` is set to ``True``. It means that if a node hasn't checked in
+ for two hours it will get a ``status`` of ``unreported`` instead.
+
+ New endpoints:
+
+ * ``events-count``: ``events_count()``
+ * ``aggregate-event-counts``: ``aggregate_event_counts()``
+ * ``server-time``: ``server_time()``
+ * ``version``: ``current_version()``
+ * ``catalog``: ``catalog()``
+
+ New types:
+
+ * ``pypuppetdb.types.Catalog``
+ * ``pypuppetdb.types.Edge``
+
+ Changes to types:
+
+ * ``pypuppetdb.types.Node`` now has:
+ * ``status`` defaulting to ``None``
+ * ``events`` defaulting to ``None``
+ * ``unreported_time`` defaulting to ``None``
+
+ 0.0.4
+ =====
+
+ Due to a fairly serious bug 0.0.3 was pulled from PyPi minutes after release.
+
+ When a bug was fixed to be able to query for all facts we accidentally
+ introduced a different bug that caused the ``facts()`` call on a node to
+ query for all facts because we were resetting the query.
+
+ * Fix a bug where ``node.facts()`` was causing us to query all facts because
+ the query to scope our request was being reset.
+
+ 0.0.3
+ =====
+
+ With the introduction of PuppetDB 1.5 a new API version, v3, was also
+ introduced. In that same release the old ``/experimental`` endpoints
+ were removed, meaning that as of PuppetDB 1.5 with the v2 API you can
+ no longer get access to reports or events.
+
+ In light of this the support for the experimental endpoints has been
+ completely removed from pypuppetdb. As of this release you can only get
+ to reports and/or events through v3 of the API.
+
+ This release includes preliminary support for the v3 API. Everything that
+ could be done with v2 plus the experimental endpoints is now possible on
+ v3. However, more advanced funtionality has not yet been implemented. That
+ will be the focus of the next release.
+
+ * Removed dependency on pytz.
+ * Fixed the behaviour of ``facts()`` and ``resources()``. We can now
+ correctly query for all facts or resources.
+ * Fixed an issue with catalog timestampless nodes.
+ * Pass along the ``timeout`` option to ``connect()``.
+ * Added preliminary PuppetDB API v3 support.
+ * Removed support for the experimental endpoints.
+ * The ``connect()`` method defaults to API v3 now.
+
+ 0.0.2
+ =====
+ * Fix a bug in ``setup.py`` preventing successful installation.
+
+ 0.0.1
+ =====
+ Initial release. Implements most of the v2 API.
+
+Keywords: puppet puppetdb
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Topic :: Software Development :: Libraries
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..c20d243
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,240 @@
+##########
+pypuppetdb
+##########
+
+.. image:: https://api.travis-ci.org/nedap/pypuppetdb.png
+ :target: https://travis-ci.org/nedap/pypuppetdb
+
+.. image:: https://coveralls.io/repos/nedap/pypuppetdb/badge.png
+ :target: https://coeralls.io/r/nedap/pypuppetdb
+
+.. image:: https://pypip.in/d/pypuppetdb/badge.png
+ :target: https://crate.io/packages/pypuppetdb
+
+.. image:: https://pypip.in/v/pypuppetdb/badge.png
+ :target: https://crate.io/packages/pypuppetdb
+
+pypuppetdtb is a library to work with PuppetDB's REST API. It is implemented
+using the `requests`_ library.
+
+.. _requests: http://docs.python-requests.org/en/latest/
+
+This library is a thin wrapper around the REST API providing some convinience
+functions and objects to request and hold data from PuppetDB.
+
+To use this library you will need:
+ * Python 2.6 or 2.7
+ * Python 3.3
+
+Installation
+============
+
+You can install this package from source or from PyPi.
+
+.. code-block:: bash
+
+ $ pip install pypuppetdb
+
+.. code-block:: bash
+
+ $ git clone https://github.com/nedap/pypuppetdb
+ $ python setup.py install
+
+If you wish to hack on it clone the repository but after that run:
+
+.. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+This will install all the runtime requirements of pypuppetdb and the
+dependencies for the test suite and generation of documentation.
+
+Usage
+=====
+
+Once you have pypuppetdb installed you can configure it to connect to PuppetDB
+and take it from there.
+
+Connecting
+----------
+
+The first thing you need to do is to connect with PuppetDB:
+
+.. code-block:: python
+
+ >>> from pypuppetdb import connect
+ >>> db = connect()
+
+Nodes
+-----
+
+The following will return a generator object yielding Node objects for every
+returned node from PuppetDB.
+
+.. code-block:: python
+
+ >>> nodes = db.nodes()
+ >>> for node in nodes:
+ >>> print(node)
+ host1
+ host2
+ ...
+
+To query a single node the singular `node()` can be used:
+
+.. code-block:: python
+
+ >>> node = db.node('hostname')
+ >>> print(node)
+ hostname
+
+Node scope
+~~~~~~~~~~
+
+The Node objects are a bit more special in that they can query for facts and
+resources themselves. Using those methods from a node object will automatically
+add a query to the request scoping the request to the node.
+
+.. code-block:: python
+
+ >>> node = db.node('hostname')
+ >>> print(node.fact('osfamily'))
+ osfamily/hostname
+
+Facts
+-----
+
+.. code-block:: python
+
+ >>> facts = db.facts('osfamily')
+ >>> for fact in facts:
+ >>> print(fact)
+ osfamily/host1
+ osfamily/host2
+
+That queries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+one per node this fact is known for.
+
+Resources
+---------
+
+.. code-block:: python
+
+ >>> resources = db.resources('file')
+
+Will return a generator object containing all file resources you're managing
+across your infrastructure. This is probably a bad idea if you have a big
+number of nodes as the response will be huge.
+
+Catalogs
+---------
+
+.. code-block:: python
+
+ >>> catalog = db.catalog('hostname')
+ >>> for res in catalog.get_resources():
+ >>> print(res)
+
+Will return a Catalog object with the latest Catalog of the definded host. This
+catalog contains the defined Resources and Edges.
+
+Getting Help
+============
+This project is still very new so it's not inconceivable you'll run into
+issues.
+
+For bug reports you can file an `issue`_. If you need help with something
+feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
+be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
+
+There's now also the #puppetboard channel on `Freenode`_ where we hang out
+and answer questions related to pypuppetdb and Puppetboard.
+
+.. _issue: https://github.com/nedap/pypuppetdb/issues
+.. _ at daenney: https://github.com/daenney
+.. _IRCnet: http://www.ircnet.org
+.. _Freenode: http://freenode.net
+
+Documentation
+=============
+API documentation is automatically generated from the docstrings using
+Sphinx's autodoc feature.
+
+Documentation will automatically be rebuilt on every push thanks to the
+Read The Docs webhook. You can `find it here`_.
+
+.. _find it here: https://pypuppetdb.readthedocs.org/en/latest/
+
+You can build the documentation manually by doing:
+
+.. code-block:: bash
+
+ $ cd docs
+ $ make html
+
+Doing so will only work if you have Sphinx installed, which you can acheive
+through:
+
+.. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+Contributing
+============
+
+We welcome contributions to this library. However, there are a few ground
+rules contributors should be aware of.
+
+License
+-------
+This project is licensed under the Apache v2.0 License. As such, your
+contributions, once accepted, are automatically covered by this license.
+
+Commit messages
+---------------
+Write decent commit messages. Don't use swear words and refrain from
+uninformative commit messages as 'fixed typo'.
+
+The preferred format of a commit message:
+
+::
+
+ docs/quickstart: Fixed a typo in the Nodes section.
+
+ If needed, elaborate further on this commit. Feel free to write a
+ complete blog post here if that helps us understand what this is
+ all about.
+
+ Fixes #4 and resolves #2.
+
+If you'd like a more elaborate guide on how to write and format your commit
+messages have a look at this post by `Tim Pope`_.
+
+.. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+
+Tests
+-----
+Commits are expected to contain tests or updates to tests if they add to or
+modify the current behaviour.
+
+The test suite is powered by `pytest`_ and requires `pytest`_, `pytest-pep8`_,
+`httpretty`_ and `pytest-httpretty`_ which will be installed for you if you
+run:
+
+.. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+.. _pytest: http://pytest.org/latest/
+.. _pytest-pep8: https://pypi.python.org/pypi/pytest-pep8
+.. _httpretty: https://pypi.python.org/pypi/httpretty/
+.. _pytest-httpretty: https://github.com/papaeye/pytest-httpretty
+
+To run the unit tests (the ones that don't require a live PuppetDB):
+
+.. code-block:: bash
+
+ $ py.test -v -m unit
+
+If the tests pass, you're golden. If not we'll have to figure out why and
+fix that. Feel free to ask for help on this.
diff --git a/pypuppetdb.egg-info/PKG-INFO b/pypuppetdb.egg-info/PKG-INFO
new file mode 100644
index 0000000..4e78d5b
--- /dev/null
+++ b/pypuppetdb.egg-info/PKG-INFO
@@ -0,0 +1,378 @@
+Metadata-Version: 1.1
+Name: pypuppetdb
+Version: 0.1.1
+Summary: Library for working with the PuppetDB REST API.
+Home-page: https://github.com/nedap/pypuppetdb
+Author: Daniele Sluijters
+Author-email: daniele.sluijters+pypi at gmail.com
+License: Apache License 2.0
+Description: ##########
+ pypuppetdb
+ ##########
+
+ .. image:: https://api.travis-ci.org/nedap/pypuppetdb.png
+ :target: https://travis-ci.org/nedap/pypuppetdb
+
+ .. image:: https://coveralls.io/repos/nedap/pypuppetdb/badge.png
+ :target: https://coeralls.io/r/nedap/pypuppetdb
+
+ .. image:: https://pypip.in/d/pypuppetdb/badge.png
+ :target: https://crate.io/packages/pypuppetdb
+
+ .. image:: https://pypip.in/v/pypuppetdb/badge.png
+ :target: https://crate.io/packages/pypuppetdb
+
+ pypuppetdtb is a library to work with PuppetDB's REST API. It is implemented
+ using the `requests`_ library.
+
+ .. _requests: http://docs.python-requests.org/en/latest/
+
+ This library is a thin wrapper around the REST API providing some convinience
+ functions and objects to request and hold data from PuppetDB.
+
+ To use this library you will need:
+ * Python 2.6 or 2.7
+ * Python 3.3
+
+ Installation
+ ============
+
+ You can install this package from source or from PyPi.
+
+ .. code-block:: bash
+
+ $ pip install pypuppetdb
+
+ .. code-block:: bash
+
+ $ git clone https://github.com/nedap/pypuppetdb
+ $ python setup.py install
+
+ If you wish to hack on it clone the repository but after that run:
+
+ .. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+ This will install all the runtime requirements of pypuppetdb and the
+ dependencies for the test suite and generation of documentation.
+
+ Usage
+ =====
+
+ Once you have pypuppetdb installed you can configure it to connect to PuppetDB
+ and take it from there.
+
+ Connecting
+ ----------
+
+ The first thing you need to do is to connect with PuppetDB:
+
+ .. code-block:: python
+
+ >>> from pypuppetdb import connect
+ >>> db = connect()
+
+ Nodes
+ -----
+
+ The following will return a generator object yielding Node objects for every
+ returned node from PuppetDB.
+
+ .. code-block:: python
+
+ >>> nodes = db.nodes()
+ >>> for node in nodes:
+ >>> print(node)
+ host1
+ host2
+ ...
+
+ To query a single node the singular `node()` can be used:
+
+ .. code-block:: python
+
+ >>> node = db.node('hostname')
+ >>> print(node)
+ hostname
+
+ Node scope
+ ~~~~~~~~~~
+
+ The Node objects are a bit more special in that they can query for facts and
+ resources themselves. Using those methods from a node object will automatically
+ add a query to the request scoping the request to the node.
+
+ .. code-block:: python
+
+ >>> node = db.node('hostname')
+ >>> print(node.fact('osfamily'))
+ osfamily/hostname
+
+ Facts
+ -----
+
+ .. code-block:: python
+
+ >>> facts = db.facts('osfamily')
+ >>> for fact in facts:
+ >>> print(fact)
+ osfamily/host1
+ osfamily/host2
+
+ That queries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+ one per node this fact is known for.
+
+ Resources
+ ---------
+
+ .. code-block:: python
+
+ >>> resources = db.resources('file')
+
+ Will return a generator object containing all file resources you're managing
+ across your infrastructure. This is probably a bad idea if you have a big
+ number of nodes as the response will be huge.
+
+ Catalogs
+ ---------
+
+ .. code-block:: python
+
+ >>> catalog = db.catalog('hostname')
+ >>> for res in catalog.get_resources():
+ >>> print(res)
+
+ Will return a Catalog object with the latest Catalog of the definded host. This
+ catalog contains the defined Resources and Edges.
+
+ Getting Help
+ ============
+ This project is still very new so it's not inconceivable you'll run into
+ issues.
+
+ For bug reports you can file an `issue`_. If you need help with something
+ feel free to hit up `@daenney`_ by e-mail or find him on IRC. He can usually
+ be found on `IRCnet`_ and `Freenode`_ and idles in #puppet.
+
+ There's now also the #puppetboard channel on `Freenode`_ where we hang out
+ and answer questions related to pypuppetdb and Puppetboard.
+
+ .. _issue: https://github.com/nedap/pypuppetdb/issues
+ .. _ at daenney: https://github.com/daenney
+ .. _IRCnet: http://www.ircnet.org
+ .. _Freenode: http://freenode.net
+
+ Documentation
+ =============
+ API documentation is automatically generated from the docstrings using
+ Sphinx's autodoc feature.
+
+ Documentation will automatically be rebuilt on every push thanks to the
+ Read The Docs webhook. You can `find it here`_.
+
+ .. _find it here: https://pypuppetdb.readthedocs.org/en/latest/
+
+ You can build the documentation manually by doing:
+
+ .. code-block:: bash
+
+ $ cd docs
+ $ make html
+
+ Doing so will only work if you have Sphinx installed, which you can acheive
+ through:
+
+ .. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+ Contributing
+ ============
+
+ We welcome contributions to this library. However, there are a few ground
+ rules contributors should be aware of.
+
+ License
+ -------
+ This project is licensed under the Apache v2.0 License. As such, your
+ contributions, once accepted, are automatically covered by this license.
+
+ Commit messages
+ ---------------
+ Write decent commit messages. Don't use swear words and refrain from
+ uninformative commit messages as 'fixed typo'.
+
+ The preferred format of a commit message:
+
+ ::
+
+ docs/quickstart: Fixed a typo in the Nodes section.
+
+ If needed, elaborate further on this commit. Feel free to write a
+ complete blog post here if that helps us understand what this is
+ all about.
+
+ Fixes #4 and resolves #2.
+
+ If you'd like a more elaborate guide on how to write and format your commit
+ messages have a look at this post by `Tim Pope`_.
+
+ .. _Tim Pope: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
+
+ Tests
+ -----
+ Commits are expected to contain tests or updates to tests if they add to or
+ modify the current behaviour.
+
+ The test suite is powered by `pytest`_ and requires `pytest`_, `pytest-pep8`_,
+ `httpretty`_ and `pytest-httpretty`_ which will be installed for you if you
+ run:
+
+ .. code-block:: bash
+
+ $ pip install -r requirements.txt
+
+ .. _pytest: http://pytest.org/latest/
+ .. _pytest-pep8: https://pypi.python.org/pypi/pytest-pep8
+ .. _httpretty: https://pypi.python.org/pypi/httpretty/
+ .. _pytest-httpretty: https://github.com/papaeye/pytest-httpretty
+
+ To run the unit tests (the ones that don't require a live PuppetDB):
+
+ .. code-block:: bash
+
+ $ py.test -v -m unit
+
+ If the tests pass, you're golden. If not we'll have to figure out why and
+ fix that. Feel free to ask for help on this.
+
+ #########
+ Changelog
+ #########
+
+ 0.1.1
+ =====
+
+ * Fix the license in our ``setup.py``. The license shouldn't be longer than
+ 200 characters. We were including the full license tripping up tools like
+ bdist_rpm.
+
+ 0.1.0
+ =====
+ Significant changes have been made in this release. The complete v3 API is
+ now supported except for query pagination.
+
+ Most changes are backwards compatible except for a change in the SSL
+ configuration. The previous behaviour was buggy and slightly misleading in
+ the names the options took:
+
+ * ``ssl`` has been renamed to ``ssl_verify`` and now defaults to ``True``.
+ * Automatically use HTTPS if ``ssl_key`` and ``ssl_cert`` are provided.
+
+ For additional instructions about getting SSL to work see the Quickstart
+ in the documentation.
+
+ Deprecation
+ ------------
+ Support for API v2 will be dropped in the 0.2.x release series.
+
+ New features
+ ------------
+
+ The following features are **only** supported for **API v3**.
+
+ The ``node()`` and ``nodes()`` function have gained the following options:
+
+ * ``with_status=False``
+ * ``unreported=2``
+
+ When ``with_status`` is set to ``True`` an additional query will be made using
+ the ``events-count`` endpoint scoped to the latest report. This will result in
+ an additional ``events`` and ``status`` keys on the node object. ``status``
+ will be either of ``changed``, ``unchanged`` or ``failed`` depending on if
+ ``events`` contains ``successes`` or ``failures`` or none.
+
+ By default ``unreported`` is set to ``2``. This is only in effect when
+ ``with_status`` is set to ``True``. It means that if a node hasn't checked in
+ for two hours it will get a ``status`` of ``unreported`` instead.
+
+ New endpoints:
+
+ * ``events-count``: ``events_count()``
+ * ``aggregate-event-counts``: ``aggregate_event_counts()``
+ * ``server-time``: ``server_time()``
+ * ``version``: ``current_version()``
+ * ``catalog``: ``catalog()``
+
+ New types:
+
+ * ``pypuppetdb.types.Catalog``
+ * ``pypuppetdb.types.Edge``
+
+ Changes to types:
+
+ * ``pypuppetdb.types.Node`` now has:
+ * ``status`` defaulting to ``None``
+ * ``events`` defaulting to ``None``
+ * ``unreported_time`` defaulting to ``None``
+
+ 0.0.4
+ =====
+
+ Due to a fairly serious bug 0.0.3 was pulled from PyPi minutes after release.
+
+ When a bug was fixed to be able to query for all facts we accidentally
+ introduced a different bug that caused the ``facts()`` call on a node to
+ query for all facts because we were resetting the query.
+
+ * Fix a bug where ``node.facts()`` was causing us to query all facts because
+ the query to scope our request was being reset.
+
+ 0.0.3
+ =====
+
+ With the introduction of PuppetDB 1.5 a new API version, v3, was also
+ introduced. In that same release the old ``/experimental`` endpoints
+ were removed, meaning that as of PuppetDB 1.5 with the v2 API you can
+ no longer get access to reports or events.
+
+ In light of this the support for the experimental endpoints has been
+ completely removed from pypuppetdb. As of this release you can only get
+ to reports and/or events through v3 of the API.
+
+ This release includes preliminary support for the v3 API. Everything that
+ could be done with v2 plus the experimental endpoints is now possible on
+ v3. However, more advanced funtionality has not yet been implemented. That
+ will be the focus of the next release.
+
+ * Removed dependency on pytz.
+ * Fixed the behaviour of ``facts()`` and ``resources()``. We can now
+ correctly query for all facts or resources.
+ * Fixed an issue with catalog timestampless nodes.
+ * Pass along the ``timeout`` option to ``connect()``.
+ * Added preliminary PuppetDB API v3 support.
+ * Removed support for the experimental endpoints.
+ * The ``connect()`` method defaults to API v3 now.
+
+ 0.0.2
+ =====
+ * Fix a bug in ``setup.py`` preventing successful installation.
+
+ 0.0.1
+ =====
+ Initial release. Implements most of the v2 API.
+
+Keywords: puppet puppetdb
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: POSIX
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Topic :: Software Development :: Libraries
diff --git a/pypuppetdb.egg-info/SOURCES.txt b/pypuppetdb.egg-info/SOURCES.txt
new file mode 100644
index 0000000..54737cd
--- /dev/null
+++ b/pypuppetdb.egg-info/SOURCES.txt
@@ -0,0 +1,19 @@
+CHANGELOG.rst
+LICENSE
+MANIFEST.in
+README.rst
+setup.cfg
+setup.py
+pypuppetdb/__init__.py
+pypuppetdb/errors.py
+pypuppetdb/package.py
+pypuppetdb/types.py
+pypuppetdb/utils.py
+pypuppetdb.egg-info/PKG-INFO
+pypuppetdb.egg-info/SOURCES.txt
+pypuppetdb.egg-info/dependency_links.txt
+pypuppetdb.egg-info/requires.txt
+pypuppetdb.egg-info/top_level.txt
+pypuppetdb/api/__init__.py
+pypuppetdb/api/v2.py
+pypuppetdb/api/v3.py
\ No newline at end of file
diff --git a/pypuppetdb.egg-info/dependency_links.txt b/pypuppetdb.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pypuppetdb.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/pypuppetdb.egg-info/requires.txt b/pypuppetdb.egg-info/requires.txt
new file mode 100644
index 0000000..ce40e87
--- /dev/null
+++ b/pypuppetdb.egg-info/requires.txt
@@ -0,0 +1 @@
+requests >= 1.2.3
\ No newline at end of file
diff --git a/pypuppetdb.egg-info/top_level.txt b/pypuppetdb.egg-info/top_level.txt
new file mode 100644
index 0000000..88c4c96
--- /dev/null
+++ b/pypuppetdb.egg-info/top_level.txt
@@ -0,0 +1 @@
+pypuppetdb
diff --git a/pypuppetdb/__init__.py b/pypuppetdb/__init__.py
new file mode 100644
index 0000000..0320911
--- /dev/null
+++ b/pypuppetdb/__init__.py
@@ -0,0 +1,114 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+"""
+pypuppetdb PuppetDB API library
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+pypuppetdb is a library to work with PuppetDB's REST API. It provides a way
+to query PuppetDB and a set of additional methods and objects to make working
+with PuppetDB's API and the responses easier:
+
+ >>> from pypuppetdb import connect
+ >>> db = connect()
+ >>> nodes = db.nodes()
+ >>> print(nodes)
+ <generator object 'nodes'>
+ >>> for node in nodes:
+ >>> print(node)
+ host1
+ host2
+ ...
+
+This will return a generator object yielding Node objects for every returned
+node from PuppetDB.
+
+To query a single node the singular db.node() can be used:
+
+ >>> node = db.node('hostname')
+ >>> print(node)
+ hostname
+
+The Node objects are a bit more special in that they can query for facts and
+resources themselves. Using those methods from a node object will automatically
+add a query to the request scoping the request to the node.
+
+ >>> node = db.node('hostname')
+ >>> print node.fact('osfamily')
+ osfamily/hostname
+
+We can also query for facts:
+
+ >>> facts = db.facts('osfamily')
+ >>> print(facts)
+ <generator object 'facts')
+ >>> for fact in facts:
+ >>> print(fact)
+ osfamily/host1
+ osfamily/host2
+
+That querries PuppetDB for the 'osfamily' fact and will yield Fact objects,
+one per node this fact is found on.
+
+ >>> resources = db.resources('file')
+
+Will return a generator object containing all file resources you're managing
+across your infrastructure. This is probably a bad idea if you have a big
+number of nodes as the response will be huge.
+"""
+import logging
+
+from pypuppetdb.api import v2
+from pypuppetdb.api import v3
+from pypuppetdb.errors import UnsupportedVersionError
+
+try: # Python 2.7+
+ from logging import NullHandler
+except ImportError: # pragma: notest
+ class NullHandler(logging.Handler):
+ def emit(self, record):
+ pass
+
+logging.getLogger(__name__).addHandler(NullHandler())
+
+
+def connect(api_version=3, host='localhost', port=8080, ssl_verify=False,
+ ssl_key=None, ssl_cert=None, timeout=10):
+ """Connect with PuppetDB. This will return an object allowing you
+ to query the API through its methods.
+
+ :param api_version: Version of the API we're initialising.
+ :type api_version: :obj:`int`
+
+ :param host: (optional) Hostname or IP of PuppetDB.
+ :type host: :obj:`string`
+
+ :param port: (optional) Port on which to talk to PuppetDB.
+ :type port: :obj:`int`
+
+ :param ssl: (optional) Talk with PuppetDB over SSL.
+ :type ssl: :obj:`bool`
+
+ :param ssl_key: (optional) Path to our client secret key.
+ :type ssl_key: :obj:`None` or :obj:`string` representing a filesystem\
+ path.
+
+ :param ssl_cert: (optional) Path to our client certificate.
+ :type ssl_cert: :obj:`None` or :obj:`string` representing a filesystem\
+ path.
+
+ :param timeout: (optional) Number of seconds to wait for a response.
+ :type timeout: :obj:`int`
+
+ :raises: :class:`~pypuppetdb.errors.UnsupportedVersionError`
+ """
+ if api_version == 3:
+ return v3.API(host=host, port=port,
+ timeout=timeout, ssl_verify=ssl_verify, ssl_key=ssl_key,
+ ssl_cert=ssl_cert)
+ if api_version == 2:
+ return v2.API(host=host, port=port,
+ timeout=timeout, ssl_verify=ssl_verify, ssl_key=ssl_key,
+ ssl_cert=ssl_cert)
+ else:
+ raise UnsupportedVersionError
diff --git a/pypuppetdb/api/__init__.py b/pypuppetdb/api/__init__.py
new file mode 100644
index 0000000..a77178a
--- /dev/null
+++ b/pypuppetdb/api/__init__.py
@@ -0,0 +1,321 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+
+import json
+import requests
+
+from pypuppetdb.errors import (
+ ImproperlyConfiguredError,
+ EmptyResponseError,
+ UnsupportedVersionError,
+ APIError,
+ )
+
+log = logging.getLogger(__name__)
+
+API_VERSIONS = {
+ 2: 'v2',
+ 3: 'v3',
+}
+
+ENDPOINTS = {
+ 2: {
+ 'facts': 'facts',
+ 'fact-names': 'fact-names',
+ 'nodes': 'nodes',
+ 'resources': 'resources',
+ 'metrics': 'metrics',
+ 'mbean': 'metrics/mbean',
+ },
+ 3: {
+ 'facts': 'facts',
+ 'fact-names': 'fact-names',
+ 'nodes': 'nodes',
+ 'resources': 'resources',
+ 'catalogs': 'catalogs',
+ 'metrics': 'metrics',
+ 'mbean': 'metrics/mbean',
+ 'reports': 'reports',
+ 'events': 'events',
+ 'event-counts': 'event-counts',
+ 'aggregate-event-counts': 'aggregate-event-counts',
+ 'server-time': 'server-time',
+ 'version': 'version',
+ },
+}
+
+ERROR_STRINGS = {
+ 'timeout': 'Connection to PuppetDB timed out on',
+ 'refused': 'Could not reach PuppetDB on',
+}
+
+
+class BaseAPI(object):
+ """This is a Base or Abstract class and is not meant to be instantiated
+ or used directly.
+
+ The BaseAPI object defines a set of methods that can be
+ reused across different versions of the PuppetDB API. If querying for a
+ certain resource is done in an identical fashion across different versions
+ it will be implemented here and should be overridden in their respective
+ versions if they deviate.
+
+ If :attr:`ssl` is set to `True` but either :attr:`ssl_key` or\
+ :attr:`ssl_cert` are `None` this will raise an error.
+
+ When at initialisation :obj:`api_version` isn't found in\
+ :const:`API_VERSIONS` this will raise an error.
+
+ :param api_version: Version of the API we're initialising.
+ :type api_version: :obj:`int`
+ :param host: (optional) Hostname or IP of PuppetDB.
+ :type host: :obj:`string`
+ :param port: (optional) Port on which to talk to PuppetDB.
+ :type port: :obj:`int`
+ :param ssl_verify: (optional) Verify PuppetDB server certificate.
+ :type ssl_verify: :obj:`bool`
+ :param ssl_key: (optional) Path to our client secret key.
+ :type ssl_key: :obj:`None` or :obj:`string` representing a filesystem\
+ path.
+ :param ssl_cert: (optional) Path to our client certificate.
+ :type ssl_cert: :obj:`None` or :obj:`string` representing a filesystem\
+ path.
+ :param timeout: (optional) Number of seconds to wait for a response.
+ :type timeout: :obj:`int`
+
+ :raises: :class:`~pypuppetdb.errors.ImproperlyConfiguredError`
+ :raises: :class:`~pypuppetdb.errors.UnsupportedVersionError`
+ """
+ def __init__(self, api_version, host='localhost', port=8080,
+ ssl_verify=True, ssl_key=None, ssl_cert=None, timeout=10):
+ """Initialises our BaseAPI object passing the parameters needed in
+ order to be able to create the connection strings, set up SSL and
+ timeouts and so forth."""
+
+ if api_version in API_VERSIONS:
+ self.api_version = API_VERSIONS[api_version]
+ else:
+ raise UnsupportedVersionError
+
+ self.host = host
+ self.port = port
+ self.ssl_verify = ssl_verify
+ self.ssl_key = ssl_key
+ self.ssl_cert = ssl_cert
+ self.timeout = timeout
+ self.endpoints = ENDPOINTS[api_version]
+
+ if self.ssl_key is not None and self.ssl_cert is not None:
+ self.protocol = 'https'
+ else:
+ self.protocol = 'http'
+
+ @property
+ def version(self):
+ """The version of the API we're querying against.
+
+ :returns: Current API version.
+ :rtype: :obj:`string`"""
+ return self.api_version
+
+ @property
+ def base_url(self):
+ """A base_url that will be used to construct the final
+ URL we're going to query against.
+
+ :returns: A URL of the form: ``proto://host:port``.
+ :rtype: :obj:`string`
+ """
+ return '{proto}://{host}:{port}'.format(
+ proto=self.protocol,
+ host=self.host,
+ port=self.port,
+ )
+
+ @property
+ def total(self):
+ """The total-count of the last request to PuppetDB
+ if enabled as parameter in _query method
+
+ :returns Number of total results
+ :rtype :obj:`int`
+ """
+ if self.last_total is not None:
+ return int(self.last_total)
+
+ def _url(self, endpoint, path=None):
+ """The complete URL we will end up querying. Depending on the
+ endpoint we pass in this will result in different URL's with
+ different prefixes.
+
+ :param endpoint: The PuppetDB API endpoint we want to query.
+ :type endpoint: :obj:`string`
+ :param path: An additional path if we don't wish to query the\
+ bare endpoint.
+ :type path: :obj:`string`
+
+ :returns: A URL constructed from :func:`base_url` with the\
+ apropraite API version/prefix and the rest of the path added\
+ to it.
+ :rtype: :obj:`string`
+ """
+
+ log.debug('_url called with endpoint: {0} and path: {1}'.format(
+ endpoint, path))
+
+ if endpoint in self.endpoints:
+ api_prefix = self.api_version
+ endpoint = self.endpoints[endpoint]
+ else:
+ # If we reach this we're trying to query an endpoint that doesn't
+ # exist. This shouldn't happen unless someone made a booboo.
+ raise APIError
+
+ url = '{base_url}/{api_prefix}/{endpoint}'.format(
+ base_url=self.base_url,
+ api_prefix=api_prefix,
+ endpoint=endpoint,
+ )
+
+ if path is not None:
+ url = '{0}/{1}'.format(url, path)
+
+ return url
+
+ def _query(self, endpoint, path=None, query=None,
+ order_by=None, limit=None, offset=None, include_total=False,
+ summarize_by=None, count_by=None, count_filter=None):
+ """This method actually querries PuppetDB. Provided an endpoint and an
+ optional path and/or query it will fire a request at PuppetDB. If
+ PuppetDB can be reached and answers within the timeout we'll decode
+ the response and give it back or raise for the HTTP Status Code
+ PuppetDB gave back.
+
+ :param endpoint: The PuppetDB API endpoint we want to query.
+ :type endpoint: :obj:`string`
+ :param path: An additional path if we don't wish to query the\
+ bare endpoint.
+ :type path: :obj:`string`
+ :param query: (optional) A query to further narrow down the resultset.
+ :type query: :obj:`string`
+ :param order_by: (optional) Set the order parameters for the resultset.
+ :type order_by: :obj:`string`
+ :param limit: (optional) Tell PuppetDB to limit it's response to this\
+ number of objects.
+ :type limit: :obj:`int`
+ :param offset: (optional) Tell PuppetDB to start it's response from\
+ the given offset. This is useful for implementing pagination\
+ but is not supported just yet.
+ :type offset: :obj:`string`
+ :param include_total: (optional) Include the total number of results
+ :type order_by: :obj:`bool`
+ :param summarize_by: (optional) Specify what type of object you'd like\
+ to see counts at the event-counts and aggregate-event-counts \
+ endpoints
+ :type summarize_by: :obj:`string`
+ :param count_by: (optional) Specify what type of object is counted
+ :type count_by: :obj:`string`
+ :param count_filter: (optional) Specify a filter for the results
+ :type count_filter: :obj:`string`
+
+ :raises: :class:`~pypuppetdb.errors.EmptyResponseError`
+
+ :returns: The decoded response from PuppetDB
+ :rtype: :obj:`dict` or :obj:`list`
+ """
+ log.debug('_query called with endpoint: {0}, path: {1}, query: {2}, '
+ 'limit: {3}, offset: {4}, summarize_by {5}, count_by {6}, '
+ 'count_filter: {7}'.format(endpoint, path, query, limit,
+ offset, summarize_by, count_by,
+ count_filter))
+
+ url = self._url(endpoint, path=path)
+ headers = {
+ 'content-type': 'application/json',
+ 'accept': 'application/json',
+ 'accept-charset': 'utf-8'
+ }
+
+ payload = {}
+ if query is not None:
+ payload['query'] = query
+ if order_by is not None:
+ payload['order-by'] = order_by
+ if limit is not None:
+ payload['limit'] = limit
+ if include_total is True:
+ payload['include-total'] = json.dumps(include_total)
+ if offset is not None:
+ payload['offset'] = offset
+ if summarize_by is not None:
+ payload['summarize-by'] = summarize_by
+ if count_by is not None:
+ payload['count-by'] = count_by
+ if count_filter is not None:
+ payload['count-filter'] = count_filter
+
+ if not (payload):
+ payload = None
+
+ try:
+ r = requests.get(url, params=payload, headers=headers,
+ verify=self.ssl_verify, cert=(self.ssl_cert,
+ self.ssl_key),
+ timeout=self.timeout)
+ r.raise_for_status()
+
+ # get total number of results if requested with include-total
+ # just a quick hack - needs improvement
+ if 'X-Records' in r.headers:
+ self.last_total = r.headers['X-Records']
+ else:
+ self.last_total = None
+
+ json_body = r.json()
+ if json_body is not None:
+ return json_body
+ else:
+ del json_body
+ raise EmptyResponseError
+
+ except requests.exceptions.Timeout:
+ log.error("{0} {1}:{2} over {3}.".format(ERROR_STRINGS['timeout'],
+ self.host, self.port,
+ self.protocol.upper()))
+ raise
+ except requests.exceptions.ConnectionError:
+ log.error("{0} {1}:{2} over {3}.".format(ERROR_STRINGS['refused'],
+ self.host, self.port,
+ self.protocol.upper()))
+ raise
+ except requests.exceptions.HTTPError as err:
+ log.error("{0} {1}:{2} over {3}.".format(err.response.text,
+ self.host, self.port,
+ self.protocol.upper()))
+ raise
+
+ # Method stubs
+
+ def nodes(self):
+ raise NotImplementedError
+
+ def node(self):
+ raise NotImplementedError
+
+ def facts(self):
+ raise NotImplementedError
+
+ def resources(self):
+ raise NotImplementedError
+
+ def metric(self, metric):
+ """Query for a specific metrc.
+
+ :param metric: The name of the metric we want.
+ :type metric: :obj:`string`
+
+ :returns: The return of :meth:`~pypuppetdb.api.BaseAPI._query`.
+ """
+ return self._query('mbean', path=metric)
diff --git a/pypuppetdb/api/v2.py b/pypuppetdb/api/v2.py
new file mode 100644
index 0000000..185c23a
--- /dev/null
+++ b/pypuppetdb/api/v2.py
@@ -0,0 +1,122 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+
+from pypuppetdb.api import BaseAPI
+from pypuppetdb.types import (
+ Node, Fact, Resource,
+ )
+
+log = logging.getLogger(__name__)
+
+
+class API(BaseAPI):
+ """The API object for version 2 of the PuppetDB API. This object contains
+ all v2 specific methods and ways of doing things.
+
+ :param \*\*kwargs: Rest of the keywoard arguments passed on to our parent\
+ :class:`~pypuppetdb.api.BaseAPI`.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initialise the API object."""
+ super(API, self).__init__(api_version=2, **kwargs)
+ log.debug('API initialised with {0}'.format(kwargs))
+
+ def node(self, name):
+ """Gets a single node from PuppetDB."""
+ nodes = self.nodes(name=name)
+ return next(node for node in nodes)
+
+ def nodes(self, name=None, query=None):
+ """Query for nodes by either name or query. If both aren't
+ provided this will return a list of all nodes.
+
+ :param name: (optional)
+ :type name: :obj:`None` or :obj:`string`
+ :param query: (optional)
+ :type query: :obj:`None` or :obj:`string`
+
+ :returns: A generator yieling Nodes.
+ :rtype: :class:`pypuppetdb.types.Node`
+ """
+
+ nodes = self._query('nodes', path=name, query=query)
+ # If we happen to only get one node back it
+ # won't be inside a list so iterating over it
+ # goes boom. Therefor we wrap a list around it.
+ if type(nodes) == dict:
+ log.debug("Request returned a single node.")
+ nodes = [nodes, ]
+
+ for node in nodes:
+ yield Node(self,
+ node['name'],
+ deactivated=node['deactivated'],
+ report_timestamp=node['report_timestamp'],
+ catalog_timestamp=node['catalog_timestamp'],
+ facts_timestamp=node['facts_timestamp'],
+ )
+
+ def facts(self, name=None, value=None, query=None):
+ """Query for facts limited by either name, value and/or query.
+ This will yield a single Fact object at a time."""
+
+ log.debug('{0}, {1}, {2}'.format(name, value, query))
+ if name is not None and value is not None:
+ path = '{0}/{1}'.format(name, value)
+ elif name is not None and value is None:
+ path = name
+ elif name is None and value is None and query is not None:
+ path = None
+ else:
+ log.debug("We want to query for all facts.")
+ query = ''
+ path = None
+
+ facts = self._query('facts', path=path, query=query)
+ for fact in facts:
+ yield Fact(
+ fact['certname'],
+ fact['name'],
+ fact['value'],
+ )
+
+ def fact_names(self):
+ """Get a list of all known facts."""
+
+ return self._query('fact-names')
+
+ def resources(self, type_=None, title=None, query=None):
+ """Query for resources limited by either type and/or title or query.
+ This will yield a Resources object for every returned resource."""
+
+ if type_ is not None:
+ # Need to capitalize the resource type since PuppetDB doesn't
+ # answer to lower case type names.
+ # bugs.puppetlabs.com/some_value
+ type_ = type_.capitalize()
+ if title is not None:
+ path = '{0}/{1}'.format(type_, title)
+ elif title is None:
+ path = type_
+ else:
+ log.debug('Going to query for all resources. This is usually a '
+ 'bad idea as it might return enormous amounts of '
+ 'resources.')
+ query = ''
+ path = None
+
+ resources = self._query('resources', path=path, query=query)
+ for resource in resources:
+ yield Resource(
+ resource['certname'],
+ resource['title'],
+ resource['type'],
+ resource['tags'],
+ resource['exported'],
+ resource['sourcefile'],
+ resource['sourceline'],
+ resource['parameters'],
+ )
diff --git a/pypuppetdb/api/v3.py b/pypuppetdb/api/v3.py
new file mode 100644
index 0000000..f5a8705
--- /dev/null
+++ b/pypuppetdb/api/v3.py
@@ -0,0 +1,254 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+
+from pypuppetdb.api import BaseAPI
+from pypuppetdb.utils import json_to_datetime
+from datetime import datetime, timedelta
+from pypuppetdb.types import (
+ Node, Fact, Resource,
+ Report, Event, Catalog
+ )
+
+log = logging.getLogger(__name__)
+
+
+class API(BaseAPI):
+ """The API object for version 3 of the PuppetDB API. This object contains
+ all v3 specific methods and ways of doing things.
+
+ :param \*\*kwargs: Rest of the keywoard arguments passed on to our parent\
+ :class:`~pypuppetdb.api.BaseAPI`.
+ """
+
+ def __init__(self, *args, **kwargs):
+ """Initialise the API object."""
+ super(API, self).__init__(api_version=3, **kwargs)
+ log.debug('API initialised with {0}.'.format(kwargs))
+
+ def node(self, name):
+ """Gets a single node from PuppetDB."""
+ nodes = self.nodes(name=name)
+ return next(node for node in nodes)
+
+ def nodes(self, name=None, query=None, unreported=2, with_status=False):
+ """Query for nodes by either name or query. If both aren't
+ provided this will return a list of all nodes. This method
+ also fetches the nodes status and event counts of the latest
+ report from puppetdb.
+
+ :param name: (optional)
+ :type name: :obj:`None` or :obj:`string`
+ :param query: (optional)
+ :type query: :obj:`None` or :obj:`string`
+ :param with_status: (optional) include the node status in the\
+ returned nodes
+ :type with_status: :bool:
+ :param unreported: (optional) amount of hours when a node gets
+ marked as unreported
+ :type unreported: :obj:`None` or integer
+
+ :returns: A generator yieling Nodes.
+ :rtype: :class:`pypuppetdb.types.Node`
+ """
+ nodes = self._query('nodes', path=name, query=query)
+ # If we happen to only get one node back it
+ # won't be inside a list so iterating over it
+ # goes boom. Therefor we wrap a list around it.
+ if type(nodes) == dict:
+ nodes = [nodes, ]
+
+ if with_status:
+ latest_events = self._query(
+ 'event-counts',
+ query='["=","latest-report?",true]',
+ summarize_by='certname')
+
+ for node in nodes:
+ node['unreported_time'] = None
+ node['status'] = None
+
+ if with_status:
+ status = [s for s in latest_events
+ if s['subject']['title'] == node['name']]
+
+ # node status from events
+ if with_status and status:
+ node['events'] = status = status[0]
+ if status['successes'] > 0:
+ node['status'] = 'changed'
+ if status['failures'] > 0:
+ node['status'] = 'failed'
+ else:
+ if with_status:
+ node['status'] = 'unchanged'
+ node['events'] = None
+
+ # node report age
+ if with_status and node['report_timestamp'] is not None:
+ try:
+ last_report = json_to_datetime(node['report_timestamp'])
+ last_report = last_report.replace(tzinfo=None)
+ now = datetime.utcnow()
+ unreported_border = now-timedelta(hours=unreported)
+ if last_report < unreported_border:
+ delta = (datetime.utcnow()-last_report)
+ node['status'] = 'unreported'
+ node['unreported_time'] = '{0}d {1}h {2}m'.format(
+ delta.days,
+ int(delta.seconds/3600),
+ int((delta.seconds % 3600)/60)
+ )
+ except AttributeError:
+ node['status'] = 'unreported'
+
+ if not node['report_timestamp'] and with_status:
+ node['status'] = 'unreported'
+
+ yield Node(self,
+ node['name'],
+ deactivated=node['deactivated'],
+ report_timestamp=node['report_timestamp'],
+ catalog_timestamp=node['catalog_timestamp'],
+ facts_timestamp=node['facts_timestamp'],
+ status=node['status'],
+ events=node['events'],
+ unreported_time=node['unreported_time']
+ )
+
+ def facts(self, name=None, value=None, query=None):
+ """Query for facts limited by either name, value and/or query.
+ This will yield a single Fact object at a time."""
+
+ log.debug('{0}, {1}, {2}'.format(name, value, query))
+ if name is not None and value is not None:
+ path = '{0}/{1}'.format(name, value)
+ elif name is not None and value is None:
+ path = name
+ elif name is None and value is None and query is not None:
+ path = None
+ else:
+ log.debug("We want to query for all facts.")
+ query = ''
+ path = None
+
+ facts = self._query('facts', path=path, query=query)
+ for fact in facts:
+ yield Fact(
+ fact['certname'],
+ fact['name'],
+ fact['value'],
+ )
+
+ def fact_names(self):
+ """Get a list of all known facts."""
+
+ return self._query('fact-names')
+
+ def resources(self, type_=None, title=None, query=None):
+ """Query for resources limited by either type and/or title or query.
+ This will yield a Resources object for every returned resource."""
+
+ log.debug('YOLO')
+ if type_ is not None:
+ # Need to capitalize the resource type since PuppetDB doesn't
+ # answer to lower case type names.
+ # bugs.puppetlabs.com/some_value
+ type_ = type_.capitalize()
+ if title is not None:
+ path = '{0}/{1}'.format(type_, title)
+ elif title is None:
+ path = type_
+ else:
+ log.debug('Going to query for all resources. This is usually a '
+ 'bad idea as it might return enormous amounts of '
+ 'resources.')
+ query = ''
+ path = None
+
+ resources = self._query('resources', path=path, query=query)
+ for resource in resources:
+ yield Resource(
+ resource['certname'],
+ resource['title'],
+ resource['type'],
+ resource['tags'],
+ resource['exported'],
+ resource['file'],
+ resource['line'],
+ resource['parameters'],
+ )
+
+ def reports(self, query):
+ """Get reports for our infrastructure. Currently reports can only
+ be filtered through a query which requests a specific certname.
+ If not it will return all reports.
+
+ This yields a Report object for every returned report."""
+ reports = self._query('reports', query=query)
+ for report in reports:
+ yield Report(
+ report['certname'],
+ report['hash'],
+ report['start-time'],
+ report['end-time'],
+ report['receive-time'],
+ report['configuration-version'],
+ report['report-format'],
+ report['puppet-version'],
+ report['transaction-uuid']
+ )
+
+ def events(self, query):
+ """A report is made up of events. This allows to query for events
+ based on the reprt hash.
+ This yields an Event object for every returned event."""
+
+ events = self._query('events', query=query)
+ for event in events:
+ yield Event(
+ event['certname'],
+ event['status'],
+ event['timestamp'],
+ event['report'],
+ event['resource-title'],
+ event['property'],
+ event['message'],
+ event['new-value'],
+ event['old-value'],
+ event['resource-type'],
+ )
+
+ def event_counts(self, query, summarize_by,
+ count_by=None, count_filter=None):
+ """Get event counts from puppetdb"""
+ return self._query('event-counts',
+ query=query,
+ summarize_by=summarize_by,
+ count_by=count_by,
+ count_filter=count_filter)
+
+ def aggregate_event_counts(self, query, summarize_by,
+ count_by=None, count_filter=None):
+ """Get event counts from puppetdb"""
+ return self._query('aggregate-event-counts',
+ query=query, summarize_by=summarize_by,
+ count_by=count_by, count_filter=count_filter)
+
+ def server_time(self):
+ """Get the current time of the clock on the PuppetDB server"""
+ return self._query('server-time')['server-time']
+
+ def current_version(self):
+ """Get version information about the running PuppetDB server"""
+ return self._query('version')['version']
+
+ def catalog(self, node):
+ """Get the most recent catalog for a given node"""
+ c = self._query('catalogs', path=node)
+ return Catalog(c['data']['name'],
+ c['data']['edges'],
+ c['data']['resources'],
+ c['data']['version'],
+ c['data']['transaction-uuid'])
diff --git a/pypuppetdb/errors.py b/pypuppetdb/errors.py
new file mode 100644
index 0000000..4e7780d
--- /dev/null
+++ b/pypuppetdb/errors.py
@@ -0,0 +1,30 @@
+class APIError(Exception):
+ """Our base exception the other errors inherit from."""
+ pass
+
+
+class ImproperlyConfiguredError(APIError):
+ """This exception is thrown when the API is initialised
+ and it detects incompatbile configuration such as SSL turned
+ on but no certificates provided."""
+ pass
+
+
+class EmptyResponseError(APIError):
+ """Will be thrown when we did recieve a response but the
+ response is empty."""
+ pass
+
+
+class UnsupportedVersionError(APIError):
+ """Triggers when using the :func:`connect` function and
+ providing it with an unknown API version."""
+ pass
+
+
+class DoesNotComputeError(APIError):
+ """This error will be thrown when a function is called with
+ an incompatible set of optional parameters. This is the 'you are
+ being a naughty developer, go read the docs' error.
+ """
+ pass
diff --git a/pypuppetdb/package.py b/pypuppetdb/package.py
new file mode 100644
index 0000000..2a98591
--- /dev/null
+++ b/pypuppetdb/package.py
@@ -0,0 +1,9 @@
+from __future__ import unicode_literals
+
+__title__ = 'pypuppetdb'
+__version_info__ = (0, 1, 1) # notest
+__version__ = '.'.join("{0}".format(num) for num in __version_info__) # notest
+__author__ = 'Daniele Sluijters'
+__license__ = 'Apache License 2.0'
+__year__ = '2013, 2014'
+__copyright__ = 'Copyright {0} {1}'.format(__year__, __author__)
diff --git a/pypuppetdb/types.py b/pypuppetdb/types.py
new file mode 100644
index 0000000..1a1af94
--- /dev/null
+++ b/pypuppetdb/types.py
@@ -0,0 +1,408 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import logging
+from pypuppetdb.utils import json_to_datetime
+
+log = logging.getLogger(__name__)
+
+
+class Event(object):
+ """This object represents an event.
+
+ :param node: The hostname of the node this event fired on.
+ :param status: The status for the event.
+ :param timestamp: A timestamp of when this event occured.
+ :param hash\_: The hash of this event.
+ :param title: The resource title this event was fired for.
+ :param property\_: The property of the resource this event was fired for.
+ :param message: A message associated with this event.
+ :param new_value: The new value/state of the resource.
+ :param old_value: The old value/state of the resource.
+ :param type\_: The type of the resource this event fired for.
+
+ :ivar status: A :obj:`string` of this event's status.
+ :ivar failed: The :obj:`bool` equivalent of `status`.
+ :ivar timestamp: A :obj:`datetime.datetime` of when this event happend.
+ :ivar node: The hostname of the machine this event\
+ occured on.
+ :ivar hash\_: The hash of this event.
+ :ivar item: :obj:`dict` with information about the item/resource this\
+ event was triggered for.
+ """
+ def __init__(self, node, status, timestamp, hash_, title, property_,
+ message, new_value, old_value, type_):
+ self.node = node
+ self.status = status
+ if self.status == 'failure':
+ self.failed = True
+ else:
+ self.failed = False
+ self.timestamp = json_to_datetime(timestamp)
+ self.hash_ = hash_
+ self.item = {}
+ self.item['title'] = title
+ self.item['type'] = type_
+ self.item['property'] = property_
+ self.item['message'] = message
+ self.item['old'] = old_value
+ self.item['new'] = new_value
+ self.__string = '{0}[{1}]/{2}'.format(self.item['type'],
+ self.item['title'],
+ self.hash_)
+
+ def __repr__(self):
+ return str('Event: {0}'.format(self.__string))
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
+
+
+class Report(object):
+ """This object represents a report.
+
+ :param node: The hostname of the node this report originated on.
+ :param hash\_: A string uniquely identifying this report.
+ :param start: The start time of the agent run.
+ :type start: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param end: The time the agent finished its run.
+ :type end: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param received: The time PuppetDB received the report.
+ :type received: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param version: The catalog / configuration version.
+ :type version: :obj:`string`
+ :param format\_: The catalog format version.
+ :type format\_: :obj:`int`
+ :param agent_version: The Puppet agent version.
+ :type agent_version: :obj:`string`
+ :param transaction: The UUID of this transaction.
+ :type transaction: :obj:`string`
+
+ :ivar node: The hostname this report originated from.
+ :ivar hash\_: Unique identifier of this report.
+ :ivar start: :obj:`datetime.datetime` when the Puppet agent run started.
+ :ivar end: :obj:`datetime.datetime` when the Puppet agent run ended.
+ :ivar received: :obj:`datetime.datetime` when the report finished\
+ uploading.
+ :ivar version: :obj:`string` catalog configuration version.
+ :ivar format\_: :obj:`int` catalog format version.
+ :ivar agent_version: :obj:`string` Puppet Agent version.
+ :ivar run_time: :obj:`datetime.timedelta` of **end** - **start**.
+ :ivar transaction: UUID identifying this transaction.
+
+ """
+ def __init__(self, node, hash_, start, end, received, version,
+ format_, agent_version, transaction):
+
+ self.node = node
+ self.hash_ = hash_
+ self.start = json_to_datetime(start)
+ self.end = json_to_datetime(end)
+ self.received = json_to_datetime(received)
+ self.version = version
+ self.format_ = format_
+ self.agent_version = agent_version
+ self.run_time = self.end - self.start
+ self.transaction = transaction
+ self.__string = '{0}'.format(self.hash_)
+
+ def __repr__(self):
+ return str('Report: {0}'.format(self.__string))
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
+
+
+class Fact(object):
+ """his object represents a fact.
+
+ :param node: The hostname this fact was collected from.
+ :param name: The fact's name, such as 'osfamily'
+ :param value: The fact's value, such as 'Debian'
+
+ :ivar node: :obj:`string` holding the hostname.
+ :ivar name: :obj:`string` holding the fact's name.
+ :ivar value: :obj:`string` holding the fact's value.
+ """
+ def __init__(self, node, name, value):
+ self.node = node
+ self.name = name
+ self.value = value
+ self.__string = '{0}/{1}'.format(self.name, self.node)
+
+ def __repr__(self):
+ return str('Fact: {0}'.format(self.__string))
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
+
+
+class Resource(object):
+ """This object represents a resource.
+
+ :param node: The hostname this resource is located on.
+ :param name: The name of the resource in the Puppet manifest.
+ :param type\_: Type of the Puppet resource.
+ :param tags: Tags associated with this resource.
+ :type tags: :obj:`list`
+ :param exported: If it's an exported resource.
+ :type exported: :obj:`bool`
+ :param sourcefile: The Puppet manifest this resource is declared in.
+ :param sourceline: The line this resource is declared at.
+ :param parameters: The parameters this resource has been declared with.
+ :type parameters: :obj:`dict`
+
+ :ivar node: The hostname this resources is located on.
+ :ivar name: The name of the resource in the Puppet manifest.
+ :ivar type\_: The type of Puppet resource.
+ :ivar exported: :obj:`bool` if the resource is exported.
+ :ivar sourcefile: The Puppet manifest this resource is declared in.
+ :ivar sourceline: The line this resource is declared at.
+ :ivar parameters: :obj:`dict` with key:value pairs of parameters.
+ """
+ def __init__(self, node, name, type_, tags, exported, sourcefile,
+ sourceline, parameters={}):
+ self.node = node
+ self.name = name
+ self.type_ = type_
+ self.tags = tags
+ self.exported = exported
+ self.sourcefile = sourcefile
+ self.sourceline = sourceline
+ self.parameters = parameters
+ self.__string = '{0}[{1}]'.format(self.type_, self.name)
+
+ def __repr__(self):
+ return str('<Resource: {0}>').format(self.__string)
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
+
+
+class Node(object):
+ """This object represents a node. It additionally has some helper methods
+ so that you can query for resources or facts directly from the node scope.
+
+ :param api: API object.
+ :param name: Hostname of this node.
+ :param deactivated: (default `None`) Time this node was deactivated at.
+ :type deactivated: :obj:`string` formatted as ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param report_timestamp: (default `None`) Time of the last report.
+ :type report_timestamp: :obj:`string` formatted as\
+ ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param catalog_timestamp: (default `None`) Time the last time a catalog\
+ was compiled.
+ :type catalog_timestamp: :obj:`string` formatted as\
+ ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param facts_timestamp: (default `None`) Time the last time facts were\
+ collected.
+ :type facts_timestamp: :obj:`string` formatted as\
+ ``%Y-%m-%dT%H:%M:%S.%fZ``
+ :param status: (default `None`) Status of the node\
+ changed | unchanged | unreported | failed
+ :type status: :obj:`string`
+ :param events: (default `None`) Counted events from latest Report
+ :type events: :obj:`dict`
+ :param unreported_time: (default `None`) Time since last report
+ :type unreported_time: :obj:`string`
+
+ :ivar name: Hostname of this node.
+ :ivar deactivated: :obj:`datetime.datetime` when this host was\
+ deactivated or `False`.
+ :ivar report_timestamp: :obj:`datetime.datetime` when the last run\
+ occured or `None`.
+ :ivar catalog_timestamp: :obj:`datetime.datetime` last time a catalog was\
+ compiled or `None`.
+ :ivar facts_timestamp: :obj:`datetime.datetime` last time when facts were\
+ collected or `None`.
+ """
+ def __init__(self, api, name, deactivated=None, report_timestamp=None,
+ catalog_timestamp=None, facts_timestamp=None,
+ status=None, events=None, unreported_time=None):
+ self.name = name
+ self.status = status
+ self.events = events
+ self.unreported_time = unreported_time
+
+ if deactivated is not None:
+ self.deactivated = json_to_datetime(deactivated)
+ else:
+ self.deactivated = False
+ if report_timestamp is not None:
+ self.report_timestamp = json_to_datetime(report_timestamp)
+ else:
+ self.report_timestamp = report_timestamp
+ if facts_timestamp is not None:
+ self.facts_timestamp = json_to_datetime(facts_timestamp)
+ else:
+ self.facts_timestamp = facts_timestamp
+ if catalog_timestamp is not None:
+ self.catalog_timestamp = json_to_datetime(catalog_timestamp)
+ else:
+ self.catalog_timestamp = catalog_timestamp
+
+ self.__api = api
+ self.__query_scope = '["=", "certname", "{0}"]'.format(self.name)
+ self.__string = self.name
+
+ def __repr__(self):
+ return str('<Node: {0}>').format(self.__string)
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
+
+ def facts(self):
+ """Get all facts of this node."""
+ return self.__api.facts(query=self.__query_scope)
+
+ def fact(self, name):
+ """Get a single fact from this node."""
+ facts = self.__api.facts(name=name, query=self.__query_scope)
+ return next(fact for fact in facts)
+
+ def resources(self, type_=None, title=None):
+ """Get all resources of this node or all resources of the specified
+ type."""
+ if type_ is None:
+ resources = self.__api.resources(query=self.__query_scope)
+ elif type_ is not None and title is None:
+ resources = self.__api.resources(type_=type_,
+ query=self.__query_scope)
+ else:
+ resources = self.__api.resources(type_=type_, title=title,
+ query=self.__query_scope)
+ return resources
+
+ def resource(self, type_, title):
+ """Get a resource matching the supplied type and title."""
+ resources = self.__api.resources(type_=type_, title=title,
+ query=self.__query_scope)
+ return next(resource for resource in resources)
+
+ def reports(self):
+ """Get all reports for this node."""
+ return self.__api.reports(self.__query_scope)
+
+
+class Catalog(object):
+ """
+ This object represents a compiled catalog from puppet. It contains Resource
+ and Edge object that represent the dependency graph.
+
+ :param node: Name of the host
+ :type edges: :obj:`string`
+ :param edges: Edges returned from Catalog data
+ :type edges: :obj:`list` containing :obj:`dict` with Edge information
+ :param resources: Resources returned from Catalog data
+ :type resources: :obj:`list` containing :obj:`dict` with Resources
+ :param version: Catalog version from Puppet (unique for each node)
+ :type version: :obj:`string`
+ :param transaction_uuid: A string used to match the catalog with the
+ corresponding report that was issued during
+ the same puppet run
+ :type transaction_uuid: :obj:`string`
+
+ :ivar node: :obj:`string` Name of the host
+ :ivar version: :obj:`string` Catalog version from Puppet
+ (unique for each node)
+ :ivar transaction_uuid: :obj:`string` used to match the catalog with
+ corresponding report
+ :ivar edges: :obj:`list` of :obj:`Edge` The source Resource object\
+ of the relationship
+ :ivar resources: :obj:`dict` of :obj:`Resource` The source Resource\
+ object of the relationship
+ """
+ def __init__(self, node, edges, resources,
+ version, transaction_uuid):
+
+ self.node = node
+ self.version = version
+ self.transaction_uuid = transaction_uuid
+
+ self.resources = dict()
+ for resource in resources:
+ if 'file' not in resource:
+ resource['file'] = None
+ if 'line' not in resource:
+ resource['line'] = None
+ identifier = resource['type']+'['+resource['title']+']'
+ res = Resource(node, resource['title'],
+ resource['type'], resource['tags'],
+ resource['exported'], resource['file'],
+ resource['line'], resource['parameters'])
+ self.resources[identifier] = res
+
+ self.edges = []
+ for edge in edges:
+ identifier_source = edge['source']['type'] + \
+ '[' + edge['source']['title'] + ']'
+ identifier_target = edge['target']['type'] + \
+ '[' + edge['target']['title'] + ']'
+ self.edges.append(Edge(self.resources[identifier_source],
+ self.resources[identifier_target],
+ edge['relationship']))
+
+ self.__string = '{0}/{1}'.format(self.node, self.transaction_uuid)
+
+ def __repr__(self):
+ return str('<Catalog: {0}>').format(self.__string)
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
+
+ def get_resources(self):
+ return self.resources.itervalues()
+
+ def get_edges(self):
+ return iter(self.edges)
+
+
+class Edge(object):
+ """
+ This object represents the connection between two Resource objects
+
+ :param source: The source Resource object of the relationship
+ :type source: :obj:`Resource`
+ :param target: The target Resource object of the relationship
+ :type target: :obj:`Resource`
+ :param relaptionship: Name of the Puppet Ressource Relationship
+ :type relationship: :obj:`string`
+
+ :ivar source: :obj:`Resource` The source Resource object
+ :ivar target: :obj:`Resource` The target Resource object
+ :ivar relationship: :obj:`string` Name of the Puppet Resource relationship
+ """
+ def __init__(self, source, target, relationship):
+ self.source = source
+ self.target = target
+ self.relationship = relationship
+ self.__string = '{0} - {1} - {2}'.format(self.source,
+ self.relationship,
+ self.target)
+
+ def __repr__(self):
+ return str('<Edge: {0}>').format(self.__string)
+
+ def __str__(self):
+ return str('{0}').format(self.__string)
+
+ def __unicode__(self):
+ return self.__string
diff --git a/pypuppetdb/utils.py b/pypuppetdb/utils.py
new file mode 100644
index 0000000..0b73d73
--- /dev/null
+++ b/pypuppetdb/utils.py
@@ -0,0 +1,43 @@
+from __future__ import unicode_literals
+from __future__ import absolute_import
+
+import warnings
+import datetime
+
+
+# A UTC class, see:
+# http://docs.python.org/2/library/datetime.html#tzinfo-objects
+class UTC(datetime.tzinfo):
+ """UTC"""
+
+ def utcoffset(self, dt):
+ return datetime.timedelta(0)
+
+ def tzname(self, dt):
+ return str('UTC')
+
+ def dst(self, dt):
+ return datetime.timedelta(0)
+
+ def __repr__(self):
+ return str('<UTC>')
+
+ def __str__(self):
+ return str('UTC')
+
+ def __unicode__(self):
+ return 'UTC'
+
+
+def json_to_datetime(date):
+ """Tranforms a JSON datetime string into a timezone aware datetime
+ object with a UTC tzinfo object.
+
+ :param date: The datetime representation.
+ :type date: :obj:`string`
+
+ :returns: A timezone aware datetime object.
+ :rtype: :class:`datetime.datetime`
+ """
+ return datetime.datetime.strptime(date, '%Y-%m-%dT%H:%M:%S.%fZ').replace(
+ tzinfo=UTC())
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..3cfb4d5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,15 @@
+[pytest]
+norecursedirs = docs .tox
+minversion = 2.3
+markers =
+ unit: mark test as a unit test. Runs without PuppetDB.
+ integration: mark test as an integration test. Needs a live PuppetDB with testdata loaded.
+
+[wheel]
+universal = 1
+
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..6f5df4b
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,62 @@
+import sys
+import os
+import codecs
+
+from setuptools import setup, find_packages
+from setuptools.command.test import test as TestCommand
+
+
+if sys.argv[-1] == 'publish':
+ os.system('python setup.py sdist upload')
+ sys.exit()
+
+
+class Tox(TestCommand):
+
+ def finalize_options(self):
+ TestCommand.finalize_options(self)
+ self.test_args = []
+ self.test_suite = True
+
+ def run_tests(self):
+ #import here, cause outside the eggs aren't loaded
+ import tox
+ errno = tox.cmdline(self.test_args)
+ sys.exit(errno)
+
+with codecs.open('README.rst', encoding='utf-8') as f:
+ README = f.read()
+
+with codecs.open('CHANGELOG.rst', encoding='utf-8') as f:
+ CHANGELOG = f.read()
+
+setup(
+ name='pypuppetdb',
+ version='0.1.1',
+ author='Daniele Sluijters',
+ author_email='daniele.sluijters+pypi at gmail.com',
+ packages=find_packages(),
+ url='https://github.com/nedap/pypuppetdb',
+ license='Apache License 2.0',
+ description='Library for working with the PuppetDB REST API.',
+ long_description='\n'.join((README, CHANGELOG)),
+ keywords='puppet puppetdb',
+ tests_require=['tox'],
+ cmdclass={'test': Tox},
+ install_requires=[
+ "requests >= 1.2.3",
+ ],
+ classifiers=[
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'Natural Language :: English',
+ 'License :: OSI Approved :: Apache Software License',
+ 'Operating System :: POSIX',
+ 'Programming Language :: Python :: 2',
+ 'Programming Language :: Python :: 2.6',
+ 'Programming Language :: Python :: 2.7',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.3',
+ 'Topic :: Software Development :: Libraries'
+ ],
+ )
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-puppet/python-pypuppetdb.git
More information about the Pkg-puppet-devel
mailing list