1============================
2Writing Cloud Driver Modules
3============================
4
5Salt Cloud runs on a module system similar to the main Salt project. The
6modules inside saltcloud exist in the ``salt/cloud/clouds`` directory of the
7salt source.
8
9There are two basic types of cloud modules. If a cloud host is supported by
10libcloud, then using it is the fastest route to getting a module written. The
11Apache Libcloud project is located at:
12
13http://libcloud.apache.org/
14
15Not every cloud host is supported by libcloud. Additionally, not every
16feature in a supported cloud host is necessarily supported by libcloud. In
17either of these cases, a module can be created which does not rely on libcloud.
18
19All Driver Modules
20==================
21The following functions are required by all driver modules, whether or not they are
22based on libcloud.
23
24The __virtual__() Function
25--------------------------
26This function determines whether or not to make this cloud module available
27upon execution. Most often, it uses ``get_configured_provider()`` to determine
28if the necessary configuration has been set up. It may also check for necessary
29imports, to decide whether to load the module. In most cases, it will return a
30``True`` or ``False`` value. If the name of the driver used does not match the
31filename, then that name should be returned instead of ``True``. An example of
32this may be seen in the Azure module:
33
34https://github.com/saltstack/salt/tree/|repo_primary_branch|/salt/cloud/clouds/msazure.py
35
36The get_configured_provider() Function
37--------------------------------------
38This function uses ``config.is_provider_configured()`` to determine whether
39all required information for this driver has been configured. The last value
40in the list of required settings should be followed by a comma.
41
42
43Libcloud Based Modules
44======================
45Writing a cloud module based on libcloud has two major advantages. First of all,
46much of the work has already been done by the libcloud project. Second, most of
47the functions necessary to Salt have already been added to the Salt Cloud
48project.
49
50The create() Function
51---------------------
52The most important function that does need to be manually written is the
53``create()`` function. This is what is used to request a virtual machine to be
54created by the cloud host, wait for it to become available, and then
55(optionally) log in and install Salt on it.
56
57A good example to follow for writing a cloud driver module based on libcloud
58is the module provided for Linode:
59
60https://github.com/saltstack/salt/tree/|repo_primary_branch|/salt/cloud/clouds/linode.py
61
62The basic flow of a ``create()`` function is as follows:
63
64* Send a request to the cloud host to create a virtual machine.
65* Wait for the virtual machine to become available.
66* Generate kwargs to be used to deploy Salt.
67* Log into the virtual machine and deploy Salt.
68* Return a data structure that describes the newly-created virtual machine.
69
70At various points throughout this function, events may be fired on the Salt
71event bus. Four of these events, which are described below, are required. Other
72events may be added by the user, where appropriate.
73
74When the ``create()`` function is called, it is passed a data structure called
75``vm_``. This dict contains a composite of information describing the virtual
76machine to be created. A dict called ``__opts__`` is also provided by Salt,
77which contains the options used to run Salt Cloud, as well as a set of
78configuration and environment variables.
79
80The first thing the ``create()`` function must do is fire an event stating that
81it has started the create process. This event is tagged
82``salt/cloud/<vm name>/creating``. The payload contains the names of the VM,
83profile, and provider.
84
85A set of kwargs is then usually created, to describe the parameters required
86by the cloud host to request the virtual machine.
87
88An event is then fired to state that a virtual machine is about to be requested.
89It is tagged as ``salt/cloud/<vm name>/requesting``. The payload contains most
90or all of the parameters that will be sent to the cloud host. Any private
91information (such as passwords) should not be sent in the event.
92
93After a request is made, a set of deploy kwargs will be generated. These will
94be used to install Salt on the target machine. Windows options are supported
95at this point, and should be generated, even if the cloud host does not
96currently support Windows. This will save time in the future if the host
97does eventually decide to support Windows.
98
99An event is then fired to state that the deploy process is about to begin. This
100event is tagged ``salt/cloud/<vm name>/deploying``. The payload for the event
101will contain a set of deploy kwargs, useful for debugging purposed. Any private
102data, including passwords and keys (including public keys) should be stripped
103from the deploy kwargs before the event is fired.
104
105If any Windows options have been passed in, the
106``salt.utils.cloud.deploy_windows()`` function will be called. Otherwise, it
107will be assumed that the target is a Linux or Unix machine, and the
108``salt.utils.cloud.deploy_script()`` will be called.
109
110Both of these functions will wait for the target machine to become available,
111then the necessary port to log in, then a successful login that can be used to
112install Salt. Minion configuration and keys will then be uploaded to a temporary
113directory on the target by the appropriate function. On a Windows target, the
114Windows Minion Installer will be run in silent mode. On a Linux/Unix target, a
115deploy script (``bootstrap-salt.sh``, by default) will be run, which will
116auto-detect the operating system, and install Salt using its native package
117manager. These do not need to be handled by the developer in the cloud module.
118
119The ``salt.utils.cloud.validate_windows_cred()`` function has been extended to
120take the number of retries and retry_delay parameters in case a specific cloud
121host has a delay between providing the Windows credentials and the
122credentials being available for use.  In their ``create()`` function, or as
123a sub-function called during the creation process, developers should use the
124``win_deploy_auth_retries`` and ``win_deploy_auth_retry_delay`` parameters from
125the provider configuration to allow the end-user the ability to customize the
126number of tries and delay between tries for their particular host.
127
128After the appropriate deploy function completes, a final event is fired
129which describes the virtual machine that has just been created. This event is
130tagged ``salt/cloud/<vm name>/created``. The payload contains the names of the
131VM, profile, and provider.
132
133Finally, a dict (queried from the provider) which describes the new virtual
134machine is returned to the user. Because this data is not fired on the event
135bus it can, and should, return any passwords that were returned by the cloud
136host. In some cases (for example, Rackspace), this is the only time that
137the password can be queried by the user; post-creation queries may not contain
138password information (depending upon the host).
139
140The libcloudfuncs Functions
141---------------------------
142A number of other functions are required for all cloud hosts. However, with
143libcloud-based modules, these are all provided for free by the libcloudfuncs
144library. The following two lines set up the imports:
145
146.. code-block:: python
147
148    from salt.cloud.libcloudfuncs import *  # pylint: disable=W0614,W0401
149    import salt.utils.functools
150
151And then a series of declarations will make the necessary functions available
152within the cloud module.
153
154.. code-block:: python
155
156    get_size = salt.utils.functools.namespaced_function(get_size, globals())
157    get_image = salt.utils.functools.namespaced_function(get_image, globals())
158    avail_locations = salt.utils.functools.namespaced_function(avail_locations, globals())
159    avail_images = salt.utils.functools.namespaced_function(avail_images, globals())
160    avail_sizes = salt.utils.functools.namespaced_function(avail_sizes, globals())
161    script = salt.utils.functools.namespaced_function(script, globals())
162    destroy = salt.utils.functools.namespaced_function(destroy, globals())
163    list_nodes = salt.utils.functools.namespaced_function(list_nodes, globals())
164    list_nodes_full = salt.utils.functools.namespaced_function(list_nodes_full, globals())
165    list_nodes_select = salt.utils.functools.namespaced_function(
166        list_nodes_select, globals()
167    )
168    show_instance = salt.utils.functools.namespaced_function(show_instance, globals())
169
170If necessary, these functions may be replaced by removing the appropriate
171declaration line, and then adding the function as normal.
172
173These functions are required for all cloud modules, and are described in detail
174in the next section.
175
176
177Non-Libcloud Based Modules
178==========================
179In some cases, using libcloud is not an option. This may be because libcloud has
180not yet included the necessary driver itself, or it may be that the driver that
181is included with libcloud does not contain all of the necessary features
182required by the developer. When this is the case, some or all of the functions
183in ``libcloudfuncs`` may be replaced. If they are all replaced, the libcloud
184imports should be absent from the Salt Cloud module.
185
186A good example of a non-libcloud driver is the DigitalOcean driver:
187
188https://github.com/saltstack/salt/tree/|repo_primary_branch|/salt/cloud/clouds/digitalocean.py
189
190The ``create()`` Function
191-------------------------
192The ``create()`` function must be created as described in the libcloud-based
193module documentation.
194
195The get_size() Function
196-----------------------
197This function is only necessary for libcloud-based modules, and does not need
198to exist otherwise.
199
200The get_image() Function
201------------------------
202This function is only necessary for libcloud-based modules, and does not need
203to exist otherwise.
204
205The avail_locations() Function
206------------------------------
207This function returns a list of locations available, if the cloud host uses
208multiple data centers. It is not necessary if the cloud host uses only one
209data center. It is normally called using the ``--list-locations`` option.
210
211.. code-block:: bash
212
213    salt-cloud --list-locations my-cloud-provider
214
215The avail_images() Function
216---------------------------
217This function returns a list of images available for this cloud provider. There
218are not currently any known cloud providers that do not provide this
219functionality, though they may refer to images by a different name (for example,
220"templates"). It is normally called using the ``--list-images`` option.
221
222.. code-block:: bash
223
224    salt-cloud --list-images my-cloud-provider
225
226The avail_sizes() Function
227--------------------------
228This function returns a list of sizes available for this cloud provider.
229Generally, this refers to a combination of RAM, CPU, and/or disk space. This
230functionality may not be present on some cloud providers. For example, the
231Parallels module breaks down RAM, CPU, and disk space into separate options,
232whereas in other providers, these options are baked into the image. It is
233normally called using the ``--list-sizes`` option.
234
235.. code-block:: bash
236
237    salt-cloud --list-sizes my-cloud-provider
238
239The script() Function
240---------------------
241This function builds the deploy script to be used on the remote machine.  It is
242likely to be moved into the ``salt.utils.cloud`` library in the near future, as
243it is very generic and can usually be copied wholesale from another module. An
244excellent example is in the Azure driver.
245
246The destroy() Function
247----------------------
248This function irreversibly destroys a virtual machine on the cloud provider.
249Before doing so, it should fire an event on the Salt event bus. The tag for this
250event is ``salt/cloud/<vm name>/destroying``. Once the virtual machine has been
251destroyed, another event is fired. The tag for that event is
252``salt/cloud/<vm name>/destroyed``.
253
254This function is normally called with the ``-d`` options:
255
256.. code-block:: bash
257
258    salt-cloud -d myinstance
259
260The list_nodes() Function
261-------------------------
262This function returns a list of nodes available on this cloud provider, using
263the following fields:
264
265* id (str)
266* image (str)
267* size (str)
268* state (str)
269* private_ips (list)
270* public_ips (list)
271
272No other fields should be returned in this function, and all of these fields
273should be returned, even if empty. The private_ips and public_ips fields should
274always be of a list type, even if empty, and the other fields should always be
275of a str type. This function is normally called with the ``-Q`` option:
276
277.. code-block:: bash
278
279    salt-cloud -Q
280
281The list_nodes_full() Function
282------------------------------
283All information available about all nodes should be returned in this function.
284The fields in the list_nodes() function should also be returned, even if they
285would not normally be provided by the cloud provider. This is because some
286functions both within Salt and 3rd party will break if an expected field is not
287present. This function is normally called with the ``-F`` option:
288
289.. code-block:: bash
290
291    salt-cloud -F
292
293The list_nodes_select() Function
294--------------------------------
295This function returns only the fields specified in the ``query.selection``
296option in ``/etc/salt/cloud``. Because this function is so generic, all of the
297heavy lifting has been moved into the ``salt.utils.cloud`` library.
298
299A function to call ``list_nodes_select()`` still needs to be present. In
300general, the following code can be used as-is:
301
302.. code-block:: python
303
304    def list_nodes_select(call=None):
305        """
306        Return a list of the VMs that are on the provider, with select fields
307        """
308        return salt.utils.cloud.list_nodes_select(
309            list_nodes_full("function"), __opts__["query.selection"], call
310        )
311
312However, depending on the cloud provider, additional variables may be required.
313For instance, some modules use a ``conn`` object, or may need to pass other
314options into ``list_nodes_full()``. In this case, be sure to update the function
315appropriately:
316
317.. code-block:: python
318
319    def list_nodes_select(conn=None, call=None):
320        """
321        Return a list of the VMs that are on the provider, with select fields
322        """
323        if not conn:
324            conn = get_conn()  # pylint: disable=E0602
325
326        return salt.utils.cloud.list_nodes_select(
327            list_nodes_full(conn, "function"), __opts__["query.selection"], call
328        )
329
330This function is normally called with the ``-S`` option:
331
332.. code-block:: bash
333
334    salt-cloud -S
335
336The show_instance() Function
337----------------------------
338This function is used to display all of the information about a single node
339that is available from the cloud provider. The simplest way to provide this is
340usually to call ``list_nodes_full()``, and return just the data for the
341requested node. It is normally called as an action:
342
343.. code-block:: bash
344
345    salt-cloud -a show_instance myinstance
346
347
348Actions and Functions
349=====================
350Extra functionality may be added to a cloud provider in the form of an
351``--action`` or a ``--function``. Actions are performed against a cloud
352instance/virtual machine, and functions are performed against a cloud provider.
353
354Actions
355-------
356Actions are calls that are performed against a specific instance or virtual
357machine. The ``show_instance`` action should be available in all cloud modules.
358Actions are normally called with the ``-a`` option:
359
360.. code-block:: bash
361
362    salt-cloud -a show_instance myinstance
363
364Actions must accept a ``name`` as a first argument, may optionally support any
365number of kwargs as appropriate, and must accept an argument of ``call``, with
366a default of ``None``.
367
368Before performing any other work, an action should normally verify that it has
369been called correctly. It may then perform the desired feature, and return
370useful information to the user. A basic action looks like:
371
372.. code-block:: python
373
374    def show_instance(name, call=None):
375        """
376        Show the details from EC2 concerning an AMI
377        """
378        if call != "action":
379            raise SaltCloudSystemExit(
380                "The show_instance action must be called with -a or --action."
381            )
382
383        return _get_node(name)
384
385Please note that generic kwargs, if used, are passed through to actions as
386``kwargs`` and not ``**kwargs``. An example of this is seen in the Functions
387section.
388
389Functions
390---------
391Functions are called that are performed against a specific cloud provider. An
392optional function that is often useful is ``show_image``, which describes an
393image in detail. Functions are normally called with the ``-f`` option:
394
395.. code-block:: bash
396
397    salt-cloud -f show_image my-cloud-provider image='Ubuntu 13.10 64-bit'
398
399A function may accept any number of kwargs as appropriate, and must accept an
400argument of ``call`` with a default of ``None``.
401
402Before performing any other work, a function should normally verify that it has
403been called correctly. It may then perform the desired feature, and return
404useful information to the user. A basic function looks like:
405
406.. code-block:: python
407
408    def show_image(kwargs, call=None):
409        """
410        Show the details from EC2 concerning an AMI
411        """
412        if call != "function":
413            raise SaltCloudSystemExit(
414                "The show_image action must be called with -f or --function."
415            )
416
417        params = {"ImageId.1": kwargs["image"], "Action": "DescribeImages"}
418        result = query(params)
419        log.info(result)
420
421        return result
422
423Take note that generic kwargs are passed through to functions as ``kwargs`` and
424not ``**kwargs``.
425