1# Copyright (c) 2015-2018 Cisco Systems, Inc. 2# 3# Permission is hereby granted, free of charge, to any person obtaining a copy 4# of this software and associated documentation files (the "Software"), to 5# deal in the Software without restriction, including without limitation the 6# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7# sell copies of the Software, and to permit persons to whom the Software is 8# furnished to do so, subject to the following conditions: 9# 10# The above copyright notice and this permission notice shall be included in 11# all copies or substantial portions of the Software. 12# 13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19# DEALINGS IN THE SOFTWARE. 20 21import abc 22import os 23 24from molecule import status 25 26Status = status.get_status() 27 28 29class Base(object): 30 __metaclass__ = abc.ABCMeta 31 32 def __init__(self, config): 33 """ 34 Base initializer for all :ref:`Driver` classes. 35 36 :param config: An instance of a Molecule config. 37 :returns: None 38 """ 39 self._config = config 40 41 @property 42 @abc.abstractmethod 43 def name(self): # pragma: no cover 44 """ 45 Name of the driver and returns a string. 46 47 :returns: str 48 """ 49 pass 50 51 @name.setter 52 @abc.abstractmethod 53 def name(self, value): # pragma: no cover 54 """ 55 Driver name setter and returns None. 56 57 :returns: None 58 """ 59 pass 60 61 @property 62 def testinfra_options(self): 63 """ 64 Testinfra specific options and returns a dict. 65 66 :returns: dict 67 """ 68 return { 69 'connection': 'ansible', 70 'ansible-inventory': self._config.provisioner.inventory_file, 71 } 72 73 @abc.abstractproperty 74 def login_cmd_template(self): # pragma: no cover 75 """ 76 The login command template to be populated by ``login_options`` and 77 returns a string. 78 79 :returns: str 80 """ 81 pass 82 83 @abc.abstractproperty 84 def default_ssh_connection_options(self): # pragma: no cover 85 """ 86 SSH client options and returns a list. 87 88 :returns: list 89 """ 90 pass 91 92 @abc.abstractproperty 93 def default_safe_files(self): # pragma: no cover 94 """ 95 Generated files to be preserved and returns a list. 96 97 :returns: list 98 """ 99 pass 100 101 @abc.abstractmethod 102 def login_options(self, instance_name): # pragma: no cover 103 """ 104 Options used in the login command and returns a dict. 105 106 :param instance_name: A string containing the instance to login to. 107 :returns: dict 108 """ 109 pass 110 111 @abc.abstractmethod 112 def ansible_connection_options(self, instance_name): # pragma: no cover 113 """ 114 Ansible specific connection options supplied to inventory and returns a 115 dict. 116 117 :param instance_name: A string containing the instance to login to. 118 :returns: dict 119 """ 120 pass 121 122 @abc.abstractmethod 123 def sanity_checks(self): 124 """ 125 Sanity checks to ensure the driver can do work successfully. For 126 example, when using the Docker driver, we want to know that the Docker 127 daemon is running and we have the correct Docker Python dependency. 128 Each driver implementation can decide what is the most stable sanity 129 check for itself. 130 131 :returns: None 132 """ 133 pass 134 135 @property 136 def options(self): 137 return self._config.config['driver']['options'] 138 139 @property 140 def instance_config(self): 141 return os.path.join( 142 self._config.scenario.ephemeral_directory, 'instance_config.yml' 143 ) 144 145 @property 146 def ssh_connection_options(self): 147 if self._config.config['driver']['ssh_connection_options']: 148 return self._config.config['driver']['ssh_connection_options'] 149 return self.default_ssh_connection_options 150 151 @property 152 def safe_files(self): 153 return self.default_safe_files + self._config.config['driver']['safe_files'] 154 155 @property 156 def delegated(self): 157 """ 158 Is the driver delegated and returns a bool. 159 160 :returns: bool 161 """ 162 return self.name == 'delegated' 163 164 @property 165 def managed(self): 166 """ 167 Is the driver is managed and returns a bool. 168 169 :returns: bool 170 """ 171 return self.options['managed'] 172 173 def status(self): 174 """ 175 Collects the instances state and returns a list. 176 177 .. important:: 178 179 Molecule assumes all instances were created successfully by 180 Ansible, otherwise Ansible would return an error on create. This 181 may prove to be a bad assumption. However, configuring Molecule's 182 driver to match the options passed to the playbook may prove 183 difficult. Especially in cases where the user is provisioning 184 instances off localhost. 185 :returns: list 186 """ 187 status_list = [] 188 for platform in self._config.platforms.instances: 189 instance_name = platform['name'] 190 driver_name = self.name 191 provisioner_name = self._config.provisioner.name 192 scenario_name = self._config.scenario.name 193 194 status_list.append( 195 Status( 196 instance_name=instance_name, 197 driver_name=driver_name, 198 provisioner_name=provisioner_name, 199 scenario_name=scenario_name, 200 created=self._created(), 201 converged=self._converged(), 202 ) 203 ) 204 205 return status_list 206 207 def _get_ssh_connection_options(self): 208 return [ 209 '-o UserKnownHostsFile=/dev/null', 210 '-o ControlMaster=auto', 211 '-o ControlPersist=60s', 212 '-o IdentitiesOnly=yes', 213 '-o StrictHostKeyChecking=no', 214 ] 215 216 def _created(self): 217 return str(self._config.state.created).lower() 218 219 def _converged(self): 220 return str(self._config.state.converged).lower() 221