Remove incidental_zabbix_host (#72142)
* Add explicit apt tests for fnmatch and update_cache
* Add explicit apt_key tests for fetching key directly from url
* ci_complete ci_coverage
* Remove repo only by repo
* ci_complete ci_coverage
* Add apt cache update after apt_repository to show that the cache doesn't update
* ci_complete ci_coverage
* Add systemd tests for enabling and disabling a service
* ci_complete ci_coverage
* Remove incidental_zabbix_host
* ci_complete ci_coverage
(cherry picked from commit 2f8dbf673e
)
This commit is contained in:
parent
dca64d3c74
commit
73991233fb
39 changed files with 86 additions and 4685 deletions
|
@ -76,6 +76,45 @@
|
|||
- "dpkg_result.rc == 1"
|
||||
- "'Auto-installing missing dependency without updating cache: {{ python_apt }}' in apt_result.warnings"
|
||||
|
||||
- name: Test installing fnmatch package
|
||||
apt:
|
||||
name:
|
||||
- hel?o
|
||||
- he?lo
|
||||
register: apt_install_fnmatch
|
||||
|
||||
- name: Test uninstalling fnmatch package
|
||||
apt:
|
||||
name:
|
||||
- hel?o
|
||||
- he?lo
|
||||
state: absent
|
||||
register: apt_uninstall_fnmatch
|
||||
|
||||
- name: verify fnmatch
|
||||
assert:
|
||||
that:
|
||||
- apt_install_fnmatch is changed
|
||||
- apt_uninstall_fnmatch is changed
|
||||
|
||||
- name: Test update_cache 1
|
||||
apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 10
|
||||
register: apt_update_cache_1
|
||||
|
||||
- name: Test update_cache 2
|
||||
apt:
|
||||
update_cache: true
|
||||
cache_valid_time: 10
|
||||
register: apt_update_cache_2
|
||||
|
||||
- name: verify update_cache
|
||||
assert:
|
||||
that:
|
||||
- apt_update_cache_1 is changed
|
||||
- apt_update_cache_2 is not changed
|
||||
|
||||
- name: uninstall {{ python_apt }} with apt again
|
||||
apt:
|
||||
pkg: "{{ python_apt }}"
|
||||
|
|
|
@ -142,11 +142,17 @@
|
|||
apt_repository: repo='{{test_ppa_spec}}' state=present
|
||||
register: result
|
||||
|
||||
- name: update the cache
|
||||
apt:
|
||||
update_cache: true
|
||||
register: result_cache
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'result.changed'
|
||||
- 'result.state == "present"'
|
||||
- 'result.repo == "{{test_ppa_spec}}"'
|
||||
- result_cache is not changed
|
||||
|
||||
- name: 'examine apt cache mtime'
|
||||
stat: path='/var/cache/apt/pkgcache.bin'
|
||||
|
@ -157,6 +163,10 @@
|
|||
that:
|
||||
- 'cache_before.stat.mtime != cache_after.stat.mtime'
|
||||
|
||||
- name: remove repo by spec
|
||||
apt_repository: repo='{{test_ppa_spec}}' state=absent
|
||||
register: result
|
||||
|
||||
# When installing a repo with the spec, the key is *NOT* added
|
||||
- name: 'ensure ppa key is absent (expect: pass)'
|
||||
apt_key: id='{{test_ppa_key}}' state=absent
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
hidden
|
|
@ -1,18 +0,0 @@
|
|||
mysql_service: mysqld
|
||||
|
||||
mysql_packages:
|
||||
- mysql-server
|
||||
- MySQL-python
|
||||
- bzip2
|
||||
|
||||
mysql_cleanup_packages:
|
||||
- mysql-community-client
|
||||
- mysql-community-common
|
||||
- mysql-community-libs
|
||||
- mysql-community-libs-compat
|
||||
- mysql-community-server
|
||||
- mysql80-community-release
|
||||
|
||||
mysql_data_dirs:
|
||||
- /var/lib/mysql
|
||||
- /usr/mysql
|
|
@ -1,25 +0,0 @@
|
|||
- name: stop mysql service
|
||||
service:
|
||||
name: "{{ mysql_service }}"
|
||||
state: stopped
|
||||
listen: cleanup mysql
|
||||
|
||||
- name: remove mysql packages
|
||||
action: '{{ ansible_pkg_mgr }}'
|
||||
args:
|
||||
name: "{{ item }}"
|
||||
state: absent
|
||||
loop: "{{ mysql_packages | union(mysql_cleanup_packages) }}"
|
||||
listen: cleanup mysql
|
||||
|
||||
- name: remove mysql data
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop: "{{ mysql_data_dirs }}"
|
||||
listen: cleanup mysql
|
||||
|
||||
- name: remove pip packages
|
||||
pip:
|
||||
name: mysql-python
|
||||
state: absent
|
|
@ -1,112 +0,0 @@
|
|||
# setup code for the mysql_db module
|
||||
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
|
||||
|
||||
# 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/>.
|
||||
|
||||
# ============================================================
|
||||
- name: python 2
|
||||
set_fact:
|
||||
python_suffix: ""
|
||||
when: ansible_python_version is version('3', '<')
|
||||
|
||||
- name: python 3
|
||||
set_fact:
|
||||
python_suffix: "-py3"
|
||||
when: ansible_python_version is version('3', '>=')
|
||||
|
||||
- name: Include distribution specific variables
|
||||
include_vars: "{{ lookup('first_found', params) }}"
|
||||
vars:
|
||||
params:
|
||||
files:
|
||||
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}{{ python_suffix }}.yml'
|
||||
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml'
|
||||
- '{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}{{ python_suffix }}.yml'
|
||||
- '{{ ansible_facts.os_family }}-{{ ansible_facts.distribution_major_version }}.yml'
|
||||
- '{{ ansible_facts.distribution }}{{ python_suffix }}.yml'
|
||||
- '{{ ansible_facts.os_family }}{{ python_suffix }}.yml'
|
||||
- 'default{{ python_suffix }}.yml'
|
||||
paths: "{{ role_path }}/vars"
|
||||
|
||||
- name: install mysqldb_test rpm dependencies
|
||||
yum:
|
||||
name: "{{ mysql_packages }}"
|
||||
state: latest
|
||||
when: ansible_pkg_mgr == 'yum'
|
||||
notify: cleanup mysql
|
||||
|
||||
- name: install mysqldb_test rpm dependencies
|
||||
dnf:
|
||||
name: '{{ mysql_packages }}'
|
||||
state: latest
|
||||
install_weak_deps: False # mariadb-server has a weak dep on python2 which break Python 3 test environments
|
||||
when: ansible_pkg_mgr == 'dnf'
|
||||
notify: cleanup mysql
|
||||
|
||||
- name: install mysqldb_test debian dependencies
|
||||
apt:
|
||||
name: "{{ mysql_packages }}"
|
||||
state: latest
|
||||
when: ansible_pkg_mgr == 'apt'
|
||||
notify: cleanup mysql
|
||||
|
||||
- name: install mysqldb_test opensuse dependencies
|
||||
zypper:
|
||||
name: "{{ mysql_packages }}"
|
||||
state: latest
|
||||
when: ansible_pkg_mgr == 'zypper'
|
||||
notify: cleanup mysql
|
||||
|
||||
- name: install mysqldb_test FreeBSD dependencies
|
||||
package:
|
||||
name: "{{ mysql_packages }}"
|
||||
state: present
|
||||
when: ansible_os_family == "FreeBSD"
|
||||
notify: cleanup mysql
|
||||
|
||||
- name: install mysql-python package via pip (FreeBSD)
|
||||
pip:
|
||||
name: mysql-python
|
||||
state: present
|
||||
when: ansible_os_family == "FreeBSD"
|
||||
notify:
|
||||
- cleanup mysql
|
||||
- remove pip packages
|
||||
|
||||
- name: enable mysql-server service (FreeBSD)
|
||||
lineinfile:
|
||||
path: /etc/rc.conf
|
||||
line: 'mysql_server_enable="YES"'
|
||||
when: ansible_os_family == "FreeBSD"
|
||||
|
||||
- name: apply work-around for OverlayFS issue
|
||||
# https://github.com/docker/for-linux/issues/72#issuecomment-319904698
|
||||
command: find {{ mysql_data_dirs[0] }} -type f -exec touch {} ;
|
||||
# find will fail if mysql has never been started, as the directory won't exist
|
||||
ignore_errors: yes
|
||||
|
||||
- name: restart mysql_db service
|
||||
service:
|
||||
name: "{{ mysql_service }}"
|
||||
state: restarted
|
||||
|
||||
- name: Detect socket path
|
||||
shell: 'echo "show variables like ''socket''\G" | mysql | grep ''Value: '' | sed ''s/[ ]\+Value: //'''
|
||||
register: _socket_path
|
||||
|
||||
- name: Set socket path
|
||||
set_fact:
|
||||
mysql_socket: '{{ _socket_path["stdout"] }}'
|
|
@ -1,16 +0,0 @@
|
|||
mysql_service: 'mysql'
|
||||
|
||||
mysql_packages:
|
||||
- mysql-server
|
||||
- python-mysqldb
|
||||
- bzip2
|
||||
|
||||
mysql_cleanup_packages:
|
||||
- mysql-client*
|
||||
- mysql-server*
|
||||
- mysql-common
|
||||
- mysql-sandbox
|
||||
|
||||
mysql_data_dirs:
|
||||
- /var/lib/mysql
|
||||
- /usr/share/mysql
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mariadb'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb-server
|
||||
- python3-PyMySQL
|
||||
- bzip2
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mariadb'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb-server
|
||||
- python-PyMySQL
|
||||
- bzip2
|
|
@ -1,5 +0,0 @@
|
|||
mysql_service: 'mysql-server'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb101-server
|
||||
- py-pymysql
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mariadb'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb-server
|
||||
- MySQL-python
|
||||
- bzip2
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mariadb'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb-server
|
||||
- python3-PyMySQL
|
||||
- bzip2
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mysqld'
|
||||
|
||||
mysql_packages:
|
||||
- mysql-server
|
||||
- MySQL-python
|
||||
- bzip2
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mysql'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb
|
||||
- python3-PyMySQL
|
||||
- bzip2
|
|
@ -1,6 +0,0 @@
|
|||
mysql_service: 'mysql'
|
||||
|
||||
mysql_packages:
|
||||
- mariadb
|
||||
- python-PyMySQL
|
||||
- bzip2
|
|
@ -1,16 +0,0 @@
|
|||
mysql_service: 'mysql'
|
||||
|
||||
mysql_packages:
|
||||
- mysql-server
|
||||
- python3-pymysql
|
||||
- bzip2
|
||||
|
||||
mysql_cleanup_packages:
|
||||
- mysql-client*
|
||||
- mysql-server*
|
||||
- mysql-common
|
||||
- mysql-sandbox
|
||||
|
||||
mysql_data_dirs:
|
||||
- /var/lib/mysql
|
||||
- /usr/share/mysql
|
|
@ -1,7 +0,0 @@
|
|||
destructive
|
||||
shippable/posix/group1
|
||||
skip/osx
|
||||
skip/macos
|
||||
skip/freebsd
|
||||
skip/rhel
|
||||
hidden
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
db_name: 'zabbix'
|
||||
db_user: 'zabbix'
|
||||
db_password: 'fLhijUs3PgekNhwJ'
|
||||
|
||||
zabbix_version: 4.4
|
||||
zabbix_apt_repository: 'deb http://repo.zabbix.com/zabbix/{{ zabbix_version }}/{{ ansible_distribution.lower() }}/ {{ ansible_distribution_release }} main'
|
||||
zabbix_apt_repository_key: 'http://repo.zabbix.com/zabbix-official-repo.key'
|
||||
|
||||
zabbix_packages:
|
||||
- zabbix-server-mysql
|
||||
- zabbix-frontend-php
|
||||
- zabbix-apache-conf
|
|
@ -1,15 +0,0 @@
|
|||
- name: remove zabbix repository
|
||||
apt_repository:
|
||||
repo: "{{ zabbix_apt_repository }}"
|
||||
filename: zabbix
|
||||
state: absent
|
||||
|
||||
- name: remove zabbix packages
|
||||
apt:
|
||||
name: "{{ zabbix_packages }}"
|
||||
state: absent
|
||||
|
||||
- name: remove zabbix pip packages
|
||||
pip:
|
||||
name: zabbix-api
|
||||
state: absent
|
|
@ -1,2 +0,0 @@
|
|||
dependencies:
|
||||
- incidental_setup_mysql_db
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
- include: setup.yml
|
||||
when: ansible_os_family == 'Debian'
|
|
@ -1,89 +0,0 @@
|
|||
# sets up and starts Zabbix with default settings using a MySQL database.
|
||||
|
||||
- name: install zabbix repository key
|
||||
apt_key:
|
||||
url: "{{ zabbix_apt_repository_key }}"
|
||||
state: present
|
||||
|
||||
- name: install zabbix repository
|
||||
apt_repository:
|
||||
repo: "{{ zabbix_apt_repository }}"
|
||||
filename: zabbix
|
||||
state: present
|
||||
notify: remove zabbix repository
|
||||
|
||||
- name: check if dpkg is set to exclude specific destinations
|
||||
stat:
|
||||
path: /etc/dpkg/dpkg.cfg.d/excludes
|
||||
register: dpkg_excludes
|
||||
|
||||
- name: ensure documentation installations are allowed for zabbix
|
||||
lineinfile:
|
||||
path: /etc/dpkg/dpkg.cfg.d/excludes
|
||||
regexp: '^path-include=/usr/share/doc/zabbix*$'
|
||||
line: 'path-include=/usr/share/doc/zabbix*'
|
||||
state: present
|
||||
when: dpkg_excludes.stat.exists
|
||||
|
||||
- name: install zabbix apt dependencies
|
||||
apt:
|
||||
name: "{{ zabbix_packages }}"
|
||||
state: latest
|
||||
update_cache: yes
|
||||
notify: remove zabbix packages
|
||||
|
||||
- name: install zabbix-api python package
|
||||
pip:
|
||||
name: zabbix-api
|
||||
state: latest
|
||||
notify: remove zabbix pip packages
|
||||
|
||||
- name: create mysql user {{ db_user }}
|
||||
mysql_user:
|
||||
name: "{{ db_user }}"
|
||||
password: "{{ db_password }}"
|
||||
state: present
|
||||
priv: "{{ db_name }}.*:ALL"
|
||||
login_unix_socket: '{{ mysql_socket }}'
|
||||
|
||||
- name: import initial zabbix database
|
||||
mysql_db:
|
||||
name: "{{ db_name }}"
|
||||
login_user: "{{ db_user }}"
|
||||
login_password: "{{ db_password }}"
|
||||
state: import
|
||||
target: /usr/share/doc/zabbix-server-mysql/create.sql.gz
|
||||
|
||||
- name: deploy zabbix-server configuration
|
||||
template:
|
||||
src: zabbix_server.conf.j2
|
||||
dest: /etc/zabbix/zabbix_server.conf
|
||||
owner: root
|
||||
group: zabbix
|
||||
mode: 0640
|
||||
|
||||
- name: deploy zabbix web frontend configuration
|
||||
template:
|
||||
src: zabbix.conf.php.j2
|
||||
dest: /etc/zabbix/web/zabbix.conf.php
|
||||
mode: 0644
|
||||
|
||||
- name: Create proper run directory for zabbix-server
|
||||
file:
|
||||
path: /var/run/zabbix
|
||||
state: directory
|
||||
owner: zabbix
|
||||
group: zabbix
|
||||
mode: 0775
|
||||
|
||||
- name: restart zabbix-server
|
||||
service:
|
||||
name: zabbix-server
|
||||
state: restarted
|
||||
enabled: yes
|
||||
|
||||
- name: restart apache2
|
||||
service:
|
||||
name: apache2
|
||||
state: restarted
|
||||
enabled: yes
|
|
@ -1,20 +0,0 @@
|
|||
<?php
|
||||
// Zabbix GUI configuration file
|
||||
global $DB;
|
||||
|
||||
$DB['TYPE'] = 'MYSQL';
|
||||
$DB['SERVER'] = 'localhost';
|
||||
$DB['PORT'] = '0';
|
||||
$DB['DATABASE'] = '{{ db_name }}';
|
||||
$DB['USER'] = '{{ db_user }}';
|
||||
$DB['PASSWORD'] = '{{ db_password }}';
|
||||
|
||||
// SCHEMA is relevant only for IBM_DB2 database
|
||||
$DB['SCHEMA'] = '';
|
||||
|
||||
$ZBX_SERVER = 'localhost';
|
||||
$ZBX_SERVER_PORT = '10051';
|
||||
$ZBX_SERVER_NAME = '';
|
||||
|
||||
$IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
|
||||
?>
|
|
@ -1,7 +0,0 @@
|
|||
PidFile=/var/run/zabbix/zabbix_server.pid
|
||||
LogFile=/tmp/zabbix_server.log
|
||||
DBName={{ db_name }}
|
||||
DBUser={{ db_user }}
|
||||
DBPassword={{ db_password }}
|
||||
Timeout=4
|
||||
LogSlowQueries=3000
|
|
@ -1,7 +0,0 @@
|
|||
destructive
|
||||
shippable/posix/incidental
|
||||
skip/aix
|
||||
skip/osx
|
||||
skip/macos
|
||||
skip/freebsd
|
||||
skip/rhel
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
|
||||
zabbix_server_url: http://127.0.0.1/zabbix/
|
||||
zabbix_login_user: Admin
|
||||
zabbix_login_password: zabbix
|
|
@ -1,2 +0,0 @@
|
|||
dependencies:
|
||||
- incidental_setup_zabbix
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
# setup stuff not testing zabbix_host
|
||||
- block:
|
||||
- include: zabbix_host_setup.yml
|
||||
|
||||
# zabbix_host module tests
|
||||
- include: zabbix_host_tests.yml
|
||||
|
||||
# documentation example tests
|
||||
- include: zabbix_host_doc.yml
|
||||
|
||||
# tear down stuff set up earlier
|
||||
- include: zabbix_host_teardown.yml
|
||||
|
||||
when:
|
||||
- ansible_distribution == 'Ubuntu'
|
|
@ -1,83 +0,0 @@
|
|||
---
|
||||
# These two tests are close to documentation example
|
||||
|
||||
- name: Create a new host or update an existing host's info
|
||||
zabbix_host:
|
||||
server_url: "{{ zabbix_server_url }}"
|
||||
login_user: "{{ zabbix_login_user }}"
|
||||
login_password: "{{ zabbix_login_password }}"
|
||||
host_name: ExampleHost1
|
||||
visible_name: ExampleName
|
||||
description: My ExampleHost Description
|
||||
host_groups:
|
||||
- Linux servers
|
||||
- Zabbix servers
|
||||
link_templates:
|
||||
- Template App IMAP Service
|
||||
- Template App NTP Service
|
||||
status: enabled
|
||||
state: present
|
||||
inventory_mode: manual
|
||||
inventory_zabbix:
|
||||
tag: test-tag
|
||||
alias: test-alias
|
||||
notes: "Special Informations: test-info"
|
||||
location: test-location
|
||||
site_rack: test-rack
|
||||
os: test-os
|
||||
hardware: test-hw
|
||||
ipmi_authtype: 2
|
||||
ipmi_privilege: 4
|
||||
ipmi_username: username
|
||||
ipmi_password: password
|
||||
interfaces:
|
||||
- type: 1
|
||||
main: 1
|
||||
useip: 1
|
||||
ip: 10.1.1.1
|
||||
dns: ""
|
||||
port: "10050"
|
||||
- type: 4
|
||||
main: 1
|
||||
useip: 1
|
||||
ip: 10.1.1.1
|
||||
dns: ""
|
||||
port: "12345"
|
||||
macros:
|
||||
- macro: '{$EXAMPLEMACRO}'
|
||||
value: ExampleMacroValue
|
||||
- macro: EXAMPLEMACRO2
|
||||
value: ExampleMacroValue2
|
||||
description: Example desc that work only with Zabbix 4.4 and higher
|
||||
tags:
|
||||
- tag: ExampleHostsTag
|
||||
- tag: ExampleHostsTag2
|
||||
value: ExampleTagValue
|
||||
register: zabbix_host1
|
||||
|
||||
- name: Update an existing host's tls settings
|
||||
zabbix_host:
|
||||
server_url: "{{ zabbix_server_url }}"
|
||||
login_user: "{{ zabbix_login_user }}"
|
||||
login_password: "{{ zabbix_login_password }}"
|
||||
host_name: ExampleHost2
|
||||
visible_name: ExampleName2
|
||||
interfaces:
|
||||
- type: 1
|
||||
main: 1
|
||||
useip: 1
|
||||
ip: 10.1.1.2
|
||||
dns: ""
|
||||
port: "10050"
|
||||
host_groups:
|
||||
- Linux servers
|
||||
tls_psk_identity: test
|
||||
tls_connect: 2
|
||||
tls_psk: 123456789abcdef123456789abcdef12
|
||||
register: zabbix_host2
|
||||
|
||||
- name: expect both to succeed
|
||||
assert:
|
||||
that:
|
||||
- "zabbix_host1 is changed"
|
||||
- "zabbix_host2 is changed"
|
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
# set up a zabbix proxy to test zabbix_host with
|
||||
|
||||
- name: Create a new proxy
|
||||
zabbix_proxy:
|
||||
server_url: "{{ zabbix_server_url }}"
|
||||
login_user: "{{ zabbix_login_user }}"
|
||||
login_password: "{{ zabbix_login_password }}"
|
||||
proxy_name: ExampleProxy
|
||||
description: ExampleProxy
|
||||
status: active
|
||||
state: present
|
||||
interface:
|
||||
type: 0
|
||||
main: 1
|
||||
useip: 1
|
||||
ip: 10.5.6.7
|
||||
dns: ""
|
||||
port: 10050
|
||||
register: zabbix_proxy
|
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
# remove zabbix_proxy (hopefully) created earlier
|
||||
|
||||
- name: remove proxy
|
||||
zabbix_proxy:
|
||||
server_url: "{{ zabbix_server_url }}"
|
||||
login_user: "{{ zabbix_login_user }}"
|
||||
login_password: "{{ zabbix_login_password }}"
|
||||
proxy_name: ExampleProxy
|
||||
state: absent
|
File diff suppressed because it is too large
Load diff
|
@ -25,6 +25,12 @@
|
|||
failed_when: False
|
||||
register: systemctl_check
|
||||
|
||||
- meta: end_host
|
||||
when: systemctl_check.rc != 0
|
||||
|
||||
- set_fact:
|
||||
ssh_service: '{{ "ssh" if ansible_os_family == "Debian" else "sshd" }}'
|
||||
|
||||
- block:
|
||||
- name: get a list of running services
|
||||
shell: systemctl | fgrep 'running' | awk '{print $1}' | sed 's/\.service//g' | fgrep -v '.' | egrep ^[a-z]
|
||||
|
@ -57,7 +63,7 @@
|
|||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result is search("Could not find the requested service {{ fake_service }}")
|
||||
- 'result is search("Could not find the requested service {{ fake_service }}")'
|
||||
|
||||
- name: the module must fail in check_mode as well when a service is not found
|
||||
systemd:
|
||||
|
@ -70,7 +76,7 @@
|
|||
- assert:
|
||||
that:
|
||||
- result is failed
|
||||
- result is search("Could not find the requested service {{ fake_service }}")
|
||||
- 'result is search("Could not find the requested service {{ fake_service }}")'
|
||||
|
||||
- name: check that the module works even when systemd is offline (eg in chroot)
|
||||
systemd:
|
||||
|
@ -79,4 +85,32 @@
|
|||
environment:
|
||||
SYSTEMD_OFFLINE: 1
|
||||
|
||||
when: 'systemctl_check.rc == 0'
|
||||
- name: Disable ssh 1
|
||||
systemd:
|
||||
name: '{{ ssh_service }}'
|
||||
enabled: false
|
||||
register: systemd_disable_ssh_1
|
||||
|
||||
- name: Disable ssh 2
|
||||
systemd:
|
||||
name: '{{ ssh_service }}'
|
||||
enabled: false
|
||||
register: systemd_disable_ssh_2
|
||||
|
||||
- name: Enable ssh 1
|
||||
systemd:
|
||||
name: '{{ ssh_service }}'
|
||||
enabled: true
|
||||
register: systemd_enable_ssh_1
|
||||
|
||||
- name: Enable ssh 2
|
||||
systemd:
|
||||
name: '{{ ssh_service }}'
|
||||
enabled: true
|
||||
register: systemd_enable_ssh_2
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- systemd_disable_ssh_2 is not changed
|
||||
- systemd_enable_ssh_1 is changed
|
||||
- systemd_enable_ssh_2 is not changed
|
||||
|
|
|
@ -1,617 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
|
||||
# Sponsored by Four Kitchens http://fourkitchens.com.
|
||||
# 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
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: mysql_db
|
||||
short_description: Add or remove MySQL databases from a remote host
|
||||
description:
|
||||
- Add or remove MySQL databases from a remote host.
|
||||
version_added: '0.6'
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the database to add or remove.
|
||||
- I(name=all) may only be provided if I(state) is C(dump) or C(import).
|
||||
- List of databases is provided with I(state=dump), I(state=present) and I(state=absent).
|
||||
- If I(name=all) it works like --all-databases option for mysqldump (Added in 2.0).
|
||||
required: true
|
||||
type: list
|
||||
elements: str
|
||||
aliases: [db]
|
||||
state:
|
||||
description:
|
||||
- The database state
|
||||
type: str
|
||||
default: present
|
||||
choices: ['absent', 'dump', 'import', 'present']
|
||||
collation:
|
||||
description:
|
||||
- Collation mode (sorting). This only applies to new table/databases and
|
||||
does not update existing ones, this is a limitation of MySQL.
|
||||
type: str
|
||||
default: ''
|
||||
encoding:
|
||||
description:
|
||||
- Encoding mode to use, examples include C(utf8) or C(latin1_swedish_ci),
|
||||
at creation of database, dump or importation of sql script.
|
||||
type: str
|
||||
default: ''
|
||||
target:
|
||||
description:
|
||||
- Location, on the remote host, of the dump file to read from or write to.
|
||||
- Uncompressed SQL files (C(.sql)) as well as bzip2 (C(.bz2)), gzip (C(.gz)) and
|
||||
xz (Added in 2.0) compressed files are supported.
|
||||
type: path
|
||||
single_transaction:
|
||||
description:
|
||||
- Execute the dump in a single transaction.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.1'
|
||||
quick:
|
||||
description:
|
||||
- Option used for dumping large tables.
|
||||
type: bool
|
||||
default: yes
|
||||
version_added: '2.1'
|
||||
ignore_tables:
|
||||
description:
|
||||
- A list of table names that will be ignored in the dump
|
||||
of the form database_name.table_name.
|
||||
type: list
|
||||
elements: str
|
||||
required: no
|
||||
default: []
|
||||
version_added: '2.7'
|
||||
hex_blob:
|
||||
description:
|
||||
- Dump binary columns using hexadecimal notation.
|
||||
required: no
|
||||
default: no
|
||||
type: bool
|
||||
version_added: '2.10'
|
||||
force:
|
||||
description:
|
||||
- Continue dump or import even if we get an SQL error.
|
||||
- Used only when I(state) is C(dump) or C(import).
|
||||
required: no
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.10'
|
||||
master_data:
|
||||
description:
|
||||
- Option to dump a master replication server to produce a dump file
|
||||
that can be used to set up another server as a slave of the master.
|
||||
- C(0) to not include master data.
|
||||
- C(1) to generate a 'CHANGE MASTER TO' statement
|
||||
required on the slave to start the replication process.
|
||||
- C(2) to generate a commented 'CHANGE MASTER TO'.
|
||||
- Can be used when I(state=dump).
|
||||
required: no
|
||||
type: int
|
||||
choices: [0, 1, 2]
|
||||
default: 0
|
||||
version_added: '2.10'
|
||||
skip_lock_tables:
|
||||
description:
|
||||
- Skip locking tables for read. Used when I(state=dump), ignored otherwise.
|
||||
required: no
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.10'
|
||||
dump_extra_args:
|
||||
description:
|
||||
- Provide additional arguments for mysqldump.
|
||||
Used when I(state=dump) only, ignored otherwise.
|
||||
required: no
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
seealso:
|
||||
- module: mysql_info
|
||||
- module: mysql_variables
|
||||
- module: mysql_user
|
||||
- module: mysql_replication
|
||||
- name: MySQL command-line client reference
|
||||
description: Complete reference of the MySQL command-line client documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/mysql.html
|
||||
- name: mysqldump reference
|
||||
description: Complete reference of the ``mysqldump`` client utility documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html
|
||||
- name: CREATE DATABASE reference
|
||||
description: Complete reference of the CREATE DATABASE command documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/create-database.html
|
||||
- name: DROP DATABASE reference
|
||||
description: Complete reference of the DROP DATABASE command documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/drop-database.html
|
||||
author: "Ansible Core Team"
|
||||
requirements:
|
||||
- mysql (command line binary)
|
||||
- mysqldump (command line binary)
|
||||
notes:
|
||||
- Requires the mysql and mysqldump binaries on the remote host.
|
||||
- This module is B(not idempotent) when I(state) is C(import),
|
||||
and will import the dump file each time if run more than once.
|
||||
extends_documentation_fragment: mysql
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create a new database with name 'bobdata'
|
||||
mysql_db:
|
||||
name: bobdata
|
||||
state: present
|
||||
|
||||
- name: Create new databases with names 'foo' and 'bar'
|
||||
mysql_db:
|
||||
name:
|
||||
- foo
|
||||
- bar
|
||||
state: present
|
||||
|
||||
# Copy database dump file to remote host and restore it to database 'my_db'
|
||||
- name: Copy database dump file
|
||||
copy:
|
||||
src: dump.sql.bz2
|
||||
dest: /tmp
|
||||
|
||||
- name: Restore database
|
||||
mysql_db:
|
||||
name: my_db
|
||||
state: import
|
||||
target: /tmp/dump.sql.bz2
|
||||
|
||||
- name: Restore database ignoring errors
|
||||
mysql_db:
|
||||
name: my_db
|
||||
state: import
|
||||
target: /tmp/dump.sql.bz2
|
||||
force: yes
|
||||
|
||||
- name: Dump multiple databases
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: db_1,db_2
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Dump multiple databases
|
||||
mysql_db:
|
||||
state: dump
|
||||
name:
|
||||
- db_1
|
||||
- db_2
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Dump all databases to hostname.sql
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: all
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Dump all databases to hostname.sql including master data
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: all
|
||||
target: /tmp/dump.sql
|
||||
master_data: 1
|
||||
|
||||
# Import of sql script with encoding option
|
||||
- name: >
|
||||
Import dump.sql with specific latin1 encoding,
|
||||
similar to mysql -u <username> --default-character-set=latin1 -p <password> < dump.sql
|
||||
mysql_db:
|
||||
state: import
|
||||
name: all
|
||||
encoding: latin1
|
||||
target: /tmp/dump.sql
|
||||
|
||||
# Dump of database with encoding option
|
||||
- name: >
|
||||
Dump of Databse with specific latin1 encoding,
|
||||
similar to mysqldump -u <username> --default-character-set=latin1 -p <password> <database>
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: db_1
|
||||
encoding: latin1
|
||||
target: /tmp/dump.sql
|
||||
|
||||
- name: Delete database with name 'bobdata'
|
||||
mysql_db:
|
||||
name: bobdata
|
||||
state: absent
|
||||
|
||||
- name: Make sure there is neither a database with name 'foo', nor one with name 'bar'
|
||||
mysql_db:
|
||||
name:
|
||||
- foo
|
||||
- bar
|
||||
state: absent
|
||||
|
||||
# Dump database with argument not directly supported by this module
|
||||
# using dump_extra_args parameter
|
||||
- name: Dump databases without including triggers
|
||||
mysql_db:
|
||||
state: dump
|
||||
name: foo
|
||||
target: /tmp/dump.sql
|
||||
dump_extra_args: --skip-triggers
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
db:
|
||||
description: Database names in string format delimited by white space.
|
||||
returned: always
|
||||
type: str
|
||||
sample: "foo bar"
|
||||
db_list:
|
||||
description: List of database names.
|
||||
returned: always
|
||||
type: list
|
||||
sample: ["foo", "bar"]
|
||||
version_added: '2.9'
|
||||
executed_commands:
|
||||
description: List of commands which tried to run.
|
||||
returned: if executed
|
||||
type: list
|
||||
sample: ["CREATE DATABASE acme"]
|
||||
version_added: '2.10'
|
||||
'''
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.database import mysql_quote_identifier
|
||||
from ansible.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
executed_commands = []
|
||||
|
||||
# ===========================================
|
||||
# MySQL module specific support methods.
|
||||
#
|
||||
|
||||
|
||||
def db_exists(cursor, db):
|
||||
res = 0
|
||||
for each_db in db:
|
||||
res += cursor.execute("SHOW DATABASES LIKE %s", (each_db.replace("_", r"\_"),))
|
||||
return res == len(db)
|
||||
|
||||
|
||||
def db_delete(cursor, db):
|
||||
if not db:
|
||||
return False
|
||||
for each_db in db:
|
||||
query = "DROP DATABASE %s" % mysql_quote_identifier(each_db, 'database')
|
||||
executed_commands.append(query)
|
||||
cursor.execute(query)
|
||||
return True
|
||||
|
||||
|
||||
def db_dump(module, host, user, password, db_name, target, all_databases, port,
|
||||
config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None,
|
||||
single_transaction=None, quick=None, ignore_tables=None, hex_blob=None,
|
||||
encoding=None, force=False, master_data=0, skip_lock_tables=False, dump_extra_args=None):
|
||||
cmd = module.get_bin_path('mysqldump', True)
|
||||
# If defined, mysqldump demands --defaults-extra-file be the first option
|
||||
if config_file:
|
||||
cmd += " --defaults-extra-file=%s" % shlex_quote(config_file)
|
||||
if user is not None:
|
||||
cmd += " --user=%s" % shlex_quote(user)
|
||||
if password is not None:
|
||||
cmd += " --password=%s" % shlex_quote(password)
|
||||
if ssl_cert is not None:
|
||||
cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert)
|
||||
if ssl_key is not None:
|
||||
cmd += " --ssl-key=%s" % shlex_quote(ssl_key)
|
||||
if ssl_ca is not None:
|
||||
cmd += " --ssl-ca=%s" % shlex_quote(ssl_ca)
|
||||
if force:
|
||||
cmd += " --force"
|
||||
if socket is not None:
|
||||
cmd += " --socket=%s" % shlex_quote(socket)
|
||||
else:
|
||||
cmd += " --host=%s --port=%i" % (shlex_quote(host), port)
|
||||
|
||||
if all_databases:
|
||||
cmd += " --all-databases"
|
||||
elif len(db_name) > 1:
|
||||
cmd += " --databases {0}".format(' '.join(db_name))
|
||||
else:
|
||||
cmd += " %s" % shlex_quote(' '.join(db_name))
|
||||
|
||||
if skip_lock_tables:
|
||||
cmd += " --skip-lock-tables"
|
||||
if (encoding is not None) and (encoding != ""):
|
||||
cmd += " --default-character-set=%s" % shlex_quote(encoding)
|
||||
if single_transaction:
|
||||
cmd += " --single-transaction=true"
|
||||
if quick:
|
||||
cmd += " --quick"
|
||||
if ignore_tables:
|
||||
for an_ignored_table in ignore_tables:
|
||||
cmd += " --ignore-table={0}".format(an_ignored_table)
|
||||
if hex_blob:
|
||||
cmd += " --hex-blob"
|
||||
if master_data:
|
||||
cmd += " --master-data=%s" % master_data
|
||||
if dump_extra_args is not None:
|
||||
cmd += " " + dump_extra_args
|
||||
|
||||
path = None
|
||||
if os.path.splitext(target)[-1] == '.gz':
|
||||
path = module.get_bin_path('gzip', True)
|
||||
elif os.path.splitext(target)[-1] == '.bz2':
|
||||
path = module.get_bin_path('bzip2', True)
|
||||
elif os.path.splitext(target)[-1] == '.xz':
|
||||
path = module.get_bin_path('xz', True)
|
||||
|
||||
if path:
|
||||
cmd = '%s | %s > %s' % (cmd, path, shlex_quote(target))
|
||||
else:
|
||||
cmd += " > %s" % shlex_quote(target)
|
||||
|
||||
executed_commands.append(cmd)
|
||||
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
|
||||
return rc, stdout, stderr
|
||||
|
||||
|
||||
def db_import(module, host, user, password, db_name, target, all_databases, port, config_file,
|
||||
socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, encoding=None, force=False):
|
||||
if not os.path.exists(target):
|
||||
return module.fail_json(msg="target %s does not exist on the host" % target)
|
||||
|
||||
cmd = [module.get_bin_path('mysql', True)]
|
||||
# --defaults-file must go first, or errors out
|
||||
if config_file:
|
||||
cmd.append("--defaults-extra-file=%s" % shlex_quote(config_file))
|
||||
if user:
|
||||
cmd.append("--user=%s" % shlex_quote(user))
|
||||
if password:
|
||||
cmd.append("--password=%s" % shlex_quote(password))
|
||||
if ssl_cert is not None:
|
||||
cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert))
|
||||
if ssl_key is not None:
|
||||
cmd.append("--ssl-key=%s" % shlex_quote(ssl_key))
|
||||
if ssl_ca is not None:
|
||||
cmd.append("--ssl-ca=%s" % shlex_quote(ssl_ca))
|
||||
if force:
|
||||
cmd.append("-f")
|
||||
if socket is not None:
|
||||
cmd.append("--socket=%s" % shlex_quote(socket))
|
||||
else:
|
||||
cmd.append("--host=%s" % shlex_quote(host))
|
||||
cmd.append("--port=%i" % port)
|
||||
if (encoding is not None) and (encoding != ""):
|
||||
cmd.append("--default-character-set=%s" % shlex_quote(encoding))
|
||||
if not all_databases:
|
||||
cmd.append("--one-database")
|
||||
cmd.append(shlex_quote(''.join(db_name)))
|
||||
|
||||
comp_prog_path = None
|
||||
if os.path.splitext(target)[-1] == '.gz':
|
||||
comp_prog_path = module.get_bin_path('gzip', required=True)
|
||||
elif os.path.splitext(target)[-1] == '.bz2':
|
||||
comp_prog_path = module.get_bin_path('bzip2', required=True)
|
||||
elif os.path.splitext(target)[-1] == '.xz':
|
||||
comp_prog_path = module.get_bin_path('xz', required=True)
|
||||
if comp_prog_path:
|
||||
# The line above is for returned data only:
|
||||
executed_commands.append('%s -dc %s | %s' % (comp_prog_path, target, ' '.join(cmd)))
|
||||
p1 = subprocess.Popen([comp_prog_path, '-dc', target], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
p2 = subprocess.Popen(cmd, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout2, stderr2) = p2.communicate()
|
||||
p1.stdout.close()
|
||||
p1.wait()
|
||||
if p1.returncode != 0:
|
||||
stderr1 = p1.stderr.read()
|
||||
return p1.returncode, '', stderr1
|
||||
else:
|
||||
return p2.returncode, stdout2, stderr2
|
||||
else:
|
||||
cmd = ' '.join(cmd)
|
||||
cmd += " < %s" % shlex_quote(target)
|
||||
executed_commands.append(cmd)
|
||||
rc, stdout, stderr = module.run_command(cmd, use_unsafe_shell=True)
|
||||
return rc, stdout, stderr
|
||||
|
||||
|
||||
def db_create(cursor, db, encoding, collation):
|
||||
if not db:
|
||||
return False
|
||||
query_params = dict(enc=encoding, collate=collation)
|
||||
res = 0
|
||||
for each_db in db:
|
||||
query = ['CREATE DATABASE %s' % mysql_quote_identifier(each_db, 'database')]
|
||||
if encoding:
|
||||
query.append("CHARACTER SET %(enc)s")
|
||||
if collation:
|
||||
query.append("COLLATE %(collate)s")
|
||||
query = ' '.join(query)
|
||||
res += cursor.execute(query, query_params)
|
||||
try:
|
||||
executed_commands.append(cursor.mogrify(query, query_params))
|
||||
except AttributeError:
|
||||
executed_commands.append(cursor._executed)
|
||||
except Exception:
|
||||
executed_commands.append(query)
|
||||
return res > 0
|
||||
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
login_user=dict(type='str'),
|
||||
login_password=dict(type='str', no_log=True),
|
||||
login_host=dict(type='str', default='localhost'),
|
||||
login_port=dict(type='int', default=3306),
|
||||
login_unix_socket=dict(type='str'),
|
||||
name=dict(type='list', required=True, aliases=['db']),
|
||||
encoding=dict(type='str', default=''),
|
||||
collation=dict(type='str', default=''),
|
||||
target=dict(type='path'),
|
||||
state=dict(type='str', default='present', choices=['absent', 'dump', 'import', 'present']),
|
||||
client_cert=dict(type='path', aliases=['ssl_cert']),
|
||||
client_key=dict(type='path', aliases=['ssl_key']),
|
||||
ca_cert=dict(type='path', aliases=['ssl_ca']),
|
||||
connect_timeout=dict(type='int', default=30),
|
||||
config_file=dict(type='path', default='~/.my.cnf'),
|
||||
single_transaction=dict(type='bool', default=False),
|
||||
quick=dict(type='bool', default=True),
|
||||
ignore_tables=dict(type='list', default=[]),
|
||||
hex_blob=dict(default=False, type='bool'),
|
||||
force=dict(type='bool', default=False),
|
||||
master_data=dict(type='int', default=0, choices=[0, 1, 2]),
|
||||
skip_lock_tables=dict(type='bool', default=False),
|
||||
dump_extra_args=dict(type='str'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if mysql_driver is None:
|
||||
module.fail_json(msg=mysql_driver_fail_msg)
|
||||
|
||||
db = module.params["name"]
|
||||
if not db:
|
||||
module.exit_json(changed=False, db=db, db_list=[])
|
||||
db = [each_db.strip() for each_db in db]
|
||||
|
||||
encoding = module.params["encoding"]
|
||||
collation = module.params["collation"]
|
||||
state = module.params["state"]
|
||||
target = module.params["target"]
|
||||
socket = module.params["login_unix_socket"]
|
||||
login_port = module.params["login_port"]
|
||||
if login_port < 0 or login_port > 65535:
|
||||
module.fail_json(msg="login_port must be a valid unix port number (0-65535)")
|
||||
ssl_cert = module.params["client_cert"]
|
||||
ssl_key = module.params["client_key"]
|
||||
ssl_ca = module.params["ca_cert"]
|
||||
connect_timeout = module.params['connect_timeout']
|
||||
config_file = module.params['config_file']
|
||||
login_password = module.params["login_password"]
|
||||
login_user = module.params["login_user"]
|
||||
login_host = module.params["login_host"]
|
||||
ignore_tables = module.params["ignore_tables"]
|
||||
for a_table in ignore_tables:
|
||||
if a_table == "":
|
||||
module.fail_json(msg="Name of ignored table cannot be empty")
|
||||
single_transaction = module.params["single_transaction"]
|
||||
quick = module.params["quick"]
|
||||
hex_blob = module.params["hex_blob"]
|
||||
force = module.params["force"]
|
||||
master_data = module.params["master_data"]
|
||||
skip_lock_tables = module.params["skip_lock_tables"]
|
||||
dump_extra_args = module.params["dump_extra_args"]
|
||||
|
||||
if len(db) > 1 and state == 'import':
|
||||
module.fail_json(msg="Multiple databases are not supported with state=import")
|
||||
db_name = ' '.join(db)
|
||||
|
||||
all_databases = False
|
||||
if state in ['dump', 'import']:
|
||||
if target is None:
|
||||
module.fail_json(msg="with state=%s target is required" % state)
|
||||
if db == ['all']:
|
||||
all_databases = True
|
||||
else:
|
||||
if db == ['all']:
|
||||
module.fail_json(msg="name is not allowed to equal 'all' unless state equals import, or dump.")
|
||||
try:
|
||||
cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca,
|
||||
connect_timeout=connect_timeout)
|
||||
except Exception as e:
|
||||
if os.path.exists(config_file):
|
||||
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. "
|
||||
"Exception message: %s" % (config_file, to_native(e)))
|
||||
else:
|
||||
module.fail_json(msg="unable to find %s. Exception message: %s" % (config_file, to_native(e)))
|
||||
|
||||
changed = False
|
||||
if not os.path.exists(config_file):
|
||||
config_file = None
|
||||
|
||||
existence_list = []
|
||||
non_existence_list = []
|
||||
|
||||
if not all_databases:
|
||||
for each_database in db:
|
||||
if db_exists(cursor, [each_database]):
|
||||
existence_list.append(each_database)
|
||||
else:
|
||||
non_existence_list.append(each_database)
|
||||
|
||||
if state == "absent":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=bool(existence_list), db=db_name, db_list=db)
|
||||
try:
|
||||
changed = db_delete(cursor, existence_list)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error deleting database: %s" % to_native(e))
|
||||
module.exit_json(changed=changed, db=db_name, db_list=db, executed_commands=executed_commands)
|
||||
elif state == "present":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=bool(non_existence_list), db=db_name, db_list=db)
|
||||
changed = False
|
||||
if non_existence_list:
|
||||
try:
|
||||
changed = db_create(cursor, non_existence_list, encoding, collation)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error creating database: %s" % to_native(e),
|
||||
exception=traceback.format_exc())
|
||||
module.exit_json(changed=changed, db=db_name, db_list=db, executed_commands=executed_commands)
|
||||
elif state == "dump":
|
||||
if non_existence_list and not all_databases:
|
||||
module.fail_json(msg="Cannot dump database(s) %r - not found" % (', '.join(non_existence_list)))
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, db=db_name, db_list=db)
|
||||
rc, stdout, stderr = db_dump(module, login_host, login_user,
|
||||
login_password, db, target, all_databases,
|
||||
login_port, config_file, socket, ssl_cert, ssl_key,
|
||||
ssl_ca, single_transaction, quick, ignore_tables,
|
||||
hex_blob, encoding, force, master_data, skip_lock_tables,
|
||||
dump_extra_args)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout,
|
||||
executed_commands=executed_commands)
|
||||
elif state == "import":
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, db=db_name, db_list=db)
|
||||
if non_existence_list and not all_databases:
|
||||
try:
|
||||
db_create(cursor, non_existence_list, encoding, collation)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="error creating database: %s" % to_native(e),
|
||||
exception=traceback.format_exc())
|
||||
rc, stdout, stderr = db_import(module, login_host, login_user,
|
||||
login_password, db, target,
|
||||
all_databases,
|
||||
login_port, config_file,
|
||||
socket, ssl_cert, ssl_key, ssl_ca, encoding, force)
|
||||
if rc != 0:
|
||||
module.fail_json(msg="%s" % stderr)
|
||||
module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout,
|
||||
executed_commands=executed_commands)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,815 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012, Mark Theunissen <mark.theunissen@gmail.com>
|
||||
# Sponsored by Four Kitchens http://fourkitchens.com.
|
||||
# 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
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: mysql_user
|
||||
short_description: Adds or removes a user from a MySQL database
|
||||
description:
|
||||
- Adds or removes a user from a MySQL database.
|
||||
version_added: "0.6"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the user (role) to add or remove.
|
||||
type: str
|
||||
required: true
|
||||
password:
|
||||
description:
|
||||
- Set the user's password..
|
||||
type: str
|
||||
encrypted:
|
||||
description:
|
||||
- Indicate that the 'password' field is a `mysql_native_password` hash.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.0"
|
||||
host:
|
||||
description:
|
||||
- The 'host' part of the MySQL username.
|
||||
type: str
|
||||
default: localhost
|
||||
host_all:
|
||||
description:
|
||||
- Override the host option, making ansible apply changes to all hostnames for a given user.
|
||||
- This option cannot be used when creating users.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.1"
|
||||
priv:
|
||||
description:
|
||||
- "MySQL privileges string in the format: C(db.table:priv1,priv2)."
|
||||
- "Multiple privileges can be specified by separating each one using
|
||||
a forward slash: C(db.table:priv/db.table:priv)."
|
||||
- The format is based on MySQL C(GRANT) statement.
|
||||
- Database and table names can be quoted, MySQL-style.
|
||||
- If column privileges are used, the C(priv1,priv2) part must be
|
||||
exactly as returned by a C(SHOW GRANT) statement. If not followed,
|
||||
the module will always report changes. It includes grouping columns
|
||||
by permission (C(SELECT(col1,col2)) instead of C(SELECT(col1),SELECT(col2))).
|
||||
- Can be passed as a dictionary (see the examples).
|
||||
type: raw
|
||||
append_privs:
|
||||
description:
|
||||
- Append the privileges defined by priv to the existing ones for this
|
||||
user instead of overwriting existing ones.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.4"
|
||||
sql_log_bin:
|
||||
description:
|
||||
- Whether binary logging should be enabled or disabled for the connection.
|
||||
type: bool
|
||||
default: yes
|
||||
version_added: "2.1"
|
||||
state:
|
||||
description:
|
||||
- Whether the user should exist.
|
||||
- When C(absent), removes the user.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
check_implicit_admin:
|
||||
description:
|
||||
- Check if mysql allows login as root/nopassword before trying supplied credentials.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.3"
|
||||
update_password:
|
||||
description:
|
||||
- C(always) will update passwords if they differ.
|
||||
- C(on_create) will only set the password for newly created users.
|
||||
type: str
|
||||
choices: [ always, on_create ]
|
||||
default: always
|
||||
version_added: "2.0"
|
||||
plugin:
|
||||
description:
|
||||
- User's plugin to authenticate (``CREATE USER user IDENTIFIED WITH plugin``).
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
plugin_hash_string:
|
||||
description:
|
||||
- User's plugin hash string (``CREATE USER user IDENTIFIED WITH plugin AS plugin_hash_string``).
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
plugin_auth_string:
|
||||
description:
|
||||
- User's plugin auth_string (``CREATE USER user IDENTIFIED WITH plugin BY plugin_auth_string``).
|
||||
type: str
|
||||
version_added: '2.10'
|
||||
|
||||
notes:
|
||||
- "MySQL server installs with default login_user of 'root' and no password. To secure this user
|
||||
as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password,
|
||||
without providing any login_user/login_password details. The second must drop a ~/.my.cnf file containing
|
||||
the new root credentials. Subsequent runs of the playbook will then succeed by reading the new credentials from
|
||||
the file."
|
||||
- Currently, there is only support for the `mysql_native_password` encrypted password hash module.
|
||||
|
||||
seealso:
|
||||
- module: mysql_info
|
||||
- name: MySQL access control and account management reference
|
||||
description: Complete reference of the MySQL access control and account management documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/access-control.html
|
||||
- name: MySQL provided privileges reference
|
||||
description: Complete reference of the MySQL provided privileges documentation.
|
||||
link: https://dev.mysql.com/doc/refman/8.0/en/privileges-provided.html
|
||||
|
||||
author:
|
||||
- Jonathan Mainguy (@Jmainguy)
|
||||
- Benjamin Malynovytch (@bmalynovytch)
|
||||
- Lukasz Tomaszkiewicz (@tomaszkiewicz)
|
||||
extends_documentation_fragment: mysql
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Removes anonymous user account for localhost
|
||||
mysql_user:
|
||||
name: ''
|
||||
host: localhost
|
||||
state: absent
|
||||
|
||||
- name: Removes all anonymous user accounts
|
||||
mysql_user:
|
||||
name: ''
|
||||
host_all: yes
|
||||
state: absent
|
||||
|
||||
- name: Create database user with name 'bob' and password '12345' with all database privileges
|
||||
mysql_user:
|
||||
name: bob
|
||||
password: 12345
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
- name: Create database user using hashed password with all database privileges
|
||||
mysql_user:
|
||||
name: bob
|
||||
password: '*EE0D72C1085C46C5278932678FBE2C6A782821B4'
|
||||
encrypted: yes
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
- name: Create database user with password and all database privileges and 'WITH GRANT OPTION'
|
||||
mysql_user:
|
||||
name: bob
|
||||
password: 12345
|
||||
priv: '*.*:ALL,GRANT'
|
||||
state: present
|
||||
|
||||
- name: Create user with password, all database privileges and 'WITH GRANT OPTION' in db1 and db2
|
||||
mysql_user:
|
||||
state: present
|
||||
name: bob
|
||||
password: 12345dd
|
||||
priv:
|
||||
'db1.*': 'ALL,GRANT'
|
||||
'db2.*': 'ALL,GRANT'
|
||||
|
||||
# Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
|
||||
- name: Modify user to require SSL connections.
|
||||
mysql_user:
|
||||
name: bob
|
||||
append_privs: yes
|
||||
priv: '*.*:REQUIRESSL'
|
||||
state: present
|
||||
|
||||
- name: Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
|
||||
mysql_user:
|
||||
login_user: root
|
||||
login_password: 123456
|
||||
name: sally
|
||||
state: absent
|
||||
|
||||
- name: Ensure no user named 'sally' exists at all
|
||||
mysql_user:
|
||||
name: sally
|
||||
host_all: yes
|
||||
state: absent
|
||||
|
||||
- name: Specify grants composed of more than one word
|
||||
mysql_user:
|
||||
name: replication
|
||||
password: 12345
|
||||
priv: "*.*:REPLICATION CLIENT"
|
||||
state: present
|
||||
|
||||
- name: Revoke all privileges for user 'bob' and password '12345'
|
||||
mysql_user:
|
||||
name: bob
|
||||
password: 12345
|
||||
priv: "*.*:USAGE"
|
||||
state: present
|
||||
|
||||
# Example privileges string format
|
||||
# mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanotherdb.*:ALL
|
||||
|
||||
- name: Example using login_unix_socket to connect to server
|
||||
mysql_user:
|
||||
name: root
|
||||
password: abc123
|
||||
login_unix_socket: /var/run/mysqld/mysqld.sock
|
||||
|
||||
- name: Example of skipping binary logging while adding user 'bob'
|
||||
mysql_user:
|
||||
name: bob
|
||||
password: 12345
|
||||
priv: "*.*:USAGE"
|
||||
state: present
|
||||
sql_log_bin: no
|
||||
|
||||
- name: Create user 'bob' authenticated with plugin 'AWSAuthenticationPlugin'
|
||||
mysql_user:
|
||||
name: bob
|
||||
plugin: AWSAuthenticationPlugin
|
||||
plugin_hash_string: RDS
|
||||
priv: '*.*:ALL'
|
||||
state: present
|
||||
|
||||
# Example .my.cnf file for setting the root password
|
||||
# [client]
|
||||
# user=root
|
||||
# password=n<_665{vS43y
|
||||
'''
|
||||
|
||||
import re
|
||||
import string
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.database import SQLParseError
|
||||
from ansible.module_utils.mysql import mysql_connect, mysql_driver, mysql_driver_fail_msg
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
VALID_PRIVS = frozenset(('CREATE', 'DROP', 'GRANT', 'GRANT OPTION',
|
||||
'LOCK TABLES', 'REFERENCES', 'EVENT', 'ALTER',
|
||||
'DELETE', 'INDEX', 'INSERT', 'SELECT', 'UPDATE',
|
||||
'CREATE TEMPORARY TABLES', 'TRIGGER', 'CREATE VIEW',
|
||||
'SHOW VIEW', 'ALTER ROUTINE', 'CREATE ROUTINE',
|
||||
'EXECUTE', 'FILE', 'CREATE TABLESPACE', 'CREATE USER',
|
||||
'PROCESS', 'PROXY', 'RELOAD', 'REPLICATION CLIENT',
|
||||
'REPLICATION SLAVE', 'SHOW DATABASES', 'SHUTDOWN',
|
||||
'SUPER', 'ALL', 'ALL PRIVILEGES', 'USAGE', 'REQUIRESSL',
|
||||
'CREATE ROLE', 'DROP ROLE', 'APPLICATION_PASSWORD_ADMIN',
|
||||
'AUDIT_ADMIN', 'BACKUP_ADMIN', 'BINLOG_ADMIN',
|
||||
'BINLOG_ENCRYPTION_ADMIN', 'CLONE_ADMIN', 'CONNECTION_ADMIN',
|
||||
'ENCRYPTION_KEY_ADMIN', 'FIREWALL_ADMIN', 'FIREWALL_USER',
|
||||
'GROUP_REPLICATION_ADMIN', 'INNODB_REDO_LOG_ARCHIVE',
|
||||
'NDB_STORED_USER', 'PERSIST_RO_VARIABLES_ADMIN',
|
||||
'REPLICATION_APPLIER', 'REPLICATION_SLAVE_ADMIN',
|
||||
'RESOURCE_GROUP_ADMIN', 'RESOURCE_GROUP_USER',
|
||||
'ROLE_ADMIN', 'SESSION_VARIABLES_ADMIN', 'SET_USER_ID',
|
||||
'SYSTEM_USER', 'SYSTEM_VARIABLES_ADMIN', 'SYSTEM_USER',
|
||||
'TABLE_ENCRYPTION_ADMIN', 'VERSION_TOKEN_ADMIN',
|
||||
'XA_RECOVER_ADMIN', 'LOAD FROM S3', 'SELECT INTO S3'))
|
||||
|
||||
|
||||
class InvalidPrivsError(Exception):
|
||||
pass
|
||||
|
||||
# ===========================================
|
||||
# MySQL module specific support methods.
|
||||
#
|
||||
|
||||
|
||||
# User Authentication Management changed in MySQL 5.7 and MariaDB 10.2.0
|
||||
def use_old_user_mgmt(cursor):
|
||||
cursor.execute("SELECT VERSION()")
|
||||
result = cursor.fetchone()
|
||||
version_str = result[0]
|
||||
version = version_str.split('.')
|
||||
|
||||
if 'mariadb' in version_str.lower():
|
||||
# Prior to MariaDB 10.2
|
||||
if int(version[0]) * 1000 + int(version[1]) < 10002:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
# Prior to MySQL 5.7
|
||||
if int(version[0]) * 1000 + int(version[1]) < 5007:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_mode(cursor):
|
||||
cursor.execute('SELECT @@GLOBAL.sql_mode')
|
||||
result = cursor.fetchone()
|
||||
mode_str = result[0]
|
||||
if 'ANSI' in mode_str:
|
||||
mode = 'ANSI'
|
||||
else:
|
||||
mode = 'NOTANSI'
|
||||
return mode
|
||||
|
||||
|
||||
def user_exists(cursor, user, host, host_all):
|
||||
if host_all:
|
||||
cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s", ([user]))
|
||||
else:
|
||||
cursor.execute("SELECT count(*) FROM mysql.user WHERE user = %s AND host = %s", (user, host))
|
||||
|
||||
count = cursor.fetchone()
|
||||
return count[0] > 0
|
||||
|
||||
|
||||
def user_add(cursor, user, host, host_all, password, encrypted,
|
||||
plugin, plugin_hash_string, plugin_auth_string, new_priv, check_mode):
|
||||
# we cannot create users without a proper hostname
|
||||
if host_all:
|
||||
return False
|
||||
|
||||
if check_mode:
|
||||
return True
|
||||
|
||||
if password and encrypted:
|
||||
cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password))
|
||||
elif password and not encrypted:
|
||||
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password))
|
||||
elif plugin and plugin_hash_string:
|
||||
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string))
|
||||
elif plugin and plugin_auth_string:
|
||||
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string))
|
||||
elif plugin:
|
||||
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin))
|
||||
else:
|
||||
cursor.execute("CREATE USER %s@%s", (user, host))
|
||||
if new_priv is not None:
|
||||
for db_table, priv in iteritems(new_priv):
|
||||
privileges_grant(cursor, user, host, db_table, priv)
|
||||
return True
|
||||
|
||||
|
||||
def is_hash(password):
|
||||
ishash = False
|
||||
if len(password) == 41 and password[0] == '*':
|
||||
if frozenset(password[1:]).issubset(string.hexdigits):
|
||||
ishash = True
|
||||
return ishash
|
||||
|
||||
|
||||
def user_mod(cursor, user, host, host_all, password, encrypted,
|
||||
plugin, plugin_hash_string, plugin_auth_string, new_priv, append_privs, module):
|
||||
changed = False
|
||||
msg = "User unchanged"
|
||||
grant_option = False
|
||||
|
||||
if host_all:
|
||||
hostnames = user_get_hostnames(cursor, [user])
|
||||
else:
|
||||
hostnames = [host]
|
||||
|
||||
for host in hostnames:
|
||||
# Handle clear text and hashed passwords.
|
||||
if bool(password):
|
||||
# Determine what user management method server uses
|
||||
old_user_mgmt = use_old_user_mgmt(cursor)
|
||||
|
||||
# Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist
|
||||
cursor.execute("""
|
||||
SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string')
|
||||
ORDER BY COLUMN_NAME DESC LIMIT 1
|
||||
""")
|
||||
colA = cursor.fetchone()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT COLUMN_NAME FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = 'mysql' AND TABLE_NAME = 'user' AND COLUMN_NAME IN ('Password', 'authentication_string')
|
||||
ORDER BY COLUMN_NAME ASC LIMIT 1
|
||||
""")
|
||||
colB = cursor.fetchone()
|
||||
|
||||
# Select hash from either Password or authentication_string, depending which one exists and/or is filled
|
||||
cursor.execute("""
|
||||
SELECT COALESCE(
|
||||
CASE WHEN %s = '' THEN NULL ELSE %s END,
|
||||
CASE WHEN %s = '' THEN NULL ELSE %s END
|
||||
)
|
||||
FROM mysql.user WHERE user = %%s AND host = %%s
|
||||
""" % (colA[0], colA[0], colB[0], colB[0]), (user, host))
|
||||
current_pass_hash = cursor.fetchone()[0]
|
||||
if isinstance(current_pass_hash, bytes):
|
||||
current_pass_hash = current_pass_hash.decode('ascii')
|
||||
|
||||
if encrypted:
|
||||
encrypted_password = password
|
||||
if not is_hash(encrypted_password):
|
||||
module.fail_json(msg="encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password))")
|
||||
else:
|
||||
if old_user_mgmt:
|
||||
cursor.execute("SELECT PASSWORD(%s)", (password,))
|
||||
else:
|
||||
cursor.execute("SELECT CONCAT('*', UCASE(SHA1(UNHEX(SHA1(%s)))))", (password,))
|
||||
encrypted_password = cursor.fetchone()[0]
|
||||
|
||||
if current_pass_hash != encrypted_password:
|
||||
msg = "Password updated"
|
||||
if module.check_mode:
|
||||
return (True, msg)
|
||||
if old_user_mgmt:
|
||||
cursor.execute("SET PASSWORD FOR %s@%s = %s", (user, host, encrypted_password))
|
||||
msg = "Password updated (old style)"
|
||||
else:
|
||||
try:
|
||||
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH mysql_native_password AS %s", (user, host, encrypted_password))
|
||||
msg = "Password updated (new style)"
|
||||
except (mysql_driver.Error) as e:
|
||||
# https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql
|
||||
# Replacing empty root password with new authentication mechanisms fails with error 1396
|
||||
if e.args[0] == 1396:
|
||||
cursor.execute(
|
||||
"UPDATE user SET plugin = %s, authentication_string = %s, Password = '' WHERE User = %s AND Host = %s",
|
||||
('mysql_native_password', encrypted_password, user, host)
|
||||
)
|
||||
cursor.execute("FLUSH PRIVILEGES")
|
||||
msg = "Password forced update"
|
||||
else:
|
||||
raise e
|
||||
changed = True
|
||||
|
||||
# Handle plugin authentication
|
||||
if plugin:
|
||||
cursor.execute("SELECT plugin, authentication_string FROM mysql.user "
|
||||
"WHERE user = %s AND host = %s", (user, host))
|
||||
current_plugin = cursor.fetchone()
|
||||
|
||||
update = False
|
||||
|
||||
if current_plugin[0] != plugin:
|
||||
update = True
|
||||
|
||||
if plugin_hash_string and current_plugin[1] != plugin_hash_string:
|
||||
update = True
|
||||
|
||||
if plugin_auth_string and current_plugin[1] != plugin_auth_string:
|
||||
# this case can cause more updates than expected,
|
||||
# as plugin can hash auth_string in any way it wants
|
||||
# and there's no way to figure it out for
|
||||
# a check, so I prefer to update more often than never
|
||||
update = True
|
||||
|
||||
if update:
|
||||
if plugin_hash_string:
|
||||
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string))
|
||||
elif plugin_auth_string:
|
||||
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string))
|
||||
else:
|
||||
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin))
|
||||
changed = True
|
||||
|
||||
# Handle privileges
|
||||
if new_priv is not None:
|
||||
curr_priv = privileges_get(cursor, user, host)
|
||||
|
||||
# If the user has privileges on a db.table that doesn't appear at all in
|
||||
# the new specification, then revoke all privileges on it.
|
||||
for db_table, priv in iteritems(curr_priv):
|
||||
# If the user has the GRANT OPTION on a db.table, revoke it first.
|
||||
if "GRANT" in priv:
|
||||
grant_option = True
|
||||
if db_table not in new_priv:
|
||||
if user != "root" and "PROXY" not in priv and not append_privs:
|
||||
msg = "Privileges updated"
|
||||
if module.check_mode:
|
||||
return (True, msg)
|
||||
privileges_revoke(cursor, user, host, db_table, priv, grant_option)
|
||||
changed = True
|
||||
|
||||
# If the user doesn't currently have any privileges on a db.table, then
|
||||
# we can perform a straight grant operation.
|
||||
for db_table, priv in iteritems(new_priv):
|
||||
if db_table not in curr_priv:
|
||||
msg = "New privileges granted"
|
||||
if module.check_mode:
|
||||
return (True, msg)
|
||||
privileges_grant(cursor, user, host, db_table, priv)
|
||||
changed = True
|
||||
|
||||
# If the db.table specification exists in both the user's current privileges
|
||||
# and in the new privileges, then we need to see if there's a difference.
|
||||
db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
|
||||
for db_table in db_table_intersect:
|
||||
priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
|
||||
if len(priv_diff) > 0:
|
||||
msg = "Privileges updated"
|
||||
if module.check_mode:
|
||||
return (True, msg)
|
||||
if not append_privs:
|
||||
privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option)
|
||||
privileges_grant(cursor, user, host, db_table, new_priv[db_table])
|
||||
changed = True
|
||||
|
||||
return (changed, msg)
|
||||
|
||||
|
||||
def user_delete(cursor, user, host, host_all, check_mode):
|
||||
if check_mode:
|
||||
return True
|
||||
|
||||
if host_all:
|
||||
hostnames = user_get_hostnames(cursor, [user])
|
||||
|
||||
for hostname in hostnames:
|
||||
cursor.execute("DROP USER %s@%s", (user, hostname))
|
||||
else:
|
||||
cursor.execute("DROP USER %s@%s", (user, host))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def user_get_hostnames(cursor, user):
|
||||
cursor.execute("SELECT Host FROM mysql.user WHERE user = %s", user)
|
||||
hostnames_raw = cursor.fetchall()
|
||||
hostnames = []
|
||||
|
||||
for hostname_raw in hostnames_raw:
|
||||
hostnames.append(hostname_raw[0])
|
||||
|
||||
return hostnames
|
||||
|
||||
|
||||
def privileges_get(cursor, user, host):
|
||||
""" MySQL doesn't have a better method of getting privileges aside from the
|
||||
SHOW GRANTS query syntax, which requires us to then parse the returned string.
|
||||
Here's an example of the string that is returned from MySQL:
|
||||
|
||||
GRANT USAGE ON *.* TO 'user'@'localhost' IDENTIFIED BY 'pass';
|
||||
|
||||
This function makes the query and returns a dictionary containing the results.
|
||||
The dictionary format is the same as that returned by privileges_unpack() below.
|
||||
"""
|
||||
output = {}
|
||||
cursor.execute("SHOW GRANTS FOR %s@%s", (user, host))
|
||||
grants = cursor.fetchall()
|
||||
|
||||
def pick(x):
|
||||
if x == 'ALL PRIVILEGES':
|
||||
return 'ALL'
|
||||
else:
|
||||
return x
|
||||
|
||||
for grant in grants:
|
||||
res = re.match("""GRANT (.+) ON (.+) TO (['`"]).*\\3@(['`"]).*\\4( IDENTIFIED BY PASSWORD (['`"]).+\\6)? ?(.*)""", grant[0])
|
||||
if res is None:
|
||||
raise InvalidPrivsError('unable to parse the MySQL grant string: %s' % grant[0])
|
||||
privileges = res.group(1).split(", ")
|
||||
privileges = [pick(x) for x in privileges]
|
||||
if "WITH GRANT OPTION" in res.group(7):
|
||||
privileges.append('GRANT')
|
||||
if "REQUIRE SSL" in res.group(7):
|
||||
privileges.append('REQUIRESSL')
|
||||
db = res.group(2)
|
||||
output[db] = privileges
|
||||
return output
|
||||
|
||||
|
||||
def privileges_unpack(priv, mode):
|
||||
""" Take a privileges string, typically passed as a parameter, and unserialize
|
||||
it into a dictionary, the same format as privileges_get() above. We have this
|
||||
custom format to avoid using YAML/JSON strings inside YAML playbooks. Example
|
||||
of a privileges string:
|
||||
|
||||
mydb.*:INSERT,UPDATE/anotherdb.*:SELECT/yetanother.*:ALL
|
||||
|
||||
The privilege USAGE stands for no privileges, so we add that in on *.* if it's
|
||||
not specified in the string, as MySQL will always provide this by default.
|
||||
"""
|
||||
if mode == 'ANSI':
|
||||
quote = '"'
|
||||
else:
|
||||
quote = '`'
|
||||
output = {}
|
||||
privs = []
|
||||
for item in priv.strip().split('/'):
|
||||
pieces = item.strip().rsplit(':', 1)
|
||||
dbpriv = pieces[0].rsplit(".", 1)
|
||||
|
||||
# Check for FUNCTION or PROCEDURE object types
|
||||
parts = dbpriv[0].split(" ", 1)
|
||||
object_type = ''
|
||||
if len(parts) > 1 and (parts[0] == 'FUNCTION' or parts[0] == 'PROCEDURE'):
|
||||
object_type = parts[0] + ' '
|
||||
dbpriv[0] = parts[1]
|
||||
|
||||
# Do not escape if privilege is for database or table, i.e.
|
||||
# neither quote *. nor .*
|
||||
for i, side in enumerate(dbpriv):
|
||||
if side.strip('`') != '*':
|
||||
dbpriv[i] = '%s%s%s' % (quote, side.strip('`'), quote)
|
||||
pieces[0] = object_type + '.'.join(dbpriv)
|
||||
|
||||
if '(' in pieces[1]:
|
||||
output[pieces[0]] = re.split(r',\s*(?=[^)]*(?:\(|$))', pieces[1].upper())
|
||||
for i in output[pieces[0]]:
|
||||
privs.append(re.sub(r'\s*\(.*\)', '', i))
|
||||
else:
|
||||
output[pieces[0]] = pieces[1].upper().split(',')
|
||||
privs = output[pieces[0]]
|
||||
new_privs = frozenset(privs)
|
||||
if not new_privs.issubset(VALID_PRIVS):
|
||||
raise InvalidPrivsError('Invalid privileges specified: %s' % new_privs.difference(VALID_PRIVS))
|
||||
|
||||
if '*.*' not in output:
|
||||
output['*.*'] = ['USAGE']
|
||||
|
||||
# if we are only specifying something like REQUIRESSL and/or GRANT (=WITH GRANT OPTION) in *.*
|
||||
# we still need to add USAGE as a privilege to avoid syntax errors
|
||||
if 'REQUIRESSL' in priv and not set(output['*.*']).difference(set(['GRANT', 'REQUIRESSL'])):
|
||||
output['*.*'].append('USAGE')
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def privileges_revoke(cursor, user, host, db_table, priv, grant_option):
|
||||
# Escape '%' since mysql db.execute() uses a format string
|
||||
db_table = db_table.replace('%', '%%')
|
||||
if grant_option:
|
||||
query = ["REVOKE GRANT OPTION ON %s" % db_table]
|
||||
query.append("FROM %s@%s")
|
||||
query = ' '.join(query)
|
||||
cursor.execute(query, (user, host))
|
||||
priv_string = ",".join([p for p in priv if p not in ('GRANT', 'REQUIRESSL')])
|
||||
query = ["REVOKE %s ON %s" % (priv_string, db_table)]
|
||||
query.append("FROM %s@%s")
|
||||
query = ' '.join(query)
|
||||
cursor.execute(query, (user, host))
|
||||
|
||||
|
||||
def privileges_grant(cursor, user, host, db_table, priv):
|
||||
# Escape '%' since mysql db.execute uses a format string and the
|
||||
# specification of db and table often use a % (SQL wildcard)
|
||||
db_table = db_table.replace('%', '%%')
|
||||
priv_string = ",".join([p for p in priv if p not in ('GRANT', 'REQUIRESSL')])
|
||||
query = ["GRANT %s ON %s" % (priv_string, db_table)]
|
||||
query.append("TO %s@%s")
|
||||
if 'REQUIRESSL' in priv:
|
||||
query.append("REQUIRE SSL")
|
||||
if 'GRANT' in priv:
|
||||
query.append("WITH GRANT OPTION")
|
||||
query = ' '.join(query)
|
||||
cursor.execute(query, (user, host))
|
||||
|
||||
|
||||
def convert_priv_dict_to_str(priv):
|
||||
"""Converts privs dictionary to string of certain format.
|
||||
|
||||
Args:
|
||||
priv (dict): Dict of privileges that needs to be converted to string.
|
||||
|
||||
Returns:
|
||||
priv (str): String representation of input argument.
|
||||
"""
|
||||
priv_list = ['%s:%s' % (key, val) for key, val in iteritems(priv)]
|
||||
|
||||
return '/'.join(priv_list)
|
||||
|
||||
# ===========================================
|
||||
# Module execution.
|
||||
#
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
login_user=dict(type='str'),
|
||||
login_password=dict(type='str', no_log=True),
|
||||
login_host=dict(type='str', default='localhost'),
|
||||
login_port=dict(type='int', default=3306),
|
||||
login_unix_socket=dict(type='str'),
|
||||
user=dict(type='str', required=True, aliases=['name']),
|
||||
password=dict(type='str', no_log=True),
|
||||
encrypted=dict(type='bool', default=False),
|
||||
host=dict(type='str', default='localhost'),
|
||||
host_all=dict(type="bool", default=False),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
priv=dict(type='raw'),
|
||||
append_privs=dict(type='bool', default=False),
|
||||
check_implicit_admin=dict(type='bool', default=False),
|
||||
update_password=dict(type='str', default='always', choices=['always', 'on_create']),
|
||||
connect_timeout=dict(type='int', default=30),
|
||||
config_file=dict(type='path', default='~/.my.cnf'),
|
||||
sql_log_bin=dict(type='bool', default=True),
|
||||
client_cert=dict(type='path', aliases=['ssl_cert']),
|
||||
client_key=dict(type='path', aliases=['ssl_key']),
|
||||
ca_cert=dict(type='path', aliases=['ssl_ca']),
|
||||
plugin=dict(default=None, type='str'),
|
||||
plugin_hash_string=dict(default=None, type='str'),
|
||||
plugin_auth_string=dict(default=None, type='str'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
login_user = module.params["login_user"]
|
||||
login_password = module.params["login_password"]
|
||||
user = module.params["user"]
|
||||
password = module.params["password"]
|
||||
encrypted = module.boolean(module.params["encrypted"])
|
||||
host = module.params["host"].lower()
|
||||
host_all = module.params["host_all"]
|
||||
state = module.params["state"]
|
||||
priv = module.params["priv"]
|
||||
check_implicit_admin = module.params['check_implicit_admin']
|
||||
connect_timeout = module.params['connect_timeout']
|
||||
config_file = module.params['config_file']
|
||||
append_privs = module.boolean(module.params["append_privs"])
|
||||
update_password = module.params['update_password']
|
||||
ssl_cert = module.params["client_cert"]
|
||||
ssl_key = module.params["client_key"]
|
||||
ssl_ca = module.params["ca_cert"]
|
||||
db = ''
|
||||
sql_log_bin = module.params["sql_log_bin"]
|
||||
plugin = module.params["plugin"]
|
||||
plugin_hash_string = module.params["plugin_hash_string"]
|
||||
plugin_auth_string = module.params["plugin_auth_string"]
|
||||
if priv and not (isinstance(priv, str) or isinstance(priv, dict)):
|
||||
module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv))
|
||||
|
||||
if priv and isinstance(priv, dict):
|
||||
priv = convert_priv_dict_to_str(priv)
|
||||
|
||||
if mysql_driver is None:
|
||||
module.fail_json(msg=mysql_driver_fail_msg)
|
||||
|
||||
cursor = None
|
||||
try:
|
||||
if check_implicit_admin:
|
||||
try:
|
||||
cursor, db_conn = mysql_connect(module, 'root', '', config_file, ssl_cert, ssl_key, ssl_ca, db,
|
||||
connect_timeout=connect_timeout)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not cursor:
|
||||
cursor, db_conn = mysql_connect(module, login_user, login_password, config_file, ssl_cert, ssl_key, ssl_ca, db,
|
||||
connect_timeout=connect_timeout)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="unable to connect to database, check login_user and login_password are correct or %s has the credentials. "
|
||||
"Exception message: %s" % (config_file, to_native(e)))
|
||||
|
||||
if not sql_log_bin:
|
||||
cursor.execute("SET SQL_LOG_BIN=0;")
|
||||
|
||||
if priv is not None:
|
||||
try:
|
||||
mode = get_mode(cursor)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
try:
|
||||
priv = privileges_unpack(priv, mode)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="invalid privileges string: %s" % to_native(e))
|
||||
|
||||
if state == "present":
|
||||
if user_exists(cursor, user, host, host_all):
|
||||
try:
|
||||
if update_password == 'always':
|
||||
changed, msg = user_mod(cursor, user, host, host_all, password, encrypted,
|
||||
plugin, plugin_hash_string, plugin_auth_string,
|
||||
priv, append_privs, module)
|
||||
else:
|
||||
changed, msg = user_mod(cursor, user, host, host_all, None, encrypted,
|
||||
plugin, plugin_hash_string, plugin_auth_string,
|
||||
priv, append_privs, module)
|
||||
|
||||
except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
else:
|
||||
if host_all:
|
||||
module.fail_json(msg="host_all parameter cannot be used when adding a user")
|
||||
try:
|
||||
changed = user_add(cursor, user, host, host_all, password, encrypted,
|
||||
plugin, plugin_hash_string, plugin_auth_string,
|
||||
priv, module.check_mode)
|
||||
if changed:
|
||||
msg = "User added"
|
||||
|
||||
except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
elif state == "absent":
|
||||
if user_exists(cursor, user, host, host_all):
|
||||
changed = user_delete(cursor, user, host, host_all, module.check_mode)
|
||||
msg = "User deleted"
|
||||
else:
|
||||
changed = False
|
||||
msg = "User doesn't exist"
|
||||
module.exit_json(changed=changed, user=user, msg=msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
File diff suppressed because it is too large
Load diff
|
@ -1,472 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2017, Alen Komic
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: zabbix_proxy
|
||||
short_description: Create/delete/get/update Zabbix proxies
|
||||
description:
|
||||
- This module allows you to create, modify, get and delete Zabbix proxy entries.
|
||||
version_added: "2.5"
|
||||
author:
|
||||
- "Alen Komic (@akomic)"
|
||||
requirements:
|
||||
- "python >= 2.6"
|
||||
- "zabbix-api >= 0.5.4"
|
||||
options:
|
||||
proxy_name:
|
||||
description:
|
||||
- Name of the proxy in Zabbix.
|
||||
required: true
|
||||
type: str
|
||||
proxy_address:
|
||||
description:
|
||||
- Comma-delimited list of IP/CIDR addresses or DNS names to accept active proxy requests from.
|
||||
- Requires I(status=active).
|
||||
- Works only with >= Zabbix 4.0. ( remove option for <= 4.0 )
|
||||
required: false
|
||||
version_added: '2.10'
|
||||
type: str
|
||||
description:
|
||||
description:
|
||||
- Description of the proxy.
|
||||
required: false
|
||||
type: str
|
||||
status:
|
||||
description:
|
||||
- Type of proxy. (4 - active, 5 - passive)
|
||||
required: false
|
||||
choices: ['active', 'passive']
|
||||
default: "active"
|
||||
type: str
|
||||
tls_connect:
|
||||
description:
|
||||
- Connections to proxy.
|
||||
required: false
|
||||
choices: ['no_encryption','PSK','certificate']
|
||||
default: 'no_encryption'
|
||||
type: str
|
||||
tls_accept:
|
||||
description:
|
||||
- Connections from proxy.
|
||||
required: false
|
||||
choices: ['no_encryption','PSK','certificate']
|
||||
default: 'no_encryption'
|
||||
type: str
|
||||
ca_cert:
|
||||
description:
|
||||
- Certificate issuer.
|
||||
required: false
|
||||
aliases: [ tls_issuer ]
|
||||
type: str
|
||||
tls_subject:
|
||||
description:
|
||||
- Certificate subject.
|
||||
required: false
|
||||
type: str
|
||||
tls_psk_identity:
|
||||
description:
|
||||
- PSK identity. Required if either I(tls_connect) or I(tls_accept) has PSK enabled.
|
||||
required: false
|
||||
type: str
|
||||
tls_psk:
|
||||
description:
|
||||
- The preshared key, at least 32 hex digits. Required if either I(tls_connect) or I(tls_accept) has PSK enabled.
|
||||
required: false
|
||||
type: str
|
||||
state:
|
||||
description:
|
||||
- State of the proxy.
|
||||
- On C(present), it will create if proxy does not exist or update the proxy if the associated data is different.
|
||||
- On C(absent) will remove a proxy if it exists.
|
||||
required: false
|
||||
choices: ['present', 'absent']
|
||||
default: "present"
|
||||
type: str
|
||||
interface:
|
||||
description:
|
||||
- Dictionary with params for the interface when proxy is in passive mode.
|
||||
- For more information, review proxy interface documentation at
|
||||
- U(https://www.zabbix.com/documentation/4.0/manual/api/reference/proxy/object#proxy_interface).
|
||||
required: false
|
||||
suboptions:
|
||||
useip:
|
||||
type: int
|
||||
description:
|
||||
- Connect to proxy interface with IP address instead of DNS name.
|
||||
- 0 (don't use ip), 1 (use ip).
|
||||
default: 0
|
||||
choices: [0, 1]
|
||||
ip:
|
||||
type: str
|
||||
description:
|
||||
- IP address used by proxy interface.
|
||||
- Required if I(useip=1).
|
||||
default: ''
|
||||
dns:
|
||||
type: str
|
||||
description:
|
||||
- DNS name of the proxy interface.
|
||||
- Required if I(useip=0).
|
||||
default: ''
|
||||
port:
|
||||
type: str
|
||||
description:
|
||||
- Port used by proxy interface.
|
||||
default: '10051'
|
||||
type:
|
||||
type: int
|
||||
description:
|
||||
- Interface type to add.
|
||||
- This suboption is currently ignored for Zabbix proxy.
|
||||
- This suboption is deprecated since Ansible 2.10 and will eventually be removed in 2.14.
|
||||
required: false
|
||||
default: 0
|
||||
main:
|
||||
type: int
|
||||
description:
|
||||
- Whether the interface is used as default.
|
||||
- This suboption is currently ignored for Zabbix proxy.
|
||||
- This suboption is deprecated since Ansible 2.10 and will eventually be removed in 2.14.
|
||||
required: false
|
||||
default: 0
|
||||
default: {}
|
||||
type: dict
|
||||
|
||||
extends_documentation_fragment:
|
||||
- zabbix
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Create or update a proxy with proxy type active
|
||||
local_action:
|
||||
module: zabbix_proxy
|
||||
server_url: http://monitor.example.com
|
||||
login_user: username
|
||||
login_password: password
|
||||
proxy_name: ExampleProxy
|
||||
description: ExampleProxy
|
||||
status: active
|
||||
state: present
|
||||
proxy_address: ExampleProxy.local
|
||||
|
||||
- name: Create a new passive proxy using only it's IP
|
||||
local_action:
|
||||
module: zabbix_proxy
|
||||
server_url: http://monitor.example.com
|
||||
login_user: username
|
||||
login_password: password
|
||||
proxy_name: ExampleProxy
|
||||
description: ExampleProxy
|
||||
status: passive
|
||||
state: present
|
||||
interface:
|
||||
useip: 1
|
||||
ip: 10.1.1.2
|
||||
port: 10051
|
||||
|
||||
- name: Create a new passive proxy using only it's DNS
|
||||
local_action:
|
||||
module: zabbix_proxy
|
||||
server_url: http://monitor.example.com
|
||||
login_user: username
|
||||
login_password: password
|
||||
proxy_name: ExampleProxy
|
||||
description: ExampleProxy
|
||||
status: passive
|
||||
state: present
|
||||
interface:
|
||||
dns: proxy.example.com
|
||||
port: 10051
|
||||
'''
|
||||
|
||||
RETURN = r''' # '''
|
||||
|
||||
|
||||
import traceback
|
||||
import atexit
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
try:
|
||||
from zabbix_api import ZabbixAPI
|
||||
|
||||
HAS_ZABBIX_API = True
|
||||
except ImportError:
|
||||
ZBX_IMP_ERR = traceback.format_exc()
|
||||
HAS_ZABBIX_API = False
|
||||
|
||||
|
||||
class Proxy(object):
|
||||
def __init__(self, module, zbx):
|
||||
self._module = module
|
||||
self._zapi = zbx
|
||||
self.existing_data = None
|
||||
|
||||
def proxy_exists(self, proxy_name):
|
||||
result = self._zapi.proxy.get({
|
||||
'output': 'extend', 'selectInterface': 'extend',
|
||||
'filter': {'host': proxy_name}})
|
||||
|
||||
if len(result) > 0 and 'proxyid' in result[0]:
|
||||
self.existing_data = result[0]
|
||||
return result[0]['proxyid']
|
||||
else:
|
||||
return result
|
||||
|
||||
def add_proxy(self, data):
|
||||
try:
|
||||
if self._module.check_mode:
|
||||
self._module.exit_json(changed=True)
|
||||
|
||||
parameters = {}
|
||||
for item in data:
|
||||
if data[item]:
|
||||
parameters[item] = data[item]
|
||||
|
||||
if 'proxy_address' in data and data['status'] != '5':
|
||||
parameters.pop('proxy_address', False)
|
||||
|
||||
if 'interface' in data and data['status'] != '6':
|
||||
parameters.pop('interface', False)
|
||||
|
||||
proxy_ids_list = self._zapi.proxy.create(parameters)
|
||||
self._module.exit_json(changed=True,
|
||||
result="Successfully added proxy %s (%s)" %
|
||||
(data['host'], data['status']))
|
||||
if len(proxy_ids_list) >= 1:
|
||||
return proxy_ids_list['proxyids'][0]
|
||||
except Exception as e:
|
||||
self._module.fail_json(msg="Failed to create proxy %s: %s" %
|
||||
(data['host'], e))
|
||||
|
||||
def delete_proxy(self, proxy_id, proxy_name):
|
||||
try:
|
||||
if self._module.check_mode:
|
||||
self._module.exit_json(changed=True)
|
||||
self._zapi.proxy.delete([proxy_id])
|
||||
self._module.exit_json(changed=True,
|
||||
result="Successfully deleted"
|
||||
+ " proxy %s" % proxy_name)
|
||||
except Exception as e:
|
||||
self._module.fail_json(msg="Failed to delete proxy %s: %s" %
|
||||
(proxy_name, str(e)))
|
||||
|
||||
def compile_interface_params(self, new_interface):
|
||||
old_interface = {}
|
||||
if 'interface' in self.existing_data and \
|
||||
len(self.existing_data['interface']) > 0:
|
||||
old_interface = self.existing_data['interface']
|
||||
|
||||
for item in ['type', 'main']:
|
||||
new_interface.pop(item, False)
|
||||
|
||||
final_interface = old_interface.copy()
|
||||
final_interface.update(new_interface)
|
||||
final_interface = dict((k, str(v)) for k, v in final_interface.items())
|
||||
|
||||
if final_interface != old_interface:
|
||||
return final_interface
|
||||
else:
|
||||
return {}
|
||||
|
||||
def update_proxy(self, proxy_id, data):
|
||||
try:
|
||||
if self._module.check_mode:
|
||||
self._module.exit_json(changed=True)
|
||||
parameters = {'proxyid': proxy_id}
|
||||
|
||||
for item in data:
|
||||
if data[item] and item in self.existing_data and \
|
||||
self.existing_data[item] != data[item]:
|
||||
parameters[item] = data[item]
|
||||
|
||||
if 'interface' in parameters:
|
||||
parameters.pop('interface')
|
||||
|
||||
if 'proxy_address' in data and data['status'] != '5':
|
||||
parameters.pop('proxy_address', False)
|
||||
|
||||
if 'interface' in data and data['status'] != '6':
|
||||
parameters.pop('interface', False)
|
||||
|
||||
if 'interface' in data and data['status'] == '6':
|
||||
new_interface = self.compile_interface_params(data['interface'])
|
||||
if len(new_interface) > 0:
|
||||
parameters['interface'] = new_interface
|
||||
|
||||
if len(parameters) > 1:
|
||||
self._zapi.proxy.update(parameters)
|
||||
self._module.exit_json(
|
||||
changed=True,
|
||||
result="Successfully updated proxy %s (%s)" %
|
||||
(data['host'], proxy_id)
|
||||
)
|
||||
else:
|
||||
self._module.exit_json(changed=False)
|
||||
except Exception as e:
|
||||
self._module.fail_json(msg="Failed to update proxy %s: %s" %
|
||||
(data['host'], e))
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
server_url=dict(type='str', required=True, aliases=['url']),
|
||||
login_user=dict(type='str', required=True),
|
||||
login_password=dict(type='str', required=True, no_log=True),
|
||||
proxy_name=dict(type='str', required=True),
|
||||
proxy_address=dict(type='str', required=False),
|
||||
http_login_user=dict(type='str', required=False, default=None),
|
||||
http_login_password=dict(type='str', required=False,
|
||||
default=None, no_log=True),
|
||||
validate_certs=dict(type='bool', required=False, default=True),
|
||||
status=dict(type='str', default="active", choices=['active', 'passive']),
|
||||
state=dict(type='str', default="present", choices=['present', 'absent']),
|
||||
description=dict(type='str', required=False),
|
||||
tls_connect=dict(type='str', default='no_encryption',
|
||||
choices=['no_encryption', 'PSK', 'certificate']),
|
||||
tls_accept=dict(type='str', default='no_encryption',
|
||||
choices=['no_encryption', 'PSK', 'certificate']),
|
||||
ca_cert=dict(type='str', required=False, default=None, aliases=['tls_issuer']),
|
||||
tls_subject=dict(type='str', required=False, default=None),
|
||||
tls_psk_identity=dict(type='str', required=False, default=None),
|
||||
tls_psk=dict(type='str', required=False, default=None),
|
||||
timeout=dict(type='int', default=10),
|
||||
interface=dict(
|
||||
type='dict',
|
||||
required=False,
|
||||
default={},
|
||||
options=dict(
|
||||
useip=dict(type='int', choices=[0, 1], default=0),
|
||||
ip=dict(type='str', default=''),
|
||||
dns=dict(type='str', default=''),
|
||||
port=dict(type='str', default='10051'),
|
||||
type=dict(type='int', default=0, removed_in_version='2.14'),
|
||||
main=dict(type='int', default=0, removed_in_version='2.14')
|
||||
),
|
||||
)
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
if not HAS_ZABBIX_API:
|
||||
module.fail_json(msg=missing_required_lib('zabbix-api', url='https://pypi.org/project/zabbix-api/'), exception=ZBX_IMP_ERR)
|
||||
|
||||
server_url = module.params['server_url']
|
||||
login_user = module.params['login_user']
|
||||
login_password = module.params['login_password']
|
||||
http_login_user = module.params['http_login_user']
|
||||
http_login_password = module.params['http_login_password']
|
||||
validate_certs = module.params['validate_certs']
|
||||
proxy_name = module.params['proxy_name']
|
||||
proxy_address = module.params['proxy_address']
|
||||
description = module.params['description']
|
||||
status = module.params['status']
|
||||
tls_connect = module.params['tls_connect']
|
||||
tls_accept = module.params['tls_accept']
|
||||
tls_issuer = module.params['ca_cert']
|
||||
tls_subject = module.params['tls_subject']
|
||||
tls_psk_identity = module.params['tls_psk_identity']
|
||||
tls_psk = module.params['tls_psk']
|
||||
state = module.params['state']
|
||||
timeout = module.params['timeout']
|
||||
interface = module.params['interface']
|
||||
|
||||
# convert enabled to 0; disabled to 1
|
||||
status = 6 if status == "passive" else 5
|
||||
|
||||
if tls_connect == 'certificate':
|
||||
tls_connect = 4
|
||||
elif tls_connect == 'PSK':
|
||||
tls_connect = 2
|
||||
else:
|
||||
tls_connect = 1
|
||||
|
||||
if tls_accept == 'certificate':
|
||||
tls_accept = 4
|
||||
elif tls_accept == 'PSK':
|
||||
tls_accept = 2
|
||||
else:
|
||||
tls_accept = 1
|
||||
|
||||
zbx = None
|
||||
# login to zabbix
|
||||
try:
|
||||
zbx = ZabbixAPI(server_url, timeout=timeout,
|
||||
user=http_login_user,
|
||||
passwd=http_login_password,
|
||||
validate_certs=validate_certs)
|
||||
zbx.login(login_user, login_password)
|
||||
atexit.register(zbx.logout)
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to connect to Zabbix server: %s" % e)
|
||||
|
||||
proxy = Proxy(module, zbx)
|
||||
|
||||
# check if proxy already exists
|
||||
proxy_id = proxy.proxy_exists(proxy_name)
|
||||
|
||||
if proxy_id:
|
||||
if state == "absent":
|
||||
# remove proxy
|
||||
proxy.delete_proxy(proxy_id, proxy_name)
|
||||
else:
|
||||
proxy.update_proxy(proxy_id, {
|
||||
'host': proxy_name,
|
||||
'description': description,
|
||||
'status': str(status),
|
||||
'tls_connect': str(tls_connect),
|
||||
'tls_accept': str(tls_accept),
|
||||
'tls_issuer': tls_issuer,
|
||||
'tls_subject': tls_subject,
|
||||
'tls_psk_identity': tls_psk_identity,
|
||||
'tls_psk': tls_psk,
|
||||
'interface': interface,
|
||||
'proxy_address': proxy_address
|
||||
})
|
||||
else:
|
||||
if state == "absent":
|
||||
# the proxy is already deleted.
|
||||
module.exit_json(changed=False)
|
||||
|
||||
proxy_id = proxy.add_proxy(data={
|
||||
'host': proxy_name,
|
||||
'description': description,
|
||||
'status': str(status),
|
||||
'tls_connect': str(tls_connect),
|
||||
'tls_accept': str(tls_accept),
|
||||
'tls_issuer': tls_issuer,
|
||||
'tls_subject': tls_subject,
|
||||
'tls_psk_identity': tls_psk_identity,
|
||||
'tls_psk': tls_psk,
|
||||
'interface': interface,
|
||||
'proxy_address': proxy_address
|
||||
})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Reference in a new issue