├── README.md ├── test_http ├── test_process └── test_tcp /README.md: -------------------------------------------------------------------------------- 1 | # ansible-testing 2 | 3 | ansible-testing is designed to be a set of modules for Ansible to allow 4 | infrastructure testing. 5 | 6 | Hopefully these modules will be absorbed into [Ansible](http://github.com/ansible/ansible) 7 | if the concept proves itself 8 | 9 | ## Installation 10 | 11 | ``` 12 | git clone http://github.com/willthames/ansible-testing 13 | ``` 14 | 15 | ## Usage 16 | ``` 17 | ansible -M path/to/ansible-testing testing-playbook.yml 18 | ``` 19 | 20 | ## Implemented 21 | 22 | ``` 23 | name: python is running app.py 24 | test_process: 25 | state: present 26 | name: python 27 | args: 28 | - app.py 29 | 30 | name: port 80 appears open from playbook machine 31 | local_action: test_tcp host=example.com state=open port=80 32 | 33 | name: check HTTP response has 200 status and contains Hello 34 | local_action: test_http url=http://example.com/helloworld status=200 regex='.*Hello.*' 35 | 36 | ``` 37 | 38 | # See also 39 | 40 | * [assert module](http://docs.ansible.com/assert_module.html) 41 | * [stat module](http://docs.ansible.com/stat_module.html) 42 | * [Testing Strategies](http://docs.ansible.com/test_strategies.html) 43 | 44 | ## Example 45 | 46 | ``` 47 | name: run hello command 48 | action: command echo -n hello 49 | register: hello_cmd 50 | 51 | name: check results of hello command 52 | assert: 53 | that: 54 | - "hello_cmd.stdout == 'hello'" 55 | - "hello_cmd.rc == 0" 56 | ``` 57 | 58 | 59 | -------------------------------------------------------------------------------- /test_http: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2014, Will Thames 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: test_http 25 | version_added: 1.7 26 | short_description: Check the results of an http connection 27 | description: 28 | - Check that the results of an HTTP request match the expected results 29 | options: 30 | url: 31 | description: 32 | - URL to connect to 33 | required: True 34 | default: null 35 | status: 36 | description: 37 | - expected HTTP status 38 | required: False 39 | default: null 40 | regex: 41 | description: 42 | - content to match in the response 43 | default: null 44 | required: False 45 | timeout: 46 | description: 47 | - duration of timeout 48 | required: false 49 | default: 10 50 | choices: ['yes', 'no'] 51 | author: Will Thames 52 | 53 | ''' 54 | 55 | EXAMPLES = ''' 56 | # Check if HTTP response contains 'Hello' 57 | test_http: url=http://example.com/helloworld regex='.*Hello.*' 58 | 59 | # Check if HTTP response code is 200 60 | test_http: url=http://example.com/helloworld status=200 61 | ''' 62 | def main(): 63 | argument_spec = url_argument_spec() 64 | argument_spec.update(dict( 65 | status=dict(required=False, type='int', default=None), 66 | regex=dict(required=False, default=None), 67 | timeout=dict(required=False, type='int', default=10), 68 | )) 69 | module = AnsibleModule( 70 | argument_spec=argument_spec, 71 | supports_check_mode=True 72 | ) 73 | url = module.params.get('url') 74 | status = module.params.get('status') 75 | regex = module.params.get('regex') 76 | timeout = module.params.get('timeout') 77 | 78 | (r, info) = fetch_url(module, url, timeout=timeout) 79 | 80 | response = r.read() 81 | if status and info['status'] != status: 82 | module.fail_json(msg='Status %s does not match expected status %s' % (info['status'], status)) 83 | 84 | truncate = (response[:97] + '...') if len(response) > 100 else response 85 | if regex: 86 | if not re.search(regex, response): 87 | module.fail_json(msg='Response %s did not match the regex %s' % (truncate, regex)) 88 | 89 | module.exit_json(info=info, response=truncate) 90 | 91 | from ansible.module_utils.basic import * 92 | from ansible.module_utils.urls import * 93 | 94 | main() 95 | 96 | -------------------------------------------------------------------------------- /test_process: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2014, Will Thames 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: test_process 25 | version_added: 1.5 26 | short_description: Check the status of a process 27 | description: 28 | - Check if a process is running or not 29 | options: 30 | name: 31 | description: 32 | - name of the process to check 33 | required: True 34 | default: null 35 | args: 36 | description: 37 | - list of one or more arguments to look for 38 | required: False 39 | default: [] 40 | state: 41 | description: 42 | - state of the process 43 | choices: [ 'present', 'absent' ] 44 | default: present 45 | author: Will Thames 46 | ''' 47 | 48 | EXAMPLES = ''' 49 | # Check if python app is running 50 | test_process: name=python state=present args=['app.py'] 51 | ''' 52 | 53 | def check_process_for_arg(process, arg): 54 | return arg in ' '.join(process.split()[1:]) 55 | 56 | 57 | def check_process(process, name, args): 58 | if not re.match(r'([^ ]*/)?%s\b' % name, process): 59 | return False 60 | if not args: 61 | return True 62 | for arg in args: 63 | if not check_process_for_arg(process, arg): 64 | return False 65 | return True 66 | 67 | 68 | def main(): 69 | module = AnsibleModule( 70 | argument_spec = dict( 71 | name=dict(required=True, default=None), 72 | args=dict(type='list', required=False, default=[]), 73 | state=dict(choices=['present', 'absent'], default='present'), 74 | ), 75 | supports_check_mode=True 76 | ) 77 | name = module.params.get('name') 78 | args = module.params.get('args') 79 | state = module.params.get('state') 80 | 81 | # Would like this to be more cross platform 82 | rc, stdout, stderr = module.run_command(['ps', 'axww', '-o', 'command']) 83 | processes = stdout.split('\n')[1:] 84 | 85 | process_running = filter(lambda x: check_process(x, name, args), processes) 86 | if state == 'present': 87 | if process_running: 88 | module.exit_json() 89 | else: 90 | module.fail_json(msg="Process with name %s not found with arguments %s" % (name,args)) 91 | if state == 'absent': 92 | if process_running: 93 | module.fail_json(msg="Process with name %s was unexpectedly found" % name) 94 | else: 95 | module.exit_json() 96 | 97 | from ansible.module_utils.basic import * 98 | 99 | main() 100 | 101 | -------------------------------------------------------------------------------- /test_tcp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # (c) 2014, Will Thames 5 | # 6 | # This file is part of Ansible 7 | # 8 | # Ansible is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # Ansible is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with Ansible. If not, see . 20 | 21 | 22 | DOCUMENTATION = ''' 23 | --- 24 | module: test_tcp 25 | version_added: 1.5 26 | short_description: Check if a tcp connection can be made 27 | description: 28 | - Check if a TCP connection can be made to a host on a port 29 | options: 30 | host: 31 | description: 32 | - host to connect to 33 | required: True 34 | default: null 35 | port: 36 | description: 37 | - port to connect to 38 | required: True 39 | default: null 40 | state: 41 | description: 42 | - state of the port 43 | choices: [ 'open', 'closed' ] 44 | default: open 45 | timeout: 46 | description: 47 | - time in seconds to wait for a response 48 | required: False 49 | default: 5 50 | author: Will Thames 51 | ''' 52 | 53 | EXAMPLES = ''' 54 | # Check if port 80 is open on a server 55 | test_tcp: port=80 state=open timeout=10 56 | ''' 57 | import socket 58 | 59 | def main(): 60 | module = AnsibleModule( 61 | argument_spec = dict( 62 | host=dict(required=True, default=None), 63 | port=dict(required=True, default=None), 64 | state=dict(choices=['open', 'closed'], default='open'), 65 | timeout=dict(required=False, default=5.0, type='float'), 66 | ), 67 | supports_check_mode=True 68 | ) 69 | host = module.params.get('host') 70 | port = module.params.get('port') 71 | state = module.params.get('state') 72 | timeout = module.params.get('timeout') 73 | 74 | s = None 75 | for res in socket.getaddrinfo(module.params.get('host'), module.params.get('port'), 76 | socket.AF_UNSPEC, socket.SOCK_STREAM): 77 | af, socktype, proto, canonname, sa = res 78 | try: 79 | s = socket.socket(af, socktype, proto) 80 | except socket.error, msg: 81 | s = None 82 | continue 83 | try: 84 | s.settimeout(timeout) 85 | s.connect(sa) 86 | except socket.error, msg: 87 | s.close() 88 | s = None 89 | continue 90 | break 91 | 92 | if s is None: 93 | if state == 'open': 94 | module.fail_json(msg='Could not connect to %s on port %s' % (host, port)) 95 | if state == 'closed': 96 | module.exit_json() 97 | else: 98 | if state == 'open': 99 | module.exit_json() 100 | if state == 'closed': 101 | module.fail_json(msg='Connection to %s on port %s should not have succeeded' % (host, port)) 102 | s.close() 103 | 104 | from ansible.module_utils.basic import * 105 | 106 | main() 107 | 108 | --------------------------------------------------------------------------------