first
This commit is contained in:
commit
b50fd16591
197 changed files with 41663 additions and 0 deletions
90
overlay/Linux/usr/local/share/ansible/library/Makefile
Normal file
90
overlay/Linux/usr/local/share/ansible/library/Makefile
Normal file
|
@ -0,0 +1,90 @@
|
|||
# -*- mode: Makefile; tab-width: 8; coding: utf-8-unix -*-
|
||||
|
||||
PENV=env http_proxy=http://127.0.0.1:3128 \
|
||||
https_proxy=http://127.0.0.1:3128 \
|
||||
no_proxy="localhost,127.0.0.1"
|
||||
NENV=env no_proxy=localhost,127.0.0.1 \
|
||||
https_proxy= socks_proxy= http_proxy= \
|
||||
|
||||
default:: test
|
||||
|
||||
.SUFFIXES: .yml .json
|
||||
|
||||
.yml.json:
|
||||
yaml2json.bash < $< > $@
|
||||
|
||||
test:: test_test test_net test_local
|
||||
test_net::
|
||||
$(MAKE) $(MFLAGS) TARGET=ipleak.net PDB="" ENV="$(PENV)" targets cleanup
|
||||
$(MAKE) $(MFLAGS) TARGET=python.org PDB="" ENV="$(PENV)" targets cleanup
|
||||
test_local::
|
||||
$(MAKE) $(MFLAGS) TARGET=polipo PDB="" ENV="" targets cleanup
|
||||
test_test::
|
||||
which firefox || exit 0
|
||||
$(NENV) /var/local/bin/python2.bash selenium_test.py test
|
||||
$(MAKE) $(MFLAGS) cleanup
|
||||
test_keepassxc::
|
||||
[ ! -f /home/devuan/Passwords.kdbx ] || \
|
||||
( cd .. ; \
|
||||
ANSIBLE_KEEPASSXC_PASSWORD=foobar ansible -i hosts.yml \
|
||||
-c local -m ansible-keepassxc \
|
||||
-a 'database=/home/devuan/Passwords.kdbx entry=test_h@creep.im group=/Root/Xmpp/Chat' \
|
||||
localhost )
|
||||
|
||||
debug:: tests/selenium_test-python.org-firefox.json
|
||||
$(PENV) /var/local/bin/python2.bash -m pdb selenium_test.py test
|
||||
$(PENV) /var/local/bin/python2.bash -m pdb selenium_test.py $<
|
||||
|
||||
cleanup::
|
||||
@ps ax | grep -v grep | grep geckodriver && killall geckodriver /usr/bin/geckodriver 2>/dev/null || true
|
||||
@ps ax | grep -v grep | grep /firefox && killall /usr/lib64/firefox/firefox 2>/dev/null || true
|
||||
@ps ax | grep -v grep | grep phantomjs && killall phantomjs 2>/dev/null || true
|
||||
|
||||
targets::
|
||||
# [ -z "${DISPLAY}" ] && xvfb-run $(MAKE) $(MFLAGS) TARGET=$(TARGET) ENV="" /tmp/firefox-$(TARGET).log \
|
||||
# || $(MAKE) $(MFLAGS) TARGET=$(TARGET) ENV="$(ENV)" /tmp/firefox-$(TARGET).log
|
||||
$(MAKE) $(MFLAGS) TARGET=$(TARGET) ENV="$(ENV) MOZ_HEADLESS=1" /tmp/firefox-$(TARGET).log
|
||||
$(MAKE) $(MFLAGS) TARGET=$(TARGET) ENV="$(ENV)" /tmp/phantomjs-$(TARGET).log
|
||||
|
||||
/tmp/chromium-$(TARGET).log::
|
||||
|
||||
/tmp/firefox-$(TARGET).log:: tests/selenium_test-$(TARGET)-firefox.json clean
|
||||
rm -f /tmp/test_ipleak.net_firefox_*
|
||||
$(ENV) /var/local/bin/python2.bash $(PDB) selenium_test.py tests/selenium_test-$(TARGET)-firefox.json > \
|
||||
/tmp/firefox-$(TARGET).out \
|
||||
&& jq .results < /tmp/firefox-$(TARGET).out > /tmp/firefox-$(TARGET).json \
|
||||
|| echo ERROR: $@
|
||||
[ ! -f /tmp/firefox-$(TARGET).log ] || cat /tmp/firefox-$(TARGET).log
|
||||
[ ! -f /tmp/geckodriver-$(TARGET).log ] || cat /tmp/geckodriver-$(TARGET).log
|
||||
[ ! -f /tmp/test_$(TARGET)_failed.png ] || fbi /tmp/test_$(TARGET)_failed.png
|
||||
|
||||
/tmp/waterfox-$(TARGET).log:: tests/selenium_test-$(TARGET)-waterfox.json clean
|
||||
rm -f /tmp/test_ipleak.net_waterfox_*
|
||||
$(ENV) /var/local/bin/python2.bash $(PDB) selenium_test.py tests/selenium_test-$(TARGET)-waterfox.json > \
|
||||
/tmp/waterfox-$(TARGET).out \
|
||||
&& jq .results < /tmp/waterfox-$(TARGET).out > /tmp/waterfox-$(TARGET).out \
|
||||
|| echo ERROR: $@
|
||||
[ ! -f /tmp/waterfox-$(TARGET).log ] || cat /tmp/waterfox-$(TARGET).log
|
||||
[ ! -f /tmp/geckodriver-$(TARGET).log ] || cat /tmp/geckodriver-$(TARGET).log
|
||||
[ ! -f /tmp/test_$(TARGET)_failed.png ] || fbi /tmp/test_$(TARGET)_failed.png
|
||||
|
||||
/tmp/phantomjs-$(TARGET).log:: tests/selenium_test-$(TARGET)-phantomjs.json clean
|
||||
rm -f /tmp/test_ipleak.net_phantomjs_*
|
||||
$(ENV) /var/local/bin/python2.bash $(PDB) selenium_test.py tests/selenium_test-$(TARGET)-phantomjs.json > \
|
||||
/tmp/phantomjs-$(TARGET).out \
|
||||
&& jq .results < /tmp/phantomjs-$(TARGET).out > /tmp/phantomjs-$(TARGET).json \
|
||||
|| echo ERROR: $@
|
||||
[ ! -f /tmp/phantomjs-$(TARGET).log ] || cat /tmp/phantomjs-$(TARGET).log
|
||||
[ ! -f /tmp/ghostdriver-$(TARGET).log ] || cat /tmp/ghostdriver-$(TARGET).log
|
||||
[ ! -f /tmp/test_$(TARGET)_failed.png ] || fbi /tmp/test_$(TARGET)_failed.png
|
||||
@ps ax | grep -v grep | grep phantomjs && killall phantomjs 2>/dev/null || true
|
||||
|
||||
tests/selenium_test-$(TARGET)-phantomjs.json:: tests/selenium_test-$(TARGET)-firefox.json
|
||||
sed -e 's/geckodriver/ghostdriver/' -e 's/firefox/phantomjs/' < $< > $@
|
||||
|
||||
|
||||
clean::
|
||||
@rm -f /tmp/firefox-$(TARGET).log /tmp/geckodriver-$(TARGET).log
|
||||
@rm -f /tmp/phantomjs-$(TARGET).log /tmp/ghostdriver-$(TARGET).log
|
||||
@rm -f /tmp/test_$(TARGET)_failed.png
|
||||
@rm -f geckodriver.log ghostdriver.log
|
1
overlay/Linux/usr/local/share/ansible/library/ansible-keepassxc
Symbolic link
1
overlay/Linux/usr/local/share/ansible/library/ansible-keepassxc
Symbolic link
|
@ -0,0 +1 @@
|
|||
ansible-keepassxc.py
|
190
overlay/Linux/usr/local/share/ansible/library/ansible-keepassxc.py
Executable file
190
overlay/Linux/usr/local/share/ansible/library/ansible-keepassxc.py
Executable file
|
@ -0,0 +1,190 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import traceback
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
|
||||
IMPORT_ERR = None
|
||||
try:
|
||||
# import _argon2_xffi_bindings
|
||||
import pykeepass as keepass
|
||||
except ImportError:
|
||||
IMPORT_ERR = traceback.format_exc()
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ansible-keepassxc
|
||||
|
||||
short_description: Module to read credentials from KeePassXC
|
||||
|
||||
version_added: "0.0.1"
|
||||
|
||||
description: Module to read credentials from KeePassXC
|
||||
|
||||
options:
|
||||
database:
|
||||
description: Path to database file
|
||||
required: true
|
||||
type: str
|
||||
password:
|
||||
description: Database Password
|
||||
required: true
|
||||
type: str
|
||||
keyfile:
|
||||
description: Path to key file
|
||||
required: false
|
||||
type: str
|
||||
entry:
|
||||
description: Entry name for the attribute to fetch
|
||||
required: true
|
||||
type: str
|
||||
group:
|
||||
decription: Group name that the Entry belongs to
|
||||
required: false
|
||||
type: str
|
||||
|
||||
author:
|
||||
- Jeremy Lumley (@jlumley)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Fetch the credentials for the server_1 entry in any group
|
||||
- name: Fetch server_1 credentials
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "s3cure_p4550rd"
|
||||
entry: "server_1"
|
||||
|
||||
# Fetch the reddit entry in the social group
|
||||
- name: Fetching reddit credentials
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "sup3r_s3cure_p4550rd"
|
||||
entry: "reddit"
|
||||
group: "social"
|
||||
|
||||
# Fetch a custom strig attribute from the github entry
|
||||
- name: Fetch Github API Token
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "d0pe_s3cure_p4550rd"
|
||||
keyfile: "/secrets/top_secret_key"
|
||||
entry: "github"
|
||||
group: "development"
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
# Return values
|
||||
username:
|
||||
description: Username of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 's3cr3t_us3r'
|
||||
password:
|
||||
description: Password of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 's3cr3t_p455word'
|
||||
url:
|
||||
description: Url of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 'http://reddit.com'
|
||||
custom_fields:
|
||||
description: dictionary containing all custom fields
|
||||
type: dict
|
||||
returned: always
|
||||
sample: False
|
||||
no_log:
|
||||
description: suppress logging of password
|
||||
type: bool
|
||||
returned: never
|
||||
sample: False
|
||||
'''
|
||||
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
database = dict(type='str', required=True),
|
||||
password = dict(type='str', required=False,
|
||||
default=os.environ.get('ANSIBLE_KEEPASSXC_PASSWORD')),
|
||||
keyfile = dict(type='str', required=False, default=None),
|
||||
entry = dict(type='str', required=True),
|
||||
group = dict(type='str', required=False),
|
||||
no_log = dict(type='bool', required=False, default=False),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
result = dict(
|
||||
changed=False,
|
||||
username='',
|
||||
password='',
|
||||
url='',
|
||||
custom_fields={}
|
||||
)
|
||||
|
||||
# Currently no support for a check_mode this maybe added later if
|
||||
# functionality to modify the database is added later
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=False,
|
||||
no_log=False
|
||||
)
|
||||
|
||||
if IMPORT_ERR:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("pykeepass"),
|
||||
exception=IMPORT_ERR
|
||||
)
|
||||
|
||||
# unlock local keepass database
|
||||
try:
|
||||
kp = keepass.PyKeePass(
|
||||
module.params['database'],
|
||||
password=module.params['password'],
|
||||
keyfile=module.params['keyfile'])
|
||||
except keepass.exceptions.CredentialsError:
|
||||
module.fail_json(msg='Invalid Credentials')
|
||||
|
||||
# find entry
|
||||
entry = kp.find_entries(
|
||||
title=module.params['entry'],
|
||||
group=module.params['group']
|
||||
)
|
||||
|
||||
# fail is entry is not present
|
||||
if not entry:
|
||||
module.fail_json(msg=f"Unable to find entry: {module.params['entry']}")
|
||||
|
||||
else:
|
||||
entry = entry[0]
|
||||
custom_field_keys = entry._get_string_field_keys(exclude_reserved=True)
|
||||
custom_fields = dict()
|
||||
for key in custom_field_keys:
|
||||
custom_fields[key] = entry.get_custom_property(key)
|
||||
result = dict (
|
||||
changed=False,
|
||||
username=entry.username,
|
||||
password=entry.password,
|
||||
url=entry.url,
|
||||
custom_fields=custom_fields
|
||||
)
|
||||
|
||||
# in the event of a successful module execution, you will want to
|
||||
# simple AnsibleModule.exit_json(), passing the key/value results
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
*** ansible-keepassxc.py.dst 2022-04-07 15:14:39.222017589 +0000
|
||||
--- ansible-keepassxc.py 2022-04-07 15:12:28.010013156 +0000
|
||||
***************
|
||||
*** 2,11 ****
|
||||
--- 2,22 ----
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
+ import os
|
||||
+ import traceback
|
||||
+ from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
+
|
||||
+ IMPORT_ERR = None
|
||||
+ try:
|
||||
+ import _argon2_xffi_bindings
|
||||
+ import pykeepass as keepass
|
||||
+ except ImportError:
|
||||
+ IMPORT_ERR = traceback.format_exc()
|
||||
+
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ansible-keepassxc
|
||||
|
||||
short_description: Module to read credentials from KeePassXC
|
||||
***************
|
||||
*** 89,112 ****
|
||||
type: dict
|
||||
returned: always
|
||||
sample: False
|
||||
'''
|
||||
|
||||
- from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
- import traceback
|
||||
-
|
||||
- IMPORT_ERR = None
|
||||
- try:
|
||||
- import pykeepass as keepass
|
||||
- except ImportError:
|
||||
- IMPORT_ERR = traceback.format_exc()
|
||||
-
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
database = dict(type='str', required=True),
|
||||
! password = dict(type='str', required=True),
|
||||
keyfile = dict(type='str', required=False, default=None),
|
||||
entry = dict(type='str', required=True),
|
||||
group = dict(type='str', required=False),
|
||||
)
|
||||
|
||||
--- 100,115 ----
|
||||
type: dict
|
||||
returned: always
|
||||
sample: False
|
||||
'''
|
||||
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
database = dict(type='str', required=True),
|
||||
! password = dict(type='str', required=False,
|
||||
! default=os.environ.get('ANSIBLE_KEEPASSXC_PASSWORD')),
|
||||
keyfile = dict(type='str', required=False, default=None),
|
||||
entry = dict(type='str', required=True),
|
||||
group = dict(type='str', required=False),
|
||||
)
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ansible-keepassxc
|
||||
|
||||
short_description: Module to read credentials from KeePassXC
|
||||
|
||||
version_added: "0.0.1"
|
||||
|
||||
description: Module to read credentials from KeePassXC
|
||||
|
||||
options:
|
||||
database:
|
||||
description: Path to database file
|
||||
required: true
|
||||
type: str
|
||||
password:
|
||||
description: Database Password
|
||||
required: true
|
||||
type: str
|
||||
keyfile:
|
||||
description: Path to key file
|
||||
required: false
|
||||
type: str
|
||||
entry:
|
||||
description: Entry name for the attribute to fetch
|
||||
required: true
|
||||
type: str
|
||||
group:
|
||||
decription: Group name that the Entry belongs to
|
||||
required: false
|
||||
type: str
|
||||
|
||||
author:
|
||||
- Jeremy Lumley (@jlumley)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Fetch the credentials for the server_1 entry in any group
|
||||
- name: Fetch server_1 credentials
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "s3cure_p4550rd"
|
||||
entry: "server_1"
|
||||
|
||||
# Fetch the reddit entry in the social group
|
||||
- name: Fetching reddit credentials
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "sup3r_s3cure_p4550rd"
|
||||
entry: "reddit"
|
||||
group: "social"
|
||||
|
||||
# Fetch a custom strig attribute from the github entry
|
||||
- name: Fetch Github API Token
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "d0pe_s3cure_p4550rd"
|
||||
keyfile: "/secrets/top_secret_key"
|
||||
entry: "github"
|
||||
group: "development"
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
# Return values
|
||||
username:
|
||||
description: Username of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 's3cr3t_us3r'
|
||||
password:
|
||||
description: Password of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 's3cr3t_p455word'
|
||||
url:
|
||||
description: Url of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 'http://reddit.com'
|
||||
custom_fields:
|
||||
description: dictionary containing all custom fields
|
||||
type: dict
|
||||
returned: always
|
||||
sample: False
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
import traceback
|
||||
|
||||
IMPORT_ERR = None
|
||||
try:
|
||||
import pykeepass as keepass
|
||||
except ImportError:
|
||||
IMPORT_ERR = traceback.format_exc()
|
||||
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
database = dict(type='str', required=True),
|
||||
password = dict(type='str', required=True),
|
||||
keyfile = dict(type='str', required=False, default=None),
|
||||
entry = dict(type='str', required=True),
|
||||
group = dict(type='str', required=False),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
result = dict(
|
||||
changed=False,
|
||||
username='',
|
||||
password='',
|
||||
url='',
|
||||
custom_fields={}
|
||||
)
|
||||
|
||||
# Currently no support for a check_mode this maybe added later if
|
||||
# functionality to modify the database is added later
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
if IMPORT_ERR:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("pykeepass"),
|
||||
exception=IMPORT_ERR
|
||||
)
|
||||
|
||||
# unlock local keepass database
|
||||
try:
|
||||
kp = keepass.PyKeePass(
|
||||
module.params['database'],
|
||||
password=module.params['password'],
|
||||
keyfile=module.params['keyfile'])
|
||||
except keepass.exceptions.CredentialsError:
|
||||
module.fail_json(msg='Invalid Credentials')
|
||||
|
||||
# find entry
|
||||
entry = kp.find_entries(
|
||||
title=module.params['entry'],
|
||||
group=module.params['group']
|
||||
)
|
||||
|
||||
# fail is entry is not present
|
||||
if not entry:
|
||||
module.fail_json(msg=f"Unable to find entry: {module.params['entry']}")
|
||||
|
||||
else:
|
||||
entry = entry[0]
|
||||
custom_field_keys = entry._get_string_field_keys(exclude_reserved=True)
|
||||
custom_fields = dict()
|
||||
for key in custom_field_keys:
|
||||
custom_fields[key] = entry.get_custom_property(key)
|
||||
result = dict (
|
||||
changed=False,
|
||||
username=entry.username,
|
||||
password=entry.password,
|
||||
url=entry.url,
|
||||
custom_fields=custom_fields
|
||||
)
|
||||
|
||||
# in the event of a successful module execution, you will want to
|
||||
# simple AnsibleModule.exit_json(), passing the key/value results
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://github.com/jlumley/ansible-keepassxc/raw/main/keepassxc.py
|
93
overlay/Linux/usr/local/share/ansible/library/eselect
Normal file
93
overlay/Linux/usr/local/share/ansible/library/eselect
Normal file
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2014, Jakub Jirutka <jakub@jirutka.cz>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: eselect
|
||||
author: Jakub Jirutka
|
||||
version_added: "unknown"
|
||||
short_description: Module for Gentoo's eselect
|
||||
description:
|
||||
- Module for Gentoo's multi-purpose configuration and management tool eselect.
|
||||
options:
|
||||
module:
|
||||
description:
|
||||
- Name of the eselect module to run.
|
||||
required: true
|
||||
action:
|
||||
description:
|
||||
- Action of the eselect module to run.
|
||||
default: set
|
||||
options:
|
||||
description:
|
||||
- An optional options for the eselect module (space separated).
|
||||
required: false
|
||||
aliases: [value, target]
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- eselect: module=editor target=/usr/bin/vim
|
||||
|
||||
- eselect: module=postgresql action=reset
|
||||
'''
|
||||
|
||||
|
||||
def run_eselect(module, *args):
|
||||
cmd = 'eselect --brief --colour=no %s' % ' '.join(args)
|
||||
rc, out, err = module.run_command(cmd)
|
||||
if rc != 0:
|
||||
module.fail_json(cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
msg='eselect failed')
|
||||
else:
|
||||
return out
|
||||
|
||||
|
||||
def action_set(module, emodule, target):
|
||||
current = run_eselect(module, emodule, 'show').strip()
|
||||
if target != current:
|
||||
run_eselect(module, emodule, 'set', target)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec={
|
||||
'module': {'required': True},
|
||||
'action': {'default': 'set'},
|
||||
'options': {'aliases': ['value', 'target'], 'default': ''}
|
||||
}
|
||||
)
|
||||
|
||||
emodule, action, options = (module.params[key] for key in ['module', 'action', 'options'])
|
||||
changed = True
|
||||
msg = ''
|
||||
|
||||
if action == 'set':
|
||||
changed = action_set(module, emodule, options)
|
||||
else:
|
||||
msg = run_eselect(module, emodule, action, options)
|
||||
|
||||
module.exit_json(changed=changed, msg=msg)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
|
@ -0,0 +1,4 @@
|
|||
1649339287010 geckodriver INFO geckodriver 0.20.0
|
||||
1649339287356 geckodriver INFO Listening on 127.0.0.1:59175
|
||||
1649339399259 geckodriver INFO geckodriver 0.20.0
|
||||
1649339399263 geckodriver INFO Listening on 127.0.0.1:15991
|
180
overlay/Linux/usr/local/share/ansible/library/keepassxc.py
Normal file
180
overlay/Linux/usr/local/share/ansible/library/keepassxc.py
Normal file
|
@ -0,0 +1,180 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: ansible-keepassxc
|
||||
|
||||
short_description: Module to read credentials from KeePassXC
|
||||
|
||||
version_added: "0.0.1"
|
||||
|
||||
description: Module to read credentials from KeePassXC
|
||||
|
||||
options:
|
||||
database:
|
||||
description: Path to database file
|
||||
required: true
|
||||
type: str
|
||||
password:
|
||||
description: Database Password
|
||||
required: true
|
||||
type: str
|
||||
keyfile:
|
||||
description: Path to key file
|
||||
required: false
|
||||
type: str
|
||||
entry:
|
||||
description: Entry name for the attribute to fetch
|
||||
required: true
|
||||
type: str
|
||||
group:
|
||||
decription: Group name that the Entry belongs to
|
||||
required: false
|
||||
type: str
|
||||
|
||||
author:
|
||||
- Jeremy Lumley (@jlumley)
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Fetch the credentials for the server_1 entry in any group
|
||||
- name: Fetch server_1 credentials
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "s3cure_p4550rd"
|
||||
entry: "server_1"
|
||||
|
||||
# Fetch the reddit entry in the social group
|
||||
- name: Fetching reddit credentials
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "sup3r_s3cure_p4550rd"
|
||||
entry: "reddit"
|
||||
group: "social"
|
||||
|
||||
# Fetch a custom strig attribute from the github entry
|
||||
- name: Fetch Github API Token
|
||||
jlumley.jlumley.ansible-keepassxc:
|
||||
database: "/secrets/db.kdbx"
|
||||
password: "d0pe_s3cure_p4550rd"
|
||||
keyfile: "/secrets/top_secret_key"
|
||||
entry: "github"
|
||||
group: "development"
|
||||
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
# Return values
|
||||
username:
|
||||
description: Username of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 's3cr3t_us3r'
|
||||
password:
|
||||
description: Password of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 's3cr3t_p455word'
|
||||
url:
|
||||
description: Url of entry if present
|
||||
type: str
|
||||
returned: always
|
||||
sample: 'http://reddit.com'
|
||||
custom_fields:
|
||||
description: dictionary containing all custom fields
|
||||
type: dict
|
||||
returned: always
|
||||
sample: False
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
import traceback
|
||||
|
||||
IMPORT_ERR = None
|
||||
try:
|
||||
import pykeepass as keepass
|
||||
except ImportError:
|
||||
IMPORT_ERR = traceback.format_exc()
|
||||
|
||||
def run_module():
|
||||
# define available arguments/parameters a user can pass to the module
|
||||
module_args = dict(
|
||||
database = dict(type='str', required=True),
|
||||
password = dict(type='str', required=True),
|
||||
keyfile = dict(type='str', required=False, default=None),
|
||||
entry = dict(type='str', required=True),
|
||||
group = dict(type='str', required=False),
|
||||
)
|
||||
|
||||
# seed the result dict in the object
|
||||
result = dict(
|
||||
changed=False,
|
||||
username='',
|
||||
password='',
|
||||
url='',
|
||||
custom_fields={}
|
||||
)
|
||||
|
||||
# Currently no support for a check_mode this maybe added later if
|
||||
# functionality to modify the database is added later
|
||||
module = AnsibleModule(
|
||||
argument_spec=module_args,
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
if IMPORT_ERR:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("pykeepass"),
|
||||
exception=IMPORT_ERR
|
||||
)
|
||||
|
||||
# unlock local keepass database
|
||||
try:
|
||||
kp = keepass.PyKeePass(
|
||||
module.params['database'],
|
||||
password=module.params['password'],
|
||||
keyfile=module.params['keyfile'])
|
||||
except keepass.exceptions.CredentialsError:
|
||||
module.fail_json(msg='Invalid Credentials')
|
||||
|
||||
# find entry
|
||||
entry = kp.find_entries(
|
||||
title=module.params['entry'],
|
||||
group=module.params['group']
|
||||
)
|
||||
|
||||
# fail is entry is not present
|
||||
if not entry:
|
||||
module.fail_json(msg=f"Unable to find entry: {module.params['entry']}")
|
||||
|
||||
else:
|
||||
entry = entry[0]
|
||||
custom_field_keys = entry._get_string_field_keys(exclude_reserved=True)
|
||||
custom_fields = dict()
|
||||
for key in custom_field_keys:
|
||||
custom_fields[key] = entry.get_custom_property(key)
|
||||
result = dict (
|
||||
changed=False,
|
||||
username=entry.username,
|
||||
password=entry.password,
|
||||
url=entry.url,
|
||||
custom_fields=custom_fields
|
||||
)
|
||||
|
||||
# in the event of a successful module execution, you will want to
|
||||
# simple AnsibleModule.exit_json(), passing the key/value results
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def main():
|
||||
run_module()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
# Based on local.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Based on chroot.py (c) 2013, Maykel Moya <mmoya@speedyrails.com>
|
||||
# (c) 2013, Michael Scherer <misc@zarb.org>
|
||||
# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
import sys
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
author: Jesse Pretorius <jesse@odyssey4.me>
|
||||
connection: community.libvirt.libvirt_qemu
|
||||
short_description: Run tasks on libvirt/qemu virtual machines
|
||||
description:
|
||||
- Run commands or put/fetch files to libvirt/qemu virtual machines using the qemu agent API.
|
||||
notes:
|
||||
- Currently DOES NOT work with selinux set to enforcing in the VM.
|
||||
- Requires the qemu-agent installed in the VM.
|
||||
- Requires access to the qemu-ga commands guest-exec, guest-exec-status, guest-file-close, guest-file-open, guest-file-read, guest-file-write.
|
||||
version_added: "2.10"
|
||||
options:
|
||||
remote_addr:
|
||||
description: Virtual machine name
|
||||
default: inventory_hostname
|
||||
vars:
|
||||
- name: ansible_host
|
||||
executable:
|
||||
description: Shell to use for execution inside container
|
||||
default: /bin/sh
|
||||
vars:
|
||||
- name: ansible_executable
|
||||
virt_uri:
|
||||
description: libvirt URI to connect to to access the virtual machine
|
||||
default: qemu:///system
|
||||
vars:
|
||||
- name: ansible_libvirt_uri
|
||||
"""
|
||||
|
||||
import base64
|
||||
import json
|
||||
import libvirt
|
||||
import libvirt_qemu
|
||||
import shlex
|
||||
import traceback
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError, AnsibleConnectionFailure, AnsibleFileNotFound
|
||||
from ansible.module_utils._text import to_bytes, to_native, to_text
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.plugins.shell.powershell import _parse_clixml
|
||||
from ansible.utils.display import Display
|
||||
from functools import partial
|
||||
from os.path import exists, getsize
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
REQUIRED_CAPABILITIES = [
|
||||
{'enabled': True, 'name': 'guest-exec', 'success-response': True},
|
||||
{'enabled': True, 'name': 'guest-exec-status', 'success-response': True},
|
||||
{'enabled': True, 'name': 'guest-file-close', 'success-response': True},
|
||||
{'enabled': True, 'name': 'guest-file-open', 'success-response': True},
|
||||
{'enabled': True, 'name': 'guest-file-read', 'success-response': True},
|
||||
{'enabled': True, 'name': 'guest-file-write', 'success-response': True}
|
||||
]
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
''' Local libvirt qemu based connections '''
|
||||
|
||||
transport = 'community.libvirt.libvirt_qemu'
|
||||
# TODO(odyssey4me):
|
||||
# Figure out why pipelining does not work and fix it
|
||||
has_pipelining = False
|
||||
has_tty = False
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
self._host = self._play_context.remote_addr
|
||||
|
||||
# Windows operates differently from a POSIX connection/shell plugin,
|
||||
# we need to set various properties to ensure SSH on Windows continues
|
||||
# to work
|
||||
if getattr(self._shell, "_IS_WINDOWS", False):
|
||||
self.has_native_async = True
|
||||
self.always_pipeline_modules = True
|
||||
self.module_implementation_preferences = ('.ps1', '.exe', '')
|
||||
self.allow_executable = False
|
||||
|
||||
def _connect(self):
|
||||
''' connect to the virtual machine; nothing to do here '''
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
|
||||
self._virt_uri = self.get_option('virt_uri')
|
||||
|
||||
self._display.vvv(u"CONNECT TO {0}".format(self._virt_uri), host=self._host)
|
||||
try:
|
||||
self.conn = libvirt.open(self._virt_uri)
|
||||
except libvirt.libvirtError as err:
|
||||
self._display.vv(u"ERROR: libvirtError CONNECT TO {0}\n{1}".format(self._virt_uri, to_native(err)), host=self._host)
|
||||
self._connected = False
|
||||
raise AnsibleConnectionFailure(to_native(err))
|
||||
|
||||
self._display.vvv(u"FIND DOMAIN {0}".format(self._host), host=self._host)
|
||||
try:
|
||||
self.domain = self.conn.lookupByName(self._host)
|
||||
except libvirt.libvirtError as err:
|
||||
raise AnsibleConnectionFailure(to_native(err))
|
||||
|
||||
request_cap = json.dumps({'execute': 'guest-info'})
|
||||
response_cap = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_cap, 5, 0))
|
||||
self.capabilities = response_cap['return']['supported_commands']
|
||||
self._display.vvvvv(u"GUEST CAPABILITIES: {0}".format(self.capabilities), host=self._host)
|
||||
missing_caps = []
|
||||
for cap in REQUIRED_CAPABILITIES:
|
||||
if cap not in self.capabilities:
|
||||
missing_caps.append(cap['name'])
|
||||
if len(missing_caps) > 0:
|
||||
self._display.vvv(u"REQUIRED CAPABILITIES MISSING: {0}".format(missing_caps), host=self._host)
|
||||
raise AnsibleConnectionFailure('Domain does not have required capabilities')
|
||||
|
||||
display.vvv(u"ESTABLISH {0} CONNECTION".format(self.transport), host=self._host)
|
||||
self._connected = True
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
""" execute a command on the virtual machine host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd), host=self._host)
|
||||
|
||||
cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict'))
|
||||
|
||||
if getattr(self._shell, "_IS_WINDOWS", False):
|
||||
# Become method 'runas' is done in the wrapper that is executed,
|
||||
# need to disable sudoable so the bare_run is not waiting for a
|
||||
# prompt that will not occur
|
||||
sudoable = False
|
||||
|
||||
# Generate powershell commands
|
||||
cmd_args_list = self._shell._encode_script(cmd, as_list=True, strict_mode=False, preserve_rc=False)
|
||||
|
||||
# TODO(odyssey4me):
|
||||
# Implement buffering much like the other connection plugins
|
||||
# Implement 'env' for the environment settings
|
||||
# Implement 'input-data' for whatever it might be useful for
|
||||
request_exec = {
|
||||
'execute': 'guest-exec',
|
||||
'arguments': {
|
||||
'path': cmd_args_list[0],
|
||||
'capture-output': True,
|
||||
'arg': cmd_args_list[1:]
|
||||
}
|
||||
}
|
||||
request_exec_json = json.dumps(request_exec)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_exec_json), host=self._host)
|
||||
|
||||
# TODO(odyssey4me):
|
||||
# Add timeout parameter
|
||||
try:
|
||||
result_exec = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_exec_json, 5, 0))
|
||||
except libvirt.libvirtError as err:
|
||||
self._display.vv(u"ERROR: libvirtError EXEC TO {0}\n{1}".format(self._virt_uri, to_native(err)), host=self._host)
|
||||
sys.stderr.write(u"ERROR: libvirtError EXEC TO {0}\n{1}\n".format(self._virt_uri, to_native(err)))
|
||||
self._connected = False
|
||||
raise AnsibleConnectionFailure(to_native(err))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_exec), host=self._host)
|
||||
|
||||
request_status = {
|
||||
'execute': 'guest-exec-status',
|
||||
'arguments': {
|
||||
'pid': result_exec['return']['pid']
|
||||
}
|
||||
}
|
||||
request_status_json = json.dumps(request_status)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_status_json), host=self._host)
|
||||
|
||||
# TODO(odyssey4me):
|
||||
# Work out a better way to wait until the command has exited
|
||||
result_status = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_status_json, 5, 0))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_status), host=self._host)
|
||||
|
||||
while not result_status['return']['exited']:
|
||||
result_status = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_status_json, 5, 0))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_status), host=self._host)
|
||||
|
||||
if result_status['return'].get('out-data'):
|
||||
stdout = base64.b64decode(result_status['return']['out-data'])
|
||||
else:
|
||||
stdout = b''
|
||||
|
||||
if result_status['return'].get('err-data'):
|
||||
stderr = base64.b64decode(result_status['return']['err-data'])
|
||||
else:
|
||||
stderr = b''
|
||||
|
||||
# Decode xml from windows
|
||||
if getattr(self._shell, "_IS_WINDOWS", False) and stdout.startswith(b"#< CLIXML"):
|
||||
stdout = _parse_clixml(stdout)
|
||||
|
||||
display.vvv(u"GA stdout: {0}".format(to_text(stdout)), host=self._host)
|
||||
display.vvv(u"GA stderr: {0}".format(to_text(stderr)), host=self._host)
|
||||
|
||||
return result_status['return']['exitcode'], stdout, stderr
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
''' transfer a file from local to domain '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._host)
|
||||
|
||||
if not exists(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound(
|
||||
"file or module does not exist: %s" % in_path)
|
||||
|
||||
request_handle = {
|
||||
'execute': 'guest-file-open',
|
||||
'arguments': {
|
||||
'path': out_path,
|
||||
'mode': 'wb+'
|
||||
}
|
||||
}
|
||||
request_handle_json = json.dumps(request_handle)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_handle_json), host=self._host)
|
||||
|
||||
result_handle = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_handle_json, 5, 0))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_handle), host=self._host)
|
||||
|
||||
# TODO(odyssey4me):
|
||||
# Handle exception for file/path IOError
|
||||
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
|
||||
for chunk in iter(partial(in_file.read, BUFSIZE), b''):
|
||||
try:
|
||||
request_write = {
|
||||
'execute': 'guest-file-write',
|
||||
'arguments': {
|
||||
'handle': result_handle['return'],
|
||||
'buf-b64': base64.b64encode(chunk).decode()
|
||||
}
|
||||
}
|
||||
request_write_json = json.dumps(request_write)
|
||||
|
||||
display.vvvvv(u"GA send: {0}".format(request_write_json), host=self._host)
|
||||
|
||||
result_write = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_write_json, 5, 0))
|
||||
|
||||
display.vvvvv(u"GA return: {0}".format(result_write), host=self._host)
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
|
||||
request_close = {
|
||||
'execute': 'guest-file-close',
|
||||
'arguments': {
|
||||
'handle': result_handle['return']
|
||||
}
|
||||
}
|
||||
request_close_json = json.dumps(request_close)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_close_json), host=self._host)
|
||||
|
||||
result_close = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_close_json, 5, 0))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_close), host=self._host)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
''' fetch a file from domain to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._host)
|
||||
|
||||
request_handle = {
|
||||
'execute': 'guest-file-open',
|
||||
'arguments': {
|
||||
'path': in_path,
|
||||
'mode': 'r'
|
||||
}
|
||||
}
|
||||
request_handle_json = json.dumps(request_handle)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_handle_json), host=self._host)
|
||||
|
||||
result_handle = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_handle_json, 5, 0))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_handle), host=self._host)
|
||||
|
||||
request_read = {
|
||||
'execute': 'guest-file-read',
|
||||
'arguments': {
|
||||
'handle': result_handle['return'],
|
||||
'count': BUFSIZE
|
||||
}
|
||||
}
|
||||
request_read_json = json.dumps(request_read)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_read_json), host=self._host)
|
||||
|
||||
with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file:
|
||||
try:
|
||||
result_read = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_read_json, 5, 0))
|
||||
display.vvvvv(u"GA return: {0}".format(result_read), host=self._host)
|
||||
out_file.write(base64.b64decode(result_read['return']['buf-b64']))
|
||||
while not result_read['return']['eof']:
|
||||
result_read = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_read_json, 5, 0))
|
||||
display.vvvvv(u"GA return: {0}".format(result_read), host=self._host)
|
||||
out_file.write(base64.b64decode(result_read['return']['buf-b64']))
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
|
||||
request_close = {
|
||||
'execute': 'guest-file-close',
|
||||
'arguments': {
|
||||
'handle': result_handle['return']
|
||||
}
|
||||
}
|
||||
request_close_json = json.dumps(request_close)
|
||||
|
||||
display.vvv(u"GA send: {0}".format(request_close_json), host=self._host)
|
||||
|
||||
result_close = json.loads(libvirt_qemu.qemuAgentCommand(self.domain, request_close_json, 5, 0))
|
||||
|
||||
display.vvv(u"GA return: {0}".format(result_close), host=self._host)
|
||||
|
||||
def close(self):
|
||||
''' terminate the connection; nothing to do here '''
|
||||
super(Connection, self).close()
|
||||
self._connected = False
|
|
@ -0,0 +1 @@
|
|||
https://github.com/ansible-community/community.libvirt-1.0.0/plugins/connection/libvirt_qemu.py
|
750
overlay/Linux/usr/local/share/ansible/library/selenium_test.py
Normal file
750
overlay/Linux/usr/local/share/ansible/library/selenium_test.py
Normal file
|
@ -0,0 +1,750 @@
|
|||
#!/usr//local/bin/python2.sh
|
||||
# -*-mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
# From: github.com/napalm255/ansible-selenium/library/selenium_test.py
|
||||
|
||||
# (c) 2016, Brad Gibson <napalm255@gmail.com>
|
||||
#
|
||||
# This file is a 3rd Party module for Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Ansible Selenium Module."""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: selenium_test
|
||||
author: "Brad Gibson"
|
||||
version_added: "2.3"
|
||||
short_description: Run selenium tests
|
||||
requires: [ selenium>=3.0.2 ]
|
||||
description:
|
||||
- Run selenium tests against provided URL.
|
||||
- Use Clicks, Typing, Waiting and Assertions.
|
||||
options:
|
||||
url:
|
||||
required: true
|
||||
description:
|
||||
- URL to run selenium tests against.
|
||||
browser:
|
||||
required: false
|
||||
default: "phantomjs"
|
||||
choices: [ "phantomjs", "firefox", "chrome" ]
|
||||
description:
|
||||
- Browser to use for testing.
|
||||
browser_executable:
|
||||
required: false
|
||||
default: ""
|
||||
browser_log:
|
||||
required: false
|
||||
default: ""
|
||||
browser_timeout:
|
||||
required: false
|
||||
default: 30
|
||||
browser_service_args:
|
||||
required: false
|
||||
default: []
|
||||
webdriver_executable:
|
||||
required: false
|
||||
default: ""
|
||||
webdriver_log:
|
||||
required: false
|
||||
default: ""
|
||||
width:
|
||||
required: false
|
||||
default: 1024
|
||||
description:
|
||||
- Browser screen width.
|
||||
height:
|
||||
required: false
|
||||
default: 768
|
||||
description:
|
||||
- Browser screen height.
|
||||
title:
|
||||
required: false
|
||||
description:
|
||||
- Title to validate after initial load.
|
||||
screenshot:
|
||||
required: false
|
||||
default: false
|
||||
description:
|
||||
- Enable/Disable screenshots.
|
||||
screenshot_when:
|
||||
required: false
|
||||
default: [ "error" ]
|
||||
choices: [ "all", "start", "end", "error" ]
|
||||
description:
|
||||
- Enable/Disable screenshots.
|
||||
screenshot_type:
|
||||
required: false
|
||||
default: "base64"
|
||||
choices: [ "base64", "file" ]
|
||||
description:
|
||||
- Screenshot format.
|
||||
screenshot_path:
|
||||
required: false
|
||||
default: "/tmp"
|
||||
description:
|
||||
- Screenshot path.
|
||||
screenshot_prefix:
|
||||
required: false
|
||||
default: "selenium_"
|
||||
description:
|
||||
- Screenshot file prefix.
|
||||
implicit_wait:
|
||||
required: false
|
||||
default: 20
|
||||
description:
|
||||
- Implicit wait value when loading webpage.
|
||||
explicit_wait:
|
||||
required: false
|
||||
default: 2
|
||||
description:
|
||||
- Explicit wait value when loading webpage.
|
||||
steps:
|
||||
required: true
|
||||
description:
|
||||
- Steps to perform.
|
||||
validate_cert:
|
||||
required: false
|
||||
default: true
|
||||
description:
|
||||
- Validate SSL certificate.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# run basic check against given url
|
||||
- selenium_test: url=http://www.python.org
|
||||
'''
|
||||
|
||||
# selenium/webdriver/remote/webdriver.py
|
||||
_W3C_CAPABILITY_NAMES = frozenset([
|
||||
'acceptInsecureCerts',
|
||||
'browserName',
|
||||
'browserVersion',
|
||||
'platformName',
|
||||
'pageLoadStrategy',
|
||||
'proxy',
|
||||
'setWindowRect',
|
||||
'timeouts',
|
||||
'unhandledPromptBehavior',
|
||||
])
|
||||
_OSS_W3C_CONVERSION = {
|
||||
'acceptSslCerts': 'acceptInsecureCerts',
|
||||
'version': 'browserVersion',
|
||||
'platform': 'platformName'
|
||||
}
|
||||
# caps['proxy']['proxyType'] = caps['proxy']['proxyType'].lower()
|
||||
|
||||
# selenium-3.9.0-py2.7.egg/selenium/webdriver/common/proxy.py
|
||||
ELTS = ['autodetect', 'ftpProxy', 'httpProxy', 'proxyAutoconfigUrl', 'sslProxy', 'noProxy', 'socksProxy', 'socksUsername', 'socksPassword']
|
||||
def add_to_capabilities(self, proxyTypeString, capabilities):
|
||||
"""
|
||||
DIRECT = ProxyTypeFactory.make(0, 'DIRECT') # Direct connection, no proxy (default on Windows).
|
||||
MANUAL = ProxyTypeFactory.make(1, 'MANUAL') # Manual proxy settings (e.g., for httpProxy).
|
||||
PAC = ProxyTypeFactory.make(2, 'PAC') # Proxy autoconfiguration from URL.
|
||||
RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1') # Never used.
|
||||
AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT') # Proxy autodetection (presumably with WPAD).
|
||||
SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM') # Use system settings (default on Linux).
|
||||
UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED') # Not initialized (for internal use).
|
||||
"""
|
||||
proxy_caps = {}
|
||||
proxy_caps['proxyType'] = proxyTypeString
|
||||
for elt in ELTS:
|
||||
proxy_caps[elt] = getattr(self, elt)
|
||||
capabilities['proxy'] = proxy_caps
|
||||
|
||||
# pylint: disable = wrong-import-position
|
||||
import sys,os # noqa
|
||||
try:
|
||||
if sys.version_info < (3, 0):
|
||||
from urlparse import urlparse # noqa
|
||||
else:
|
||||
from urllib.parse import urlparse # noqa
|
||||
URLPARSE_INSTALLED_ERR = ''
|
||||
except ImportError as e:
|
||||
URLPARSE_INSTALLED_ERR = 'ERROR: selenium_test.py urllib.parse import urlparse not imported.\n' + str(e)
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, jsonify # noqa
|
||||
try:
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import NoSuchElementException, TimeoutException
|
||||
SELENIUM_INSTALLED_ERR = ''
|
||||
except ImportError as e:
|
||||
SELENIUM_INSTALLED_ERR = 'ERROR: selenium_test.py selenium not imported.\n' + str(e)
|
||||
|
||||
|
||||
class AnsibleSelenium(object):
|
||||
"""Ansible Selenium Class."""
|
||||
|
||||
def __init__(self, module):
|
||||
"""Init."""
|
||||
self.module = module
|
||||
self.arg = lambda: None
|
||||
for arg in self.module.params:
|
||||
setattr(self.arg, arg, self.module.params[arg])
|
||||
|
||||
self.steps_num = len(self.arg.steps) - 1
|
||||
self.result = {'changed': False,
|
||||
'failed': False,
|
||||
'results': {
|
||||
'steps': [],
|
||||
'num': self.steps_num
|
||||
}}
|
||||
self.browser_oFd = None
|
||||
self.browser = self._browser()
|
||||
|
||||
def __enter__(self):
|
||||
"""Enter by loading website and return self."""
|
||||
# validate url
|
||||
url_parsed = urlparse(self.arg.url)
|
||||
if url_parsed.scheme not in ['http', 'https']:
|
||||
self.failed('invalid url scheme: ' + url_parsed.scheme)
|
||||
if not url_parsed.netloc:
|
||||
self.failed('invalid url: empty netloc')
|
||||
|
||||
# load browser
|
||||
self.browser.get(self.arg.url)
|
||||
self.result['browser_closed'] = False
|
||||
|
||||
# validate title
|
||||
if not self.arg.title:
|
||||
#?mike how is this used? Set it to None if missing or blank?
|
||||
self.result['results']['title'] = None
|
||||
elif self.arg.title in self.browser.title:
|
||||
self.result['results']['title'] = True
|
||||
else:
|
||||
self.result['results']['title'] = False
|
||||
self.failed('title "' +self.browser.title +'" does not contain: ' + self.arg.title)
|
||||
|
||||
# process steps
|
||||
self.steps()
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
"""Exit by closing and quitting the browser."""
|
||||
# pylint: disable = redefined-builtin
|
||||
if self.browser_oFd:
|
||||
self.browser_oFd.close()
|
||||
self.browser_oFd = None
|
||||
try: self.browser.close()
|
||||
except: pass
|
||||
try: self.browser.quit()
|
||||
except: pass
|
||||
self.result['browser_closed'] = True
|
||||
|
||||
def _browser(self):
|
||||
"""Select browser and return object."""
|
||||
name = self.arg.browser
|
||||
if 'phantomjs' in name:
|
||||
return self._phantomjs()
|
||||
elif 'firefox' in name:
|
||||
return self._firefox()
|
||||
elif 'chrome' in name:
|
||||
return self._chrome()
|
||||
else:
|
||||
raise RuntimeError("Unrecognized browser " +name)
|
||||
|
||||
def _dRaw_proxy(self):
|
||||
# FixMe? socks_proxy
|
||||
dRaw = dict()
|
||||
# SYSTEM
|
||||
dRaw["proxyType"] = "MANUAL"
|
||||
if ('no_proxy' in os.environ and os.environ['no_proxy']):
|
||||
dRaw['noProxy'] = os.environ['no_proxy']
|
||||
else:
|
||||
dRaw['noProxy'] = 'localhost,127.0.0.1'
|
||||
|
||||
if os.environ['http_proxy']:
|
||||
o = urlparse(os.environ['http_proxy'])
|
||||
if o.scheme not in ['http', 'https']:
|
||||
self.failed('invalid http_proxy url scheme: ' + o.scheme)
|
||||
if not o.netloc:
|
||||
self.failed('invalid http_proxy url: empty netloc')
|
||||
PROXY_HOST, PROXY_PORT = o.netloc.split(':')
|
||||
# 'http://' +
|
||||
dRaw['httpProxy'] = PROXY_HOST +':' +PROXY_PORT
|
||||
if os.environ['https_proxy']:
|
||||
o = urlparse(os.environ['https_proxy'])
|
||||
if o.scheme not in ['http', 'https']:
|
||||
self.failed('invalid https_proxy url scheme: ' + o.scheme)
|
||||
if not o.netloc:
|
||||
self.failed('invalid https_proxy url: empty netloc')
|
||||
PROXY_HOST, PROXY_PORT = o.netloc.split(':')
|
||||
# 'https://' +
|
||||
dRaw['sslProxy'] = PROXY_HOST +':' +PROXY_PORT
|
||||
return dRaw
|
||||
|
||||
def _phantomjs(self):
|
||||
"""Use PhantomJS browser."""
|
||||
# Adding the list of possibilities (integer values) for the "network.proxy.type".
|
||||
# 0 - Direct connection (or) no proxy.
|
||||
# 1 - Manual proxy configuration
|
||||
# 2 - Proxy auto-configuration (PAC).
|
||||
# 4 - Auto-detect proxy settings.
|
||||
# 5 - Use system proxy settings.
|
||||
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
|
||||
# DesiredCapabilities.PHANTOMJS.copy()
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", "Selenium support for PhantomJS has been deprecated.*", UserWarning)
|
||||
dCapabilities = {
|
||||
"browserName": "phantomjs",
|
||||
"browserVersion": "",
|
||||
"platform": "ANY",
|
||||
"javascriptEnabled": True,
|
||||
}
|
||||
if not self.arg.browser_executable:
|
||||
self.arg.browser_executable = 'phantomjs'
|
||||
service_args = self.arg.browser_service_args
|
||||
service_args += ['--ssl-protocol=any','--web-security=no']
|
||||
if ('http_proxy' in os.environ and os.environ['http_proxy']) or \
|
||||
('https_proxy' in os.environ and os.environ['https_proxy']):
|
||||
dRaw = self._dRaw_proxy()
|
||||
dCapabilities['proxy'] = dRaw
|
||||
# bogus - old
|
||||
# --proxy-type=<val> 'http' (default), 'none' (disable completely), or 'socks5'
|
||||
service_args += ['--proxy='+dRaw['sslProxy'], '--proxy-type='+'http']
|
||||
# --ssl-protocol=TLSv1.2
|
||||
# --ssl-certificates-path= the location for custom CA certificates (if none set, uses SSL_CERT_DIR)
|
||||
if not self.arg.validate_cert:
|
||||
service_args.append('--ignore-ssl-errors=true')
|
||||
if False:
|
||||
# Starts in 'Remote WebDriver mode' (embedded GhostDriver): '[[<IP>:]<PORT>]' (default '127.0.0.1:8910')
|
||||
service_args.append('----webdriver=' +'127.0.0.1:8910')
|
||||
# WebDriver Logging Level: (supported: 'ERROR', 'WARN', 'INFO', 'DEBUG') (default 'INFO') (NOTE: needs '--webdriver')
|
||||
service_args.append('----webdriver-loglevel' +'INFO')
|
||||
|
||||
if self.arg.webdriver_log:
|
||||
service_args += ['--webdriver-logfile='+self.arg.webdriver_log]
|
||||
|
||||
# service_args : A List of command line arguments to pass to PhantomJS
|
||||
driver = webdriver.PhantomJS(service_args=service_args,
|
||||
desired_capabilities=dCapabilities)
|
||||
driver.set_window_size(self.arg.width, self.arg.height)
|
||||
driver.set_page_load_timeout(self.arg.implicit_wait)
|
||||
return driver
|
||||
|
||||
def _firefox(self):
|
||||
"""Use Firefox browser."""
|
||||
# pylint: disable = no-self-use
|
||||
dCapabilities = {
|
||||
"browserName": "firefox",
|
||||
"acceptInsecureCerts": True,
|
||||
}
|
||||
if self.arg.validate_cert:
|
||||
dCapabilities["acceptInsecureCerts"] = False
|
||||
|
||||
# from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
|
||||
# firefox_profile = FirefoxProfile(profile_directory=None)
|
||||
# warnings.warn("Please use FirefoxOptions to set browser profile", DeprecationWarning,
|
||||
|
||||
if ('http_proxy' in os.environ and os.environ['http_proxy']) or \
|
||||
('https_proxy' in os.environ and os.environ['https_proxy']):
|
||||
dRaw = self._dRaw_proxy()
|
||||
from selenium.webdriver.common.proxy import Proxy
|
||||
dRaw["proxyType"] = "MANUAL"
|
||||
oProxy = Proxy(raw=dRaw)
|
||||
#dRawCopy = dRaw.copy()
|
||||
#dRawCopy["proxyType"] = "MANUAL".lower()
|
||||
#? dCapabilities['proxy'] = dRawCopy
|
||||
else:
|
||||
oProxy = None
|
||||
|
||||
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
|
||||
if not self.arg.browser_executable:
|
||||
# their code looks for firefox or iceweasel
|
||||
self.arg.browser_executable = 'firefox'
|
||||
# should use FirefoxBinary.which() to make sure its on the PATH
|
||||
self.arg.browser_executable = which(self.arg.browser_executable)
|
||||
assert self.arg.browser_executable
|
||||
|
||||
# A file object to redirect the firefox process output to. It can be sys.stdout.
|
||||
# morons: not a file
|
||||
if not self.arg.browser_log:
|
||||
self.browser_oFd = None
|
||||
else:
|
||||
self.browser_oFd = open(self.arg.browser_log, 'w')
|
||||
|
||||
firefox_binary = FirefoxBinary(firefox_path=self.arg.browser_executable, log_file=self.browser_oFd)
|
||||
# firefox_binary.add_command_line_options()
|
||||
|
||||
if self.arg.webdriver_executable == 'legacy':
|
||||
dCapabilities["marionette"] = False
|
||||
self.arg.webdriver_executable = ''
|
||||
else:
|
||||
dCapabilities["marionette"] = True
|
||||
if not self.arg.webdriver_executable:
|
||||
self.arg.webdriver_executable = 'geckodriver'
|
||||
if self.arg.webdriver_log:
|
||||
log_path = self.arg.webdriver_log
|
||||
else:
|
||||
log_path = "geckodriver.log"
|
||||
self.arg.webdriver_executable = which(self.arg.webdriver_executable)
|
||||
assert self.arg.webdriver_executable
|
||||
|
||||
# options overrides capabilities
|
||||
# As some of the options, such as `firefox_profile` and
|
||||
# `options.profile` are mutually exclusive, precedence is
|
||||
# given from how specific the setting is. `capabilities` is the
|
||||
# least specific keyword argument, followed by `options`,
|
||||
# followed by `firefox_binary` and `firefox_profile`.
|
||||
|
||||
#geckodriver
|
||||
# -v Log level verbosity (-v for debug and -vv for trace level)
|
||||
# -b, --binary <BINARY> Path to the Firefox binary
|
||||
# --log <LEVEL> Set Gecko log level [possible values: fatal, error, warn, info, config, debug, trace]
|
||||
service_args = self.arg.browser_service_args
|
||||
|
||||
kwargs = dict(firefox_profile=None,
|
||||
firefox_binary=firefox_binary,
|
||||
timeout=self.arg.browser_timeout,
|
||||
capabilities=dCapabilities,
|
||||
proxy=oProxy,
|
||||
executable_path=self.arg.webdriver_executable,
|
||||
options=None,
|
||||
log_path=log_path,
|
||||
firefox_options=None,
|
||||
service_args=service_args)
|
||||
driver = webdriver.Firefox(**kwargs)
|
||||
return driver
|
||||
|
||||
def _chrome(self):
|
||||
"""Use Chrome browser."""
|
||||
# pylint: disable = no-self-use
|
||||
if not self.arg.webdriver_executable:
|
||||
self.arg.webdriver_executable = "chromedriver"
|
||||
if not self.arg.browser_executable:
|
||||
self.arg.browser_executable = '/opt/chromium-browser/chromium-launcher.sh'
|
||||
if not self.arg.browser_service_args:
|
||||
self.arg.browser_service_args = None
|
||||
if not self.arg.webdriver_log:
|
||||
self.arg.webdriver_log = None
|
||||
# options get added to desired_capabilities - desired_capabilities.update(options.to_capabilities())
|
||||
# warnings.warn('use options instead of chrome_options', DeprecationWarning)
|
||||
kwargs = dict(executable_path=self.arg.webdriver_executable,
|
||||
port=0,
|
||||
options=None,
|
||||
service_args=self.arg.browser_service_args,
|
||||
desired_capabilities=None,
|
||||
service_log_path=self.arg.webdriver_log,
|
||||
chrome_options=None)
|
||||
# selenium/webdriver/chrome/webdriver.py IGNORES the following arguments
|
||||
# proxy=None, keep_alive=False, file_detector=None, to
|
||||
# selenium-3.9.0-py2.7.egg/selenium/webdriver/remote/webdriver.py
|
||||
|
||||
driver = webdriver.Chrome(**kwargs)
|
||||
return driver
|
||||
|
||||
def failed(self, msg, step=None):
|
||||
"""Failed."""
|
||||
# self.result['failed'] = True
|
||||
self.result['msg'] = msg
|
||||
self.result['failed'] = True
|
||||
if step:
|
||||
step['error'] = True
|
||||
step['msg'] = msg
|
||||
when = self.arg.screenshot_when
|
||||
if 'all' in when or 'error' in when:
|
||||
step['screenshot'] = self.screenshot('failed')
|
||||
self.result['results']['steps'].append(step)
|
||||
else:
|
||||
self.result['results']['screenshot'] = self.screenshot('failed')
|
||||
# cmd=cmd, rc=rc, stdout=out, stderr=err,
|
||||
self.module.fail_json(**self.result)
|
||||
|
||||
def screenshot(self, suffix='default'):
|
||||
"""Screenshot."""
|
||||
details = {}
|
||||
if 'base64' in self.arg.screenshot_type:
|
||||
base64 = self.browser.get_screenshot_as_base64()
|
||||
details['base64'] = base64
|
||||
elif 'file' in self.arg.screenshot_type:
|
||||
path = '%s/%s%s.png' % (self.arg.screenshot_path,
|
||||
self.arg.screenshot_prefix,
|
||||
suffix)
|
||||
self.browser.get_screenshot_as_file(path)
|
||||
details['file'] = path
|
||||
return details
|
||||
|
||||
def keys(self, step, step_result):
|
||||
"""Keys."""
|
||||
step_result['keys'] = False
|
||||
try:
|
||||
keys_method = getattr(self.browser, step['keys']['type'])
|
||||
if 'text' in step['keys']:
|
||||
value = step['keys']['text']
|
||||
assert value and value.lower() != 'undefined'
|
||||
keys_method(step['keys']['value']).send_keys(value)
|
||||
if 'key' in step['keys']:
|
||||
key_type = getattr(Keys, step['keys']['key'])
|
||||
assert key_type and key_type.lower() != 'undefined'
|
||||
keys_method(step['keys']['value']).send_keys(key_type)
|
||||
except KeyError:
|
||||
self.failed('configuration failure. check syntax.', step_result)
|
||||
except AttributeError:
|
||||
self.failed('type error. check syntax.', step_result)
|
||||
except NoSuchElementException:
|
||||
self.failed('no such element.', step_result)
|
||||
step_result['keys'] = True
|
||||
return step_result
|
||||
|
||||
def click(self, step, step_result):
|
||||
"""Click."""
|
||||
step_result['click'] = False
|
||||
try:
|
||||
click_method = getattr(self.browser, step['click']['type'])
|
||||
click_method(step['click']['text']).click()
|
||||
except KeyError:
|
||||
self.failed('KeyError click configuration failure. check syntax.', step_result)
|
||||
except AttributeError:
|
||||
self.failed('AttributeError. check syntax.', step_result)
|
||||
except NoSuchElementException:
|
||||
self.failed('no such element.', step_result)
|
||||
step_result['click'] = True
|
||||
return step_result
|
||||
|
||||
def wait_for(self, step, step_result):
|
||||
"""Wait for."""
|
||||
step_result['wait_for'] = False
|
||||
try:
|
||||
waitfor_method = getattr(EC, step['wait_for']['method'])
|
||||
waitfor_type = getattr(By, step['wait_for']['type'])
|
||||
waitfor_text = step['wait_for']['text']
|
||||
except KeyError:
|
||||
self.failed('KeyError wait_for configuration failure. check syntax.', step_result)
|
||||
except AttributeError:
|
||||
self.failed('method or type error. check syntax.', step_result)
|
||||
|
||||
try:
|
||||
WebDriverWait(self.browser, self.arg.explicit_wait).until(
|
||||
waitfor_method((waitfor_type, waitfor_text))
|
||||
)
|
||||
except TimeoutException:
|
||||
self.failed('failure waiting for element.', step_result)
|
||||
step_result['wait_for'] = True
|
||||
return step_result
|
||||
|
||||
def asserts(self, step, step_result):
|
||||
"""Assertions.
|
||||
/usr/lib64/python2.7/site-packages/selenium/webdriver/remote/webdriver.py
|
||||
def find_element_by_id(self, id_):
|
||||
def find_elements_by_id(self, id_):
|
||||
def find_element_by_xpath(self, xpath):
|
||||
def find_elements_by_xpath(self, xpath):
|
||||
def find_element_by_link_text(self, link_text):
|
||||
def find_elements_by_link_text(self, text):
|
||||
def find_element_by_partial_link_text(self, link_text):
|
||||
def find_elements_by_partial_link_text(self, link_text):
|
||||
def find_element_by_name(self, name):
|
||||
def find_elements_by_name(self, name):
|
||||
def find_element_by_tag_name(self, name):
|
||||
def find_elements_by_tag_name(self, name):
|
||||
def find_element_by_class_name(self, name):
|
||||
def find_elements_by_class_name(self, name):
|
||||
def find_element_by_css_selector(self, css_selector):
|
||||
def find_elements_by_css_selector(self, css_selector):
|
||||
def find_element(self, by=By.ID, value=None):
|
||||
def find_elements(self, by=By.ID, value=None):
|
||||
"""
|
||||
step_result['assert'] = False
|
||||
step_result['assert_results'] = []
|
||||
for aidx, item in enumerate(step['assert']):
|
||||
step_result['assert_results'].append(False)
|
||||
try:
|
||||
assert_method = getattr(self.browser, item['type'])
|
||||
assert assert_method(item['text'])
|
||||
except KeyError:
|
||||
self.failed('configuration failure. check syntax.',
|
||||
step_result)
|
||||
except NoSuchElementException:
|
||||
self.failed('no such element: ' +item['type'] +' - ' +item['text'], step_result)
|
||||
step_result['assert_results'][aidx] = True
|
||||
step_result['assert'] = True
|
||||
return step_result
|
||||
|
||||
def steps(self):
|
||||
"""Loop through steps."""
|
||||
for idx, step in enumerate(self.arg.steps):
|
||||
step_result = {'id': idx,
|
||||
'screenshot': 'no'}
|
||||
if 'name' in step:
|
||||
step_result['name'] = step['name']
|
||||
|
||||
if 'keys' in step:
|
||||
step_result.update(self.keys(step, step_result))
|
||||
|
||||
if 'click' in step:
|
||||
step_result.update(self.click(step, step_result))
|
||||
|
||||
if 'wait_for' in step:
|
||||
step_result.update(self.wait_for(step, step_result))
|
||||
|
||||
if 'assert' in step:
|
||||
step_result.update(self.asserts(step, step_result))
|
||||
|
||||
when = self.arg.screenshot_when
|
||||
if self.arg.screenshot:
|
||||
capture = False
|
||||
if 'all' in when:
|
||||
capture = True
|
||||
elif 'start' in when and idx is 0:
|
||||
capture = True
|
||||
elif 'end' in when and idx is self.steps_num:
|
||||
capture = True
|
||||
|
||||
if capture:
|
||||
suffix = idx
|
||||
if 'name' in step:
|
||||
suffix = '%s_%s' % (idx, step['name'])
|
||||
step_result['screenshot'] = self.screenshot(suffix)
|
||||
self.result['results']['steps'].append(step_result)
|
||||
|
||||
|
||||
def which(fname):
|
||||
"""Returns the fully qualified path by searching Path of the given
|
||||
name"""
|
||||
for pe in os.environ['PATH'].split(os.pathsep):
|
||||
checkname = os.path.join(pe, fname)
|
||||
if os.access(checkname, os.X_OK) and not os.path.isdir(checkname):
|
||||
return checkname
|
||||
return None
|
||||
|
||||
def test():
|
||||
# https://intoli.com/blog/running-selenium-with-headless-firefox/
|
||||
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
|
||||
# Set the MOZ_HEADLESS environment variable which casues Firefox to start in headless mode.
|
||||
if not 'MOZ_HEADLESS' in os.environ: os.environ['MOZ_HEADLESS'] = "1"
|
||||
|
||||
# Select your Firefox binary.
|
||||
firefox_path = '/usr/bin/firefox'
|
||||
assert os.path.isfile(firefox_path)
|
||||
binary = FirefoxBinary(firefox_path=firefox_path, log_file=sys.stdout)
|
||||
# Start selenium with the configured binary.
|
||||
driver = webdriver.Firefox(firefox_binary=binary)
|
||||
|
||||
# Visit this webpage.
|
||||
driver.get("http://localhost:3128/")
|
||||
heading_element = driver.find_element_by_xpath('//*[@href="doc/"]')
|
||||
if heading_element:
|
||||
textContent = heading_element.get_property('textContent').strip()
|
||||
print('textContent = ' +textContent)
|
||||
assert textContent == 'The Polipo manual'
|
||||
else:
|
||||
print("Heading element not found!")
|
||||
|
||||
driver.get("http://localhost:9090/")
|
||||
heading_element = driver.find_element_by_xpath('//*[@href="/+history/FrontPage"]')
|
||||
if heading_element:
|
||||
textContent = heading_element.get_property('textContent').strip()
|
||||
print('textContent = ' +textContent)
|
||||
assert textContent == 'History'
|
||||
else:
|
||||
print("Heading element not found!")
|
||||
|
||||
driver.quit()
|
||||
# Ideally, we could instruct Firefox to run in headless mode by including the -headless flag when running the
|
||||
# binary with something like
|
||||
# binary.add_command_line_options('-headless')
|
||||
# However, on current versions of Firefox (up to and including Nightly 58.0a1) running on Windows 10 this
|
||||
# flag doesn?t seem to work. Luckily, we can achieve the same effect by setting the MOZ_HEADLESS environment
|
||||
# variable either from the command line with set MOZ_HEADLESS=1 or from the python script itself as above.
|
||||
return 0
|
||||
|
||||
def main():
|
||||
"""Main."""
|
||||
# pylint: disable = too-many-branches
|
||||
|
||||
from ansible.module_utils.basic import _load_params,_ANSIBLE_ARGS
|
||||
if len(sys.argv) > 1:
|
||||
if not os.path.isfile(sys.argv[1]):
|
||||
raise RuntimeError('ERROR: ' +'file not found ' +sys.argv[1])
|
||||
|
||||
# this allows us to leave stdin alone for pdb
|
||||
try:
|
||||
with open(sys.argv[1], 'rb') as fd:
|
||||
_ANSIBLE_ARGS = fd.read()
|
||||
except Exception as e:
|
||||
raise RuntimeError('ERROR: ' +'file not read ' +repr(sys.argv) +str(e))
|
||||
|
||||
try:
|
||||
module = AnsibleModule(
|
||||
# check_invalid_arguments=False,
|
||||
argument_spec=dict(
|
||||
url=dict(type='str', required=True),
|
||||
browser=dict(type='str', default='phantomjs',
|
||||
choices=['phantomjs', 'firefox', 'chrome']),
|
||||
browser_executable=dict(type='path', default=""),
|
||||
browser_log=dict(type='path', default=""),
|
||||
browser_timeout=dict(type='int', default=30),
|
||||
browser_service_args=dict(type='list', default=[]),
|
||||
# For firefox - if this is "legacy" marionette will not be used -
|
||||
# The legacy driver provided and maintained by the Selenium project
|
||||
# doesn't work for Firefox 48 or higher, and will never work for newer versions of Firefox.
|
||||
webdriver_executable=dict(type='path', default=""),
|
||||
webdriver_log=dict(type='str', default=""),
|
||||
width=dict(type='int', default=1024),
|
||||
height=dict(type='int', default=768),
|
||||
title=dict(type='str', default=""),
|
||||
screenshot=dict(type='bool', default=False),
|
||||
screenshot_when=dict(type='list', default=['error']),
|
||||
screenshot_type=dict(type='str', default='base64',
|
||||
choices=['file', 'base64']),
|
||||
screenshot_path=dict(type='str', default='/tmp'),
|
||||
screenshot_prefix=dict(type='str', default='selenium_'),
|
||||
steps=dict(type='list', required=True),
|
||||
explicit_wait=dict(type='int', default=2),
|
||||
implicit_wait=dict(type='int', default=20),
|
||||
validate_cert=dict(type='bool', default=True),
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
except Exception as e:
|
||||
results = {'failed': True, 'msg': 'ERROR: selenium_test.py AnsibleModule failed ' +str(e)}
|
||||
print('\n' +jsonify(results))
|
||||
sys.exit(1)
|
||||
|
||||
# check urlparse dependency
|
||||
if URLPARSE_INSTALLED_ERR:
|
||||
dArgs=dict(error=True, msg=URLPARSE_INSTALLED_ERR)
|
||||
module.fail_json(**dArgs)
|
||||
# check selenium dependency
|
||||
if SELENIUM_INSTALLED_ERR:
|
||||
dArgs=dict(error=True, msg=SELENIUM_INSTALLED_ERR)
|
||||
module.fail_json(**dArgs)
|
||||
try:
|
||||
# initiate module
|
||||
results = {'failed': True, 'msg': 'ERROR: selenium_test.py something went wrong'}
|
||||
with AnsibleSelenium(module) as sel:
|
||||
results = sel.result
|
||||
module.exit_json(**results)
|
||||
except Exception as e:
|
||||
results = {'failed': True, 'msg': 'ERROR: selenium_test.py AnsibleSelenium failed ' +str(e)}
|
||||
print('\n' +jsonify(results))
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == 'test':
|
||||
i = test()
|
||||
sys.exit(i)
|
||||
main()
|
||||
|
||||
# [ -f selenium_test.json ] || yaml2json.bash < selenium_test.json > selenium_test.json
|
||||
# python2.bash selenium_test.py selenium_test.json
|
||||
# old -version 3.141 with manifest
|
||||
# /usr/local/lib64/python2.7/site-packages/selenium/webdriver/firefox/webdriver.xpi
|
402
overlay/Linux/usr/local/share/ansible/library/selenium_test.py.dst
Executable file
402
overlay/Linux/usr/local/share/ansible/library/selenium_test.py.dst
Executable file
|
@ -0,0 +1,402 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2016, Brad Gibson <napalm255@gmail.com>
|
||||
#
|
||||
# This file is a 3rd Party module for Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Ansible Selenium Module."""
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: selenium_test
|
||||
author: "Brad Gibson"
|
||||
version_added: "2.3"
|
||||
short_description: Run selenium tests
|
||||
requires: [ selenium>=3.0.2 ]
|
||||
description:
|
||||
- Run selenium tests against provided URL.
|
||||
- Use Clicks, Typing, Waiting and Assertions.
|
||||
options:
|
||||
url:
|
||||
required: true
|
||||
description:
|
||||
- URL to run selenium tests against.
|
||||
browser:
|
||||
required: false
|
||||
default: "phantomjs"
|
||||
choices: [ "phantomjs", "firefox", "chrome" ]
|
||||
description:
|
||||
- Browser to use for testing.
|
||||
width:
|
||||
required: false
|
||||
default: 1024
|
||||
description:
|
||||
- Browser screen width.
|
||||
height:
|
||||
required: false
|
||||
default: 768
|
||||
description:
|
||||
- Browser screen height.
|
||||
title:
|
||||
required: false
|
||||
description:
|
||||
- Title to validate after initial load.
|
||||
screenshot:
|
||||
required: false
|
||||
default: false
|
||||
description:
|
||||
- Enable/Disable screenshots.
|
||||
screenshot_when:
|
||||
required: false
|
||||
default: [ "error" ]
|
||||
choices: [ "all", "start", "end", "error" ]
|
||||
description:
|
||||
- Enable/Disable screenshots.
|
||||
screenshot_type:
|
||||
required: false
|
||||
default: "base64"
|
||||
choices: [ "base64", "file" ]
|
||||
description:
|
||||
- Screenshot format.
|
||||
screenshot_path:
|
||||
required: false
|
||||
default: "/tmp"
|
||||
description:
|
||||
- Screenshot path.
|
||||
screenshot_prefix:
|
||||
required: false
|
||||
default: "selenium_"
|
||||
description:
|
||||
- Screenshot file prefix.
|
||||
implicit_wait:
|
||||
required: false
|
||||
default: 20
|
||||
description:
|
||||
- Implicit wait value when loading webpage.
|
||||
explicit_wait:
|
||||
required: false
|
||||
default: 2
|
||||
description:
|
||||
- Explicit wait value when loading webpage.
|
||||
steps:
|
||||
required: true
|
||||
description:
|
||||
- Steps to perform.
|
||||
validate_cert:
|
||||
required: false
|
||||
default: true
|
||||
description:
|
||||
- Validate SSL certificate.
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# run basic check against given url
|
||||
- selenium_test: url=http://www.python.org
|
||||
'''
|
||||
|
||||
# pylint: disable = wrong-import-position
|
||||
import sys # noqa
|
||||
try:
|
||||
if sys.version_info < (3, 0):
|
||||
from urlparse import urlparse # noqa
|
||||
else:
|
||||
from urllib.parse import urlparse # noqa
|
||||
URLPARSE_INSTALLED = True
|
||||
except ImportError:
|
||||
URLPARSE_INSTALLED = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule # noqa
|
||||
try:
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.common.exceptions import TimeoutException
|
||||
SELENIUM_INSTALLED = True
|
||||
except ImportError:
|
||||
SELENIUM_INSTALLED = False
|
||||
|
||||
|
||||
class AnsibleSelenium(object):
|
||||
"""Ansible Selenium Class."""
|
||||
|
||||
def __init__(self, module):
|
||||
"""Init."""
|
||||
self.module = module
|
||||
self.arg = lambda: None
|
||||
for arg in self.module.params:
|
||||
setattr(self.arg, arg, self.module.params[arg])
|
||||
|
||||
self.steps_num = len(self.arg.steps) - 1
|
||||
self.result = {'changed': False,
|
||||
'failed': False,
|
||||
'results': {
|
||||
'steps': [],
|
||||
'num': self.steps_num
|
||||
}}
|
||||
|
||||
self.browser = self._browser()
|
||||
|
||||
def __enter__(self):
|
||||
"""Enter by loading website and return self."""
|
||||
# validate url
|
||||
url_parsed = urlparse(self.arg.url)
|
||||
if url_parsed.scheme not in ['http', 'https']:
|
||||
self.failed('invalid url.')
|
||||
if not url_parsed.netloc:
|
||||
self.failed('invalid url.')
|
||||
|
||||
# load browser
|
||||
self.browser.get(self.arg.url)
|
||||
self.result['browser_closed'] = False
|
||||
|
||||
# validate title
|
||||
if self.arg.title in self.browser.title:
|
||||
self.result['results']['title'] = True
|
||||
else:
|
||||
self.result['results']['title'] = False
|
||||
self.failed('title does not match.')
|
||||
|
||||
# process steps
|
||||
self.steps()
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
"""Exit by closing and quitting the browser."""
|
||||
# pylint: disable = redefined-builtin
|
||||
self.browser.close()
|
||||
self.browser.quit()
|
||||
self.result['browser_closed'] = True
|
||||
|
||||
def _browser(self):
|
||||
"""Select browser and return object."""
|
||||
name = self.arg.browser
|
||||
if 'phantomjs' in name:
|
||||
return self._phantomjs()
|
||||
elif 'firefox' in name:
|
||||
return self._firefox()
|
||||
elif 'chrome' in name:
|
||||
return self._chrome()
|
||||
|
||||
def _phantomjs(self):
|
||||
"""Use PhantomJS browser."""
|
||||
service_args = ['--ssl-protocol=any']
|
||||
if not self.arg.validate_cert:
|
||||
service_args.append('--ignore-ssl-errors=true')
|
||||
driver = webdriver.PhantomJS(service_args=service_args)
|
||||
driver.set_window_size(self.arg.width, self.arg.height)
|
||||
driver.set_page_load_timeout(self.arg.implicit_wait)
|
||||
return driver
|
||||
|
||||
def _firefox(self):
|
||||
"""Use Firefox browser."""
|
||||
# pylint: disable = no-self-use
|
||||
driver = webdriver.Firefox()
|
||||
return driver
|
||||
|
||||
def _chrome(self):
|
||||
"""Use Chrome browser."""
|
||||
# pylint: disable = no-self-use
|
||||
driver = webdriver.Chrome()
|
||||
return driver
|
||||
|
||||
def failed(self, msg, step=None):
|
||||
"""Failed."""
|
||||
# self.result['failed'] = True
|
||||
self.result['msg'] = msg
|
||||
if step:
|
||||
step['error'] = True
|
||||
step['msg'] = msg
|
||||
when = self.arg.screenshot_when
|
||||
if 'all' in when or 'error' in when:
|
||||
step['screenshot'] = self.screenshot('failed')
|
||||
self.result['results']['steps'].append(step)
|
||||
else:
|
||||
self.result['results']['screenshot'] = self.screenshot('failed')
|
||||
self.module.fail_json(**self.result)
|
||||
|
||||
def screenshot(self, suffix='default'):
|
||||
"""Screenshot."""
|
||||
details = {}
|
||||
if 'base64' in self.arg.screenshot_type:
|
||||
base64 = self.browser.get_screenshot_as_base64()
|
||||
details['base64'] = base64
|
||||
elif 'file' in self.arg.screenshot_type:
|
||||
path = '%s/%s%s.png' % (self.arg.screenshot_path,
|
||||
self.arg.screenshot_prefix,
|
||||
suffix)
|
||||
self.browser.get_screenshot_as_file(path)
|
||||
details['file'] = path
|
||||
return details
|
||||
|
||||
def keys(self, step, step_result):
|
||||
"""Keys."""
|
||||
step_result['keys'] = False
|
||||
try:
|
||||
keys_method = getattr(self.browser, step['keys']['type'])
|
||||
if 'text' in step['keys']:
|
||||
value = step['keys']['text']
|
||||
keys_method(step['keys']['value']).send_keys(value)
|
||||
if 'key' in step['keys']:
|
||||
key_type = getattr(Keys, step['keys']['key'])
|
||||
keys_method(step['keys']['value']).send_keys(key_type)
|
||||
except KeyError:
|
||||
self.failed('configuration failure. check syntax.', step_result)
|
||||
except AttributeError:
|
||||
self.failed('type error. check syntax.', step_result)
|
||||
except NoSuchElementException:
|
||||
self.failed('no such element.', step_result)
|
||||
step_result['keys'] = True
|
||||
return step_result
|
||||
|
||||
def click(self, step, step_result):
|
||||
"""Click."""
|
||||
step_result['click'] = False
|
||||
try:
|
||||
click_method = getattr(self.browser, step['click']['type'])
|
||||
click_method(step['click']['text']).click()
|
||||
except KeyError:
|
||||
self.failed('configuration failure. check syntax.', step_result)
|
||||
except AttributeError:
|
||||
self.failed('type error. check syntax.', step_result)
|
||||
except NoSuchElementException:
|
||||
self.failed('no such element.', step_result)
|
||||
step_result['click'] = True
|
||||
return step_result
|
||||
|
||||
def wait_for(self, step, step_result):
|
||||
"""Wait for."""
|
||||
step_result['wait_for'] = False
|
||||
try:
|
||||
waitfor_method = getattr(EC, step['wait_for']['method'])
|
||||
waitfor_type = getattr(By, step['wait_for']['type'])
|
||||
waitfor_text = step['wait_for']['text']
|
||||
except KeyError:
|
||||
self.failed('configuration failure. check syntax.', step_result)
|
||||
except AttributeError:
|
||||
self.failed('method or type error. check syntax.', step_result)
|
||||
|
||||
try:
|
||||
WebDriverWait(self.browser, self.arg.explicit_wait).until(
|
||||
waitfor_method((waitfor_type, waitfor_text))
|
||||
)
|
||||
except TimeoutException:
|
||||
self.failed('failure waiting for element.', step_result)
|
||||
step_result['wait_for'] = True
|
||||
return step_result
|
||||
|
||||
def asserts(self, step, step_result):
|
||||
"""Assertions."""
|
||||
step_result['assert'] = False
|
||||
step_result['assert_results'] = []
|
||||
for aidx, item in enumerate(step['assert']):
|
||||
step_result['assert_results'].append(False)
|
||||
try:
|
||||
assert_method = getattr(self.browser, item['type'])
|
||||
assert assert_method(item['text'])
|
||||
except KeyError:
|
||||
self.failed('configuration failure. check syntax.',
|
||||
step_result)
|
||||
except NoSuchElementException:
|
||||
self.failed('no such element.', step_result)
|
||||
step_result['assert_results'][aidx] = True
|
||||
step_result['assert'] = True
|
||||
return step_result
|
||||
|
||||
def steps(self):
|
||||
"""Loop through steps."""
|
||||
for idx, step in enumerate(self.arg.steps):
|
||||
step_result = {'id': idx,
|
||||
'screenshot': 'no'}
|
||||
if 'name' in step:
|
||||
step_result['name'] = step['name']
|
||||
|
||||
if 'keys' in step:
|
||||
step_result.update(self.keys(step, step_result))
|
||||
|
||||
if 'click' in step:
|
||||
step_result.update(self.click(step, step_result))
|
||||
|
||||
if 'wait_for' in step:
|
||||
step_result.update(self.wait_for(step, step_result))
|
||||
|
||||
if 'assert' in step:
|
||||
step_result.update(self.asserts(step, step_result))
|
||||
|
||||
when = self.arg.screenshot_when
|
||||
if self.arg.screenshot:
|
||||
capture = False
|
||||
if 'all' in when:
|
||||
capture = True
|
||||
elif 'start' in when and idx is 0:
|
||||
capture = True
|
||||
elif 'end' in when and idx is self.steps_num:
|
||||
capture = True
|
||||
|
||||
if capture:
|
||||
suffix = idx
|
||||
if 'name' in step:
|
||||
suffix = '%s_%s' % (idx, step['name'])
|
||||
step_result['screenshot'] = self.screenshot(suffix)
|
||||
self.result['results']['steps'].append(step_result)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main."""
|
||||
# pylint: disable = too-many-branches
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
url=dict(type='str', required=True),
|
||||
browser=dict(type='str', default='phantomjs',
|
||||
choices=['phantomjs', 'firefox', 'chrome']),
|
||||
width=dict(type='int', default=1024),
|
||||
height=dict(type='int', default=768),
|
||||
title=dict(type='str'),
|
||||
screenshot=dict(type='bool', default=False),
|
||||
screenshot_when=dict(type='list', default=['error']),
|
||||
screenshot_type=dict(type='str', default='base64',
|
||||
choices=['file', 'base64']),
|
||||
screenshot_path=dict(type='str', default='/tmp'),
|
||||
screenshot_prefix=dict(type='str', default='selenium_'),
|
||||
steps=dict(type='list', required=True),
|
||||
explicit_wait=dict(type='int', default=2),
|
||||
implicit_wait=dict(type='int', default=20),
|
||||
validate_cert=dict(type='bool', default=True),
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
# check urlparse dependency
|
||||
if not URLPARSE_INSTALLED:
|
||||
module.fail_json(msg='urlparse not installed.')
|
||||
|
||||
# check selenium dependency
|
||||
if not SELENIUM_INSTALLED:
|
||||
module.fail_json(msg='selenium not installed.')
|
||||
|
||||
# initiate module
|
||||
results = {'failed': True, 'msg': 'something went wrong'}
|
||||
with AnsibleSelenium(module) as sel:
|
||||
results = sel.result
|
||||
module.exit_json(**results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1 @@
|
|||
https://github.com/napalm255/ansible-selenium
|
222
overlay/Linux/usr/local/share/ansible/library/test_test.err
Normal file
222
overlay/Linux/usr/local/share/ansible/library/test_test.err
Normal file
|
@ -0,0 +1,222 @@
|
|||
which firefox || exit 0
|
||||
/usr/bin/firefox
|
||||
/var/local/bin/python2.bash selenium_test.py test
|
||||
Traceback (most recent call last):
|
||||
File "selenium_test.py", line 743, in <module>
|
||||
i = test()
|
||||
File "selenium_test.py", line 638, in test
|
||||
driver = webdriver.Firefox(firefox_binary=binary)
|
||||
File "/var/local/lib/python2.7/site-packages/selenium-4.0.0a6.post2-py2.7.egg/selenium/webdriver/firefox/webdriver.py", line 191, in __init__
|
||||
keep_alive=True)
|
||||
File "/var/local/lib/python2.7/site-packages/selenium-4.0.0a6.post2-py2.7.egg/selenium/webdriver/remote/webdriver.py", line 183, in __init__
|
||||
self.start_session(capabilities, browser_profile)
|
||||
File "/var/local/lib/python2.7/site-packages/selenium-4.0.0a6.post2-py2.7.egg/selenium/webdriver/remote/webdriver.py", line 280, in start_session
|
||||
response = self.execute(Command.NEW_SESSION, parameters)
|
||||
File "/var/local/lib/python2.7/site-packages/selenium-4.0.0a6.post2-py2.7.egg/selenium/webdriver/remote/webdriver.py", line 349, in execute
|
||||
self.error_handler.check_response(response)
|
||||
File "/var/local/lib/python2.7/site-packages/selenium-4.0.0a6.post2-py2.7.egg/selenium/webdriver/remote/errorhandler.py", line 204, in check_response
|
||||
raise exception_class(value)
|
||||
selenium.common.exceptions.WebDriverException: Message: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>503 - Forwarding failure (Privoxy@localhost)</title>
|
||||
<meta http-equiv="Content-Style-Type" content="text/css">
|
||||
<meta http-equiv="Content-Script-Type" content="text/javascript">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<link rel="shortcut icon" href="//config.privoxy.org/error-favicon.ico">
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
* CSS for Privoxy CGI and script output
|
||||
*/
|
||||
|
||||
/*
|
||||
* General rules: Font, Color, Headings, Margins, Links
|
||||
*/
|
||||
body,td,th { font-family: arial, helvetica, helv, sans-serif; }
|
||||
body { background-color: #ffffff; color: #000000; }
|
||||
|
||||
h1 { font-size: 140%; margin: 0px; }
|
||||
h2 { font-size: 120%; margin: 0px; }
|
||||
h3 { font-size: 110%; margin: 0px; }
|
||||
|
||||
p,pre { margin-left: 15px; }
|
||||
li { margin: 2px 15px; }
|
||||
dl { margin: 2px 15px; }
|
||||
|
||||
a:link { color: #0000dd; text-decoration: none; }
|
||||
a:visited { color: #330099; text-decoration: none; }
|
||||
a:active { color: #3333ff; text-decoration: none; }
|
||||
|
||||
/*
|
||||
* Boxen as Table elements:
|
||||
*/
|
||||
td.title { border: solid black 1px; background-color: #dddddd; }
|
||||
td.box { border: solid black 1px; background-color: #eeeeee; }
|
||||
td.info { border: solid black 1px; background-color: #ccccff; }
|
||||
td.warning { border: solid black 1px; background-color: #ffdddd; }
|
||||
|
||||
/*
|
||||
* Special Table Boxen: for nesting, naked container and for
|
||||
* the Status field in CGI Output:
|
||||
*/
|
||||
td.wrapbox { border: solid black 1px; padding: 5px; }
|
||||
td.container { padding: 0px; }
|
||||
td.status { border: solid black 1px; background-color: #ff0000; color: #ffffff; font-size: 300%; font-weight: bolder; }
|
||||
|
||||
/*
|
||||
* Same Boxen as <div>s:
|
||||
*/
|
||||
div.title { border: solid black 1px; background-color: #dddddd; margin: 20px; padding: 20px; }
|
||||
div.box { border: solid black 1px; background-color: #eeeeee; margin: 20px; padding: 20px; }
|
||||
div.info { border: solid black 1px; background-color: #ccccff; margin: 20px; padding: 20px; }
|
||||
div.warning { border: solid black 1px; background-color: #ffdddd; margin: 20px; padding: 20px; }
|
||||
div.wrapbox { border: solid black 1px; margin: 20px; padding: 5px; }
|
||||
|
||||
|
||||
/*
|
||||
* Bold definitions in <dl>s, grey BG for table headings, transparent (no-bordered) table
|
||||
*/
|
||||
dt { font-weight: bold; }
|
||||
th { background-color: #dddddd; }
|
||||
table.transparent { border-style: none}
|
||||
|
||||
/*
|
||||
* Special purpose paragraphs: Small for page footers,
|
||||
* Important for quoting wrong or dangerous examples,
|
||||
* Whiteframed for the toggle?mini=y CGI
|
||||
*/
|
||||
p.small { font-size: 10px; margin: 0px; }
|
||||
p.important { border: solid black 1px; background-color: #ffdddd; font-weight: bold; padding: 2px; }
|
||||
p.whiteframed { margin: 5px; padding: 5px; border: solid black 1px; text-align: center; background-color: #eeeeee; }
|
||||
|
||||
/*
|
||||
* Links as buttons:
|
||||
*/
|
||||
|
||||
td.buttons {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
a.cmd, td.indentbuttons a, td.buttons a {
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
padding: 2px;
|
||||
background-color: #dddddd;
|
||||
color: #000000;
|
||||
text-decoration: none;
|
||||
border-top: 1px solid #ffffff;
|
||||
border-left: 1px solid #ffffff;
|
||||
border-bottom: 1px solid #000000;
|
||||
border-right: 1px solid #000000;
|
||||
}
|
||||
a.cmd:hover, td.indentbuttons a:hover, td.buttons a:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
a.cmd:active, td.indentbuttons a:active, td.buttons a:active {
|
||||
border-top: 1px solid #000000;
|
||||
border-left: 1px solid #000000;
|
||||
border-bottom: 1px solid #ffffff;
|
||||
border-right: 1px solid #ffffff;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Special red emphasis:
|
||||
*/
|
||||
em.warning, strong.warning { color: #ff0000 }
|
||||
|
||||
/*
|
||||
* In show-status we use tables directly behind headlines
|
||||
* and for some reason or another the headlines are set to
|
||||
* "margin:0" and leave the tables no air to breath.
|
||||
*
|
||||
* A proper fix would be to replace or remove the "margin:0",
|
||||
* but as this affects every cgi page we do it another time
|
||||
* and use this workaround until then.
|
||||
*/
|
||||
.box table { margin-top: 1em; }
|
||||
|
||||
/*
|
||||
* Let the URL and pattern input fields scale with the browser
|
||||
* width and try to prevent vertical scroll bars if the width
|
||||
* is less than 80 characters.
|
||||
*/
|
||||
input.url, input.pattern { width: 95%; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<table summary="" cellpadding="20" cellspacing="10" border="0" width="100%">
|
||||
<tr>
|
||||
<td class="status">
|
||||
503
|
||||
</td>
|
||||
<td class="title" style="width: 100%">
|
||||
|
||||
<h1>
|
||||
This is <a href="https://www.privoxy.org/">Privoxy</a> 3.0.32 on localhost (127.0.0.1), port 3128<!-- @if-can-toggle-start -->,
|
||||
disabled<!-- if-can-toggle-end@ -->
|
||||
</h1>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- -->
|
||||
|
||||
<tr>
|
||||
<td class="warning" colspan=2>
|
||||
<h2>Forwarding failure</h2>
|
||||
<p>Privoxy was unable to <b>socks5t-forward</b> your request
|
||||
<a title="Repeat the request"
|
||||
href="http://localhost:15991/session"><b>http://localhost:15991/session</b></a>
|
||||
through <b>127.0.0.1</b>:
|
||||
<strong>SOCKS5 request failed</strong></p>
|
||||
</p>
|
||||
<p>Just <a title="Repeat the request" href="http://localhost:15991/session">try again</a> to
|
||||
see if this is a temporary problem, or check your <a title="Privoxy's show-status page"
|
||||
href="//config.privoxy.org/show-status">forwarding settings</a>
|
||||
and make sure that all forwarding servers are working correctly and
|
||||
listening where they are supposed to be listening.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="box" colspan="2">
|
||||
<h2>More Privoxy:</h2>
|
||||
<ul><li><a href="//config.privoxy.org/">Privoxy main page</a></li><li><a href="//config.privoxy.org/show-status">View & change the current configuration</a></li><li><a href="//config.privoxy.org/client-tags">View or toggle the tags that can be set based on the client's address</a></li><li><a href="//config.privoxy.org/show-request">View the request headers</a></li><li><a href="//config.privoxy.org/show-url-info">Look up which actions apply to a URL and why</a></li><li><a href="//config.privoxy.org/user-manual/">Documentation</a></li></ul>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- -->
|
||||
|
||||
<tr>
|
||||
<td class="info" colspan="2">
|
||||
|
||||
<h2>Support and Service:</h2>
|
||||
<p>
|
||||
The Privoxy Team values your feedback.
|
||||
</p>
|
||||
<p>
|
||||
Please have a look at the User Manual to learn how to
|
||||
<a title="Privoxy User Manual: Contacting the developers, Bug Reporting and Feature Requests"
|
||||
href="//config.privoxy.org/user-manual/contact.html">get support or report problems</a>.
|
||||
<p>
|
||||
If you want to support the Privoxy Team, you can
|
||||
<a href="https://www.privoxy.org/participate">participate</a>
|
||||
or <a href="https://www.privoxy.org/donate">donate</a>.
|
||||
</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
make: *** [Makefile:22: test_test] Error 1
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"ANSIBLE_MODULE_ARGS": {
|
||||
"screenshot_when": [
|
||||
"end",
|
||||
"error"
|
||||
],
|
||||
"webdriver_log": "/tmp/test_ipleak.net_firefox_geckodriver.log",
|
||||
"screenshot": true,
|
||||
"title": "IP/DNS Detect - What is your IP, what is your DNS, what informations you send to websites",
|
||||
"url": "https://ipv4.ipleak.net/",
|
||||
"webdriver_executable": "/usr/bin/geckodriver",
|
||||
"validate_cert": true,
|
||||
"browser_log": "/tmp/test_ipleak.net_firefox_geckodriver.log",
|
||||
"screenshot_path": "/tmp",
|
||||
"steps": [
|
||||
{
|
||||
"assert": [
|
||||
{
|
||||
"text": "IPv4",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
],
|
||||
"wait_for": {
|
||||
"text": "IPv4",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "IPv4",
|
||||
"click": {
|
||||
"text": "IPv4",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"screenshot_prefix": "test_ipleak.net_firefox_",
|
||||
"screenshot_type": "file",
|
||||
"browser_executable": "/usr/bin/firefox",
|
||||
"browser": "firefox"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: "https://ipv4.ipleak.net/"
|
||||
title: "IP/DNS Detect - What is your IP, what is your DNS, what informations you send to websites"
|
||||
browser: "firefox"
|
||||
browser_log: "/tmp/test_ipleak.net_firefox_geckodriver.log"
|
||||
# must run webdriver.xpi
|
||||
browser_executable: "/usr/bin/firefox"
|
||||
webdriver_log: "/tmp/test_ipleak.net_firefox_geckodriver.log"
|
||||
webdriver_executable: "/usr/bin/geckodriver"
|
||||
screenshot: true
|
||||
screenshot_prefix: test_ipleak.net_firefox_
|
||||
screenshot_when: ['end', 'error']
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
validate_cert: true
|
||||
steps:
|
||||
- name: IPv4
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'IPv4'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'IPv4'
|
||||
assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'IPv4'
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"ANSIBLE_MODULE_ARGS": {
|
||||
"screenshot_when": [
|
||||
"end",
|
||||
"error"
|
||||
],
|
||||
"webdriver_log": "/tmp/test_ipleak.net_phantomjs_ghostdriver.log",
|
||||
"screenshot": true,
|
||||
"title": "IP/DNS Detect - What is your IP, what is your DNS, what informations you send to websites",
|
||||
"url": "https://ipv4.ipleak.net/",
|
||||
"webdriver_executable": "/usr/bin/ghostdriver",
|
||||
"validate_cert": true,
|
||||
"browser_log": "/tmp/test_ipleak.net_phantomjs_ghostdriver.log",
|
||||
"screenshot_path": "/tmp",
|
||||
"steps": [
|
||||
{
|
||||
"assert": [
|
||||
{
|
||||
"text": "IPv4",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
],
|
||||
"wait_for": {
|
||||
"text": "IPv4",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "IPv4",
|
||||
"click": {
|
||||
"text": "IPv4",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"screenshot_prefix": "test_ipleak.net_phantomjs_",
|
||||
"screenshot_type": "file",
|
||||
"browser_executable": "/usr/bin/phantomjs",
|
||||
"browser": "phantomjs"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: "https://ipv4.ipleak.net/"
|
||||
title: "IP/DNS Detect - What is your IP, what is your DNS, what informations you send to websites"
|
||||
browser: "phantomjs"
|
||||
browser_log: "/tmp/phantomjs-ipleak.net.log"
|
||||
# must run webdriver.xpi
|
||||
browser_executable: "/var/local/bin/phantomjs.bash"
|
||||
webdriver_log: "/tmp/ghostdriver-ipleak.net.log"
|
||||
# webdriver_executable: "/var/local/bin/ghostdriver.bash"
|
||||
screenshot: true
|
||||
screenshot_prefix: "privacy_ipleak.net_phantomjs_"
|
||||
screenshot_when: ['end', 'error']
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
validate_cert: true
|
||||
steps:
|
||||
- name: IPv4
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'IPv4'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'IPv4'
|
||||
assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'IPv4'
|
|
@ -0,0 +1,32 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: "https://ipv4.ipleak.net/"
|
||||
title: "IP/DNS Detect - What is your IP, what is your DNS, what informations you send to websites"
|
||||
browser: "firefox"
|
||||
browser_log: "/tmp/waterfox-ipleak.net.log"
|
||||
# must run webdriver.xpi
|
||||
browser_executable: "/var/local/bin/waterfox-classic.bash"
|
||||
webdriver_log: "/tmp/geckodriver-ipleak.net.log"
|
||||
webdriver_executable: "/var/local/bin/geckodriver.bash"
|
||||
screenshot: true
|
||||
screenshot_prefix: test_ipleak.net_waterfox_
|
||||
screenshot_when: ['end', 'error']
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
validate_cert: true
|
||||
steps:
|
||||
- name: IPv4
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'IPv4'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'IPv4'
|
||||
assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'IPv4'
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"ANSIBLE_MODULE_ARGS": {
|
||||
"screenshot_when": [
|
||||
"end",
|
||||
"error"
|
||||
],
|
||||
"webdriver_log": "/tmp/geckodriver-polipo.log",
|
||||
"screenshot": true,
|
||||
"url": "http://localhost:3128/",
|
||||
"webdriver_executable": "geckodriver",
|
||||
"validate_cert": false,
|
||||
"browser_log": "/tmp/firefox-polipo.log",
|
||||
"screenshot_path": "/tmp",
|
||||
"steps": [
|
||||
{
|
||||
"name": "The Polipo Manual!",
|
||||
"click": {
|
||||
"text": "The Polipo manual",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"wait_for": {
|
||||
"text": "Variable index",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
}
|
||||
},
|
||||
{
|
||||
"assert": [
|
||||
{
|
||||
"text": "1 Background",
|
||||
"type": "find_element_by_link_text"
|
||||
},
|
||||
{
|
||||
"text": "2 Running Polipo",
|
||||
"type": "find_element_by_link_text"
|
||||
},
|
||||
{
|
||||
"text": "3 Polipo and the network",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"screenshot_prefix": "privacy_polipo_firefox_",
|
||||
"screenshot_type": "file",
|
||||
"browser_executable": "firefox",
|
||||
"browser": "firefox"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: "http://localhost:3128/"
|
||||
browser: firefox
|
||||
browser_log: "/tmp/firefox-polipo.log"
|
||||
browser_executable: "firefox"
|
||||
webdriver_executable: "geckodriver"
|
||||
webdriver_log: "/tmp/geckodriver-polipo.log"
|
||||
# not found
|
||||
# title: "Welcome to Polipo!"
|
||||
screenshot: true
|
||||
screenshot_prefix: privacy_polipo_firefox_
|
||||
screenshot_when: ['end', 'error']
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
validate_cert: false
|
||||
steps:
|
||||
- name: The Polipo Manual!
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'The Polipo manual'
|
||||
|
||||
- wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Variable index'
|
||||
|
||||
- assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: '1 Background'
|
||||
|
||||
- type: 'find_element_by_link_text'
|
||||
text: '2 Running Polipo'
|
||||
|
||||
- type: 'find_element_by_link_text'
|
||||
text: '3 Polipo and the network'
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"ANSIBLE_MODULE_ARGS": {
|
||||
"screenshot_when": [
|
||||
"end",
|
||||
"error"
|
||||
],
|
||||
"webdriver_log": "/tmp/ghostdriver-polipo.log",
|
||||
"screenshot": true,
|
||||
"url": "http://localhost:3128/",
|
||||
"webdriver_executable": "ghostdriver",
|
||||
"validate_cert": false,
|
||||
"browser_log": "/tmp/phantomjs-polipo.log",
|
||||
"screenshot_path": "/tmp",
|
||||
"steps": [
|
||||
{
|
||||
"name": "The Polipo Manual!",
|
||||
"click": {
|
||||
"text": "The Polipo manual",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"wait_for": {
|
||||
"text": "Variable index",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
}
|
||||
},
|
||||
{
|
||||
"assert": [
|
||||
{
|
||||
"text": "1 Background",
|
||||
"type": "find_element_by_link_text"
|
||||
},
|
||||
{
|
||||
"text": "2 Running Polipo",
|
||||
"type": "find_element_by_link_text"
|
||||
},
|
||||
{
|
||||
"text": "3 Polipo and the network",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"screenshot_prefix": "privacy_polipo_phantomjs_",
|
||||
"screenshot_type": "file",
|
||||
"browser_executable": "phantomjs",
|
||||
"browser": "phantomjs"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: "http://localhost:3128/"
|
||||
# not found
|
||||
# title: "Welcome to Polipo!"
|
||||
browser: phantomjs
|
||||
browser_log: "/tmp/phantomjs-polipo.log"
|
||||
browser_executable: "var/local/bin/phantomjs.bash"
|
||||
webdriver_log: "/tmp/ghostdriver-polipo.log"
|
||||
screenshot: true
|
||||
screenshot_prefix: privacy_polipo_phantomjs_
|
||||
screenshot_when: ['end', 'error']
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
validate_cert: false
|
||||
steps:
|
||||
- name: The Polipo Manual!
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'The Polipo manual'
|
||||
|
||||
- wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Variable index'
|
||||
|
||||
- assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: '1 Background'
|
||||
|
||||
- type: 'find_element_by_link_text'
|
||||
text: '2 Running Polipo'
|
||||
|
||||
- type: 'find_element_by_link_text'
|
||||
text: '3 Polipo and the network'
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"ANSIBLE_MODULE_ARGS": {
|
||||
"screenshot_when": [
|
||||
"start",
|
||||
"end",
|
||||
"error"
|
||||
],
|
||||
"webdriver_log": "/tmp/geckodriver-python.org.log",
|
||||
"screenshot": true,
|
||||
"title": "Python",
|
||||
"url": "https://www.python.org",
|
||||
"validate_cert": true,
|
||||
"browser_log": "/tmp/firefox-python.org.log",
|
||||
"screenshot_path": "/tmp",
|
||||
"steps": [
|
||||
{
|
||||
"keys": {
|
||||
"text": "garbage",
|
||||
"type": "find_element_by_id",
|
||||
"value": "id-search-field",
|
||||
"key": "RETURN"
|
||||
},
|
||||
"wait_for": {
|
||||
"text": "Python 2.0",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "Login"
|
||||
},
|
||||
{
|
||||
"wait_for": {
|
||||
"text": "Conferences and Workshops",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "About",
|
||||
"click": {
|
||||
"text": "About",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"wait_for": {
|
||||
"text": "Download Python 3.5.2",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "Downloads",
|
||||
"click": {
|
||||
"text": "Downloads",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"assert": [
|
||||
{
|
||||
"text": "Python 3.x Docs",
|
||||
"type": "find_element_by_link_text"
|
||||
},
|
||||
{
|
||||
"text": "Python 2.x Docs",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
],
|
||||
"wait_for": {
|
||||
"text": "Python 3.x Docs",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "Documentation",
|
||||
"click": {
|
||||
"text": "Documentation",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"screenshot_prefix": "test_python.org_firefox_",
|
||||
"screenshot_type": "file",
|
||||
"browser_executable": "/usr/bin/firefox",
|
||||
"browser": "firefox"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
|
||||
#- hosts: localhost
|
||||
# connection: local
|
||||
# gather_facts: false
|
||||
#
|
||||
# tasks:
|
||||
# - name: Debug
|
||||
# debug: var=inventory_hostname
|
||||
#
|
||||
# - name: selenium
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: https://www.python.org
|
||||
title: Python
|
||||
browser: firefox
|
||||
browser_log: "/tmp/firefox-python.org.log"
|
||||
browser_executable: "/usr/bin/firefox"
|
||||
webdriver_log: "/tmp/geckodriver-python.org.log"
|
||||
screenshot: true
|
||||
screenshot_prefix: test_python.org_firefox_
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
screenshot_when: ['start', 'end', 'error']
|
||||
validate_cert: true
|
||||
steps:
|
||||
- name: Login
|
||||
keys:
|
||||
type: 'find_element_by_id'
|
||||
value: 'id-search-field'
|
||||
text: 'garbage'
|
||||
key: 'RETURN'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Python 2.0'
|
||||
- name: About
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'About'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Conferences and Workshops'
|
||||
- name: Downloads
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'Downloads'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Download Python 3.5.2'
|
||||
- name: Documentation
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'Documentation'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Python 3.x Docs'
|
||||
assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'Python 3.x Docs'
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'Python 2.x Docs'
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"ANSIBLE_MODULE_ARGS": {
|
||||
"screenshot_when": [
|
||||
"start",
|
||||
"end",
|
||||
"error"
|
||||
],
|
||||
"webdriver_log": "/tmp/ghostdriver-python.org.log",
|
||||
"screenshot": true,
|
||||
"title": "Python",
|
||||
"url": "https://www.python.org",
|
||||
"validate_cert": true,
|
||||
"browser_log": "/tmp/phantomjs-python.org.log",
|
||||
"screenshot_path": "/tmp",
|
||||
"steps": [
|
||||
{
|
||||
"keys": {
|
||||
"text": "garbage",
|
||||
"type": "find_element_by_id",
|
||||
"value": "id-search-field",
|
||||
"key": "RETURN"
|
||||
},
|
||||
"wait_for": {
|
||||
"text": "Python 2.0",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "Login"
|
||||
},
|
||||
{
|
||||
"wait_for": {
|
||||
"text": "Conferences and Workshops",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "About",
|
||||
"click": {
|
||||
"text": "About",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"wait_for": {
|
||||
"text": "Download Python 3.5.2",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "Downloads",
|
||||
"click": {
|
||||
"text": "Downloads",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"assert": [
|
||||
{
|
||||
"text": "Python 3.x Docs",
|
||||
"type": "find_element_by_link_text"
|
||||
},
|
||||
{
|
||||
"text": "Python 2.x Docs",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
],
|
||||
"wait_for": {
|
||||
"text": "Python 3.x Docs",
|
||||
"type": "LINK_TEXT",
|
||||
"method": "presence_of_element_located"
|
||||
},
|
||||
"name": "Documentation",
|
||||
"click": {
|
||||
"text": "Documentation",
|
||||
"type": "find_element_by_link_text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"screenshot_prefix": "test_python.org_phantomjs_",
|
||||
"screenshot_type": "file",
|
||||
"browser_executable": "/usr/bin/phantomjs",
|
||||
"browser": "phantomjs"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
# -*- mode: yaml; indent-tabs-mode: nil; tab-width: 2; coding: utf-8-unix -*-
|
||||
|
||||
---
|
||||
|
||||
#- hosts: localhost
|
||||
# connection: local
|
||||
# gather_facts: false
|
||||
#
|
||||
# tasks:
|
||||
# - name: Debug
|
||||
# debug: var=inventory_hostname
|
||||
#
|
||||
# - name: selenium
|
||||
# selenium_test:
|
||||
ANSIBLE_MODULE_ARGS:
|
||||
url: https://www.python.org
|
||||
title: Python
|
||||
browser: phantomjs
|
||||
browser_log: "/tmp/phantomjs-python.org.log"
|
||||
browser_executable: "var/local/bin/phantomjs.bash"
|
||||
webdriver_log: "/tmp/ghostdriver-python.org.log"
|
||||
screenshot: true
|
||||
screenshot_prefix: test_python.org_phantomjs_
|
||||
screenshot_type: file
|
||||
screenshot_path: /tmp
|
||||
screenshot_when: ['start', 'end', 'error']
|
||||
validate_cert: true
|
||||
steps:
|
||||
- name: Login
|
||||
keys:
|
||||
type: 'find_element_by_id'
|
||||
value: 'id-search-field'
|
||||
text: 'garbage'
|
||||
key: 'RETURN'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Python 2.0'
|
||||
- name: About
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'About'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Conferences and Workshops'
|
||||
- name: Downloads
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'Downloads'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Download Python 3.5.2'
|
||||
- name: Documentation
|
||||
click:
|
||||
type: 'find_element_by_link_text'
|
||||
text: 'Documentation'
|
||||
wait_for:
|
||||
method: 'presence_of_element_located'
|
||||
type: 'LINK_TEXT'
|
||||
text: 'Python 3.x Docs'
|
||||
assert:
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'Python 3.x Docs'
|
||||
- type: 'find_element_by_link_text'
|
||||
text: 'Python 2.x Docs'
|
Binary file not shown.
|
@ -0,0 +1,23 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
|
||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
||||
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
|
||||
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
|
||||
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
|
||||
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
|
||||
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
|
||||
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
|
||||
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
|
||||
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
|
||||
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
|
||||
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
|
||||
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
|
||||
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
|
||||
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
|
||||
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
|
||||
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
|
||||
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
|
||||
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
|
||||
+OkuE6N36B9K
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,6 @@
|
|||
s/ & / and /g
|
||||
s/"/_/g
|
||||
s/'/_/g
|
||||
s/[^ .,~%+=^@!0-9a-zA-z_#-]/_/g
|
||||
s/\[/_/g
|
||||
s/\]/_/g
|
Loading…
Add table
Add a link
Reference in a new issue