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 21from molecule import logger 22from molecule import util 23from molecule.driver import base 24 25LOG = logger.get_logger(__name__) 26 27 28class Delegated(base.Base): 29 """ 30 The class responsible for managing delegated instances. Delegated is `not` 31 the default driver used in Molecule. 32 33 Under this driver, it is the developers responsibility to implement the 34 create and destroy playbooks. ``Managed`` is the default behaviour of all 35 drivers. 36 37 .. code-block:: yaml 38 39 driver: 40 name: delegated 41 42 However, the developer must adhere to the instance-config API. The 43 developer's create playbook must provide the following instance-config 44 data, and the developer's destroy playbook must reset the instance-config. 45 46 .. code-block:: yaml 47 48 - address: ssh_endpoint 49 identity_file: ssh_identity_file # mutually exclusive with password 50 instance: instance_name 51 port: ssh_port_as_string 52 user: ssh_user 53 password: ssh_password # mutually exclusive with identity_file 54 become_method: valid_ansible_become_method # optional 55 become_pass: password_if_required # optional 56 57 - address: winrm_endpoint 58 instance: instance_name 59 connection: 'winrm' 60 port: winrm_port_as_string 61 user: winrm_user 62 password: winrm_password 63 winrm_transport: ntlm/credssp/kerberos 64 winrm_cert_pem: <path to the credssp public certificate key> 65 winrm_cert_key_pem: <path to the credssp private certificate key> 66 winrm_server_cert_validation: True/False 67 68 This article covers how to configure and use WinRM with Ansible: 69 https://docs.ansible.com/ansible/latest/user_guide/windows_winrm.html 70 71 Molecule can also skip the provisioning/deprovisioning steps. It is the 72 developers responsibility to manage the instances, and properly configure 73 Molecule to connect to said instances. 74 75 .. code-block:: yaml 76 77 driver: 78 name: delegated 79 options: 80 managed: False 81 login_cmd_template: 'docker exec -ti {instance} bash' 82 ansible_connection_options: 83 ansible_connection: docker 84 platforms: 85 - name: instance-docker 86 87 .. code-block:: bash 88 89 $ docker run \\ 90 -d \\ 91 --name instance-docker \\ 92 --hostname instance-docker \\ 93 -it molecule_local/ubuntu:latest sleep infinity & wait 94 95 Use Molecule with delegated instances, which are accessible over ssh. 96 97 .. important:: 98 99 It is the developer's responsibility to configure the ssh config file. 100 101 .. code-block:: yaml 102 103 driver: 104 name: delegated 105 options: 106 managed: False 107 login_cmd_template: 'ssh {instance} -F /tmp/ssh-config' 108 ansible_connection_options: 109 ansible_connection: ssh 110 ansible_ssh_common_args: '-F /path/to/ssh-config' 111 platforms: 112 - name: instance-vagrant 113 114 Provide the files Molecule will preserve post ``destroy`` action. 115 116 .. code-block:: yaml 117 118 driver: 119 name: delegated 120 safe_files: 121 - foo 122 123 And in order to use localhost as molecule's target: 124 125 .. code-block:: yaml 126 127 driver: 128 name: delegated 129 options: 130 managed: False 131 ansible_connection_options: 132 ansible_connection: local 133 """ 134 135 def __init__(self, config): 136 super(Delegated, self).__init__(config) 137 self._name = 'delegated' 138 139 @property 140 def name(self): 141 return self._name 142 143 @name.setter 144 def name(self, value): 145 self._name = value 146 147 @property 148 def login_cmd_template(self): 149 if self.managed: 150 connection_options = ' '.join(self.ssh_connection_options) 151 152 return ( 153 'ssh {{address}} ' 154 '-l {{user}} ' 155 '-p {{port}} ' 156 '-i {{identity_file}} ' 157 '{}' 158 ).format(connection_options) 159 return self.options['login_cmd_template'] 160 161 @property 162 def default_safe_files(self): 163 return [] 164 165 @property 166 def default_ssh_connection_options(self): 167 if self.managed: 168 return self._get_ssh_connection_options() 169 return [] 170 171 def login_options(self, instance_name): 172 if self.managed: 173 d = {'instance': instance_name} 174 175 return util.merge_dicts(d, self._get_instance_config(instance_name)) 176 return {'instance': instance_name} 177 178 def ansible_connection_options(self, instance_name): 179 if self.managed: 180 try: 181 d = self._get_instance_config(instance_name) 182 conn_dict = {} 183 conn_dict['ansible_user'] = d.get('user') 184 conn_dict['ansible_host'] = d.get('address') 185 conn_dict['ansible_port'] = d.get('port') 186 conn_dict['ansible_connection'] = d.get('connection', 'smart') 187 if d.get('become_method'): 188 conn_dict['ansible_become_method'] = d.get('become_method') 189 if d.get('become_pass'): 190 conn_dict['ansible_become_pass'] = d.get('become_pass') 191 if d.get('identity_file'): 192 conn_dict['ansible_private_key_file'] = d.get('identity_file') 193 conn_dict['ansible_ssh_common_args'] = ' '.join( 194 self.ssh_connection_options 195 ) 196 if d.get('password'): 197 conn_dict['ansible_password'] = d.get('password') 198 if d.get('winrm_transport'): 199 conn_dict['ansible_winrm_transport'] = d.get('winrm_transport') 200 if d.get('winrm_cert_pem'): 201 conn_dict['ansible_winrm_cert_pem'] = d.get('winrm_cert_pem') 202 if d.get('winrm_cert_key_pem'): 203 conn_dict['ansible_winrm_cert_key_pem'] = d.get( 204 'winrm_cert_key_pem' 205 ) 206 if d.get('winrm_server_cert_validation'): 207 conn_dict['ansible_winrm_server_cert_validation'] = d.get( 208 'winrm_server_cert_validation' 209 ) 210 211 return conn_dict 212 213 except StopIteration: 214 return {} 215 except IOError: 216 # Instance has yet to be provisioned , therefore the 217 # instance_config is not on disk. 218 return {} 219 return self.options['ansible_connection_options'] 220 221 def _created(self): 222 if self.managed: 223 return super(Delegated, self)._created() 224 return 'unknown' 225 226 def _get_instance_config(self, instance_name): 227 instance_config_dict = util.safe_load_file(self._config.driver.instance_config) 228 229 return next( 230 item for item in instance_config_dict if item['instance'] == instance_name 231 ) 232 233 def sanity_checks(self): 234 # Note(decentral1se): Cannot implement driver specifics are unknown 235 pass 236