Hello community, here is the log from the commit of package salt-api for openSUSE:Factory checked in at 2013-07-19 17:29:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/salt-api (Old) and /work/SRC/openSUSE:Factory/.salt-api.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "salt-api" Changes: -------- --- /work/SRC/openSUSE:Factory/salt-api/salt-api.changes 2013-05-16 11:39:07.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.salt-api.new/salt-api.changes 2013-07-19 17:29:21.000000000 +0200 @@ -1,0 +2,11 @@ +Thu Jul 18 04:46:39 UTC 2013 - [email protected] + +- Update package to 0.8.2 +- Backward incompatible needs salt 0.15.9 or greater +- Changes to rest_cherrypy: + - Fixed issue #87 which caused the Salt master's PID file to be overwritten. + - Fixed an inconsistency with the return format for the /minions convenience URL. + - Added a dedicated URL for serving an HTML app + - Added dedicated URL for serving static media + +------------------------------------------------------------------- Old: ---- salt-api-0.8.1.tar.gz New: ---- salt-api-0.8.2.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ salt-api.spec ++++++ --- /var/tmp/diff_new_pack.zu8NXM/_old 2013-07-19 17:29:22.000000000 +0200 +++ /var/tmp/diff_new_pack.zu8NXM/_new 2013-07-19 17:29:22.000000000 +0200 @@ -16,7 +16,7 @@ # Name: salt-api -Version: 0.8.1 +Version: 0.8.2 Release: 1%{?dist} License: Apache-2.0 Summary: The api for Salt a parallel remote execution system @@ -35,7 +35,7 @@ BuildRequires: fdupes BuildRequires: python-devel -BuildRequires: salt +BuildRequires: salt >= 0.15.9 BuildRequires: salt-master Requires: salt ++++++ salt-api-0.8.1.tar.gz -> salt-api-0.8.2.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/PKG-INFO new/salt-api-0.8.2/PKG-INFO --- old/salt-api-0.8.1/PKG-INFO 2013-04-22 20:46:24.000000000 +0200 +++ new/salt-api-0.8.2/PKG-INFO 2013-07-17 18:36:01.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: salt-api -Version: 0.8.1 +Version: 0.8.2 Summary: Generic interface for providing external access APIs to Salt Home-page: http://saltstack.org Author: Thomas S Hatch diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/doc/topics/releases/0.8.2.rst new/salt-api-0.8.2/doc/topics/releases/0.8.2.rst --- old/salt-api-0.8.1/doc/topics/releases/0.8.2.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/salt-api-0.8.2/doc/topics/releases/0.8.2.rst 2013-07-17 18:12:45.000000000 +0200 @@ -0,0 +1,25 @@ +============== +salt-api 0.8.2 +============== + +:program:`salt-api` 0.8.2 is largely a bugfix release that fixes a +compatibility issue with changes in Salt 0.15.9. + +.. note:: + + Requires Salt 0.15.9 or greater + +The following changes have been made to the :py:mod:`rest_cherrypy +<saltapi.netapi.rest_cherrypy.app>` netapi module that provides a RESTful +interface for a running Salt system: + +* Fixed issue #87 which caused the Salt master's PID file to be overwritten. +* Fixed an inconsistency with the return format for the ``/minions`` + convenience URL. + + .. warning:: + + This is a backward incompatible change. + +* Added a dedicated URL for serving an HTML app +* Added dedicated URL for serving static media diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/saltapi/cli.py new/salt-api-0.8.2/saltapi/cli.py --- old/salt-api-0.8.1/saltapi/cli.py 2013-02-27 19:49:08.000000000 +0100 +++ new/salt-api-0.8.2/saltapi/cli.py 2013-07-16 01:24:33.000000000 +0200 @@ -1,7 +1,12 @@ ''' CLI entry-point for salt-api ''' +# Import python libs +import sys +import logging + # Import salt libs +import salt.utils.verify from salt.utils.parsers import ( ConfigDirMixIn, DaemonMixIn, @@ -16,6 +21,8 @@ import saltapi.config import saltapi.version +log = logging.getLogger(__name__) + class SaltAPI(OptionParser, ConfigDirMixIn, LogLevelMixIn, PidfileMixin, DaemonMixIn, MergeConfigMixIn): @@ -26,16 +33,35 @@ VERSION = saltapi.version.__version__ + # ConfigDirMixIn config filename attribute + _config_filename_ = 'master' + # LogLevelMixIn attributes + _default_logging_logfile_ = '/var/log/salt/api' + def setup_config(self): - return saltapi.config.api_config(self.get_config_file_path('master')) + return saltapi.config.api_config(self.get_config_file_path()) def run(self): ''' Run the api ''' self.parse_args() - self.process_config_dir() + try: + if self.config['verify_env']: + logfile = self.config['log_file'] + if logfile is not None and not logfile.startswith('tcp://') \ + and not logfile.startswith('udp://') \ + and not logfile.startswith('file://'): + # Logfile is not using Syslog, verify + salt.utils.verify.verify_files( + [logfile], self.config['user'] + ) + except OSError as err: + log.error(err) + sys.exit(err.errno) + + self.setup_logfile_logger() + client = saltapi.client.SaltAPIClient(self.config) self.daemonize_if_required() self.set_pidfile() - client = saltapi.client.SaltAPIClient(self.config) client.run() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/saltapi/config.py new/salt-api-0.8.2/saltapi/config.py --- old/salt-api-0.8.1/saltapi/config.py 2013-03-05 06:46:00.000000000 +0100 +++ new/salt-api-0.8.2/saltapi/config.py 2013-07-08 19:42:08.000000000 +0200 @@ -8,7 +8,7 @@ DEFAULT_API_OPTS = { # ----- Salt master settings overridden by Salt-API ---------------------> 'pidfile': '/var/run/salt-api.pid', - 'logfile': '/var/log/api', + 'logfile': '/var/log/salt/api', # <---- Salt master settings overridden by Salt-API ---------------------- } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/saltapi/netapi/rest_cherrypy/__init__.py new/salt-api-0.8.2/saltapi/netapi/rest_cherrypy/__init__.py --- old/salt-api-0.8.1/saltapi/netapi/rest_cherrypy/__init__.py 2013-04-10 21:15:24.000000000 +0200 +++ new/salt-api-0.8.2/saltapi/netapi/rest_cherrypy/__init__.py 2013-07-16 01:09:36.000000000 +0200 @@ -85,6 +85,12 @@ from . import wsgi application = wsgi.get_application(root, apiopts, conf) + if not 'ssl_crt' in apiopts or not 'ssl_key' in apiopts: + logger.error("Not starting '%s'. Options 'ssl_crt' and 'ssl_key' " + "are required in production mode." % __name__) + + return None + # Mount and start the WSGI app using the production CherryPy server verify_certs(apiopts['ssl_crt'], apiopts['ssl_key']) @@ -92,7 +98,7 @@ apiopts['ssl_crt'], apiopts['ssl_key']) wsgi_d = wsgiserver.WSGIPathInfoDispatcher({'/': application}) server = wsgiserver.CherryPyWSGIServer( - ('0.0.0.0', apiopts['port']), + (apiopts.get('host', '0.0.0.0'), apiopts['port']), wsgi_app=wsgi_d) server.ssl_adapter = ssl_a diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/saltapi/netapi/rest_cherrypy/app.py new/salt-api-0.8.2/saltapi/netapi/rest_cherrypy/app.py --- old/salt-api-0.8.1/saltapi/netapi/rest_cherrypy/app.py 2013-04-22 04:33:42.000000000 +0200 +++ new/salt-api-0.8.2/saltapi/netapi/rest_cherrypy/app.py 2013-07-17 18:12:45.000000000 +0200 @@ -2,6 +2,8 @@ A REST API for Salt =================== +.. py:currentmodule:: saltapi.netapi.rest_cherrypy.app + :depends: - CherryPy Python module :configuration: All authentication is done through Salt's :ref:`external auth <acl-eauth>` system. Be sure that it is enabled and the user you are @@ -15,6 +17,10 @@ **Required** The port for the webserver to listen on. + host : ``0.0.0.0`` + The socket interface for the HTTP server to listen on. + + .. versionadded:: 0.8.2 debug : ``False`` Starts a for-development web server instead of the production-ready web server. @@ -30,14 +36,24 @@ The path to the private key for your SSL certificate. (See below) static A filesystem path to static HTML/JavaScript/CSS/image assets. - If this directory contains a ``index.html`` file, it will be served at - the root URL when HTML is requested by a client via the ``Accept`` - header. + static_path : ``/static`` + The URL prefix to use when serving static assets out of the directory + specified in the ``static`` setting. + + .. versionadded:: 0.8.2 + app + A filesystem path to an HTML file that will be served as a static file. + This is useful for bootstrapping a single-page JavaScript app. + + .. versionadded:: 0.8.2 + app_path : ``/app`` + The URL prefix to use for serving the HTML file specifed in the ``app`` + setting. This should be a simple name containing no slashes. - This directory may point to a clone of the `salt-ui`_ project to - bootstrap a graphical interface for interacting with Salt. + Any path information after the specified path is ignored; this is + useful for apps that utilize the HTML5 history API. - .. _`salt-ui`: https://github.com/saltstack/salt-ui + .. versionadded:: 0.8.2 Example production configuration block: @@ -58,6 +74,18 @@ % salt-call tls.create_self_signed_cert +Authentication +-------------- + +Authentication is performed by passing a session token with each request. The +token may be sent either via a custom header named :mailheader:`X-Auth-Token` +or sent inside a cookie. (The result is the same but browsers and some HTTP +clients handle cookies automatically and transparently so it is a convenience.) + +Token are generated via the :py:class:`Login` URL. + +.. seealso:: You can bypass the session handling via the :py:class:`Run` URL. + Usage ----- @@ -120,9 +148,10 @@ URL reference ------------- -The main entry point is the root URL (``/``) and all functionality is available -at that URL. The other URLs are largely convenience URLs that wrap that main -entry point. +The main entry point is the :py:class:`root URL (/) <LowDataAdapter>` and all +functionality is available at that URL. The other URLs are largely convenience +URLs that wrap that main entry point with shorthand or specialized +functionality. ''' # We need a custom pylintrc here... @@ -178,24 +207,6 @@ cherrypy.response.headers['Cache-Control'] = 'private' -def wants_html(): - ''' - Determine if the request is asking for HTML specifically. - - Returns an empty string or a string containing the output of the - cherrypy.lib.cptools.accept() function. - ''' - # Short-circuit if the request is vague or overly broad - if (not 'Accept' in cherrypy.request.headers - or cherrypy.request.headers['Accept'] == '*/*'): - return '' - - try: - return cherrypy.lib.cptools.accept( - ['text/html'] + [i for (i, _) in ct_out_map]) - except (AttributeError, cherrypy.CherryPyException): - return '' - # Be conservative in what you send # Maps Content-Type to serialization functions; this is a tuple of tuples to # preserve order of preference. @@ -214,14 +225,6 @@ :param args: Pass args through to the main handler :param kwargs: Pass kwargs through to the main handler ''' - # If we're being asked for HTML, try to serve index.html from the 'static' - # directory; this is useful (as a random, non-specific example) for - # bootstrapping the salt-ui app - if 'static' in cherrypy.config and 'html' in wants_html(): - index = os.path.join(cherrypy.config['static'], 'index.html') - if os.path.exists(index): - return cherrypy.lib.static.serve_file(index) - # Execute the real handler. Handle or pass-through any errors we know how # to handle (auth & HTTP errors). Reformat any errors we don't know how to # handle as a data structure. @@ -258,6 +261,8 @@ def hypermedia_out(): ''' + Determine the best handler for the requested content type + Wrap the normal handler and transform the output from that handler into the requested content type ''' @@ -265,12 +270,7 @@ request._hypermedia_inner_handler = request.handler request.handler = hypermedia_handler - # cherrypy.response.headers['Alternates'] = self.ct_out_map.keys() - # TODO: add 'negotiate' to Vary header and 'list' to TCN header - # Alternates: {"paper.1" 0.9 {type text/html} {language en}}, - # {"paper.2" 0.7 {type text/html} {language fr}}, - # {"paper.3" 1.0 {type application/postscript} {language en}} - + cherrypy.response.headers['Access-Control-Allow-Origin'] = '*' @functools.wraps @@ -391,13 +391,9 @@ 'tools.hypermedia_in.on': True, } - def __init__(self, opts): - ''' - :param opts: A dictionary of options from Salt's master config (e.g. - Salt's, ``__opts__``) - ''' - self.opts = opts - self.api = saltapi.APIClient(opts) + def __init__(self): + self.opts = cherrypy.config['saltopts'] + self.api = saltapi.APIClient(self.opts) def exec_lowstate(self): ''' @@ -440,9 +436,20 @@ :status 401: authentication required :status 406: requested Content-Type not available ''' + import inspect + + # Grab all available client interfaces + clients = [name for name, _ in inspect.getmembers(saltapi.APIClient, + predicate=inspect.ismethod) if not name.startswith('__')] + clients.remove('run') # run method calls client interfaces + + # Grab a list of output formats + formats = [ctype for ctype, _ in ct_out_map] + return { - 'status': cherrypy.response.status, 'return': "Welcome", + 'clients': clients, + 'output': formats, } def POST(self, **kwargs): @@ -586,7 +593,13 @@ - return: jid: '20130118105423694155' - minions: [ms-4, ms-3, ms-2, ms-1, ms-0] + + return: + - jid: '20130603122505459265' + minions: [ms-4, ms-3, ms-2, ms-1, ms-0] + _links: + jobs: + - href: /jobs/20130603122505459265 :form lowstate: lowstate data for the :py:mod:`~salt.client.LocalClient`; the ``client`` parameter will @@ -601,12 +614,16 @@ ''' for chunk in cherrypy.request.lowstate: chunk['client'] = 'local_async' - job_data = next(self.exec_lowstate(), {}) + job_data = list(self.exec_lowstate()) cherrypy.response.status = 202 - return [{ + return { 'return': job_data, - }] + '_links': { + 'jobs': [{'href': '/jobs/{0}'.format(i['jid'])} + for i in job_data], + }, + } class Jobs(LowDataAdapter): @@ -859,9 +876,47 @@ .. versionadded:: 0.8.0 - This entry point is primarily for "one-off" commands. Each request must - pass full Salt external authentication credentials. Otherwise this URL - is identical to the root (``/``) execution URL. + .. http:post:: /run + + This entry point is primarily for "one-off" commands. Each request + must pass full Salt authentication credentials. Otherwise this URL + is identical to the root (``/``) execution URL. + + **Example request**:: + + % curl -sS localhost:8000/run \\ + -H 'Accept: application/x-yaml' \\ + -d client='local' \\ + -d tgt='*' \\ + -d fun='test.ping' \\ + -d username='saltdev' \\ + -d password='saltdev' \\ + -d eauth='pam' + + .. code-block:: http + + POST /run HTTP/1.1 + Host: localhost:8000 + Accept: application/x-yaml + Content-Length: 75 + Content-Type: application/x-www-form-urlencoded + + client=local&tgt=*&fun=test.ping&username=saltdev&password=saltdev&eauth=pam + + **Example response**: + + .. code-block:: http + + HTTP/1.1 200 OK + Content-Length: 73 + Content-Type: application/x-yaml + + return: + - ms-0: true + ms-1: true + ms-2: true + ms-3: true + ms-4: true :form lowstate: A list of :term:`lowstate` data appropriate for the :ref:`client <client-apis>` specified client interface. Full @@ -875,6 +930,13 @@ } +class App(object): + exposed = True + def GET(self, *args): + apiopts = cherrypy.config['apiopts'] + return cherrypy.lib.static.serve_file(apiopts['app']) + + class API(object): ''' Collect configuration and URL map for building the CherryPy app @@ -888,34 +950,41 @@ 'jobs': Jobs, } - def __init__(self, opts): - self.opts = opts + def __init__(self): + self.opts = cherrypy.config['saltopts'] + self.apiopts = cherrypy.config['apiopts'] + for url, cls in self.url_map.items(): - setattr(self, url, cls(self.opts)) + setattr(self, url, cls()) + + if 'app' in self.apiopts: + setattr(self, self.apiopts.get('app_path', 'app').lstrip('/'), App()) - def get_conf(self, apiopts): + def get_conf(self): ''' Combine the CherryPy configuration with the rest_cherrypy config values pulled from the master config and return the CherryPy configuration ''' conf = { 'global': { - 'server.socket_host': '0.0.0.0', - 'server.socket_port': apiopts.get('port', 8000), - 'debug': apiopts.get('debug', False), + 'server.socket_host': self.apiopts.get('host', '0.0.0.0'), + 'server.socket_port': self.apiopts.get('port', 8000), + 'debug': self.apiopts.get('debug', False), }, '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 'tools.trailing_slash.on': True, 'tools.gzip.on': True, - - 'tools.staticdir.on': True if 'static' in apiopts else False, - 'tools.staticdir.dir': apiopts.get('static', ''), }, } - conf['global'].update(apiopts) + # Serve static media if the directory has been set in the configuration + if 'static' in self.apiopts: + conf[self.apiopts.get('static_path', '/static')] = { + 'tools.staticdir.on': True, + 'tools.staticdir.dir': self.apiopts['static'], + } # Add to global config cherrypy.config.update(conf['global']) @@ -927,11 +996,15 @@ ''' Returns a WSGI app and a configuration dictionary ''' - root = API(opts) # cherrypy app apiopts = opts.get(__name__.rsplit('.', 2)[-2], {}) # rest_cherrypy opts - cpyopts = root.get_conf(apiopts) # cherrypy app opts - gconf = cpyopts.get('global', {}) # 'global' section of cpyopts + # Add Salt and salt-api config options to the main CherryPy config dict + cherrypy.config['saltopts'] = opts + cherrypy.config['apiopts'] = apiopts + + root = API() # cherrypy app + + cpyopts = root.get_conf() # cherrypy app opts # Register salt-specific hooks cherrypy.tools.salt_token = cherrypy.Tool('on_start_resource', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/salt-api-0.8.1/saltapi/version.py new/salt-api-0.8.2/saltapi/version.py --- old/salt-api-0.8.1/saltapi/version.py 2013-04-22 04:44:55.000000000 +0200 +++ new/salt-api-0.8.2/saltapi/version.py 2013-07-17 18:13:41.000000000 +0200 @@ -1,4 +1,4 @@ -__version_info__ = (0, 8, 1) +__version_info__ = (0, 8, 2) __version__ = '.'.join(map(str, __version_info__)) # If we can get a version from Git use that instead, otherwise carry on -- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
