├── .devcontainer ├── Dockerfile ├── README.md └── devcontainer.json ├── .fixtures.yml ├── .geppetto-rc.json ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── mend.yml │ ├── nightly.yml │ ├── release.yml │ └── release_prep.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .pdkignore ├── .puppet-lint.rc ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .sync.yml ├── .vscode └── extensions.json ├── .yardopts ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Gemfile ├── HISTORY.md ├── LICENSE ├── NOTICE ├── README.md ├── REFERENCE.md ├── Rakefile ├── TODO ├── data └── common.yaml ├── examples ├── backup.pp ├── bindings.pp ├── java.pp ├── monitor.pp ├── mysql_database.pp ├── mysql_db.pp ├── mysql_grant.pp ├── mysql_login_path.pp ├── mysql_plugin.pp ├── mysql_user.pp ├── mysqltuner.pp ├── perl.pp ├── python.pp ├── rh-mysql80-server.pp ├── ruby.pp ├── server.pp └── server │ ├── account_security.pp │ └── config.pp ├── hiera.yaml ├── lib ├── facter │ ├── mysql_server_id.rb │ ├── mysql_version.rb │ └── mysqld_version.rb └── puppet │ ├── functions │ ├── mysql │ │ ├── innobackupex_args.rb │ │ ├── normalise_and_deepmerge.rb │ │ ├── password.rb │ │ └── strip_hash.rb │ └── mysql_password.rb │ ├── provider │ ├── mysql.rb │ ├── mysql_database │ │ └── mysql.rb │ ├── mysql_datadir │ │ └── mysql.rb │ ├── mysql_grant │ │ └── mysql.rb │ ├── mysql_login_path │ │ ├── inifile.rb │ │ ├── mysql_login_path.rb │ │ └── sensitive.rb │ ├── mysql_plugin │ │ └── mysql.rb │ └── mysql_user │ │ └── mysql.rb │ └── type │ ├── mysql_database.rb │ ├── mysql_datadir.rb │ ├── mysql_grant.rb │ ├── mysql_login_path.rb │ ├── mysql_plugin.rb │ └── mysql_user.rb ├── manifests ├── backup │ ├── mysqlbackup.pp │ ├── mysqldump.pp │ └── xtrabackup.pp ├── bindings.pp ├── bindings │ ├── client_dev.pp │ ├── daemon_dev.pp │ ├── java.pp │ ├── perl.pp │ ├── php.pp │ ├── python.pp │ └── ruby.pp ├── client.pp ├── client │ └── install.pp ├── db.pp ├── params.pp ├── server.pp └── server │ ├── account_security.pp │ ├── backup.pp │ ├── config.pp │ ├── install.pp │ ├── installdb.pp │ ├── managed_dirs.pp │ ├── providers.pp │ ├── root_password.pp │ └── service.pp ├── metadata.json ├── pdk.yaml ├── provision.yaml ├── readmes ├── README_ja_JP.md └── REFERENCE_ja_JP.md ├── spec ├── acceptance │ ├── 00_mysql_server_spec.rb │ ├── 01_mysql_db_spec.rb │ ├── 02_mysql_mariadb_spec.rb │ ├── 03_mysql_task_spec.rb │ ├── 04_mysql_backup_spec.rb │ ├── 05_mysql_xtrabackup_spec.rb │ └── types │ │ ├── mysql_database_spec.rb │ │ ├── mysql_grant_spec.rb │ │ ├── mysql_login_path_spec.rb │ │ ├── mysql_plugin_spec.rb │ │ └── mysql_user_spec.rb ├── classes │ ├── graceful_failures_spec.rb │ ├── mycnf_template_spec.rb │ ├── mysql_backup_mysqldump_spec.rb │ ├── mysql_backup_xtrabackup_spec.rb │ ├── mysql_bindings_spec.rb │ ├── mysql_client_spec.rb │ ├── mysql_server_account_security_spec.rb │ ├── mysql_server_backup_spec.rb │ └── mysql_server_spec.rb ├── default_facts.yml ├── defines │ └── mysql_db_spec.rb ├── functions │ ├── mysql_innobackupex_args_spec.rb │ ├── mysql_normalise_and_deepmerge_spec.rb │ ├── mysql_password_spec.rb │ └── mysql_strip_hash_spec.rb ├── spec_helper.rb ├── spec_helper_acceptance.rb ├── spec_helper_acceptance_local.rb ├── spec_helper_local.rb ├── support │ └── mysql_login_path │ │ ├── mysql-5.6 │ │ ├── my_print_defaults │ │ └── mysql_config_editor │ │ ├── mysql-5.7 │ │ ├── my_print_defaults │ │ └── mysql_config_editor │ │ └── mysql-8.0 │ │ ├── my_print_defaults │ │ └── mysql_config_editor └── unit │ ├── facter │ ├── mysql_server_id_spec.rb │ ├── mysql_version_spec.rb │ └── mysqld_version_spec.rb │ └── puppet │ ├── provider │ ├── mysql_database │ │ └── mysql_spec.rb │ ├── mysql_login_path │ │ ├── mysql_login_path_spec.rb │ │ └── sensitive_spec.rb │ ├── mysql_plugin │ │ └── mysql_spec.rb │ └── mysql_user │ │ └── mysql_spec.rb │ └── type │ ├── mysql_database_spec.rb │ ├── mysql_grant_spec.rb │ ├── mysql_login_path_spec.rb │ ├── mysql_plugin_spec.rb │ └── mysql_user_spec.rb ├── tasks ├── export.json ├── export.rb ├── sql.json └── sql.rb ├── templates ├── meb.cnf.epp ├── my.cnf.epp ├── my.cnf.pass.epp ├── mysqlbackup.sh.epp └── xtrabackup.sh.epp └── types └── options.pp /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM puppet/pdk:latest 2 | 3 | # [Optional] Uncomment this section to install additional packages. 4 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 5 | # && apt-get -y install --no-install-recommends 6 | 7 | -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | # devcontainer 2 | 3 | 4 | For format details, see https://aka.ms/devcontainer.json. 5 | 6 | For config options, see the README at: 7 | https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/puppet 8 | 9 | ``` json 10 | { 11 | "name": "Puppet Development Kit (Community)", 12 | "dockerFile": "Dockerfile", 13 | 14 | // Set *default* container specific settings.json values on container create. 15 | "settings": { 16 | "terminal.integrated.profiles.linux": { 17 | "bash": { 18 | "path": "bash", 19 | } 20 | } 21 | }, 22 | 23 | // Add the IDs of extensions you want installed when the container is created. 24 | "extensions": [ 25 | "puppet.puppet-vscode", 26 | "rebornix.Ruby" 27 | ], 28 | 29 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 30 | "forwardPorts": [], 31 | 32 | // Use 'postCreateCommand' to run commands after the container is created. 33 | "postCreateCommand": "pdk --version", 34 | } 35 | ``` 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Puppet Development Kit (Community)", 3 | "dockerFile": "Dockerfile", 4 | 5 | "settings": { 6 | "terminal.integrated.profiles.linux": { 7 | "bash": { 8 | "path": "bash" 9 | } 10 | } 11 | }, 12 | 13 | "extensions": [ 14 | "puppet.puppet-vscode", 15 | "rebornix.Ruby" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | fixtures: 2 | repositories: 3 | "stdlib": "https://github.com/puppetlabs/puppetlabs-stdlib" 4 | "cron_core": "https://github.com/puppetlabs/puppetlabs-cron_core.git" 5 | "facts": "https://github.com/puppetlabs/puppetlabs-facts.git" 6 | "provision": "https://github.com/puppetlabs/provision.git" 7 | puppet_agent: 8 | repo: 'https://github.com/puppetlabs/puppetlabs-puppet_agent.git' 9 | ref: v4.21.0 10 | -------------------------------------------------------------------------------- /.geppetto-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "excludes": [ 3 | "**/contrib/**", 4 | "**/examples/**", 5 | "**/tests/**", 6 | "**/spec/**", 7 | "**/pkg/**" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rb eol=lf 2 | *.erb eol=lf 3 | *.pp eol=lf 4 | *.sh eol=lf 5 | *.epp eol=lf 6 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | Provide a detailed description of all the changes present in this pull request. 3 | 4 | ## Additional Context 5 | Add any additional context about the problem here. 6 | - [ ] Root cause and the steps to reproduce. (If applicable) 7 | - [ ] Thought process behind the implementation. 8 | 9 | ## Related Issues (if any) 10 | Mention any related issues or pull requests. 11 | 12 | ## Checklist 13 | - [ ] 🟢 Spec tests. 14 | - [ ] 🟢 Acceptance tests. 15 | - [ ] Manually verified. (For example `puppet apply`) -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "ci" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | Spec: 11 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 12 | with: 13 | runs_on: "ubuntu-24.04" 14 | secrets: "inherit" 15 | 16 | setup_matrix: 17 | name: "Setup Test Matrix" 18 | needs: "Spec" 19 | runs-on: ubuntu-24.04 20 | outputs: 21 | matrix: ${{ steps.get-matrix.outputs.matrix }} 22 | 23 | steps: 24 | - name: Checkout Source 25 | uses: actions/checkout@v3 26 | 27 | - name: Activate Ruby 2.7 28 | uses: ruby/setup-ruby@v1 29 | with: 30 | ruby-version: "2.7" 31 | bundler-cache: true 32 | 33 | - name: Print bundle environment 34 | run: | 35 | echo ::group::bundler environment 36 | bundle env 37 | echo ::endgroup:: 38 | 39 | - name: Setup Acceptance Test Matrix 40 | id: get-matrix 41 | run: | 42 | bundle exec matrix_from_metadata_v2 --exclude-platforms '["Debian-12-arm", "Ubuntu-22.04-arm", "RedHat-9-arm"]' 43 | 44 | Acceptance: 45 | name: "${{matrix.platforms.label}}, ${{matrix.collection}}" 46 | needs: 47 | - setup_matrix 48 | if: ${{ needs.setup_matrix.outputs.matrix != '{}' }} 49 | 50 | runs-on: ubuntu-24.04 51 | strategy: 52 | fail-fast: false 53 | matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} 54 | 55 | env: 56 | PUPPET_GEM_VERSION: '~> 7.24' 57 | FACTER_GEM_VERSION: 'https://github.com/puppetlabs/facter#main' 58 | 59 | steps: 60 | - name: Checkout Source 61 | uses: actions/checkout@v3 62 | 63 | - name: Activate Ruby 2.7 64 | uses: ruby/setup-ruby@v1 65 | with: 66 | ruby-version: "2.7" 67 | bundler-cache: true 68 | 69 | - name: Print bundle environment 70 | run: | 71 | bundle env 72 | 73 | - name: "Disable mysqld apparmor profile" 74 | if: ${{matrix.platforms.provider == 'docker'}} 75 | run: | 76 | sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ 77 | sudo apparmor_parser -R /etc/apparmor.d/disable/usr.sbin.mysqld 78 | sudo systemctl disable apparmor 79 | sudo systemctl stop apparmor 80 | 81 | - name: Provision test environment 82 | run: | 83 | bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]' 84 | FILE='spec/fixtures/litmus_inventory.yaml' 85 | sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true 86 | 87 | - name: Install agent 88 | run: | 89 | bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]' 90 | 91 | - name: Install module 92 | run: | 93 | bundle exec rake 'litmus:install_module' 94 | 95 | - name: Run acceptance tests 96 | run: | 97 | bundle exec rake 'litmus:acceptance:parallel' 98 | 99 | - name: Remove test environment 100 | if: ${{ always() }} 101 | continue-on-error: true 102 | run: | 103 | bundle exec rake 'litmus:tear_down' 104 | -------------------------------------------------------------------------------- /.github/workflows/mend.yml: -------------------------------------------------------------------------------- 1 | name: "mend" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | 13 | mend: 14 | uses: "puppetlabs/cat-github-actions/.github/workflows/mend_ruby.yml@main" 15 | secrets: "inherit" 16 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: "nightly" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | Spec: 10 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 11 | with: 12 | runs_on: "ubuntu-24.04" 13 | secrets: "inherit" 14 | 15 | setup_matrix: 16 | name: "Setup Test Matrix" 17 | needs: "Spec" 18 | runs-on: ubuntu-24.04 19 | outputs: 20 | matrix: ${{ steps.get-matrix.outputs.matrix }} 21 | 22 | steps: 23 | - name: Checkout Source 24 | uses: actions/checkout@v3 25 | 26 | - name: Activate Ruby 2.7 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: "2.7" 30 | bundler-cache: true 31 | 32 | - name: Print bundle environment 33 | run: | 34 | echo ::group::bundler environment 35 | bundle env 36 | echo ::endgroup:: 37 | 38 | - name: Setup Acceptance Test Matrix 39 | id: get-matrix 40 | run: | 41 | bundle exec matrix_from_metadata_v2 --exclude-platforms '["Debian-12-arm", "Ubuntu-22.04-arm", "RedHat-9-arm"]' 42 | 43 | Acceptance: 44 | name: "${{matrix.platforms.label}}, ${{matrix.collection}}" 45 | needs: 46 | - setup_matrix 47 | if: ${{ needs.setup_matrix.outputs.matrix != '{}' }} 48 | 49 | runs-on: ubuntu-24.04 50 | strategy: 51 | fail-fast: false 52 | matrix: ${{fromJson(needs.setup_matrix.outputs.matrix)}} 53 | 54 | env: 55 | PUPPET_GEM_VERSION: '~> 7.24' 56 | FACTER_GEM_VERSION: 'https://github.com/puppetlabs/facter#main' 57 | 58 | steps: 59 | - name: Checkout Source 60 | uses: actions/checkout@v3 61 | 62 | - name: Activate Ruby 2.7 63 | uses: ruby/setup-ruby@v1 64 | with: 65 | ruby-version: "2.7" 66 | bundler-cache: true 67 | 68 | - name: Print bundle environment 69 | run: | 70 | bundle env 71 | 72 | - name: "Disable mysqld apparmor profile" 73 | if: ${{matrix.platforms.provider == 'docker'}} 74 | run: | 75 | sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ 76 | sudo apparmor_parser -R /etc/apparmor.d/disable/usr.sbin.mysqld 77 | sudo systemctl disable apparmor 78 | sudo systemctl stop apparmor 79 | 80 | - name: Provision test environment 81 | run: | 82 | bundle exec rake 'litmus:provision[${{matrix.platforms.provider}},${{ matrix.platforms.image }}]' 83 | FILE='spec/fixtures/litmus_inventory.yaml' 84 | sed -e 's/password: .*/password: "[redacted]"/' < $FILE || true 85 | 86 | - name: Install agent 87 | run: | 88 | bundle exec rake 'litmus:install_agent[${{ matrix.collection }}]' 89 | 90 | - name: Install module 91 | run: | 92 | bundle exec rake 'litmus:install_module' 93 | 94 | - name: Run acceptance tests 95 | run: | 96 | bundle exec rake 'litmus:acceptance:parallel' 97 | 98 | - name: Remove test environment 99 | if: ${{ always() }} 100 | continue-on-error: true 101 | run: | 102 | bundle exec rake 'litmus:tear_down' 103 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish module" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_release.yml@main" 9 | secrets: "inherit" 10 | -------------------------------------------------------------------------------- /.github/workflows/release_prep.yml: -------------------------------------------------------------------------------- 1 | name: "Release Prep" 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "Module version to be released. Must be a valid semver string. (1.2.3)" 8 | required: true 9 | 10 | jobs: 11 | release_prep: 12 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_release_prep.yml@main" 13 | with: 14 | version: "${{ github.event.inputs.version }}" 15 | secrets: "inherit" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .*.sw[op] 3 | .metadata 4 | .yardoc 5 | .yardwarns 6 | *.iml 7 | /.bundle/ 8 | /.idea/ 9 | /.vagrant/ 10 | /coverage/ 11 | /bin/ 12 | /doc/ 13 | /Gemfile.local 14 | /Gemfile.lock 15 | /junit/ 16 | /log/ 17 | /pkg/ 18 | /spec/fixtures/manifests/ 19 | /spec/fixtures/modules/* 20 | /tmp/ 21 | /vendor/ 22 | /.vendor/ 23 | /convert_report.txt 24 | /update_report.txt 25 | .DS_Store 26 | .project 27 | .envrc 28 | /inventory.yaml 29 | /spec/fixtures/litmus_inventory.yaml 30 | .resource_types 31 | .modules 32 | .task_cache.json 33 | .plan_cache.json 34 | .rerun.json 35 | bolt-debug.log 36 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | RUN sudo wget https://apt.puppet.com/puppet-tools-release-bionic.deb && \ 3 | wget https://apt.puppetlabs.com/puppet6-release-bionic.deb && \ 4 | sudo dpkg -i puppet6-release-bionic.deb && \ 5 | sudo dpkg -i puppet-tools-release-bionic.deb && \ 6 | sudo apt-get update && \ 7 | sudo apt-get install -y pdk zsh puppet-agent && \ 8 | sudo apt-get clean && \ 9 | sudo rm -rf /var/lib/apt/lists/* 10 | RUN sudo usermod -s $(which zsh) gitpod && \ 11 | sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" && \ 12 | echo "plugins=(git gitignore github gem pip bundler python ruby docker docker-compose)" >> /home/gitpod/.zshrc && \ 13 | echo 'PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin"' >> /home/gitpod/.zshrc && \ 14 | sudo /opt/puppetlabs/puppet/bin/gem install puppet-debugger hub -N && \ 15 | mkdir -p /home/gitpod/.config/puppet && \ 16 | /opt/puppetlabs/puppet/bin/ruby -r yaml -e "puts ({'disabled' => true}).to_yaml" > /home/gitpod/.config/puppet/analytics.yml 17 | RUN rm -f puppet6-release-bionic.deb puppet-tools-release-bionic.deb 18 | ENTRYPOINT /usr/bin/zsh 19 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | tasks: 5 | - init: pdk bundle install 6 | 7 | vscode: 8 | extensions: 9 | - puppet.puppet-vscode@1.2.0:f5iEPbmOj6FoFTOV6q8LTg== 10 | -------------------------------------------------------------------------------- /.pdkignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .*.sw[op] 3 | .metadata 4 | .yardoc 5 | .yardwarns 6 | *.iml 7 | /.bundle/ 8 | /.idea/ 9 | /.vagrant/ 10 | /coverage/ 11 | /bin/ 12 | /doc/ 13 | /Gemfile.local 14 | /Gemfile.lock 15 | /junit/ 16 | /log/ 17 | /pkg/ 18 | /spec/fixtures/manifests/ 19 | /spec/fixtures/modules/* 20 | /tmp/ 21 | /vendor/ 22 | /.vendor/ 23 | /convert_report.txt 24 | /update_report.txt 25 | .DS_Store 26 | .project 27 | .envrc 28 | /inventory.yaml 29 | /spec/fixtures/litmus_inventory.yaml 30 | .resource_types 31 | .modules 32 | .task_cache.json 33 | .plan_cache.json 34 | .rerun.json 35 | bolt-debug.log 36 | /.fixtures.yml 37 | /Gemfile 38 | /.gitattributes 39 | /.github/ 40 | /.gitignore 41 | /.pdkignore 42 | /.puppet-lint.rc 43 | /Rakefile 44 | /rakelib/ 45 | /.rspec 46 | /..yml 47 | /.yardopts 48 | /spec/ 49 | /.vscode/ 50 | /.sync.yml 51 | /.devcontainer/ 52 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | --relative 2 | --no-anchor_resource-check 3 | --no-params_empty_string_assignment-check 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-12-05 10:38:45 UTC using RuboCop version 1.48.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 143 10 | # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. 11 | # SupportedStyles: always, named_only 12 | RSpec/NamedSubject: 13 | Exclude: 14 | - 'spec/classes/graceful_failures_spec.rb' 15 | - 'spec/classes/mycnf_template_spec.rb' 16 | - 'spec/classes/mysql_backup_mysqldump_spec.rb' 17 | - 'spec/classes/mysql_backup_xtrabackup_spec.rb' 18 | - 'spec/classes/mysql_client_spec.rb' 19 | - 'spec/classes/mysql_server_account_security_spec.rb' 20 | - 'spec/classes/mysql_server_backup_spec.rb' 21 | - 'spec/classes/mysql_server_spec.rb' 22 | - 'spec/defines/mysql_db_spec.rb' 23 | - 'spec/functions/mysql_innobackupex_args_spec.rb' 24 | - 'spec/functions/mysql_normalise_and_deepmerge_spec.rb' 25 | - 'spec/functions/mysql_strip_hash_spec.rb' 26 | -------------------------------------------------------------------------------- /.sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ".gitlab-ci.yml": 3 | delete: true 4 | appveyor.yml: 5 | delete: true 6 | rubocop.yml: 7 | include_todos: true 8 | 9 | 10 | spec/spec_helper.rb: 11 | spec_overrides: 12 | - require 'spec_helper_local' 13 | coverage_report: true 14 | "  changelog_user": puppetlabs 15 | .gitpod.Dockerfile: 16 | unmanaged: false 17 | .gitpod.yml: 18 | unmanaged: false 19 | .github/workflows/auto_release.yml: 20 | unmanaged: false 21 | .github/workflows/ci.yml: 22 | unmanaged: true 23 | .github/workflows/nightly.yml: 24 | unmanaged: true 25 | .github/workflows/release.yml: 26 | unmanaged: false 27 | .travis.yml: 28 | delete: true 29 | changelog_since_tag: 'v11.0.3' 30 | Rakefile: 31 | changelog_max_issues: 500 32 | extra_disabled_lint_checks: 33 | - anchor_resource 34 | - params_empty_string_assignment 35 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "puppet.puppet-vscode", 4 | "Shopify.ruby-lsp" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Setting ownership to the modules team 2 | * @puppetlabs/modules @alexjfisher @bastelfreak 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Puppet modules 2 | 3 | Check out our [Contributing to Supported Modules Blog Post](https://puppetlabs.github.io/iac/docs/contributing_to_a_module.html) to find all the information that you will need. 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 2 | 3 | def location_for(place_or_version, fake_version = nil) 4 | git_url_regex = %r{\A(?(https?|git)[:@][^#]*)(#(?.*))?} 5 | file_url_regex = %r{\Afile:\/\/(?.*)} 6 | 7 | if place_or_version && (git_url = place_or_version.match(git_url_regex)) 8 | [fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact 9 | elsif place_or_version && (file_url = place_or_version.match(file_url_regex)) 10 | ['>= 0', { path: File.expand_path(file_url[:path]), require: false }] 11 | else 12 | [place_or_version, { require: false }] 13 | end 14 | end 15 | 16 | group :development do 17 | gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 18 | gem "json", '= 2.3.0', require: false if Gem::Requirement.create(['>= 2.7.0', '< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 19 | gem "json", '= 2.5.1', require: false if Gem::Requirement.create(['>= 3.0.0', '< 3.0.5']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 20 | gem "json", '= 2.6.1', require: false if Gem::Requirement.create(['>= 3.1.0', '< 3.1.3']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 21 | gem "json", '= 2.6.3', require: false if Gem::Requirement.create(['>= 3.2.0', '< 4.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 22 | gem "racc", '~> 1.4.0', require: false if Gem::Requirement.create(['>= 2.7.0', '< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 23 | gem "deep_merge", '~> 1.2.2', require: false 24 | gem "voxpupuli-puppet-lint-plugins", '~> 5.0', require: false 25 | gem "facterdb", '~> 2.1', require: false 26 | gem "metadata-json-lint", '~> 4.0', require: false 27 | gem "rspec-puppet-facts", '~> 4.0', require: false 28 | gem "dependency_checker", '~> 1.0.0', require: false 29 | gem "parallel_tests", '= 3.12.1', require: false 30 | gem "pry", '~> 0.10', require: false 31 | gem "simplecov-console", '~> 0.9', require: false 32 | gem "puppet-debugger", '~> 1.0', require: false 33 | gem "rubocop", '~> 1.50.0', require: false 34 | gem "rubocop-performance", '= 1.16.0', require: false 35 | gem "rubocop-rspec", '= 2.19.0', require: false 36 | gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] 37 | gem "rexml", '>= 3.3.9', require: false 38 | end 39 | group :development, :release_prep do 40 | gem "puppet-strings", '~> 4.0', require: false 41 | gem "puppetlabs_spec_helper", '~> 7.0', require: false 42 | end 43 | group :system_tests do 44 | gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] 45 | gem "CFPropertyList", '< 3.0.7', require: false, platforms: [:mswin, :mingw, :x64_mingw] 46 | gem "serverspec", '~> 2.41', require: false 47 | end 48 | 49 | puppet_version = ENV['PUPPET_GEM_VERSION'] 50 | facter_version = ENV['FACTER_GEM_VERSION'] 51 | hiera_version = ENV['HIERA_GEM_VERSION'] 52 | 53 | gems = {} 54 | 55 | gems['puppet'] = location_for(puppet_version) 56 | 57 | # If facter or hiera versions have been specified via the environment 58 | # variables 59 | 60 | gems['facter'] = location_for(facter_version) if facter_version 61 | gems['hiera'] = location_for(hiera_version) if hiera_version 62 | 63 | gems.each do |gem_name, gem_params| 64 | gem gem_name, *gem_params 65 | end 66 | 67 | # Evaluate Gemfile.local and ~/.gemfile if they exist 68 | extra_gemfiles = [ 69 | "#{__FILE__}.local", 70 | File.join(Dir.home, '.gemfile'), 71 | ] 72 | 73 | extra_gemfiles.each do |gemfile| 74 | if File.file?(gemfile) && File.readable?(gemfile) 75 | eval(File.read(gemfile), binding) 76 | end 77 | end 78 | # vim: syntax=ruby 79 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Puppet Module - puppetlabs-mysql 2 | 3 | Copyright 2018 Puppet, Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler' 4 | require 'puppet_litmus/rake_tasks' if Gem.loaded_specs.key? 'puppet_litmus' 5 | require 'puppetlabs_spec_helper/rake_tasks' 6 | require 'puppet-syntax/tasks/puppet-syntax' 7 | require 'puppet-strings/tasks' if Gem.loaded_specs.key? 'puppet-strings' 8 | 9 | PuppetLint.configuration.send('disable_relative') 10 | PuppetLint.configuration.send('disable_anchor_resource') 11 | PuppetLint.configuration.send('disable_params_empty_string_assignment') 12 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | The best that I can tell is that this code traces back to David Schmitt. It has been forked many times since then :) 2 | 3 | 1. you cannot add databases to an instance that has a root password 4 | 2. you have to specify username as USER@BLAH or it cannot be found 5 | 3. mysql_grant does not complain if user does not exist 6 | 4. Needs support for pre-seeding on debian 7 | 5. the types may need to take user/password 8 | 6. rather or not to configure /etc/.my.cnf should be configurable 9 | -------------------------------------------------------------------------------- /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /examples/backup.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::server': 2 | root_password => 'password', 3 | } 4 | 5 | class { 'mysql::server::backup': 6 | backupuser => 'myuser', 7 | backuppassword => 'mypassword', 8 | backupdir => '/tmp/backups', 9 | } 10 | -------------------------------------------------------------------------------- /examples/bindings.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::bindings': 2 | php_enable => true, 3 | } 4 | -------------------------------------------------------------------------------- /examples/java.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::java': } 2 | -------------------------------------------------------------------------------- /examples/monitor.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Add a monitoring user to the database 3 | 4 | $mysql_monitor_password = 'password' 5 | $mysql_monitor_username = 'monitoring' 6 | $mysql_monitor_hostname = $facts['networking']['hostname'] 7 | 8 | mysql_user { "${mysql_monitor_username}@${mysql_monitor_hostname}": 9 | ensure => present, 10 | password_hash => mysql::password($mysql_monitor_password), 11 | require => Class['mysql::server::service'], 12 | } 13 | 14 | mysql_grant { "${mysql_monitor_username}@${mysql_monitor_hostname}/*.*": 15 | ensure => present, 16 | user => "${mysql_monitor_username}@${mysql_monitor_hostname}", 17 | table => '*.*', 18 | privileges => ['PROCESS', 'SUPER'], 19 | require => Mysql_user["${mysql_monitor_username}@${mysql_monitor_hostname}"], 20 | } 21 | -------------------------------------------------------------------------------- /examples/mysql_database.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::server': 2 | root_password => 'password', 3 | } 4 | mysql::db { ['test1', 'test2', 'test3']: 5 | ensure => present, 6 | charset => 'utf8', 7 | require => Class['mysql::server'], 8 | } 9 | mysql::db { 'test4': 10 | ensure => present, 11 | charset => 'latin1', 12 | } 13 | mysql::db { 'test5': 14 | ensure => present, 15 | charset => 'binary', 16 | collate => 'binary', 17 | } 18 | -------------------------------------------------------------------------------- /examples/mysql_db.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::server': 2 | root_password => 'password', 3 | } 4 | mysql::db { 'mydb': 5 | user => 'myuser', 6 | password => 'mypass', 7 | host => 'localhost', 8 | grant => ['SELECT', 'UPDATE'], 9 | } 10 | mysql::db { "mydb_${facts['networking']['fqdn']}": 11 | user => 'myuser', 12 | password => 'mypass', 13 | dbname => 'mydb', 14 | host => $facts['networking']['fqdn'], 15 | grant => ['SELECT', 'UPDATE'], 16 | tag => $domain, 17 | } 18 | -------------------------------------------------------------------------------- /examples/mysql_grant.pp: -------------------------------------------------------------------------------- 1 | mysql_grant { 'test1@localhost/redmine.*': 2 | user => 'test1@localhost', 3 | table => 'redmine.*', 4 | privileges => ['UPDATE'], 5 | } 6 | -------------------------------------------------------------------------------- /examples/mysql_login_path.pp: -------------------------------------------------------------------------------- 1 | # Debian MySQL Commiunity Server 8.0 2 | include apt 3 | apt::source { 'repo.mysql.com': 4 | location => 'http://repo.mysql.com/apt/debian', 5 | release => $facts['os']['distro']['codename'], 6 | repos => 'mysql-8.0', 7 | key => { 8 | id => 'A4A9406876FCBD3C456770C88C718D3B5072E1F5', 9 | server => 'hkp://keyserver.ubuntu.com:80', 10 | }, 11 | include => { 12 | src => false, 13 | deb => true, 14 | }, 15 | notify => Exec['apt-get update'], 16 | } 17 | exec { 'apt-get update': 18 | path => '/usr/bin:/usr/sbin:/bin:/sbin', 19 | refreshonly => true, 20 | } 21 | 22 | $root_pw = 'password' 23 | class { 'mysql::server': 24 | root_password => $root_pw, 25 | service_name => 'mysql', 26 | package_name => 'mysql-community-server', 27 | create_root_my_cnf => false, 28 | require => [ 29 | Apt::Source['repo.mysql.com'], 30 | Exec['apt-get update'] 31 | ], 32 | notify => Mysql_login_path['client'], 33 | } 34 | 35 | class { 'mysql::client': 36 | package_manage => false, 37 | package_name => 'mysql-community-client', 38 | require => Class['mysql::server'], 39 | } 40 | 41 | mysql_login_path { 'client': 42 | ensure => present, 43 | host => 'localhost', 44 | user => 'root', 45 | password => Sensitive($root_pw), 46 | socket => '/var/run/mysqld/mysqld.sock', 47 | owner => root, 48 | } 49 | 50 | mysql_login_path { 'local_dan': 51 | ensure => present, 52 | host => '127.0.0.1', 53 | user => 'dan', 54 | password => Sensitive('blah'), 55 | port => 3306, 56 | owner => root, 57 | require => Class['mysql::server'], 58 | } 59 | 60 | mysql_user { 'dan@localhost': 61 | ensure => present, 62 | password_hash => mysql::password('blah'), 63 | require => Mysql_login_path['client'], 64 | } 65 | -------------------------------------------------------------------------------- /examples/mysql_plugin.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::server': 2 | root_password => 'password', 3 | } 4 | 5 | $validate_password_soname = $facts['os']['family'] ? { 6 | 'windows' => 'validate_password.dll', 7 | default => 'validate_password.so' 8 | } 9 | 10 | mysql_plugin { 'validate_password': 11 | ensure => present, 12 | soname => $validate_password_soname, 13 | } 14 | 15 | $auth_socket_soname = $facts['os']['family'] ? { 16 | 'windows' => 'auth_socket.dll', 17 | default => 'auth_socket.so' 18 | } 19 | 20 | mysql_plugin { 'auth_socket': 21 | ensure => present, 22 | soname => $auth_socket_soname, 23 | } 24 | -------------------------------------------------------------------------------- /examples/mysql_user.pp: -------------------------------------------------------------------------------- 1 | $mysql_root_pw = 'password' 2 | 3 | class { 'mysql::server': 4 | root_password => 'password', 5 | } 6 | 7 | mysql_user { 'redmine@localhost': 8 | ensure => present, 9 | password_hash => mysql::password('redmine'), 10 | require => Class['mysql::server'], 11 | } 12 | 13 | mysql_user { 'dan@localhost': 14 | ensure => present, 15 | password_hash => mysql::password('blah'), 16 | } 17 | 18 | mysql_user { 'dan@%': 19 | ensure => present, 20 | password_hash => mysql::password('blah'), 21 | } 22 | 23 | mysql_user { 'socketplugin@%': 24 | ensure => present, 25 | plugin => 'unix_socket', 26 | } 27 | 28 | mysql_user { 'socketplugin@%': 29 | ensure => present, 30 | password_hash => mysql::password('blah'), 31 | plugin => 'mysql_native_password', 32 | } 33 | -------------------------------------------------------------------------------- /examples/mysqltuner.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Manage the MySQLTuner package. 3 | 4 | $version = 'v1.3.0' 5 | 6 | file { '/usr/local/bin/mysqltuner': 7 | ensure => 'file', 8 | owner => 'root', 9 | group => 'root', 10 | mode => '0550', 11 | source => "https://github.com/major/MySQLTuner-perl/raw/${version}/mysqltuner.pl", 12 | } 13 | -------------------------------------------------------------------------------- /examples/perl.pp: -------------------------------------------------------------------------------- 1 | include mysql::bindings::perl 2 | -------------------------------------------------------------------------------- /examples/python.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::bindings::python': } 2 | -------------------------------------------------------------------------------- /examples/rh-mysql80-server.pp: -------------------------------------------------------------------------------- 1 | file { '/etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo': 2 | source => 'https://raw.githubusercontent.com/sclorg/centos-release-scl/master/centos-release-scl/RPM-GPG-KEY-CentOS-SIG-SCLo', 3 | } 4 | 5 | yumrepo { 'centos-sclo-rh': 6 | ensure => present, 7 | name => 'CentOS-SCLo-scl-rh', 8 | enabled => true, 9 | baseurl => 'http://mirror.centos.org/centos/7/sclo/$basearch/rh/', 10 | mirrorlist => 'http://mirrorlist.centos.org?arch=$basearch&release=7&repo=sclo-rh', 11 | descr => 'CentOS-7 - SCLo rh', 12 | gpgcheck => true, 13 | gpgkey => 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-SCLo', 14 | } 15 | class { 'mysql::server': 16 | package_name => 'rh-mysql80', 17 | package_ensure => 'installed', 18 | service_name => 'rh-mysql80-mysqld', 19 | config_file => '/etc/my.cnf', 20 | includedir => '/etc/my.cnf.d', 21 | options => { mysqld => { log_error => '/var/log/mysqld.log', datadir => '/var/lib/mysql' } }, 22 | } 23 | -------------------------------------------------------------------------------- /examples/ruby.pp: -------------------------------------------------------------------------------- 1 | include mysql::bindings::ruby 2 | -------------------------------------------------------------------------------- /examples/server.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::server': 2 | root_password => 'password', 3 | } 4 | -------------------------------------------------------------------------------- /examples/server/account_security.pp: -------------------------------------------------------------------------------- 1 | class { 'mysql::server': 2 | root_password => 'password', 3 | } 4 | class { 'mysql::server::account_security': } 5 | -------------------------------------------------------------------------------- /examples/server/config.pp: -------------------------------------------------------------------------------- 1 | mysql::server::config { 'testfile': 2 | } 3 | -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | 4 | defaults: # Used for any hierarchy level that omits these keys. 5 | datadir: data # This path is relative to hiera.yaml's directory. 6 | data_hash: yaml_data # Use the built-in YAML backend. 7 | 8 | hierarchy: 9 | - name: "osfamily/major release" 10 | paths: 11 | # Used to distinguish between Debian and Ubuntu 12 | - "os/%{facts.os.name}/%{facts.os.release.major}.yaml" 13 | - "os/%{facts.os.family}/%{facts.os.release.major}.yaml" 14 | - name: "osfamily" 15 | paths: 16 | - "os/%{facts.os.name}.yaml" 17 | - "os/%{facts.os.family}.yaml" 18 | - name: 'common' 19 | path: 'common.yaml' 20 | -------------------------------------------------------------------------------- /lib/facter/mysql_server_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def mysql_id_get 4 | # Convert the existing mac to an integer 5 | macval = Facter.value(:macaddress).delete(':').to_i(16) 6 | 7 | # Valid range is from 1 - 4294967295 for replication hosts. 8 | # We can not guarantee a fully unique value, this reduces the 9 | # full mac value down to into that number space. 10 | # 11 | # The -1/+1 ensures that we keep above 1 if we get unlucky 12 | # enough to hit a mac address that evenly divides. 13 | (macval % (4_294_967_295 - 1)) + 1 14 | end 15 | 16 | Facter.add('mysql_server_id') do 17 | setcode do 18 | mysql_id_get 19 | rescue StandardError 20 | nil 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/facter/mysql_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Facter.add('mysql_version') do 4 | setcode do 5 | mysql_ver = if Facter::Core::Execution.which('mysql') 6 | Facter::Core::Execution.execute('mysql --version') 7 | elsif Facter::Core::Execution.which('mariadb') 8 | Facter::Core::Execution.execute('mariadb --version') 9 | end 10 | mysql_ver.match(%r{\d+\.\d+\.\d+})[0] if mysql_ver 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/facter/mysqld_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Facter.add('mysqld_version') do 4 | setcode do 5 | if Facter::Core::Execution.which('mysqld') || Facter::Core::Execution.which('/usr/libexec/mysqld') 6 | Facter::Core::Execution.execute('env PATH=$PATH:/usr/libexec mysqld --no-defaults -V 2>/dev/null') 7 | elsif Facter::Core::Execution.which('mariadbd') 8 | Facter::Core::Execution.execute('mariadbd --no-defaults -V 2>/dev/null') 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/puppet/functions/mysql/innobackupex_args.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @summary 4 | # This function populates and returns the string of arguments which later gets injected in template. Arguments that return string holds is conditional and decided by the the input given to function. 5 | # 6 | Puppet::Functions.create_function(:'mysql::innobackupex_args') do 7 | # @param backupuser 8 | # The user to use for the backup. 9 | # @param backupcompress 10 | # If the backup should be compressed. 11 | # @param backuppassword_unsensitive 12 | # The password to use for the backup. 13 | # @param backupdatabases 14 | # The databases to backup. 15 | # @param optional_args 16 | # Additional arguments to pass to innobackupex. 17 | # 18 | # @return String 19 | # Generated on the basis of provided values. 20 | # 21 | dispatch :innobackupex_args do 22 | required_param 'Optional[String]', :backupuser 23 | required_param 'Boolean', :backupcompress 24 | required_param 'Optional[Variant[String, Sensitive[String]]]', :backuppassword_unsensitive 25 | required_param 'Array[String[1]]', :backupdatabases 26 | required_param 'Array[String[1]]', :optional_args 27 | return_type 'Variant[String]' 28 | end 29 | 30 | def innobackupex_args(backupuser, backupcompress, backuppassword_unsensitive, backupdatabases, optional_args) 31 | innobackupex_args = '' 32 | innobackupex_args = "--user=\"#{backupuser}\" --password=\"#{backuppassword_unsensitive}\"" if backupuser && backuppassword_unsensitive 33 | 34 | innobackupex_args = "#{innobackupex_args} --compress" if backupcompress 35 | 36 | innobackupex_args = "#{innobackupex_args} --databases=\"#{backupdatabases.join(' ')}\"" if backupdatabases.is_a?(Array) && !backupdatabases.empty? 37 | 38 | if optional_args.is_a?(Array) 39 | optional_args.each do |arg| 40 | innobackupex_args = "#{innobackupex_args} #{arg}" 41 | end 42 | end 43 | innobackupex_args 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/puppet/functions/mysql/normalise_and_deepmerge.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @summary Recursively merges two or more hashes together, normalises keys with differing use of dashes and underscores. 4 | # 5 | # @example 6 | # $hash1 = {'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } } 7 | # $hash2 = {'two' => 'dos', 'three' => { 'five' => 5 } } 8 | # $merged_hash = mysql::normalise_and_deepmerge($hash1, $hash2) 9 | # # The resulting hash is equivalent to: 10 | # # $merged_hash = { 'one' => 1, 'two' => 'dos', 'three' => { 'four' => 4, 'five' => 5 } } 11 | # 12 | # - When there is a duplicate key that is a hash, they are recursively merged. 13 | # - When there is a duplicate key that is not a hash, the key in the rightmost hash will "win." 14 | # - When there are conficting uses of dashes and underscores in two keys (which mysql would otherwise equate), the rightmost style will win. 15 | # 16 | Puppet::Functions.create_function(:'mysql::normalise_and_deepmerge') do 17 | # @param args 18 | # Hash to be normalised 19 | # 20 | # @return hash 21 | # The given hash normalised 22 | # 23 | def normalise_and_deepmerge(*args) 24 | raise Puppet::ParseError, _('mysql::normalise_and_deepmerge(): wrong number of arguments (%{args_length}; must be at least 2)') % { args_length: args.length } if args.length < 2 25 | 26 | result = {} 27 | args.each do |arg| 28 | next if arg.is_a?(String) && arg.empty? # empty string is synonym for puppet's undef 29 | # If the argument was not a hash, skip it. 30 | raise Puppet::ParseError, _('mysql::normalise_and_deepmerge: unexpected argument type %{arg_class}, only expects hash arguments.') % { args_class: args.class } unless arg.is_a?(Hash) 31 | 32 | # We need to make a copy of the hash since it is frozen by puppet 33 | current = deep_copy(arg) 34 | 35 | # Now we have to traverse our hash assigning our non-hash values 36 | # to the matching keys in our result while following our hash values 37 | # and repeating the process. 38 | overlay(result, current) 39 | end 40 | result 41 | end 42 | 43 | def normalized?(hash, key) 44 | return true if hash.key?(key) 45 | return false unless %r{-|_}.match?(key) 46 | 47 | other_key = key.include?('-') ? key.tr('-', '_') : key.tr('_', '-') 48 | return false unless hash.key?(other_key) 49 | 50 | hash[key] = hash.delete(other_key) 51 | true 52 | end 53 | 54 | def overlay(hash1, hash2) 55 | hash2.each do |key, value| 56 | if normalized?(hash1, key) && value.is_a?(Hash) && hash1[key].is_a?(Hash) 57 | overlay(hash1[key], value) 58 | else 59 | hash1[key] = value 60 | end 61 | end 62 | end 63 | 64 | def deep_copy(inputhash) 65 | return inputhash unless inputhash.is_a? Hash 66 | 67 | hash = {} 68 | inputhash.each do |k, v| 69 | hash.store(k, deep_copy(v)) 70 | end 71 | hash 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/puppet/functions/mysql/password.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'digest/sha1' 4 | # @summary 5 | # Hash a string as mysql's "PASSWORD()" function would do it 6 | # 7 | Puppet::Functions.create_function(:'mysql::password') do 8 | # @param password 9 | # Plain text password. 10 | # @param sensitive 11 | # If the mysql password hash should be of datatype Sensitive[String] 12 | # 13 | # @return hash 14 | # The mysql password hash from the clear text password. 15 | # 16 | dispatch :password do 17 | required_param 'Variant[String, Sensitive[String]]', :password 18 | optional_param 'Boolean', :sensitive 19 | return_type 'Variant[String, Sensitive[String]]' 20 | end 21 | 22 | def password(password, sensitive = false) 23 | password = password.unwrap if password.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) 24 | 25 | result_string = if %r{^\*[A-F0-9]{40}$}.match?(password) 26 | password 27 | elsif password.empty? 28 | '' 29 | else 30 | "*#{Digest::SHA1.hexdigest(Digest::SHA1.digest(password)).upcase}" 31 | end 32 | 33 | if sensitive 34 | Puppet::Pops::Types::PSensitiveType::Sensitive.new(result_string) 35 | else 36 | result_string 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/puppet/functions/mysql/strip_hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @summary 4 | # When given a hash this function strips out all blank entries. 5 | # 6 | Puppet::Functions.create_function(:'mysql::strip_hash') do 7 | # @param hash 8 | # Hash to be stripped 9 | # 10 | # @return hash 11 | # The given hash with all blank entries removed 12 | # 13 | dispatch :strip_hash do 14 | required_param 'Hash', :hash 15 | return_type 'Hash' 16 | end 17 | 18 | def strip_hash(hash) 19 | # Filter out all the top level blanks. 20 | hash.reject { |_k, v| v == '' }.each do |_k, v| 21 | v.reject! { |_ki, vi| vi == '' } if v.is_a?(Hash) 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/puppet/functions/mysql_password.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # @summary DEPRECATED. Use the namespaced function [`mysql::password`](#mysqlpassword) instead. 4 | Puppet::Functions.create_function(:mysql_password) do 5 | # @param password 6 | # Plain text password. 7 | # @param sensitive 8 | # If the mysql password hash should be of datatype Sensitive[String] 9 | # 10 | # @return 11 | # The mysql password hash from the 4.x function mysql::password. 12 | dispatch :mysql_password do 13 | required_param 'Variant[String, Sensitive[String]]', :password 14 | optional_param 'Boolean', :sensitive 15 | return_type 'Variant[String, Sensitive[String]]' 16 | end 17 | 18 | def mysql_password(password, sensitive = false) 19 | call_function('deprecation', 'mysql_password', "This method has been deprecated, please use the namespaced version 'mysql::password' instead.") 20 | call_function('mysql::password', password, sensitive) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/puppet/provider/mysql_database/mysql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) 4 | Puppet::Type.type(:mysql_database).provide(:mysql, parent: Puppet::Provider::Mysql) do 5 | desc 'Manages MySQL databases.' 6 | 7 | commands mysql_raw: 'mysql' 8 | 9 | def self.instances 10 | mysql_caller('show databases', 'regular').split("\n").map do |name| 11 | attributes = {} 12 | mysql_caller(["show variables like '%_database'", name], 'regular').split("\n").each do |line| 13 | k, v = line.split(%r{\s}) 14 | attributes[k] = v 15 | end 16 | new(name: name, 17 | ensure: :present, 18 | charset: attributes['character_set_database'], 19 | collate: attributes['collation_database']) 20 | end 21 | end 22 | 23 | # We iterate over each mysql_database entry in the catalog and compare it against 24 | # the contents of the property_hash generated by self.instances 25 | def self.prefetch(resources) 26 | databases = instances 27 | resources.each_key do |database| 28 | provider = databases.find { |db| db.name == database } 29 | resources[database].provider = provider if provider 30 | end 31 | end 32 | 33 | def create 34 | self.class.mysql_caller("create database if not exists `#{@resource[:name]}` character set `#{@resource[:charset]}` collate `#{@resource[:collate]}`", 'regular') 35 | 36 | @property_hash[:ensure] = :present 37 | @property_hash[:charset] = @resource[:charset] 38 | @property_hash[:collate] = @resource[:collate] 39 | 40 | exists? ? (return true) : (return false) 41 | end 42 | 43 | def destroy 44 | self.class.mysql_caller("drop database if exists `#{@resource[:name]}`", 'regular') 45 | 46 | @property_hash.clear 47 | exists? ? (return false) : (return true) 48 | end 49 | 50 | def exists? 51 | @property_hash[:ensure] == :present || false 52 | end 53 | 54 | mk_resource_methods 55 | 56 | def charset=(value) 57 | self.class.mysql_caller("alter database `#{resource[:name]}` CHARACTER SET #{value}", 'regular') 58 | @property_hash[:charset] = value 59 | (charset == value) ? (return true) : (return false) 60 | end 61 | 62 | def collate=(value) 63 | self.class.mysql_caller("alter database `#{resource[:name]}` COLLATE #{value}", 'regular') 64 | @property_hash[:collate] = value 65 | (collate == value) ? (return true) : (return false) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/puppet/provider/mysql_datadir/mysql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) 4 | Puppet::Type.type(:mysql_datadir).provide(:mysql, parent: Puppet::Provider::Mysql) do 5 | desc 'manage data directories for mysql instances' 6 | 7 | initvars 8 | 9 | # Make sure we find mysqld on CentOS and mysql_install_db on Gentoo and Solaris 11 10 | ENV['PATH'] = [ 11 | ENV.fetch('PATH', nil), 12 | '/usr/libexec', 13 | '/usr/share/mysql/scripts', 14 | '/opt/rh/rh-mysql80/root/usr/bin', 15 | '/opt/rh/rh-mysql80/root/usr/libexec', 16 | '/opt/rh/rh-mysql57/root/usr/bin', 17 | '/opt/rh/rh-mysql57/root/usr/libexec', 18 | '/opt/rh/rh-mysql56/root/usr/bin', 19 | '/opt/rh/rh-mysql56/root/usr/libexec', 20 | '/opt/rh/rh-mariadb101/root/usr/bin', 21 | '/opt/rh/rh-mariadb101/root/usr/libexec', 22 | '/opt/rh/rh-mariadb100/root/usr/bin', 23 | '/opt/rh/rh-mariadb100/root/usr/libexec', 24 | '/opt/rh/rh-mariadb102/root/usr/bin', 25 | '/opt/rh/rh-mariadb102/root/usr/libexec', 26 | '/opt/rh/rh-mariadb103/root/usr/bin', 27 | '/opt/rh/rh-mariadb103/root/usr/libexec', 28 | '/opt/rh/mysql55/root/usr/bin', 29 | '/opt/rh/mysql55/root/usr/libexec', 30 | '/opt/rh/mariadb55/root/usr/bin', 31 | '/opt/rh/mariadb55/root/usr/libexec', 32 | '/usr/mysql/5.5/bin', 33 | '/usr/mysql/5.6/bin', 34 | '/usr/mysql/5.7/bin', 35 | ].join(':') 36 | 37 | commands mysqld: 'mysqld' 38 | optional_commands mysql_install_db: 'mysql_install_db' 39 | # rubocop:disable Lint/UselessAssignment 40 | def create 41 | name = @resource[:name] 42 | insecure = @resource.value(:insecure) || true 43 | defaults_extra_file = @resource.value(:defaults_extra_file) 44 | user = @resource.value(:user) || 'mysql' 45 | basedir = @resource.value(:basedir) 46 | datadir = @resource.value(:datadir) || @resource[:name] 47 | log_error = @resource.value(:log_error) || '/var/tmp/mysqld_initialize.log' 48 | # rubocop:enable Lint/UselessAssignment 49 | unless defaults_extra_file.nil? 50 | raise ArgumentError, _('Defaults-extra-file %{file} is missing.') % { file: defaults_extra_file } unless File.exist?(defaults_extra_file) 51 | 52 | defaults_extra_file = "--defaults-extra-file=#{defaults_extra_file}" 53 | end 54 | 55 | initialize = if insecure == true 56 | '--initialize-insecure' 57 | else 58 | '--initialize' 59 | end 60 | 61 | opts = [defaults_extra_file] 62 | ['basedir', 'datadir', 'user'].each do |opt| 63 | val = eval(opt) # rubocop:disable Security/Eval 64 | opts << "--#{opt}=#{val}" unless val.nil? 65 | end 66 | 67 | if !mysqld_version.nil? && newer_than('mysql' => '5.7.6', 'percona' => '5.7.6') 68 | opts << "--log-error=#{log_error}" 69 | opts << initialize.to_s 70 | debug("Initializing MySQL data directory >= 5.7.6 with mysqld: #{opts.compact.join(' ')}") 71 | mysqld(opts.compact) 72 | else 73 | debug("Installing MySQL data directory with mysql_install_db #{opts.compact.join(' ')}") 74 | mysql_install_db(opts.compact) 75 | end 76 | 77 | exists? 78 | end 79 | 80 | def destroy 81 | name = @resource[:name] # rubocop:disable Lint/UselessAssignment 82 | raise ArgumentError, _('ERROR: `Resource` can not be removed.') 83 | end 84 | 85 | def exists? 86 | datadir = @resource[:datadir] 87 | File.directory?("#{datadir}/mysql") && (Dir.entries("#{datadir}/mysql") - ['.', '..']).any? 88 | end 89 | 90 | ## 91 | ## MySQL datadir properties 92 | ## 93 | 94 | # Generates method for all properties of the property_hash 95 | mk_resource_methods 96 | end 97 | -------------------------------------------------------------------------------- /lib/puppet/provider/mysql_login_path/mysql_login_path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), 'inifile')) 4 | require File.expand_path(File.join(File.dirname(__FILE__), 'sensitive')) 5 | require 'puppet/resource_api/simple_provider' 6 | require 'puppet/util/execution' 7 | require 'puppet/util/suidmanager' 8 | require 'open3' 9 | require 'pty' 10 | require 'expect' 11 | require 'fileutils' 12 | require 'English' 13 | 14 | # Implementation for the mysql_login_path type using the Resource API. 15 | class Puppet::Provider::MysqlLoginPath::MysqlLoginPath < Puppet::ResourceApi::SimpleProvider 16 | def get_homedir(_context, uid) 17 | result = Puppet::Util::Execution.execute(['/usr/bin/getent', 'passwd', uid], failonfail: true) 18 | result.split(':')[5] 19 | end 20 | 21 | def mysql_config_editor_set_cmd(context, uid, password = nil, *args) 22 | args.unshift('/usr/bin/mysql_config_editor') 23 | homedir = get_homedir(context, uid) 24 | login_file_path = "#{homedir}/.mylogin.cnf" 25 | 26 | if args.is_a?(Array) 27 | command = args.flatten.map(&:to_s) 28 | command_str = command.join(' ') 29 | elsif args.is_a?(String) 30 | command_str = command 31 | end 32 | 33 | begin 34 | Puppet::Util::SUIDManager.asuser(uid) do 35 | FileUtils.touch login_file_path 36 | FileUtils.chmod 0o600, login_file_path 37 | end 38 | 39 | PTY.spawn({ 'HOME' => homedir }, command_str) do |input, output, _pid| 40 | if password 41 | input.expect(%r{Enter password:}) 42 | output.puts password 43 | end 44 | end 45 | rescue StandardError => e 46 | raise Puppet::ExecutionFailure, _( 47 | "Execution of '%{str}' returned %{exit_status}: %{output}", 48 | ) % { 49 | str: command_str, 50 | exit_status: $CHILD_STATUS.exitstatus, 51 | output: e.message 52 | } 53 | end 54 | end 55 | 56 | def mysql_config_editor_cmd(context, uid, *args) 57 | args.unshift('/usr/bin/mysql_config_editor') 58 | homedir = get_homedir(context, uid) 59 | Puppet::Util::Execution.execute( 60 | args, 61 | failonfail: true, 62 | uid: uid, 63 | custom_environment: { 'HOME' => homedir }, 64 | ) 65 | end 66 | 67 | def my_print_defaults_cmd(context, uid, *args) 68 | args.unshift('/usr/bin/my_print_defaults') 69 | homedir = get_homedir(context, uid) 70 | Puppet::Util::Execution.execute( 71 | args, 72 | failonfail: true, 73 | uid: uid, 74 | custom_environment: { 'HOME' => homedir }, 75 | ) 76 | end 77 | 78 | def get_password(context, uid, name) 79 | result = '' 80 | output = my_print_defaults_cmd(context, uid, '-s', name) 81 | output.split("\n").each do |line| 82 | result = line.sub(%r{--password=}, '') if line.include?('--password') 83 | end 84 | result 85 | end 86 | 87 | def save_login_path(context, name, should) 88 | uid = name.fetch(:owner) 89 | 90 | args = ['set', '--skip-warn'] 91 | args.push('-G', should[:name].to_s) if should[:name] 92 | args.push('-h', should[:host].to_s) if should[:host] 93 | args.push('-u', should[:user].to_s) if should[:user] 94 | args.push('-S', should[:socket].to_s) if should[:socket] 95 | args.push('-P', should[:port].to_s) if should[:port] 96 | args.push('-p') if should[:password] && extract_pw(should[:password]) 97 | password = (should[:password] && extract_pw(should[:password])) ? extract_pw(should[:password]) : nil 98 | 99 | mysql_config_editor_set_cmd(context, uid, password, args) 100 | end 101 | 102 | def delete_login_path(context, name) 103 | login_path = name.fetch(:name) 104 | uid = name.fetch(:owner) 105 | mysql_config_editor_cmd(context, uid, 'remove', '-G', login_path) 106 | end 107 | 108 | def gen_pw(password) 109 | Puppet::Provider::MysqlLoginPath::Sensitive.new(password) 110 | end 111 | 112 | def extract_pw(sensitive) 113 | sensitive.unwrap 114 | end 115 | 116 | def list_login_paths(context, uid) 117 | result = [] 118 | output = mysql_config_editor_cmd(context, uid, 'print', '--all') 119 | ini = Puppet::Provider::MysqlLoginPath::IniFile.new(content: output) 120 | ini.each_section do |section| 121 | result.push(ensure: 'present', 122 | name: section, 123 | owner: uid.to_s, 124 | title: "#{section}-#{uid}", 125 | host: ini[section]['host'].nil? ? nil : ini[section]['host'], 126 | user: ini[section]['user'].nil? ? nil : ini[section]['user'], 127 | password: ini[section]['password'].nil? ? nil : gen_pw(get_password(context, uid, section)), 128 | socket: ini[section]['socket'].nil? ? nil : ini[section]['socket'], 129 | port: ini[section]['port'].nil? ? nil : ini[section]['port']) 130 | end 131 | result 132 | end 133 | 134 | def get(context, name) 135 | result = [] 136 | owner = name.empty? ? ['root'] : name.filter_map { |item| item[:owner] }.uniq 137 | owner.each do |uid| 138 | login_paths = list_login_paths(context, uid) 139 | result += login_paths 140 | end 141 | result 142 | end 143 | 144 | def create(context, name, should) 145 | save_login_path(context, name, should) 146 | end 147 | 148 | def update(context, name, should) 149 | delete_login_path(context, name) 150 | save_login_path(context, name, should) 151 | end 152 | 153 | def delete(context, name) 154 | delete_login_path(context, name) 155 | end 156 | 157 | def canonicalize(_context, resources) 158 | resources.each do |r| 159 | r[:password] = gen_pw(extract_pw(r[:password])) if r.key?(:password) && r[:password].is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) 160 | end 161 | end 162 | end 163 | -------------------------------------------------------------------------------- /lib/puppet/provider/mysql_login_path/sensitive.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # A Puppet Language type that makes the Sensitive Type comparable 4 | # 5 | class Puppet::Provider::MysqlLoginPath::Sensitive < Puppet::Pops::Types::PSensitiveType::Sensitive 6 | def ==(other) 7 | return true if other.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive) && unwrap == other.unwrap 8 | end 9 | 10 | def encode_with(coder) 11 | coder.tag = nil 12 | coder.scalar = 'Puppet::Provider::MysqlLoginPath::Sensitive <>' 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/puppet/provider/mysql_plugin/mysql.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'mysql')) 4 | Puppet::Type.type(:mysql_plugin).provide(:mysql, parent: Puppet::Provider::Mysql) do 5 | desc 'Manages MySQL plugins.' 6 | 7 | commands mysql_raw: 'mysql' 8 | 9 | def self.instances 10 | mysql_caller('show plugins', 'regular').split("\n").map do |line| 11 | name, _status, _type, library, _license = line.split(%r{\t}) 12 | new(name: name, 13 | ensure: :present, 14 | soname: library) 15 | end 16 | end 17 | 18 | # We iterate over each mysql_plugin entry in the catalog and compare it against 19 | # the contents of the property_hash generated by self.instances 20 | def self.prefetch(resources) 21 | plugins = instances 22 | resources.each_key do |plugin| 23 | if provider = plugins.find { |pl| pl.name == plugin } # rubocop:disable Lint/AssignmentInCondition 24 | resources[plugin].provider = provider 25 | end 26 | end 27 | end 28 | 29 | def create 30 | # Use plugin_name.so as soname if it's not specified. This won't work on windows as 31 | # there it should be plugin_name.dll 32 | @resource[:soname].nil? ? (soname = "#{@resource[:name]}.so") : (soname = @resource[:soname]) 33 | self.class.mysql_caller("install plugin #{@resource[:name]} soname '#{soname}'", 'regular') 34 | 35 | @property_hash[:ensure] = :present 36 | @property_hash[:soname] = @resource[:soname] 37 | 38 | exists? ? (return true) : (return false) 39 | end 40 | 41 | def destroy 42 | self.class.mysql_caller("uninstall plugin #{@resource[:name]}", 'regular') 43 | 44 | @property_hash.clear 45 | exists? ? (return false) : (return true) 46 | end 47 | 48 | def exists? 49 | @property_hash[:ensure] == :present || false 50 | end 51 | 52 | mk_resource_methods 53 | end 54 | -------------------------------------------------------------------------------- /lib/puppet/type/mysql_database.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Type.newtype(:mysql_database) do 4 | @doc = <<-PUPPET 5 | @summary Manage a MySQL database. 6 | PUPPET 7 | 8 | ensurable 9 | 10 | autorequire(:file) { '/root/.my.cnf' } 11 | autorequire(:class) { 'mysql::server' } 12 | 13 | newparam(:name, namevar: true) do 14 | desc 'The name of the MySQL database to manage.' 15 | end 16 | 17 | newproperty(:charset) do 18 | desc 'The CHARACTER SET setting for the database' 19 | defaultto :utf8 20 | newvalue(%r{^\S+$}) 21 | end 22 | 23 | newproperty(:collate) do 24 | desc 'The COLLATE setting for the database' 25 | defaultto :utf8_general_ci 26 | newvalue(%r{^\S+$}) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/puppet/type/mysql_datadir.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Type.newtype(:mysql_datadir) do 4 | @doc = <<-PUPPET 5 | @summary Manage MySQL datadirs with mysql_install_db OR mysqld (5.7.6 and above). 6 | 7 | @api private 8 | PUPPET 9 | 10 | ensurable 11 | 12 | autorequire(:package) { 'mysql-server' } 13 | 14 | newparam(:datadir, namevar: true) do 15 | desc 'The datadir name' 16 | end 17 | 18 | newparam(:basedir) do 19 | desc 'The basedir name, default /usr.' 20 | newvalues(%r{^/}) 21 | end 22 | 23 | newparam(:user) do 24 | desc 'The user for the directory default mysql (name, not uid).' 25 | end 26 | 27 | newparam(:defaults_extra_file) do 28 | desc 'MySQL defaults-extra-file with absolute path (*.cnf).' 29 | newvalues(%r{^/.*\.cnf$}) 30 | end 31 | 32 | newparam(:insecure, boolean: true) do 33 | desc 'Insecure initialization (needed for 5.7.6++).' 34 | end 35 | 36 | newparam(:log_error) do 37 | desc 'The path to the mysqld error log file (used with the --log-error option)' 38 | newvalues(%r{^/}) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/puppet/type/mysql_grant.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Type.newtype(:mysql_grant) do 4 | @doc = <<-PUPPET 5 | @summary Manage a MySQL user's rights. 6 | PUPPET 7 | ensurable 8 | 9 | autorequire(:file) { '/root/.my.cnf' } 10 | autorequire(:mysql_user) { self[:user] } 11 | 12 | def initialize(*args) 13 | super 14 | # Forcibly munge any privilege with 'ALL' in the array to exist of just 15 | # 'ALL'. This can't be done in the munge in the property as that iterates 16 | # over the array and there's no way to replace the entire array before it's 17 | # returned to the provider. 18 | self[:privileges] = 'ALL' if self[:ensure] == :present && Array(self[:privileges]).size > 1 && self[:privileges].to_s.include?('ALL') 19 | # Sort the privileges array in order to ensure the comparision in the provider 20 | # self.instances method match. Otherwise this causes it to keep resetting the 21 | # privileges. 22 | # rubocop:disable Style/MultilineBlockChain 23 | self[:privileges] = Array(self[:privileges]).map { |priv| 24 | # split and sort the column_privileges in the parentheses and rejoin 25 | if priv.include?('(') 26 | type, col = priv.strip.split(%r{\s+|\b}, 2) 27 | "#{type.upcase} (#{col.slice(1...-1).strip.split(%r{\s*,\s*}).sort.join(', ')})" 28 | else 29 | priv.strip.upcase 30 | end 31 | }.uniq.reject { |k| ['GRANT', 'GRANT OPTION'].include?(k) }.sort! 32 | end 33 | # rubocop:enable Style/MultilineBlockChain 34 | validate do 35 | raise(_('mysql_grant: `privileges` `parameter` is required.')) if self[:ensure] == :present && self[:privileges].nil? 36 | raise(_('mysql_grant: `privileges` `parameter`: PROXY can only be specified by itself.')) if Array(self[:privileges]).size > 1 && Array(self[:privileges]).include?('PROXY') 37 | raise(_('mysql_grant: `table` `parameter` is required.')) if self[:ensure] == :present && self[:table].nil? 38 | raise(_('mysql_grant: `user` `parameter` is required.')) if self[:ensure] == :present && self[:user].nil? 39 | 40 | raise(_('mysql_grant: `name` `parameter` must match user@host/table format.')) if self[:user] && self[:table] && (self[:name] != "#{self[:user]}/#{self[:table]}") 41 | end 42 | 43 | newparam(:name, namevar: true) do 44 | desc 'Name to describe the grant.' 45 | 46 | munge do |value| 47 | value.delete("'") 48 | end 49 | end 50 | 51 | newproperty(:privileges, array_matching: :all) do 52 | desc 'Privileges for user' 53 | 54 | validate do |value| 55 | mysql_version = Facter.value(:mysql_version) 56 | if value =~ %r{proxy}i && Puppet::Util::Package.versioncmp(mysql_version, '5.5.0').negative? 57 | raise(ArgumentError, _('mysql_grant: PROXY user not supported on mysql versions < 5.5.0. Current version %{version}.') % { version: mysql_version }) 58 | end 59 | end 60 | end 61 | 62 | newproperty(:table) do 63 | desc 'Table to apply privileges to.' 64 | 65 | validate do |value| 66 | if Array(@resource[:privileges]).include?('PROXY') && !%r{^[0-9a-zA-Z$_]*@[\w%.:\-/]*$}.match(value) 67 | raise(ArgumentError, _('mysql_grant: `table` `property` for PROXY should be specified as proxy_user@proxy_host.')) 68 | end 69 | end 70 | 71 | munge do |value| 72 | value.delete('`') 73 | end 74 | 75 | newvalues(%r{.*\..*}, %r{^[0-9a-zA-Z$_]*@[\w%.:\-/]*$}) 76 | end 77 | 78 | newproperty(:user) do 79 | desc 'User to operate on.' 80 | validate do |value| 81 | # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html 82 | # If at least one special char is used, string must be quoted 83 | # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 84 | # rubocop:disable Lint/AssignmentInCondition 85 | # rubocop:disable Lint/UselessAssignment 86 | if matches = %r{^(['`"])((?!\1).)*\1@([\w%.:\-/]+)$}.match(value) 87 | user_part = matches[2] 88 | host_part = matches[3] 89 | elsif matches = %r{^([0-9a-zA-Z$_]*)@([\w%.:\-/]+)$}.match(value) || matches = %r{^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$}.match(value) 90 | user_part = matches[1] 91 | host_part = matches[2] 92 | else 93 | raise(ArgumentError, _('mysql_grant: Invalid database user %{user}.') % { user: value }) 94 | end 95 | # rubocop:enable Lint/AssignmentInCondition 96 | # rubocop:enable Lint/UselessAssignment 97 | mysql_version = Facter.value(:mysql_version) 98 | unless mysql_version.nil? 99 | raise(ArgumentError, _('mysql_grant: MySQL usernames are limited to a maximum of 16 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8').negative? && user_part.size > 16 100 | raise(ArgumentError, _('mysql_grant: MySQL usernames are limited to a maximum of 32 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0').negative? && user_part.size > 32 101 | raise(ArgumentError, _('mysql_grant: MySQL usernames are limited to a maximum of 80 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0').positive? && user_part.size > 80 102 | end 103 | end 104 | 105 | munge do |value| 106 | matches = %r{^((['`"]?).*\2)@(.+)$}.match(value) 107 | "#{matches[1]}@#{matches[3].downcase}" 108 | end 109 | end 110 | 111 | newproperty(:options, array_matching: :all) do 112 | desc 'Options to grant.' 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /lib/puppet/type/mysql_login_path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/resource_api' 4 | 5 | Puppet::ResourceApi.register_type( 6 | name: 'mysql_login_path', 7 | 8 | docs: <<-DESCRIPTION, 9 | @summary Manage a MySQL login path. 10 | @see 11 | https://dev.mysql.com/doc/refman/8.0/en/mysql-config-editor.html 12 | @example 13 | mysql_login_path { 'local_socket': 14 | owner => 'root', 15 | host => 'localhost', 16 | user => 'root', 17 | password => Sensitive('secure'), 18 | socket => '/var/run/mysql/mysql.sock', 19 | ensure => present, 20 | } 21 | 22 | mysql_login_path { 'local_tcp': 23 | owner => 'root', 24 | host => '127.0.0.1', 25 | user => 'root', 26 | password => Sensitive('more_secure'), 27 | port => 3306, 28 | ensure => present, 29 | } 30 | 31 | This type provides Puppet with the capabilities to store authentication credentials in an obfuscated login path file 32 | named .mylogin.cnf created with the mysql_config_editor utility. Supports only MySQL Community Edition > v5.6.6. 33 | DESCRIPTION 34 | features: ['simple_get_filter', 'canonicalize'], 35 | title_patterns: [ 36 | { 37 | pattern: %r{^(?.*[^-])-(?.*)$}, 38 | desc: 'Where the name of the and the owner are provided with a hyphen seperator' 39 | }, 40 | { 41 | pattern: %r{^(?.*)$}, 42 | desc: 'Where only the name is provided' 43 | }, 44 | ], 45 | attributes: { 46 | ensure: { 47 | type: 'Enum[present, absent]', 48 | desc: 'Whether this resource should be present or absent on the target system.' 49 | }, 50 | name: { 51 | type: 'String', 52 | desc: 'Name of the login path you want to manage.', 53 | behaviour: :namevar 54 | }, 55 | owner: { 56 | type: 'String', 57 | desc: 'The user to whom the logon path should belong.', 58 | behaviour: :namevar, 59 | default: 'root' 60 | }, 61 | host: { 62 | type: 'Optional[String]', 63 | desc: 'Host name to be entered into the login path.' 64 | }, 65 | user: { 66 | type: 'Optional[String]', 67 | desc: 'Username to be entered into the login path.' 68 | }, 69 | password: { 70 | type: 'Optional[Sensitive[String[1]]]', 71 | desc: 'Password to be entered into login path' 72 | }, 73 | socket: { 74 | type: 'Optional[String]', 75 | desc: 'Socket path to be entered into login path' 76 | }, 77 | port: { 78 | type: 'Optional[Integer[0,65535]]', 79 | desc: 'Port number to be entered into login path.' 80 | } 81 | }, 82 | ) 83 | -------------------------------------------------------------------------------- /lib/puppet/type/mysql_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Puppet::Type.newtype(:mysql_plugin) do 4 | @doc = <<-PUPPET 5 | @summary Manage MySQL plugins. 6 | 7 | @example 8 | mysql_plugin { 'some_plugin': 9 | soname => 'some_pluginlib.so', 10 | } 11 | 12 | PUPPET 13 | 14 | ensurable 15 | 16 | autorequire(:file) { '/root/.my.cnf' } 17 | 18 | newparam(:name, namevar: true) do 19 | desc 'The name of the MySQL plugin to manage.' 20 | end 21 | 22 | newproperty(:soname) do 23 | desc 'The name of the library' 24 | newvalue(%r{^\w+\.\w+$}) 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/puppet/type/mysql_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This has to be a separate type to enable collecting 4 | Puppet::Type.newtype(:mysql_user) do 5 | @doc = <<-PUPPET 6 | @summary Manage a MySQL user. This includes management of users password as well as privileges. 7 | PUPPET 8 | 9 | ensurable 10 | 11 | autorequire(:file) { '/root/.my.cnf' } 12 | autorequire(:class) { 'mysql::server' } 13 | 14 | newparam(:name, namevar: true) do 15 | desc "The name of the user. This uses the 'username@hostname' or username@hostname." 16 | validate do |value| 17 | # http://dev.mysql.com/doc/refman/5.5/en/identifiers.html 18 | # If at least one special char is used, string must be quoted 19 | # http://stackoverflow.com/questions/8055727/negating-a-backreference-in-regular-expressions/8057827#8057827 20 | mysql_version = Facter.value(:mysql_version) 21 | # rubocop:disable Lint/AssignmentInCondition 22 | # rubocop:disable Lint/UselessAssignment 23 | if matches = %r{^(['`"])((?:(?!\1).)*)\1@([\w%.:\-/]+)$}.match(value) 24 | user_part = matches[2] 25 | host_part = matches[3] 26 | elsif matches = %r{^([0-9a-zA-Z$_]*)@([\w%.:\-/]+)$}.match(value) || matches = %r{^((?!['`"]).*[^0-9a-zA-Z$_].*)@(.+)$}.match(value) 27 | user_part = matches[1] 28 | host_part = matches[2] 29 | else 30 | raise ArgumentError, _('Invalid database user %{user}.') % { user: value } 31 | end 32 | # rubocop:enable Lint/AssignmentInCondition 33 | # rubocop:enable Lint/UselessAssignment 34 | unless mysql_version.nil? 35 | raise(ArgumentError, _('MySQL usernames are limited to a maximum of 16 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '5.7.8').negative? && user_part.size > 16 36 | raise(ArgumentError, _('MySQL usernames are limited to a maximum of 32 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0').negative? && user_part.size > 32 37 | raise(ArgumentError, _('MySQL usernames are limited to a maximum of 80 characters.')) if Puppet::Util::Package.versioncmp(mysql_version, '10.0.0').positive? && user_part.size > 80 38 | end 39 | end 40 | 41 | munge do |value| 42 | matches = %r{^((['`"]?).*\2)@(.+)$}.match(value) 43 | "#{matches[1]}@#{matches[3].downcase}" 44 | end 45 | end 46 | 47 | newproperty(:password_hash) do 48 | desc 'The password hash of the user. Use mysql::password() for creating such a hash.' 49 | newvalue(%r{\w*}) 50 | 51 | def change_to_s(currentvalue, _newvalue) 52 | (currentvalue == :absent) ? 'created password' : 'changed password' 53 | end 54 | 55 | # rubocop:disable Naming/PredicateName 56 | def is_to_s(_currentvalue) 57 | '[old password hash redacted]' 58 | end 59 | # rubocop:enable Naming/PredicateName 60 | 61 | def should_to_s(_newvalue) 62 | '[new password hash redacted]' 63 | end 64 | end 65 | 66 | newproperty(:plugin) do 67 | desc 'The authentication plugin of the user.' 68 | newvalue(%r{\w+}) 69 | end 70 | 71 | newproperty(:max_user_connections) do 72 | desc 'Max concurrent connections for the user. 0 means no (or global) limit.' 73 | newvalue(%r{\d+}) 74 | end 75 | 76 | newproperty(:max_connections_per_hour) do 77 | desc 'Max connections per hour for the user. 0 means no (or global) limit.' 78 | newvalue(%r{\d+}) 79 | end 80 | 81 | newproperty(:max_queries_per_hour) do 82 | desc 'Max queries per hour for the user. 0 means no (or global) limit.' 83 | newvalue(%r{\d+}) 84 | end 85 | 86 | newproperty(:max_updates_per_hour) do 87 | desc 'Max updates per hour for the user. 0 means no (or global) limit.' 88 | newvalue(%r{\d+}) 89 | end 90 | 91 | newproperty(:tls_options, array_matching: :all) do 92 | desc 'Options to that set the TLS-related REQUIRE attributes for the user.' 93 | validate do |value| 94 | value = [value] unless value.is_a?(Array) 95 | if value.include?('NONE') || value.include?('SSL') || value.include?('X509') 96 | raise(ArgumentError, _('`tls_options` `property`: The values NONE, SSL and X509 cannot be used with other options, you may only pick one of them.')) if value.length > 1 97 | else 98 | value.each do |opt| 99 | o = opt.match(%r{^(CIPHER|ISSUER|SUBJECT)}i) 100 | raise(ArgumentError, _('Invalid tls option %{option}.') % { option: o }) unless o 101 | end 102 | end 103 | end 104 | def insync?(insync) 105 | # The current value may be nil and we don't 106 | # want to call sort on it so make sure we have arrays 107 | if insync.is_a?(Array) && @should.is_a?(Array) 108 | insync.sort == @should.sort 109 | else 110 | insync == @should 111 | end 112 | end 113 | end 114 | end 115 | -------------------------------------------------------------------------------- /manifests/backup/mysqlbackup.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Manage the mysqlbackup client. 3 | # 4 | # @api private 5 | # 6 | class mysql::backup::mysqlbackup ( 7 | String $backupuser = '', 8 | Variant[String, Sensitive[String]] $backuppassword = '', 9 | String[1] $maxallowedpacket = '1M', 10 | String $backupdir = '', 11 | String[1] $backupdirmode = '0700', 12 | String[1] $backupdirowner = 'root', 13 | String[1] $backupdirgroup = $mysql::params::root_group, 14 | Boolean $backupcompress = true, 15 | Variant[Integer, String[1]] $backuprotate = 30, 16 | String $backupmethod = '', 17 | Optional[String[1]] $backup_success_file_path = undef, 18 | Boolean $ignore_events = true, 19 | Boolean $delete_before_dump = false, 20 | Array[String[1]] $backupdatabases = [], 21 | Boolean $file_per_database = false, 22 | Boolean $include_triggers = true, 23 | Boolean $include_routines = false, 24 | Enum['present', 'absent'] $ensure = 'present', 25 | Variant[Array[String[1]], Array[Integer]] $time = ['23', '5'], 26 | Variant[Boolean, String[1], Array[String[1]]] $prescript = false, 27 | Variant[Boolean, String[1], Array[String[1]]] $postscript = false, 28 | String[1] $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', 29 | Array[String[1]] $optional_args = [], 30 | Boolean $incremental_backups = false, 31 | Boolean $install_cron = true, 32 | Optional[String[1]] $compression_command = undef, 33 | Optional[String[1]] $compression_extension = undef, 34 | Optional[String[1]] $backupmethod_package = undef, 35 | ) inherits mysql::params { 36 | $backuppassword_unsensitive = if $backuppassword =~ Sensitive { 37 | $backuppassword.unwrap 38 | } else { 39 | $backuppassword 40 | } 41 | mysql_user { "${backupuser}@localhost": 42 | ensure => $ensure, 43 | password_hash => Deferred('mysql::password', [$backuppassword]), 44 | require => Class['mysql::server::root_password'], 45 | } 46 | 47 | package { 'meb': 48 | ensure => $ensure, 49 | } 50 | 51 | # http://dev.mysql.com/doc/mysql-enterprise-backup/3.11/en/mysqlbackup.privileges.html 52 | mysql_grant { "${backupuser}@localhost/*.*": 53 | ensure => $ensure, 54 | user => "${backupuser}@localhost", 55 | table => '*.*', 56 | privileges => ['RELOAD', 'SUPER', 'REPLICATION CLIENT'], 57 | require => Mysql_user["${backupuser}@localhost"], 58 | } 59 | 60 | mysql_grant { "${backupuser}@localhost/mysql.backup_progress": 61 | ensure => $ensure, 62 | user => "${backupuser}@localhost", 63 | table => 'mysql.backup_progress', 64 | privileges => ['CREATE', 'INSERT', 'DROP', 'UPDATE'], 65 | require => Mysql_user["${backupuser}@localhost"], 66 | } 67 | 68 | mysql_grant { "${backupuser}@localhost/mysql.backup_history": 69 | ensure => $ensure, 70 | user => "${backupuser}@localhost", 71 | table => 'mysql.backup_history', 72 | privileges => ['CREATE', 'INSERT', 'SELECT', 'DROP', 'UPDATE'], 73 | require => Mysql_user["${backupuser}@localhost"], 74 | } 75 | 76 | if $install_cron { 77 | if $facts['os']['family'] == 'RedHat' { 78 | stdlib::ensure_packages('cronie') 79 | } elsif $facts['os']['family'] != 'FreeBSD' { 80 | stdlib::ensure_packages('cron') 81 | } 82 | } 83 | 84 | cron { 'mysqlbackup-weekly': 85 | ensure => $ensure, 86 | command => 'mysqlbackup backup', 87 | user => 'root', 88 | hour => $time[0], 89 | minute => $time[1], 90 | weekday => '0', 91 | require => Package['meb'], 92 | } 93 | 94 | cron { 'mysqlbackup-daily': 95 | ensure => $ensure, 96 | command => 'mysqlbackup --incremental backup', 97 | user => 'root', 98 | hour => $time[0], 99 | minute => $time[1], 100 | weekday => '1-6', 101 | require => Package['meb'], 102 | } 103 | 104 | $default_options = { 105 | 'mysqlbackup' => { 106 | 'backup-dir' => $backupdir, 107 | 'with-timestamp' => true, 108 | 'incremental_base' => 'history:last_backup', 109 | 'incremental_backup_dir' => $backupdir, 110 | 'user' => $backupuser, 111 | 'password' => Deferred('mysql::password', [$backuppassword_unsensitive]), 112 | }, 113 | } 114 | $options = mysql::normalise_and_deepmerge($default_options, $mysql::server::override_options) 115 | 116 | file { 'mysqlbackup-config-file': 117 | path => '/etc/mysql/conf.d/meb.cnf', 118 | content => stdlib::deferrable_epp('mysql/meb.cnf.epp', { 'options' => $options }), 119 | mode => '0600', 120 | } 121 | 122 | file { $backupdir: 123 | ensure => 'directory', 124 | mode => $backupdirmode, 125 | owner => $backupdirowner, 126 | group => $backupdirgroup, 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /manifests/backup/mysqldump.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # "Provider" for mysqldump 3 | # @api private 4 | # 5 | class mysql::backup::mysqldump ( 6 | String $backupuser = '', 7 | Variant[String, Sensitive[String]] $backuppassword = '', 8 | String $backupdir = '', 9 | String[1] $maxallowedpacket = '1M', 10 | String[1] $backupdirmode = '0700', 11 | String[1] $backupdirowner = 'root', 12 | String[1] $backupdirgroup = $mysql::params::root_group, 13 | Boolean $backupcompress = true, 14 | Variant[Integer, String[1]] $backuprotate = 30, 15 | String[1] $backupmethod = 'mysqldump', 16 | Optional[String[1]] $backup_success_file_path = undef, 17 | Boolean $ignore_events = true, 18 | Boolean $delete_before_dump = false, 19 | Array[String[1]] $backupdatabases = [], 20 | Boolean $file_per_database = false, 21 | Boolean $include_triggers = false, 22 | Boolean $include_routines = false, 23 | Enum['present', 'absent'] $ensure = 'present', 24 | Variant[Array[String[1]], Array[Integer]] $time = ['23', '5'], 25 | Variant[Boolean, String[1], Array[String[1]]] $prescript = false, 26 | Variant[Boolean, String[1], Array[String[1]]] $postscript = false, 27 | String[1] $execpath = '/usr/bin:/usr/sbin:/bin:/sbin', 28 | Array[String[1]] $optional_args = [], 29 | String[1] $mysqlbackupdir_ensure = 'directory', 30 | Optional[String[1]] $mysqlbackupdir_target = undef, 31 | Boolean $incremental_backups = false, 32 | Boolean $install_cron = true, 33 | String[1] $compression_command = 'bzcat -zc', 34 | String[1] $compression_extension = '.bz2', 35 | Optional[String[1]] $backupmethod_package = undef, 36 | Array[String] $excludedatabases = [], 37 | ) inherits mysql::params { 38 | $backuppassword_unsensitive = if $backuppassword =~ Sensitive { 39 | $backuppassword.unwrap 40 | } else { 41 | $backuppassword 42 | } 43 | 44 | unless $facts['os']['family'] == 'FreeBSD' { 45 | if $backupcompress and $compression_command == 'bzcat -zc' { 46 | stdlib::ensure_packages(['bzip2']) 47 | Package['bzip2'] -> File['mysqlbackup.sh'] 48 | } 49 | } 50 | 51 | mysql_user { "${backupuser}@localhost": 52 | ensure => $ensure, 53 | password_hash => Deferred('mysql::password', [$backuppassword]), 54 | require => Class['mysql::server::root_password'], 55 | } 56 | 57 | if $include_triggers { 58 | $privs = ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS', 'TRIGGER'] 59 | } else { 60 | $privs = ['SELECT', 'RELOAD', 'LOCK TABLES', 'SHOW VIEW', 'PROCESS'] 61 | } 62 | 63 | mysql_grant { "${backupuser}@localhost/*.*": 64 | ensure => $ensure, 65 | user => "${backupuser}@localhost", 66 | table => '*.*', 67 | privileges => $privs, 68 | require => Mysql_user["${backupuser}@localhost"], 69 | } 70 | 71 | if $install_cron { 72 | if $facts['os']['family'] == 'RedHat' { 73 | stdlib::ensure_packages('cronie') 74 | } elsif $facts['os']['family'] != 'FreeBSD' { 75 | stdlib::ensure_packages('cron') 76 | } 77 | } 78 | 79 | cron { 'mysql-backup': 80 | ensure => $ensure, 81 | command => '/usr/local/sbin/mysqlbackup.sh', 82 | user => 'root', 83 | hour => $time[0], 84 | minute => $time[1], 85 | monthday => $time[2], 86 | month => $time[3], 87 | weekday => $time[4], 88 | require => File['mysqlbackup.sh'], 89 | } 90 | 91 | $parameters = { 92 | 'backupuser'=> $backupuser, 93 | 'backuppassword_unsensitive'=> $backuppassword_unsensitive, 94 | 'maxallowedpacket'=> $maxallowedpacket, 95 | 'backupdir'=> $backupdir, 96 | 'backuprotate'=> $backuprotate, 97 | 'prescript'=> $prescript, 98 | 'ignore_events'=> $ignore_events, 99 | 'backupdatabases'=> $backupdatabases, 100 | 'include_triggers'=> $include_triggers, 101 | 'optional_args'=> $optional_args, 102 | 'execpath'=> $execpath, 103 | 'delete_before_dump'=> $delete_before_dump, 104 | 'excludedatabases'=> $excludedatabases, 105 | 'backupmethod'=> $backupmethod, 106 | 'backupcompress'=> $backupcompress, 107 | 'compression_command'=> $compression_command, 108 | 'compression_extension'=> $compression_extension, 109 | 'backup_success_file_path'=> $backup_success_file_path, 110 | 'postscript'=> $postscript, 111 | 'file_per_database'=> $file_per_database, 112 | 'include_routines' => $include_routines, 113 | } 114 | 115 | # TODO: use EPP instead of ERB, as EPP can handle Data of Type Sensitive without further ado 116 | file { 'mysqlbackup.sh': 117 | ensure => $ensure, 118 | path => '/usr/local/sbin/mysqlbackup.sh', 119 | mode => '0700', 120 | owner => 'root', 121 | group => $mysql::params::root_group, 122 | content => epp('mysql/mysqlbackup.sh.epp',$parameters), 123 | } 124 | 125 | if $mysqlbackupdir_target { 126 | file { $backupdir: 127 | ensure => $mysqlbackupdir_ensure, 128 | target => $mysqlbackupdir_target, 129 | mode => $backupdirmode, 130 | owner => $backupdirowner, 131 | group => $backupdirgroup, 132 | } 133 | } else { 134 | file { $backupdir: 135 | ensure => $mysqlbackupdir_ensure, 136 | mode => $backupdirmode, 137 | owner => $backupdirowner, 138 | group => $backupdirgroup, 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /manifests/bindings/client_dev.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing client development bindings 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::client_dev { 7 | if $mysql::bindings::client_dev_package_name { 8 | package { 'mysql-client_dev': 9 | ensure => $mysql::bindings::client_dev_package_ensure, 10 | install_options => $mysql::bindings::install_options, 11 | name => $mysql::bindings::client_dev_package_name, 12 | provider => $mysql::bindings::client_dev_package_provider, 13 | } 14 | } else { 15 | warning("No MySQL client development package configured for ${::facts['os']['family']}.") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /manifests/bindings/daemon_dev.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing daemon development bindings 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::daemon_dev { 7 | if $mysql::bindings::daemon_dev_package_name { 8 | package { 'mysql-daemon_dev': 9 | ensure => $mysql::bindings::daemon_dev_package_ensure, 10 | install_options => $mysql::bindings::install_options, 11 | name => $mysql::bindings::daemon_dev_package_name, 12 | provider => $mysql::bindings::daemon_dev_package_provider, 13 | } 14 | } else { 15 | warning("No MySQL daemon development package configured for ${::facts['os']['family']}.") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /manifests/bindings/java.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing java language bindings. 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::java { 7 | package { 'mysql-connector-java': 8 | ensure => $mysql::bindings::java_package_ensure, 9 | install_options => $mysql::bindings::install_options, 10 | name => $mysql::bindings::java_package_name, 11 | provider => $mysql::bindings::java_package_provider, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /manifests/bindings/perl.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing perl language bindings. 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::perl { 7 | package { 'perl_mysql': 8 | ensure => $mysql::bindings::perl_package_ensure, 9 | install_options => $mysql::bindings::install_options, 10 | name => $mysql::bindings::perl_package_name, 11 | provider => $mysql::bindings::perl_package_provider, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /manifests/bindings/php.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing php language bindings 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::php { 7 | package { 'php-mysql': 8 | ensure => $mysql::bindings::php_package_ensure, 9 | install_options => $mysql::bindings::install_options, 10 | name => $mysql::bindings::php_package_name, 11 | provider => $mysql::bindings::php_package_provider, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /manifests/bindings/python.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing python language bindings 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::python { 7 | package { 'python-mysqldb': 8 | ensure => $mysql::bindings::python_package_ensure, 9 | install_options => $mysql::bindings::install_options, 10 | name => $mysql::bindings::python_package_name, 11 | provider => $mysql::bindings::python_package_provider, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /manifests/bindings/ruby.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for installing ruby language bindings 3 | # 4 | # @api private 5 | # 6 | class mysql::bindings::ruby { 7 | package { 'ruby_mysql': 8 | ensure => $mysql::bindings::ruby_package_ensure, 9 | install_options => $mysql::bindings::install_options, 10 | name => $mysql::bindings::ruby_package_name, 11 | provider => $mysql::bindings::ruby_package_provider, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /manifests/client.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Installs and configures the MySQL client. 3 | # 4 | # @example Install the MySQL client 5 | # class {'::mysql::client': 6 | # package_name => 'mysql-client', 7 | # package_ensure => 'present', 8 | # bindings_enable => true, 9 | # } 10 | # 11 | # @param bindings_enable 12 | # Whether to automatically install all bindings. Valid values are `true`, `false`. Default to `false`. 13 | # @param install_options 14 | # Array of install options for managed package resources. You must pass the appropriate options for the package manager. 15 | # @param package_ensure 16 | # Whether the MySQL package should be present, absent, or a specific version. Valid values are 'present', 'absent', or 'x.y.z'. 17 | # @param package_manage 18 | # Whether to manage the MySQL client package. Defaults to `true`. 19 | # @param package_name 20 | # The name of the MySQL client package to install. 21 | # @param package_provider 22 | # Specify the provider of the package. Optional. Valid value is a String. 23 | # @param package_source 24 | # Specify the path to the package source. Optional. Valid value is a String 25 | # 26 | class mysql::client ( 27 | Boolean $bindings_enable = $mysql::params::bindings_enable, 28 | Optional[Array[String[1]]] $install_options = undef, 29 | Variant[Enum['present','absent'], Pattern[/(\d+)[\.](\d+)[\.](\d+)/]] $package_ensure = $mysql::params::client_package_ensure, 30 | Boolean $package_manage = $mysql::params::client_package_manage, 31 | String[1] $package_name = $mysql::params::client_package_name, 32 | Optional[String[1]] $package_provider = undef, 33 | Optional[String[1]] $package_source = undef, 34 | ) inherits mysql::params { 35 | include 'mysql::client::install' 36 | 37 | if $bindings_enable { 38 | class { 'mysql::bindings': 39 | java_enable => true, 40 | perl_enable => true, 41 | php_enable => true, 42 | python_enable => true, 43 | ruby_enable => true, 44 | } 45 | } 46 | 47 | # Anchor pattern workaround to avoid resources of mysql::client::install to 48 | # "float off" outside mysql::client 49 | anchor { 'mysql::client::start': } 50 | -> Class['mysql::client::install'] 51 | -> anchor { 'mysql::client::end': } 52 | } 53 | -------------------------------------------------------------------------------- /manifests/client/install.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for MySQL client install. 3 | # 4 | # @api private 5 | # 6 | class mysql::client::install { 7 | if $mysql::client::package_manage { 8 | package { 'mysql_client': 9 | ensure => $mysql::client::package_ensure, 10 | install_options => $mysql::client::install_options, 11 | name => $mysql::client::package_name, 12 | provider => $mysql::client::package_provider, 13 | source => $mysql::client::package_source, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /manifests/db.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Create and configure a MySQL database. 3 | # 4 | # @example Create a database 5 | # mysql::db { 'mydb': 6 | # user => 'myuser', 7 | # password => 'mypass', 8 | # host => 'localhost', 9 | # grant => ['SELECT', 'UPDATE'], 10 | # } 11 | # 12 | # @param name 13 | # The name of the database to create. Database names must: 14 | # * not be longer than 64 characters. 15 | # * not contain '/' '\' or '.' characters. 16 | # * not contain characters that are not permitted in file names. 17 | # * not end with space characters. 18 | # @param user 19 | # The user for the database you're creating. 20 | # @param password 21 | # The password for $user for the database you're creating. 22 | # @param tls_options 23 | # The tls_options for $user for the database you're creating. 24 | # @param dbname 25 | # The name of the database to create. 26 | # @param charset 27 | # The character set for the database. Must have the same value as collate to avoid corrective changes. See https://dev.mysql.com/doc/refman/8.0/en/charset-mysql.html for charset and collation pairs. 28 | # @param collate 29 | # The collation for the database. Must have the same value as charset to avoid corrective changes. See https://dev.mysql.com/doc/refman/8.0/en/charset-mysql.html for charset and collation pairs. 30 | # @param host 31 | # The host to use as part of user@host for grants. 32 | # @param grant 33 | # The privileges to be granted for user@host on the database. 34 | # @param grant_options 35 | # The grant_options for the grant for user@host on the database. 36 | # @param sql 37 | # The path to the sqlfile you want to execute. This can be an array containing one or more file paths. 38 | # @param enforce_sql 39 | # Specifies whether executing the sqlfiles should happen on every run. If set to false, sqlfiles only run once. 40 | # @param ensure 41 | # Specifies whether to create the database. Valid values are 'present', 'absent'. Defaults to 'present'. 42 | # @param import_timeout 43 | # Timeout, in seconds, for loading the sqlfiles. Defaults to 300. 44 | # @param import_cat_cmd 45 | # Command to read the sqlfile for importing the database. Useful for compressed sqlfiles. For example, you can use 'zcat' for .gz files. 46 | # @param mysql_exec_path 47 | # Specify the path in which mysql has been installed if done in the non-standard bin/sbin path. 48 | # 49 | define mysql::db ( 50 | String[1] $user, 51 | Variant[String, Sensitive[String]] $password, 52 | Optional[Array[String[1]]] $tls_options = undef, 53 | String $dbname = $name, 54 | String[1] $charset = 'utf8mb3', 55 | String[1] $collate = 'utf8mb3_general_ci', 56 | String[1] $host = 'localhost', 57 | Variant[String[1], Array[String[1]]] $grant = 'ALL', 58 | Optional[Variant[String[1], Array[String[1]]]] $grant_options = undef, 59 | Optional[Array] $sql = undef, 60 | Boolean $enforce_sql = false, 61 | Enum['absent', 'present'] $ensure = 'present', 62 | Integer $import_timeout = 300, 63 | Enum['cat', 'zcat', 'bzcat'] $import_cat_cmd = 'cat', 64 | Optional[String] $mysql_exec_path = undef, 65 | ) { 66 | include 'mysql::client' 67 | 68 | # Ensure that the database name is valid. 69 | if $dbname !~ /^[^\/?%*:|\""<>.\s;]{1,64}$/ { 70 | $message = "The database name '${dbname}' is invalid. Values must: 71 | * not be longer than 64 characters. 72 | * not contain '/' '\\' or '.' characters. 73 | * not contain characters that are not permitted in file names. 74 | * not end with space characters." 75 | fail($message) 76 | } 77 | 78 | # Ensure that the sql files passed are valid file paths. 79 | if $sql { 80 | $sql.each | $sqlfile | { 81 | if $sqlfile !~ /^\/(?:.[.A-Za-z0-9_-]+\/?+)+(?:\.[.A-Za-z0-9]+)+$/ { 82 | $message = "The file '${sqlfile}' is invalid. A valid file path is expected." 83 | fail($message) 84 | } 85 | } 86 | } 87 | 88 | if ($mysql_exec_path) { 89 | $_mysql_exec_path = $mysql_exec_path 90 | } else { 91 | $_mysql_exec_path = $mysql::params::exec_path 92 | } 93 | 94 | $db_resource = { 95 | ensure => $ensure, 96 | charset => $charset, 97 | collate => $collate, 98 | provider => 'mysql', 99 | require => [Class['mysql::client']], 100 | } 101 | ensure_resource('mysql_database', $dbname, $db_resource) 102 | 103 | $user_resource = { 104 | ensure => $ensure, 105 | password_hash => Deferred('mysql::password', [$password]), 106 | tls_options => $tls_options, 107 | } 108 | ensure_resource('mysql_user', "${user}@${host}", $user_resource) 109 | 110 | if $ensure == 'present' { 111 | $table = "${dbname}.*" 112 | 113 | mysql_grant { "${user}@${host}/${table}": 114 | privileges => $grant, 115 | provider => 'mysql', 116 | user => "${user}@${host}", 117 | table => $table, 118 | options => $grant_options, 119 | require => [ 120 | Mysql_database[$dbname], 121 | Mysql_user["${user}@${host}"], 122 | ], 123 | } 124 | 125 | if $sql { 126 | exec { "${dbname}-import": 127 | command => "${import_cat_cmd} ${shell_join($sql)} | mysql ${dbname}", 128 | logoutput => true, 129 | environment => "HOME=${facts['root_home']}", 130 | refreshonly => ! $enforce_sql, 131 | path => "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:${_mysql_exec_path}", 132 | require => Mysql_grant["${user}@${host}/${table}"], 133 | subscribe => Mysql_database[$dbname], 134 | timeout => $import_timeout, 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /manifests/server/account_security.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for ensuring localhost accounts do not exist 3 | # 4 | # @api private 5 | # 6 | class mysql::server::account_security { 7 | mysql_user { 8 | ['root@127.0.0.1', 9 | 'root@::1', 10 | '@localhost', 11 | '@%']: 12 | ensure => 'absent', 13 | require => Anchor['mysql::server::end'], 14 | } 15 | if ($facts['networking']['fqdn'] != 'localhost.localdomain') { 16 | mysql_user { 17 | ['root@localhost.localdomain', 18 | '@localhost.localdomain']: 19 | ensure => 'absent', 20 | require => Anchor['mysql::server::end'], 21 | } 22 | } 23 | if ($facts['networking']['fqdn'] and $facts['networking']['fqdn'] != 'localhost') { 24 | mysql_user { 25 | ["root@${facts['networking']['fqdn']}", 26 | "@${facts['networking']['fqdn']}"]: 27 | ensure => 'absent', 28 | require => Anchor['mysql::server::end'], 29 | } 30 | } 31 | if ($facts['networking']['fqdn'] != $facts['networking']['hostname']) { 32 | if ($facts['networking']['hostname'] != 'localhost') { 33 | mysql_user { ["root@${facts['networking']['hostname']}", "@${facts['networking']['hostname']}"]: 34 | ensure => 'absent', 35 | require => Anchor['mysql::server::end'], 36 | } 37 | } 38 | } 39 | mysql_database { 'test': 40 | ensure => 'absent', 41 | require => Anchor['mysql::server::end'], 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /manifests/server/config.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for MySQL server configuration. 3 | # 4 | # @api private 5 | # 6 | class mysql::server::config { 7 | $options = $mysql::server::_options 8 | $includedir = $mysql::server::includedir 9 | $managed_dirs = $mysql::server::managed_dirs 10 | 11 | File { 12 | owner => 'root', 13 | group => $mysql::server::root_group, 14 | mode => '0400', 15 | } 16 | 17 | if $includedir and $includedir != '' { 18 | file { $includedir: 19 | ensure => directory, 20 | mode => '0755', 21 | recurse => $mysql::server::purge_conf_dir, 22 | purge => $mysql::server::purge_conf_dir, 23 | } 24 | 25 | # on some systems this is /etc/my.cnf.d, while Debian has /etc/mysql/conf.d and FreeBSD something in /usr/local. For the latter systems, 26 | # managing this basedir is also required, to have it available before the package is installed. 27 | $includeparentdir = dirname($includedir) 28 | if $includeparentdir != '/' and $includeparentdir != '/etc' { 29 | file { $includeparentdir: 30 | ensure => directory, 31 | mode => '0755', 32 | } 33 | } 34 | } 35 | 36 | #Debian: Creating world readable directories before installing. 37 | case $facts['os']['family'] { 38 | 'Debian': { 39 | if $managed_dirs { 40 | $managed_dirs.each | $entry | { 41 | $dir = $options['mysqld']["${entry}"] 42 | 43 | if ( $dir and $dir != '/usr' and $dir != '/tmp' ) { 44 | $clean_dir = stdlib::shell_escape($dir) 45 | $clean_package_name = stdlib::shell_escape($mysql::server::package_name) 46 | 47 | exec { "${entry}-managed_dir-mkdir": 48 | command => ['/bin/mkdir', '-p', $clean_dir], 49 | unless => [['/usr/bin/dpkg', '-s', $clean_package_name]], 50 | notify => Exec["${entry}-managed_dir-chmod"], 51 | } 52 | 53 | exec { "${entry}-managed_dir-chmod": 54 | command => ['/bin/chmod', '777', $clean_dir], 55 | refreshonly => true, 56 | } 57 | } 58 | } 59 | } 60 | } 61 | default: {} 62 | } 63 | 64 | $parameters= { 65 | 'options' => $options, 66 | 'includedir' => $includedir, 67 | } 68 | 69 | if $mysql::server::manage_config_file { 70 | file { 'mysql-config-file': 71 | path => $mysql::server::config_file, 72 | content => epp('mysql/my.cnf.epp', $parameters), 73 | mode => $mysql::server::config_file_mode, 74 | owner => $mysql::server::mycnf_owner, 75 | group => $mysql::server::mycnf_group, 76 | selinux_ignore_defaults => true, 77 | } 78 | 79 | # on mariadb systems, $includedir is not defined, but /etc/my.cnf.d has 80 | # to be managed to place the server.cnf there 81 | $configparentdir = dirname($mysql::server::config_file) 82 | # Before setting $configparentdir we first check to make sure that it's value is valid 83 | if $configparentdir != '/' and $configparentdir != '/etc' { 84 | # We then check that the value of $includedir is either undefined or that different from $configparentdir 85 | # We first check that it is undefined due to dirname throwing an error when given undef/empty strings 86 | if $includedir == undef or $includedir == '' or 87 | ($configparentdir != $includedir and $configparentdir != dirname($includedir)) { 88 | file { $configparentdir: 89 | ensure => directory, 90 | mode => '0755', 91 | } 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /manifests/server/install.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for managing MySQL package. 3 | # 4 | # @api private 5 | # 6 | class mysql::server::install { 7 | if $mysql::server::package_manage { 8 | package { 'mysql-server': 9 | ensure => $mysql::server::package_ensure, 10 | install_options => $mysql::server::install_options, 11 | name => $mysql::server::package_name, 12 | provider => $mysql::server::package_provider, 13 | source => $mysql::server::package_source, 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /manifests/server/installdb.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Builds initial databases on installation. 3 | # 4 | # @api private 5 | # 6 | class mysql::server::installdb { 7 | $options = $mysql::server::_options 8 | 9 | if $mysql::server::package_manage { 10 | # Build the initial databases. 11 | $mysqluser = $mysql::server::_options['mysqld']['user'] 12 | $datadir = $mysql::server::_options['mysqld']['datadir'] 13 | $basedir = $mysql::server::_options['mysqld']['basedir'] 14 | $config_file = $mysql::server::config_file 15 | $log_error = $mysql::server::_options['mysqld']['log-error'] 16 | $log_dir = '/var/log/mysql' 17 | 18 | if $mysql::server::manage_config_file and $config_file != $mysql::params::config_file { 19 | $_config_file=$config_file 20 | } else { 21 | $_config_file=undef 22 | } 23 | 24 | if $options['mysqld']['log-error'] { 25 | file { $options['mysqld']['log-error']: 26 | ensure => file, 27 | owner => $mysqluser, 28 | group => $mysql::server::mysql_group, 29 | mode => 'u+rw', 30 | before => Mysql_datadir[$datadir], 31 | } 32 | } 33 | 34 | file { $log_dir: 35 | ensure => 'directory', 36 | owner => $mysqluser, 37 | group => $mysql::server::mysql_group, 38 | } 39 | 40 | mysql_datadir { $datadir: 41 | ensure => 'present', 42 | datadir => $datadir, 43 | basedir => $basedir, 44 | user => $mysqluser, 45 | log_error => $log_error, 46 | defaults_extra_file => $_config_file, 47 | } 48 | 49 | if $mysql::server::restart { 50 | Mysql_datadir[$datadir] { 51 | notify => Class['mysql::server::service'], 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /manifests/server/managed_dirs.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Binary log configuration requires the mysql user to be present. This must be done after package install. 3 | # 4 | # @api private 5 | # 6 | class mysql::server::managed_dirs { 7 | $options = $mysql::server::_options 8 | $includedir = $mysql::server::includedir 9 | $managed_dirs = $mysql::server::managed_dirs 10 | 11 | #Debian: Fix permission on directories 12 | if $managed_dirs { 13 | $managed_dirs_path = $managed_dirs.map |$path| { $options['mysqld']["${path}"] } 14 | $managed_dirs.each | $entry | { 15 | $dir = $options['mysqld']["${entry}"] 16 | if ( $dir and $dir != '/usr' and $dir != '/tmp' ) { 17 | file { "${entry}-managed_dir": 18 | ensure => directory, 19 | path => $dir, 20 | mode => '0700', 21 | owner => $options['mysqld']['user'], 22 | group => $options['mysqld']['user'], 23 | } 24 | } 25 | } 26 | } else { 27 | $managed_dirs_path = [] 28 | } 29 | 30 | $logbin = pick($options['mysqld']['log-bin'], $options['mysqld']['log_bin'], false) 31 | 32 | if $logbin { 33 | $logbindir = dirname($logbin) 34 | 35 | #Stop puppet from managing directory if just a filename/prefix is specified or is not already managed 36 | if (!($logbindir == '.' or $logbindir in $managed_dirs_path)) { 37 | file { $logbindir: 38 | ensure => directory, 39 | mode => '0700', 40 | owner => $options['mysqld']['user'], 41 | group => $options['mysqld']['user'], 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /manifests/server/providers.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Convenience class to call each of the three providers with the corresponding hashes provided in mysql::server. 3 | # 4 | # @api private 5 | # 6 | class mysql::server::providers { 7 | create_resources('mysql_user', $mysql::server::users) 8 | create_resources('mysql_grant', $mysql::server::grants) 9 | create_resources('mysql_database', $mysql::server::databases) 10 | } 11 | -------------------------------------------------------------------------------- /manifests/server/root_password.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for managing the root password 3 | # 4 | # @api private 5 | # 6 | class mysql::server::root_password { 7 | if $mysql::server::root_password =~ Sensitive { 8 | $root_password = $mysql::server::root_password.unwrap 9 | } else { 10 | $root_password = $mysql::server::root_password 11 | } 12 | if $root_password == 'UNSET' { 13 | $root_password_set = false 14 | } else { 15 | $root_password_set = true 16 | } 17 | 18 | $options = $mysql::server::_options 19 | $login_file = $mysql::server::login_file 20 | 21 | # New installations of MySQL will configure a default random password for the root user 22 | # with an expiration. No actions can be performed until this password is changed. The 23 | # below exec will remove this default password. If the user has supplied a root 24 | # password it will be set further down with the mysql_user resource. 25 | exec { 'remove install pass': 26 | command => "mysqladmin -u root --password=\$(grep -o '[^ ]\\+\$' /.mysql_secret) password && (rm -f /.mysql_secret; exit 0) || (rm -f /.mysql_secret; exit 1)", 27 | onlyif => [['test', '-f' ,'/.mysql_secret']], 28 | path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin', 29 | } 30 | 31 | # manage root password if it is set 32 | if $mysql::server::create_root_user and $root_password_set { 33 | mysql_user { 'root@localhost': 34 | ensure => present, 35 | password_hash => Deferred('mysql::password', [$mysql::server::root_password]), 36 | require => Exec['remove install pass'], 37 | } 38 | } 39 | 40 | $parameters = { 41 | 'root_password_set' => $root_password_set, 42 | 'root_password' => $root_password, 43 | 'options' => $options, 44 | } 45 | 46 | if $mysql::server::create_root_my_cnf and $root_password_set { 47 | # TODO: use EPP instead of ERB, as EPP can handle Data of Type Sensitive without further ado 48 | file { "${facts['root_home']}/.my.cnf": 49 | content => epp('mysql/my.cnf.pass.epp',$parameters), 50 | owner => 'root', 51 | mode => '0600', 52 | } 53 | 54 | # show_diff was added with puppet 3.0 55 | if versioncmp($facts['puppetversion'], '3.0') >= 0 { 56 | File["${facts['root_home']}/.my.cnf"] { show_diff => false } 57 | } 58 | if $mysql::server::create_root_user { 59 | Mysql_user['root@localhost'] -> File["${facts['root_home']}/.my.cnf"] 60 | } 61 | } 62 | 63 | if $mysql::server::create_root_login_file and $root_password_set { 64 | file { "${facts['root_home']}/.mylogin.cnf": 65 | source => $login_file, 66 | owner => 'root', 67 | mode => '0600', 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /manifests/server/service.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Private class for managing the MySQL service 3 | # 4 | # @api private 5 | # 6 | class mysql::server::service { 7 | $options = $mysql::server::_options 8 | 9 | if $mysql::server::real_service_manage { 10 | if $mysql::server::real_service_enabled { 11 | $service_ensure = 'running' 12 | } else { 13 | $service_ensure = 'stopped' 14 | } 15 | } else { 16 | $service_ensure = undef 17 | } 18 | 19 | if $mysql::server::override_options and $mysql::server::override_options['mysqld'] 20 | and $mysql::server::override_options['mysqld']['user'] { 21 | $mysqluser = $mysql::server::override_options['mysqld']['user'] 22 | } else { 23 | $mysqluser = $options['mysqld']['user'] 24 | } 25 | 26 | if $mysql::server::real_service_manage { 27 | service { 'mysqld': 28 | ensure => $service_ensure, 29 | name => $mysql::server::service_name, 30 | enable => $mysql::server::real_service_enabled, 31 | provider => $mysql::server::service_provider, 32 | } 33 | 34 | # only establish ordering between service and package if 35 | # we're managing the package. 36 | if $mysql::server::package_manage { 37 | Service['mysqld'] { 38 | require => Package['mysql-server'], 39 | } 40 | } 41 | 42 | # only establish ordering between config file and service if 43 | # we're managing the config file. 44 | if $mysql::server::manage_config_file { 45 | if $mysql::server::reload_on_config_change { 46 | File['mysql-config-file'] ~> Service['mysqld'] 47 | } else { 48 | File['mysql-config-file'] -> Service['mysqld'] 49 | } 50 | } 51 | 52 | if $mysql::server::override_options and $mysql::server::override_options['mysqld'] 53 | and $mysql::server::override_options['mysqld']['socket'] { 54 | $mysqlsocket = $mysql::server::override_options['mysqld']['socket'] 55 | } else { 56 | $mysqlsocket = $options['mysqld']['socket'] 57 | } 58 | 59 | $test_command = ['test', '-S', stdlib::shell_escape($mysqlsocket)] 60 | if $service_ensure != 'stopped' { 61 | exec { 'wait_for_mysql_socket_to_open': 62 | command => $test_command, 63 | unless => [$test_command], 64 | tries => '3', 65 | try_sleep => '10', 66 | require => Service['mysqld'], 67 | path => '/bin:/usr/bin', 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetlabs-mysql", 3 | "version": "16.2.0", 4 | "author": "puppetlabs", 5 | "summary": "Installs, configures, and manages the MySQL service.", 6 | "license": "Apache-2.0", 7 | "source": "git://github.com/puppetlabs/puppetlabs-mysql", 8 | "project_page": "http://github.com/puppetlabs/puppetlabs-mysql", 9 | "issues_url": "https://github.com/puppetlabs/puppetlabs-mysql/issues", 10 | "dependencies": [ 11 | { 12 | "name": "puppetlabs/stdlib", 13 | "version_requirement": ">= 9.0.0 < 10.0.0" 14 | } 15 | ], 16 | "operatingsystem_support": [ 17 | { 18 | "operatingsystem": "RedHat", 19 | "operatingsystemrelease": [ 20 | "7", 21 | "8", 22 | "9" 23 | ] 24 | }, 25 | { 26 | "operatingsystem": "CentOS", 27 | "operatingsystemrelease": [ 28 | "7", 29 | "8" 30 | ] 31 | }, 32 | { 33 | "operatingsystem": "OracleLinux", 34 | "operatingsystemrelease": [ 35 | "7" 36 | ] 37 | }, 38 | { 39 | "operatingsystem": "Debian", 40 | "operatingsystemrelease": [ 41 | "10", 42 | "11", 43 | "12" 44 | ] 45 | }, 46 | { 47 | "operatingsystem": "Scientific", 48 | "operatingsystemrelease": [ 49 | "7" 50 | ] 51 | }, 52 | { 53 | "operatingsystem": "SLES", 54 | "operatingsystemrelease": [ 55 | "12", 56 | "15" 57 | ] 58 | }, 59 | { 60 | "operatingsystem": "Ubuntu", 61 | "operatingsystemrelease": [ 62 | "18.04", 63 | "20.04", 64 | "22.04" 65 | ] 66 | }, 67 | { 68 | "operatingsystem": "Rocky", 69 | "operatingsystemrelease": [ 70 | "8" 71 | ] 72 | }, 73 | { 74 | "operatingsystem": "AlmaLinux", 75 | "operatingsystemrelease": [ 76 | "8" 77 | ] 78 | } 79 | ], 80 | "requirements": [ 81 | { 82 | "name": "puppet", 83 | "version_requirement": ">= 7.0.0 < 9.0.0" 84 | } 85 | ], 86 | "description": "MySQL module", 87 | "template-url": "https://github.com/puppetlabs/pdk-templates#main", 88 | "template-ref": "tags/3.2.0.4-0-g5d17ec1", 89 | "pdk-version": "3.2.0" 90 | } 91 | -------------------------------------------------------------------------------- /pdk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignore: [] 3 | -------------------------------------------------------------------------------- /provision.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default: 3 | provisioner: docker_exp 4 | images: 5 | - litmusimage/centos:7 6 | vagrant: 7 | provisioner: vagrant 8 | images: 9 | - centos/7 10 | - generic/ubuntu1804 11 | docker_deb: 12 | provisioner: docker 13 | images: 14 | - litmusimage/debian:9 15 | - litmusimage/debian:10 16 | docker_ub_5: 17 | provisioner: docker 18 | images: 19 | - litmusimage/ubuntu:16.04 20 | - litmusimage/ubuntu:18.04 21 | docker_ub_6: 22 | provisioner: docker 23 | images: 24 | - litmusimage/ubuntu:16.04 25 | - litmusimage/ubuntu:18.04 26 | - litmusimage/ubuntu:20.04 27 | docker_el7: 28 | provisioner: docker_exp 29 | images: 30 | - litmusimage/centos:7 31 | - litmusimage/oraclelinux:7 32 | - litmusimage/scientificlinux:7 33 | docker_el8: 34 | provisioner: docker 35 | images: 36 | - litmusimage/centos:8 37 | release_checks_5: 38 | provisioner: abs 39 | images: 40 | - redhat-6-x86_64 41 | - redhat-7-x86_64 42 | - redhat-8-x86_64 43 | - centos-6-x86_64 44 | - centos-7-x86_64 45 | - centos-8-x86_64 46 | - oracle-5-x86_64 47 | - oracle-6-x86_64 48 | - oracle-7-x86_64 49 | - scientific-6-x86_64 50 | - scientific-7-x86_64 51 | - debian-9-x86_64 52 | - debian-10-x86_64 53 | - ubuntu-1604-x86_64 54 | - ubuntu-1804-x86_64 55 | release_checks_6: 56 | provisioner: abs 57 | images: 58 | - redhat-6-x86_64 59 | - redhat-7-x86_64 60 | - redhat-8-x86_64 61 | - centos-6-x86_64 62 | - centos-7-x86_64 63 | - centos-8-x86_64 64 | - oracle-5-x86_64 65 | - oracle-6-x86_64 66 | - oracle-7-x86_64 67 | - scientific-6-x86_64 68 | - scientific-7-x86_64 69 | - debian-9-x86_64 70 | - debian-10-x86_64 71 | - ubuntu-1604-x86_64 72 | - ubuntu-1804-x86_64 73 | - ubuntu-2004-x86_64 74 | release_checks_7: 75 | provisioner: abs 76 | images: 77 | - redhat-7-x86_64 78 | - redhat-8-x86_64 79 | - centos-7-x86_64 80 | - centos-8-x86_64 81 | - oracle-7-x86_64 82 | - scientific-7-x86_64 83 | - sles-12-x86_64 84 | - sles-15-x86_64 85 | - debian-9-x86_64 86 | - debian-10-x86_64 87 | - ubuntu-1804-x86_64 88 | - ubuntu-2004-x86_64 89 | -------------------------------------------------------------------------------- /spec/acceptance/00_mysql_server_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'mysql class' do 6 | describe 'advanced config' do 7 | let(:pp) do 8 | <<-MANIFEST 9 | class { 'mysql::server': 10 | manage_config_file => 'true', 11 | override_options => { 'mysqld' => { 'key_buffer_size' => '32M' }}, 12 | package_ensure => 'present', 13 | purge_conf_dir => 'true', 14 | remove_default_accounts => 'true', 15 | restart => 'true', 16 | root_group => 'root', 17 | root_password => 'test', 18 | service_enabled => 'true', 19 | service_manage => 'true', 20 | users => { 21 | 'someuser@localhost' => { 22 | ensure => 'present', 23 | max_connections_per_hour => '0', 24 | max_queries_per_hour => '0', 25 | max_updates_per_hour => '0', 26 | max_user_connections => '0', 27 | password_hash => '*F3A2A51A9B0F2BE2468926B4132313728C250DBF', 28 | }}, 29 | grants => { 30 | 'someuser@localhost/somedb.*' => { 31 | ensure => 'present', 32 | options => ['GRANT'], 33 | privileges => ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], 34 | table => 'somedb.*', 35 | user => 'someuser@localhost', 36 | }, 37 | }, 38 | databases => { 39 | 'somedb' => { 40 | ensure => 'present', 41 | charset => '#{charset}', 42 | collate => '#{charset}_general_ci', 43 | }, 44 | } 45 | } 46 | MANIFEST 47 | end 48 | 49 | it 'behaves idempotently' do 50 | idempotent_apply(pp) 51 | end 52 | 53 | describe 'override_options' do 54 | let(:pp) do 55 | <<-MANIFEST 56 | class { '::mysql::server': 57 | override_options => { 58 | 'mysqld' => { 59 | 'log-bin' => '/var/log/mariadb/mariadb-bin.log',} 60 | } 61 | } 62 | MANIFEST 63 | end 64 | 65 | it 'can be set' do 66 | # TODO : Returning warning message while running above manifest 67 | # Warning: Facter: Container runtime, 'docker', is unsupported, setting to, 'container_other' 68 | apply_manifest(pp) 69 | end 70 | end 71 | end 72 | 73 | describe 'syslog configuration' do 74 | let(:pp) do 75 | <<-MANIFEST 76 | class { 'mysql::server': 77 | override_options => { 'mysqld' => { 'log-error' => undef }, 'mysqld_safe' => { 'log-error' => false, 'syslog' => true }}, 78 | } 79 | MANIFEST 80 | end 81 | 82 | it 'behaves idempotently' do 83 | idempotent_apply(pp) 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/acceptance/01_mysql_db_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'mysql::db define' do 6 | describe 'creating a database' do 7 | let(:pp) do 8 | <<-MANIFEST 9 | class { 'mysql::server': 10 | root_password => 'password', 11 | service_enabled => 'true', 12 | service_manage => 'true', 13 | } 14 | mysql::db { 'spec1': 15 | user => 'root1', 16 | password => 'password', 17 | charset => '#{charset}', 18 | collate => '#{charset}_general_ci', 19 | } 20 | MANIFEST 21 | end 22 | 23 | it 'behaves idempotently' do 24 | idempotent_apply(pp) 25 | end 26 | 27 | it 'Checking exit code and stdout' do 28 | result = run_shell("mysql -e 'show databases;'") 29 | expect(result.exit_code).to eq 0 30 | expect(result.stdout).to match %r{^spec1$} 31 | end 32 | end 33 | 34 | describe 'creating a database with post-sql' do 35 | let(:pp) do 36 | <<-MANIFEST 37 | class { 'mysql::server': override_options => { 'root_password' => 'password' } } 38 | file { '/tmp/spec.sql': 39 | ensure => file, 40 | content => 'CREATE TABLE IF NOT EXISTS table1 (id int);', 41 | before => Mysql::Db['spec2'], 42 | } 43 | mysql::db { 'spec2': 44 | user => 'root1', 45 | password => 'password', 46 | charset => '#{charset}', 47 | collate => '#{charset}_general_ci', 48 | sql => ['/tmp/spec.sql'], 49 | } 50 | MANIFEST 51 | end 52 | 53 | it 'behaves idempotently' do 54 | idempotent_apply(pp) 55 | end 56 | 57 | it 'Checking exit code and stdout' do 58 | result = run_shell("mysql -e 'show tables;' spec2") 59 | expect(result.exit_code).to eq 0 60 | expect(result.stdout).to match %r{^table1$} 61 | end 62 | end 63 | 64 | describe 'creating a database with dbname parameter' do 65 | let(:check_command) { ' | grep realdb' } 66 | let(:pp) do 67 | <<-MANIFEST 68 | class { 'mysql::server': override_options => { 'root_password' => 'password' } } 69 | mysql::db { 'spec1': 70 | user => 'root1', 71 | password => 'password', 72 | dbname => 'realdb', 73 | charset => '#{charset}', 74 | collate => '#{charset}_general_ci', 75 | } 76 | MANIFEST 77 | end 78 | 79 | it 'behaves idempotently' do 80 | idempotent_apply(pp) 81 | end 82 | 83 | it 'Checking exit code and stdout' do 84 | result = run_shell("mysql -e 'show databases;'") 85 | expect(result.exit_code).to eq 0 86 | expect(result.stdout).to match %r{^realdb$} 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/acceptance/02_mysql_mariadb_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'mysql server class', if: ((os[:family] == 'debian') || (os[:family] == 'redhat' && os[:release].to_i > 6)) do 6 | describe 'mariadb' do 7 | let(:pp) do 8 | <<-MANIFEST 9 | $osname = $facts['os']['name'].downcase 10 | yumrepo {'mariadb': 11 | baseurl => "http://yum.mariadb.org/10.4/$osname${facts['os']['release']['major']}-aarch64/", 12 | gpgkey => 'https://yum.mariadb.org/RPM-GPG-KEY-MariaDB', 13 | descr => "MariaDB 10.4", 14 | enabled => 0, 15 | gpgcheck => 0, 16 | }-> 17 | class { '::mysql::server': 18 | require => Yumrepo['mariadb'], 19 | package_name => 'mariadb-server', 20 | service_name => 'mariadb', 21 | root_password => 'strongpassword', 22 | remove_default_accounts => true, 23 | managed_dirs => ['/var/log','/var/run/mysql'], 24 | override_options => { 25 | mysqld => { 26 | log-error => '/var/log/mariadb.log', 27 | pid-file => '/var/run/mysql/mysqld.pid', 28 | }, 29 | mysqld_safe => { 30 | log-error => '/var/log/mariadb.log', 31 | }, 32 | }, 33 | } 34 | MANIFEST 35 | end 36 | 37 | it 'apply manifest' do 38 | apply_manifest(pp) 39 | end 40 | 41 | it 'mariadb connection' do 42 | result = run_shell('mysql --user="root" --password="strongpassword" -e "status"') 43 | expect(result.stdout).to match(%r{MariaDB}) 44 | expect(result.stderr).to be_empty 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/acceptance/03_mysql_task_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # run a test task 4 | require 'spec_helper_acceptance' 5 | 6 | describe 'mysql tasks', if: os[:family] != 'sles' do 7 | describe 'execute some sql' do 8 | pp = <<-MANIFEST 9 | class { 'mysql::server': root_password => 'password' } 10 | mysql::db { 'spec1': 11 | user => 'root1', 12 | password => 'password', 13 | } 14 | MANIFEST 15 | 16 | it 'sets up a mysql instance' do 17 | apply_manifest(pp, catch_failures: true) 18 | end 19 | 20 | it 'execute arbitary sql' do 21 | result = run_bolt_task('mysql::sql', 'sql' => 'show databases;', 'password' => 'password') 22 | expect(result.stdout).to contain(%r{information_schema}) 23 | expect(result.stdout).to contain(%r{spec1}) 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /spec/acceptance/04_mysql_backup_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'mysql::server::backup class' do 6 | context 'should work with no errors' do 7 | pp = <<-MANIFEST 8 | class { 'mysql::server': root_password => 'password' } 9 | mysql::db { [ 10 | 'backup1', 11 | 'backup2' 12 | ]: 13 | user => 'backup', 14 | password => 'secret', 15 | charset => '#{charset}', 16 | collate => '#{charset}_general_ci', 17 | } 18 | 19 | class { 'mysql::server::backup': 20 | backupuser => 'myuser', 21 | backuppassword => 'mypassword', 22 | backupdir => '/tmp/backups', 23 | backupcompress => true, 24 | postscript => [ 25 | 'rm -rf /var/tmp/mysqlbackups', 26 | 'rm -f /var/tmp/mysqlbackups.done', 27 | 'cp -r /tmp/backups /var/tmp/mysqlbackups', 28 | 'touch /var/tmp/mysqlbackups.done', 29 | ], 30 | execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', 31 | } 32 | MANIFEST 33 | it 'when configuring mysql backups' do 34 | idempotent_apply(pp) 35 | end 36 | end 37 | 38 | describe 'mysqlbackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') do 39 | it 'runs mysqlbackup.sh with no errors' do 40 | run_shell('/usr/local/sbin/mysqlbackup.sh') do |r| 41 | expect(r.stderr).to eq('') 42 | end 43 | end 44 | 45 | it 'dumps all databases to single file' do 46 | run_shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| 47 | expect(r.stdout).to match(%r{1}) 48 | expect(r.exit_code).to be_zero 49 | end 50 | end 51 | 52 | context 'should create one file per database per run' do 53 | it 'executes mysqlbackup.sh a second time' do 54 | run_shell('sleep 1') 55 | run_shell('/usr/local/sbin/mysqlbackup.sh') 56 | end 57 | 58 | it 'creates at least one backup tarball' do 59 | run_shell('ls -l /tmp/backups/mysql_backup_*-*.sql.bz2 | wc -l') do |r| 60 | expect(r.stdout).to match(%r{2}) 61 | expect(r.exit_code).to be_zero 62 | end 63 | end 64 | end 65 | end 66 | 67 | context 'with one file per database' do 68 | context 'should work with no errors' do 69 | pp = <<-MANIFEST 70 | class { 'mysql::server': root_password => 'password' } 71 | mysql::db { [ 72 | 'backup1', 73 | 'backup2' 74 | ]: 75 | user => 'backup', 76 | password => 'secret', 77 | charset => '#{charset}', 78 | collate => '#{charset}_general_ci', 79 | } 80 | 81 | class { 'mysql::server::backup': 82 | backupuser => 'myuser', 83 | backuppassword => 'mypassword', 84 | backupdir => '/tmp/backups', 85 | backupcompress => true, 86 | file_per_database => true, 87 | postscript => [ 88 | 'rm -rf /var/tmp/mysqlbackups', 89 | 'rm -f /var/tmp/mysqlbackups.done', 90 | 'cp -r /tmp/backups /var/tmp/mysqlbackups', 91 | 'touch /var/tmp/mysqlbackups.done', 92 | ], 93 | execpath => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', 94 | } 95 | MANIFEST 96 | it 'when configuring mysql backups' do 97 | idempotent_apply(pp) 98 | end 99 | end 100 | 101 | describe 'mysqlbackup.sh', if: Gem::Version.new(mysql_version) < Gem::Version.new('5.7.0') do 102 | it 'runs mysqlbackup.sh with no errors without root credentials' do 103 | run_shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') do |r| 104 | expect(r.stderr).to eq('') 105 | end 106 | end 107 | 108 | it 'creates one file per database' do 109 | ['backup1', 'backup2'].each do |database| 110 | run_shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| 111 | expect(r.stdout).to match(%r{1}) 112 | expect(r.exit_code).to be_zero 113 | end 114 | end 115 | end 116 | 117 | it 'executes mysqlbackup.sh a second time' do 118 | run_shell('sleep 1') 119 | run_shell('HOME=/tmp/dontreadrootcredentials /usr/local/sbin/mysqlbackup.sh') 120 | end 121 | 122 | it 'has one file per database per run' do 123 | ['backup1', 'backup2'].each do |database| 124 | run_shell("ls -l /tmp/backups/mysql_backup_#{database}_*-*.sql.bz2 | wc -l") do |r| 125 | expect(r.stdout).to match(%r{2}) 126 | expect(r.exit_code).to be_zero 127 | end 128 | end 129 | end 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /spec/acceptance/types/mysql_database_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'mysql_database' do 6 | describe 'setup' do 7 | pp = <<-MANIFEST 8 | class { 'mysql::server': } 9 | MANIFEST 10 | it 'works with no errors' do 11 | apply_manifest(pp, catch_failures: true) 12 | end 13 | end 14 | 15 | describe 'creating database' do 16 | pp = <<-MANIFEST 17 | mysql_database { 'spec_db': 18 | ensure => present, 19 | charset => '#{charset}', 20 | collate => '#{charset}_general_ci', 21 | } 22 | MANIFEST 23 | it 'works without errors' do 24 | apply_manifest(pp, catch_failures: true) 25 | end 26 | 27 | it 'finds the database #stdout' do 28 | run_shell("mysql -NBe \"SHOW DATABASES LIKE 'spec_db'\"") do |r| 29 | expect(r.stdout).to match(%r{^spec_db$}) 30 | expect(r.stderr).to be_empty 31 | end 32 | end 33 | end 34 | 35 | describe 'charset and collate' do 36 | pp = <<-MANIFEST 37 | mysql_database { 'spec_latin1': 38 | charset => 'latin1', 39 | collate => 'latin1_swedish_ci', 40 | } 41 | mysql_database { 'spec_utf8': 42 | charset => '#{charset}', 43 | collate => '#{charset}_general_ci', 44 | } 45 | MANIFEST 46 | it 'creates two db of different types idempotently' do 47 | idempotent_apply(pp) 48 | end 49 | 50 | it 'finds latin1 db #stdout' do 51 | run_shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_latin1") do |r| 52 | expect(r.stdout).to match(%r{^character_set_database\tlatin1\ncollation_database\tlatin1_swedish_ci$}) 53 | expect(r.stderr).to be_empty 54 | end 55 | end 56 | 57 | it 'finds utf8 db #stdout' do 58 | run_shell("mysql -NBe \"SHOW VARIABLES LIKE '%_database'\" spec_utf8") do |r| 59 | expect(r.stdout).to match(%r{^character_set_database\tutf8(mb3)?\ncollation_database\tutf8(mb3)?_general_ci$}) 60 | expect(r.stderr).to be_empty 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/acceptance/types/mysql_plugin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | # Different operating systems (and therefore different versions/forks 6 | # of mysql) have varying levels of support for plugins and have 7 | # different plugins available. Choose a plugin that works or don't try 8 | # to test plugins if not available. 9 | case os[:family] 10 | when 'redhat' 11 | case os[:release].to_i 12 | when 7 13 | plugin = 'pam' 14 | plugin_lib = 'auth_pam.so' 15 | end 16 | when 'debian' 17 | if os[:family] == 'ubuntu' 18 | if %r{^16\.04|^18\.04}.match?(os[:release]) 19 | # On Xenial running 5.7.12, the example plugin does not appear to be available. 20 | plugin = 'validate_password' 21 | plugin_lib = 'validate_password.so' 22 | else 23 | plugin = 'example' 24 | plugin_lib = 'ha_example.so' 25 | end 26 | end 27 | when 'suse' 28 | plugin = nil # Plugin library path is broken on Suse http://lists.opensuse.org/opensuse-bugs/2013-08/msg01123.html 29 | end 30 | 31 | describe 'mysql_plugin' do 32 | if plugin # if plugins are supported 33 | describe 'setup' do 34 | it 'works with no errors' do 35 | pp = <<-MANIFEST 36 | class { 'mysql::server': } 37 | MANIFEST 38 | 39 | apply_manifest(pp, catch_failures: true) 40 | end 41 | end 42 | 43 | describe 'load plugin' do 44 | pp = <<-MANIFEST 45 | mysql_plugin { #{plugin}: 46 | ensure => present, 47 | soname => '#{plugin_lib}', 48 | } 49 | MANIFEST 50 | it 'works without errors' do 51 | apply_manifest(pp, catch_failures: true) 52 | end 53 | 54 | it 'finds the plugin #stdout' do 55 | run_shell("mysql -NBe \"select plugin_name from information_schema.plugins where plugin_name='#{plugin}'\"") do |r| 56 | expect(r.stdout).to match(%r{^#{plugin}$}i) 57 | expect(r.stderr).to be_empty 58 | end 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/classes/graceful_failures_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::server' do 6 | context 'on an unsupported OS' do 7 | let(:facts) do 8 | { 9 | os: { family: 'UNSUPPORTED', 10 | name: 'UNSUPPORTED' } 11 | } 12 | end 13 | 14 | it 'gracefully fails' do 15 | expect(subject).to compile.and_raise_error(%r{Unsupported platform:}) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/classes/mycnf_template_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::server' do 6 | on_supported_os.each do |os, facts| 7 | context "my.cnf template - on #{os}" do 8 | let(:facts) do 9 | facts.merge(root_home: '/root') 10 | end 11 | 12 | context 'normal entry' do 13 | let(:params) { { override_options: { 'mysqld' => { 'socket' => '/var/lib/mysql/mysql.sock' } } } } 14 | 15 | it do 16 | expect(subject).to contain_file('mysql-config-file').with(mode: '0644', 17 | selinux_ignore_defaults: true).with_content(%r{socket = /var/lib/mysql/mysql.sock}) 18 | end 19 | end 20 | 21 | describe 'array entry' do 22 | let(:params) { { override_options: { 'mysqld' => { 'replicate-do-db' => ['base1', 'base2'] } } } } 23 | 24 | it do 25 | expect(subject).to contain_file('mysql-config-file').with_content( 26 | %r{.*replicate-do-db = base1\nreplicate-do-db = base2.*}, 27 | ) 28 | end 29 | end 30 | 31 | describe 'skip-name-resolve set to an empty string' do 32 | let(:params) { { override_options: { 'mysqld' => { 'skip-name-resolve' => '' } } } } 33 | 34 | it { is_expected.to contain_file('mysql-config-file').with_content(%r{^skip-name-resolve$}) } 35 | end 36 | 37 | describe 'ssl set to true' do 38 | let(:params) { { override_options: { 'mysqld' => { 'ssl' => true } } } } 39 | 40 | it { is_expected.to contain_file('mysql-config-file').with_content(%r{ssl}) } 41 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl = true}) } 42 | end 43 | 44 | describe 'ssl set to false' do 45 | let(:params) { { override_options: { 'mysqld' => { 'ssl' => false } } } } 46 | 47 | it { is_expected.to contain_file('mysql-config-file').with_content(%r{ssl = false}) } 48 | end 49 | 50 | describe 'ssl set to false filters out ssl options' do 51 | let(:params) { { override_options: { 'mysqld' => { 'ssl' => false, 'ssl-disable' => false, 'ssl-key' => '/etc/key.pem' } } } } 52 | 53 | it { is_expected.to contain_file('mysql-config-file').with_content(%r{ssl = false}) } 54 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl-key}) } 55 | end 56 | 57 | # ssl-disable (and ssl) are special cased within mysql. 58 | describe 'possibility of disabling ssl completely' do 59 | let(:params) { { override_options: { 'mysqld' => { 'ssl' => true, 'ssl-disable' => true } } } } 60 | 61 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl = true}) } 62 | end 63 | 64 | describe 'ssl-disable filters other ssl options' do 65 | let(:params) { { override_options: { 'mysqld' => { 'ssl' => true, 'ssl-disable' => true, 'ssl-key' => '/etc/key.pem' } } } } 66 | 67 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl = true}) } 68 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl-disable}) } 69 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{ssl-key}) } 70 | end 71 | 72 | describe 'a non ssl option set to true' do 73 | let(:params) { { override_options: { 'mysqld' => { 'test' => true } } } } 74 | 75 | it { is_expected.to contain_file('mysql-config-file').with_content(%r{^test$}) } 76 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{test = true}) } 77 | end 78 | 79 | context 'with includedir' do 80 | let(:params) { { includedir: '/etc/my.cnf.d' } } 81 | 82 | it 'makes the directory' do 83 | expect(subject).to contain_file('/etc/my.cnf.d').with(ensure: :directory, 84 | mode: '0755') 85 | end 86 | 87 | it { is_expected.to contain_file('mysql-config-file').with_content(%r{!includedir}) } 88 | end 89 | 90 | context 'without includedir' do 91 | let(:params) { { includedir: '' } } 92 | 93 | it 'shouldnt contain the directory' do 94 | expect(subject).not_to contain_file('mysql-config-file').with(ensure: :directory, 95 | mode: '0755') 96 | end 97 | 98 | it { is_expected.to contain_file('mysql-config-file').without_content(%r{!includedir}) } 99 | end 100 | 101 | context 'with file mode 0644' do 102 | let(:params) { { 'config_file_mode' => '0644' } } 103 | 104 | it do 105 | expect(subject).to contain_file('mysql-config-file').with(mode: '0644') 106 | end 107 | end 108 | 109 | context 'with file mode 0664' do 110 | let(:params) { { 'config_file_mode' => '0664' } } 111 | 112 | it do 113 | expect(subject).to contain_file('mysql-config-file').with(mode: '0664') 114 | end 115 | end 116 | 117 | context 'with file mode 0660' do 118 | let(:params) { { 'config_file_mode' => '0660' } } 119 | 120 | it do 121 | expect(subject).to contain_file('mysql-config-file').with(mode: '0660') 122 | end 123 | end 124 | 125 | context 'with file mode 0641' do 126 | let(:params) { { 'config_file_mode' => '0641' } } 127 | 128 | it do 129 | expect(subject).to contain_file('mysql-config-file').with(mode: '0641') 130 | end 131 | end 132 | 133 | context 'with file mode 0610' do 134 | let(:params) { { 'config_file_mode' => '0610' } } 135 | 136 | it do 137 | expect(subject).to contain_file('mysql-config-file').with(mode: '0610') 138 | end 139 | end 140 | 141 | context 'with file 0600' do 142 | let(:params) { { 'config_file_mode' => '0600' } } 143 | 144 | it do 145 | expect(subject).to contain_file('mysql-config-file').with(mode: '0600') 146 | end 147 | end 148 | 149 | context 'user owner 12345' do 150 | let(:params) { { 'mycnf_owner' => '12345' } } 151 | 152 | it do 153 | expect(subject).to contain_file('mysql-config-file').with( 154 | owner: '12345', 155 | ) 156 | end 157 | end 158 | 159 | context 'group owner 12345' do 160 | let(:params) { { 'mycnf_group' => '12345' } } 161 | 162 | it do 163 | expect(subject).to contain_file('mysql-config-file').with( 164 | group: '12345', 165 | ) 166 | end 167 | end 168 | 169 | context 'user and group owner 12345' do 170 | let(:params) { { 'mycnf_owner' => '12345', 'mycnf_group' => '12345' } } 171 | 172 | it do 173 | expect(subject).to contain_file('mysql-config-file').with( 174 | owner: '12345', 175 | group: '12345', 176 | ) 177 | end 178 | end 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /spec/classes/mysql_backup_mysqldump_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::backup::mysqldump' do 6 | on_supported_os.each do |os, facts| 7 | context "on #{os}" do 8 | let(:pre_condition) do 9 | <<-MANIFEST 10 | class { 'mysql::server': } 11 | MANIFEST 12 | end 13 | let(:facts) do 14 | facts.merge(root_home: '/root') 15 | end 16 | 17 | let(:default_params) do 18 | { 'backupuser' => 'testuser', 19 | 'backuppassword' => 'testpass', 20 | 'backupdir' => '/tmp/mysql-backup', 21 | 'backuprotate' => '25', 22 | 'delete_before_dump' => true, 23 | 'execpath' => '/usr/bin:/usr/sbin:/bin:/sbin:/opt/zimbra/bin', 24 | 'maxallowedpacket' => '1M' } 25 | end 26 | 27 | context 'with time included' do 28 | let(:params) do 29 | { time: [23, 59, 30, 12, 6] }.merge(default_params) 30 | end 31 | 32 | it { 33 | expect(subject).to contain_cron('mysql-backup').with( 34 | hour: 23, 35 | minute: 59, 36 | monthday: 30, 37 | month: 12, 38 | weekday: 6, 39 | ) 40 | } 41 | end 42 | 43 | context 'with defaults' do 44 | let(:params) { default_params } 45 | 46 | it { 47 | expect(subject).to contain_cron('mysql-backup').with( 48 | command: '/usr/local/sbin/mysqlbackup.sh', 49 | ensure: 'present', 50 | hour: 23, 51 | minute: 5, 52 | ) 53 | } 54 | end 55 | 56 | context 'with compression_command' do 57 | let(:params) do 58 | { 59 | compression_command: 'TEST -TEST', 60 | compression_extension: '.TEST' 61 | }.merge(default_params) 62 | end 63 | 64 | it { 65 | expect(subject).to contain_file('mysqlbackup.sh').with_content( 66 | %r{(\| TEST -TEST)}, 67 | ) 68 | expect(subject).to contain_file('mysqlbackup.sh').with_content( 69 | %r{(\.TEST)}, 70 | ) 71 | expect(subject).not_to contain_package('bzip2') 72 | } 73 | end 74 | 75 | context 'with file_per_database and excludedatabases' do 76 | let(:params) do 77 | { 78 | 'file_per_database' => true, 79 | 'excludedatabases' => ['information_schema', 'performance_schema'] 80 | }.merge(default_params) 81 | end 82 | 83 | it { 84 | expect(subject).to contain_file('mysqlbackup.sh').with_content( 85 | %r{information_schema\\\|performance_schema}, 86 | ) 87 | } 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/classes/mysql_bindings_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::bindings' do 6 | on_supported_os.each do |os, facts| 7 | next if facts[:os]['family'] == 'Archlinux' 8 | 9 | context "on #{os}" do 10 | let(:facts) do 11 | facts.merge(root_home: '/root') 12 | end 13 | 14 | let(:params) do 15 | { 16 | 'java_enable' => true, 17 | 'perl_enable' => true, 18 | 'php_enable' => true, 19 | 'python_enable' => true, 20 | 'ruby_enable' => true, 21 | 'client_dev' => true, 22 | 'daemon_dev' => true, 23 | 'client_dev_package_name' => 'libmysqlclient-devel', 24 | 'daemon_dev_package_name' => 'mysql-devel' 25 | } 26 | end 27 | 28 | it { is_expected.to contain_package('mysql-connector-java') } 29 | it { is_expected.to contain_package('perl_mysql') } 30 | it { is_expected.to contain_package('python-mysqldb') } 31 | it { is_expected.to contain_package('ruby_mysql') } 32 | it { is_expected.to contain_package('mysql-client_dev') } 33 | it { is_expected.to contain_package('mysql-daemon_dev') } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/classes/mysql_client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::client' do 6 | on_supported_os.each do |os, facts| 7 | context "on #{os}" do 8 | let(:facts) do 9 | facts.merge(root_home: '/root') 10 | end 11 | 12 | context 'with defaults' do 13 | it { is_expected.not_to contain_class('mysql::bindings') } 14 | it { is_expected.to contain_package('mysql_client') } 15 | end 16 | 17 | context 'with bindings enabled' do 18 | let(:params) { { bindings_enable: true } } 19 | 20 | it { is_expected.to contain_class('mysql::bindings') } 21 | it { is_expected.to contain_package('mysql_client') } 22 | end 23 | 24 | context 'with package_manage set to true' do 25 | let(:params) { { package_manage: true } } 26 | 27 | it { is_expected.to contain_package('mysql_client') } 28 | end 29 | 30 | context 'with package_manage set to false' do 31 | let(:params) { { package_manage: false } } 32 | 33 | it { is_expected.not_to contain_package('mysql_client') } 34 | end 35 | 36 | context 'with package provider' do 37 | let(:params) do 38 | { 39 | package_provider: 'dpkg', 40 | package_source: '/somewhere' 41 | } 42 | end 43 | 44 | it do 45 | expect(subject).to contain_package('mysql_client').with( 46 | provider: 'dpkg', 47 | source: '/somewhere', 48 | ) 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/classes/mysql_server_account_security_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::server::account_security' do 6 | on_supported_os.each do |os, os_facts| 7 | let(:facts) { os_facts } 8 | context "on #{os}" do 9 | let(:pre_condition) do 10 | <<-MANIFEST 11 | anchor {'mysql::server::end': } 12 | MANIFEST 13 | end 14 | 15 | context 'with fqdn==myhost.mydomain' do 16 | let(:facts) do 17 | override_facts( 18 | super(), 19 | 'root_home' => '/root', 20 | 'networking' => { 21 | 'fqdn' => 'myhost.mydomain', 22 | 'hostname' => 'myhost', 23 | }, 24 | ) 25 | end 26 | 27 | ['root@localhost.localdomain', 28 | '@localhost.localdomain', 29 | 'root@myhost.mydomain', 30 | 'root@127.0.0.1', 31 | 'root@::1', 32 | '@myhost.mydomain', 33 | '@localhost', 34 | '@%'].each do |user| 35 | it "removes Mysql_User[#{user}]" do # rubocop:disable RSpec/RepeatedExample,RSpec/RepeatedDescription 36 | expect(subject).to contain_mysql_user(user).with_ensure('absent') 37 | end 38 | end 39 | 40 | # When the hostname doesn't match the fqdn we also remove these. 41 | # We don't need to test the inverse as when they match they are 42 | # covered by the above list. 43 | ['root@myhost', '@myhost'].each do |user| 44 | it "removes Mysql_User[#{user}]" do # rubocop:disable RSpec/RepeatedExample,RSpec/RepeatedDescription 45 | expect(subject).to contain_mysql_user(user).with_ensure('absent') 46 | end 47 | end 48 | 49 | it 'removes Mysql_database[test]' do 50 | expect(subject).to contain_mysql_database('test').with_ensure('absent') 51 | end 52 | end 53 | 54 | context 'with fqdn==localhost' do 55 | let(:facts) do 56 | override_facts( 57 | super(), 58 | 'root_home' => '/root', 59 | 'networking' => { 60 | 'fqdn' => 'localhost', 61 | 'hostname' => 'localhost', 62 | }, 63 | ) 64 | end 65 | 66 | ['root@127.0.0.1', 67 | 'root@::1', 68 | '@localhost', 69 | 'root@localhost.localdomain', 70 | '@localhost.localdomain', 71 | '@%'].each do |user| 72 | it "removes Mysql_User[#{user}] for fqdn==localhost" do 73 | expect(subject).to contain_mysql_user(user).with_ensure('absent') 74 | end 75 | end 76 | end 77 | 78 | context 'with fqdn==localhost.localdomain' do 79 | let(:facts) do 80 | override_facts( 81 | super(), 82 | 'root_home' => '/root', 83 | 'networking' => { 84 | 'fqdn' => 'localhost.localdomain', 85 | 'hostname' => 'localhost', 86 | }, 87 | ) 88 | end 89 | 90 | ['root@127.0.0.1', 91 | 'root@::1', 92 | '@localhost', 93 | 'root@localhost.localdomain', 94 | '@localhost.localdomain', 95 | '@%'].each do |user| 96 | it "removes Mysql_User[#{user}] for fqdn==localhost.localdomain" do 97 | expect(subject).to contain_mysql_user(user).with_ensure('absent') 98 | end 99 | end 100 | end 101 | end 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /spec/default_facts.yml: -------------------------------------------------------------------------------- 1 | # Use default_module_facts.yml for module specific facts. 2 | # 3 | # Facts specified here will override the values provided by rspec-puppet-facts. 4 | --- 5 | networking: 6 | ip: "172.16.254.254" 7 | ip6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA" 8 | mac: "AA:AA:AA:AA:AA:AA" 9 | is_pe: false 10 | -------------------------------------------------------------------------------- /spec/defines/mysql_db_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::db', type: :define do 6 | on_supported_os.each do |os, facts| 7 | context "on #{os}" do 8 | let(:facts) do 9 | facts.merge(root_home: '/root') 10 | end 11 | 12 | let(:title) { 'test_db' } 13 | 14 | let(:params) do 15 | { 'user' => 'testuser', 16 | 'password' => 'testpass', 17 | 'mysql_exec_path' => '' } 18 | end 19 | 20 | let(:sql) { ['/tmp/test.sql'] } 21 | 22 | it 'does not notify the import sql exec if no sql script was provided' do 23 | expect(subject).to contain_mysql_database('test_db').without_notify 24 | end 25 | 26 | it 'subscribes to database if sql script is given' do 27 | params['sql'] = sql 28 | expect(subject).to contain_mysql_database('test_db') 29 | expect(subject).to contain_exec('test_db-import').with_subscribe('Mysql_database[test_db]') 30 | end 31 | 32 | it 'onlies import sql script on creation if not enforcing' do 33 | params.merge!('sql' => sql, 'enforce_sql' => false) 34 | expect(subject).to contain_exec('test_db-import').with_refreshonly(true) 35 | end 36 | 37 | it 'imports sql script on creation' do 38 | params.merge!('sql' => sql, 'enforce_sql' => true) 39 | # ' if enforcing #refreshonly' 40 | expect(subject).to contain_exec('test_db-import').with_refreshonly(false) 41 | # 'if enforcing #command' 42 | expect(subject).to contain_exec('test_db-import').with_command('cat /tmp/test.sql | mysql test_db') 43 | end 44 | 45 | it 'imports sql script with custom command on creation' do 46 | params.merge!('sql' => sql, 'enforce_sql' => true, 'import_cat_cmd' => 'zcat') 47 | # if enforcing #refreshonly 48 | expect(subject).to contain_exec('test_db-import').with_refreshonly(false) 49 | # if enforcing #command 50 | expect(subject).to contain_exec('test_db-import').with_command('zcat /tmp/test.sql | mysql test_db') 51 | end 52 | 53 | it 'imports sql scripts when more than one is specified' do 54 | params['sql'] = ['/tmp/test.sql', '/tmp/test_2.sql'] 55 | expect(subject).to contain_exec('test_db-import').with_command('cat /tmp/test.sql /tmp/test_2.sql | mysql test_db') 56 | end 57 | 58 | it 'does not create database' do 59 | params.merge!('ensure' => 'absent', 'host' => 'localhost') 60 | expect(subject).to contain_mysql_database('test_db').with_ensure('absent') 61 | expect(subject).to contain_mysql_user('testuser@localhost').with_ensure('absent') 62 | end 63 | 64 | it 'creates with an appropriate collate and charset' do 65 | params.merge!('charset' => 'utf8', 'collate' => 'utf8_danish_ci') 66 | expect(subject).to contain_mysql_database('test_db').with('charset' => 'utf8', 67 | 'collate' => 'utf8_danish_ci') 68 | end 69 | 70 | it 'uses dbname parameter as database name instead of name' do 71 | params['dbname'] = 'real_db' 72 | expect(subject).to contain_mysql_database('real_db') 73 | end 74 | 75 | it 'uses tls_options for user when set' do 76 | params['tls_options'] = ['SSL'] 77 | expect(subject).to contain_mysql_user('testuser@localhost').with_tls_options(['SSL']) 78 | end 79 | 80 | it 'uses grant_options for grant when set' do 81 | params['grant_options'] = ['GRANT'] 82 | expect(subject).to contain_mysql_grant('testuser@localhost/test_db.*').with_options(['GRANT']) 83 | end 84 | 85 | # Invalid file paths 86 | [ 87 | '|| ls -la ||', 88 | '|| touch /tmp/foo.txt ||', 89 | '/tmp/foo.txt;echo', 90 | 'myPath;', 91 | '\\myPath\\', 92 | '//myPath has spaces//', 93 | '/', 94 | ].each do |path| 95 | it "fails when provided '#{path}' as a value to the 'sql' parameter" do 96 | params['sql'] = [path] 97 | expect(subject).to raise_error(Puppet::PreformattedError, %r{The file '#{Regexp.escape(path)}' is invalid. A valid file path is expected.}) 98 | end 99 | end 100 | 101 | # Valid file paths 102 | [ 103 | '/tmp/test.txt', 104 | '/tmp/.test', 105 | '/foo.test', 106 | '/foo.test.txt', 107 | '/foo/test/test-1.2.3/schema/test.sql', 108 | '/foo/test/test-1.2.3/schema/foo.test.sql', 109 | '/foo/foo.t1.t2.t3/foo.test-1.2.3/test.test.schema/test..app.sql', 110 | '/foo/foo.t1.t2...t3/foo.test-1.2.3/test.test.schema/test.app.sql', 111 | ].each do |path| 112 | it "succeeds when provided '#{path}' as a value to the 'sql' parameter" do 113 | params['sql'] = [path] 114 | expect(subject).to contain_exec('test_db-import').with_command("cat #{path} | mysql test_db") 115 | end 116 | end 117 | 118 | # Invalid database names 119 | [ 120 | 'test db', 121 | 'test_db;', 122 | 'test/db', 123 | '|| ls -la ||', 124 | '|| touch /tmp/foo.txt ||', 125 | ].each do |name| 126 | it "fails when provided '#{name}' as a value to the 'name' parameter" do 127 | params['name'] = name 128 | expect(subject).to raise_error(Puppet::PreformattedError, %r{The database name '#{name}' is invalid.}) 129 | end 130 | end 131 | 132 | # Valid database names 133 | [ 134 | 'test_db', 135 | 'testdb', 136 | 'test-db', 137 | 'TESTDB', 138 | ].each do |name| 139 | it "succeeds when the provided '#{name}' as a value to the 'dbname' parameter" do 140 | params['dbname'] = name 141 | expect(subject).to contain_mysql_database(name) 142 | end 143 | end 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/functions/mysql_innobackupex_args_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::innobackupex_args' do 6 | it 'exists' do 7 | expect(subject).not_to be_nil 8 | end 9 | 10 | it 'accepts empty strings as puppet undef' do 11 | expect(subject).to run.with_params('', true, '', [], []) 12 | end 13 | 14 | context 'should work with username and password' do 15 | it 'returns args with username and password' do 16 | expect(subject).to run.with_params('root', false, '12345', [], []).and_return('--user="root" --password="12345"') 17 | end 18 | 19 | it 'returns args with database lists' do 20 | expect(subject).to run.with_params('root', false, '12345', ['db1', 'db2'], []).and_return('--user="root" --password="12345" --databases="db1 db2"') 21 | end 22 | 23 | it 'returns args with backup compress only' do 24 | expected_results = '--user="root" --password="12345" --compress' 25 | expect(subject).to run.with_params('root', true, '12345', [], []).and_return(expected_results) 26 | end 27 | 28 | it 'returns args with backup compress, database list and optional_args' do 29 | expected_results = '--user="root" --password="12345" --compress --databases="db1 db2" tst_arg_1 tst_arg_2' 30 | expect(subject).to run.with_params('root', true, '12345', ['db1', 'db2'], ['tst_arg_1', 'tst_arg_2']).and_return(expected_results) 31 | end 32 | end 33 | 34 | context 'should work without database args' do 35 | it 'returns args without database list' do 36 | expect(subject).to run.with_params('root', false, '12345', [], []).and_return('--user="root" --password="12345"') 37 | end 38 | end 39 | 40 | it 'returns args without backup compress' do 41 | expect(subject).to run.with_params('root', false, '12345', [], []).and_return('--user="root" --password="12345"') 42 | end 43 | 44 | it 'returns args with backup compress and database list' do 45 | expected_results = '--user="root" --password="12345" --compress --databases="db1 db2"' 46 | expect(subject).to run.with_params('root', true, '12345', ['db1', 'db2'], []).and_return(expected_results) 47 | end 48 | 49 | it 'returns args without backup compress database list and optional_args' do 50 | expected_results = '--user="root" --password="12345" --databases="db1 db2" tst_arg_1 tst_arg_2' 51 | expect(subject).to run.with_params('root', false, '12345', ['db1', 'db2'], ['tst_arg_1', 'tst_arg_2']).and_return(expected_results) 52 | end 53 | 54 | it 'returns args without backup compress database list and with optional_args' do 55 | expected_results = '--user="root" --password="12345" tst_arg_1 tst_arg_2' 56 | expect(subject).to run.with_params('root', false, '12345', [], ['tst_arg_1', 'tst_arg_2']).and_return(expected_results) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/functions/mysql_normalise_and_deepmerge_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::normalise_and_deepmerge' do 6 | it 'exists' do 7 | expect(subject).not_to be_nil 8 | end 9 | 10 | it 'throws error with no arguments' do 11 | expect(subject).to run.with_params.and_raise_error(Puppet::ParseError) 12 | end 13 | 14 | it 'throws error with only one argument' do 15 | expect(subject).to run.with_params('one' => 1).and_raise_error(Puppet::ParseError) 16 | end 17 | 18 | it 'accepts empty strings as puppet undef' do 19 | expect(subject).to run.with_params({}, '') 20 | end 21 | 22 | index_values = ['one', 'two', 'three'] 23 | expected_values_one = ['1', '2', '2'] 24 | it 'merge two hashes' do 25 | new_hash = subject.execute({ 'one' => '1', 'two' => '1' }, 'two' => '2', 'three' => '2') 26 | index_values.each_with_index do |index, expected| 27 | expect(new_hash[index]).to eq(expected_values_one[expected]) 28 | end 29 | end 30 | 31 | it 'merges multiple hashes' do 32 | hash = subject.execute({ 'one' => 1 }, { 'one' => '2' }, 'one' => '3') 33 | expect(hash['one']).to eq('3') 34 | end 35 | 36 | it 'accepts empty hashes' do 37 | expect(subject).to run.with_params({}, {}, {}).and_return({}) 38 | end 39 | 40 | expected_values_two = [1, 2, { 'four' => 4 }] 41 | it 'merges subhashes' do 42 | hash = subject.execute({ 'one' => 1 }, 'two' => 2, 'three' => { 'four' => 4 }) 43 | index_values.each_with_index do |index, expected| 44 | expect(hash[index]).to eq(expected_values_two[expected]) 45 | end 46 | end 47 | 48 | it 'appends to subhashes' do 49 | hash = subject.execute({ 'one' => { 'two' => 2 } }, 'one' => { 'three' => 3 }) 50 | expect(hash['one']).to eq('two' => 2, 'three' => 3) 51 | end 52 | 53 | expected_values_three = [1, 'dos', { 'four' => 4, 'five' => 5 }] 54 | it 'appends to subhashes 2' do 55 | hash = subject.execute({ 'one' => 1, 'two' => 2, 'three' => { 'four' => 4 } }, 'two' => 'dos', 'three' => { 'five' => 5 }) 56 | index_values.each_with_index do |index, expected| 57 | expect(hash[index]).to eq(expected_values_three[expected]) 58 | end 59 | end 60 | 61 | index_values_two = ['key1', 'key2'] 62 | expected_values_four = [{ 'a' => 1, 'b' => 99 }, 'c' => 3] 63 | it 'appends to subhashes 3' do 64 | hash = subject.execute({ 'key1' => { 'a' => 1, 'b' => 2 }, 'key2' => { 'c' => 3 } }, 'key1' => { 'b' => 99 }) 65 | index_values_two.each_with_index do |index, expected| 66 | expect(hash[index]).to eq(expected_values_four[expected]) 67 | end 68 | end 69 | 70 | it 'equates keys mod dash and underscore #value' do 71 | hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10) 72 | expect(hash['a_b_c']).to eq(10) 73 | end 74 | 75 | it 'equates keys mod dash and underscore #not' do 76 | hash = subject.execute({ 'a-b-c' => 1 }, 'a_b_c' => 10) 77 | expect(hash).not_to have_key('a-b-c') 78 | end 79 | 80 | index_values_three = ['a_b_c', 'b-c-d'] 81 | expected_values_five = [10, { 'e-f-g' => 3, 'c_d_e' => 12 }] 82 | index_values_error = ['a-b-c', 'b_c_d'] 83 | index_values_three.each_with_index do |index, expected| 84 | it 'keeps style of the last when keys are equal mod dash and underscore #value' do 85 | hash = subject.execute({ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 } }, 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 }) 86 | expect(hash[index]).to eq(expected_values_five[expected]) 87 | end 88 | 89 | it 'keeps style of the last when keys are equal mod dash and underscore #not' do 90 | hash = subject.execute({ 'a-b-c' => 1, 'b_c_d' => { 'c-d-e' => 2, 'e-f-g' => 3 } }, 'a_b_c' => 10, 'b-c-d' => { 'c_d_e' => 12 }) 91 | expect(hash).not_to have_key(index_values_error[expected]) 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/functions/mysql_password_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | shared_examples 'mysql::password function' do 6 | it 'exists' do 7 | expect(subject).not_to be_nil 8 | end 9 | 10 | it 'raises a ArgumentError if there is less than 1 arguments' do 11 | expect(subject).to run.with_params.and_raise_error(ArgumentError) 12 | end 13 | 14 | it 'raises a ArgumentError if there is more than 2 arguments' do 15 | expect(subject).to run.with_params('foo', false, 'bar').and_raise_error(ArgumentError) 16 | end 17 | 18 | it 'converts password into a hash' do 19 | expect(subject).to run.with_params('password').and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') 20 | end 21 | 22 | it 'accept password as Sensitive' do 23 | expect(subject).to run.with_params(sensitive('password')).and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') 24 | end 25 | 26 | # Test of a Returnvalue of Datatype Sensitive does not work 27 | it 'returns Sensitive with sensitive=true' do 28 | expect(subject).to run.with_params('password', true).and_return(sensitive('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19')) 29 | end 30 | 31 | it 'password should be String' do 32 | expect(subject).to run.with_params(123).and_raise_error(ArgumentError) 33 | end 34 | 35 | it 'converts an empty password into a empty string' do 36 | expect(subject).to run.with_params('').and_return('') 37 | end 38 | 39 | it 'converts the password when its given in caps with * sign' do 40 | expect(subject).to run.with_params('AFDJKFD1*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29').and_return('*91FF6DD4E1FC57D2EFC57F49552D0596F7D46BAF') 41 | end 42 | 43 | it 'does not convert a password that is already a hash' do 44 | expect(subject).to run.with_params('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19').and_return('*2470C0C06DEE42FD1618BB99005ADCA2EC9D1E19') 45 | end 46 | end 47 | 48 | describe 'mysql::password' do 49 | it_behaves_like 'mysql::password function' 50 | 51 | describe 'non-namespaced shim' do 52 | describe 'mysql_password', type: :puppet_function do 53 | it_behaves_like 'mysql::password function' 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/functions/mysql_strip_hash_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'mysql::strip_hash' do 6 | it 'exists' do 7 | expect(subject).not_to be_nil 8 | end 9 | 10 | it 'raises a ArgumentError if there is less than 1 arguments' do 11 | expect(subject).to run.with_params.and_raise_error(ArgumentError) 12 | end 13 | 14 | it 'raises a ArgumentError if there is more than 1 arguments' do 15 | expect(subject).to run.with_params({ 'foo' => 1 }, 'bar' => 2).and_raise_error(ArgumentError) 16 | end 17 | 18 | it 'raises a ArgumentError if argument is not a hash' do 19 | expect(subject).to run.with_params('foo').and_raise_error(ArgumentError) 20 | end 21 | 22 | it 'passes a hash without blanks through' do 23 | expect(subject).to run.with_params('one' => 1, 'two' => 2, 'three' => 3).and_return('one' => 1, 'two' => 2, 'three' => 3) 24 | end 25 | 26 | it 'removes blank hash elements' do 27 | expect(subject).to run.with_params('one' => 1, 'two' => '', 'three' => nil, 'four' => 4).and_return('one' => 1, 'three' => nil, 'four' => 4) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |c| 4 | c.mock_with :rspec 5 | end 6 | 7 | require 'puppetlabs_spec_helper/module_spec_helper' 8 | require 'rspec-puppet-facts' 9 | 10 | require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) 11 | 12 | include RspecPuppetFacts 13 | 14 | default_facts = { 15 | puppetversion: Puppet.version, 16 | facterversion: Facter.version, 17 | } 18 | 19 | default_fact_files = [ 20 | File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')), 21 | File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')), 22 | ] 23 | 24 | default_fact_files.each do |f| 25 | next unless File.exist?(f) && File.readable?(f) && File.size?(f) 26 | 27 | begin 28 | require 'deep_merge' 29 | default_facts.deep_merge!(YAML.safe_load(File.read(f), permitted_classes: [], permitted_symbols: [], aliases: true)) 30 | rescue StandardError => e 31 | RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" 32 | end 33 | end 34 | 35 | # read default_facts and merge them over what is provided by facterdb 36 | default_facts.each do |fact, value| 37 | add_custom_fact fact, value, merge_facts: true 38 | end 39 | 40 | RSpec.configure do |c| 41 | c.default_facts = default_facts 42 | c.before :each do 43 | # set to strictest setting for testing 44 | # by default Puppet runs at warning level 45 | Puppet.settings[:strict] = :warning 46 | Puppet.settings[:strict_variables] = true 47 | end 48 | c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] 49 | c.after(:suite) do 50 | RSpec::Puppet::Coverage.report!(0) 51 | end 52 | 53 | # Filter backtrace noise 54 | backtrace_exclusion_patterns = [ 55 | %r{spec_helper}, 56 | %r{gems}, 57 | ] 58 | 59 | if c.respond_to?(:backtrace_exclusion_patterns) 60 | c.backtrace_exclusion_patterns = backtrace_exclusion_patterns 61 | elsif c.respond_to?(:backtrace_clean_patterns) 62 | c.backtrace_clean_patterns = backtrace_exclusion_patterns 63 | end 64 | end 65 | 66 | # Ensures that a module is defined 67 | # @param module_name Name of the module 68 | def ensure_module_defined(module_name) 69 | module_name.split('::').reduce(Object) do |last_module, next_module| 70 | last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) 71 | last_module.const_get(next_module, false) 72 | end 73 | end 74 | 75 | # 'spec_overrides' from sync.yml will appear below this line 76 | require 'spec_helper_local' 77 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet_litmus' 4 | require 'spec_helper_acceptance_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_acceptance_local.rb')) 5 | 6 | PuppetLitmus.configure! 7 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance_local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'singleton' 4 | 5 | class LitmusHelper 6 | include Singleton 7 | include PuppetLitmus 8 | end 9 | 10 | def mysql_version 11 | shell_output = LitmusHelper.instance.run_shell('mysql --version', expect_failures: true) 12 | if shell_output.stdout.match(%r{\d+\.\d+\.\d+}).nil? 13 | # mysql is not yet installed, so we apply this class to install it 14 | LitmusHelper.instance.apply_manifest('include mysql::server', catch_failures: true) 15 | shell_output = LitmusHelper.instance.run_shell('mysql --version') 16 | raise _('unable to get mysql version') if shell_output.stdout.match(%r{\d+\.\d+\.\d+}).nil? 17 | end 18 | shell_output.stdout.match(%r{\d+\.\d+\.\d+})[0] 19 | end 20 | 21 | def supports_xtrabackup? 22 | (os[:family] == 'redhat' && os[:release].to_i > 7) || 23 | os[:family] == 'debian' || 24 | os[:family] == 'ubuntu' 25 | end 26 | 27 | def redhat_9? 28 | os[:family] == 'redhat' && os[:release].to_i == 9 29 | end 30 | 31 | def ubuntu_2204? 32 | os[:family] == 'ubuntu' && os[:release].to_f == 22.04 33 | end 34 | 35 | def sles_15? 36 | os[:family] == 'sles' && os[:release].to_i == 15 37 | end 38 | 39 | def debian_12? 40 | os[:family] == 'debian' && os[:release].to_i == 12 41 | end 42 | 43 | def charset 44 | @charset ||= (debian_12? || ubuntu_2204? || sles_15?) ? 'utf8mb3' : 'utf8' 45 | end 46 | 47 | RSpec.configure do |c| 48 | c.before :suite do 49 | if os[:family] == 'debian' || os[:family] == 'ubuntu' 50 | # needed for the puppet fact 51 | LitmusHelper.instance.apply_manifest("package { 'lsb-release': ensure => installed, }", expect_failures: false) 52 | LitmusHelper.instance.apply_manifest("package { 'ap': ensure => installed, }", expect_failures: false) 53 | end 54 | # needed for the grant tests, not installed on el7 docker images 55 | LitmusHelper.instance.apply_manifest("package { 'which': ensure => installed, }", expect_failures: false) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/spec_helper_local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rspec-puppet-facts' 4 | include RspecPuppetFacts 5 | 6 | if ENV['COVERAGE'] == 'yes' 7 | require 'simplecov' 8 | require 'simplecov-console' 9 | require 'codecov' 10 | 11 | SimpleCov.formatters = [ 12 | SimpleCov::Formatter::HTMLFormatter, 13 | SimpleCov::Formatter::Console, 14 | SimpleCov::Formatter::Codecov, 15 | ] 16 | SimpleCov.start do 17 | track_files 'lib/**/*.rb' 18 | 19 | add_filter '/spec' 20 | 21 | # do not track vendored files 22 | add_filter '/vendor' 23 | add_filter '/.vendor' 24 | 25 | # do not track gitignored files 26 | # this adds about 4 seconds to the coverage check 27 | # this could definitely be optimized 28 | add_filter do |f| 29 | # system returns true if exit status is 0, which with git-check-ignore means file is ignored 30 | system("git check-ignore --quiet #{f.filename}") 31 | end 32 | end 33 | end 34 | 35 | # Override facts 36 | # Taken from: https://github.com/voxpupuli/voxpupuli-test/blob/master/lib/voxpupuli/test/facts.rb 37 | # 38 | # This doesn't use deep_merge because that's highly unpredictable. It can merge 39 | # nested hashes in place, modifying the original. It's also unable to override 40 | # true to false. 41 | # 42 | # A deep copy is obtained by using Marshal so it can be modified in place. Then 43 | # it recursively overrides values. If the result is a hash, it's recursed into. 44 | # 45 | # A typical example: 46 | # 47 | # let(:facts) do 48 | # override_facts(super(), os: {'selinux' => {'enabled' => false}}) 49 | # end 50 | def override_facts(base_facts, **overrides) 51 | facts = Marshal.load(Marshal.dump(base_facts)) 52 | apply_overrides!(facts, overrides, false) 53 | facts 54 | end 55 | 56 | # A private helper to override_facts 57 | def apply_overrides!(facts, overrides, enforce_strings) 58 | overrides.each do |key, value| 59 | # Nested facts are strings 60 | key = key.to_s if enforce_strings 61 | 62 | if value.is_a?(Hash) 63 | facts[key] = {} unless facts.key?(key) 64 | apply_overrides!(facts[key], value, true) 65 | else 66 | facts[key] = value 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/support/mysql_login_path/mysql-5.6/my_print_defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-mysql/cf0ffe8484ae0c9246baadfb8393f37ede7c1823/spec/support/mysql_login_path/mysql-5.6/my_print_defaults -------------------------------------------------------------------------------- /spec/support/mysql_login_path/mysql-5.6/mysql_config_editor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-mysql/cf0ffe8484ae0c9246baadfb8393f37ede7c1823/spec/support/mysql_login_path/mysql-5.6/mysql_config_editor -------------------------------------------------------------------------------- /spec/support/mysql_login_path/mysql-5.7/my_print_defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-mysql/cf0ffe8484ae0c9246baadfb8393f37ede7c1823/spec/support/mysql_login_path/mysql-5.7/my_print_defaults -------------------------------------------------------------------------------- /spec/support/mysql_login_path/mysql-5.7/mysql_config_editor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-mysql/cf0ffe8484ae0c9246baadfb8393f37ede7c1823/spec/support/mysql_login_path/mysql-5.7/mysql_config_editor -------------------------------------------------------------------------------- /spec/support/mysql_login_path/mysql-8.0/my_print_defaults: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-mysql/cf0ffe8484ae0c9246baadfb8393f37ede7c1823/spec/support/mysql_login_path/mysql-8.0/my_print_defaults -------------------------------------------------------------------------------- /spec/support/mysql_login_path/mysql-8.0/mysql_config_editor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-mysql/cf0ffe8484ae0c9246baadfb8393f37ede7c1823/spec/support/mysql_login_path/mysql-8.0/mysql_config_editor -------------------------------------------------------------------------------- /spec/unit/facter/mysql_server_id_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Facter::Util::Fact.to_s do 6 | before(:each) do 7 | Facter.clear 8 | end 9 | 10 | describe 'mysql_server_id' do 11 | context "igalic's laptop" do 12 | before :each do 13 | allow(Facter).to receive(:value).with(:macaddress).and_return('3c:97:0e:69:fb:e1') 14 | end 15 | 16 | it do 17 | Facter.fact(:mysql_server_id).value.to_s.should == '241857808' 18 | end 19 | end 20 | 21 | context 'node with lo only' do 22 | before :each do 23 | allow(Facter).to receive(:value).with(:macaddress).and_return('00:00:00:00:00:00') 24 | end 25 | 26 | it do 27 | Facter.fact(:mysql_server_id).value.to_s.should == '1' 28 | end 29 | end 30 | 31 | context 'test nil case' do 32 | before :each do 33 | allow(Facter).to receive(:value).with(:macaddress).and_return(nil) 34 | end 35 | 36 | it do 37 | Facter.fact(:mysql_server_id).value.to_s.should == '' 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/unit/facter/mysql_version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Facter::Util::Fact.to_s do 6 | before(:each) do 7 | Facter.clear 8 | end 9 | 10 | describe 'mysql_version' do 11 | context 'with mysql' do 12 | before :each do 13 | allow(Facter::Core::Execution).to receive(:which).with('mysql').and_return('fake_mysql_path') 14 | allow(Facter::Core::Execution).to receive(:which).with('mariadb').and_return(false) 15 | allow(Facter::Core::Execution).to receive(:execute).with('mysql --version').and_return('mysql Ver 14.12 Distrib 5.0.95, for redhat-linux-gnu (x86_64) using readline 5.1') 16 | end 17 | 18 | it { 19 | expect(Facter.fact(:mysql_version).value).to eq('5.0.95') 20 | } 21 | end 22 | 23 | context 'with mariadb' do 24 | before :each do 25 | allow(Facter::Core::Execution).to receive(:which).with('mysql').and_return(false) 26 | allow(Facter::Core::Execution).to receive(:which).with('mariadb').and_return('/usr/bin/mariadb') 27 | allow(Facter::Core::Execution).to receive(:execute).with('mariadb --version').and_return('mariadb from 11.4.2-MariaDB, client 15.2 for debian-linux-gnu (x86_64) using EditLine wrapper') 28 | end 29 | 30 | it { 31 | expect(Facter.fact(:mysql_version).value).to eq('11.4.2') 32 | } 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/unit/facter/mysqld_version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Facter::Util::Fact.to_s do 6 | before(:each) do 7 | Facter.clear 8 | end 9 | 10 | describe 'mysqld_version' do 11 | context 'with mysqld' do 12 | before :each do 13 | allow(Facter::Core::Execution).to receive(:which).with('mysqld').and_return('/usr/sbin/mysqld') 14 | allow(Facter::Core::Execution).to receive(:which).with('mariadbd').and_return(false) 15 | allow(Facter::Core::Execution).to receive(:execute).with('env PATH=$PATH:/usr/libexec mysqld --no-defaults -V 2>/dev/null') 16 | .and_return('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') 17 | end 18 | 19 | it { 20 | expect(Facter.fact(:mysqld_version).value).to eq('mysqld Ver 5.5.49-37.9 for Linux on x86_64 (Percona Server (GPL), Release 37.9, Revision efa0073)') 21 | } 22 | end 23 | 24 | context 'with mariadb' do 25 | before :each do 26 | allow(Facter::Core::Execution).to receive(:which).with('mysqld').and_return(false) 27 | allow(Facter::Core::Execution).to receive(:which).with('/usr/libexec/mysqld').and_return(false) 28 | allow(Facter::Core::Execution).to receive(:which).with('mariadbd').and_return('/usr/sbin/mariadbd') 29 | allow(Facter::Core::Execution).to receive(:execute).with('mariadbd --no-defaults -V 2>/dev/null') 30 | .and_return('mariadbd Ver 11.4.2-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)') 31 | end 32 | 33 | it { 34 | expect(Facter.fact(:mysqld_version).value).to eq('mariadbd Ver 11.4.2-MariaDB-ubu2404 for debian-linux-gnu on x86_64 (mariadb.org binary distribution)') 35 | } 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/mysql_database/mysql_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Puppet::Type.type(:mysql_database).provider(:mysql) do 6 | let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } 7 | let(:parsed_databases) { ['information_schema', 'mydb', 'mysql', 'performance_schema', 'test'] } 8 | let(:provider) { resource.provider } 9 | let(:instance) { provider.class.instances.first } 10 | let(:resource) do 11 | Puppet::Type.type(:mysql_database).new( 12 | ensure: :present, charset: 'latin1', 13 | collate: 'latin1_swedish_ci', name: 'new_database', 14 | provider: described_class.name 15 | ) 16 | end 17 | let(:raw_databases) do 18 | <<~SQL_OUTPUT 19 | information_schema 20 | mydb 21 | mysql 22 | performance_schema 23 | test 24 | SQL_OUTPUT 25 | end 26 | 27 | before :each do 28 | allow(Facter.fact(:value)).to receive(:root_home).and_return('/root') 29 | allow(Puppet::Util).to receive(:which).with('mysql').and_return('/usr/bin/mysql') 30 | allow(File).to receive(:file?).with('/root/.my.cnf').and_return(true) 31 | allow(provider.class).to receive(:mysql_caller).with('show databases', 'regular').and_return('new_database') 32 | allow(provider.class).to receive(:mysql_caller).with(["show variables like '%_database'", 'new_database'], 'regular').and_return("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") # rubocop:disable Layout/LineLength 33 | end 34 | 35 | describe 'self.instances' do 36 | it 'returns an array of databases' do 37 | allow(provider.class).to receive(:mysql_caller).with('show databases', 'regular').and_return(raw_databases) 38 | raw_databases.each_line do |db| 39 | allow(provider.class).to receive(:mysql_caller).with(["show variables like '%_database'", db.chomp], 'regular').and_return("character_set_database latin1\ncollation_database latin1_swedish_ci\nskip_show_database OFF") # rubocop:disable Layout/LineLength 40 | end 41 | databases = provider.class.instances.map(&:name) 42 | expect(parsed_databases).to match_array(databases) 43 | end 44 | end 45 | 46 | describe 'self.prefetch' do 47 | it 'exists' do 48 | provider.class.instances 49 | provider.class.prefetch({}) 50 | end 51 | end 52 | 53 | describe 'create' do 54 | it 'makes a database' do 55 | expect(provider.class).to receive(:mysql_caller).with("create database if not exists `#{resource[:name]}` character set `#{resource[:charset]}` collate `#{resource[:collate]}`", 'regular') 56 | expect(provider).to receive(:exists?).and_return(true) 57 | expect(provider.create).to be_truthy 58 | end 59 | end 60 | 61 | describe 'destroy' do 62 | it 'removes a database if present' do 63 | expect(provider.class).to receive(:mysql_caller).with("drop database if exists `#{resource[:name]}`", 'regular') 64 | expect(provider).to receive(:exists?).and_return(false) 65 | expect(provider.destroy).to be_truthy 66 | end 67 | end 68 | 69 | describe 'exists?' do 70 | it 'checks if database exists' do 71 | expect(instance).to be_exists 72 | end 73 | end 74 | 75 | describe 'self.defaults_file' do 76 | before :each do 77 | allow(Facter).to receive(:value).with(:root_home).and_return('/root') 78 | end 79 | 80 | it 'sets --defaults-extra-file' do 81 | allow(File).to receive(:file?).with('/root/.my.cnf').and_return(true) 82 | expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' 83 | end 84 | 85 | it 'fails if file missing' do 86 | allow(File).to receive(:file?).with('/root/.my.cnf').and_return(false) 87 | expect(provider.defaults_file).to be_nil 88 | end 89 | end 90 | 91 | describe 'charset' do 92 | it 'returns a charset' do 93 | expect(instance.charset).to eq('latin1') 94 | end 95 | end 96 | 97 | describe 'charset=' do 98 | it 'changes the charset' do 99 | expect(provider.class).to receive(:mysql_caller).with("alter database `#{resource[:name]}` CHARACTER SET blah", 'regular').and_return('0') 100 | provider.charset = 'blah' 101 | end 102 | end 103 | 104 | describe 'collate' do 105 | it 'returns a collate' do 106 | expect(instance.collate).to eq('latin1_swedish_ci') 107 | end 108 | end 109 | 110 | describe 'collate=' do 111 | it 'changes the collate' do 112 | expect(provider.class).to receive(:mysql_caller).with("alter database `#{resource[:name]}` COLLATE blah", 'regular').and_return('0') 113 | provider.collate = 'blah' 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/mysql_login_path/mysql_login_path_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet/resource_api/base_context' 5 | 6 | ensure_module_defined('Puppet::Provider::MysqlLoginPath') 7 | require 'puppet/provider/mysql_login_path/mysql_login_path' 8 | 9 | RSpec.describe Puppet::Provider::MysqlLoginPath::MysqlLoginPath do 10 | subject(:provider) { described_class.new } 11 | 12 | let(:context) { instance_double(Puppet::ResourceApi::BaseContext) } 13 | let(:wait_thr) { instance_double(wait_thr) } 14 | let(:wait_thr_value) { instance_double(wait_thr_value) } 15 | let(:sensitive_secure) { Puppet::Provider::MysqlLoginPath::Sensitive.new('secure') } 16 | let(:sensitive_more_secure) { Puppet::Provider::MysqlLoginPath::Sensitive.new('more_secure') } 17 | 18 | before :each do 19 | # Puppet::Util::Execution.stubs(:execute).with(['/usr/bin/getent', 'passwd', 'root'], failonfail: true).returns('root:x:0:0:root:/root:/bin/bash') 20 | allow(Puppet::Util::Execution).to receive(:execute).with(['/usr/bin/getent', 'passwd', 'root'], failonfail: true).and_return('root:x:0:0:root:/root:/bin/bash') 21 | allow(Puppet::Util::Execution).to receive(:execute).with(['/usr/bin/mysql_config_editor', 'print', '--all'], failonfail: true, uid: 'root', custom_environment: { 'HOME' => '/root' }) 22 | .and_return("[local_tcp]\nuser = root\npassword = *****\nhost = 127.0.0.1\nport = 3306") 23 | allow(Puppet::Util::Execution).to receive(:execute).with(['/usr/bin/mysql_config_editor', 'remove', '-G', 'local_socket'], failonfail: true, uid: 'root', custom_environment: { 'HOME' => '/root' }) 24 | allow(Puppet::Util::Execution).to receive(:execute).with(['/usr/bin/my_print_defaults', '-s', 'local_tcp'], failonfail: true, uid: 'root', custom_environment: { 'HOME' => '/root' }) 25 | .and_return("--user=root\n--password=secure\n--host=127.0.0.1\n--port=3306") 26 | allow(Puppet::Util::Execution).to receive(:execute).with(['/usr/bin/my_print_defaults', '-s', 'local_socket'], failonfail: true, uid: 'root', custom_environment: { 'HOME' => '/root' }) 27 | .and_return("--user=root\n--password=more_secure\n--host=localhost\n--socket=/var/run/mysql.sock") 28 | 29 | allow(Puppet::Util::SUIDManager).to receive(:asuser).with('root').and_return(`(exit 0)`) 30 | allow(PTY).to receive(:spawn) 31 | .with({ 'HOME' => '/root' }, 32 | '/usr/bin/mysql_config_editor set --skip-warn -G local_socket -h localhost -u root ' \ 33 | '-S /var/run/mysql/mysql.sock -p') 34 | .and_return(`(exit 0)`) 35 | 36 | allow(PTY).to receive(:spawn) 37 | .with({ 'HOME' => '/root' }, 38 | '/usr/bin/mysql_config_editor set --skip-warn -G local_socket -h 127.0.0.1 -u root -P 3306 -p') 39 | .and_return(`(exit 0)`) 40 | end 41 | 42 | describe '#get' do 43 | it 'processes resources' do 44 | expect(provider.get(context, [{ owner: 'root' }])).to eq [ 45 | { 46 | ensure: 'present', 47 | host: '127.0.0.1', 48 | name: 'local_tcp', 49 | owner: 'root', 50 | password: sensitive_secure, 51 | port: 3306, 52 | socket: nil, 53 | title: 'local_tcp-root', 54 | user: 'root' 55 | }, 56 | ] 57 | end 58 | end 59 | 60 | describe 'create(context, name, should)' do 61 | it 'creates the resource' do 62 | provider.create(context, { name: 'local_socket', owner: 'root' }, 63 | name: 'local_socket', 64 | owner: 'root', 65 | host: 'localhost', 66 | user: 'root', 67 | password: sensitive_more_secure, 68 | socket: '/var/run/mysql/mysql.sock', 69 | ensure: 'present') 70 | end 71 | end 72 | 73 | describe 'update(context, name, should)' do 74 | it 'updates the resource' do 75 | provider.update(context, { name: 'local_socket', owner: 'root' }, 76 | name: 'local_socket', 77 | owner: 'root', 78 | host: '127.0.0.1', 79 | user: 'root', 80 | password: sensitive_more_secure, 81 | port: 3306, 82 | ensure: 'present') 83 | end 84 | end 85 | 86 | describe 'delete(context, name)' do 87 | it 'deletes the resource' do 88 | provider.delete(context, name: 'local_socket', owner: 'root') 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/mysql_login_path/sensitive_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | ensure_module_defined('Puppet::Provider::MysqlLoginPath') 6 | require 'puppet/provider/mysql_login_path/sensitive' 7 | require 'psych' 8 | 9 | RSpec.describe Puppet::Provider::MysqlLoginPath::Sensitive do 10 | subject(:sensitive) { described_class.new('secret') } 11 | 12 | describe 'Puppet::Provider::MysqlLoginPath::Sensitive' do 13 | it 'encodes its value correctly into transactionstore.yaml' do 14 | psych_encoded = Psych.load(Psych.dump(sensitive)) 15 | expect(psych_encoded).to eq 'Puppet::Provider::MysqlLoginPath::Sensitive <>' 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/mysql_plugin/mysql_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Puppet::Type.type(:mysql_plugin).provider(:mysql) do 6 | let(:defaults_file) { '--defaults-extra-file=/root/.my.cnf' } 7 | let(:provider) { resource.provider } 8 | let(:instance) { provider.class.instances.first } 9 | let(:resource) do 10 | Puppet::Type.type(:mysql_plugin).new( 11 | ensure: :present, 12 | soname: 'auth_socket.so', 13 | name: 'auth_socket', 14 | provider: described_class.name, 15 | ) 16 | end 17 | 18 | before :each do 19 | allow(Facter).to receive(:value).with(:root_home).and_return('/root') 20 | allow(Puppet::Util).to receive(:which).with('mysql').and_return('/usr/bin/mysql') 21 | allow(File).to receive(:file?).with('/root/.my.cnf').and_return(true) 22 | allow(provider.class).to receive(:mysql_caller).with('show plugins', 'regular').and_return('auth_socket ACTIVE AUTHENTICATION auth_socket.so GPL') 23 | end 24 | 25 | describe 'self.prefetch' do 26 | it 'exists' do 27 | provider.class.instances 28 | provider.class.prefetch({}) 29 | end 30 | end 31 | 32 | describe 'create' do 33 | it 'loads a plugin' do 34 | expect(provider.class).to receive(:mysql_caller).with("install plugin #{resource[:name]} soname '#{resource[:soname]}'", 'regular') 35 | expect(provider).to receive(:exists?).and_return(true) 36 | expect(provider.create).to be_truthy 37 | end 38 | end 39 | 40 | describe 'destroy' do 41 | it 'unloads a plugin if present' do 42 | expect(provider.class).to receive(:mysql_caller).with("uninstall plugin #{resource[:name]}", 'regular') 43 | expect(provider).to receive(:exists?).and_return(false) 44 | expect(provider.destroy).to be_truthy 45 | end 46 | end 47 | 48 | describe 'exists?' do 49 | it 'checks if plugin exists' do 50 | expect(instance).to be_exists 51 | end 52 | end 53 | 54 | describe 'self.defaults_file' do 55 | it 'sets --defaults-extra-file' do 56 | allow(File).to receive(:file?).with('/root/.my.cnf').and_return(true) 57 | expect(provider.defaults_file).to eq '--defaults-extra-file=/root/.my.cnf' 58 | end 59 | 60 | it 'fails if file missing' do 61 | allow(File).to receive(:file?).with('/root/.my.cnf').and_return(false) 62 | expect(provider.defaults_file).to be_nil 63 | end 64 | end 65 | 66 | describe 'soname' do 67 | it 'returns a soname' do 68 | expect(instance.soname).to eq('auth_socket.so') 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/mysql_database_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet' 4 | require 'puppet/type/mysql_database' 5 | describe Puppet::Type.type(:mysql_database) do 6 | let(:user) { Puppet::Type.type(:mysql_database).new(name: 'test', charset: 'utf8', collate: 'utf8_blah_ci') } 7 | 8 | it 'accepts a database name' do 9 | expect(user[:name]).to eq('test') 10 | end 11 | 12 | it 'accepts a charset' do 13 | user[:charset] = 'latin1' 14 | expect(user[:charset]).to eq('latin1') 15 | end 16 | 17 | it 'accepts a collate' do 18 | user[:collate] = 'latin1_swedish_ci' 19 | expect(user[:collate]).to eq('latin1_swedish_ci') 20 | end 21 | 22 | it 'requires a name' do 23 | expect { 24 | Puppet::Type.type(:mysql_database).new({}) 25 | }.to raise_error(Puppet::Error, 'Title or name must be provided') 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/mysql_grant_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet' 4 | require 'puppet/type/mysql_grant' 5 | require 'spec_helper' 6 | describe Puppet::Type.type(:mysql_grant) do 7 | let(:user) { Puppet::Type.type(:mysql_grant).new(name: 'foo@localhost/*.*', privileges: ['ALL'], table: ['*.*'], user: 'foo@localhost') } 8 | 9 | it 'accepts a grant name' do 10 | expect(user[:name]).to eq('foo@localhost/*.*') 11 | end 12 | 13 | it 'accepts ALL privileges' do 14 | user[:privileges] = 'ALL' 15 | expect(user[:privileges]).to eq(['ALL']) 16 | end 17 | 18 | context 'PROXY privilege with mysql greater than or equal to 5.5.0' do 19 | before :each do 20 | allow(Facter).to receive(:value).with(:mysql_version).and_return('5.5.0') 21 | end 22 | 23 | it 'does not raise error' do 24 | user[:privileges] = 'PROXY' 25 | user[:table] = 'proxy_user@proxy_host' 26 | expect(user[:privileges]).to eq(['PROXY']) 27 | end 28 | end 29 | 30 | context 'PROXY privilege with mysql greater than or equal to 5.4.0' do 31 | before :each do 32 | allow(Facter).to receive(:value).with(:mysql_version).and_return('5.4.0') 33 | end 34 | 35 | it 'raises error' do 36 | expect { 37 | user[:privileges] = 'PROXY' 38 | }.to raise_error(Puppet::ResourceError, %r{PROXY user not supported on mysql versions < 5.5.0}) 39 | end 40 | end 41 | 42 | it 'accepts a table' do 43 | user[:table] = '*.*' 44 | expect(user[:table]).to eq('*.*') 45 | end 46 | 47 | it 'accepts @ for table' do 48 | user[:table] = '@' 49 | expect(user[:table]).to eq('@') 50 | end 51 | 52 | it 'accepts proxy user for table' do 53 | user[:table] = 'proxy_user@proxy_host' 54 | expect(user[:table]).to eq('proxy_user@proxy_host') 55 | end 56 | 57 | it 'accepts a user' do 58 | user[:user] = 'foo@localhost' 59 | expect(user[:user]).to eq('foo@localhost') 60 | end 61 | 62 | it 'requires a name' do 63 | expect { 64 | Puppet::Type.type(:mysql_grant).new({}) 65 | }.to raise_error(Puppet::Error, 'Title or name must be provided') 66 | end 67 | 68 | it 'requires the name to match the user and table #general' do 69 | expect { 70 | Puppet::Type.type(:mysql_grant).new(name: 'foo@localhost/*.*', privileges: ['ALL'], table: ['*.*'], user: 'foo@localhost') 71 | }.not_to raise_error 72 | end 73 | 74 | it 'requires the name to match the user and table #specific' do 75 | expect { 76 | Puppet::Type.type(:mysql_grant).new(name: 'foo', privileges: ['ALL'], table: ['*.*'], user: 'foo@localhost') 77 | }.to raise_error %r{mysql_grant: `name` `parameter` must match user@host/table format} 78 | end 79 | 80 | describe 'it should munge privileges' do 81 | it 'to just ALL' do 82 | user = Puppet::Type.type(:mysql_grant).new( 83 | name: 'foo@localhost/*.*', table: ['*.*'], user: 'foo@localhost', 84 | privileges: ['ALL'] 85 | ) 86 | expect(user[:privileges]).to eq(['ALL']) 87 | end 88 | 89 | it 'to upcase and ordered' do 90 | user = Puppet::Type.type(:mysql_grant).new( 91 | name: 'foo@localhost/*.*', table: ['*.*'], user: 'foo@localhost', 92 | privileges: ['select', 'Insert'] 93 | ) 94 | expect(user[:privileges]).to eq(['INSERT', 'SELECT']) 95 | end 96 | 97 | it 'ordered including column privileges' do 98 | user = Puppet::Type.type(:mysql_grant).new( 99 | name: 'foo@localhost/*.*', table: ['*.*'], user: 'foo@localhost', 100 | privileges: ['SELECT(Host,Address)', 'Insert'] 101 | ) 102 | expect(user[:privileges]).to eq(['INSERT', 'SELECT (Address, Host)']) 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/mysql_login_path_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet' 4 | require 'puppet/type/mysql_login_path' 5 | 6 | describe Puppet::Type.type(:mysql_login_path) do 7 | it 'loads' do 8 | expect(Puppet::Type.type(:mysql_login_path)).not_to be_nil 9 | end 10 | 11 | it 'requires a name' do 12 | expect { 13 | Puppet::Type.type(:mysql_login_path).new({}) 14 | }.to raise_error(Puppet::Error, 'Title or name must be provided') 15 | end 16 | 17 | context 'using login path with socket' do 18 | let(:login_path) do 19 | Puppet::Type.type(:mysql_login_path).new( 20 | name: 'local_socket', 21 | host: 'localhost', 22 | user: 'root', 23 | password: Puppet::Pops::Types::PSensitiveType::Sensitive.new('secure'), 24 | socket: '/var/run/mysql/mysql.sock', 25 | ) 26 | end 27 | 28 | it 'accepts a name' do 29 | login_path[:name] = 'local_socket' 30 | expect(login_path[:name]).to eq('local_socket') 31 | end 32 | 33 | it 'accepts a host' do 34 | login_path[:host] = '10.0.0.1' 35 | expect(login_path[:host]).to eq('10.0.0.1') 36 | end 37 | 38 | it 'accepts a user' do 39 | login_path[:user] = 'user1' 40 | expect(login_path[:user]).to eq('user1') 41 | end 42 | 43 | it 'accepts a password' do 44 | login_path[:password] = Puppet::Pops::Types::PSensitiveType::Sensitive.new('even_more_secure') 45 | expect(login_path[:password].unwrap).to eq('even_more_secure') 46 | end 47 | end 48 | 49 | context 'using login path with tcp' do 50 | let(:login_path) do 51 | Puppet::Type.type(:mysql_login_path).new( 52 | name: 'local_tcp', 53 | host: '127.0.0.1', 54 | user: 'root', 55 | password: Puppet::Pops::Types::PSensitiveType::Sensitive.new('secure'), 56 | port: 3306, 57 | ) 58 | end 59 | 60 | it 'accepts a port' do 61 | login_path[:port] = 3307 62 | expect(login_path[:port]).to eq(3307) 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/mysql_plugin_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet' 4 | require 'puppet/type/mysql_plugin' 5 | describe Puppet::Type.type(:mysql_plugin) do 6 | let(:plugin) { Puppet::Type.type(:mysql_plugin).new(name: 'test', soname: 'test.so') } 7 | 8 | it 'accepts a plugin name' do 9 | expect(plugin[:name]).to eq('test') 10 | end 11 | 12 | it 'accepts a library name' do 13 | plugin[:soname] = 'test.so' 14 | expect(plugin[:soname]).to eq('test.so') 15 | end 16 | 17 | it 'requires a name' do 18 | expect { 19 | Puppet::Type.type(:mysql_plugin).new({}) 20 | }.to raise_error(Puppet::Error, 'Title or name must be provided') 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/mysql_user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet' 4 | require 'puppet/type/mysql_user' 5 | require 'spec_helper' 6 | describe Puppet::Type.type(:mysql_user) do 7 | context 'On MySQL 5.x' do 8 | before :each do 9 | allow(Facter).to receive(:value).with(:mysql_version).and_return('5.6.24') 10 | end 11 | 12 | it 'fails with a long user name' do 13 | expect { 14 | Puppet::Type.type(:mysql_user).new(name: '12345678901234567@localhost', password_hash: 'pass') 15 | }.to raise_error %r{MySQL usernames are limited to a maximum of 16 characters} 16 | end 17 | end 18 | 19 | context 'On MariaDB 10.0.0+' do 20 | let(:user) { Puppet::Type.type(:mysql_user).new(name: '12345678901234567@localhost', password_hash: 'pass') } 21 | 22 | before :each do 23 | allow(Facter).to receive(:value).with(:mysql_version).and_return('10.0.19') 24 | end 25 | 26 | it 'succeeds with a long user name on MariaDB' do 27 | expect(user[:name]).to eq('12345678901234567@localhost') 28 | end 29 | end 30 | 31 | it 'requires a name' do 32 | expect { 33 | Puppet::Type.type(:mysql_user).new({}) 34 | }.to raise_error(Puppet::Error, 'Title or name must be provided') 35 | end 36 | 37 | context 'using foo@localhost' do 38 | let(:user) { Puppet::Type.type(:mysql_user).new(name: 'foo@localhost', password_hash: 'pass') } 39 | 40 | it 'accepts a user name' do 41 | expect(user[:name]).to eq('foo@localhost') 42 | end 43 | 44 | it 'accepts a password' do 45 | user[:password_hash] = 'foo' 46 | expect(user[:password_hash]).to eq('foo') 47 | end 48 | 49 | it 'accepts an empty password' do 50 | user[:password_hash] = '' 51 | expect(user[:password_hash]).to eq('') 52 | end 53 | end 54 | 55 | context 'using foo@LocalHost' do 56 | let(:user) { Puppet::Type.type(:mysql_user).new(name: 'foo@LocalHost', password_hash: 'pass') } 57 | 58 | it 'lowercases the user name' do 59 | expect(user[:name]).to eq('foo@localhost') 60 | end 61 | end 62 | 63 | context 'using foo@192.168.1.0/255.255.255.0' do 64 | let(:user) { Puppet::Type.type(:mysql_user).new(name: 'foo@192.168.1.0/255.255.255.0', password_hash: 'pass') } 65 | 66 | it 'creates the user with the netmask' do 67 | expect(user[:name]).to eq('foo@192.168.1.0/255.255.255.0') 68 | end 69 | end 70 | 71 | context 'using allo_wed$char@localhost' do 72 | let(:user) { Puppet::Type.type(:mysql_user).new(name: 'allo_wed$char@localhost', password_hash: 'pass') } 73 | 74 | it 'accepts a user name' do 75 | expect(user[:name]).to eq('allo_wed$char@localhost') 76 | end 77 | end 78 | 79 | context 'ensure the default \'debian-sys-main\'@localhost user can be parsed' do 80 | let(:user) { Puppet::Type.type(:mysql_user).new(name: '\'debian-sys-maint\'@localhost', password_hash: 'pass') } 81 | 82 | it 'accepts a user name' do 83 | expect(user[:name]).to eq('\'debian-sys-maint\'@localhost') 84 | end 85 | end 86 | 87 | context 'using a quoted 16 char username' do 88 | let(:user) { Puppet::Type.type(:mysql_user).new(name: '"debian-sys-maint"@localhost', password_hash: 'pass') } 89 | 90 | it 'accepts a user name' do 91 | expect(user[:name]).to eq('"debian-sys-maint"@localhost') 92 | end 93 | end 94 | 95 | context 'using a quoted username that is too long' do 96 | before :each do 97 | allow(Facter).to receive(:value).with(:mysql_version).and_return('5.6.24') 98 | end 99 | 100 | it 'fails with a size error' do 101 | expect { 102 | Puppet::Type.type(:mysql_user).new(name: '"debian-sys-maint2"@localhost', password_hash: 'pass') 103 | }.to raise_error %r{MySQL usernames are limited to a maximum of 16 characters} 104 | end 105 | end 106 | 107 | context 'using `speci!al#`@localhost' do 108 | let(:user) { Puppet::Type.type(:mysql_user).new(name: '`speci!al#`@localhost', password_hash: 'pass') } 109 | 110 | it 'accepts a quoted user name with special chatracters' do 111 | expect(user[:name]).to eq('`speci!al#`@localhost') 112 | end 113 | end 114 | 115 | context 'using in-valid@localhost' do 116 | let(:user) { Puppet::Type.type(:mysql_user).new(name: 'in-valid@localhost', password_hash: 'pass') } 117 | 118 | it 'accepts a user name with special chatracters' do 119 | expect(user[:name]).to eq('in-valid@localhost') 120 | end 121 | end 122 | 123 | context 'using "misquoted@localhost' do 124 | it 'fails with a misquoted username is used' do 125 | expect { 126 | Puppet::Type.type(:mysql_user).new(name: '"misquoted@localhost', password_hash: 'pass') 127 | }.to raise_error %r{Invalid database user "misquoted@localhost} 128 | end 129 | end 130 | 131 | context 'using invalid options' do 132 | it 'fails with an invalid option' do 133 | expect { 134 | Puppet::Type.type(:mysql_user).new(name: 'misquoted@localhost', password_hash: 'pass', tls_options: ['SOMETHING_ELSE']) 135 | }.to raise_error %r{Invalid tls option} 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /tasks/export.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Allows you to backup your database to local file.", 3 | "input_method": "stdin", 4 | "parameters": { 5 | "database": { 6 | "description": "Database to connect to", 7 | "type": "Optional[String[1]]" 8 | }, 9 | "user": { 10 | "description": "The user", 11 | "type": "Optional[String[1]]" 12 | }, 13 | "password": { 14 | "description": "The password", 15 | "type": "Optional[String[1]]" 16 | }, 17 | "file": { 18 | "description": "Path to file you want backup to", 19 | "type": "String[1]" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tasks/export.rb: -------------------------------------------------------------------------------- 1 | #!/opt/puppetlabs/puppet/bin/ruby 2 | # frozen_string_literal: true 3 | 4 | require 'json' 5 | require 'open3' 6 | require 'puppet' 7 | 8 | def get(file, database, user, password) 9 | cmd_string = 'mysqldump' 10 | cmd_string << " --databases #{database}" unless database.nil? 11 | cmd_string << " --user=#{user}" unless user.nil? 12 | cmd_string << " --password=#{password}" unless password.nil? 13 | cmd_string << " > #{file}" unless file.nil? 14 | stdout, stderr, status = Open3.capture3(cmd_string) 15 | raise Puppet::Error, _("stderr: '%{stderr}'" % { stderr: stderr }) if status != 0 16 | 17 | { status: stdout.strip } 18 | end 19 | 20 | params = JSON.parse($stdin.read) 21 | database = params['database'] 22 | user = params['user'] 23 | password = params['password'] 24 | file = params['file'] 25 | 26 | begin 27 | result = get(file, database, user, password) 28 | puts result.to_json 29 | exit 0 30 | rescue Puppet::Error => e 31 | puts({ status: 'failure', error: e.message }.to_json) 32 | exit 1 33 | end 34 | -------------------------------------------------------------------------------- /tasks/sql.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Allows you to execute arbitary SQL", 3 | "input_method": "stdin", 4 | "parameters": { 5 | "database": { 6 | "description": "Database to connect to", 7 | "type": "Optional[String[1]]" 8 | }, 9 | "user": { 10 | "description": "The user", 11 | "type": "Optional[String[1]]" 12 | }, 13 | "password": { 14 | "description": "The password", 15 | "type": "Optional[String[1]]" 16 | }, 17 | "sql": { 18 | "description": "The SQL you want to execute", 19 | "type": "String[1]" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tasks/sql.rb: -------------------------------------------------------------------------------- 1 | #!/opt/puppetlabs/puppet/bin/ruby 2 | # frozen_string_literal: true 3 | 4 | require 'json' 5 | require 'open3' 6 | require 'puppet' 7 | 8 | def get(sql, database, user, password) 9 | cmd = ['mysql', '-e', "#{sql} "] 10 | cmd << "--database=#{database}" unless database.nil? 11 | cmd << "--user=#{user}" unless user.nil? 12 | cmd << "--password=#{password}" unless password.nil? 13 | stdout, stderr, status = Open3.capture3(*cmd) 14 | raise Puppet::Error, _("stderr: '%{stderr}'" % { stderr: stderr }) if status != 0 15 | 16 | { status: stdout.strip } 17 | end 18 | 19 | params = JSON.parse($stdin.read) 20 | database = params['database'] 21 | user = params['user'] 22 | password = params['password'] 23 | sql = params['sql'] 24 | 25 | begin 26 | result = get(sql, database, user, password) 27 | puts result.to_json 28 | exit 0 29 | rescue Puppet::Error => e 30 | puts({ status: 'failure', error: e.message }.to_json) 31 | exit 1 32 | end 33 | -------------------------------------------------------------------------------- /templates/meb.cnf.epp: -------------------------------------------------------------------------------- 1 | ### MANAGED BY PUPPET ### 2 | 3 | <% $options.map |Any $k, Any $v| { -%> 4 | <% if $v.is_a(Hash) { -%> 5 | [<%= $k %>] 6 | <% $v.map |Any $ki, Any $vi| { -%> 7 | <% if $vi == true or $v == '' {-%> 8 | <%= $ki %> 9 | <% } elsif $vi.is_a(Hash) { -%> 10 | <% $vi.each |$vii| { -%> 11 | <%= $ki %> = <%= $vii %> 12 | <% } -%> 13 | <% } elsif !($vi == '' or $vi == undef ) { -%> 14 | <%= $ki %> = <%= $vi %> 15 | <% } -%> 16 | <% } -%> 17 | <% } %> 18 | <% } -%> 19 | -------------------------------------------------------------------------------- /templates/my.cnf.epp: -------------------------------------------------------------------------------- 1 | ### MANAGED BY PUPPET ### 2 | 3 | <% sort($options.map |$key, $value| { [$key, $value] }).map |$v| { -%> 4 | <% if type($v[1]) =~ Type[Hash] { -%> 5 | [<%= $v[0] %>] 6 | <%sort($v[1].map |$key, $value| { [$key, $value] }).map |$vi| { -%> 7 | <%- if ($vi[0] == 'ssl-disable') or ($vi[0] =~ /^ssl/ and $v[1]['ssl-disable'] == true) or ($vi[0] =~ /^ssl-/ and $v[1]['ssl'] == false) { -%> 8 | <%- next -%> 9 | <%- } elsif $vi[1] == true or $vi[1] == '' { -%> 10 | <%= $vi[0] -%> 11 | <%- } elsif type($vi[1]) =~ Type[Array] { -%> 12 | <%- $vi[1].each |$vii| { -%> 13 | <%-$base = $vi[0]-%> 14 | <%= $base %> = <%= $vii %> 15 | <%- } -%> 16 | <%- } elsif !($vi[1] ==nil or $vi[1]=='' or $vi[1]==undef) { -%> 17 | <%-$base = $vi[0]-%> 18 | <%= $base %> = <%= $vi[1] -%> 19 | <% } %> 20 | <% } %> 21 | <% } %> 22 | <% } %> 23 | <% if $includedir and $includedir != '' { -%> 24 | !includedir <%= $includedir %> 25 | <% } -%> 26 | -------------------------------------------------------------------------------- /templates/my.cnf.pass.epp: -------------------------------------------------------------------------------- 1 | <%['mysql', 'client', 'mysqldump', 'mysqladmin', 'mysqlcheck'].each |$section| { %> 2 | [<%= $section -%>] 3 | user=root 4 | host=localhost 5 | <% if $root_password_set { -%> 6 | password='<%= $root_password %>' 7 | <% } -%> 8 | socket=<%= $options['client']['socket'] %> 9 | <% } %> 10 | -------------------------------------------------------------------------------- /templates/mysqlbackup.sh.epp: -------------------------------------------------------------------------------- 1 | <%- if $kernel == 'Linux' { -%> 2 | #!/bin/bash 3 | <%- } else { -%> 4 | #!/bin/sh 5 | <%- } -%> 6 | # 7 | # MySQL Backup Script 8 | # Dumps mysql databases to a file for another backup tool to pick up. 9 | # 10 | # MySQL code: 11 | # GRANT SELECT, RELOAD, LOCK TABLES ON *.* TO 'user'@'localhost' 12 | # IDENTIFIED BY 'password'; 13 | # FLUSH PRIVILEGES; 14 | # 15 | ##### START CONFIG ################################################### 16 | 17 | USER=<%= $backupuser %> 18 | PASS='<%= $backuppassword_unsensitive %>' 19 | MAX_ALLOWED_PACKET=<%= $maxallowedpacket %> 20 | DIR=<%= $backupdir %> 21 | ROTATE=<%= [ Integer($backuprotate) - 1, 0 ].max %> 22 | 23 | # Create temporary mysql cnf file. 24 | TMPFILE=`mktemp /tmp/backup.XXXXXX` || exit 1 25 | echo -e "[client]\npassword=$PASS\nuser=$USER\nmax_allowed_packet=$MAX_ALLOWED_PACKET" > $TMPFILE 26 | 27 | <% if $prescript { -%> 28 | <%- [$prescript].flatten().filter |$value| {$value}.each |$script| { %> 29 | <%= $script %> 30 | <%- } -%> 31 | <% } -%> 32 | 33 | # Ensure backup directory exist. 34 | mkdir -p $DIR 35 | 36 | PREFIX=mysql_backup_ 37 | <% if $ignore_events { %> 38 | ADDITIONAL_OPTIONS="--ignore-table=mysql.event" 39 | <% } else { %> 40 | ADDITIONAL_OPTIONS="--events" 41 | <% } %> 42 | 43 | <%# Only include routines or triggers if we're doing a file per database -%> 44 | <%# backup. This happens if we named databases, or if we explicitly set -%> 45 | <%# file per database mode -%> 46 | <% if !$backupdatabases.empty or $file_per_database { -%> 47 | <% if $include_triggers { -%> 48 | ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --triggers" 49 | <% } else { -%> 50 | ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-triggers" 51 | <% } -%> 52 | <% if $include_routines { -%> 53 | ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --routines" 54 | <% } else { -%> 55 | ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS --skip-routines" 56 | <% } -%> 57 | <% } -%> 58 | 59 | <%- if $optional_args and type($optional_args) =~ Type(Array) { -%> 60 | <% $optional_args.each |$arg| { -%> 61 | ADDITIONAL_OPTIONS="$ADDITIONAL_OPTIONS <%= $arg %>" 62 | <%- } -%> 63 | <%- } -%> 64 | ##### STOP CONFIG #################################################### 65 | PATH=<%= $execpath %> 66 | 67 | <%- if $kernel == 'Linux' { -%> 68 | set -o pipefail 69 | <%- } -%> 70 | 71 | 72 | 73 | cleanup() 74 | { 75 | find "${DIR}/" -maxdepth 1 -type f -name "${PREFIX}*.sql*" -mtime +${ROTATE} -print0 | xargs -0 -r rm -f 76 | } 77 | 78 | <% if $delete_before_dump { -%> 79 | cleanup 80 | 81 | <% } -%> 82 | <% if $backupdatabases.empty { -%> 83 | <% if $file_per_database { -%> 84 | <% if $excludedatabases.empty { -%> 85 | mysql --defaults-extra-file=$TMPFILE -s -r -N -e 'SHOW DATABASES' | while read dbname 86 | <%} else {-%> 87 | mysql --defaults-extra-file=$TMPFILE -s -r -N -e 'SHOW DATABASES' | grep -v '^\(<%= $excludedatabases.join('\\|') %>\)$' | while read dbname 88 | <% } -%> 89 | do 90 | <%= $backupmethod %> --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ 91 | ${ADDITIONAL_OPTIONS} \ 92 | ${dbname} <% if $backupcompress { %>| <%= $compression_command %> <% } %>> ${DIR}/${PREFIX}${dbname}_`date +%Y%m%d-%H%M%S`.sql<% if $backupcompress { %><%= $compression_extension %><% } %> 93 | done 94 | <% } else { -%> 95 | <%= $backupmethod %> --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ 96 | ${ADDITIONAL_OPTIONS} \ 97 | --all-databases <% if $backupcompress { %>| <%= $compression_command %> <% } %>> ${DIR}/${PREFIX}`date +%Y%m%d-%H%M%S`.sql<% if $backupcompress { %><%= $compression_extension %><% } %> 98 | <% } -%> 99 | <% } else { -%> 100 | <% $backupdatabases.each |$db| { -%> 101 | <%= $backupmethod %> --defaults-extra-file=$TMPFILE --opt --flush-logs --single-transaction \ 102 | ${ADDITIONAL_OPTIONS} \ 103 | <%= $db %><% if $backupcompress { %>| <%= $compression_command %> <% } %>> ${DIR}/${PREFIX}<%= $db %>_`date +%Y%m%d-%H%M%S`.sql<% if $backupcompress { %><%= $compression_extension %><% } %> 104 | <% } -%> 105 | <% } -%> 106 | 107 | <% unless $delete_before_dump { -%> 108 | if [ $? -eq 0 ] ; then 109 | cleanup 110 | touch <%= $backup_success_file_path %> 111 | fi 112 | <% } -%> 113 | 114 | <% if $postscript { -%> 115 | <%- [$postscript].flatten().filter |$value| { $value }.each |$script| { %> 116 | <%= $script %> 117 | <%- } -%> 118 | <% } -%> 119 | 120 | # Remove temporary file 121 | rm -f $TMPFILE 122 | -------------------------------------------------------------------------------- /templates/xtrabackup.sh.epp: -------------------------------------------------------------------------------- 1 | <%- if $kernel == 'Linux' {-%> 2 | #!/bin/bash 3 | <%-} else {-%> 4 | #!/bin/sh 5 | <%- } -%> 6 | # 7 | # A wrapper for Xtrabackup 8 | 9 | ROTATE=<%= [ Integer($backuprotate) - 1, 0 ].max %> 10 | DIR=<%= $backupdir %> 11 | 12 | # Ensure backup directory exist. 13 | mkdir -p $DIR 14 | 15 | <%- if $kernel == 'Linux' {-%> 16 | set -o pipefail 17 | <%- } -%> 18 | 19 | <% if $prescript {-%> 20 | <%- [$prescript].flatten().filter |$value| {$value}.each |$script| { -%> 21 | 22 | <%= $script %> 23 | <%- } -%> 24 | <% } -%> 25 | 26 | cleanup() 27 | { 28 | find "${DIR}/" -mindepth 1 -maxdepth 1 -mtime +${ROTATE} -print0 | xargs -0 -r rm -rf 29 | } 30 | 31 | <% if $delete_before_dump { -%> 32 | cleanup 33 | <% } -%> 34 | 35 | <%= $backupmethod %> <%= $innobackupex_args %> $@ 36 | 37 | <% unless $delete_before_dump {-%> 38 | if [ $? -eq 0 ] ; then 39 | cleanup 40 | <% if $backup_success_file_path { -%> 41 | touch <%= $backup_success_file_path %> 42 | <% } -%> 43 | fi 44 | <% } -%> 45 | 46 | <% if $postscript { -%> 47 | <%- [$postscript].flatten().filter |$value| {$value}.each |$script| {%> 48 | <%= $script %> 49 | <%- } -%> 50 | <% } -%> 51 | -------------------------------------------------------------------------------- /types/options.pp: -------------------------------------------------------------------------------- 1 | # @summary A hash of options structured like the override_options, but not merged with the default options. 2 | # Use this if you don’t want your options merged with the default options. 3 | type Mysql::Options = Hash[ 4 | String, 5 | Hash, 6 | ] 7 | --------------------------------------------------------------------------------