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
This commit is contained in:
Matt Martz 2020-10-08 13:32:59 -05:00 committed by GitHub
parent cf0cd4b50b
commit 2f8dbf673e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 103 additions and 4685 deletions

View file

@ -76,6 +76,45 @@
- "dpkg_result.rc == 1" - "dpkg_result.rc == 1"
- "'Auto-installing missing dependency without updating cache: {{ python_apt }}' in apt_result.warnings" - "'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 - name: uninstall {{ python_apt }} with apt again
apt: apt:
pkg: "{{ python_apt }}" pkg: "{{ python_apt }}"

View file

@ -28,3 +28,20 @@
- 'both_file_keyserver is failed' - 'both_file_keyserver is failed'
- 'only_file.changed' - 'only_file.changed'
- 'not only_keyserver.changed' - 'not only_keyserver.changed'
- name: remove fedora.gpg
apt_key:
id: 97A1AE57C3A2372CCA3A4ABA6C13026D12C944D0
state: absent
register: remove_fedora
- name: add key from url
apt_key:
url: https://getfedora.org/static/fedora.gpg
register: apt_key_url
- name: verify key from url
assert:
that:
- remove_fedora is changed
- apt_key_url is changed

View file

@ -142,11 +142,17 @@
apt_repository: repo='{{test_ppa_spec}}' state=present apt_repository: repo='{{test_ppa_spec}}' state=present
register: result register: result
- name: update the cache
apt:
update_cache: true
register: result_cache
- assert: - assert:
that: that:
- 'result.changed' - 'result.changed'
- 'result.state == "present"' - 'result.state == "present"'
- 'result.repo == "{{test_ppa_spec}}"' - 'result.repo == "{{test_ppa_spec}}"'
- result_cache is not changed
- name: 'examine apt cache mtime' - name: 'examine apt cache mtime'
stat: path='/var/cache/apt/pkgcache.bin' stat: path='/var/cache/apt/pkgcache.bin'
@ -157,6 +163,10 @@
that: that:
- 'cache_before.stat.mtime != cache_after.stat.mtime' - '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 # When installing a repo with the spec, the key is *NOT* added
- name: 'ensure ppa key is absent (expect: pass)' - name: 'ensure ppa key is absent (expect: pass)'
apt_key: id='{{test_ppa_key}}' state=absent apt_key: id='{{test_ppa_key}}' state=absent

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- python3-PyMySQL
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- python-PyMySQL
- bzip2

View file

@ -1,5 +0,0 @@
mysql_service: 'mysql-server'
mysql_packages:
- mariadb101-server
- py-pymysql

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- MySQL-python
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mariadb'
mysql_packages:
- mariadb-server
- python3-PyMySQL
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mysqld'
mysql_packages:
- mysql-server
- MySQL-python
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mysql'
mysql_packages:
- mariadb
- python3-PyMySQL
- bzip2

View file

@ -1,6 +0,0 @@
mysql_service: 'mysql'
mysql_packages:
- mariadb
- python-PyMySQL
- bzip2

View file

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

View file

@ -1,7 +0,0 @@
destructive
shippable/posix/group1
skip/osx
skip/macos
skip/freebsd
skip/rhel
hidden

View file

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

View file

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

View file

@ -1,2 +0,0 @@
dependencies:
- incidental_setup_mysql_db

View file

@ -1,3 +0,0 @@
---
- include: setup.yml
when: ansible_os_family == 'Debian'

View file

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

View file

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

View file

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

View file

@ -1,7 +0,0 @@
destructive
shippable/posix/incidental
skip/aix
skip/osx
skip/macos
skip/freebsd
skip/rhel

View file

@ -1,5 +0,0 @@
---
zabbix_server_url: http://127.0.0.1/zabbix/
zabbix_login_user: Admin
zabbix_login_password: zabbix

View file

@ -1,2 +0,0 @@
dependencies:
- incidental_setup_zabbix

View file

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

View file

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

View file

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

View file

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

View file

@ -25,6 +25,12 @@
failed_when: False failed_when: False
register: systemctl_check register: systemctl_check
- meta: end_host
when: systemctl_check.rc != 0
- set_fact:
ssh_service: '{{ "ssh" if ansible_os_family == "Debian" else "sshd" }}'
- block: - block:
- name: get a list of running services - name: get a list of running services
shell: systemctl | fgrep 'running' | awk '{print $1}' | sed 's/\.service//g' | fgrep -v '.' | egrep ^[a-z] shell: systemctl | fgrep 'running' | awk '{print $1}' | sed 's/\.service//g' | fgrep -v '.' | egrep ^[a-z]
@ -57,7 +63,7 @@
- assert: - assert:
that: that:
- result is failed - 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 - name: the module must fail in check_mode as well when a service is not found
systemd: systemd:
@ -70,7 +76,7 @@
- assert: - assert:
that: that:
- result is failed - 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) - name: check that the module works even when systemd is offline (eg in chroot)
systemd: systemd:
@ -79,4 +85,32 @@
environment: environment:
SYSTEMD_OFFLINE: 1 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

View file

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

View file

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

View file

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