diff --git a/MANIFEST.in b/MANIFEST.in index 8bf096fa16..b124c45569 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ include LICENSE include README.rst include _requirements.txt include raet-requirements.txt +include cloud-requirements.txt include zeromq-requirements.txt include tests/*.py recursive-include tests * diff --git a/doc/.tx/config b/doc/.tx/config index 791fade073..259a037c5c 100644 --- a/doc/.tx/config +++ b/doc/.tx/config @@ -4424,3 +4424,123 @@ source_file = _build/locale/ref/states/all/salt.states.serverdensity_device.pot source_lang = en source_name = ref/states/all/salt.states.serverdensity_device.rst +[salt.ref--cli--salt-api] +file_filter = locale//LC_MESSAGES/ref/cli/salt-api.po +source_file = _build/locale/ref/cli/salt-api.pot +source_lang = en +source_name = ref/cli/salt-api.rst + +[salt.ref--netapi--all--index] +file_filter = locale//LC_MESSAGES/ref/netapi/all/index.po +source_file = _build/locale/ref/netapi/all/index.pot +source_lang = en +source_name = ref/netapi/all/index.rst + +[salt.ref--netapi--all--salt_netapi_rest_cherrypy] +file_filter = locale//LC_MESSAGES/ref/netapi/all/salt.netapi.rest_cherrypy.po +source_file = _build/locale/ref/netapi/all/salt.netapi.rest_cherrypy.pot +source_lang = en +source_name = ref/netapi/all/salt.netapi.rest_cherrypy.rst + +[salt.ref--netapi--all--salt_netapi_rest_tornado] +file_filter = locale//LC_MESSAGES/ref/netapi/all/salt.netapi.rest_tornado.po +source_file = _build/locale/ref/netapi/all/salt.netapi.rest_tornado.pot +source_lang = en +source_name = ref/netapi/all/salt.netapi.rest_tornado.rst + +[salt.ref--netapi--all--salt_netapi_rest_wsgi] +file_filter = locale//LC_MESSAGES/ref/netapi/all/salt.netapi.rest_wsgi.po +source_file = _build/locale/ref/netapi/all/salt.netapi.rest_wsgi.pot +source_lang = en +source_name = ref/netapi/all/salt.netapi.rest_wsgi.rst + +[salt.ref--pillar--all--salt_pillar_foreman] +file_filter = locale//LC_MESSAGES/ref/pillar/all/salt.pillar.foreman.po +source_file = _build/locale/ref/pillar/all/salt.pillar.foreman.pot +source_lang = en +source_name = ref/pillar/all/salt.pillar.foreman.rst + +[salt.topics--netapi--index] +file_filter = locale//LC_MESSAGES/topics/netapi/index.po +source_file = _build/locale/topics/netapi/index.pot +source_lang = en +source_name = topics/netapi/index.rst + +[salt.topics--netapi--writing] +file_filter = locale//LC_MESSAGES/topics/netapi/writing.po +source_file = _build/locale/topics/netapi/writing.pot +source_lang = en +source_name = topics/netapi/writing.rst + +[salt.topics--releases--2014_1_5] +file_filter = locale//LC_MESSAGES/topics/releases/2014.1.5.po +source_file = _build/locale/topics/releases/2014.1.5.pot +source_lang = en +source_name = topics/releases/2014.1.5.rst + +[salt.topics--releases--saltapi--0_5_0] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.5.0.po +source_file = _build/locale/topics/releases/saltapi/0.5.0.pot +source_lang = en +source_name = topics/releases/saltapi/0.5.0.rst + +[salt.topics--releases--saltapi--0_6_0] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.6.0.po +source_file = _build/locale/topics/releases/saltapi/0.6.0.pot +source_lang = en +source_name = topics/releases/saltapi/0.6.0.rst + +[salt.topics--releases--saltapi--0_7_0] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.7.0.po +source_file = _build/locale/topics/releases/saltapi/0.7.0.pot +source_lang = en +source_name = topics/releases/saltapi/0.7.0.rst + +[salt.topics--releases--saltapi--0_7_5] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.7.5.po +source_file = _build/locale/topics/releases/saltapi/0.7.5.pot +source_lang = en +source_name = topics/releases/saltapi/0.7.5.rst + +[salt.topics--releases--saltapi--0_8_0] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.8.0.po +source_file = _build/locale/topics/releases/saltapi/0.8.0.pot +source_lang = en +source_name = topics/releases/saltapi/0.8.0.rst + +[salt.topics--releases--saltapi--0_8_2] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.8.2.po +source_file = _build/locale/topics/releases/saltapi/0.8.2.pot +source_lang = en +source_name = topics/releases/saltapi/0.8.2.rst + +[salt.topics--releases--saltapi--0_8_3] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.8.3.po +source_file = _build/locale/topics/releases/saltapi/0.8.3.pot +source_lang = en +source_name = topics/releases/saltapi/0.8.3.rst + +[salt.topics--releases--saltapi--0_8_4] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/0.8.4.po +source_file = _build/locale/topics/releases/saltapi/0.8.4.pot +source_lang = en +source_name = topics/releases/saltapi/0.8.4.rst + +[salt.topics--releases--saltapi--index] +file_filter = locale//LC_MESSAGES/topics/releases/saltapi/index.po +source_file = _build/locale/topics/releases/saltapi/index.pot +source_lang = en +source_name = topics/releases/saltapi/index.rst + +[salt.topics--sdb--index] +file_filter = locale//LC_MESSAGES/topics/sdb/index.po +source_file = _build/locale/topics/sdb/index.pot +source_lang = en +source_name = topics/sdb/index.rst + +[salt.topics--targeting--ipcidr] +file_filter = locale//LC_MESSAGES/topics/targeting/ipcidr.po +source_file = _build/locale/topics/targeting/ipcidr.pot +source_lang = en +source_name = topics/targeting/ipcidr.rst + diff --git a/doc/man/salt-api.7 b/doc/man/salt-api.7 deleted file mode 100644 index 721e46993b..0000000000 --- a/doc/man/salt-api.7 +++ /dev/null @@ -1,2237 +0,0 @@ -.\" Man page generated from reStructuredText. -. -.TH "SALT-API" "7" "April 05, 2014" "0.8.3" "salt-api" -.SH NAME -salt-api \- salt-api Documentation -. -.nr rst2man-indent-level 0 -. -.de1 rstReportMargin -\\$1 \\n[an-margin] -level \\n[rst2man-indent-level] -level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] -- -\\n[rst2man-indent0] -\\n[rst2man-indent1] -\\n[rst2man-indent2] -.. -.de1 INDENT -.\" .rstReportMargin pre: -. RS \\$1 -. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] -. nr rst2man-indent-level +1 -.\" .rstReportMargin post: -.. -.de UNINDENT -. RE -.\" indent \\n[an-margin] -.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] -.nr rst2man-indent-level -1 -.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] -.in \\n[rst2man-indent\\n[rst2man-indent-level]]u -.. -.sp -\fBsalt\-api\fP is a modular interface on top of \fI\%Salt\fP that can provide a -variety of entry points into a running Salt system. It can start and manage -multiple interfaces allowing a REST API to coexist with XMLRPC or even a -Websocket API. -.SH GETTING STARTED -.INDENT 0.0 -.IP 1. 3 -Install \fBsalt\-api\fP on the same machine as your Salt master. -.IP 2. 3 -Edit your Salt master config file for all required options for each -\fBnetapi\fP module you wish to run. -.IP 3. 3 -Install any required additional libraries or software for each \fBnetapi\fP -module you wish to run. -.IP 4. 3 -Run \fBsalt\-api\fP which will then start all configured \fBnetapi\fP -modules. -.UNINDENT -.sp -\fBNOTE:\fP -.INDENT 0.0 -.INDENT 3.5 -Each \fBnetapi\fP module will have differing configuration requirements and -differing required software libraries. -.sp -Exactly like the various module types in Salt (\fIexecution modules\fP, -\fIrenderer modules\fP, \fIreturner modules\fP, etc.), \fInetapi -modules\fP in \fBsalt\-api\fP will \fInot\fP be loaded into memory or started -if all requirements are not met. -.UNINDENT -.UNINDENT -.SH INSTALLATION QUICKSTART -.SS salt\-api Quickstart -.sp -\fBsalt\-api\fP manages \fInetapi modules\fP which are modules that -(usually) bind to a port and start a service. Each netapi module will have -specific requirements for third\-party libraries and configuration (which goes -in the master config file). Read the documentation for each netapi module to -determine what is needed. -.sp -For example, the \fBrest_cherrypy\fP -netapi module requires that CherryPy be installed and that a \fBrest_cherrypy\fP -section be added to the master config that specifies which port to listen on. -.SS Installation -.SS PyPI -.sp -\fI\%https://pypi.python.org/pypi/salt\-api\fP -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -pip install salt\-api -.ft P -.fi -.UNINDENT -.UNINDENT -.SS RHEL, Fedora, CentOS -.sp -RPMs are available in the Fedora repositories and EPEL: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -yum install salt\-api -.ft P -.fi -.UNINDENT -.UNINDENT -.SS Ubuntu -.sp -PPA packages available for Ubuntu on LaunchPad: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -sudo add\-apt\-repository ppa:saltstack/salt -sudo apt\-get update -sudo apt\-get install salt\-api -.ft P -.fi -.UNINDENT -.UNINDENT -.SS openSUSE, SLES -.sp -RPMs are available via the OBS: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -zypper install salt\-api -.ft P -.fi -.UNINDENT -.UNINDENT -.SH NETAPI MODULES -.sp -The core functionality for \fBsalt\-api\fP lies in pluggable \fBnetapi\fP -modules that adhere to the simple interface of binding to a port and starting a -service. \fBsalt\-api\fP can manage one or many services concurrently. -.SS Full list of \fBnetapi\fP modules -.SS Full list of netapi modules -.SS rest_cherrypy -.SS A REST API for Salt -.INDENT 0.0 -.TP -.B depends -.INDENT 7.0 -.IP \(bu 2 -CherryPy Python module -.UNINDENT -.TP -.B configuration -All authentication is done through Salt\(aqs \fI\%external auth\fP system. Be sure that it is enabled and the user you are -authenticating as has permissions for all the functions you will be -running. -.sp -Example production configuration block; add to the Salt master config file: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -rest_cherrypy: - port: 8000 - ssl_crt: /etc/pki/tls/certs/localhost.crt - ssl_key: /etc/pki/tls/certs/localhost.key -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -The REST interface strongly recommends a secure HTTPS connection since Salt -authentication credentials will be sent over the wire. If you don\(aqt already -have a certificate and don\(aqt wish to buy one, you can generate a -self\-signed certificate using the -\fI\%create_self_signed_cert()\fP function in Salt (note -the dependencies for this module): -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% salt\-call tls.create_self_signed_cert -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -All available configuration options are detailed below. These settings -configure the CherryPy HTTP server and do not apply when using an external -server such as Apache or Nginx. -.INDENT 7.0 -.TP -.B port -\fBRequired\fP -.sp -The port for the webserver to listen on. -.TP -.B host -\fB0.0.0.0\fP -The socket interface for the HTTP server to listen on. -.sp -New in version 0.8.2. - -.TP -.B debug -\fBFalse\fP -Starts the web server in development mode. It will reload itself when -the underlying code is changed and will output more debugging info. -.TP -.B ssl_crt -The path to a SSL certificate. (See below) -.TP -.B ssl_key -The path to the private key for your SSL certificate. (See below) -.TP -.B disable_ssl -A flag to disable SSL. Warning: your Salt authentication credentials -will be sent in the clear! -.sp -New in version 0.8.3. - -.TP -.B thread_pool -\fB100\fP -The number of worker threads to start up in the pool. -.sp -Changed in version 0.8.4: Previous versions defaulted to a pool of \fB10\fP - -.TP -.B socket_queue_size -\fB30\fP -Specify the maximum number of HTTP connections to queue. -.sp -Changed in version 0.8.4: Previous versions defaulted to \fB5\fP connections. - -.TP -.B max_request_body_size -\fB1048576\fP -Changed in version 0.8.4: Previous versions defaulted to \fB104857600\fP for the size of the -request body - -.TP -.B collect_stats -False -Collect and report statistics about the CherryPy server -.sp -New in version 0.8.4. - -.sp -Reports are available via the \fBStats\fP URL. -.TP -.B static -A filesystem path to static HTML/JavaScript/CSS/image assets. -.TP -.B static_path -\fB/static\fP -The URL prefix to use when serving static assets out of the directory -specified in the \fBstatic\fP setting. -.sp -New in version 0.8.2. - -.TP -.B 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. -.sp -New in version 0.8.2. - -.TP -.B app_path -\fB/app\fP -The URL prefix to use for serving the HTML file specified in the \fBapp\fP -setting. This should be a simple name containing no slashes. -.sp -Any path information after the specified path is ignored; this is -useful for apps that utilize the HTML5 history API. -.sp -New in version 0.8.2. - -.TP -.B root_prefix -\fB/\fP -A URL path to the main entry point for the application. This is useful -for serving multiple applications from the same URL. -.sp -New in version 0.8.4. - -.UNINDENT -.UNINDENT -.SS Authentication -.sp -Authentication is performed by passing a session token with each request. The -token may be sent either via a custom header named \fIX\-Auth\-Token\fP -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.) -.sp -Token are generated via the \fBLogin\fP URL. -.sp -\fBSEE ALSO:\fP -.INDENT 0.0 -.INDENT 3.5 -You can bypass the session handling via the \fBRun\fP URL. -.UNINDENT -.UNINDENT -.SS Usage -.sp -You access a running Salt master via this module by sending HTTP requests to -the URLs detailed below. -.INDENT 0.0 -.INDENT 3.5 -.IP "Content negotiation" -.sp -This REST interface is flexible in what data formats it will accept as well -as what formats it will return (e.g., JSON, YAML, x\-www\-form\-urlencoded). -.INDENT 0.0 -.IP \(bu 2 -Specify the format of data you are sending in a request by including the -\fIContent\-Type\fP header. -.IP \(bu 2 -Specify your desired output format for the response with the -\fIAccept\fP header. -.UNINDENT -.UNINDENT -.UNINDENT -.sp -This REST interface expects data sent in \fI\%POST\fP and -\fI\%PUT\fP requests to be in the format of a list of lowstate -dictionaries. This allows you to specify multiple commands in a single request. -.INDENT 0.0 -.TP -.B lowstate -A dictionary containing various keys that instruct Salt which command -to run, where that command lives, any parameters for that command, any -authentication credentials, what returner to use, etc. -.sp -Salt uses the lowstate data format internally in many places to pass -command data between functions. Salt also uses lowstate for the -\fI\%LocalClient()\fP Python API interface. -.UNINDENT -.sp -For example (in JSON format): -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -[{ - \(aqclient\(aq: \(aqlocal\(aq, - \(aqtgt\(aq: \(aq*\(aq, - \(aqfun\(aq: \(aqtest.fib\(aq, - \(aqarg\(aq: [\(aq10\(aq], -}] -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 0.0 -.INDENT 3.5 -.IP "x\-www\-form\-urlencoded" -.sp -This REST interface accepts data in the x\-www\-form\-urlencoded format. This -is the format used by HTML forms, the default format used by -\fBcurl\fP, the default format used by many JavaScript AJAX libraries -(such as jQuery), etc. This format will be converted to the -\fIlowstate\fP format as best as possible with the caveats below. It is -always preferable to format data in the lowstate format directly in a more -capable format such as JSON or YAML. -.INDENT 0.0 -.IP \(bu 2 -Only a single command may be sent in this format per HTTP request. -.IP \(bu 2 -Multiple \fBarg\fP params will be sent as a single list of params. -.sp -Note, some popular frameworks and languages (notably jQuery, PHP, and -Ruby on Rails) will automatically append empty brackets onto repeated -parameters. E.g., arg=one, arg=two will be sent as arg[]=one, arg[]=two. -Again, it is preferable to send lowstate via JSON or YAML directly by -specifying the \fIContent\-Type\fP header in the request. -.UNINDENT -.UNINDENT -.UNINDENT -.SS URL reference -.sp -The main entry point is the \fBroot URL (/)\fP 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. -.SS Deployment -.sp -The \fBrest_cherrypy\fP netapi module is a standard Python WSGI app. It can be -deployed one of two ways. -.SS \fBsalt\-api\fP using the CherryPy server -.sp -The default configuration is to run this module using \fBsalt\-api\fP to -start the Python\-based CherryPy server. This server is lightweight, -multi\-threaded, encrypted with SSL, and should be considered production\-ready. -.SS Using a WSGI\-compliant web server -.sp -This module may be deplayed on any WSGI\-compliant server such as Apache with -mod_wsgi or Nginx with FastCGI, to name just two (there are many). -.sp -Note, external WSGI servers handle URLs, paths, and SSL certs directly. The -\fBrest_cherrypy\fP configuration options are ignored and the \fBsalt\-api\fP daemon -does not need to be running at all. Remember Salt authentication credentials -are sent in the clear unless SSL is being enforced! -.sp -An example Apache virtual host configuration: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C - - ServerName example.com - ServerAlias *.example.com - - ServerAdmin webmaster@example.com - - LogLevel warn - ErrorLog /var/www/example.com/logs/error.log - CustomLog /var/www/example.com/logs/access.log combined - - DocumentRoot /var/www/example.com/htdocs - - WSGIScriptAlias / /path/to/saltapi/netapi/rest_cherrypy/wsgi.py - -.ft P -.fi -.UNINDENT -.UNINDENT -.SS REST URI Reference -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.LowDataAdapter -The primary entry point to the REST API. All functionality is available -through this URL. The other available URLs provide convenience wrappers -around this URL. -.INDENT 7.0 -.TP -.B GET() -.INDENT 7.0 -.TP -.B GET / -An explanation of the API with links of where to go next. -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-i localhost:8000 -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -GET / HTTP/1.1 -Host: localhost:8000 -Accept: application/json -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Type: application/json -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Status 200 -success -.TP -.B Status 401 -authentication required -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Login(*args, **kwargs) -All interactions with this REST API must be authenticated. Authentication -is performed through Salt\(aqs eauth system. You must set the eauth backend -and allowed users by editing the \fI\%external_auth\fP section in -your master config. -.sp -Authentication credentials are passed to the REST API via a session id in -one of two ways: -.sp -If the request is initiated from a browser it must pass a session id via a -cookie and that session must be valid and active. -.sp -If the request is initiated programmatically, the request must contain a -\fIX\-Auth\-Token\fP header with valid and active session id. -.INDENT 7.0 -.TP -.B GET() -Present the login interface -.INDENT 7.0 -.TP -.B GET /login -An explanation of how to log in. -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-i localhost:8000/login -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -GET /login HTTP/1.1 -Host: localhost:8000 -Accept: text/html -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Type: text/html -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Status 401 -authentication required -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B POST(**kwargs) -Authenticate against Salt\(aqs eauth system -.sp -Changed in version 0.8.0: No longer returns a 302 redirect on success. - -.sp -Changed in version 0.8.1: Returns 401 on authentication failure - -.INDENT 7.0 -.TP -.B POST /login -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-si localhost:8000/login \e - \-H "Accept: application/json" \e - \-d username=\(aqsaltuser\(aq \e - \-d password=\(aqsaltpass\(aq \e - \-d eauth=\(aqpam\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -POST / HTTP/1.1 -Host: localhost:8000 -Content\-Length: 42 -Content\-Type: application/x\-www\-form\-urlencoded -Accept: application/json - -username=saltuser&password=saltpass&eauth=pam -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Type: application/json -Content\-Length: 206 -X\-Auth\-Token: 6d1b722e -Set\-Cookie: session_id=6d1b722e; expires=Sat, 17 Nov 2012 03:23:52 GMT; Path=/ - -{"return": { - "token": "6d1b722e", - "start": 1363805943.776223, - "expire": 1363849143.776224, - "user": "saltuser", - "eauth": "pam", - "perms": [ - "grains.*", - "status.*", - "sys.*", - "test.*" - ] -}} -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Form eauth -the eauth backend configured in your master config -.TP -.B Form username -username -.TP -.B Form password -password -.TP -.B Status 200 -success -.TP -.B Status 401 -could not authenticate using provided credentials -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Logout -.INDENT 7.0 -.TP -.B POST() -Destroy the currently active session and expire the session cookie -.sp -New in version 0.8.0. - -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Minions -.INDENT 7.0 -.TP -.B GET(mid=None) -A convenience URL for getting lists of minions or getting minion -details -.INDENT 7.0 -.TP -.B GET /minions/(mid) -Get grains, modules, functions, and inline function documentation -for all minions or a single minion -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-i localhost:8000/minions/ms\-3 -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -GET /minions/ms\-3 HTTP/1.1 -Host: localhost:8000 -Accept: application/x\-yaml -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Length: 129005 -Content\-Type: application/x\-yaml - -return: -\- ms\-3: - grains.items: - ... -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Parameters -\fBmid\fP \-\- (optional) a minion id -.TP -.B Status 200 -success -.TP -.B Status 401 -authentication required -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B POST(**kwargs) -Start an execution command and immediately return the job id -.INDENT 7.0 -.TP -.B POST /minions -You must pass low\-data in the request body either from an HTML form -or as JSON or YAML. The \fBclient\fP option is pre\-set to -\fBlocal_async\fP\&. -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sSi localhost:8000/minions \e - \-H "Accept: application/x\-yaml" \e - \-d tgt=\(aq*\(aq \e - \-d fun=\(aqstatus.diskusage\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -POST /minions HTTP/1.1 -Host: localhost:8000 -Accept: application/x\-yaml -Content\-Length: 26 -Content\-Type: application/x\-www\-form\-urlencoded - -tgt=*&fun=status.diskusage -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 202 Accepted -Content\-Length: 86 -Content\-Type: application/x\-yaml - -return: -\- jid: \(aq20130603122505459265\(aq - minions: [ms\-4, ms\-3, ms\-2, ms\-1, ms\-0] -_links: - jobs: - \- href: /jobs/20130603122505459265 -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Form lowstate -lowstate data for the -\fBLocalClient\fP; the \fBclient\fP parameter will -be set to \fBlocal_async\fP -.sp -Lowstate may be supplied in any supported format by specifying the -\fIContent\-Type\fP header in the request. Supported formats -are listed in the \fIAlternates\fP response header. -.TP -.B Status 202 -success -.TP -.B Status 401 -authentication required -.TP -.B Status 406 -requested \fIContent\-Type\fP not available -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Jobs -.INDENT 7.0 -.TP -.B GET(jid=None) -A convenience URL for getting lists of previously run jobs or getting -the return from a single job -.INDENT 7.0 -.TP -.B GET /jobs/(jid) -Get grains, modules, functions, and inline function documentation -for all minions or a single minion -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-i localhost:8000/jobs -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -GET /jobs HTTP/1.1 -Host: localhost:8000 -Accept: application/x\-yaml -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Length: 165 -Content\-Type: application/x\-yaml - -return: -\- \(aq20121130104633606931\(aq: - Arguments: - \- \(aq3\(aq - Function: test.fib - Start Time: 2012, Nov 30 10:46:33.606931 - Target: jerry - Target\-type: glob -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-i localhost:8000/jobs/20121130104633606931 -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -GET /jobs/20121130104633606931 HTTP/1.1 -Host: localhost:8000 -Accept: application/x\-yaml -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Length: 73 -Content\-Type: application/x\-yaml - -info: -\- Arguments: - \- \(aq3\(aq - Function: test.fib - Minions: - \- jerry - Start Time: 2012, Nov 30 10:46:33.606931 - Target: \(aq*\(aq - Target\-type: glob - User: saltdev - jid: \(aq20121130104633606931\(aq -return: -\- jerry: - \- \- 0 - \- 1 - \- 1 - \- 2 - \- 6.9141387939453125e\-06 -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Parameters -\fBmid\fP \-\- (optional) a minion id -.TP -.B Status 200 -success -.TP -.B Status 401 -authentication required -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Run -.INDENT 7.0 -.TP -.B POST(**kwargs) -Run commands bypassing the normal session handling -.sp -New in version 0.8.0. - -.INDENT 7.0 -.TP -.B 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 (\fB/\fP) execution URL. -.sp -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS localhost:8000/run \e - \-H \(aqAccept: application/x\-yaml\(aq \e - \-d client=\(aqlocal\(aq \e - \-d tgt=\(aq*\(aq \e - \-d fun=\(aqtest.ping\(aq \e - \-d username=\(aqsaltdev\(aq \e - \-d password=\(aqsaltdev\(aq \e - \-d eauth=\(aqpam\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -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 -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -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 -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Form lowstate -A list of \fIlowstate\fP data appropriate for the -\fI\%client\fP specified client interface. Full -external authentication credentials must be included. -.TP -.B Status 200 -success -.TP -.B Status 401 -authentication failed -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Events -The event bus on the Salt master exposes a large variety of things, notably -when executions are started on the master and also when minions ultimately -return their results. This URL provides a real\-time window into a running -Salt infrastructure. -.INDENT 7.0 -.TP -.B GET(token=None) -Return an HTTP stream of the Salt master event bus; this stream is -formatted per the Server Sent Events (SSE) spec -.sp -New in version 0.8.3. - -.sp -Browser clients currently lack Cross\-origin resource sharing (CORS) -support for the \fBEventSource()\fP API. Cross\-domain requests from a -browser may instead pass the \fIX\-Auth\-Token\fP value as an URL -parameter: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-NsS localhost:8000/events/6d1b722e -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B GET /events -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-NsS localhost:8000/events -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -GET /events HTTP/1.1 -Host: localhost:8000 -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Connection: keep\-alive -Cache\-Control: no\-cache -Content\-Type: text/event\-stream;charset=utf\-8 - -retry: 400 -data: {\(aqtag\(aq: \(aq\(aq, \(aqdata\(aq: {\(aqminions\(aq: [\(aqms\-4\(aq, \(aqms\-3\(aq, \(aqms\-2\(aq, \(aqms\-1\(aq, \(aqms\-0\(aq]}} - -data: {\(aqtag\(aq: \(aq20130802115730568475\(aq, \(aqdata\(aq: {\(aqjid\(aq: \(aq20130802115730568475\(aq, \(aqreturn\(aq: True, \(aqretcode\(aq: 0, \(aqsuccess\(aq: True, \(aqcmd\(aq: \(aq_return\(aq, \(aqfun\(aq: \(aqtest.ping\(aq, \(aqid\(aq: \(aqms\-1\(aq}} -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.sp -The event stream can be easily consumed via JavaScript: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -# Note, you must be authenticated! -var source = new EventSource(\(aq/events\(aq); -source.onopen = function() { console.debug(\(aqopening\(aq) }; -source.onerror = function(e) { console.debug(\(aqerror!\(aq, e) }; -source.onmessage = function(e) { console.debug(e.data) }; -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -It is also possible to consume the stream via the shell. -.sp -Records are separated by blank lines; the \fBdata:\fP and \fBtag:\fP -prefixes will need to be removed manually before attempting to -unserialize the JSON. -.sp -curl\(aqs \fB\-N\fP flag turns off input buffering which is required to -process the stream incrementally. -.sp -Here is a basic example of printing each event as it comes in: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-NsS localhost:8000/events |\e - while IFS= read \-r line ; do - echo $line - done -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -Here is an example of using awk to filter events based on tag: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-NsS localhost:8000/events |\e - awk \(aq - BEGIN { RS=""; FS="\en" } - $1 ~ /^tag: salt\e/job\e/[0\-9]+\e/new$/ { print $0 } - \(aq -tag: salt/job/20140112010149808995/new -data: {"tag": "salt/job/20140112010149808995/new", "data": {"tgt_type": "glob", "jid": "20140112010149808995", "tgt": "jerry", "_stamp": "2014\-01\-12_01:01:49.809617", "user": "shouse", "arg": [], "fun": "test.ping", "minions": ["jerry"]}} -tag: 20140112010149808995 -data: {"tag": "20140112010149808995", "data": {"fun_args": [], "jid": "20140112010149808995", "return": true, "retcode": 0, "success": true, "cmd": "_return", "_stamp": "2014\-01\-12_01:01:49.819316", "fun": "test.ping", "id": "jerry"}} -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Status 200 -success -.TP -.B Status 401 -could not authenticate using provided credentials -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Webhook -A generic web hook entry point that fires an event on Salt\(aqs event bus -.sp -External services can POST data to this URL to trigger an event in Salt. -For example, Jenkins\-CI or Travis\-CI, or GitHub web hooks. -.sp -This entry point does not require authentication. The event data is taken -from the request body. -.sp -\fBNOTE:\fP -.INDENT 7.0 -.INDENT 3.5 -Be mindful of security -.sp -Salt\(aqs Reactor can run any code. If you write a Reactor SLS that -responds to a hook event be sure to validate that the event came from a -trusted source and contains valid data! Pass a secret key and use SSL. -.sp -This is a generic interface and securing it is up to you! -.UNINDENT -.UNINDENT -.sp -The event tag is prefixed with \fBsalt/netapi/hook\fP and the URL path is -appended to the end. For example, a \fBPOST\fP request sent to -\fB/hook/mycompany/myapp/mydata\fP will produce a Salt event with the tag -\fBsalt/netapi/hook/mycompany/myapp/mydata\fP\&. See the \fI\%Salt Reactor\fP documentation for how to react to events with various tags. -.sp -The following is an example \fB\&.travis.yml\fP file to send notifications to -Salt of successful test runs: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -language: python -script: python \-m unittest tests -after_success: - \- \(aqcurl \-sS http://saltapi\-url.example.com:8000/hook/travis/build/success \-d branch="${TRAVIS_BRANCH}" \-d commit="${TRAVIS_COMMIT}"\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B POST(*args, **kwargs) -Fire an event in Salt with a custom event tag and data -.sp -New in version 0.8.4. - -.INDENT 7.0 -.TP -.B POST /hook -\fBExample request\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS localhost:8000/hook \-d foo=\(aqFoo!\(aq \-d bar=\(aqBar!\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -POST /hook HTTP/1.1 -Host: localhost:8000 -Content\-Length: 16 -Content\-Type: application/x\-www\-form\-urlencoded - -foo=Foo&bar=Bar! -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.1 200 OK -Content\-Length: 14 -Content\-Type: application/json - -{"success": true} -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.sp -As a practical example, an internal continuous\-integration build -server could send an HTTP POST request to the URL -\fBhttp://localhost:8000/hook/mycompany/build/success\fP which contains -the result of a build and the SHA of the version that was built as -JSON. That would then produce the following event in Salt that could be -used to kick off a deployment via Salt\(aqs Reactor: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -Event fired at Fri Feb 14 17:40:11 2014 -************************* -Tag: salt/netapi/hook/mycompany/build/success -Data: -{\(aq_stamp\(aq: \(aq2014\-02\-14_17:40:11.440996\(aq, - \(aqheaders\(aq: { - \(aqX\-My\-Secret\-Key\(aq: \(aqF0fAgoQjIT@W\(aq, - \(aqContent\-Length\(aq: \(aq37\(aq, - \(aqContent\-Type\(aq: \(aqapplication/json\(aq, - \(aqHost\(aq: \(aqlocalhost:8000\(aq, - \(aqRemote\-Addr\(aq: \(aq127.0.0.1\(aq}, - \(aqpost\(aq: {\(aqrevision\(aq: \(aqaa22a3c4b2e7\(aq, \(aqresult\(aq: True}} -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -Salt\(aqs Reactor could listen for the event: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -reactor: - \- \(aqsalt/netapi/hook/mycompany/build/*\(aq: - \- /srv/reactor/react_ci_builds.sls -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -And finally deploy the new build: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -{% set secret_key = data.get(\(aqheaders\(aq, {}).get(\(aqX\-My\-Secret\-Key\(aq) %} -{% set build = data.get(\(aqpost\(aq, {}) %} - -{% if secret_key == \(aqF0fAgoQjIT@W\(aq and build.result == True %} -deploy_my_app: - cmd.state.sls: - \- tgt: \(aqapplication*\(aq - \- arg: - \- myapp.deploy - \- \(aqpillar={revision: {{ revision }}}\(aq -{% endif %} -.ft P -.fi -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B Status 200 -success -.TP -.B Status 406 -requested Content\-Type not available -.TP -.B Status 413 -request body is too large -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B class saltapi.netapi.rest_cherrypy.app.Stats -Expose statistics on the running CherryPy server -.INDENT 7.0 -.TP -.B GET() -Return a dump of statistics collected from the CherryPy server -.INDENT 7.0 -.TP -.B Status 200 -success -.TP -.B Status 406 -requested Content\-Type not available -.UNINDENT -.UNINDENT -.UNINDENT -.SS rest_wsgi -.SS A minimalist REST API for Salt -.sp -This \fBrest_wsgi\fP module provides a no\-frills REST interface to a running Salt -master. There are no dependencies. -.sp -Please read this introductory section in entirety before deploying this module. -.INDENT 0.0 -.TP -.B configuration -All authentication is done through Salt\(aqs \fI\%external auth\fP system. Be sure that it is enabled and the user you are -authenticating as has permissions for all the functions you will be -running. -.sp -The configuration options for this module resides in the Salt master config -file. All available options are detailed below. -.INDENT 7.0 -.TP -.B port -\fBRequired\fP -.sp -The port for the webserver to listen on. -.UNINDENT -.sp -Example configuration: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -rest_wsgi: - port: 8001 -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.sp -This API is not very "RESTful"; please note the following: -.INDENT 0.0 -.IP \(bu 2 -All requests must be sent to the root URL (\fB/\fP). -.IP \(bu 2 -All requests must be sent as a POST request with JSON content in the request -body. -.IP \(bu 2 -All responses are in JSON. -.UNINDENT -.sp -\fBSEE ALSO:\fP -.INDENT 0.0 -.INDENT 3.5 -\fBrest_cherrypy\fP -.sp -The \fBrest_cherrypy\fP module is -more full\-featured, production\-ready, and has builtin security features. -.UNINDENT -.UNINDENT -.SS Deployment -.sp -The \fBrest_wsgi\fP netapi module is a standard Python WSGI app. It can be -deployed one of two ways. -.SS \fBsalt\-api\fP using a development\-only server -.sp -If run directly via salt\-api it uses the \fI\%wsgiref.simple_server()\fP that ships -in the Python standard library. This is a single\-threaded server that is -intended for testing and development. This server does \fBnot\fP use encryption; -please note that raw Salt authentication credentials must be sent with every -HTTP request. -.sp -\fBRunning this module via salt\-api is not recommended for most use!\fP -.SS Using a WSGI\-compliant web server -.sp -This module may be run via any WSGI\-compliant production server such as Apache -with mod_wsgi or Nginx with FastCGI. -.sp -It is highly recommended that this app be used with a server that supports -HTTPS encryption since raw Salt authentication credentials must be sent with -every request. Any apps that access Salt through this interface will need to -manually manage authentication credentials (either username and password or a -Salt token). Tread carefully. -.SS Usage examples -.INDENT 0.0 -.TP -.B POST / -\fBExample request\fP for a basic \fBtest.ping\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS \-i \e - \-H \(aqContent\-Type: application/json\(aq \e - \-d \(aq[{"eauth":"pam","username":"saltdev","password":"saltdev","client":"local","tgt":"*","fun":"test.ping"}]\(aq localhost:8001 -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.0 200 OK -Content\-Length: 89 -Content\-Type: application/json - -{"return": [{"ms\-\-4": true, "ms\-\-3": true, "ms\-\-2": true, "ms\-\-1": true, "ms\-\-0": true}]} -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample request\fP for an asyncronous \fBtest.ping\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS \-i \e - \-H \(aqContent\-Type: application/json\(aq \e - \-d \(aq[{"eauth":"pam","username":"saltdev","password":"saltdev","client":"local_async","tgt":"*","fun":"test.ping"}]\(aq localhost:8001 -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.0 200 OK -Content\-Length: 103 -Content\-Type: application/json - -{"return": [{"jid": "20130412192112593739", "minions": ["ms\-\-4", "ms\-\-3", "ms\-\-2", "ms\-\-1", "ms\-\-0"]}]} -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample request\fP for looking up a job ID: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS \-i \e - \-H \(aqContent\-Type: application/json\(aq \e - \-d \(aq[{"eauth":"pam","username":"saltdev","password":"saltdev","client":"runner","fun":"jobs.lookup_jid","jid":"20130412192112593739"}]\(aq localhost:8001 -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -\fBExample response\fP: -.INDENT 7.0 -.INDENT 3.5 -.sp -.nf -.ft C -HTTP/1.0 200 OK -Content\-Length: 89 -Content\-Type: application/json - -{"return": [{"ms\-\-4": true, "ms\-\-3": true, "ms\-\-2": true, "ms\-\-1": true, "ms\-\-0": true}]} -.ft P -.fi -.UNINDENT -.UNINDENT -.UNINDENT -.INDENT 0.0 -.TP -.B form lowstate -A list of \fIlowstate\fP data appropriate for the -\fI\%client\fP interface you are calling. -.TP -.B status 200 -success -.TP -.B status 401 -authentication required -.UNINDENT -.SS \fBnetapi\fP developer reference -.SS Introduction to netapi modules -.sp -netapi modules generally bind to a port and start a service. They are -purposefully open\-ended. There could be multiple netapi modules that provide a -REST interface, a module that provides an XMPP interface, or Websockets, or -XMLRPC. -.sp -netapi modules are enabled by adding configuration to your master config file. -Check the docs for each module to see external requirements and configuration -settings. -.sp -Communication with Salt and Salt satellite projects is done by passing a list of -lowstate dictionaries to a client interface. A list of available client -interfaces is below. The lowstate dictionary items map to keyword arguments on -the client interface. -.sp -\fBSEE ALSO:\fP -.INDENT 0.0 -.INDENT 3.5 -\fI\%Python client API\fP -.UNINDENT -.UNINDENT -.SS Client interfaces -.INDENT 0.0 -.TP -.B class saltapi.APIClient(opts) -Provide a uniform method of accessing the various client interfaces in Salt -in the form of low\-data data structures. For example: -.sp -.nf -.ft C ->>> client = APIClient(__opts__) ->>> lowstate = {\(aqclient\(aq: \(aqlocal\(aq, \(aqtgt\(aq: \(aq*\(aq, \(aqfun\(aq: \(aqtest.ping\(aq, \(aqarg\(aq: \(aq\(aq} ->>> client.run(lowstate) -.ft P -.fi -.INDENT 7.0 -.TP -.B local(*args, **kwargs) -Run \fI\%execution modules\fP syncronously -.sp -Wraps \fI\%salt.client.LocalClient.cmd()\fP\&. -.INDENT 7.0 -.TP -.B Returns -Returns the result from the execution module -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B local_async(*args, **kwargs) -Run \fI\%execution modules\fP asyncronously -.sp -Wraps \fI\%salt.client.LocalClient.run_job()\fP\&. -.INDENT 7.0 -.TP -.B Returns -job ID -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B local_batch(*args, **kwargs) -Run \fI\%execution modules\fP against batches of minions -.sp -New in version 0.8.4. - -.sp -Wraps \fI\%salt.client.LocalClient.cmd_batch()\fP -.INDENT 7.0 -.TP -.B Returns -Returns the result from the exeuction module for each batch of -returns -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B runner(fun, **kwargs) -Run \fIrunner modules \fP -.sp -Wraps \fI\%salt.runner.RunnerClient.low()\fP\&. -.INDENT 7.0 -.TP -.B Returns -Returns the result from the runner module -.UNINDENT -.UNINDENT -.INDENT 7.0 -.TP -.B wheel(fun, **kwargs) -Run \fI\%wheel modules\fP -.sp -Wraps \fI\%salt.wheel.WheelClient.master_call()\fP\&. -.INDENT 7.0 -.TP -.B Returns -Returns the result from the wheel module -.UNINDENT -.UNINDENT -.UNINDENT -.SS Writing netapi modules -.sp -\fBnetapi\fP modules, put simply, bind a port and start a service. -They are purposefully open\-ended and can be used to present a variety of -external interfaces to Salt, and even present multiple interfaces at once. -.sp -\fBSEE ALSO:\fP -.INDENT 0.0 -.INDENT 3.5 -\fIThe full list of netapi modules\fP -.UNINDENT -.UNINDENT -.SS Configuration -.sp -All \fBnetapi\fP configuration is done in the \fI\%Salt master -config\fP and takes a form similar to the following: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -rest_cherrypy: - port: 8000 - debug: True - ssl_crt: /etc/pki/tls/certs/localhost.crt - ssl_key: /etc/pki/tls/certs/localhost.key -.ft P -.fi -.UNINDENT -.UNINDENT -.SS The \fB__virtual__\fP function -.sp -Like all module types in Salt, \fBnetapi\fP modules go through -Salt\(aqs loader interface to determine if they should be loaded into memory and -then executed. -.sp -The \fB__virtual__\fP function in the module makes this determination and should -return \fBFalse\fP or a string that will serve as the name of the module. If the -module raises an \fBImportError\fP or any other errors, it will not be loaded. -.SS The \fBstart\fP function -.sp -The \fBstart()\fP function will be called for each \fBnetapi\fP -module that is loaded. This function should contain the server loop that -actually starts the service. This is started in a multiprocess. -.SS Inline documentation -.sp -As with the rest of Salt, it is a best\-practice to include liberal inline -documentation in the form of a module docstring and docstrings on any classes, -methods, and functions in your \fBnetapi\fP module. -.SS Loader “magic” methods -.sp -The loader makes the \fB__opts__\fP data structure available to any function in -a \fBnetapi\fP module. -.SH RELEASES -.SS Release notes -.SS salt\-api 0.5.0 -.sp -\fBsalt\-api\fP is gearing up for the initial public release with 0.5.0. -Although this release ships with working basic functionality it is awaiting the -authentication backend that will be introduced in Salt 0.10.4 before it can be -considered ready for testing at large. -.SS REST API -.sp -This release presents the flagship netapi module which provides a RESTful -interface to a running Salt system. It allows for viewing minions, runners, and -jobs as well as running execution modules and runners of a running Salt system -through a REST API that returns JSON. -.SS Participation -.sp -\fBsalt\-api\fP is just getting off the ground so feedback, questions, and -ideas are critical as we solidify how this project fits into the overall Salt -infrastructure management stack. Please get involved by \fI\%filing issues\fP on -GitHub, \fI\%discussing on the mailing list\fP, and chatting in \fB#salt\fP on -Freenode. -.SS salt\-api 0.6.0 -.sp -\fBsalt\-api\fP inches closer to prime\-time with 0.6.0. This release adds -the beginnings of a universal interface for accessing Salt components via the -tried and true method of passing low\-data to functions (a core component of -Salt\(aqs remote execution and state management). -.SS Low\-data interface -.sp -A new view accepts :\fI\%http:post\fP: requests at the root URL that accepts raw -low\-data as :\fI\%http:post\fP: data and passes that low\-data along to a client -interface in Salt. Currently only LocalClient and RunnerClient interfaces have -been implemented in Salt with more coming in the next Salt release. -.SS External authentication -.sp -Raw low\-data can contain authentication credentials that make use of Salt\(aqs new -\fI\%external_auth\fP system. -.sp -The following is a proof\-of\-concept of a working eauth call. (It bears -repeating this is a pre\-alpha release and this should not be used by anyone for -anything real.) -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-si localhost:8000 \e - \-d client=local \e - \-d tgt=\(aq*\(aq \e - \-d fun=\(aqtest.ping\(aq \e - \-d arg \e - \-d eauth=pam \e - \-d username=saltdev \e - \-d password=saltdev -.ft P -.fi -.UNINDENT -.UNINDENT -.SS Participation -.sp -\fBsalt\-api\fP is just getting off the ground so feedback, questions, and -ideas are critical as we solidify how this project fits into the overall Salt -infrastructure management stack. Please get involved by \fI\%filing issues\fP on -GitHub, \fI\%discussing on the mailing list\fP, and chatting in \fB#salt\-devel\fP on -Freenode. -.SS salt\-api 0.7.0 -.sp -\fBsalt\-api\fP is ready for alpha\-testing in the real world. This release -solidifies how \fBsalt\-api\fP will communicate with the larger Salt -ecosystem. In addition authentication and encryption (via SSL) have been added. -.sp -The first netapi module was a proof of concept written in Flask. It was quite -useful to be able to quickly hammer out a URL structure and solidify on an -interface for programmatically calling out to Salt components. As of this -release that module has been deprecated and removed in favor of a netapi module -written in CherryPy. CherryPy affords tremendous flexibility when composing a -REST interface and will present a stable platform for building out a very -adaptable and featureful REST API—also we\(aqre using the excellent and fast -CherryPy webserver for securely serving the API. -.SS Low\-data interface -.sp -The last release introduced a proof\-of\-concept for how the various Salt -components will communicate with each other. This is done by passing a data -structure to a client interface. This release expands on that. There are -currently three client interfaces in Salt. -.sp -\fBSEE ALSO:\fP -.INDENT 0.0 -.INDENT 3.5 -\fInetapi\-introduction\fP -.UNINDENT -.UNINDENT -.SS Encryption and authentication -.sp -Encryption has been added via SSL. You can supply an existing certificate or -generate a self\-signed certificate through Salt\(aqs \fI\%tls\fP -module. -.sp -Authentication is performed through Salt\(aqs incredibly flexible \fI\%external -auth\fP system and is maintained when accessing the API via session -tokens. -.SS Participation -.sp -\fBsalt\-api\fP is just getting off the ground so feedback, questions, and -ideas are critical as we solidify how this project fits into the overall Salt -infrastructure management stack. Please get involved by \fI\%filing issues\fP on -GitHub, \fI\%discussing on the mailing list\fP, and chatting in \fB#salt\-devel\fP on -Freenode. -.SS salt\-api 0.7.5 -.sp -This release is a mostly a minor release to pave a better path for -\fBsalt\-ui\fP though there are some small feature additions and bugfixes. -.SS Changes -.INDENT 0.0 -.IP \(bu 2 -Convenience URLs \fB/minions\fP and \fB/jobs\fP have been added as well as a -async client wrapper. This starts a job and immediately returns the job ID, -allowing you to fetch the result of that job at a later time. -.IP \(bu 2 -The return format will now default to JSON if no specific format is -requested. -.IP \(bu 2 -A new setting \fBstatic\fP has been added that will serve any static media from -the directory specified. In addition if an \fBindex.html\fP file is found -in that directory and the \fBAccept\fP header in the request prefer HTML that -file will be served. -.IP \(bu 2 -All HTML, including the login form, has been removed from \fBsalt\-api\fP -and moved into the \fBsalt\-ui\fP project. -.IP \(bu 2 -Sessions now live as long as the Salt token. -.UNINDENT -.SS Participation -.sp -\fBsalt\-api\fP is just getting off the ground so feedback, questions, and -ideas are critical as we solidify how this project fits into the overall Salt -infrastructure management stack. Please get involved by \fI\%filing issues\fP on -GitHub, \fI\%discussing on the mailing list\fP, and chatting in \fB#salt\-devel\fP on -Freenode. -.SS salt\-api 0.8.0 -.sp -We are happy to announce the release of \fBsalt\-api\fP 0.8.0. -.sp -This release encompasses bugfixes and new features for the -\fBrest_cherrypy\fP netapi module that -provides a RESTful interface for a running Salt system. -.sp -\fBNOTE:\fP -.INDENT 0.0 -.INDENT 3.5 -Requires Salt 0.13 -.UNINDENT -.UNINDENT -.SS Changes -.sp -In addition to the usual documentation improvements and bug fixes this release -introduces the following changes and additions. -.sp -Please note the backward incompatible change detailed below. -.SS RPM packaging -.sp -Thanks to Andrew Niemantsvedriet (\fI\%@kaptk2\fP) \fBsalt\-api\fP is now -available in Fedora package repositories as well as RHEL compatible systems via -EPEL. -.INDENT 0.0 -.IP \(bu 2 -\fI\%http://dl.fedoraproject.org/pub/epel/5/i386/repoview/salt\-api.html\fP -.IP \(bu 2 -\fI\%http://dl.fedoraproject.org/pub/epel/5/x86_64/repoview/salt\-api.html\fP -.IP \(bu 2 -\fI\%http://dl.fedoraproject.org/pub/epel/6/i386/repoview/salt\-api.html\fP -.IP \(bu 2 -\fI\%http://dl.fedoraproject.org/pub/epel/6/x86_64/repoview/salt\-api.html\fP -.UNINDENT -.sp -Thanks also to Clint Savage (\fI\%@herlo\fP) and Thomas Spura (\fI\%@tomspur\fP) for -helping with that process. -.SS Ubuntu PPA packaging -.sp -Thanks to Sean Channel (\fI\%@seanchannel\fP, pentabular) \fBsalt\-api\fP is -available as a PPA on the SaltStack LaunchPad team. -.sp -\fI\%https://launchpad.net/~saltstack/+archive/salt\fP -.SS Authentication information on login -.sp -\fBWARNING:\fP -.INDENT 0.0 -.INDENT 3.5 -Backward incompatible change -.sp -The \fB/login\fP URL no -longer responds with a 302 redirect for success. -.sp -Although this is behavior is common in the browser world it is not useful -from an API so we have changed it to return a 200 response in this release. -.sp -We take backward compatibility very seriously and we apologize for the -inconvenience. In this case we felt the previous behavior was limiting. -Changes such as this will be rare. -.UNINDENT -.UNINDENT -.sp -New in this release is displaying information about the current session and the -current user. For example: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS localhost:8000/login \e - \-H \(aqAccept: application/x\-yaml\(aq - \-d username=\(aqsaltdev\(aq - \-d password=\(aqsaltdev\(aq - \-d eauth=\(aqpam\(aq - -return: -\- eauth: pam - expire: 1365508324.359403 - perms: - \- \(aq@wheel\(aq - \- grains.* - \- state.* - \- status.* - \- sys.* - \- test.* - start: 1365465124.359402 - token: caa7aa2b9dbc4a8adb6d2e19c3e52be68995ef4b - user: saltdev -.ft P -.fi -.UNINDENT -.UNINDENT -.SS Bypass session handling -.sp -A convenience URL has been added -(\fB/run\fP) to bypass the normal -session\-handling process. -.sp -The REST interface uses the concept of "lowstate" data to specify what function -should be executed in Salt (plus where that function is and any arguments to -the function). This is a thin wrapper around Salt\(aqs various "client" -interfaces, for example Salt\(aqs \fI\%LocalClient()\fP which can -accept authentication credentials directly. -.sp -Authentication with the REST API typically goes through the login URL and a -session is generated that is tied to a Salt external_auth token. That token is -then automatically added to the lowstate for subsequent requests that match the -current session. -.sp -It is sometimes useful to handle authentication or token management manually -from another program or script. For example: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -curl \-sS localhost:8000/run \e - \-d client=\(aqlocal\(aq \e - \-d tgt=\(aq*\(aq \e - \-d fun=\(aqtest.ping\(aq \e - \-d eauth=\(aqpam\(aq \e - \-d username=\(aqsaltdev\(aq \e - \-d password=\(aqsaltdev\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -It is a Bad Idea (TM) to do this unless you have a very good reason and a well -thought out security model. -.SS Logout -.sp -An URL has been added -(\fB/logout\fP) that will cause -the client\-side to expire the session cookie and the server\-side session to be -invalidated. -.SS Running the REST interface via any WSGI\-compliant server -.sp -The \fBrest_cherrypy\fP netapi module is -a regular WSGI application written using the CherryPy framework. It was written -with the intent of also running from any WSGI\-compliant server such as Apache -and mod_wsgi, Gunicorn, uWSGI, Nginx and FastCGI, etc. -.sp -The WSGI application entry point has been factored out into a stand\-alone file -in this release suitable for calling from an external server. -\fBsalt\-api\fP does not need to be running in this scenario. -.sp -For example, an Apache virtual host configuration: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C - - ServerName example.com - ServerAlias *.example.com - - ServerAdmin webmaster@example.com - - LogLevel warn - ErrorLog /var/www/example.com/logs/error.log - CustomLog /var/www/example.com/logs/access.log combined - - DocumentRoot /var/www/example.com/htdocs - - WSGIScriptAlias / /path/to/saltapi/netapi/rest_cherrypy/wsgi.py - -.ft P -.fi -.UNINDENT -.UNINDENT -.SS Participation -.sp -Please get involved by \fI\%filing issues\fP on GitHub, \fI\%discussing on the mailing -list\fP, and chatting in \fB#salt\-devel\fP on Freenode. -.SS salt\-api 0.8.2 -.sp -\fBsalt\-api\fP 0.8.2 is largely a bugfix release that fixes a -compatibility issue with changes in Salt 0.15.9. -.sp -\fBNOTE:\fP -.INDENT 0.0 -.INDENT 3.5 -Requires Salt 0.15.9 or greater -.UNINDENT -.UNINDENT -.sp -The following changes have been made to the \fBrest_cherrypy\fP netapi module that provides a RESTful -interface for a running Salt system: -.INDENT 0.0 -.IP \(bu 2 -Fixed issue #87 which caused the Salt master\(aqs PID file to be overwritten. -.IP \(bu 2 -Fixed an inconsistency with the return format for the \fB/minions\fP -convenience URL. -.sp -\fBWARNING:\fP -.INDENT 2.0 -.INDENT 3.5 -This is a backward incompatible change. -.UNINDENT -.UNINDENT -.IP \(bu 2 -Added a dedicated URL for serving an HTML app -.IP \(bu 2 -Added dedicated URL for serving static media -.UNINDENT -.SS salt\-api 0.8.3 -.sp -\fBsalt\-api\fP 0.8.3 is a small release largely concerning changes and -fixes to the \fBrest_cherrypy\fP netapi -module. -.sp -This release will likely be the final salt\-api release as a separate project. -The Salt team has begun the process of merging this project directly in to the -main Salt project. What this means for end users is only that there will be one -fewer package to install. Salt itself will ship with the current \fBnetapi\fP -modules and the API and configuration will remain otherwise unchanged. -.sp -The reasoning behind merging the two projects is simply to lower the barrier to -entry. Having a separate project was useful for experimentation and exploration -but there was no technical reason for the separation \-\- salt\-api uses the same -flexible module system that Salt uses and those modules will simply be moved -into Salt. -.sp -Going forward, Salt will ship with the same REST interface that salt\-api -currently provides. This will have the side benefit of not having to coordinate -incompatible Salt and salt\-api releases. -.SS \fBrest_cherrypy\fP changes -.sp -An HTTP stream of Salt\(aqs event bus has been added. This stream conforms to the -SSE (Server Sent Events) spec and is easily consumed via JavaScript clients. -This HTTP stream allows a real\-time window into a running Salt system. A client -watching the stream can see as soon as individual minions return data for a -job, authentication events, and any other events that go through the Salt -master. -.sp -A new configuration option to only allow access to whitelisted IP addresses. Of -course, IP addresses can be easily spoofed so this feature should be thought of -as a usability addition and not used for security purposes. -.sp -An option to disable SSL has been added. Previously SSL could only be disabled -while running the HTTP server with debugging options enabled. Now each item can -be enabled or disabled independently of the other. -.sp -In addition, there has been several bug fixes, packaging fixes, and minor code -simplification. -.SS salt\-api 0.8.4 -.sp -\fBsalt\-api\fP 0.8.4 sees a number of new features and feature -enhancements in the \fBrest_cherrypy\fP -netapi module. -.sp -Work to merge \fBsalt\-api\fP into the main Salt distribution continues and -it is likely to be included in Salt\(aqs Helium release. -.SS \fBrest_cherrypy\fP changes -.SS Web hooks -.sp -This release adds a \fBnew URL /hook\fP that allows salt\-api to serve as a -generic web hook interface for Salt. POST requests to the URL trigger events on -Salt\(aqs event bus. -.sp -External services like Amazon SNS, Travis CI, GitHub, etc can easily send -signals through Salt\(aqs Reactor. -.sp -The following HTTP call will trigger the following Salt event. -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -% curl \-sS http://localhost:8000/hook/some/tag \e - \-d some=\(aqData!\(aq -.ft P -.fi -.UNINDENT -.UNINDENT -.sp -Event tag: \fBsalt/netapi/hook/some/tag\fP\&. Event data: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -{ - "_stamp": "2014\-04\-04T12:14:54.389614", - "post": { - "some": "Data!" - }, - "headers": { - "Content\-Type": "application/x\-www\-form\-urlencoded", - "Host": "localhost:8000", - "User\-Agent": "curl/7.32.0", - "Accept": "*/*", - "Content\-Length": "10", - "Remote\-Addr": "127.0.0.1" - } -} -.ft P -.fi -.UNINDENT -.UNINDENT -.SS Batch mode -.sp -The \fBlocal_batch()\fP client exposes Salt\(aqs batch mode -for executing commands on incremental subsets of minions. -.SS Tests! -.sp -We have added the necessary framework for testing the rest_cherrypy module and -this release includes a number of both unit and integration tests. The suite -can be run with the following command: -.INDENT 0.0 -.INDENT 3.5 -.sp -.nf -.ft C -python \-m unittest discover \-v -.ft P -.fi -.UNINDENT -.UNINDENT -.SS CherryPy server stats and configuration -.sp -A number of settings have been added to better configure the performance of the -CherryPy web server. In addition, a \fBnew URL /stats\fP has been added to expose metrics on -the health of the CherryPy web server. -.SS Improvements for running with external WSGI servers -.sp -Running the \fBrest_cherrypy\fP module via a WSGI\-capable server such as Apache -or Nginx can be tricky since the user the server is running as must have -permission to access the running Salt system. This release eases some of those -restrictions by accessing Salt\(aqs key interface through the external auth -system. Read access to the Salt configuration is required for the user the -server is running as and everything else should go through external auth. -.SS More information in the jobs URLs -.sp -The output for the \fB/jobs/\fP has been augmented with more -information about the job such as which minions are expected to return for that -job. This same output will be added to the other salt\-api URLs in the next -release. -.SS Improvements to the Server Sent Events stream -.sp -Event tags have been added to \fBthe HTTP event stream\fP as SSE tags which allows JavaScript -or other consumers to more easily match on certain tags without having to -inspect the whole event. -.SH REFERENCE -.INDENT 0.0 -.IP \(bu 2 -\fIgenindex\fP -.IP \(bu 2 -\fImodindex\fP -.IP \(bu 2 -\fIsearch\fP -.IP \(bu 2 -\fIglossary\fP -.UNINDENT -.SH AUTHOR -Thomas S. Hatch and many others, please see the Authors file -.SH COPYRIGHT -2012, Thomas S. Hatch -.\" Generated by docutils manpage writer. -. diff --git a/doc/ref/netapi/all/salt.netapi.rest_tornado.rst b/doc/ref/netapi/all/salt.netapi.rest_tornado.rst index 5ee57bbae1..cd65ea4ff3 100644 --- a/doc/ref/netapi/all/salt.netapi.rest_tornado.rst +++ b/doc/ref/netapi/all/salt.netapi.rest_tornado.rst @@ -4,4 +4,6 @@ rest_tornado .. automodule:: salt.netapi.rest_tornado.saltnado +.. automodule:: salt.netapi.rest_tornado.saltnado_websockets + .. ............................................................................ diff --git a/doc/topics/releases/index.rst b/doc/topics/releases/index.rst index 48b411a1a0..2d58d89776 100644 --- a/doc/topics/releases/index.rst +++ b/doc/topics/releases/index.rst @@ -5,7 +5,7 @@ Release notes .. releasestree:: :maxdepth: 1 - 2014.1.4 + 2014.1.5 Archive ======= diff --git a/pkg/shar/build_shar.sh b/pkg/shar/build_shar.sh index 083911cc2e..ac2ac860d9 100755 --- a/pkg/shar/build_shar.sh +++ b/pkg/shar/build_shar.sh @@ -13,6 +13,10 @@ # # It will fetch libzmq and build it as a pyzmq extension. # +# IMPORTANT: Unpacking the shar requires uudecode, which is distributed along +# with sharutils. Thus, you should have sharutils installed on any host which +# will need to unpack the shar archive. +# # The script is capable of building a shar archive using several methods: # # 1. Using a custom pip requirements file @@ -27,8 +31,10 @@ # option can be used to specify directory from which dependencies will be # sourced. Any missing dependencies will be retrieved with pip. # -# It is recommended to run this script on a machine which does not have any of -# the Salt dependencies already installed. +# It is strongly recommended to run this script on a machine which does not +# have any of the Salt dependencies already installed, because if the script +# detects that ZeroMQ is already installed, then pyzmq's setup.py will not +# build a bundled ZeroMQ. # # Run the script with -h for usage details. # diff --git a/pkg/smartos/salt-minion.xml b/pkg/smartos/salt-minion.xml index 1373ffcff0..619276dc1e 100644 --- a/pkg/smartos/salt-minion.xml +++ b/pkg/smartos/salt-minion.xml @@ -31,7 +31,12 @@ - + + + + + `_ for example. -Or the tornado -`client `_. - -.. code-block:: python - - # Note, you must be authenticated! - - from websocket import create_connection - - # Get the Websocket connection to Salt - ws = create_connection('wss://localhost:8000/all_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7') - - # Get Salt's "real time" event stream. - ws.send('websocket client ready') - - - # Simple listener to print results of Salt's "real time" event stream. - # Look at https://pypi.python.org/pypi/websocket-client/ for more examples. - while listening_to_events: - print ws.recv() # Salt's "real time" event data as serialized JSON. - - # Terminates websocket connection and Salt's "real time" event stream on the server. - ws.close() - - # Please refer to https://github.com/liris/websocket-client/issues/81 when using a self signed cert - -Above examples show how to establish a websocket connection to Salt and activating -real time updates from Salt's event stream by signaling ``websocket client ready``. - - -Formatted Events ------------------ - -Exposes ``formatted`` "real-time" events from Salt's event bus on a websocket connection. -It should be noted that "Real-time" here means these events are made available -to the server as soon as any salt related action (changes to minions, new jobs etc) happens. -Clients are however assumed to be able to tolerate any network transport related latencies. -Functionality provided by this endpoint is similar to the ``/events`` end point. - -The event bus on the Salt master exposes a large variety of things, notably -when executions are started on the master and also when minions ultimately -return their results. This URL provides a real-time window into a running -Salt infrastructure. Uses websocket as the transport mechanism. - -Formatted events parses the raw "real time" event stream and maintains -a current view of the following: - -- minions -- jobs - -A change to the minions (such as addition, removal of keys or connection drops) -or jobs is processed and clients are updated. -Since we use salt's presence events to track minions, -please enable ``presence_events`` -and set a small value for the ``loop_interval`` -in the salt master config file. - -Exposes GET method to return websocket connections. -All requests should include an auth token. -A way to obtain obtain authentication tokens is shown below. - -.. code-block:: bash - - % curl -si localhost:8000/login \\ - -H "Accept: application/json" \\ - -d username='salt' \\ - -d password='salt' \\ - -d eauth='pam' - -Which results in the response - -.. code-block:: json - - { - "return": [{ - "perms": [".*", "@runner", "@wheel"], - "start": 1400556492.277421, - "token": "d0ce6c1a37e99dcc0374392f272fe19c0090cca7", - "expire": 1400599692.277422, - "user": "salt", - "eauth": "pam" - }] - } - -In this example the ``token`` returned is ``d0ce6c1a37e99dcc0374392f272fe19c0090cca7`` and can be included -in subsequent websocket requests (as part of the URL). - -The event stream can be easily consumed via JavaScript: - -.. code-block:: javascript - - // Note, you must be authenticated! - - // Get the Websocket connection to Salt - var source = new Websocket('wss://localhost:8000/formatted_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7'); - - // Get Salt's "real time" event stream. - source.onopen = function() { source.send('websocket client ready'); }; - - // Other handlers - source.onerror = function(e) { console.debug('error!', e); }; - - // e.data represents Salt's "real time" event data as serialized JSON. - source.onmessage = function(e) { console.debug(e.data); }; - - // Terminates websocket connection and Salt's "real time" event stream on the server. - source.close(); - -Or via Python, using the Python module -`websocket-client `_ for example. -Or the tornado -`client `_. - -.. code-block:: python - - # Note, you must be authenticated! - - from websocket import create_connection - - # Get the Websocket connection to Salt - ws = create_connection('wss://localhost:8000/formatted_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7') - - # Get Salt's "real time" event stream. - ws.send('websocket client ready') - - - # Simple listener to print results of Salt's "real time" event stream. - # Look at https://pypi.python.org/pypi/websocket-client/ for more examples. - while listening_to_events: - print ws.recv() # Salt's "real time" event data as serialized JSON. - - # Terminates websocket connection and Salt's "real time" event stream on the server. - ws.close() - - # Please refer to https://github.com/liris/websocket-client/issues/81 when using a self signed cert - -Above examples show how to establish a websocket connection to Salt and activating -real time updates from Salt's event stream by signaling ``websocket client ready``. - -Example responses ------------------ - -``Minion information`` is a dictionary keyed by each connected minion's ``id`` (``mid``), -grains information for each minion is also included. - -Minion information is sent in response to the following minion events: - -- connection drops - - requires running ``manage.present`` periodically every ``loop_interval`` seconds -- minion addition -- minon removal - -.. code-block:: python - - # Not all grains are shown - data: { - "minions": { - "minion1": { - "id": "minion1", - "grains": { - "kernel": "Darwin", - "domain": "local", - "zmqversion": "4.0.3", - "kernelrelease": "13.2.0" - } - } - } - } - -``Job information`` is also tracked and delivered. - -Job information is also a dictionary -in which each job's information is keyed by salt's ``jid``. - -.. code-block:: python - - data: { - "jobs": { - "20140609153646699137": { - "tgt_type": "glob", - "jid": "20140609153646699137", - "tgt": "*", - "start_time": "2014-06-09T15:36:46.700315", - "state": "complete", - "fun": "test.ping", - "minions": { - "minion1": { - "return": true, - "retcode": 0, - "success": true - } - } - } - } - } - -Setup -===== In order to run rest_tornado with the salt-master add the following to your salt master config file. @@ -320,15 +56,13 @@ import tornado.httpserver import tornado.ioloop import tornado.web import tornado.gen -import tornado.websocket + from tornado.concurrent import Future -from . import event_processor from collections import defaultdict import math import functools -import json import yaml import zmq import fnmatch @@ -343,6 +77,8 @@ import salt.runner import salt.auth from salt import syspaths + +json = salt.utils.import_json() logger = logging.getLogger() # The clients rest_cherrypi supports. We want to mimic the interface, but not @@ -358,7 +94,9 @@ logger = logging.getLogger() class SaltClientsMixIn(object): - + ''' + MixIn class to container all of the salt clients that the API needs + ''' @property def saltclients(self): if not hasattr(self, '__saltclients'): @@ -398,13 +136,19 @@ class Any(Future): class EventListener(object): + ''' + Class responsible for listening to the salt master event bus and updating + futures. This is the core of what makes this async, this allows us to do + non-blocking work in the main processes and "wait" for an event to happen + ''' + def __init__(self, mod_opts, opts): self.mod_opts = mod_opts self.opts = opts self.event = salt.utils.event.get_event( - 'master', - opts['sock_dir'], - opts['transport']) + 'master', + opts['sock_dir'], + opts['transport']) # tag -> list of futures self.tag_map = defaultdict(list) @@ -430,9 +174,11 @@ class EventListener(object): if len(self.tag_map[tag]) == 0: del self.tag_map[tag] - def get_event(self, request, - tag='', - callback=None): + def get_event(self, + request, + tag='', + callback=None, + ): ''' Get an event (async of course) return a future that will get it later ''' @@ -680,8 +426,8 @@ class SaltAuthHandler(BaseSaltAPIHandler): perms = self.application.opts['external_auth'][token['eauth']][token['name']] except (AttributeError, IndexError): logging.debug("Configuration for external_auth malformed for " - "eauth '{0}', and user '{1}'." - .format(token.get('eauth'), token.get('name')), exc_info=True) + "eauth '{0}', and user '{1}'." + .format(token.get('eauth'), token.get('name')), exc_info=True) # TODO better error -- 'Configuration for external_auth could not be read.' self.send_error(500) @@ -771,9 +517,9 @@ class SaltAPIHandler(BaseSaltAPIHandler, SaltClientsMixIn): # ping all the minions (to see who we have to talk to) # TODO: actually ping them all? this just gets the pub data minions = self.saltclients['local'](chunk['tgt'], - 'test.ping', - [], - expr_form=f_call['kwargs']['expr_form'])['minions'] + 'test.ping', + [], + expr_form=f_call['kwargs']['expr_form'])['minions'] chunk_ret = {} maxflight = get_batch_size(f_call['kwargs']['batch'], len(minions)) @@ -992,108 +738,6 @@ class EventsSaltAPIHandler(SaltAPIHandler): self.finish() -class AllEventsHandler(tornado.websocket.WebSocketHandler): - ''' - Server side websocket handler. - ''' - def open(self, token): - ''' - Return a websocket connection to Salt - representing Salt's "real time" event stream. - ''' - logger.debug('In the websocket open method') - - self.token = token - # close the connection, if not authenticated - if not self.application.auth.get_tok(token): - logger.debug('Refusing websocket connection, bad token!') - self.close() - return - - self.connected = False - - @tornado.gen.coroutine - def on_message(self, message): - """Listens for a "websocket client ready" message. - Once that message is received an asynchronous job - is stated that yeilds messages to the client. - These messages make up salt's - "real time" event stream. - """ - logger.debug('Got websocket message {0}'.format(message)) - if message == 'websocket client ready': - if self.connected: - # TBD: Add ability to run commands in this branch - logger.debug('Websocket already connected, returning') - return - - self.connected = True - - while True: - try: - event = yield self.application.event_listener.get_event(self) - self.write_message(u'data: {0}\n\n'.format(json.dumps(event))) - except Exception as err: - logger.info('Error! Ending server side websocket connection. Reason = {0}'.format(str(err))) - break - - self.close() - else: - # TBD: Add logic to run salt commands here - pass - - def on_close(self, *args, **kwargs): - '''Cleanup. - - ''' - logger.debug('In the websocket close method') - self.close() - - -class FormattedEventsHandler(AllEventsHandler): - - @tornado.gen.coroutine - def on_message(self, message): - """Listens for a "websocket client ready" message. - Once that message is received an asynchronous job - is stated that yeilds messages to the client. - These messages make up salt's - "real time" event stream. - """ - logger.debug('Got websocket message {0}'.format(message)) - if message == 'websocket client ready': - if self.connected: - # TBD: Add ability to run commands in this branch - logger.debug('Websocket already connected, returning') - return - - self.connected = True - - evt_processor = event_processor.SaltInfo(self) - client = salt.netapi.NetapiClient(self.application.opts) - client.run({ - 'fun': 'grains.items', - 'tgt': '*', - 'token': self.token, - 'mode': 'client', - 'async': 'local_async', - 'client': 'local' - }) - while True: - try: - event = yield self.application.event_listener.get_event(self) - evt_processor.process(event, self.token, self.application.opts) - # self.write_message(u'data: {0}\n\n'.format(json.dumps(event))) - except Exception as err: - logger.debug('Error! Ending server side websocket connection. Reason = {0}'.format(str(err))) - break - - self.close() - else: - # TBD: Add logic to run salt commands here - pass - - class WebhookSaltAPIHandler(SaltAPIHandler): ''' Handler for /run requests @@ -1110,9 +754,9 @@ class WebhookSaltAPIHandler(SaltAPIHandler): # TODO: consolidate?? self.event = salt.utils.event.get_event( - 'master', - self.application.opts['sock_dir'], - self.application.opts['transport']) + 'master', + self.application.opts['sock_dir'], + self.application.opts['transport']) ret = self.event.fire_event({ 'post': self.raw_data, diff --git a/salt/netapi/rest_tornado/saltnado_websockets.py b/salt/netapi/rest_tornado/saltnado_websockets.py new file mode 100644 index 0000000000..d90e6ab52f --- /dev/null +++ b/salt/netapi/rest_tornado/saltnado_websockets.py @@ -0,0 +1,406 @@ +# encoding: utf-8 +''' +A Websockets add-on to saltnado +=================== + +.. py:currentmodule:: salt.netapi.rest_tornado.saltnado + +:depends: - tornado Python module + +In order to enable saltnado_webosockets you must add websockets: True to your +saltnado config block. + +.. code-block:: yaml + + rest_tornado: + # can be any port + port: 8000 + ssl_crt: /etc/pki/api/certs/server.crt + # no need to specify ssl_key if cert and key + # are in one single file + ssl_key: /etc/pki/api/certs/server.key + debug: False + disable_ssl: False + websockets: True + +All Events +---------- + +Exposes ``all`` "real-time" events from Salt's event bus on a websocket connection. +It should be noted that "Real-time" here means these events are made available +to the server as soon as any salt related action (changes to minions, new jobs etc) happens. +Clients are however assumed to be able to tolerate any network transport related latencies. +Functionality provided by this endpoint is similar to the ``/events`` end point. + +The event bus on the Salt master exposes a large variety of things, notably +when executions are started on the master and also when minions ultimately +return their results. This URL provides a real-time window into a running +Salt infrastructure. Uses websocket as the transport mechanism. + +Exposes GET method to return websocket connections. +All requests should include an auth token. +A way to obtain obtain authentication tokens is shown below. + +.. code-block:: bash + + % curl -si localhost:8000/login \\ + -H "Accept: application/json" \\ + -d username='salt' \\ + -d password='salt' \\ + -d eauth='pam' + +Which results in the response + +.. code-block:: json + + { + "return": [{ + "perms": [".*", "@runner", "@wheel"], + "start": 1400556492.277421, + "token": "d0ce6c1a37e99dcc0374392f272fe19c0090cca7", + "expire": 1400599692.277422, + "user": "salt", + "eauth": "pam" + }] + } + +In this example the ``token`` returned is ``d0ce6c1a37e99dcc0374392f272fe19c0090cca7`` and can be included +in subsequent websocket requests (as part of the URL). + +The event stream can be easily consumed via JavaScript: + +.. code-block:: javascript + + // Note, you must be authenticated! + + // Get the Websocket connection to Salt + var source = new Websocket('wss://localhost:8000/all_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7'); + + // Get Salt's "real time" event stream. + source.onopen = function() { source.send('websocket client ready'); }; + + // Other handlers + source.onerror = function(e) { console.debug('error!', e); }; + + // e.data represents Salt's "real time" event data as serialized JSON. + source.onmessage = function(e) { console.debug(e.data); }; + + // Terminates websocket connection and Salt's "real time" event stream on the server. + source.close(); + +Or via Python, using the Python module +`websocket-client `_ for example. +Or the tornado +`client `_. + +.. code-block:: python + + # Note, you must be authenticated! + + from websocket import create_connection + + # Get the Websocket connection to Salt + ws = create_connection('wss://localhost:8000/all_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7') + + # Get Salt's "real time" event stream. + ws.send('websocket client ready') + + + # Simple listener to print results of Salt's "real time" event stream. + # Look at https://pypi.python.org/pypi/websocket-client/ for more examples. + while listening_to_events: + print ws.recv() # Salt's "real time" event data as serialized JSON. + + # Terminates websocket connection and Salt's "real time" event stream on the server. + ws.close() + + # Please refer to https://github.com/liris/websocket-client/issues/81 when using a self signed cert + +Above examples show how to establish a websocket connection to Salt and activating +real time updates from Salt's event stream by signaling ``websocket client ready``. + + +Formatted Events +----------------- + +Exposes ``formatted`` "real-time" events from Salt's event bus on a websocket connection. +It should be noted that "Real-time" here means these events are made available +to the server as soon as any salt related action (changes to minions, new jobs etc) happens. +Clients are however assumed to be able to tolerate any network transport related latencies. +Functionality provided by this endpoint is similar to the ``/events`` end point. + +The event bus on the Salt master exposes a large variety of things, notably +when executions are started on the master and also when minions ultimately +return their results. This URL provides a real-time window into a running +Salt infrastructure. Uses websocket as the transport mechanism. + +Formatted events parses the raw "real time" event stream and maintains +a current view of the following: + +- minions +- jobs + +A change to the minions (such as addition, removal of keys or connection drops) +or jobs is processed and clients are updated. +Since we use salt's presence events to track minions, +please enable ``presence_events`` +and set a small value for the ``loop_interval`` +in the salt master config file. + +Exposes GET method to return websocket connections. +All requests should include an auth token. +A way to obtain obtain authentication tokens is shown below. + +.. code-block:: bash + + % curl -si localhost:8000/login \\ + -H "Accept: application/json" \\ + -d username='salt' \\ + -d password='salt' \\ + -d eauth='pam' + +Which results in the response + +.. code-block:: json + + { + "return": [{ + "perms": [".*", "@runner", "@wheel"], + "start": 1400556492.277421, + "token": "d0ce6c1a37e99dcc0374392f272fe19c0090cca7", + "expire": 1400599692.277422, + "user": "salt", + "eauth": "pam" + }] + } + +In this example the ``token`` returned is ``d0ce6c1a37e99dcc0374392f272fe19c0090cca7`` and can be included +in subsequent websocket requests (as part of the URL). + +The event stream can be easily consumed via JavaScript: + +.. code-block:: javascript + + // Note, you must be authenticated! + + // Get the Websocket connection to Salt + var source = new Websocket('wss://localhost:8000/formatted_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7'); + + // Get Salt's "real time" event stream. + source.onopen = function() { source.send('websocket client ready'); }; + + // Other handlers + source.onerror = function(e) { console.debug('error!', e); }; + + // e.data represents Salt's "real time" event data as serialized JSON. + source.onmessage = function(e) { console.debug(e.data); }; + + // Terminates websocket connection and Salt's "real time" event stream on the server. + source.close(); + +Or via Python, using the Python module +`websocket-client `_ for example. +Or the tornado +`client `_. + +.. code-block:: python + + # Note, you must be authenticated! + + from websocket import create_connection + + # Get the Websocket connection to Salt + ws = create_connection('wss://localhost:8000/formatted_events/d0ce6c1a37e99dcc0374392f272fe19c0090cca7') + + # Get Salt's "real time" event stream. + ws.send('websocket client ready') + + + # Simple listener to print results of Salt's "real time" event stream. + # Look at https://pypi.python.org/pypi/websocket-client/ for more examples. + while listening_to_events: + print ws.recv() # Salt's "real time" event data as serialized JSON. + + # Terminates websocket connection and Salt's "real time" event stream on the server. + ws.close() + + # Please refer to https://github.com/liris/websocket-client/issues/81 when using a self signed cert + +Above examples show how to establish a websocket connection to Salt and activating +real time updates from Salt's event stream by signaling ``websocket client ready``. + +Example responses +----------------- + +``Minion information`` is a dictionary keyed by each connected minion's ``id`` (``mid``), +grains information for each minion is also included. + +Minion information is sent in response to the following minion events: + +- connection drops + - requires running ``manage.present`` periodically every ``loop_interval`` seconds +- minion addition +- minon removal + +.. code-block:: python + + # Not all grains are shown + data: { + "minions": { + "minion1": { + "id": "minion1", + "grains": { + "kernel": "Darwin", + "domain": "local", + "zmqversion": "4.0.3", + "kernelrelease": "13.2.0" + } + } + } + } + +``Job information`` is also tracked and delivered. + +Job information is also a dictionary +in which each job's information is keyed by salt's ``jid``. + +.. code-block:: python + + data: { + "jobs": { + "20140609153646699137": { + "tgt_type": "glob", + "jid": "20140609153646699137", + "tgt": "*", + "start_time": "2014-06-09T15:36:46.700315", + "state": "complete", + "fun": "test.ping", + "minions": { + "minion1": { + "return": true, + "retcode": 0, + "success": true + } + } + } + } + } + +Setup +===== +''' + +import tornado.websocket +from . import event_processor + +import tornado.gen + +import salt.utils +import salt.netapi + +json = salt.utils.import_json() + +import logging +logger = logging.getLogger() + + +class AllEventsHandler(tornado.websocket.WebSocketHandler): # pylint: disable=W0232 + ''' + Server side websocket handler. + ''' + def open(self, token): + ''' + Return a websocket connection to Salt + representing Salt's "real time" event stream. + ''' + logger.debug('In the websocket open method') + + self.token = token + # close the connection, if not authenticated + if not self.application.auth.get_tok(token): + logger.debug('Refusing websocket connection, bad token!') + self.close() + return + + self.connected = False + + @tornado.gen.coroutine + def on_message(self, message): + """Listens for a "websocket client ready" message. + Once that message is received an asynchronous job + is stated that yeilds messages to the client. + These messages make up salt's + "real time" event stream. + """ + logger.debug('Got websocket message {0}'.format(message)) + if message == 'websocket client ready': + if self.connected: + # TBD: Add ability to run commands in this branch + logger.debug('Websocket already connected, returning') + return + + self.connected = True + + while True: + try: + event = yield self.application.event_listener.get_event(self) + self.write_message(u'data: {0}\n\n'.format(json.dumps(event))) + except Exception as err: + logger.info('Error! Ending server side websocket connection. Reason = {0}'.format(str(err))) + break + + self.close() + else: + # TBD: Add logic to run salt commands here + pass + + def on_close(self, *args, **kwargs): + '''Cleanup. + + ''' + logger.debug('In the websocket close method') + self.close() + + +class FormattedEventsHandler(AllEventsHandler): # pylint: disable=W0232 + + @tornado.gen.coroutine + def on_message(self, message): + """Listens for a "websocket client ready" message. + Once that message is received an asynchronous job + is stated that yeilds messages to the client. + These messages make up salt's + "real time" event stream. + """ + logger.debug('Got websocket message {0}'.format(message)) + if message == 'websocket client ready': + if self.connected: + # TBD: Add ability to run commands in this branch + logger.debug('Websocket already connected, returning') + return + + self.connected = True + + evt_processor = event_processor.SaltInfo(self) + client = salt.netapi.NetapiClient(self.application.opts) + client.run({ + 'fun': 'grains.items', + 'tgt': '*', + 'token': self.token, + 'mode': 'client', + 'async': 'local_async', + 'client': 'local' + }) + while True: + try: + event = yield self.application.event_listener.get_event(self) + evt_processor.process(event, self.token, self.application.opts) + # self.write_message(u'data: {0}\n\n'.format(json.dumps(event))) + except Exception as err: + logger.debug('Error! Ending server side websocket connection. Reason = {0}'.format(str(err))) + break + + self.close() + else: + # TBD: Add logic to run salt commands here + pass diff --git a/salt/pillar/git_pillar.py b/salt/pillar/git_pillar.py index fa69116887..9fcf392ae6 100644 --- a/salt/pillar/git_pillar.py +++ b/salt/pillar/git_pillar.py @@ -102,9 +102,9 @@ class GitPillar(object): self.working_dir = '' self.repo = None - needle = '{0} {1}'.format(self.branch, self.rp_location) for idx, opts_dict in enumerate(self.opts['ext_pillar']): - if opts_dict.get('git', '').startswith(needle): + lopts = opts_dict.get('git', '').split() + if len(lopts) >= 2 and lopts[:2] == [self.branch, self.rp_location]: rp_ = os.path.join(self.opts['cachedir'], 'pillar_gitfs', str(idx)) diff --git a/salt/returners/couchbase_return.py b/salt/returners/couchbase_return.py index 48b89e7437..983915b868 100644 --- a/salt/returners/couchbase_return.py +++ b/salt/returners/couchbase_return.py @@ -55,14 +55,8 @@ def __virtual__(): return False # try to load some faster json libraries. In order of fastest to slowest - for fast_json in ('ujson', 'yajl'): - try: - mod = __import__(fast_json) - couchbase.set_json_converters(mod.dumps, mod.loads) - log.info('loaded {0} json lib'.format(fast_json)) - break - except ImportError: - continue + json = salt.utils.import_json() + couchbase.set_json_converters(json.dumps, json.loads) return __virtualname__ diff --git a/salt/states/cmd.py b/salt/states/cmd.py index e1ec147912..70d58fddac 100644 --- a/salt/states/cmd.py +++ b/salt/states/cmd.py @@ -158,7 +158,7 @@ executed when the state it is watching changes. Example: - require: - file: /usr/local/bin/postinstall.sh -How do I create a environment from a pillar map? +How do I create an environment from a pillar map? ------------------------------------------------------------------------------- The map that comes from a pillar cannot be directly consumed by the env option. diff --git a/salt/states/ddns.py b/salt/states/ddns.py index c023918221..7e53945b3c 100644 --- a/salt/states/ddns.py +++ b/salt/states/ddns.py @@ -12,6 +12,9 @@ type dynamic updates. Requires dnspython module. ddns.present: - zone: example.com - ttl: 60 + - data: 111.222.333.444 + - nameserver: 123.234.345.456 + - keyfile: /srv/salt/tsig_key.txt ''' @@ -39,7 +42,7 @@ def present(name, zone, ttl, data, rdtype='A', **kwargs): DNS resource type. Default 'A'. ``**kwargs`` - Additional arguments the ddns.update function may need (e.g. keyfile). + Additional arguments the ddns.update function may need (e.g. nameserver, keyfile, keyname). ''' ret = {'name': name, 'changes': {}, @@ -90,7 +93,7 @@ def absent(name, zone, data=None, rdtype=None, **kwargs): DNS resource type. If omitted, all types will be purged. ``**kwargs`` - Additional arguments the ddns.update function may need (e.g. keyfile). + Additional arguments the ddns.delete function may need (e.g. nameserver, keyfile, keyname). ''' ret = {'name': name, 'changes': {}, @@ -110,7 +113,10 @@ def absent(name, zone, data=None, rdtype=None, **kwargs): elif status: ret['result'] = True ret['comment'] = 'Deleted DNS record(s)' - ret['changes'] = True + ret['changes'] = {'Deleted': {'name': name, + 'zone': zone + } + } else: ret['result'] = False ret['comment'] = 'Failed to delete DNS record(s)' diff --git a/salt/states/dockerio.py b/salt/states/dockerio.py index 9833fd1e86..51637dac13 100644 --- a/salt/states/dockerio.py +++ b/salt/states/dockerio.py @@ -556,7 +556,7 @@ def script(*args, **kw): def running(name, container=None, port_bindings=None, binds=None, publish_all_ports=False, links=None, lxc_conf=None, privileged=False, dns=None, volumes_from=None, - check_is_running=True): + network_mode=None, check_is_running=True): ''' Ensure that a container is running. (`docker inspect`) @@ -623,6 +623,16 @@ def running(name, container=None, port_bindings=None, binds=None, - dns: - name_other_container + network_mode + - 'bridge': creates a new network stack for the container on the docker bridge + - 'none': no networking for this container + - 'container:[name|id]': reuses another container network stack) + - 'host': use the host network stack inside the container + + .. code-block:: yaml + + - network_mode: host + check_is_running Enable checking if a container should run or not. Useful for data-only containers that must be linked to another one. @@ -639,7 +649,7 @@ def running(name, container=None, port_bindings=None, binds=None, container, binds=binds, port_bindings=port_bindings, lxc_conf=lxc_conf, publish_all_ports=publish_all_ports, links=links, privileged=privileged, - dns=dns, volumes_from=volumes_from, + dns=dns, volumes_from=volumes_from, network_mode=network_mode, ) if check_is_running: is_running = __salt__['docker.is_running'](container) @@ -651,9 +661,14 @@ def running(name, container=None, port_bindings=None, binds=None, changes={name: True}) else: return _invalid( - comment=('Container {0!r}' - ' cannot be started\n{0!s}').format(container, - started['out'])) + comment=( + 'Container {0!r} cannot be started\n{0!s}' + .format( + container, + started['out'], + ) + ) + ) else: return _valid( comment='Container {0!r} started.\n'.format(container), diff --git a/salt/states/file.py b/salt/states/file.py index d36eaa363f..1a75995e5e 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -6,11 +6,12 @@ Operations on regular files, special files, directories, and symlinks Salt States can aggressively manipulate files on a system. There are a number of ways in which files can be managed. -Regular files can be enforced with the ``managed`` function. This function -downloads files from the salt master and places them on the target system. -The downloaded files can be rendered as a jinja, mako, or wempy template, -adding a dynamic component to file management. An example of ``file.managed`` -which makes use of the jinja templating system would look like this: +Regular files can be enforced with the :mod:`file.managed +` state. This state downloads files from the salt +master and places them on the target system. Managed files can be rendered as a +jinja, mako, or wempy template, adding a dynamic component to file management. +An example of :mod:`file.managed ` which makes use of +the jinja templating system would look like this: .. code-block:: yaml @@ -29,6 +30,17 @@ which makes use of the jinja templating system would look like this: custom_var: "override" {% endif %} +It is also possible to use the :mod:`py renderer ` as a +templating option. The template would be a python script which would need to +contain a function called ``run()``, which returns a string. The returned +string will be the contents of the managed file. For example: + +.. code-block:: python + + def run(): + lines = ('foo', 'bar', 'baz') + return '\n\n'.join(lines) + .. note:: When using both the ``defaults`` and ``context`` arguments, note the extra diff --git a/salt/states/git.py b/salt/states/git.py index 6b3d57a580..d0029eeae8 100644 --- a/salt/states/git.py +++ b/salt/states/git.py @@ -484,7 +484,7 @@ def config(name, cwd=repo, user=user) except CommandExecutionError: - oval = 'None' + oval = None if value == oval: ret['comment'] = 'No changes made' @@ -504,6 +504,9 @@ def config(name, cwd=repo, user=user) + if oval is None: + oval = 'None' + ret['changes'][name] = '{0} => {1}'.format(oval, nval) return ret diff --git a/salt/states/schedule.py b/salt/states/schedule.py new file mode 100644 index 0000000000..2018d4757d --- /dev/null +++ b/salt/states/schedule.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +''' +Management of the Salt scheduler +============================================== + +.. code-block:: yaml + + job3: + schedule.present: + - function: test.ping + - seconds: 3600 + - splay: 10 + + This will schedule the command: test.ping every 3600 seconds + (every hour) splaying the time between 0 and 10 seconds + + job2: + schedule.present: + - function: test.ping + - seconds: 15 + - splay: + - start: 10 + - end: 20 + + This will schedule the command: test.ping every 3600 seconds + (every hour) splaying the time between 10 and 20 seconds + + job1: + schedule.present: + - function: state.sls + - args: + - httpd + - kwargs: + test: True + - when: + - Monday 5:00pm + - Tuesday 3:00pm + - Wednesday 5:00pm + - Thursday 3:00pm + - Friday 5:00pm + + This will schedule the command: state.sls httpd test=True at 5pm on Monday, + Wednesday and Friday, and 3pm on Tuesday and Thursday. + +''' + +import logging +log = logging.getLogger(__name__) + + +def present(name, + **kwargs): + ''' + Ensure a job is present in the schedule + + name + The unique name that is given to the scheduled job. + + seconds + The scheduled job will be executed after the specified + number of seconds have passed. + + minutes + The scheduled job will be executed after the specified + number of minutes have passed. + + hours + The scheduled job will be executed after the specified + number of hours have passed. + + days + The scheduled job will be executed after the specified + number of days have passed. + + when + This will schedule the job at the specified time(s). + The when parameter must be a single value or a dictionary + with the date string(s) using the dateutil format. + + function + The function that should be executed by the scheduled job. + + job_args + The arguments that will be used by the scheduled job. + + job_kwargs + The keyword arguments that will be used by the scheduled job. + + maxrunning + Ensure that there are no more than N copies of a particular job running. + + jid_include + Include the job into the job cache. + + splay + The amount of time in seconds to splay a scheduled job. + Can be specified as a single value in seconds or as a dictionary + range with 'start' and 'end' values. + + range + This will schedule the command within the range specified. + The range parameter must be a dictionary with the date strings + using the dateutil format. + + ''' + + ret = {'name': name, + 'result': True, + 'changes': {}, + 'comment': []} + + current_schedule = __salt__['schedule.list'](show_all=True, return_yaml=False) + + if name in current_schedule: + new_item = __salt__['schedule.build_schedule_item'](name, **kwargs) + if new_item == current_schedule[name]: + ret['comment'].append('Job {0} in correct state'.format(name)) + else: + result = __salt__['schedule.modify'](name, **kwargs) + if not result['result']: + ret['result'] = result['result'] + ret['comment'].append(result['comment']) + return ret + else: + ret['comment'].append('Modifying job {0} in schedule'.format(name)) + ret['changes'] = result['changes'] + else: + result = __salt__['schedule.add'](name, **kwargs) + if not result['result']: + ret['result'] = result['result'] + ret['comment'].append(result['comment']) + return ret + else: + ret['comment'].append('Adding new job {0} to schedule'.format(name)) + + ret['comment'] = '\n'.join(ret['comment']) + return ret + + +def absent(name, **kwargs): + ''' + Ensure a job is absent from the schedule + + name + The unique name that is given to the scheduled job. + + ''' + ### NOTE: The keyword arguments in **kwargs are ignored in this state, but + ### cannot be removed from the function definition, otherwise the use + ### of unsupported arguments will result in a traceback. + + ret = {'name': name, + 'result': True, + 'changes': {}, + 'comment': []} + + current_schedule = __salt__['schedule.list'](show_all=True, return_yaml=False) + if name in current_schedule: + result = __salt__['schedule.delete'](name) + if not result['result']: + ret['result'] = result['result'] + ret['comment'].append(result['comment']) + else: + ret['comment'].append('Removed job {0} from schedule'.format(name)) + else: + ret['comment'].append('Job {0} not present in schedule'.format(name)) + + ret['comment'] = '\n'.join(ret['comment']) + return ret diff --git a/salt/states/zcbuildout.py b/salt/states/zcbuildout.py index 3befb11506..f080785f67 100644 --- a/salt/states/zcbuildout.py +++ b/salt/states/zcbuildout.py @@ -133,7 +133,9 @@ def installed(name, debug=False, verbose=False, unless=None, - onlyif=None): + onlyif=None, + use_vt=False, + loglevel='debug'): ''' Install buildout in a specific directory @@ -198,6 +200,12 @@ def installed(name, verbose run buildout in verbose mode (-vvvvv) + use_vt + Use the new salt VT to stream output [experimental] + + loglevel + loglevel for buildout commands + ''' ret = {} @@ -248,6 +256,7 @@ def installed(name, verbose=verbose, onlyif=onlyif, unless=unless, + use_vt=use_vt ) ret.update(_ret_status(func(**kwargs), name, quiet=quiet)) return ret diff --git a/salt/utils/__init__.py b/salt/utils/__init__.py index 95574f0a5b..8401c2e8c2 100644 --- a/salt/utils/__init__.py +++ b/salt/utils/__init__.py @@ -2333,3 +2333,16 @@ def total_seconds(td): method which does not exist in versions of Python < 2.7. ''' return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + + +def import_json(): + ''' + Import a json module, starting with the quick ones and going down the list) + ''' + for fast_json in ('ujson', 'yajl', 'json'): + try: + mod = __import__(fast_json) + log.info('loaded {0} json lib'.format(fast_json)) + return mod + except ImportError: + continue diff --git a/salt/utils/schedule.py b/salt/utils/schedule.py index 8f1d8bbc6f..eb33ae4317 100644 --- a/salt/utils/schedule.py +++ b/salt/utils/schedule.py @@ -77,8 +77,8 @@ localtime. - Thursday 3:00pm - Friday 5:00pm -This will schedule the command: state.sls httpd test=True at 5pm on Monday, Wednesday -and Friday, and 3pm on Tuesday and Thursday. +This will schedule the command: state.sls httpd test=True at 5pm on Monday, +Wednesday and Friday, and 3pm on Tuesday and Thursday. schedule: job1: @@ -92,7 +92,6 @@ and Friday, and 3pm on Tuesday and Thursday. start: 8:00am end: 5:00pm -w This will schedule the command: state.sls httpd test=True every 3600 seconds (every hour) between the hours of 8am and 5pm. The range parameter must be a dictionary with the date strings using the dateutil format. diff --git a/setup.py b/setup.py index 21948889c2..7b643cbaa7 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ BOOTSTRAP_SCRIPT_DISTRIBUTED_VERSION = os.environ.get( 'BOOTSTRAP_SCRIPT_VERSION', # If no bootstrap-script version was provided from the environment, let's # provide the one we define. - 'v2014.06.19' + 'v2014.06.21' ) # Store a reference to the executing platform @@ -407,19 +407,13 @@ class Install(install): self.salt_transport ) ) - if self.salt_transport == 'none': + elif self.salt_transport == 'none': for requirement in _parse_requirements_file(SALT_ZEROMQ_REQS): if requirement not in self.distribution.install_requires: continue self.distribution.install_requires.remove(requirement) - return - if self.salt_transport in ('zeromq', 'both'): - self.distribution.install_requires.extend( - _parse_requirements_file(SALT_ZEROMQ_REQS) - ) - - if self.salt_transport in ('raet', 'both'): + elif self.salt_transport in ('raet', 'both'): self.distribution.install_requires.extend( _parse_requirements_file(SALT_RAET_REQS) ) @@ -550,7 +544,9 @@ SETUP_KWARGS = {'name': NAME, ], # Required for esky builds, ZeroMQ or RAET deps will be added # at install time - 'install_requires': _parse_requirements_file(SALT_REQS), + 'install_requires': + _parse_requirements_file(SALT_REQS) + + _parse_requirements_file(SALT_ZEROMQ_REQS), 'extras_require': { 'RAET': _parse_requirements_file(SALT_RAET_REQS), 'Cloud': _parse_requirements_file(SALT_CLOUD_REQS) @@ -570,6 +566,7 @@ if IS_WINDOWS_PLATFORM is False: 'doc/man/salt-master.1', 'doc/man/salt-key.1', 'doc/man/salt.1', + 'doc/man/salt-api.1', 'doc/man/salt-syndic.1', 'doc/man/salt-run.1', 'doc/man/salt-ssh.1', diff --git a/tests/integration/cloud/providers/digital_ocean.py b/tests/integration/cloud/providers/digital_ocean.py index 56274668f6..fd5422e0cc 100644 --- a/tests/integration/cloud/providers/digital_ocean.py +++ b/tests/integration/cloud/providers/digital_ocean.py @@ -6,15 +6,14 @@ # Import Python Libs import os -# Import Salt Libs -import integration -from salt.config import cloud_providers_config - # Import Salt Testing Libs from salttesting import skipIf from salttesting.helpers import ensure_in_syspath, expensiveTest ensure_in_syspath('../../../') +# Import Salt Libs +import integration +from salt.config import cloud_providers_config # Import Third-Party Libs try: @@ -38,6 +37,7 @@ class DigitalOceanTest(integration.ShellCase): Integration tests for the Digital Ocean cloud provider in Salt-Cloud ''' + @expensiveTest def setUp(self): ''' Sets up the test requirements @@ -70,7 +70,6 @@ class DigitalOceanTest(integration.ShellCase): .format(provider) ) - @expensiveTest def test_instance(self): ''' Test creating an instance on Digital Ocean diff --git a/tests/integration/cloud/providers/gogrid.py b/tests/integration/cloud/providers/gogrid.py index 0a0f4730ac..d5de728d73 100644 --- a/tests/integration/cloud/providers/gogrid.py +++ b/tests/integration/cloud/providers/gogrid.py @@ -6,16 +6,16 @@ # Import Python Libs import os -# Import Salt Libs -import integration -from salt.config import cloud_providers_config - # Import Salt Testing Libs from salttesting import skipIf from salttesting.helpers import ensure_in_syspath, expensiveTest ensure_in_syspath('../../../') +# Import Salt Libs +import integration +from salt.config import cloud_providers_config + # Import Third-Party Libs try: import libcloud # pylint: disable=W0611 @@ -31,6 +31,7 @@ class GoGridTest(integration.ShellCase): Integration tests for the GoGrid cloud provider in Salt-Cloud ''' + @expensiveTest def setUp(self): ''' Sets up the test requirements @@ -63,7 +64,6 @@ class GoGridTest(integration.ShellCase): .format(provider) ) - @expensiveTest def test_instance(self): ''' Test creating an instance on GoGrid diff --git a/tests/integration/cloud/providers/linode.py b/tests/integration/cloud/providers/linode.py index 43f51da372..5e71044d8d 100644 --- a/tests/integration/cloud/providers/linode.py +++ b/tests/integration/cloud/providers/linode.py @@ -6,16 +6,16 @@ # Import Python Libs import os -# Import Salt Libs -import integration -from salt.config import cloud_providers_config - # Import Salt Testing Libs from salttesting import skipIf from salttesting.helpers import ensure_in_syspath, expensiveTest ensure_in_syspath('../../../') +# Import Salt Libs +import integration +from salt.config import cloud_providers_config + # Import Third-Party Libs try: import libcloud # pylint: disable=W0611 @@ -30,6 +30,7 @@ class LinodeTest(integration.ShellCase): Integration tests for the Linode cloud provider in Salt-Cloud ''' + @expensiveTest def setUp(self): ''' Sets up the test requirements @@ -63,7 +64,6 @@ class LinodeTest(integration.ShellCase): ) ) - @expensiveTest def test_instance(self): ''' Test creating an instance on Linode diff --git a/tests/integration/cloud/providers/rackspace.py b/tests/integration/cloud/providers/rackspace.py index c7f78f9115..85558dbc36 100644 --- a/tests/integration/cloud/providers/rackspace.py +++ b/tests/integration/cloud/providers/rackspace.py @@ -6,16 +6,16 @@ # Import Python Libs import os -# Import Salt Libs -import integration -from salt.config import cloud_providers_config - # Import Salt Testing Libs from salttesting import skipIf from salttesting.helpers import ensure_in_syspath, expensiveTest ensure_in_syspath('../../../') +# Import Salt Libs +import integration +from salt.config import cloud_providers_config + # Import Third-Party Libs try: import libcloud # pylint: disable=W0611 @@ -30,6 +30,7 @@ class RackspaceTest(integration.ShellCase): Integration tests for the Rackspace cloud provider using the Openstack driver ''' + @expensiveTest def setUp(self): ''' Sets up the test requirements @@ -63,7 +64,6 @@ class RackspaceTest(integration.ShellCase): .format(provider) ) - @expensiveTest def test_instance(self): ''' Test creating an instance on rackspace with the openstack driver diff --git a/tests/integration/loader/globals.py b/tests/integration/loader/globals.py new file mode 100644 index 0000000000..d3cea2fd3f --- /dev/null +++ b/tests/integration/loader/globals.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +''' + integration.loader.globals + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Test Salt's loader regarding globals that it should pack in +''' + +# Import Salt Testing libs +from salttesting.helpers import ensure_in_syspath +ensure_in_syspath('../') + +# Import salt libs +import integration +import salt.loader +import inspect +import yaml + + +class LoaderGlobalsTest(integration.ModuleCase): + ''' + Test all of the globals that the loader is responsible for adding to modules + + This shouldn't be done here, but should rather be done per module type (in the cases where they are used) + so they can check ALL globals that they have (or should have) access to. + + This is intended as a shorter term way of testing these so we don't break the loader + ''' + def _verify_globals(self, mod_dict): + ''' + Verify that the globals listed in the doc string (from the test) are in these modules + ''' + # find the globals + global_vars = None + for val in mod_dict.itervalues(): + if hasattr(val, '__globals__'): + global_vars = val.__globals__ + break + # if we couldn't find any, then we have no modules-- so something is broken + if global_vars is None: + # TODO: log or something? Skip however we do that + return + + # get the names of the globals you should have + func_name = inspect.stack()[1][3] + names = yaml.load(getattr(self, func_name).__doc__).values()[0] + + for name in names: + assert name in global_vars + + def test_auth(self): + ''' + Test that auth mods have: + - __pillar__ + - __grains__ + - __salt__ + ''' + self._verify_globals(salt.loader.auth(self.master_opts)) + + def test_runners(self): + ''' + Test that runners have: + - __pillar__ + - __salt__ + - __opts__ + - __grains__ + ''' + self._verify_globals(salt.loader.runner(self.master_opts)) + + def test_returners(self): + ''' + Test that returners have: + - __salt__ + - __opts__ + - __pillar__ + - __grains__ + ''' + self._verify_globals(salt.loader.returners(self.master_opts, {})) + + def test_pillars(self): + ''' + Test that pillars have: + - __salt__ + - __opts__ + - __pillar__ + - __grains__ + ''' + self._verify_globals(salt.loader.pillars(self.master_opts, {})) + + def test_tops(self): + ''' + Test that tops have: [] + ''' + self._verify_globals(salt.loader.tops(self.master_opts)) + + def test_outputters(self): + ''' + Test that outputters have: + - __opts__ + - __pillar__ + - __grains__ + ''' + self._verify_globals(salt.loader.outputters(self.master_opts)) + + def test_states(self): + ''' + Test that states: + - __pillar__ + - __salt__ + - __opts__ + - __grains__ + ''' + self._verify_globals(salt.loader.states(self.master_opts, {})) + + def test_log_handlers(self): + ''' + Test that log_handlers have: + - __path__ + ''' + self._verify_globals(salt.loader.log_handlers(self.master_opts)) + + def test_renderers(self): + ''' + Test that renderers have: + - __salt__ # Execution functions (i.e. __salt__['test.echo']('foo')) + - __grains__ # Grains (i.e. __grains__['os']) + - __pillar__ # Pillar data (i.e. __pillar__['foo']) + - __opts__ # Minion configuration options + ''' + self._verify_globals(salt.loader.render(self.master_opts, {})) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(LoaderGlobalsTest, needs_daemon=False) diff --git a/tests/minionswarm.py b/tests/minionswarm.py index d0ca813ed6..541c3bbec4 100644 --- a/tests/minionswarm.py +++ b/tests/minionswarm.py @@ -66,6 +66,10 @@ def parse(): dest='root_dir', default=None, help='Override the minion root_dir config') + parser.add_option('--transport', + dest='transport', + default='zeromq', + help='Declare which transport to use, default is zeromq') parser.add_option( '-c', '--config-dir', default='/etc/salt', help=('Pass in an alternative configuration directory. Default: ' @@ -99,7 +103,8 @@ class Swarm(object): self.swarm_root = tempfile.mkdtemp(prefix='mswarm-root', suffix='.d', dir=tmpdir) - self.pki = self._pki_dir() + if self.opts['transport'] == 'zeromq': + self.pki = self._pki_dir() self.__zfill = len(str(self.opts['minions'])) self.confs = set() @@ -133,22 +138,25 @@ class Swarm(object): dpath = os.path.join(self.swarm_root, minion_id) os.makedirs(dpath) - minion_pkidir = os.path.join(dpath, 'pki') - os.makedirs(minion_pkidir) - minion_pem = os.path.join(self.pki, 'minion.pem') - minion_pub = os.path.join(self.pki, 'minion.pub') - shutil.copy(minion_pem, minion_pkidir) - shutil.copy(minion_pub, minion_pkidir) - data = { 'id': minion_id, 'user': self.opts['user'], - 'pki_dir': minion_pkidir, 'cachedir': os.path.join(dpath, 'cache'), 'master': self.opts['master'], 'log_file': os.path.join(dpath, 'minion.log') } + if self.opts['transport'] == 'zeromq': + minion_pkidir = os.path.join(dpath, 'pki') + os.makedirs(minion_pkidir) + minion_pem = os.path.join(self.pki, 'minion.pem') + minion_pub = os.path.join(self.pki, 'minion.pub') + shutil.copy(minion_pem, minion_pkidir) + shutil.copy(minion_pub, minion_pkidir) + data['pki_dir'] = minion_pkidir + elif self.opts['transport'] == 'raet': + data['transport'] = 'raet' + if self.opts['root_dir']: data['root_dir'] = self.opts['root_dir'] diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 425e73d068..416f0e6b53 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -474,6 +474,10 @@ class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): sconfig.get_id(cache=False), (MOCK_HOSTNAME, False) ) +# <---- Salt Cloud Configuration Tests --------------------------------------------- + + # cloud_config tests + def test_cloud_config_vm_profiles_config(self): ''' Tests passing in vm_config and profiles_config. @@ -524,7 +528,8 @@ class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): profiles_config_path='foo', profiles_config='bar') @patch('salt.config.load_config', MagicMock(return_value={})) - @patch('salt.config.apply_cloud_config', MagicMock(return_value={'providers': 'foo'})) + @patch('salt.config.apply_cloud_config', + MagicMock(return_value={'providers': 'foo'})) def test_cloud_config_providers_in_opts(self): ''' Tests mixing old cloud providers with pre-configured providers configurations @@ -534,7 +539,8 @@ class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): providers_config='bar') @patch('salt.config.load_config', MagicMock(return_value={})) - @patch('salt.config.apply_cloud_config', MagicMock(return_value={'providers': 'foo'})) + @patch('salt.config.apply_cloud_config', + MagicMock(return_value={'providers': 'foo'})) @patch('os.path.isfile', MagicMock(return_value=True)) def test_cloud_config_providers_in_opts_path(self): ''' @@ -544,88 +550,65 @@ class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): self.assertRaises(SaltCloudConfigError, sconfig.cloud_config, PATH, providers_config_path='bar') - def test_load_cloud_config_from_environ_var(self): - original_environ = os.environ.copy() + # apply_vm_profiles_config tests - tempdir = tempfile.mkdtemp(dir=integration.SYS_TMP_DIR) - try: - env_root_dir = os.path.join(tempdir, 'foo', 'env') - os.makedirs(env_root_dir) - env_fpath = os.path.join(env_root_dir, 'config-env') - - salt.utils.fopen(env_fpath, 'w').write( - 'root_dir: {0}\n' - 'log_file: {1}\n'.format(env_root_dir, env_fpath) - ) - - os.environ['SALT_CLOUD_CONFIG'] = env_fpath - # Should load from env variable, not the default configuration file - config = sconfig.cloud_config('/etc/salt/cloud') - self.assertEqual(config['log_file'], env_fpath) - os.environ.clear() - os.environ.update(original_environ) - - root_dir = os.path.join(tempdir, 'foo', 'bar') - os.makedirs(root_dir) - fpath = os.path.join(root_dir, 'config') - salt.utils.fopen(fpath, 'w').write( - 'root_dir: {0}\n' - 'log_file: {1}\n'.format(root_dir, fpath) - ) - # Let's set the environment variable, yet, since the configuration - # file path is not the default one, ie, the user has passed an - # alternative configuration file form the CLI parser, the - # environment variable will be ignored. - os.environ['SALT_CLOUD_CONFIG'] = env_fpath - config = sconfig.cloud_config(fpath) - self.assertEqual(config['log_file'], fpath) - finally: - # Reset the environ - os.environ.clear() - os.environ.update(original_environ) - - if os.path.isdir(tempdir): - shutil.rmtree(tempdir) - - def test_deploy_search_path_as_string(self): - temp_conf_dir = os.path.join(integration.TMP, 'issue-8863') - config_file_path = os.path.join(temp_conf_dir, 'cloud') - deploy_dir_path = os.path.join(temp_conf_dir, 'test-deploy.d') - try: - for directory in (temp_conf_dir, deploy_dir_path): - if not os.path.isdir(directory): - os.makedirs(directory) - - default_config = sconfig.cloud_config(config_file_path) - default_config['deploy_scripts_search_path'] = deploy_dir_path - with salt.utils.fopen(config_file_path, 'w') as cfd: - cfd.write(yaml.dump(default_config)) - - default_config = sconfig.cloud_config(config_file_path) - - # Our custom deploy scripts path was correctly added to the list - self.assertIn( - deploy_dir_path, - default_config['deploy_scripts_search_path'] - ) - - # And it's even the first occurrence as it should - self.assertEqual( - deploy_dir_path, - default_config['deploy_scripts_search_path'][0] - ) - finally: - if os.path.isdir(temp_conf_dir): - shutil.rmtree(temp_conf_dir) - - def test_includes_load(self): + def test_apply_vm_profiles_config_bad_profile_format(self): ''' - Tests that cloud.{providers,profiles}.d directories are loaded, even if not - directly passed in through path + Tests passing in a bad profile format in overrides ''' - config = sconfig.cloud_config(self.get_config_file_path('cloud')) - self.assertIn('ec2-config', config['providers']) - self.assertIn('Ubuntu-13.04-AMD64', config['profiles']) + overrides = {'foo': 'bar', 'conf_file': PATH} + self.assertRaises(SaltCloudConfigError, sconfig.apply_vm_profiles_config, + PATH, overrides, defaults=DEFAULT) + + def test_apply_vm_profiles_config_success(self): + ''' + Tests passing in valid provider and profile config files successfully + ''' + providers = {'test-provider': + {'digital_ocean': + {'provider': 'digital_ocean', 'profiles': {}}}} + overrides = {'test-profile': + {'provider': 'test-provider', + 'image': 'Ubuntu 12.10 x64', + 'size': '512MB'}, + 'conf_file': PATH} + ret = {'test-profile': + {'profile': 'test-profile', + 'provider': 'test-provider:digital_ocean', + 'image': 'Ubuntu 12.10 x64', + 'size': '512MB'}} + self.assertEqual(sconfig.apply_vm_profiles_config(providers, + overrides, + defaults=DEFAULT), ret) + + def test_apply_vm_profiles_config_extend_success(self): + ''' + Tests profile extends functionality with valid provider and profile configs + ''' + providers = {'test-config': {'ec2': {'profiles': {}, 'provider': 'ec2'}}} + overrides = {'Amazon': {'image': 'test-image-1', + 'extends': 'dev-instances'}, + 'Fedora': {'image': 'test-image-2', + 'extends': 'dev-instances'}, + 'conf_file': PATH, + 'dev-instances': {'ssh_username': 'test_user', + 'provider': 'test-config'}} + ret = {'Amazon': {'profile': 'Amazon', + 'ssh_username': 'test_user', + 'image': 'test-image-1', + 'provider': 'test-config:ec2'}, + 'Fedora': {'profile': 'Fedora', + 'ssh_username': 'test_user', + 'image': 'test-image-2', + 'provider': 'test-config:ec2'}, + 'dev-instances': {'profile': 'dev-instances', + 'ssh_username': 'test_user', + 'provider': 'test-config:ec2'}} + self.assertEqual(sconfig.apply_vm_profiles_config(providers, + overrides, + defaults=DEFAULT), ret) + + # apply_cloud_providers_config tests def test_apply_cloud_providers_config_same_providers(self): ''' @@ -686,7 +669,10 @@ class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): 'provider': 'ec2', 'id': 'ABCDEFGHIJKLMNOP', 'user': 'user@mycorp.com'}}} - self.assertEqual(ret, sconfig.apply_cloud_providers_config(overrides, defaults=DEFAULT)) + self.assertEqual(ret, + sconfig.apply_cloud_providers_config( + overrides, + defaults=DEFAULT)) def test_apply_cloud_providers_config_extend_multiple(self): ''' @@ -821,6 +807,168 @@ class ConfigTestCase(TestCase, integration.AdaptedConfigurationTestCaseMixIn): overrides, DEFAULT) + # is_provider_configured tests + + def test_is_provider_configured_no_alias(self): + ''' + Tests when provider alias is not in opts + ''' + opts = {'providers': 'test'} + provider = 'foo:bar' + self.assertFalse(sconfig.is_provider_configured(opts, provider)) + + def test_is_provider_configured_no_driver(self): + ''' + Tests when provider driver is not in opts + ''' + opts = {'providers': {'foo': 'baz'}} + provider = 'foo:bar' + self.assertFalse(sconfig.is_provider_configured(opts, provider)) + + def test_is_provider_configured_key_is_none(self): + ''' + Tests when a required configuration key is not set + ''' + opts = {'providers': {'foo': {'bar': {'api_key': None}}}} + provider = 'foo:bar' + self.assertFalse( + sconfig.is_provider_configured(opts, + provider, + required_keys=('api_key',))) + + def test_is_provider_configured_success(self): + ''' + Tests successful cloud provider configuration + ''' + opts = {'providers': {'foo': {'bar': {'api_key': 'baz'}}}} + provider = 'foo:bar' + ret = {'api_key': 'baz'} + self.assertEqual( + sconfig.is_provider_configured(opts, + provider, + required_keys=('api_key',)), ret) + + def test_is_provider_configured_multiple_driver_not_provider(self): + ''' + Tests when the drive is not the same as the provider when + searching through multiple providers + ''' + opts = {'providers': {'foo': {'bar': {'api_key': 'baz'}}}} + provider = 'foo' + self.assertFalse(sconfig.is_provider_configured(opts, provider)) + + def test_is_provider_configured_multiple_key_is_none(self): + ''' + Tests when a required configuration key is not set when + searching through multiple providers + ''' + opts = {'providers': {'foo': {'bar': {'api_key': None}}}} + provider = 'bar' + self.assertFalse( + sconfig.is_provider_configured(opts, + provider, + required_keys=('api_key',))) + + def test_is_provider_configured_multiple_success(self): + ''' + Tests successful cloud provider configuration when searching + through multiple providers + ''' + opts = {'providers': {'foo': {'bar': {'api_key': 'baz'}}}} + provider = 'bar' + ret = {'api_key': 'baz'} + self.assertEqual( + sconfig.is_provider_configured(opts, + provider, + required_keys=('api_key',)), ret) + + # other cloud configuration tests + + def test_load_cloud_config_from_environ_var(self): + original_environ = os.environ.copy() + + tempdir = tempfile.mkdtemp(dir=integration.SYS_TMP_DIR) + try: + env_root_dir = os.path.join(tempdir, 'foo', 'env') + os.makedirs(env_root_dir) + env_fpath = os.path.join(env_root_dir, 'config-env') + + salt.utils.fopen(env_fpath, 'w').write( + 'root_dir: {0}\n' + 'log_file: {1}\n'.format(env_root_dir, env_fpath) + ) + + os.environ['SALT_CLOUD_CONFIG'] = env_fpath + # Should load from env variable, not the default configuration file + config = sconfig.cloud_config('/etc/salt/cloud') + self.assertEqual(config['log_file'], env_fpath) + os.environ.clear() + os.environ.update(original_environ) + + root_dir = os.path.join(tempdir, 'foo', 'bar') + os.makedirs(root_dir) + fpath = os.path.join(root_dir, 'config') + salt.utils.fopen(fpath, 'w').write( + 'root_dir: {0}\n' + 'log_file: {1}\n'.format(root_dir, fpath) + ) + # Let's set the environment variable, yet, since the configuration + # file path is not the default one, ie, the user has passed an + # alternative configuration file form the CLI parser, the + # environment variable will be ignored. + os.environ['SALT_CLOUD_CONFIG'] = env_fpath + config = sconfig.cloud_config(fpath) + self.assertEqual(config['log_file'], fpath) + finally: + # Reset the environ + os.environ.clear() + os.environ.update(original_environ) + + if os.path.isdir(tempdir): + shutil.rmtree(tempdir) + + def test_deploy_search_path_as_string(self): + temp_conf_dir = os.path.join(integration.TMP, 'issue-8863') + config_file_path = os.path.join(temp_conf_dir, 'cloud') + deploy_dir_path = os.path.join(temp_conf_dir, 'test-deploy.d') + try: + for directory in (temp_conf_dir, deploy_dir_path): + if not os.path.isdir(directory): + os.makedirs(directory) + + default_config = sconfig.cloud_config(config_file_path) + default_config['deploy_scripts_search_path'] = deploy_dir_path + with salt.utils.fopen(config_file_path, 'w') as cfd: + cfd.write(yaml.dump(default_config)) + + default_config = sconfig.cloud_config(config_file_path) + + # Our custom deploy scripts path was correctly added to the list + self.assertIn( + deploy_dir_path, + default_config['deploy_scripts_search_path'] + ) + + # And it's even the first occurrence as it should + self.assertEqual( + deploy_dir_path, + default_config['deploy_scripts_search_path'][0] + ) + finally: + if os.path.isdir(temp_conf_dir): + shutil.rmtree(temp_conf_dir) + + def test_includes_load(self): + ''' + Tests that cloud.{providers,profiles}.d directories are loaded, even if not + directly passed in through path + ''' + config = sconfig.cloud_config(self.get_config_file_path('cloud')) + self.assertIn('ec2-config', config['providers']) + self.assertIn('Ubuntu-13.04-AMD64', config['profiles']) + +# <---- Salt Cloud Configuration Tests --------------------------------------------- + if __name__ == '__main__': from integration import run_tests run_tests(ConfigTestCase, needs_daemon=False)