diff --git a/.travis-pre-commit-config.yaml b/.travis-pre-commit-config.yaml
new file mode 100644
index 0000000..9ce3990
--- /dev/null
+++ b/.travis-pre-commit-config.yaml
@@ -0,0 +1,66 @@
+---
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.5.0
+ hooks:
+ - id: check-ast
+ - id: check-case-conflict
+ - id: check-executables-have-shebangs
+ - id: check-yaml
+ name: Check yaml files
+ - id: debug-statements
+ name: Check for Python debug statements
+ - id: trailing-whitespace
+ name: Check trailing whitespace
+ args: [--markdown-linebreak-ext=md]
+ - repo: https://gitlab.com/pycqa/flake8.git
+ rev: 3.7.9
+ hooks:
+ - id: flake8
+ name: Check python (flake8)
+ args: ["--ignore=E501"]
+ - repo: https://github.com/adrienverge/yamllint.git
+ rev: v1.23.0
+ hooks:
+ - id: yamllint
+ name: Check yaml files (yamllint)
+ - repo: https://github.com/ansible/ansible-lint.git
+ rev: v4.3.0a0
+ hooks:
+ - id: ansible-lint
+ name: Check yaml files (ansible-lint)
+ args: ["-x", "602"]
+ types: [yaml]
+ - repo: local
+ hooks:
+ - id: docker-markdownlint
+ name: Run markdownlint with docker
+ entry: markdownlint/markdownlint -r "~MD013"
+ language: docker_image
+ types: [markdown]
+ - id: docker-shell-shfmt
+ name: Run shfmt with docker
+ entry: mvdan/shfmt:latest -d -i 2 -ci
+ language: docker_image
+ types: [shell]
+ - id: docker-shell-lint
+ name: Run shellcheck with docker
+ language: docker_image
+ entry: koalaman/shellcheck:stable
+ types: [shell]
+ - id: docker-prettier
+ name: Run prettier with docker
+ entry: tmknom/prettier:latest -l
+ language: docker_image
+ files: "\\.(\
+ css|less|scss\
+ |graphql|gql\
+ |html\
+ |js|jsx\
+ |json\
+ |md|markdown|mdown|mkdn\
+ |mdx\
+ |ts|tsx\
+ |vue\
+ |yaml|yml\
+ )$"
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..c997a81
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,37 @@
+---
+language: python
+services: docker
+
+env:
+ global:
+ - ROLE_NAME: mariadb
+ matrix:
+ - MOLECULE_DISTRO: debian-10
+ - MOLECULE_DISTRO: debian-sid
+ - MOLECULE_DISTRO: ubuntu-18.04
+ - MOLECULE_DISTRO: ubuntu-20.04
+
+cache:
+ directories:
+ - $HOME/.cache/pre-commit
+
+install:
+ - pip3 install -r requirements.txt
+
+jobs:
+ include:
+ - name: lint with pre-commit
+ install: skip
+ script:
+ - pip3 install pre-commit
+ - pre-commit run -c .travis-pre-commit-config.yaml -a
+
+script:
+ - export ANSIBLE_STRATEGY=mitogen_linear
+ - export ANSIBLE_STRATEGY_PLUGINS=${VIRTUAL_ENV}/lib/python${TRAVIS_PYTHON_VERSION}/site-packages/ansible_mitogen/plugins/strategy
+ - molecule --version
+ - molecule test
+#
+# //TEMP import later to galaxy
+# notifications:
+# webhooks: https://galaxy.ansible.com/api/v1/notifications/
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 0000000..5d7b323
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,7 @@
+---
+extends: default
+ignore: |
+ .direnv
+rules:
+ line-length: { max: 200, level: warning }
+ braces: { min-spaces-inside: 1, max-spaces-inside: 1 }
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6b51add
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+venv:
+ pip3 install -r requirements.txt
+
+venv_upgrade:
+ for i in $$(cat requirements.txt|cut -d "=" -f1); do pip3 install $$i -U; done
+
+test:
+ molecule test
diff --git a/README.md b/README.md
index 5e79b21..639cf17 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,237 @@
-# ansible-role-mariadb
-Ansible Role: MariaDB
+# Ansible role: MariaDB
+
+[](https://travis-ci.org/fauust/ansible-role-mariadb)
+
+Install and configure MariaDB server on Debian/Ubuntu.
+
+Optionally, this role also permits to:
+
+- deploy a master/slave cluster;
+- setup backups and rotation of the dumps.
+
+## Requirements
+
+The role use
+[`mysql_user`](https://docs.ansible.com/ansible/latest/modules/mysql_user_module.html)
+and
+[`mysql_db`](https://docs.ansible.com/ansible/latest/modules/mysql_db_module.html)
+ansible modules that depends on [PyMySQL](https://github.com/PyMySQL/PyMySQL) so
+the following Python 2.7 or Python 3.X versions are needed.
+
+For older Python versions, you may use
+[MySQLdb](http://mysql-python.sourceforge.net/MySQLdb.html) but then the role
+may not be idempotent (not tested).
+
+## Fact gathering (performance)
+
+Unless you want to use the MariaDB official repository (and then need
+`ansible_distribution_version`, see [`tasks/setup.yml`](./tasks/setup.yml)) this
+role does not require fact gathering.
+
+## Role variables
+
+Available variables are listed below, along with default values (see
+[`defaults/main.yml`](./defaults/main.yml)):
+
+### MariaDB version
+
+```yaml
+mariadb_use_official_repo: false
+mariadb_use_official_repo_url: "http://ftp.igh.cnrs.fr/pub/mariadb/repo"
+mariadb_use_official_repo_version: "10.4"
+```
+
+You may deploy the MariaDB Server version that comes with your distribution (Debian/Ubuntu) or
+deploy the version packaged by the MariaDB Foundation.
+You will find the repositories URL here:
+
+
+By default we deploy the MariaDB Server version that comes with the distribution.
+
+### MariaDB service activation and restart
+
+```yaml
+mariadb_enabled_on_startup: true
+mariadb_can_restart: true
+```
+
+**Warning:** you may consider setting `mariadb_can_restart` to `false` on
+production systems to prevent ansible runs to restart the MariaDB server.
+
+### General configuration
+
+To populate the MariaDB Server configuration file, we use almost only raw
+variables. This permits more flexibility and a very simple
+[`templates/my.cnf.j2`](./templates/my.cnf.j2) file.
+
+By default, some common and standard options are deployed.
+
+#### Basic settings
+
+The following variables are also needed by playbooks so we can not use raw
+variables.
+
+```yaml
+mariadb_config_file: /etc/mysql/my.cnf
+mariadb_datadir: "/var/lib/mysql"
+mariadb_port: "3306"
+mariadb_bind_address: "127.0.0.1"
+mariadb_unix_socket: "/run/mysqld/mysqld.sock"
+```
+
+```yaml
+mariadb_basic_settings_raw: |
+ user = mysql
+ pid-file = /run/mysqld/mysqld.pid
+ socket = {{ mariadb_unix_socket }}
+ basedir = /usr
+ datadir = {{ mariadb_datadir }}
+ tmpdir = /tmp
+ lc-messages-dir = /usr/share/mysql
+ lc_messages = en_US
+ skip-external-locking
+ port = {{ mariadb_port }}
+ bind-address = {{ mariadb_bind_address }}
+```
+
+#### Fine tuning
+
+```yaml
+mariadb_fine_tuning_raw: |
+ max_connections = 100
+ connect_timeout = 5
+ wait_timeout = 600
+ max_allowed_packet = 16M
+ thread_cache_size = 128
+ sort_buffer_size = 4M
+ bulk_insert_buffer_size = 16M
+ tmp_table_size = 32M
+ max_heap_table_size = 32M
+```
+
+#### Query cache
+
+```yaml
+mariadb_query_cache_raw: |
+ query_cache_size = 16M
+```
+
+#### Logging
+
+```yaml
+mariadb_logging_raw: |
+ log_error = /var/log/mysql/error.log
+```
+
+#### Character sets
+
+```yaml
+mariadb_character_sets_raw: |
+ character-set-server = utf8mb4
+ collation-server = utf8mb4_general_ci
+```
+
+#### InnoDB
+
+```yaml
+mariadb_innodb_raw: |
+ # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
+ # Read the manual for more InnoDB related options. There are many!
+```
+
+#### Mariadbdump
+
+```yaml
+mariadb_mysqldump_raw: |
+ quick
+ quote-names
+ max_allowed_packet = 16M
+```
+
+### Databases management
+
+```yaml
+# Databases.
+mariadb_databases: []
+# - name: db1
+# collation: utf8_general_ci
+# encoding: utf8
+# replicate: true|false
+```
+
+See:
+
+### Users management
+
+```yaml
+# Users.
+mariadb_users: []
+# - name: user
+# host: 100.64.200.10
+# password: password
+# priv: "*.*:USAGE/db1.*:ALL"
+# state: present|absent
+```
+
+See:
+
+### Replication (optional)
+
+Replication is only enabled if `mariadb_replication_role` has a value (`master` or
+`slave`).
+
+The replication setup on the slave use the GTID autopositionning
+`master_use_gtid=slave_pos`. See:
+
+
+For the moment, we use an `SQL` command but in ansible 2.10, we should be able
+to use the
+[`mysql_replication`](https://docs.ansible.com/ansible/latest/modules/mysql_replication_module.html)
+module (see
+[`tasks/replication_slave.yml`](./tasks/replication_slave.yml#L09-L33) and
+).
+
+```yaml
+mariadb_replication_role: "" # master|slave
+mariadb_replication_master_ip: ""
+mariadb_server_id: "1"
+mariadb_max_binlog_size: "100M"
+mariadb_binlog_format: "MIXED"
+mariadb_expire_logs_days: "10"
+
+# Same keys as `mariadb_users` above.
+# priv is set to "*.*:REPLICATION SLAVE" by default
+mariadb_replication_user: []
+```
+
+### Backups (optional)
+
+```yaml
+# db dumps backup
+mariadb_backup_db: false
+mariadb_backup_db_cron_min: "50"
+mariadb_backup_db_cron_hour: "00"
+mariadb_backup_db_dir: "/mnt/backup"
+mariadb_backup_db_rotation: "15"
+
+# name of the database to dump
+# (mandatory if mariadb_backup_db is set to true)
+mariadb_backup_db_name: []
+# - db1
+# - db2
+```
+
+Database dump is done serially and compression (`gzip`) is done at the end to
+avoid too long locks.
+
+## Example Playbook
+
+```yaml
+- hosts: database
+ roles:
+ - fauust.mariadb
+```
+
+## Lincense
+
+GNU General Public License v3.0
diff --git a/defaults/main.yml b/defaults/main.yml
new file mode 100644
index 0000000..bcac895
--- /dev/null
+++ b/defaults/main.yml
@@ -0,0 +1,110 @@
+---
+# Configure the following to use official MariaDB repository
+# see: https://downloads.mariadb.org/mariadb/repositories
+mariadb_use_official_repo: false
+mariadb_use_official_repo_url: "http://ftp.igh.cnrs.fr/pub/mariadb/repo"
+mariadb_use_official_repo_version: "10.4"
+
+mariadb_enabled_on_startup: true
+# The following is set to true by default but you may consider setting it to
+# false on production servers to prevent ansible from restarting mariadb server.
+mariadb_can_restart: true
+
+# Whether global conf file should be updated on every run
+mariadb_overwrite_global_config_file: true
+
+# MariaDB configuration file
+mariadb_config_file: /etc/mysql/my.cnf
+# Basic settings
+mariadb_datadir: "/var/lib/mysql"
+mariadb_port: "3306"
+mariadb_bind_address: "127.0.0.1"
+mariadb_unix_socket: "/run/mysqld/mysqld.sock"
+mariadb_basic_settings_raw: |
+ user = mysql
+ pid-file = /run/mysqld/mysqld.pid
+ socket = {{ mariadb_unix_socket }}
+ basedir = /usr
+ datadir = {{ mariadb_datadir }}
+ tmpdir = /tmp
+ lc-messages-dir = /usr/share/mysql
+ lc_messages = en_US
+ skip-external-locking
+ port = {{ mariadb_port }}
+ bind-address = {{ mariadb_bind_address }}
+
+# Fine tuning
+mariadb_fine_tuning_raw: |
+ max_connections = 100
+ connect_timeout = 5
+ wait_timeout = 600
+ max_allowed_packet = 16M
+ thread_cache_size = 128
+ sort_buffer_size = 4M
+ bulk_insert_buffer_size = 16M
+ tmp_table_size = 32M
+ max_heap_table_size = 32M
+
+# Query cache
+mariadb_query_cache_raw: |
+ query_cache_size = 16M
+
+# Logging
+mariadb_logging_raw: |
+ log_error = /var/log/mysql/error.log
+
+# Character sets
+mariadb_character_sets_raw: |
+ character-set-server = utf8mb4
+ collation-server = utf8mb4_general_ci
+
+# InnoDB
+mariadb_innodb_raw: |
+ # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/.
+ # Read the manual for more InnoDB related options. There are many!
+
+# mariadbdump
+mariadb_mysqldump_raw: |
+ quick
+ quote-names
+ max_allowed_packet = 16M
+
+# Databases
+mariadb_databases: []
+# - name: db1
+# collation: utf8_general_ci
+# encoding: utf8
+# replicate: true|false
+
+# Users
+mariadb_users: []
+# - name: user
+# host: 100.64.200.10
+# password: password
+# priv: "*.*:USAGE/db1.*:ALL"
+# state: present|absent
+
+# Replication
+# replication is only enabled if mariadb_replication_role has values
+mariadb_replication_role: "" # master|slave
+mariadb_replication_master_ip: ""
+mariadb_max_binlog_size: "100M"
+mariadb_binlog_format: "MIXED"
+mariadb_expire_logs_days: "10"
+
+# Replication users
+# same keys as mariadb_users above
+# priv is set to "*.*:REPLICATION SLAVE" by default
+mariadb_replication_user: []
+
+# Backups
+mariadb_backup_db: false
+mariadb_backup_db_cron_min: "50"
+mariadb_backup_db_cron_hour: "00"
+mariadb_backup_db_dir: "/opt/backup"
+mariadb_backup_db_rotation: "15"
+
+# DB to backup
+mariadb_backup_db_name: []
+# - db1
+# - db2
diff --git a/files/mariadb_dump_db.sh b/files/mariadb_dump_db.sh
new file mode 100755
index 0000000..feb99ec
--- /dev/null
+++ b/files/mariadb_dump_db.sh
@@ -0,0 +1,103 @@
+#!/usr/bin/env bash
+
+set -o errexit
+set -o pipefail
+set -o nounset
+set -o posix
+
+err() {
+ echo >&2 "[$(date +'%Y-%m-%dT%H:%M:%S%z')] ERROR: $*"
+}
+
+usage() {
+ cat >&2 <<-EOF
+Usage : $0 -d -l
+ -d dump directory destination (mandatory)
+ -l list of db to dump (coma separated, mandatory)
+ -k dumps to keep (in days)
+ -h help
+EOF
+}
+
+typeset VAR_DIR_ARGS=""
+typeset VAR_DB_LIST_ARGS=""
+typeset VAR_ROTATION_DAYS_ARGS=""
+
+while getopts "d:l:k:h" OPTION; do
+ case $OPTION in
+ d)
+ VAR_DIR_ARGS="$OPTARG"
+ ;;
+ l)
+ VAR_DB_LIST_ARGS="$OPTARG"
+ ;;
+ k)
+ VAR_ROTATION_DAYS_ARGS="$OPTARG"
+ ;;
+ h)
+ usage
+ exit 0
+ ;;
+ *)
+ usage
+ exit 1
+ ;;
+ esac
+done
+
+[[ $VAR_DIR_ARGS != "" ]] || {
+ usage
+ exit 1
+}
+[[ $VAR_DB_LIST_ARGS != "" ]] || {
+ usage
+ exit 1
+}
+
+# remove eventual trailing slash
+typeset -r VAR_DUMPS_DST_DIR=${VAR_DIR_ARGS%/}
+
+if [[ ! -d $VAR_DUMPS_DST_DIR ]]; then
+ mkdir -p "$VAR_DUMPS_DST_DIR" || {
+ err "mkdir -p $VAR_DUMPS_DST_DIR"
+ exit 1
+ }
+fi
+
+for cmd in mysqldump gzip; do
+ command -v "$cmd" >/dev/null || {
+ err "$cmd command not found"
+ exit 1
+ }
+done
+
+for db in ${VAR_DB_LIST_ARGS//,/ }; do
+ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: start $db dump."
+ typeset DUMP_FILE=$VAR_DUMPS_DST_DIR/$db.$(date +%F_%H%M%S).sql
+ mysqldump --single-transaction --quick --routines "$db" >"$DUMP_FILE"
+ # shellcheck disable=SC2181
+ if (($? != 0)); then
+ err "unable do dump $db"
+ exit 1
+ fi
+ echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: done.\n"
+done
+
+for sql in "$VAR_DUMPS_DST_DIR/"*.sql; do
+ echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: compress $sql."
+ gzip -- "$sql" || {
+ err "gzip $sql"
+ exit 1
+ }
+ echo -e "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: done.\n"
+done
+
+if [[ -n $VAR_ROTATION_DAYS_ARGS ]]; then
+ # rotation
+ echo "Rotation of old dumps (-${VAR_ROTATION_DAYS_ARGS}d)"
+ if ! find "$VAR_DUMPS_DST_DIR" -name "*.sql.gz" -type f -mtime +"$((VAR_ROTATION_DAYS_ARGS - 1))" -exec /bin/rm -vf {} \;; then
+ err "clean old dumps"
+ exit 1
+ fi
+ echo -e "done.\n"
+fi
diff --git a/handlers/main.yml b/handlers/main.yml
new file mode 100644
index 0000000..e8710e8
--- /dev/null
+++ b/handlers/main.yml
@@ -0,0 +1,8 @@
+---
+- name: restart mariadb
+ service:
+ name: mariadb
+ state: restarted
+ when:
+ - mariadb_can_restart
+ - not ansible_check_mode
diff --git a/meta/main.yml b/meta/main.yml
new file mode 100644
index 0000000..b136bd4
--- /dev/null
+++ b/meta/main.yml
@@ -0,0 +1,21 @@
+---
+dependencies: []
+
+galaxy_info:
+ author: fauust
+ role_name: mariadb
+ description: MariaDB server for Debian/Ubuntu.
+ license: "license GPL-3.0-only"
+ min_ansible_version: 2.8
+ platforms:
+ - name: Debian
+ versions:
+ - buster
+ - sid
+ - name: Ubuntu
+ versions:
+ - bionic
+ - focal
+ galaxy_tags:
+ - mariadb
+ - mysql
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
new file mode 100644
index 0000000..e1b1085
--- /dev/null
+++ b/molecule/default/converge.yml
@@ -0,0 +1,14 @@
+---
+- name: Converge
+ hosts: all
+ gather_facts: true
+ vars:
+ ansible_python_interpreter: /usr/bin/python3
+ vars_files: testvars.yml
+
+ pre_tasks:
+ - name: Update apt cache
+ apt: update_cache=true cache_valid_time=600
+
+ roles:
+ - role: ansible-role-mariadb
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
new file mode 100644
index 0000000..647a451
--- /dev/null
+++ b/molecule/default/molecule.yml
@@ -0,0 +1,20 @@
+---
+dependency:
+ name: galaxy
+driver:
+ name: docker
+platforms:
+ - name: instance
+ image: "fauust/docker-ansible:${MOLECULE_DISTRO:-debian-10}"
+ volumes:
+ - /sys/fs/cgroup:/sys/fs/cgroup:ro
+ privileged: true
+ command: /lib/systemd/systemd
+provisioner:
+ name: ansible
+ env:
+ ANSIBLE_GATHERING: explicit
+ ANSIBLE_FORCE_COLOR: true
+ ANSIBLE_PYTHON_INTERPRETER: /usr/bin/python3
+verifier:
+ name: testinfra
diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py
new file mode 100644
index 0000000..bdce1b0
--- /dev/null
+++ b/molecule/default/tests/test_default.py
@@ -0,0 +1,69 @@
+import os
+
+import testinfra.utils.ansible_runner
+
+testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
+ os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
+
+
+def test_passwd_file(host):
+ passwd = host.file("/etc/passwd")
+ assert passwd.contains("mysql")
+ assert passwd.user == "root"
+ assert passwd.group == "root"
+
+
+def test_mariadb_is_installed(host):
+ package = host.package("mariadb-server")
+ assert package.is_installed
+
+
+def test_ensure_mariadb_is_listening_on_requiered_port(host):
+ assert host.socket("tcp://0.0.0.0:3306").is_listening
+
+
+def test_mariadb_enabled_and_running(host):
+ assert host.service("mariadb").is_running
+ assert host.service("mariadb").is_enabled
+
+
+def test_ensure_custom_config_is_applied(host):
+ config = host.file("/etc/mysql/my.cnf")
+ assert config.contains("datadir")
+ assert config.user == "root"
+ assert config.group == "root"
+ assert config.mode == 0o644
+
+
+def test_ensure_innodb_is_enabled(host):
+ assert host.run("mariadb -Bse 'SHOW ENGINES' |\
+ grep -qE '^InnoDB.DEFAULT.*YES.YES.YES$'").rc == 0
+
+
+def test_ensure_system_db_exist(host):
+ assert host.run("mariadb -Bse 'SHOW DATABASES' |\
+ grep -q '^mysql$'").rc == 0
+ assert host.run("mariadb -Bse 'SHOW DATABASES' |\
+ grep -q '^information_schema$'").rc == 0
+ assert host.run("mariadb -Bse 'SHOW DATABASES' |\
+ grep -q '^performance_schema$'").rc == 0
+
+
+def test_ensure_test_db_exist(host):
+ assert host.run("mariadb -Bse 'SHOW DATABASES' |\
+ grep -q '^db1'").rc == 0
+ assert host.run("mariadb -Bse 'SHOW DATABASES' |\
+ grep -q '^db2'").rc == 0
+
+
+def test_some_sql_queries(host):
+ assert host.run("mariadb -e 'CREATE DATABASE db'").rc == 0
+ assert host.run("mariadb -e 'CREATE TABLE\
+ db.t_innodb(a1 SERIAL, c1 CHAR(8)) ENGINE=InnoDB;\
+ INSERT INTO db.t_innodb VALUES (1,\"foo\"),(2,\"bar\")'").rc == 0
+ assert host.run("mariadb -e 'CREATE FUNCTION db.f()\
+ RETURNS INT DETERMINISTIC RETURN 1'").rc == 0
+ assert host.run("mariadb -e 'SHOW TABLES IN db'").rc == 0
+ assert host.run("mariadb -e 'SELECT * FROM db.t_innodb;\
+ INSERT INTO db.t_innodb VALUES (3,\"foo\"),(4,\"bar\")'").rc == 0
+ assert host.run("mariadb -e 'SELECT db.f()'").rc == 0
diff --git a/molecule/default/testvars.yml b/molecule/default/testvars.yml
new file mode 100644
index 0000000..4b64303
--- /dev/null
+++ b/molecule/default/testvars.yml
@@ -0,0 +1,47 @@
+---
+mariadb_server_id: 1
+mariadb_bind_address: 0.0.0.0
+mariadb_replication_role: master
+
+mariadb_innodb_raw: |
+ innodb_buffer_pool_size = 512M
+ innodb_log_file_size = 64M
+ innodb_file_per_table = 1
+
+mariadb_databases:
+ - name: db1
+ collation: latin1_swedish_ci
+ encoding: latin1
+ state: present
+ - name: db2
+ state: present
+ replicate: true
+
+mariadb_users:
+ - name: user1
+ host: "%"
+ password: user1passwd
+ priv: "db2.*:SELECT"
+ state: present
+ - name: user2
+ host: 100.64.10.3
+ password: user2passwd
+ priv: "db2.*:ALL"
+ state: present
+
+mariadb_replication_user:
+ - name: ReplicationUser
+ host: 100.64.10.33
+ password: ReplicationPassword
+ state: present
+
+# backup
+mariadb_backup_db: true
+mariadb_backup_db_cron_min: "50"
+mariadb_backup_db_cron_hour: "00"
+mariadb_backup_db_dir: "/mnt/backup"
+mariadb_backup_db_rotation: "15"
+
+mariadb_backup_db_name:
+ - db1
+ - db2
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..12cf7fb
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,8 @@
+ansible-lint==4.2.0
+ansible==2.9.9
+docker==4.2.0
+flake8==3.8.1
+mitogen==0.2.9
+molecule==3.0.4
+testinfra==5.0.0
+yamllint==1.23.0
diff --git a/tasks/backup.yml b/tasks/backup.yml
new file mode 100644
index 0000000..4726ac2
--- /dev/null
+++ b/tasks/backup.yml
@@ -0,0 +1,36 @@
+---
+- name: Install cron package
+ apt:
+ package:
+ - cron
+ state: present
+
+- name: Deploy DB dump script
+ copy:
+ src: mariadb_dump_db.sh
+ dest: /usr/local/bin/mariadb_dump_db.sh
+ owner: root
+ group: root
+ mode: 0744
+ validate: "bash -n %s"
+
+- name: Ensure backup dir exists
+ file:
+ path: "{{ mariadb_backup_db_dir }}"
+ state: directory
+ owner: root
+ group: root
+ mode: 0755
+
+- name: Deploy crontab file
+ cron:
+ name: "Dump MariaBD databases"
+ minute: "{{ mariadb_backup_db_cron_min }}"
+ hour: "{{ mariadb_backup_db_cron_hour }}"
+ job: "/usr/local/bin/mariadb_dump_db.sh \
+ -d \"{{ mariadb_backup_db_dir }}\" \
+ -l \"{{ mariadb_backup_db_name|join(',') }}\" \
+ -k {{ mariadb_backup_db_rotation }} \
+ >{{ mariadb_backup_db_dir }}/mariadb_dump_db.log 2>&1"
+ cron_file: mariadb_dump_db
+ user: root
diff --git a/tasks/configure.yml b/tasks/configure.yml
new file mode 100644
index 0000000..e702749
--- /dev/null
+++ b/tasks/configure.yml
@@ -0,0 +1,28 @@
+---
+- name: Copy global MariaDB configuration
+ template:
+ src: my.cnf.j2
+ dest: "{{ mariadb_config_file }}"
+ owner: root
+ group: root
+ mode: 0644
+ force: "{{ mariadb_overwrite_global_config_file }}"
+ notify: restart mariadb
+
+- name: Create datadir if it does not exist
+ file:
+ path: "{{ mariadb_datadir }}"
+ state: directory
+ owner: mysql
+ group: mysql
+ mode: 0755
+
+- name: Ensure MariaDB is started and enabled on boot
+ service:
+ name: mariadb
+ state: started
+ enabled: "{{ mariadb_enabled_on_startup }}"
+
+# immediately restart mariadb
+# this is necessary for replication setup
+- meta: flush_handlers
diff --git a/tasks/databases.yml b/tasks/databases.yml
new file mode 100644
index 0000000..920e1c4
--- /dev/null
+++ b/tasks/databases.yml
@@ -0,0 +1,9 @@
+---
+- name: Ensure MariaDB databases are present (or absent)
+ mysql_db:
+ name: "{{ item.name }}"
+ collation: "{{ item.collation | default('utf8_general_ci') }}"
+ encoding: "{{ item.encoding | default('utf8') }}"
+ state: "{{ item.state | default('present') }}"
+ login_unix_socket: "{{ mariadb_unix_socket | default ('/run/mysqld/mysqld.sock') }}"
+ with_items: "{{ mariadb_databases }}"
diff --git a/tasks/main.yml b/tasks/main.yml
new file mode 100644
index 0000000..9ca9cf3
--- /dev/null
+++ b/tasks/main.yml
@@ -0,0 +1,32 @@
+---
+- name: include task setup.yml
+ include_tasks: setup.yml
+
+- name: include task configure.yml
+ include_tasks: configure.yml
+
+- name: include task databases.yml
+ include_tasks: databases.yml
+ when:
+ - mariadb_databases is defined
+ - mariadb_replication_role != "slave"
+
+- name: include task users.yml
+ include_tasks: users.yml
+ when:
+ - mariadb_users is defined
+ - mariadb_replication_role != "slave"
+
+- name: include task replication_master.yml
+ include_tasks: replication_master.yml
+ when: mariadb_replication_role == "master"
+
+- name: include task replication_slave.yml
+ include_tasks: replication_slave.yml
+ when:
+ - not ansible_check_mode
+ - mariadb_replication_role == "slave"
+
+- name: include task backup.yml
+ include_tasks: backup.yml
+ when: mariadb_backup_db
diff --git a/tasks/replication_master.yml b/tasks/replication_master.yml
new file mode 100644
index 0000000..2fb3ee9
--- /dev/null
+++ b/tasks/replication_master.yml
@@ -0,0 +1,12 @@
+---
+- name: Ensure replication user exists on master
+ mysql_user:
+ name: "{{ item.name }}"
+ host: "{{ item.host | default('%') }}"
+ password: "{{ item.password }}"
+ priv: "{{ item.priv | default('*.*:REPLICATION SLAVE') }}"
+ state: present
+ login_unix_socket: "{{ mariadb_unix_socket }}"
+ with_items: "{{ mariadb_replication_user }}"
+ when: not ansible_check_mode
+ no_log: true
diff --git a/tasks/replication_slave.yml b/tasks/replication_slave.yml
new file mode 100644
index 0000000..6c9ad0d
--- /dev/null
+++ b/tasks/replication_slave.yml
@@ -0,0 +1,54 @@
+---
+- name: Check slave replication status
+ mysql_replication:
+ mode: getslave
+ login_unix_socket: "{{ mariadb_unix_socket }}"
+ register: slave
+ no_log: true
+
+# For the moment, we have to use a sql command.
+# In ansible 2.10, we should be able to use mysql_replication module.
+# See https://github.com/ansible/ansible/pull/62648 (and below)
+- name: Configure replication on the slave
+ command: |
+ /usr/bin/mariadb -e "CHANGE MASTER TO master_host='{{ mariadb_replication_master_ip }}',
+ master_user='{{ item.name }}', master_password='{{ item.password }}', master_use_gtid=slave_pos"
+ with_items: "{{ mariadb_replication_user }}"
+ when:
+ - not slave.Is_Slave
+ no_log: true
+
+# # Following (not tested) should work on ansible 2.10
+# - name: Configure replication on the slave
+# mysql_replication:
+# mode: changemaster
+# master_host: "{{ mariadb_replication_master_ip }}"
+# master_user: "{{ item.name }}"
+# master_password: "{{ item.password }}"
+# master_use_gtid: "{{ mariadb_replication_gtid | default('slave_pos') }}
+# login_unix_socket: "{{ mariadb_unix_socket }}"
+# with_items: "{{ mariadb_replication_user }}"
+# when:
+# - not slave.Is_Slave
+# no_log: true
+
+- name: Reset slave replication
+ mysql_replication:
+ mode: resetslave
+ login_unix_socket: "{{ mariadb_unix_socket }}"
+ when:
+ - not slave.Is_Slave
+
+- name: Check slave replication status (second time)
+ mysql_replication:
+ mode: getslave
+ login_unix_socket: "{{ mariadb_unix_socket }}"
+ register: slave2
+ no_log: true
+
+- name: Start slave replication
+ mysql_replication:
+ mode: startslave
+ login_unix_socket: "{{ mariadb_unix_socket }}"
+ when:
+ - slave2.Slave_IO_Running == "No"
diff --git a/tasks/setup.yml b/tasks/setup.yml
new file mode 100644
index 0000000..daa6b8d
--- /dev/null
+++ b/tasks/setup.yml
@@ -0,0 +1,36 @@
+---
+- name: Install mariadb repo necessary packages
+ apt:
+ package:
+ - software-properties-common
+ - dirmngr
+ state: present
+ when: mariadb_use_official_repo
+
+- name: Install mariadb repository key
+ apt_key:
+ keyserver: keyserver.ubuntu.com
+ id: "0xF1656F24C74CD1D8"
+ when: mariadb_use_official_repo
+
+- name: Setup mariadb repository sourcelist entry
+ apt_repository:
+ repo: deb {{ mariadb_use_official_repo_url }}/{{ mariadb_use_official_repo_version }}/debian {{ ansible_distribution_release }} main
+ state: present
+ when: mariadb_use_official_repo
+
+- name: Install mariadb
+ apt:
+ package:
+ - mariadb-server
+ state: present
+ update_cache: true
+
+- name: Determine required MariaDB Python libraries
+ set_fact:
+ deb_mariadb_python_package: "{% if 'python3' in ansible_python_interpreter|default('') %}python3-pymysql{% else %}python-pymysql{% endif %}"
+
+- name: Install python mariadb driver
+ apt:
+ name: "{{ deb_mariadb_python_package }}"
+ state: present
diff --git a/tasks/users.yml b/tasks/users.yml
new file mode 100644
index 0000000..2acc735
--- /dev/null
+++ b/tasks/users.yml
@@ -0,0 +1,13 @@
+---
+- name: Ensure MariaDB users are present (or absent)
+ mysql_user:
+ name: "{{ item.name }}"
+ host: "{{ item.host | default('localhost') }}"
+ password: "{{ item.password }}"
+ priv: "{{ item.priv | default('*.*:USAGE') }}"
+ state: "{{ item.state | default('present') }}"
+ append_privs: "{{ item.append_privs | default('no') }}"
+ encrypted: "{{ item.encrypted | default('no') }}"
+ login_unix_socket: "{{ mariadb_unix_socket }}"
+ with_items: "{{ mariadb_users }}"
+ no_log: true
diff --git a/templates/my.cnf.j2 b/templates/my.cnf.j2
new file mode 100644
index 0000000..94cd4c4
--- /dev/null
+++ b/templates/my.cnf.j2
@@ -0,0 +1,49 @@
+# {{ ansible_managed }}
+
+[mariadb]
+
+# Basic settings
+{{ mariadb_basic_settings_raw }}
+# Fine tuning
+{{ mariadb_fine_tuning_raw }}
+# Logging
+{{ mariadb_logging_raw }}
+# Query cache
+{{ mariadb_query_cache_raw }}
+# Character sets
+{{ mariadb_character_sets_raw }}
+# InnoDB
+{{ mariadb_innodb_raw }}
+
+{% if mariadb_replication_role != '' %}
+# Replication
+server-id = {{ mariadb_server_id }}
+log-basename = mariadb
+
+{% if mariadb_replication_role == 'master' %}
+log_bin
+expire_logs_days = {{ mariadb_expire_logs_days }}
+max_binlog_size = {{ mariadb_max_binlog_size }}
+binlog_format = {{mariadb_binlog_format}}
+# the following permits to simplify the process of moving a slave to a master
+# role by ensuring that replication is not started on master
+skip-slave-start
+
+{% for db in mariadb_databases %}
+{% if db.replicate|default(false) %}
+binlog_do_db = {{ db.name }}
+{% else %}
+binlog_ignore_db = {{ db.name }}
+{% endif %}
+{% endfor %}
+{% endif %}
+
+{% if mariadb_replication_role == 'slave' %}
+read_only
+relay-log = relay-bin
+relay-log-index = relay-bin.index
+{% endif %}
+{% endif -%}
+
+[mysqldump]
+{{ mariadb_mysqldump_raw }}