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