This commit is contained in:
embed@git.macaw.me 2024-01-06 01:38:28 +00:00
commit b50fd16591
197 changed files with 41663 additions and 0 deletions

View 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

View file

@ -0,0 +1 @@
ansible-keepassxc.py

View 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()

View file

@ -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),
)

View 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()

View file

@ -0,0 +1 @@
https://github.com/jlumley/ansible-keepassxc/raw/main/keepassxc.py

View 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()

View file

@ -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

View 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()

View file

@ -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

View file

@ -0,0 +1 @@
https://github.com/ansible-community/community.libvirt-1.0.0/plugins/connection/libvirt_qemu.py

View 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

View 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()

View file

@ -0,0 +1 @@
https://github.com/napalm255/ansible-selenium

View 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 &amp; 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&#39;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

View file

@ -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"
}
}

View file

@ -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'

View file

@ -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"
}
}

View file

@ -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'

View file

@ -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'

View file

@ -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"
}
}

View file

@ -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'

View file

@ -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"
}
}

View file

@ -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'

View file

@ -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"
}
}

View file

@ -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'

View file

@ -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"
}
}

View file

@ -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'

View file

@ -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-----

View file

@ -0,0 +1,6 @@
s/ &amp; / and /g
s/&quot;/_/g
s/&#x27;/_/g
s/[^ .,~%+=^@!0-9a-zA-z_#-]/_/g
s/\[/_/g
s/\]/_/g