├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin └── gratan ├── docker-compose.yml ├── gratan.gemspec ├── lib ├── gratan.rb └── gratan │ ├── client.rb │ ├── driver.rb │ ├── dsl.rb │ ├── dsl │ ├── context.rb │ ├── context │ │ ├── on.rb │ │ └── user.rb │ ├── converter.rb │ └── validator.rb │ ├── exporter.rb │ ├── ext │ └── string_ext.rb │ ├── grant_parser.rb │ ├── identifier.rb │ ├── identifier │ ├── auto.rb │ ├── csv.rb │ └── null.rb │ ├── logger.rb │ ├── template_helper.rb │ └── version.rb └── spec ├── change ├── change_grants_2_spec.rb ├── change_grants_3_spec.rb ├── change_grants_4_spec.rb ├── change_grants_expired_spec.rb ├── change_grants_func_prcd_spec.rb ├── change_grants_ignore_not_exist_spec.rb ├── change_grants_multi_hosts_spec.rb ├── change_grants_regexp_spec.rb ├── change_grants_spec.rb ├── change_grants_target_spec.rb └── change_grants_with_ignore_object_spec.rb ├── create ├── create_user_2_spec.rb ├── create_user_3_spec.rb ├── create_user_multi_hosts_spec.rb ├── create_user_regexp_spec.rb ├── create_user_spec.rb ├── create_user_target_spec.rb ├── create_user_with_func_prcd_spec.rb ├── create_user_with_ignore_object_spec.rb └── create_user_with_template_spec.rb ├── drop ├── drop_user_2_spec.rb ├── drop_user_spec.rb └── expire_user_spec.rb ├── export ├── export_chunk_spec.rb ├── export_func_prcd_spec.rb ├── export_spec.rb └── export_with_ignore_object_spec.rb ├── misc ├── misc_spec.rb └── require_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | test.rb 16 | Grantfile 17 | *.grant 18 | /_site/ 19 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: ruby 3 | rvm: 4 | - 2.4.5 5 | - 2.5.3 6 | - 2.6.0 7 | cache: 8 | - bundler 9 | - apt 10 | env: 11 | matrix: 12 | - MYSQL57=0 MYSQ_PORT=14406 # MySQL 5.6 13 | - MYSQL57=1 MYSQ_PORT=14407 # MySQL 5.7 14 | before_script: 15 | - sudo service mysql stop 16 | - bundle install 17 | - docker-compose up -d 18 | - function mysql_ping { mysqladmin -u root -h 127.0.0.1 -P $MYSQ_PORT ping; } 19 | - for i in {1..60}; do mysql_ping && break; sleep 1; done 20 | script: 21 | - bundle exec rake 22 | services: 23 | - docker 24 | addons: 25 | apt: 26 | packages: 27 | - mysql-client-core-5.6 28 | - mysql-client-5.6 29 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in gratan.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Genki Sugawara 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gratan 2 | 3 | Gratan is a tool to manage MySQL permissions. 4 | 5 | It defines the state of MySQL permissions using Ruby DSL, and updates permissions according to DSL. 6 | 7 | [![Gem Version](https://badge.fury.io/rb/gratan.svg)](http://badge.fury.io/rb/gratan) 8 | [![Build Status](https://travis-ci.org/codenize-tools/gratan.svg?branch=master)](https://travis-ci.org/codenize-tools/gratan) 9 | 10 | ## Notice 11 | 12 | * `>= 0.3.0` 13 | * Support template 14 | * `>= 0.3.1` 15 | * Fix `` password 16 | 17 | ## Installation 18 | 19 | Add this line to your application's Gemfile: 20 | 21 | ```ruby 22 | gem 'gratan' 23 | ``` 24 | 25 | And then execute: 26 | 27 | $ bundle 28 | 29 | Or install it yourself as: 30 | 31 | $ gem install gratan 32 | 33 | ## Usage 34 | 35 | ```sh 36 | gratan -e -o Grantfile 37 | vi Grantfile 38 | gratan -a --dry-run 39 | gratan -a 40 | ``` 41 | 42 | ## Help 43 | 44 | ```sh 45 | Usage: gratan [options] 46 | --host HOST 47 | --port PORT 48 | --socket SOCKET 49 | --username USERNAME 50 | --password PASSWORD 51 | --database DATABASE 52 | -a, --apply 53 | -f, --file FILE 54 | --dry-run 55 | -e, --export 56 | --with-identifier 57 | --split 58 | --chunk-by-user 59 | -o, --output FILE 60 | --ignore-user REGEXP 61 | --target-user REGEXP 62 | --ignore-object REGEXP 63 | --enable-expired 64 | --ignore-not-exist 65 | --ignore-password-secret 66 | --skip-disable-log-bin 67 | --override-sql-mode 68 | --use-show-create-user 69 | --no-color 70 | --debug 71 | --auto-identify OUTPUT 72 | --csv-identify CSV 73 | --mysql2-options JSON 74 | -h, --help 75 | ``` 76 | 77 | A default connection to a database can be established by setting the following environment variables: 78 | - `GRATAN_DB_HOST`: database host 79 | - `GRATAN_DB_PORT`: database port 80 | - `GRATAN_DB_SOCKET`: database socket 81 | - `GRATAN_DB_DATABASE`: database database name 82 | - `GRATAN_DB_USERNAME`: database user 83 | - `GRATAN_DB_PASSWORD`: database password 84 | 85 | ## Grantfile example 86 | 87 | ```ruby 88 | require 'other/grantfile' 89 | 90 | user "scott", "%" do 91 | on "*.*" do 92 | grant "USAGE" 93 | end 94 | 95 | on "test.*", expired: '2014/10/08', identified: "PASSWORD '*ABCDEF'" do 96 | grant "SELECT" 97 | grant "INSERT" 98 | end 99 | 100 | on /^foo\.prefix_/ do 101 | grant "SELECT" 102 | grant "INSERT" 103 | end 104 | end 105 | 106 | user "scott", ["localhost", "192.168.%"], expired: '2014/10/10' do 107 | on "*.*", with: 'GRANT OPTION' do 108 | grant "ALL PRIVILEGES" 109 | end 110 | end 111 | ``` 112 | 113 | ### Use template 114 | 115 | ```ruby 116 | template 'all db template' do 117 | on '*.*' do 118 | grant 'SELECT' 119 | end 120 | end 121 | 122 | template 'test db template' do 123 | grant context.default 124 | 125 | context.extra.each do |priv| 126 | grant priv 127 | end 128 | end 129 | 130 | user 'scott', 'localhost', identified: 'tiger' do 131 | include_template 'all db template' 132 | 133 | on 'test.*' do 134 | context.default = 'SELECT' 135 | include_template 'test db template', extra: ['INSERT', 'UPDATE'] 136 | end 137 | end 138 | ``` 139 | 140 | ## Run tests 141 | 142 | ```sh 143 | bundle install 144 | docker-compose up -d 145 | bundle exec rake 146 | # MYSQL57=1 bundle exec rake 147 | ``` 148 | 149 | ## Similar tools 150 | * [Codenize.tools](http://codenize.tools/) 151 | 152 | ## What does "Gratan" mean? 153 | 154 | [![](http://i.gyazo.com/c37d934ba0a61f760603ce4c56401e60.png)](https://www.google.com/search?q=gratin&tbm=isch) 155 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new('spec') 5 | task :default => :spec 6 | -------------------------------------------------------------------------------- /bin/gratan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.expand_path("#{File.dirname __FILE__}/../lib") 3 | require 'rubygems' 4 | require 'gratan' 5 | require 'optparse' 6 | require 'json' 7 | 8 | Version = Gratan::VERSION 9 | DEFAULT_FILENAME = 'Grantfile' 10 | 11 | mode = nil 12 | file = DEFAULT_FILENAME 13 | output_file = '-' 14 | split = false 15 | MAGIC_COMMENT = <<-EOS 16 | # -*- mode: ruby -*- 17 | # vi: set ft=ruby : 18 | EOS 19 | 20 | mysql_options = { 21 | host: ENV['GRATAN_DB_HOST'] || 'localhost', 22 | username: ENV['GRATAN_DB_USERNAME'] || 'root', 23 | port: ENV['GRATAN_DB_PORT'] || 3306 24 | } 25 | 26 | mysql_options[:socket] = ENV['GRATAN_DB_SOCKET'] if ENV['GRATAN_DB_SOCKET'] 27 | mysql_options[:password] = ENV['GRATAN_DB_PASSWORD'] if ENV['GRATAN_DB_PASSWORD'] 28 | mysql_options[:database] = ENV['GRATAN_DB_DATABASE'] if ENV['GRATAN_DB_DATABASE'] 29 | 30 | options = { 31 | :dry_run => false, 32 | :color => true, 33 | :debug => false, 34 | } 35 | 36 | ARGV.options do |opt| 37 | begin 38 | opt.on('' , '--host HOST') {|v| mysql_options[:host] = v } 39 | opt.on('' , '--port PORT', Integer) {|v| mysql_options[:port] = v } 40 | opt.on('' , '--socket SOCKET') {|v| mysql_options[:socket] = v } 41 | opt.on('' , '--username USERNAME') {|v| mysql_options[:username] = v } 42 | opt.on('' , '--password PASSWORD') {|v| mysql_options[:password] = v } 43 | opt.on('' , '--database DATABASE') {|v| mysql_options[:database] = v } 44 | opt.on('-a', '--apply') { mode = :apply } 45 | opt.on('-f', '--file FILE') {|v| file = v } 46 | opt.on('' , '--dry-run') { options[:dry_run] = true } 47 | opt.on('-e', '--export') { mode = :export } 48 | opt.on('' , '--with-identifier') { options[:with_identifier] = true } 49 | opt.on('' , '--split') { split = true } 50 | opt.on('' , '--chunk-by-user') { options[:chunk_by_user] = true } 51 | opt.on('-o', '--output FILE') {|v| output_file = v } 52 | opt.on('' , '--ignore-user REGEXP') {|v| options[:ignore_user] = Regexp.new(v) } 53 | opt.on('' , '--target-user REGEXP') {|v| options[:target_user] = Regexp.new(v) } 54 | opt.on('' , '--ignore-object REGEXP') {|v| options[:ignore_object] = Regexp.new(v) } 55 | opt.on('' , '--enable-expired') { options[:enable_expired] = true } 56 | opt.on('' , '--ignore-not-exist') {|v| options[:ignore_not_exist] = true } 57 | opt.on('' , '--ignore-password-secret') {|v| options[:ignore_password_secret] = true } 58 | opt.on('' , '--skip-disable-log-bin') { options[:skip_disable_log_bin] = true } 59 | opt.on('' , '--override-sql-mode') { options[:override_sql_mode] = true } 60 | opt.on('' , '--use-show-create-user') { options[:use_show_create_user] = true } 61 | opt.on('' , '--no-color') { options[:color] = false } 62 | opt.on('' , '--debug') { options[:debug] = true } 63 | opt.on('' , '--wait-timeout SECOND', Integer) {|v| options[:wait_timeout] = v } 64 | opt.on('' , '--auto-identify OUTPUT') {|v| options[:identifier] = Gratan::Identifier::Auto.new(v, options) } 65 | opt.on('' , '--csv-identify CSV') {|v| options[:identifier] = Gratan::Identifier::CSV.new(v, options) } 66 | 67 | opt.on('' , '--mysql2-options JSON') do |json| 68 | json = JSON.parse(json) 69 | 70 | json.each do |key, value| 71 | options[key.to_sym] = value 72 | end 73 | end 74 | 75 | opt.on('-h', '--help') do 76 | puts opt.help 77 | exit 1 78 | end 79 | 80 | opt.parse! 81 | 82 | unless mode 83 | puts opt.help 84 | exit 1 85 | end 86 | rescue => e 87 | $stderr.puts("[ERROR] #{e.message}") 88 | exit 1 89 | end 90 | end 91 | 92 | options.update(mysql_options) 93 | String.colorize = options[:color] 94 | 95 | begin 96 | logger = Gratan::Logger.instance 97 | logger.set_debug(options[:debug]) 98 | client = Gratan::Client.new(options) 99 | 100 | case mode 101 | when :export 102 | if split 103 | logger.info('Export Grants') 104 | output_file = DEFAULT_FILENAME if output_file == '-' 105 | requires = [] 106 | 107 | client.export do |user, dsl| 108 | grant_file = File.join(File.dirname(output_file), "#{user}.grant") 109 | requires << grant_file 110 | logger.info(" write `#{grant_file}`") 111 | 112 | open(grant_file, 'wb') do |f| 113 | f.puts MAGIC_COMMENT 114 | f.puts dsl 115 | end 116 | end 117 | 118 | logger.info(" write `#{output_file}`") 119 | 120 | open(output_file, 'wb') do |f| 121 | f.puts MAGIC_COMMENT 122 | 123 | requires.each do |grant_file| 124 | f.puts "require '#{File.basename grant_file}'" 125 | end 126 | end 127 | else 128 | if output_file == '-' 129 | logger.info('# Export Grants') 130 | puts client.export(options) 131 | else 132 | logger.info("Export Grants to `#{output_file}`") 133 | open(output_file, 'wb') do |f| 134 | f.puts MAGIC_COMMENT 135 | f.puts client.export(options) 136 | end 137 | end 138 | end 139 | when :apply 140 | unless File.exist?(file) 141 | raise "No Grantfile found (looking for: #{file})" 142 | end 143 | 144 | mysql_info = mysql_options.dup 145 | mysql_info.delete(:password) 146 | mysql_info = JSON.dump(mysql_info) 147 | 148 | msg = "Apply `#{file}` to #{mysql_info}" 149 | msg << ' (dry-run)' if options[:dry_run] 150 | logger.info(msg) 151 | 152 | updated = client.apply(file) 153 | 154 | logger.info('No change'.intense_blue) unless updated 155 | end 156 | rescue => e 157 | if options[:debug] 158 | raise e 159 | else 160 | $stderr.puts("[ERROR] #{e.message}".red) 161 | exit 1 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | mysql56: 2 | image: "mysql:5.6" 3 | ports: 4 | - "14406:3306" 5 | environment: 6 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 7 | mysql57: 8 | image: "mysql:5.7" 9 | ports: 10 | - "14407:3306" 11 | environment: 12 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 13 | -------------------------------------------------------------------------------- /gratan.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'gratan/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'gratan' 8 | spec.version = Gratan::VERSION 9 | spec.authors = ['Genki Sugawara'] 10 | spec.email = ['sgwr_dts@yahoo.co.jp'] 11 | spec.summary = %q{Gratan is a tool to manage MySQL permissions using Ruby DSL.} 12 | spec.description = %q{Gratan is a tool to manage MySQL permissions using Ruby DSL.} 13 | spec.homepage = 'http://gratan.codenize.tools/' 14 | spec.license = 'MIT' 15 | 16 | spec.files = `git ls-files -z`.split("\x0") 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_dependency 'mysql2' 22 | spec.add_dependency 'term-ansicolor' 23 | spec.add_dependency 'deep_merge' 24 | spec.add_dependency 'hashie' 25 | spec.add_development_dependency 'bundler' 26 | spec.add_development_dependency 'rake', '~> 10.0' 27 | spec.add_development_dependency 'rspec', '>= 3.0.0' 28 | spec.add_development_dependency 'timecop' 29 | end 30 | -------------------------------------------------------------------------------- /lib/gratan.rb: -------------------------------------------------------------------------------- 1 | require 'logger' 2 | require 'singleton' 3 | require 'time' 4 | 5 | require 'deep_merge' 6 | require 'mysql2' 7 | require 'term/ansicolor' 8 | require 'hashie' 9 | 10 | module Gratan; end 11 | require 'gratan/logger' 12 | require 'gratan/template_helper' 13 | require 'gratan/client' 14 | require 'gratan/driver' 15 | require 'gratan/dsl' 16 | require 'gratan/dsl/validator' 17 | require 'gratan/dsl/context' 18 | require 'gratan/dsl/context/user' 19 | require 'gratan/dsl/context/on' 20 | require 'gratan/dsl/converter' 21 | require 'gratan/exporter' 22 | require 'gratan/ext/string_ext' 23 | require 'gratan/grant_parser' 24 | require 'gratan/identifier' 25 | require 'gratan/identifier/auto' 26 | require 'gratan/identifier/csv' 27 | require 'gratan/identifier/null' 28 | require 'gratan/version' 29 | -------------------------------------------------------------------------------- /lib/gratan/client.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Client 2 | include Gratan::Logger::Helper 3 | 4 | def initialize(options = {}) 5 | @options = options 6 | @options[:identifier] ||= Gratan::Identifier::Null.new 7 | client = Mysql2::Client.new(options) 8 | @driver = Gratan::Driver.new(client, options) 9 | end 10 | 11 | def export(options = {}) 12 | options = @options.merge(options) 13 | exported = Gratan::Exporter.export(@driver, options) 14 | 15 | if options[:chunk_by_user] 16 | exported = chunk_by_user(exported) 17 | end 18 | 19 | if block_given? 20 | exported.sort_by {|user_host, attrs| 21 | user_host[0].empty? ? 'root' : user_host[0] 22 | }.chunk {|user_host, attrs| 23 | user_host[0].empty? ? 'root' : user_host[0] 24 | }.each {|user, grants| 25 | h = {} 26 | grants.sort_by {|k, v| k }.each {|k, v| h[k] = v } 27 | dsl = Gratan::DSL.convert(h, options) 28 | yield(user, dsl) 29 | } 30 | else 31 | Gratan::DSL.convert(exported, options) 32 | end 33 | end 34 | 35 | def chunk_by_user(exported) 36 | chunked = {} 37 | 38 | exported.sort_by {|user_host, attrs| 39 | user_host[0] 40 | }.chunk {|user_host, attrs| 41 | user_host[0] 42 | }.each {|user, grants| 43 | merged_attrs = {} 44 | hosts = [] 45 | 46 | grants.each do |user_host, attrs| 47 | hosts << user_host[1] 48 | merged_attrs.deep_merge!(attrs) 49 | end 50 | 51 | user_host = [user, hosts.sort] 52 | chunked[user_host] = merged_attrs 53 | } 54 | 55 | chunked 56 | end 57 | 58 | def apply(file, options = {}) 59 | options = @options.merge(options) 60 | 61 | in_progress do 62 | walk(file, options) 63 | end 64 | end 65 | 66 | private 67 | 68 | def walk(file, options) 69 | expected = load_file(file, options) 70 | actual = Gratan::Exporter.export(@driver, options.merge(:with_identifier => true)) 71 | 72 | expected.each do |user_host, expected_attrs| 73 | next if user_host[0] =~ options[:ignore_user] 74 | 75 | if options[:target_user] 76 | next unless user_host[0] =~ options[:target_user] 77 | end 78 | 79 | actual_attrs = actual.delete(user_host) 80 | 81 | if actual_attrs 82 | walk_user(*user_host, expected_attrs, actual_attrs) 83 | else 84 | create_user(*user_host, expected_attrs) 85 | end 86 | end 87 | 88 | actual.each do |user_host, attrs| 89 | next if user_host[0] =~ options[:ignore_user] 90 | 91 | if options[:target_user] 92 | next unless user_host[0] =~ options[:target_user] 93 | end 94 | 95 | drop_user(*user_host) 96 | end 97 | end 98 | 99 | def create_user(user, host, attrs) 100 | attrs[:options] ||= {} 101 | 102 | unless attrs[:options].has_key?(:identified) 103 | identified = @options[:identifier].identify(user, host) 104 | 105 | if identified 106 | attrs = attrs.dup 107 | attrs[:options] = attrs[:options].dup 108 | attrs[:options][:identified] = identified 109 | end 110 | end 111 | 112 | @driver.create_user(user, host, attrs) 113 | update! 114 | end 115 | 116 | def drop_user(user, host) 117 | @driver.drop_user(user, host) 118 | update! 119 | end 120 | 121 | def walk_user(user, host, expected_attrs, actual_attrs) 122 | walk_options(user, host, expected_attrs[:options], actual_attrs[:options]) 123 | walk_objects(user, host, expected_attrs[:objects], actual_attrs[:objects]) 124 | end 125 | 126 | def walk_options(user, host, expected_options, actual_options) 127 | if expected_options.has_key?(:identified) 128 | walk_identified(user, host, expected_options[:identified], actual_options[:identified]) 129 | end 130 | 131 | walk_required(user, host, expected_options[:required], actual_options[:required]) 132 | end 133 | 134 | def walk_identified(user, host, expected_identified, actual_identified) 135 | if actual_identified == 'PASSWORD ' 136 | unless @options[:ignore_password_secret] 137 | log(:warn, "cannot change the password (`PASSWORD `)", :color => :yellow) 138 | end 139 | elsif expected_identified != actual_identified 140 | @driver.identify(user, host, expected_identified) 141 | update! 142 | end 143 | end 144 | 145 | def walk_required(user, host, expected_required, actual_required) 146 | if expected_required != actual_required 147 | @driver.set_require(user, host, expected_required) 148 | update! 149 | end 150 | end 151 | 152 | def walk_objects(user, host, expected_objects, actual_objects) 153 | expected_objects.each do |object_or_regexp, expected_options| 154 | @driver.expand_object(object_or_regexp).each do |object| 155 | expected_options ||= {} 156 | actual_options = actual_objects.delete(object) 157 | 158 | if actual_options 159 | walk_object(user, host, object, expected_options, actual_options) 160 | else 161 | @driver.grant(user, host, object, expected_options) 162 | update! 163 | end 164 | end 165 | end 166 | 167 | actual_objects.each do |object, options| 168 | options ||= {} 169 | @driver.revoke(user, host, object, options) 170 | update! 171 | end 172 | end 173 | 174 | def walk_object(user, host, object, expected_options, actual_options) 175 | walk_with_option(user, host, object, expected_options[:with], actual_options[:with]) 176 | walk_privs(user, host, object, expected_options[:privs], actual_options[:privs]) 177 | end 178 | 179 | def walk_with_option(user, host, object, expected_with_option, actual_with_option) 180 | expected_with_option = (expected_with_option || '').upcase 181 | actual_with_option = (actual_with_option || '').upcase 182 | 183 | if expected_with_option != actual_with_option 184 | @driver.update_with_option(user, host, object, expected_with_option) 185 | update! 186 | end 187 | end 188 | 189 | def walk_privs(user, host, object, expected_privs, actual_privs) 190 | expected_privs = normalize_privs(expected_privs) 191 | actual_privs = normalize_privs(actual_privs) 192 | 193 | revoke_privs = actual_privs - expected_privs 194 | grant_privs = expected_privs - actual_privs 195 | 196 | unless revoke_privs.empty? 197 | if revoke_privs.length == 1 and revoke_privs[0] == 'USAGE' and not grant_privs.empty? 198 | # nothing to do 199 | else 200 | @driver.revoke(user, host, object, :privs => revoke_privs) 201 | update! 202 | end 203 | end 204 | 205 | unless grant_privs.empty? 206 | @driver.grant(user, host, object, :privs => grant_privs) 207 | update! 208 | end 209 | end 210 | 211 | def normalize_privs(privs) 212 | privs.map do |priv| 213 | priv = priv.split('(', 2) 214 | priv[0].upcase! 215 | 216 | if priv[1] 217 | priv[1] = priv[1].split(',').map {|i| i.gsub(')', '').strip }.sort.join(', ') 218 | priv[1] << ')' 219 | end 220 | 221 | priv = priv.join('(') 222 | 223 | if priv == 'ALL' 224 | priv = 'ALL PRIVILEGES' 225 | end 226 | 227 | priv 228 | end 229 | end 230 | 231 | def load_file(file, options) 232 | if file.kind_of?(String) 233 | open(file) do |f| 234 | Gratan::DSL.parse(f.read, file, options) 235 | end 236 | elsif file.respond_to?(:read) 237 | Gratan::DSL.parse(file.read, file.path, options) 238 | else 239 | raise TypeError, "can't convert #{file} into File" 240 | end 241 | end 242 | 243 | def in_progress 244 | updated = false 245 | 246 | begin 247 | @driver.disable_log_bin_local 248 | @driver.override_sql_mode 249 | @driver.set_wait_timeout 250 | @updated = false 251 | yield 252 | updated = @updated 253 | @driver.flush_privileges if updated 254 | ensure 255 | @updated = nil 256 | end 257 | 258 | if @options[:dry_run] 259 | false 260 | else 261 | updated 262 | end 263 | end 264 | 265 | def update! 266 | @updated = true 267 | end 268 | end 269 | -------------------------------------------------------------------------------- /lib/gratan/driver.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Driver 2 | include Gratan::Logger::Helper 3 | 4 | ER_NO_SUCH_TABLE = 1146 5 | 6 | def initialize(client, options = {}) 7 | @client = client 8 | @options = options 9 | end 10 | 11 | def each_user 12 | query('SELECT user, host FROM mysql.user').each do |row| 13 | yield(row['user'], row['host']) 14 | end 15 | end 16 | 17 | def show_grants(user, host) 18 | query("SHOW GRANTS FOR #{quote_user(user, host)}").each do |row| 19 | yield(row.values.first) 20 | end 21 | end 22 | 23 | def show_create_user(user, host) 24 | query("SHOW CREATE USER #{quote_user(user, host)}").first.values.first 25 | end 26 | 27 | def show_databases 28 | query("SHOW DATABASES").map {|i| i.values.first } 29 | end 30 | 31 | def show_tables(database) 32 | query("SHOW TABLES FROM `#{database}`").map {|i| i.values.first } 33 | rescue Mysql2::Error => e 34 | raise e if e.error_number != 1102 35 | 36 | log(:debug, e.message) 37 | [] 38 | end 39 | 40 | def show_all_tables 41 | @all_tables ||= show_databases.map {|database| 42 | show_tables(database).map do |table| 43 | "#{database}.#{table}" 44 | end 45 | }.flatten 46 | end 47 | 48 | def expand_object(object_or_regexp) 49 | if object_or_regexp.kind_of?(Regexp) 50 | objects = show_all_tables.select {|i| i =~ object_or_regexp } 51 | else 52 | objects = [object_or_regexp] 53 | end 54 | 55 | objects.select do |object| 56 | object !~ @options[:ignore_object] 57 | end 58 | end 59 | 60 | def flush_privileges 61 | update("FLUSH PRIVILEGES") 62 | end 63 | 64 | def create_user(user, host, options = {}) 65 | objects = options[:objects] 66 | grant_options = options[:options] 67 | granted = false 68 | 69 | objects.each do |object_or_regexp, object_options| 70 | expand_object(object_or_regexp).each do |object| 71 | grant(user, host, object, grant_options.merge(object_options)) 72 | granted = true 73 | end 74 | end 75 | 76 | unless granted 77 | log(:warn, "there was no privileges to grant to #{quote_user(user, host)}", :color => :yellow) 78 | end 79 | end 80 | 81 | def drop_user(user, host) 82 | sql = "DROP USER #{quote_user(user, host)}" 83 | delete(sql) 84 | end 85 | 86 | def grant(user, host, object, options) 87 | privs = options.fetch(:privs) 88 | identified = options[:identified] 89 | required = options[:required] 90 | with_option = options[:with] 91 | 92 | sql = 'GRANT %s ON %s TO %s' % [ 93 | privs.join(', '), 94 | quote_object(object), 95 | quote_user(user, host), 96 | ] 97 | 98 | sql << " IDENTIFIED BY #{quote_identifier(identified)}" if identified 99 | sql << " REQUIRE #{required}" if required 100 | sql << " WITH #{with_option}" if with_option 101 | 102 | begin 103 | update(sql) 104 | rescue Mysql2::Error => e 105 | if @options[:ignore_not_exist] and e.error_number == ER_NO_SUCH_TABLE 106 | log(:warn, e.message, :color => :yellow) 107 | else 108 | raise e 109 | end 110 | end 111 | end 112 | 113 | def identify(user, host, identifier) 114 | sql = 'GRANT USAGE ON *.* TO %s IDENTIFIED BY %s' % [ 115 | quote_user(user, host), 116 | quote_identifier(identifier), 117 | ] 118 | 119 | update(sql) 120 | 121 | if (identifier || '').empty? 122 | set_password(user, host, identifier) 123 | end 124 | end 125 | 126 | def set_password(user, host, password, options = {}) 127 | password ||= '' 128 | 129 | unless options[:hash] 130 | password = "PASSWORD('#{escape(password)}')" 131 | end 132 | 133 | sql = 'SET PASSWORD FOR %s = %s' % [ 134 | quote_user(user, host), 135 | password, 136 | ] 137 | 138 | update(sql) 139 | end 140 | 141 | def set_require(user, host, required) 142 | required ||= 'NONE' 143 | 144 | sql = 'GRANT USAGE ON *.* TO %s REQUIRE %s' % [ 145 | quote_user(user, host), 146 | required 147 | ] 148 | 149 | update(sql) 150 | end 151 | 152 | def revoke(user, host, object, options = {}) 153 | privs = options.fetch(:privs) 154 | with_option = options[:with] 155 | 156 | if with_option =~ /\bGRANT\s+OPTION\b/i 157 | revoke0(user, host, object, ['GRANT OPTION']) 158 | 159 | if privs.length == 1 and privs[0] =~ /\AUSAGE\z/i 160 | return 161 | end 162 | end 163 | 164 | revoke0(user, host, object, privs) 165 | end 166 | 167 | def revoke0(user, host, object, privs) 168 | sql = 'REVOKE %s ON %s FROM %s' % [ 169 | privs.join(', '), 170 | quote_object(object), 171 | quote_user(user, host), 172 | ] 173 | 174 | delete(sql) 175 | end 176 | 177 | def update_with_option(user, host, object, with_option) 178 | options = [] 179 | 180 | if with_option =~ /\bGRANT\s+OPTION\b/i 181 | options << 'GRANT OPTION' 182 | else 183 | revoke(user, host, object, :privs => ['GRANT OPTION']) 184 | end 185 | 186 | %w( 187 | MAX_QUERIES_PER_HOUR 188 | MAX_UPDATES_PER_HOUR 189 | MAX_CONNECTIONS_PER_HOUR 190 | MAX_USER_CONNECTIONS 191 | ).each do |name| 192 | count = 0 193 | 194 | if with_option =~ /\b#{name}\s+(\d+)\b/i 195 | count = $1 196 | end 197 | 198 | options << [name, count].join(' ') 199 | end 200 | 201 | unless options.empty? 202 | grant(user, host, object, :privs => ['USAGE'], :with => options.join(' ')) 203 | end 204 | end 205 | 206 | def disable_log_bin_local 207 | unless @options[:skip_disable_log_bin] 208 | query('SET SQL_LOG_BIN = 0') 209 | end 210 | end 211 | 212 | def override_sql_mode 213 | if @options[:override_sql_mode] 214 | query('SET SQL_MODE = ""') 215 | end 216 | end 217 | 218 | def set_wait_timeout 219 | if @options[:wait_timeout] 220 | query("SET @@wait_timeout = #{@options[:wait_timeout]}") 221 | end 222 | end 223 | 224 | private 225 | 226 | def query(sql) 227 | log(:debug, sql, :dry_run => false) 228 | @client.query(sql) 229 | end 230 | 231 | def update(sql) 232 | log(:info, sql, :color => :green) 233 | @client.query(sql) unless @options[:dry_run] 234 | end 235 | 236 | def delete(sql) 237 | log(:info, sql, :color => :red) 238 | @client.query(sql) unless @options[:dry_run] 239 | end 240 | 241 | def escape(str) 242 | @client.escape(str) 243 | end 244 | 245 | def quote_user(user, host) 246 | "'%s'@'%s'" % [escape(user), escape(host)] 247 | end 248 | 249 | def quote_object(object) 250 | if object =~ /\A(FUNCTION|PROCEDURE)\s+/i 251 | object_type, object = object.split(/\s+/, 2) 252 | object_type << ' ' 253 | else 254 | object_type = '' 255 | end 256 | 257 | object_type + object.split('.', 2).map {|i| i == '*' ? i : "`#{i}`" }.join('.') 258 | end 259 | 260 | def quote_identifier(identifier) 261 | identifier ||= '' 262 | 263 | unless identifier =~ /\APASSWORD\s+'.+'\z/ 264 | identifier = "'#{escape(identifier)}'" 265 | end 266 | 267 | identifier 268 | end 269 | end 270 | -------------------------------------------------------------------------------- /lib/gratan/dsl.rb: -------------------------------------------------------------------------------- 1 | class Gratan::DSL 2 | def self.convert(exported, options = {}) 3 | Gratan::DSL::Converter.convert(exported, options) 4 | end 5 | 6 | def self.parse(dsl, path, options = {}) 7 | Gratan::DSL::Context.eval(dsl, path, options).result 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/gratan/dsl/context.rb: -------------------------------------------------------------------------------- 1 | class Gratan::DSL::Context 2 | include Gratan::DSL::Validator 3 | include Gratan::Logger::Helper 4 | include Gratan::TemplateHelper 5 | 6 | def self.eval(dsl, path, options = {}) 7 | self.new(path, options) do 8 | eval(dsl, binding, path) 9 | end 10 | end 11 | 12 | attr_reader :result 13 | 14 | def initialize(path, options = {}, &block) 15 | @path = path 16 | @options = options 17 | @result = {} 18 | 19 | @context = Hashie::Mash.new( 20 | :path => path, 21 | :options => options, 22 | :templates => {} 23 | ) 24 | 25 | instance_eval(&block) 26 | end 27 | 28 | private 29 | 30 | def template(name, &block) 31 | @context.templates[name.to_s] = block 32 | end 33 | 34 | def require(file) 35 | grantfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file)) 36 | 37 | if File.exist?(grantfile) 38 | instance_eval(File.read(grantfile), grantfile) 39 | elsif File.exist?(grantfile + '.rb') 40 | instance_eval(File.read(grantfile + '.rb'), grantfile + '.rb') 41 | else 42 | Kernel.require(file) 43 | end 44 | end 45 | 46 | def user(name, host_or_array, options = {}, &block) 47 | name = name.to_s 48 | hosts = [host_or_array].flatten.map {|i| i.to_s } 49 | 50 | hosts.each do |host| 51 | options ||= {} 52 | 53 | __validate("User `#{name}@#{host}` is already defined") do 54 | not @result.has_key?([name, host]) 55 | end 56 | 57 | if @options[:enable_expired] and (expired = options.delete(:expired)) 58 | expired = Time.parse(expired) 59 | 60 | if Time.new >= expired 61 | log(:warn, "User `#{name}@#{host}` has expired", :color => :yellow) 62 | return 63 | end 64 | end 65 | 66 | @result[[name, host]] = { 67 | :objects => Gratan::DSL::Context::User.new(@context, name, host, @options, &block).result, 68 | :options => options, 69 | } 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/gratan/dsl/context/on.rb: -------------------------------------------------------------------------------- 1 | class Gratan::DSL::Context::On 2 | include Gratan::DSL::Validator 3 | include Gratan::TemplateHelper 4 | 5 | attr_reader :result 6 | 7 | def initialize(context, user, host, object, options, &block) 8 | @object_identifier = "User `#{user}@#{host}` on `#{object}`" 9 | @user = user 10 | @host = host 11 | @object = object 12 | @options = options 13 | @context = context.merge(:object => object, :grant_options => options) 14 | @result = [] 15 | instance_eval(&block) 16 | end 17 | 18 | def grant(name, options = {}) 19 | __validate("Grant `#{name}` is already defined") do 20 | not @result.include?(name) 21 | end 22 | 23 | @result << name 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/gratan/dsl/context/user.rb: -------------------------------------------------------------------------------- 1 | class Gratan::DSL::Context::User 2 | include Gratan::DSL::Validator 3 | include Gratan::Logger::Helper 4 | include Gratan::TemplateHelper 5 | 6 | attr_reader :result 7 | 8 | def initialize(context, user, host, options, &block) 9 | @object_identifier = "User `#{user}@#{host}`" 10 | @user = user 11 | @host = host 12 | @options = options 13 | @context = context.merge(:user => user, :host => host, :user_options => options) 14 | @result = {} 15 | instance_eval(&block) 16 | end 17 | 18 | def on(name, options = {}, &block) 19 | name = name.kind_of?(Regexp) ? name : name.to_s 20 | 21 | __validate("Object `#{name}` is already defined") do 22 | not @result.has_key?(name) 23 | end 24 | 25 | if @options[:enable_expired] and (expired = options.delete(:expired)) 26 | expired = Time.parse(expired) 27 | 28 | if Time.new >= expired 29 | log(:warn, "Object `#{name}` has expired", :color => :yellow) 30 | return 31 | end 32 | end 33 | 34 | grant = {:privs => Gratan::DSL::Context::On.new(@context, @user, @host, name, @options, &block).result} 35 | grant[:with] = options[:with] if options[:with] 36 | @result[name] = grant 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/gratan/dsl/converter.rb: -------------------------------------------------------------------------------- 1 | class Gratan::DSL::Converter 2 | def self.convert(exported, options = {}) 3 | self.new(exported, options).convert 4 | end 5 | 6 | def initialize(exported, options = {}) 7 | @exported = exported 8 | @options = options 9 | end 10 | 11 | def convert 12 | @exported.map {|user_host, attrs| 13 | output_user(user_host, attrs) 14 | }.join("\n") 15 | end 16 | 17 | private 18 | 19 | def output_user(user_host, attrs) 20 | user, host = user_host 21 | objects, options = attrs.values_at(:objects, :options) 22 | options = output_user_options(options) 23 | 24 | <<-EOS 25 | user #{user.inspect}, #{host.inspect}#{options}do 26 | #{output_objects(objects)} 27 | end 28 | EOS 29 | end 30 | 31 | def output_user_options(options) 32 | if options.empty? 33 | ' ' 34 | else 35 | options = strip_hash_brace(options.inspect) 36 | ", #{options} " 37 | end 38 | end 39 | 40 | def output_objects(objects) 41 | objects.sort_by {|k, v| k }.map {|object, grant| 42 | options = output_object_options(grant) 43 | 44 | <<-EOS 45 | on #{object.inspect}#{options}do 46 | #{output_grant(grant)} 47 | end 48 | EOS 49 | }.join("\n").strip 50 | end 51 | 52 | def output_object_options(grant) 53 | with_option = grant.delete(:with) 54 | 55 | if with_option 56 | options = strip_hash_brace({:with => with_option}.inspect) 57 | ", #{options} " 58 | else 59 | ' ' 60 | end 61 | end 62 | 63 | def output_grant(grant) 64 | grant[:privs].sort.map {|priv| 65 | <<-EOS 66 | grant #{priv.inspect} 67 | EOS 68 | }.join.strip 69 | end 70 | 71 | def strip_hash_brace(hash_str) 72 | hash_str.sub(/\A\{/, '').sub(/\}\z/, '') 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/gratan/dsl/validator.rb: -------------------------------------------------------------------------------- 1 | module Gratan::DSL::Validator 2 | def __validate(errmsg) 3 | raise __identify(errmsg) unless yield 4 | end 5 | 6 | def __identify(errmsg) 7 | if @object_identifier 8 | errmsg = "#{@object_identifier}: #{errmsg}" 9 | end 10 | 11 | return errmsg 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/gratan/exporter.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Exporter 2 | def self.export(driver, options = {}, &block) 3 | self.new(driver, options).export(&block) 4 | end 5 | 6 | def initialize(driver, options = {}) 7 | @driver = driver 8 | @options = options 9 | end 10 | 11 | def export 12 | grants = [] 13 | 14 | @driver.each_user do |user, host| 15 | next if user =~ @options[:ignore_user] 16 | 17 | if @options[:target_user] 18 | next unless user =~ @options[:target_user] 19 | end 20 | 21 | if @options[:use_show_create_user] 22 | create_user = @driver.show_create_user(user, host) 23 | end 24 | 25 | @driver.show_grants(user, host) do |stmt| 26 | grants << Gratan::GrantParser.parse(stmt, create_user) 27 | end 28 | end 29 | 30 | pack(grants) 31 | end 32 | 33 | private 34 | 35 | def pack(grants) 36 | packed = {} 37 | 38 | grants.each do |grant| 39 | user = grant.delete(:user) 40 | host = grant.delete(:host) 41 | user_host = [user, host] 42 | object = grant.delete(:object) 43 | next if object =~ @options[:ignore_object] 44 | identified = grant.delete(:identified) 45 | required = grant.delete(:require) 46 | 47 | packed[user_host] ||= {:objects => {}, :options => {}} 48 | packed[user_host][:objects][object] = grant 49 | packed[user_host][:options][:required] = required if required 50 | 51 | if @options[:with_identifier] and identified 52 | packed[user_host][:options][:identified] = identified 53 | end 54 | end 55 | 56 | packed 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/gratan/ext/string_ext.rb: -------------------------------------------------------------------------------- 1 | class String 2 | @@colorize = false 3 | 4 | class << self 5 | def colorize=(value) 6 | @@colorize = value 7 | end 8 | 9 | def colorize 10 | @@colorize 11 | end 12 | end # of class methods 13 | 14 | Term::ANSIColor::Attribute.named_attributes.map do |attribute| 15 | class_eval(<<-EOS, __FILE__, __LINE__ + 1) 16 | def #{attribute.name} 17 | if @@colorize 18 | Term::ANSIColor.send(#{attribute.name.inspect}, self) 19 | else 20 | self 21 | end 22 | end 23 | EOS 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/gratan/grant_parser.rb: -------------------------------------------------------------------------------- 1 | class Gratan::GrantParser 2 | def initialize(stmt, create_user = nil) 3 | @stmt = stmt.strip 4 | @create_user = create_user 5 | @parsed = {} 6 | end 7 | 8 | def self.parse(stmt, create_user = nil) 9 | parser = self.new(stmt, create_user) 10 | parser.parse! 11 | end 12 | 13 | def parse! 14 | parse_grant 15 | parse_require 16 | parse_identified 17 | parse_main 18 | @parsed 19 | end 20 | 21 | private 22 | 23 | def parse_grant 24 | @stmt.slice!(/\s+WITH\s+(.+?)\z/) 25 | with_option = $1 26 | 27 | if with_option 28 | @parsed[:with] = with_option.strip 29 | end 30 | end 31 | 32 | def parse_require 33 | @stmt.slice!(/\s+REQUIRE\s+(.+?)\z/) 34 | required = $1 35 | 36 | if @create_user 37 | @create_user.slice!(/\s+REQUIRE\s+(\S+(?:\s+'[^']+')?)(?:\s+WITH\s+(.+))?\s+PASSWORD\s+.+\z/) 38 | required = $1 39 | resource_option = $2 40 | 41 | if resource_option 42 | @parsed[:with] ||= '' 43 | @parsed[:with] << ' ' << resource_option.strip 44 | @parsed[:with].strip! 45 | end 46 | end 47 | 48 | if required && required != 'NONE' 49 | @parsed[:require] = required.strip 50 | end 51 | end 52 | 53 | def parse_identified 54 | @stmt.slice!(/\s+IDENTIFIED BY\s+(.+?)\z/) 55 | identified = $1 56 | 57 | if @create_user 58 | @create_user.slice!(/\s+IDENTIFIED\s+WITH\s+'[^']+'\s+AS\s+('[^']+')/) 59 | identified = $1 60 | identified = "PASSWORD #{identified}" if identified 61 | end 62 | 63 | if identified 64 | @parsed[:identified] = identified.strip 65 | end 66 | end 67 | 68 | def parse_main 69 | md = /\AGRANT\s+(.+?)\s+ON\s+(.+?)\s+TO\s+'(.*)'@'(.+)'\z/.match(@stmt) 70 | privs, object, user, host = md.captures 71 | @parsed[:privs] = parse_privs(privs.strip) 72 | @parsed[:object] = object.gsub('`', '').strip 73 | @parsed[:user] = user 74 | @parsed[:host] = host 75 | end 76 | 77 | def parse_privs(privs) 78 | privs << ',' 79 | priv_list = [] 80 | 81 | while priv = privs.slice!(/\A[^,(]+(?:\([^)]+\))?\s*,\s*/) 82 | priv_list << priv.strip.sub(/,\z/, '').strip 83 | end 84 | 85 | priv_list 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/gratan/identifier.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Identifier 2 | end 3 | -------------------------------------------------------------------------------- /lib/gratan/identifier/auto.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Identifier::Auto 2 | def initialize(output, options = {}) 3 | @output = output 4 | @options = options 5 | @cache = {} 6 | end 7 | 8 | def identify(user, host) 9 | if @cache[user] 10 | password = @cache[user] 11 | else 12 | password = mkpasswd 13 | @cache[user] = password 14 | end 15 | 16 | puts_password(user, host, password) 17 | password 18 | end 19 | 20 | private 21 | 22 | def mkpasswd(len = 8) 23 | [*1..9, *'A'..'Z', *'a'..'z'].shuffle.slice(0, len).join 24 | end 25 | 26 | def puts_password(user, host, password) 27 | open_output do |f| 28 | f.puts("#{user}@#{host},#{password}") 29 | end 30 | end 31 | 32 | def open_output 33 | return if @options[:dry_run] 34 | 35 | if @output == '-' 36 | yield($stdout) 37 | $stdout.flush 38 | else 39 | open(@output, 'a') do |f| 40 | yield(f) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/gratan/identifier/csv.rb: -------------------------------------------------------------------------------- 1 | require 'csv' 2 | 3 | class Gratan::Identifier::CSV 4 | include Gratan::Logger::Helper 5 | 6 | def initialize(path, options = {}) 7 | @options = options 8 | @passwords = {} 9 | 10 | CSV.foreach(path) do |row| 11 | @passwords[row[0]] = row[1] 12 | end 13 | end 14 | 15 | def identify(user, host) 16 | user_host = "#{user}@#{host}" 17 | password = @passwords[user_host] 18 | 19 | unless password 20 | log(:warn, "password for `#{user_host}` can not be found", :color => :yellow) 21 | end 22 | 23 | password 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/gratan/identifier/null.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Identifier::Null 2 | def identify(user, host) 3 | nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/gratan/logger.rb: -------------------------------------------------------------------------------- 1 | class Gratan::Logger < ::Logger 2 | include Singleton 3 | 4 | def initialize 5 | super($stdout) 6 | 7 | self.formatter = proc do |severity, datetime, progname, msg| 8 | "#{msg}\n" 9 | end 10 | 11 | self.level = Logger::INFO 12 | end 13 | 14 | def set_debug(value) 15 | self.level = value ? Logger::DEBUG : Logger::INFO 16 | end 17 | 18 | module Helper 19 | def log(level, message, options = {}) 20 | global_options = @options || {} 21 | message = "#{@object_identifier}: #{message}" if @object_identifier 22 | message = "[#{level.to_s.upcase}] #{message}" unless level == :info 23 | 24 | if global_options[:dry_run] and options[:dry_run] != false 25 | message << ' (dry-run)' if global_options[:dry_run] 26 | end 27 | 28 | message = message.send(options[:color]) if options[:color] 29 | logger = global_options[:logger] || Gratan::Logger.instance 30 | logger.send(level, message) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/gratan/template_helper.rb: -------------------------------------------------------------------------------- 1 | module Gratan 2 | module TemplateHelper 3 | def include_template(template_name, context = {}) 4 | tmplt = @context.templates[template_name.to_s] 5 | 6 | unless tmplt 7 | raise "Template `#{template_name}` is not defined" 8 | end 9 | 10 | context_orig = @context 11 | @context = @context.merge(context) 12 | instance_eval(&tmplt) 13 | @context = context_orig 14 | end 15 | 16 | def context 17 | @context 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/gratan/version.rb: -------------------------------------------------------------------------------- 1 | module Gratan 2 | VERSION = '0.3.2' 3 | end 4 | -------------------------------------------------------------------------------- /spec/change/change_grants_2_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'USAGE' 8 | end 9 | end 10 | 11 | user 'bob', 'localhost' do 12 | on '*.*', with: 'GRANT OPTION' do 13 | grant 'ALL PRIVILEGES' 14 | end 15 | end 16 | RUBY 17 | } 18 | end 19 | 20 | context 'when update password' do 21 | subject { client } 22 | 23 | it do 24 | apply(subject) { 25 | <<-RUBY 26 | user 'scott', 'localhost', identified: '123', required: 'SSL' do 27 | on '*.*' do 28 | grant 'USAGE' 29 | end 30 | end 31 | 32 | user 'bob', 'localhost', identified: '456' do 33 | on '*.*', with: 'GRANT OPTION' do 34 | grant 'ALL PRIVILEGES' 35 | end 36 | end 37 | RUBY 38 | } 39 | 40 | expect(show_grants).to match_array [ 41 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' IDENTIFIED BY PASSWORD '*531E182E2F72080AB0740FE2F2D689DBE0146E04' WITH GRANT OPTION", 42 | "GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*23AE809DDACAF96AF0FD78ED04B6A265E05AA257' REQUIRE SSL", 43 | ].normalize 44 | end 45 | end 46 | 47 | context 'when remove password' do 48 | subject { client } 49 | 50 | it do 51 | apply(subject) { 52 | <<-RUBY 53 | user 'scott', 'localhost', identified: nil, required: 'SSL' do 54 | on '*.*' do 55 | grant 'USAGE' 56 | end 57 | end 58 | 59 | user 'bob', 'localhost' do 60 | on '*.*', with: 'GRANT OPTION' do 61 | grant 'ALL PRIVILEGES' 62 | end 63 | end 64 | RUBY 65 | } 66 | 67 | expect(show_grants).to match_array [ 68 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' WITH GRANT OPTION", 69 | "GRANT USAGE ON *.* TO 'scott'@'localhost' REQUIRE SSL", 70 | ].normalize 71 | end 72 | end 73 | 74 | context 'when skip update password' do 75 | subject { client } 76 | 77 | it do 78 | apply(subject) { 79 | <<-RUBY 80 | user 'scott', 'localhost', required: 'SSL' do 81 | on '*.*' do 82 | grant 'USAGE' 83 | end 84 | end 85 | 86 | user 'bob', 'localhost' do 87 | on '*.*', with: 'GRANT OPTION' do 88 | grant 'ALL PRIVILEGES' 89 | end 90 | end 91 | RUBY 92 | } 93 | 94 | expect(show_grants).to match_array [ 95 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' WITH GRANT OPTION", 96 | "GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 97 | ].normalize 98 | end 99 | end 100 | 101 | context 'when update require' do 102 | subject { client } 103 | 104 | it do 105 | apply(subject) { 106 | <<-RUBY 107 | user 'scott', 'localhost', required: 'X509' do 108 | on '*.*' do 109 | grant 'USAGE' 110 | end 111 | end 112 | 113 | user 'bob', 'localhost', required: 'SSL' do 114 | on '*.*', with: 'GRANT OPTION' do 115 | grant 'ALL PRIVILEGES' 116 | end 117 | end 118 | RUBY 119 | } 120 | 121 | expect(show_grants).to match_array [ 122 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost' REQUIRE SSL WITH GRANT OPTION", 123 | "GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE X509", 124 | ].normalize 125 | end 126 | end 127 | 128 | context 'when update with option' do 129 | subject { client } 130 | 131 | it do 132 | apply(subject) { 133 | <<-RUBY 134 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 135 | on '*.*', with: 'GRANT OPTION MAX_QUERIES_PER_HOUR 1 MAX_UPDATES_PER_HOUR 2 MAX_CONNECTIONS_PER_HOUR 3 MAX_USER_CONNECTIONS 4' do 136 | grant 'USAGE' 137 | end 138 | end 139 | 140 | user 'bob', 'localhost' do 141 | on '*.*' do 142 | grant 'ALL PRIVILEGES' 143 | end 144 | end 145 | RUBY 146 | } 147 | 148 | expect(show_grants).to match_array [ 149 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'localhost'", 150 | "GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL WITH GRANT OPTION MAX_QUERIES_PER_HOUR 1 MAX_UPDATES_PER_HOUR 2 MAX_CONNECTIONS_PER_HOUR 3 MAX_USER_CONNECTIONS 4", 151 | ].normalize 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /spec/change/change_grants_3_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when grant privs' do 35 | subject { client(dry_run: true) } 36 | 37 | it do 38 | apply(subject) { 39 | <<-RUBY 40 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 41 | on '*.*' do 42 | grant 'SELECT' 43 | grant 'INSERT' 44 | grant 'UPDATE' 45 | grant 'DELETE' 46 | end 47 | 48 | on 'test.*' do 49 | grant 'SELECT' 50 | grant 'INSERT' 51 | grant 'UPDATE' 52 | grant 'DELETE' 53 | end 54 | 55 | on 'mysql.user' do 56 | grant 'SELECT (user)' 57 | grant 'UPDATE (host)' 58 | end 59 | end 60 | 61 | user 'bob', 'localhost' do 62 | on '*.*' do 63 | grant 'USAGE' 64 | end 65 | 66 | on 'test.*' do 67 | grant 'ALL PRIVILEGES' 68 | end 69 | end 70 | RUBY 71 | } 72 | 73 | expect(show_grants).to match_array [ 74 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 75 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 76 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 77 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 78 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 79 | ].normalize 80 | end 81 | end 82 | 83 | context 'when revoke privs' do 84 | subject { client(dry_run: true) } 85 | 86 | it do 87 | apply(subject) { 88 | <<-RUBY 89 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 90 | on '*.*' do 91 | grant 'SELECT' 92 | end 93 | 94 | on 'mysql.user' do 95 | grant 'UPDATE (host)' 96 | end 97 | end 98 | 99 | user 'bob', 'localhost' do 100 | on '*.*' do 101 | grant 'USAGE' 102 | end 103 | 104 | on 'test.*' do 105 | grant 'ALL PRIVILEGES' 106 | end 107 | end 108 | RUBY 109 | } 110 | 111 | expect(show_grants).to match_array [ 112 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 113 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 114 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 115 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 116 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 117 | ].normalize 118 | end 119 | end 120 | 121 | context 'when grant/revoke privs' do 122 | subject { client(dry_run: true) } 123 | 124 | it do 125 | apply(subject) { 126 | <<-RUBY 127 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 128 | on '*.*' do 129 | grant 'UPDATE' 130 | grant 'DELETE' 131 | end 132 | 133 | on 'test.*' do 134 | grant 'SELECT' 135 | grant 'INSERT' 136 | end 137 | 138 | on 'mysql.user' do 139 | grant 'UPDATE (host)' 140 | end 141 | end 142 | 143 | user 'mary', 'localhost' do 144 | on '*.*' do 145 | grant 'USAGE' 146 | end 147 | 148 | on 'test.*' do 149 | grant 'ALL PRIVILEGES' 150 | end 151 | end 152 | RUBY 153 | } 154 | 155 | expect(show_grants).to match_array [ 156 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 157 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 158 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 159 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 160 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 161 | ].normalize 162 | end 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /spec/change/change_grants_4_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when revoke privs with grant option' do 3 | before do 4 | apply { 5 | <<-RUBY 6 | user 'scott', 'localhost' do 7 | on '*.*' do 8 | grant 'USAGE' 9 | end 10 | 11 | on 'test.*', with: 'GRANT OPTION' do 12 | grant 'ALL PRIVILEGES' 13 | end 14 | end 15 | RUBY 16 | } 17 | end 18 | 19 | subject { client } 20 | 21 | it do 22 | apply(subject) { 23 | <<-RUBY 24 | user 'scott', 'localhost' do 25 | on '*.*' do 26 | grant 'USAGE' 27 | end 28 | end 29 | RUBY 30 | } 31 | 32 | expect(show_grants).to match_array [ 33 | "GRANT USAGE ON *.* TO 'scott'@'localhost'", 34 | ] 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/change/change_grants_expired_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | RUBY 21 | } 22 | end 23 | 24 | context 'when object has expired' do 25 | let(:logger) do 26 | logger = Logger.new('/dev/null') 27 | expect(logger).to receive(:warn).with('[WARN] User `scott@localhost`: Object `test.*` has expired') 28 | logger 29 | end 30 | 31 | subject do 32 | client( 33 | enable_expired: true, 34 | logger: logger 35 | ) 36 | end 37 | 38 | it do 39 | dsl = <<-RUBY 40 | user 'scott', 'localhost', required: 'SSL' do 41 | on '*.*' do 42 | grant 'SELECT' 43 | grant 'INSERT' 44 | end 45 | 46 | on 'test.*', expired: '2014/10/06' do 47 | grant 'UPDATE' 48 | grant 'DELETE' 49 | end 50 | 51 | on 'mysql.user' do 52 | grant 'SELECT (user)' 53 | end 54 | end 55 | RUBY 56 | 57 | Timecop.freeze(Time.parse('2014/10/06')) do 58 | apply(subject) { dsl } 59 | end 60 | 61 | expect(show_grants).to match_array [ 62 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 63 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 64 | ].normalize 65 | end 66 | end 67 | 68 | context 'when object has not expired' do 69 | subject { client(enable_expired: true) } 70 | 71 | it do 72 | dsl = <<-RUBY 73 | user 'scott', 'localhost', required: 'SSL' do 74 | on '*.*' do 75 | grant 'SELECT' 76 | grant 'INSERT' 77 | end 78 | 79 | on 'test.*', expired: '2014/10/06' do 80 | grant 'UPDATE' 81 | grant 'DELETE' 82 | end 83 | 84 | on 'mysql.user' do 85 | grant 'SELECT (user)' 86 | end 87 | end 88 | RUBY 89 | 90 | Timecop.freeze(Time.parse('2014/10/05 23:59:59')) do 91 | result = apply(subject) { dsl } 92 | expect(result).to be_falsey 93 | end 94 | 95 | expect(show_grants).to match_array [ 96 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 97 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 98 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 99 | ].normalize 100 | end 101 | end 102 | 103 | context 'when enable_expired is false' do 104 | subject { client(enable_expired: false) } 105 | 106 | it do 107 | dsl = <<-RUBY 108 | user 'scott', 'localhost', required: 'SSL' do 109 | on '*.*' do 110 | grant 'SELECT' 111 | grant 'INSERT' 112 | end 113 | 114 | on 'test.*', expired: '2014/10/06' do 115 | grant 'UPDATE' 116 | grant 'DELETE' 117 | end 118 | 119 | on 'mysql.user' do 120 | grant 'SELECT (user)' 121 | end 122 | end 123 | RUBY 124 | 125 | 126 | Timecop.freeze(Time.parse('2014/10/10')) do 127 | result = apply(subject) { dsl } 128 | expect(result).to be_falsey 129 | end 130 | 131 | expect(show_grants).to match_array [ 132 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 133 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 134 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 135 | ].normalize 136 | end 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /spec/change/change_grants_func_prcd_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | mysql do |cli| 4 | drop_database(cli) 5 | create_database(cli) 6 | create_function(cli, :my_func) 7 | create_procedure(cli, :my_prcd) 8 | end 9 | end 10 | 11 | context 'when func -> prcd' do 12 | subject { client } 13 | 14 | before do 15 | apply(subject) { 16 | <<-RUBY 17 | user "scott", "%" do 18 | on "*.*" do 19 | grant "USAGE" 20 | end 21 | 22 | on "FUNCTION #{TEST_DATABASE}.my_func" do 23 | grant "EXECUTE" 24 | end 25 | end 26 | RUBY 27 | } 28 | 29 | end 30 | 31 | it do 32 | apply(subject) { 33 | <<-RUBY 34 | user "scott", "%" do 35 | on "*.*" do 36 | grant "USAGE" 37 | end 38 | 39 | on "PROCEDURE #{TEST_DATABASE}.my_prcd" do 40 | grant "EXECUTE" 41 | end 42 | end 43 | RUBY 44 | } 45 | 46 | expect(show_grants).to match_array [ 47 | "GRANT USAGE ON *.* TO 'scott'@'%'", 48 | "GRANT EXECUTE ON PROCEDURE `#{TEST_DATABASE}`.`my_prcd` TO 'scott'@'%'" 49 | ] 50 | end 51 | end 52 | 53 | context 'when prcd -> func' do 54 | subject { client } 55 | 56 | before do 57 | apply(subject) { 58 | <<-RUBY 59 | user "scott", "%" do 60 | on "*.*" do 61 | grant "USAGE" 62 | end 63 | 64 | on "PROCEDURE #{TEST_DATABASE}.my_prcd" do 65 | grant "EXECUTE" 66 | end 67 | end 68 | RUBY 69 | } 70 | 71 | end 72 | 73 | it do 74 | apply(subject) { 75 | <<-RUBY 76 | user "scott", "%" do 77 | on "*.*" do 78 | grant "USAGE" 79 | end 80 | 81 | on "FUNCTION #{TEST_DATABASE}.my_func" do 82 | grant "EXECUTE" 83 | end 84 | end 85 | RUBY 86 | } 87 | 88 | expect(show_grants).to match_array [ 89 | "GRANT USAGE ON *.* TO 'scott'@'%'", 90 | "GRANT EXECUTE ON FUNCTION `#{TEST_DATABASE}`.`my_func` TO 'scott'@'%'" 91 | ] 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /spec/change/change_grants_ignore_not_exist_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when grant privs (ignore_not_exist: true)' do 3 | let(:logger) do 4 | logger = Logger.new('/dev/null') 5 | expect(logger).to receive(:warn).with("[WARN] Table 'yamada.taro' doesn't exist") 6 | logger 7 | end 8 | 9 | subject do 10 | client( 11 | ignore_not_exist: true, 12 | logger: logger 13 | ) 14 | end 15 | 16 | before do 17 | apply { 18 | <<-RUBY 19 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 20 | on '*.*' do 21 | grant 'SELECT' 22 | grant 'INSERT' 23 | end 24 | end 25 | RUBY 26 | } 27 | end 28 | 29 | it do 30 | apply(subject) { 31 | <<-RUBY 32 | user 'scott', 'localhost', required: 'SSL' do 33 | on '*.*' do 34 | grant 'SELECT' 35 | grant 'INSERT' 36 | grant 'UPDATE' 37 | grant 'DELETE' 38 | end 39 | 40 | on 'yamada.taro' do 41 | grant 'SELECT' 42 | grant 'INSERT' 43 | grant 'UPDATE' 44 | grant 'DELETE' 45 | end 46 | end 47 | RUBY 48 | } 49 | 50 | expect(show_grants).to match_array [ 51 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 52 | ].normalize 53 | end 54 | end 55 | 56 | context 'when grant privs (ignore_not_exist: false)' do 57 | subject { client(ignore_not_exist: false) } 58 | 59 | before do 60 | apply { 61 | <<-RUBY 62 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 63 | on '*.*' do 64 | grant 'SELECT' 65 | grant 'INSERT' 66 | end 67 | end 68 | RUBY 69 | } 70 | end 71 | 72 | it do 73 | dsl = <<-RUBY 74 | user 'scott', 'localhost', required: 'SSL' do 75 | on '*.*' do 76 | grant 'SELECT' 77 | grant 'INSERT' 78 | grant 'UPDATE' 79 | grant 'DELETE' 80 | end 81 | 82 | on 'yamada.taro' do 83 | grant 'SELECT' 84 | grant 'INSERT' 85 | grant 'UPDATE' 86 | grant 'DELETE' 87 | end 88 | end 89 | RUBY 90 | 91 | expect { 92 | apply(subject) { dsl } 93 | }.to raise_error("Table 'yamada.taro' doesn't exist") 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /spec/change/change_grants_multi_hosts_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when change privs (multi hosts)' do 3 | before(:each) do 4 | apply { 5 | <<-RUBY 6 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 7 | on '*.*' do 8 | grant 'SELECT' 9 | grant 'INSERT' 10 | end 11 | 12 | on 'test.*' do 13 | grant 'UPDATE' 14 | grant 'DELETE' 15 | end 16 | 17 | on 'mysql.user' do 18 | grant 'SELECT (user)' 19 | end 20 | end 21 | 22 | user 'scott', '%', identified: 'tiger', required: 'SSL' do 23 | on '*.*' do 24 | grant 'SELECT' 25 | grant 'INSERT' 26 | end 27 | 28 | on 'test.*' do 29 | grant 'UPDATE' 30 | grant 'DELETE' 31 | end 32 | 33 | on 'mysql.user' do 34 | grant 'SELECT (user)' 35 | end 36 | end 37 | RUBY 38 | } 39 | end 40 | 41 | subject { client } 42 | 43 | it do 44 | apply(subject) { 45 | <<-RUBY 46 | user 'scott', ['localhost', '%', '127.0.0.1'], required: 'SSL' do 47 | on '*.*' do 48 | grant 'SELECT' 49 | grant 'DELETE' 50 | end 51 | 52 | on 'test.*' do 53 | grant 'UPDATE' 54 | grant 'INSERT' 55 | end 56 | end 57 | RUBY 58 | } 59 | 60 | expect(show_grants).to match_array [ 61 | "GRANT INSERT, UPDATE ON `test`.* TO 'scott'@'%'", 62 | "GRANT INSERT, UPDATE ON `test`.* TO 'scott'@'127.0.0.1'", 63 | "GRANT INSERT, UPDATE ON `test`.* TO 'scott'@'localhost'", 64 | "GRANT SELECT, DELETE ON *.* TO 'scott'@'%' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 65 | "GRANT SELECT, DELETE ON *.* TO 'scott'@'127.0.0.1' REQUIRE SSL", 66 | "GRANT SELECT, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 67 | ].normalize 68 | end 69 | end 70 | 71 | context 'when no change privs (multi hosts)' do 72 | before(:each) do 73 | apply { 74 | <<-RUBY 75 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 76 | on '*.*' do 77 | grant 'SELECT' 78 | grant 'INSERT' 79 | end 80 | 81 | on 'test.*' do 82 | grant 'UPDATE' 83 | grant 'DELETE' 84 | end 85 | 86 | on 'mysql.user' do 87 | grant 'SELECT (user)' 88 | end 89 | end 90 | 91 | user 'scott', '%', identified: 'tiger', required: 'SSL' do 92 | on '*.*' do 93 | grant 'SELECT' 94 | grant 'INSERT' 95 | end 96 | 97 | on 'test.*' do 98 | grant 'UPDATE' 99 | grant 'DELETE' 100 | end 101 | 102 | on 'mysql.user' do 103 | grant 'SELECT (user)' 104 | end 105 | end 106 | RUBY 107 | } 108 | end 109 | 110 | subject { client } 111 | 112 | it do 113 | result = apply(subject) { 114 | <<-RUBY 115 | user 'scott', ['localhost', '%'], required: 'SSL' do 116 | on '*.*' do 117 | grant 'SELECT' 118 | grant 'INSERT' 119 | end 120 | 121 | on 'test.*' do 122 | grant 'UPDATE' 123 | grant 'DELETE' 124 | end 125 | 126 | on 'mysql.user' do 127 | grant 'SELECT (user)' 128 | end 129 | end 130 | RUBY 131 | } 132 | 133 | expect(result).to be_falsey 134 | 135 | expect(show_grants).to match_array [ 136 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'%'", 137 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 138 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'%' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 139 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 140 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'%'", 141 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 142 | ].normalize 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /spec/change/change_grants_regexp_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when change privs using regexp' do 35 | subject { client } 36 | 37 | it do 38 | dsl = <<-RUBY 39 | user 'scott', 'localhost', required: 'SSL' do 40 | on '*.*' do 41 | grant 'SELECT' 42 | grant 'INSERT' 43 | end 44 | 45 | on 'test.*' do 46 | grant 'UPDATE' 47 | grant 'DELETE' 48 | end 49 | 50 | on /\\Agratan_test\\.(foo|bar)\\z/ do 51 | grant 'SELECT' 52 | grant 'INSERT' 53 | end 54 | end 55 | 56 | user 'bob', 'localhost' do 57 | on '*.*' do 58 | grant 'USAGE' 59 | end 60 | 61 | on 'test.*' do 62 | grant 'ALL PRIVILEGES' 63 | end 64 | 65 | on /\\Agratan_test\\.z/ do 66 | grant 'UPDATE' 67 | grant 'DELETE' 68 | end 69 | end 70 | RUBY 71 | 72 | create_tables(:foo, :bar, :zoo, :baz) do 73 | apply(subject) { dsl } 74 | 75 | expect(show_grants).to match_array [ 76 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 77 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 78 | "GRANT SELECT, INSERT ON `gratan_test`.`bar` TO 'scott'@'localhost'", 79 | "GRANT SELECT, INSERT ON `gratan_test`.`foo` TO 'scott'@'localhost'", 80 | "GRANT UPDATE, DELETE ON `gratan_test`.`zoo` TO 'bob'@'localhost'", 81 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 82 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 83 | ].normalize 84 | end 85 | end 86 | end 87 | 88 | context 'when no change privs using regexp' do 89 | subject { client } 90 | 91 | it do 92 | dsl = <<-RUBY 93 | user 'scott', 'localhost', required: 'SSL' do 94 | on '*.*' do 95 | grant 'SELECT' 96 | grant 'INSERT' 97 | end 98 | 99 | on 'test.*' do 100 | grant 'UPDATE' 101 | grant 'DELETE' 102 | end 103 | 104 | on 'mysql.user' do 105 | grant 'SELECT (user)' 106 | end 107 | 108 | on /\\Agratan_test\\.x(foo|bar)\\z/ do 109 | grant 'SELECT' 110 | grant 'INSERT' 111 | end 112 | end 113 | 114 | user 'bob', 'localhost' do 115 | on '*.*' do 116 | grant 'USAGE' 117 | end 118 | 119 | on 'test.*' do 120 | grant 'ALL PRIVILEGES' 121 | end 122 | 123 | on /\\Agratan_test\\.xz/ do 124 | grant 'UPDATE' 125 | grant 'DELETE' 126 | end 127 | end 128 | RUBY 129 | 130 | create_tables(:foo, :bar, :zoo, :baz) do 131 | result = apply(subject) { dsl } 132 | expect(result).to be_falsey 133 | 134 | expect(show_grants).to match_array [ 135 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 136 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 137 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 138 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 139 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 140 | ].normalize 141 | end 142 | end 143 | end 144 | end 145 | -------------------------------------------------------------------------------- /spec/change/change_grants_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when grant privs' do 35 | subject { client } 36 | 37 | it do 38 | apply(subject) { 39 | <<-RUBY 40 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 41 | on '*.*' do 42 | grant 'SELECT' 43 | grant 'INSERT' 44 | grant 'UPDATE' 45 | grant 'DELETE' 46 | end 47 | 48 | on 'test.*' do 49 | grant 'SELECT' 50 | grant 'INSERT' 51 | grant 'UPDATE' 52 | grant 'DELETE' 53 | end 54 | 55 | on 'mysql.user' do 56 | grant 'SELECT (user)' 57 | grant 'UPDATE (host)' 58 | end 59 | end 60 | 61 | user 'bob', 'localhost' do 62 | on '*.*' do 63 | grant 'USAGE' 64 | end 65 | 66 | on 'test.*' do 67 | grant 'ALL PRIVILEGES' 68 | end 69 | end 70 | RUBY 71 | } 72 | 73 | expect(show_grants).to match_array [ 74 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 75 | "GRANT SELECT (user), UPDATE (host) ON `mysql`.`user` TO 'scott'@'localhost'", 76 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 77 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 78 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 79 | ].normalize 80 | end 81 | end 82 | 83 | context 'when revoke privs' do 84 | subject { client } 85 | 86 | it do 87 | apply(subject) { 88 | <<-RUBY 89 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 90 | on '*.*' do 91 | grant 'SELECT' 92 | end 93 | 94 | on 'mysql.user' do 95 | grant 'UPDATE (host)' 96 | end 97 | end 98 | 99 | user 'bob', 'localhost' do 100 | on '*.*' do 101 | grant 'USAGE' 102 | end 103 | 104 | on 'test.*' do 105 | grant 'ALL PRIVILEGES' 106 | end 107 | end 108 | RUBY 109 | } 110 | 111 | expect(show_grants).to match_array [ 112 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 113 | "GRANT SELECT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 114 | "GRANT UPDATE (host) ON `mysql`.`user` TO 'scott'@'localhost'", 115 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 116 | ].normalize 117 | end 118 | end 119 | 120 | context 'when grant/revoke privs' do 121 | subject { client } 122 | 123 | it do 124 | apply(subject) { 125 | <<-RUBY 126 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 127 | on '*.*' do 128 | grant 'UPDATE' 129 | grant 'DELETE' 130 | end 131 | 132 | on 'test.*' do 133 | grant 'SELECT' 134 | grant 'INSERT' 135 | end 136 | 137 | on 'mysql.user' do 138 | grant 'UPDATE (host)' 139 | end 140 | end 141 | 142 | user 'mary', 'localhost' do 143 | on '*.*' do 144 | grant 'USAGE' 145 | end 146 | 147 | on 'test.*' do 148 | grant 'ALL PRIVILEGES' 149 | end 150 | end 151 | RUBY 152 | } 153 | 154 | expect(show_grants).to match_array [ 155 | "GRANT ALL PRIVILEGES ON `test`.* TO 'mary'@'localhost'", 156 | "GRANT SELECT, INSERT ON `test`.* TO 'scott'@'localhost'", 157 | "GRANT UPDATE (host) ON `mysql`.`user` TO 'scott'@'localhost'", 158 | "GRANT UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 159 | "GRANT USAGE ON *.* TO 'mary'@'localhost'", 160 | ].normalize 161 | end 162 | end 163 | 164 | context 'when all privileges is normalized' do 165 | subject { client } 166 | 167 | it do 168 | result = apply(subject) { 169 | <<-RUBY 170 | user 'scott', 'localhost', required: 'SSL' do 171 | on '*.*' do 172 | grant 'SELECT' 173 | grant 'INSERT' 174 | end 175 | 176 | on 'test.*' do 177 | grant 'UPDATE' 178 | grant 'DELETE' 179 | end 180 | 181 | on 'mysql.user' do 182 | grant 'SELECT (user)' 183 | end 184 | end 185 | 186 | user 'bob', 'localhost' do 187 | on '*.*' do 188 | grant 'USAGE' 189 | end 190 | 191 | on 'test.*' do 192 | grant 'ALL' 193 | end 194 | end 195 | RUBY 196 | } 197 | 198 | expect(result).to be_falsey 199 | 200 | expect(show_grants).to match_array [ 201 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 202 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 203 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 204 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 205 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 206 | ].normalize 207 | end 208 | end 209 | end 210 | -------------------------------------------------------------------------------- /spec/change/change_grants_target_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when grant privs with target' do 35 | subject { client(target_user: /bob/) } 36 | 37 | it do 38 | apply(subject) { 39 | <<-RUBY 40 | user 'scott', 'localhost', required: 'SSL' do 41 | on '*.*' do 42 | grant 'SELECT' 43 | grant 'INSERT' 44 | grant 'UPDATE' 45 | grant 'DELETE' 46 | end 47 | 48 | on 'test.*' do 49 | grant 'SELECT' 50 | grant 'INSERT' 51 | grant 'UPDATE' 52 | grant 'DELETE' 53 | end 54 | 55 | on 'mysql.user' do 56 | grant 'SELECT (user)' 57 | grant 'UPDATE (host)' 58 | end 59 | end 60 | 61 | user 'bob', 'localhost' do 62 | on '*.*' do 63 | grant 'USAGE' 64 | end 65 | 66 | on 'test.*' do 67 | grant 'SELECT' 68 | end 69 | end 70 | RUBY 71 | } 72 | 73 | expect(show_grants).to match_array [ 74 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 75 | "GRANT SELECT ON `test`.* TO 'bob'@'localhost'", 76 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 77 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 78 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 79 | ].normalize 80 | end 81 | end 82 | 83 | context 'when grant privs with target (no change)' do 84 | subject { client(target_user: /mary/) } 85 | 86 | it do 87 | dsl = <<-RUBY 88 | user 'scott', 'localhost', required: 'SSL' do 89 | on '*.*' do 90 | grant 'SELECT' 91 | grant 'INSERT' 92 | grant 'UPDATE' 93 | grant 'DELETE' 94 | end 95 | 96 | on 'test.*' do 97 | grant 'SELECT' 98 | grant 'INSERT' 99 | grant 'UPDATE' 100 | grant 'DELETE' 101 | end 102 | 103 | on 'mysql.user' do 104 | grant 'SELECT (user)' 105 | grant 'UPDATE (host)' 106 | end 107 | end 108 | 109 | user 'bob', 'localhost' do 110 | on '*.*' do 111 | grant 'USAGE' 112 | end 113 | 114 | on 'test.*' do 115 | grant 'SELECT' 116 | end 117 | end 118 | RUBY 119 | 120 | result = apply(subject) { dsl } 121 | expect(result).to be_falsey 122 | 123 | expect(show_grants).to match_array [ 124 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 125 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 126 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 127 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 128 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 129 | ].normalize 130 | end 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /spec/change/change_grants_with_ignore_object_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when grant privs with ignore_object' do 35 | subject { client(ignore_object: /user/) } 36 | 37 | it do 38 | apply(subject) { 39 | <<-RUBY 40 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 41 | on '*.*' do 42 | grant 'SELECT' 43 | grant 'INSERT' 44 | grant 'UPDATE' 45 | grant 'DELETE' 46 | end 47 | 48 | on 'test.*' do 49 | grant 'SELECT' 50 | grant 'INSERT' 51 | grant 'UPDATE' 52 | grant 'DELETE' 53 | end 54 | 55 | on 'mysql.user' do 56 | grant 'SELECT (user)' 57 | grant 'UPDATE (host)' # ignore 58 | end 59 | end 60 | 61 | user 'bob', 'localhost' do 62 | on '*.*' do 63 | grant 'USAGE' 64 | end 65 | 66 | on 'test.*' do 67 | grant 'ALL PRIVILEGES' 68 | end 69 | end 70 | RUBY 71 | } 72 | 73 | expect(show_grants).to match_array [ 74 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 75 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 76 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 77 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 78 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 79 | ].normalize 80 | end 81 | end 82 | 83 | context 'when grant privs with ignore_object (2)' do 84 | subject { client(ignore_object: /user2/) } 85 | 86 | it do 87 | apply(subject) { 88 | <<-RUBY 89 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 90 | on '*.*' do 91 | grant 'SELECT' 92 | grant 'INSERT' 93 | grant 'UPDATE' 94 | grant 'DELETE' 95 | end 96 | 97 | on 'test.*' do 98 | grant 'SELECT' 99 | grant 'INSERT' 100 | grant 'UPDATE' 101 | grant 'DELETE' 102 | end 103 | 104 | on 'mysql.user' do 105 | grant 'SELECT (user)' 106 | grant 'UPDATE (host)' 107 | end 108 | end 109 | 110 | user 'bob', 'localhost' do 111 | on '*.*' do 112 | grant 'USAGE' 113 | end 114 | 115 | on 'test.*' do 116 | grant 'ALL PRIVILEGES' 117 | end 118 | end 119 | RUBY 120 | } 121 | 122 | expect(show_grants).to match_array [ 123 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 124 | "GRANT SELECT (user), UPDATE (host) ON `mysql`.`user` TO 'scott'@'localhost'", 125 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 126 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 127 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 128 | ].normalize 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /spec/create/create_user_2_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user with auto identify' do 3 | let(:auto_identifier) do 4 | identifier = Gratan::Identifier::Auto.new('/dev/null') 5 | allow(identifier).to receive(:mkpasswd) { 'foobarzoo' } 6 | identifier 7 | end 8 | 9 | subject { client(identifier: auto_identifier) } 10 | 11 | it do 12 | apply(subject) { 13 | <<-RUBY 14 | user 'scott', 'localhost' do 15 | on '*.*' do 16 | grant 'SELECT' 17 | grant 'INSERT' 18 | grant 'UPDATE' 19 | grant 'DELETE' 20 | end 21 | 22 | on 'test.*' do 23 | grant 'SELECT' 24 | grant 'INSERT' 25 | grant 'UPDATE' 26 | grant 'DELETE' 27 | end 28 | end 29 | RUBY 30 | } 31 | 32 | expect(show_grants).to match_array [ 33 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*6F498C84277BCC2089932690304BD4EDABC74547'", 34 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 35 | ].normalize 36 | end 37 | end 38 | 39 | context 'when create user with auto identify (dry-run)' do 40 | let(:auto_identifier) do 41 | identifier = Gratan::Identifier::Auto.new('/dev/null') 42 | allow(identifier).to receive(:mkpasswd) { 'foobarzoo' } 43 | identifier 44 | end 45 | 46 | subject { client(identifier: auto_identifier, dry_run: true) } 47 | 48 | it do 49 | apply(subject) { 50 | <<-RUBY 51 | user 'scott', 'localhost' do 52 | on '*.*' do 53 | grant 'SELECT' 54 | grant 'INSERT' 55 | grant 'UPDATE' 56 | grant 'DELETE' 57 | end 58 | 59 | on 'test.*' do 60 | grant 'SELECT' 61 | grant 'INSERT' 62 | grant 'UPDATE' 63 | grant 'DELETE' 64 | end 65 | end 66 | RUBY 67 | } 68 | 69 | expect(show_grants).to match_array [] 70 | end 71 | end 72 | 73 | context 'when create user with csv identify' do 74 | let(:csv_identifier) do 75 | identifier = nil 76 | 77 | csv = <<-CSV 78 | scott@localhost,foobarzoo 79 | CSV 80 | 81 | tempfile(csv) do |f| 82 | identifier = Gratan::Identifier::CSV.new(f.path) 83 | end 84 | 85 | identifier 86 | end 87 | 88 | subject { client(identifier: csv_identifier) } 89 | 90 | it do 91 | apply(subject) { 92 | <<-RUBY 93 | user 'scott', 'localhost' do 94 | on '*.*' do 95 | grant 'SELECT' 96 | grant 'INSERT' 97 | grant 'UPDATE' 98 | grant 'DELETE' 99 | end 100 | 101 | on 'test.*' do 102 | grant 'SELECT' 103 | grant 'INSERT' 104 | grant 'UPDATE' 105 | grant 'DELETE' 106 | end 107 | end 108 | RUBY 109 | } 110 | 111 | expect(show_grants).to match_array [ 112 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*6F498C84277BCC2089932690304BD4EDABC74547'", 113 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 114 | ].normalize 115 | end 116 | end 117 | 118 | context 'when create user with csv identify (there is no password)' do 119 | let(:logger) do 120 | logger = Logger.new('/dev/null') 121 | expect(logger).to receive(:warn).with('[WARN] password for `scott@localhost` can not be found') 122 | logger 123 | end 124 | 125 | let(:csv_identifier) do 126 | identifier = nil 127 | 128 | csv = <<-CSV 129 | scott2@localhost,foobarzoo 130 | CSV 131 | 132 | tempfile(csv) do |f| 133 | identifier = Gratan::Identifier::CSV.new(f.path, logger: logger) 134 | end 135 | 136 | identifier 137 | end 138 | 139 | subject do 140 | client( 141 | identifier: csv_identifier, 142 | logger: logger 143 | ) 144 | end 145 | 146 | it do 147 | apply(subject) { 148 | <<-RUBY 149 | user 'scott', 'localhost' do 150 | on '*.*' do 151 | grant 'SELECT' 152 | grant 'INSERT' 153 | grant 'UPDATE' 154 | grant 'DELETE' 155 | end 156 | 157 | on 'test.*' do 158 | grant 'SELECT' 159 | grant 'INSERT' 160 | grant 'UPDATE' 161 | grant 'DELETE' 162 | end 163 | end 164 | RUBY 165 | } 166 | 167 | expect(show_grants).to match_array [ 168 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost'", 169 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 170 | ] 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /spec/create/create_user_3_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user' do 3 | subject { client(dry_run: true) } 4 | 5 | it do 6 | result = apply(subject) { 7 | <<-RUBY 8 | user 'scott', 'localhost', identified: 'tiger' do 9 | on '*.*' do 10 | grant 'SELECT' 11 | grant 'INSERT' 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'test.*' do 17 | grant 'SELECT' 18 | grant 'INSERT' 19 | grant 'UPDATE' 20 | grant 'DELETE' 21 | end 22 | end 23 | RUBY 24 | } 25 | 26 | expect(result).to be_falsey 27 | expect(show_grants).to match_array [] 28 | end 29 | end 30 | 31 | context 'when add user' do 32 | before do 33 | apply { 34 | <<-RUBY 35 | user 'bob', '%', required: 'SSL' do 36 | on '*.*' do 37 | grant 'ALL PRIVILEGES' 38 | end 39 | 40 | on 'test.*' do 41 | grant 'SELECT' 42 | end 43 | end 44 | RUBY 45 | } 46 | end 47 | 48 | subject { client(dry_run: true) } 49 | 50 | it do 51 | apply(subject) { 52 | <<-RUBY 53 | user 'bob', '%', required: 'SSL' do 54 | on '*.*' do 55 | grant 'ALL PRIVILEGES' 56 | end 57 | 58 | on 'test.*' do 59 | grant 'SELECT' 60 | end 61 | end 62 | 63 | user 'scott', 'localhost', identified: 'tiger' do 64 | on '*.*' do 65 | grant 'SELECT' 66 | grant 'INSERT' 67 | grant 'UPDATE' 68 | grant 'DELETE' 69 | end 70 | 71 | on 'test.*' do 72 | grant 'SELECT' 73 | grant 'INSERT' 74 | grant 'UPDATE' 75 | grant 'DELETE' 76 | end 77 | end 78 | RUBY 79 | } 80 | 81 | expect(show_grants).to match_array [ 82 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'%' REQUIRE SSL", 83 | "GRANT SELECT ON `test`.* TO 'bob'@'%'", 84 | ].normalize 85 | end 86 | end 87 | 88 | context 'when create user with grant option' do 89 | subject { client(dry_run: true) } 90 | 91 | it do 92 | apply(subject) { 93 | <<-RUBY 94 | user 'scott', 'localhost', identified: 'tiger' do 95 | on '*.*', with: 'grant option' do 96 | grant 'SELECT' 97 | grant 'INSERT' 98 | grant 'UPDATE' 99 | grant 'DELETE' 100 | end 101 | 102 | on 'test.*' do 103 | grant 'SELECT' 104 | grant 'INSERT' 105 | grant 'UPDATE' 106 | grant 'DELETE' 107 | end 108 | end 109 | RUBY 110 | } 111 | 112 | expect(show_grants).to match_array [] 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/create/create_user_multi_hosts_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user (multi hosts)' do 3 | subject { client } 4 | 5 | it do 6 | apply(subject) { 7 | <<-RUBY 8 | user 'scott', ['localhost', '127.0.0.1', '192.168.%'], identified: 'tiger' do 9 | on '*.*' do 10 | grant 'SELECT' 11 | grant 'INSERT' 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'test.*' do 17 | grant 'SELECT' 18 | grant 'INSERT' 19 | grant 'UPDATE' 20 | grant 'DELETE' 21 | end 22 | end 23 | RUBY 24 | } 25 | 26 | expect(show_grants).to match_array [ 27 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 28 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 29 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'127.0.0.1' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 30 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'127.0.0.1'", 31 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'192.168.%' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 32 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'192.168.%'", 33 | ].normalize 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/create/create_user_regexp_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user using regexp' do 3 | subject { client } 4 | 5 | it do 6 | dsl = <<-RUBY 7 | user 'scott', 'localhost', identified: 'tiger' do 8 | on 'test.*' do 9 | grant 'SELECT' 10 | grant 'INSERT' 11 | grant 'UPDATE' 12 | grant 'DELETE' 13 | end 14 | 15 | on /\\Agratan_test\\.(foo|bar)\\z/ do 16 | grant 'SELECT' 17 | grant 'INSERT' 18 | end 19 | 20 | on /\\Agratan_test\\.z/ do 21 | grant 'UPDATE' 22 | grant 'DELETE' 23 | end 24 | end 25 | RUBY 26 | 27 | create_tables(:foo, :bar, :zoo, :baz) do 28 | apply(subject) { dsl } 29 | 30 | expect(show_grants).to match_array [ 31 | "GRANT SELECT, INSERT ON `gratan_test`.`bar` TO 'scott'@'localhost'", 32 | "GRANT SELECT, INSERT ON `gratan_test`.`foo` TO 'scott'@'localhost'", 33 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 34 | "GRANT UPDATE, DELETE ON `gratan_test`.`zoo` TO 'scott'@'localhost'", 35 | "GRANT USAGE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 36 | ].normalize 37 | end 38 | end 39 | end 40 | 41 | context 'when create user using regexp (no privileges)' do 42 | let(:logger) do 43 | logger = Logger.new('/dev/null') 44 | expect(logger).to receive(:warn).with("[WARN] there was no privileges to grant to 'scott'@'localhost'") 45 | logger 46 | end 47 | 48 | subject { client(logger: logger) } 49 | 50 | it do 51 | dsl = <<-RUBY 52 | user 'scott', 'localhost', identified: 'tiger' do 53 | on /\\Agratan_test\\.x(foo|bar)\\z/ do 54 | grant 'SELECT' 55 | grant 'INSERT' 56 | end 57 | end 58 | RUBY 59 | 60 | create_tables(:foo, :bar, :zoo, :baz) do 61 | apply(subject) { dsl } 62 | 63 | expect(show_grants).to match_array [] 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/create/create_user_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when user does not exist' do 3 | subject { client } 4 | 5 | it do 6 | result = apply(subject) { '' } 7 | expect(result).to be_falsey 8 | expect(show_grants).to match_array [] 9 | end 10 | end 11 | 12 | context 'when create user' do 13 | subject { client } 14 | 15 | it do 16 | result = apply(subject) { 17 | <<-RUBY 18 | user 'scott', 'localhost', identified: 'tiger' do 19 | on '*.*' do 20 | grant 'SELECT' 21 | grant 'INSERT' 22 | grant 'UPDATE' 23 | grant 'DELETE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'SELECT' 28 | grant 'INSERT' 29 | grant 'UPDATE' 30 | grant 'DELETE' 31 | end 32 | end 33 | RUBY 34 | } 35 | 36 | expect(result).to be_truthy 37 | 38 | expect(show_grants).to match_array [ 39 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 40 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 41 | ].normalize 42 | end 43 | end 44 | 45 | context 'when add user' do 46 | before do 47 | apply { 48 | <<-RUBY 49 | user 'bob', '%', required: 'SSL' do 50 | on '*.*' do 51 | grant 'ALL PRIVILEGES' 52 | end 53 | 54 | on 'test.*' do 55 | grant 'SELECT' 56 | end 57 | end 58 | RUBY 59 | } 60 | end 61 | 62 | subject { client } 63 | 64 | it do 65 | apply(subject) { 66 | <<-RUBY 67 | user 'bob', '%', required: 'SSL' do 68 | on '*.*' do 69 | grant 'ALL PRIVILEGES' 70 | end 71 | 72 | on 'test.*' do 73 | grant 'SELECT' 74 | end 75 | end 76 | 77 | user 'scott', 'localhost', identified: 'tiger' do 78 | on '*.*' do 79 | grant 'SELECT' 80 | grant 'INSERT' 81 | grant 'UPDATE' 82 | grant 'DELETE' 83 | end 84 | 85 | on 'test.*' do 86 | grant 'SELECT' 87 | grant 'INSERT' 88 | grant 'UPDATE' 89 | grant 'DELETE' 90 | end 91 | end 92 | RUBY 93 | } 94 | 95 | expect(show_grants).to match_array [ 96 | "GRANT ALL PRIVILEGES ON *.* TO 'bob'@'%' REQUIRE SSL", 97 | "GRANT SELECT ON `test`.* TO 'bob'@'%'", 98 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 99 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 100 | ].normalize 101 | end 102 | end 103 | 104 | context 'when create user with grant option' do 105 | subject { client } 106 | 107 | it do 108 | apply(subject) { 109 | <<-RUBY 110 | user 'scott', 'localhost', identified: 'tiger' do 111 | on '*.*', with: 'grant option' do 112 | grant 'SELECT' 113 | grant 'INSERT' 114 | grant 'UPDATE' 115 | grant 'DELETE' 116 | end 117 | 118 | on 'test.*' do 119 | grant 'SELECT' 120 | grant 'INSERT' 121 | grant 'UPDATE' 122 | grant 'DELETE' 123 | end 124 | end 125 | RUBY 126 | } 127 | 128 | expect(show_grants).to match_array [ 129 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' WITH GRANT OPTION", 130 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 131 | ].normalize 132 | end 133 | end 134 | 135 | context 'when duplicata user' do 136 | subject { client } 137 | 138 | it do 139 | dsl = <<-RUBY 140 | user 'scott', 'localhost', required: 'SSL' do 141 | on '*.*' do 142 | grant 'ALL PRIVILEGES' 143 | end 144 | 145 | on 'test.*' do 146 | grant 'SELECT' 147 | end 148 | end 149 | 150 | user 'scott', 'localhost', identified: 'tiger' do 151 | on '*.*' do 152 | grant 'SELECT' 153 | grant 'INSERT' 154 | grant 'UPDATE' 155 | grant 'DELETE' 156 | end 157 | 158 | on 'test.*' do 159 | grant 'SELECT' 160 | grant 'INSERT' 161 | grant 'UPDATE' 162 | grant 'DELETE' 163 | end 164 | end 165 | RUBY 166 | 167 | expect { 168 | apply(subject) { dsl } 169 | }.to raise_error('User `scott@localhost` is already defined') 170 | end 171 | end 172 | 173 | context 'when duplicata object' do 174 | subject { client } 175 | 176 | it do 177 | dsl = <<-RUBY 178 | user 'scott', 'localhost', required: 'SSL' do 179 | on '*.*' do 180 | grant 'ALL PRIVILEGES' 181 | end 182 | 183 | on '*.*' do 184 | grant 'SELECT' 185 | end 186 | end 187 | RUBY 188 | 189 | expect { 190 | apply(subject) { dsl } 191 | }.to raise_error('User `scott@localhost`: Object `*.*` is already defined') 192 | end 193 | end 194 | end 195 | -------------------------------------------------------------------------------- /spec/create/create_user_target_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user with target' do 3 | subject { client(target_user: /scott/) } 4 | 5 | it do 6 | apply(subject) { 7 | <<-RUBY 8 | user 'scott', 'localhost', identified: 'tiger' do 9 | on '*.*' do 10 | grant 'SELECT' 11 | grant 'INSERT' 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'test.*' do 17 | grant 'SELECT' 18 | grant 'INSERT' 19 | grant 'UPDATE' 20 | grant 'DELETE' 21 | end 22 | end 23 | 24 | user 'bob', '%', required: 'SSL' do 25 | on '*.*' do 26 | grant 'ALL PRIVILEGES' 27 | end 28 | 29 | on 'test.*' do 30 | grant 'SELECT' 31 | end 32 | end 33 | RUBY 34 | } 35 | 36 | expect(show_grants).to match_array [ 37 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 38 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 39 | ].normalize 40 | end 41 | end 42 | 43 | context 'when create user with target (no change)' do 44 | subject { client(target_user: /mary/) } 45 | 46 | it do 47 | dsl = <<-RUBY 48 | user 'scott', 'localhost', identified: 'tiger' do 49 | on '*.*' do 50 | grant 'SELECT' 51 | grant 'INSERT' 52 | grant 'UPDATE' 53 | grant 'DELETE' 54 | end 55 | 56 | on 'test.*' do 57 | grant 'SELECT' 58 | grant 'INSERT' 59 | grant 'UPDATE' 60 | grant 'DELETE' 61 | end 62 | end 63 | 64 | user 'bob', '%', required: 'SSL' do 65 | on '*.*' do 66 | grant 'ALL PRIVILEGES' 67 | end 68 | 69 | on 'test.*' do 70 | grant 'SELECT' 71 | end 72 | end 73 | RUBY 74 | 75 | result = apply(subject) { dsl } 76 | expect(result).to be_falsey 77 | 78 | expect(show_grants).to match_array [] 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/create/create_user_with_func_prcd_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user with function' do 3 | subject { client } 4 | 5 | it do 6 | create_functions(:foo) do 7 | result = apply(subject) { 8 | <<-RUBY 9 | user 'scott', 'localhost' do 10 | on '*.*' do 11 | grant 'USAGE' 12 | end 13 | 14 | on 'FUNCTION #{TEST_DATABASE}.foo' do 15 | grant 'EXECUTE' 16 | end 17 | end 18 | RUBY 19 | } 20 | 21 | expect(result).to be_truthy 22 | 23 | expect(show_grants).to match_array [ 24 | "GRANT USAGE ON *.* TO 'scott'@'localhost'", 25 | "GRANT EXECUTE ON FUNCTION `#{TEST_DATABASE}`.`foo` TO 'scott'@'localhost'" 26 | ] 27 | end 28 | end 29 | end 30 | 31 | context 'when create user with procedure' do 32 | subject { client } 33 | 34 | it do 35 | create_procedures(:foo) do 36 | result = apply(subject) { 37 | <<-RUBY 38 | user 'scott', 'localhost' do 39 | on '*.*' do 40 | grant 'USAGE' 41 | end 42 | 43 | on 'PROCEDURE #{TEST_DATABASE}.foo' do 44 | grant 'EXECUTE' 45 | end 46 | end 47 | RUBY 48 | } 49 | 50 | expect(result).to be_truthy 51 | 52 | expect(show_grants).to match_array [ 53 | "GRANT USAGE ON *.* TO 'scott'@'localhost'", 54 | "GRANT EXECUTE ON PROCEDURE `#{TEST_DATABASE}`.`foo` TO 'scott'@'localhost'" 55 | ] 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/create/create_user_with_ignore_object_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when user does not exist' do 3 | subject { client } 4 | 5 | it do 6 | result = apply(subject) { '' } 7 | expect(result).to be_falsey 8 | expect(show_grants).to match_array [] 9 | end 10 | end 11 | 12 | context 'when create user with ignore_object' do 13 | subject { client(ignore_object: /test/) } 14 | 15 | it do 16 | result = apply(subject) { 17 | <<-RUBY 18 | user 'scott', 'localhost', identified: 'tiger' do 19 | on '*.*' do 20 | grant 'SELECT' 21 | grant 'INSERT' 22 | grant 'UPDATE' 23 | grant 'DELETE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'SELECT' 28 | grant 'INSERT' 29 | grant 'UPDATE' 30 | grant 'DELETE' 31 | end 32 | end 33 | RUBY 34 | } 35 | 36 | expect(result).to be_truthy 37 | 38 | expect(show_grants).to match_array [ 39 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 40 | ].normalize 41 | end 42 | end 43 | 44 | context 'when create user with ignore_object (2)' do 45 | subject { client(ignore_object: /test2/) } 46 | 47 | it do 48 | result = apply(subject) { 49 | <<-RUBY 50 | user 'scott', 'localhost', identified: 'tiger' do 51 | on '*.*' do 52 | grant 'SELECT' 53 | grant 'INSERT' 54 | grant 'UPDATE' 55 | grant 'DELETE' 56 | end 57 | 58 | on 'test.*' do 59 | grant 'SELECT' 60 | grant 'INSERT' 61 | grant 'UPDATE' 62 | grant 'DELETE' 63 | end 64 | end 65 | RUBY 66 | } 67 | 68 | expect(result).to be_truthy 69 | 70 | expect(show_grants).to match_array [ 71 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 72 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 73 | ].normalize 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/create/create_user_with_template_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when create user with template' do 3 | subject { client } 4 | 5 | it do 6 | result = apply(subject) { 7 | <<-RUBY 8 | template 'all db template' do 9 | on '*.*' do 10 | grant 'SELECT' 11 | end 12 | end 13 | 14 | template 'test db template' do 15 | grant context.default 16 | 17 | context.extra.each do |priv| 18 | grant priv 19 | end 20 | end 21 | 22 | user 'scott', 'localhost', identified: 'tiger' do 23 | include_template 'all db template' 24 | 25 | on 'test.*' do 26 | context.default = 'SELECT' 27 | include_template 'test db template', extra: ['INSERT', 'UPDATE'] 28 | end 29 | end 30 | RUBY 31 | } 32 | 33 | expect(result).to be_truthy 34 | 35 | expect(show_grants).to match_array [ 36 | "GRANT SELECT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 37 | "GRANT SELECT, INSERT, UPDATE ON `test`.* TO 'scott'@'localhost'", 38 | ].normalize 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/drop/drop_user_2_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when drop user' do 35 | subject { client(dry_run: true) } 36 | 37 | it do 38 | apply(subject) { 39 | <<-RUBY 40 | user 'bob', 'localhost' do 41 | on '*.*' do 42 | grant 'USAGE' 43 | end 44 | 45 | on 'test.*' do 46 | grant 'ALL PRIVILEGES' 47 | end 48 | end 49 | RUBY 50 | } 51 | 52 | expect(show_grants).to match_array [ 53 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 54 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 55 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 56 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 57 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 58 | ].normalize 59 | end 60 | end 61 | 62 | context 'when drop all users' do 63 | subject { client(dry_run: true) } 64 | 65 | it do 66 | apply(subject) { '' } 67 | 68 | expect(show_grants).to match_array [ 69 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 70 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 71 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 72 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 73 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 74 | ].normalize 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/drop/drop_user_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when drop user' do 35 | subject { client } 36 | 37 | it do 38 | apply(subject) { 39 | <<-RUBY 40 | user 'bob', 'localhost' do 41 | on '*.*' do 42 | grant 'USAGE' 43 | end 44 | 45 | on 'test.*' do 46 | grant 'ALL PRIVILEGES' 47 | end 48 | end 49 | RUBY 50 | } 51 | 52 | expect(show_grants).to match_array [ 53 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 54 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 55 | ] 56 | end 57 | end 58 | 59 | context 'when drop all users' do 60 | subject { client } 61 | 62 | it do 63 | apply(subject) { '' } 64 | expect(show_grants).to match_array [] 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/drop/expire_user_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | before(:each) do 3 | apply { 4 | <<-RUBY 5 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL' do 6 | on '*.*' do 7 | grant 'SELECT' 8 | grant 'INSERT' 9 | end 10 | 11 | on 'test.*' do 12 | grant 'UPDATE' 13 | grant 'DELETE' 14 | end 15 | 16 | on 'mysql.user' do 17 | grant 'SELECT (user)' 18 | end 19 | end 20 | 21 | user 'bob', 'localhost' do 22 | on '*.*' do 23 | grant 'USAGE' 24 | end 25 | 26 | on 'test.*' do 27 | grant 'ALL PRIVILEGES' 28 | end 29 | end 30 | RUBY 31 | } 32 | end 33 | 34 | context 'when has expired' do 35 | let(:logger) do 36 | logger = Logger.new('/dev/null') 37 | expect(logger).to receive(:warn).with('[WARN] User `scott@localhost` has expired') 38 | logger 39 | end 40 | 41 | subject do 42 | client( 43 | enable_expired: true, 44 | logger: logger 45 | ) 46 | end 47 | 48 | it do 49 | dsl = <<-RUBY 50 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL', expired: '2014/10/06' do 51 | on '*.*' do 52 | grant 'SELECT' 53 | grant 'INSERT' 54 | end 55 | 56 | on 'test.*' do 57 | grant 'UPDATE' 58 | grant 'DELETE' 59 | end 60 | 61 | on 'mysql.user' do 62 | grant 'SELECT (user)' 63 | end 64 | end 65 | 66 | user 'bob', 'localhost' do 67 | on '*.*' do 68 | grant 'USAGE' 69 | end 70 | 71 | on 'test.*' do 72 | grant 'ALL PRIVILEGES' 73 | end 74 | end 75 | RUBY 76 | 77 | Timecop.freeze(Time.parse('2014/10/06')) do 78 | apply(subject) { dsl } 79 | end 80 | 81 | expect(show_grants).to match_array [ 82 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 83 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 84 | ] 85 | end 86 | end 87 | 88 | context 'when has not expired' do 89 | subject { client(enable_expired: true) } 90 | 91 | it do 92 | dsl = <<-RUBY 93 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL', expired: '2014/10/06' do 94 | on '*.*' do 95 | grant 'SELECT' 96 | grant 'INSERT' 97 | end 98 | 99 | on 'test.*' do 100 | grant 'UPDATE' 101 | grant 'DELETE' 102 | end 103 | 104 | on 'mysql.user' do 105 | grant 'SELECT (user)' 106 | end 107 | end 108 | 109 | user 'bob', 'localhost' do 110 | on '*.*' do 111 | grant 'USAGE' 112 | end 113 | 114 | on 'test.*' do 115 | grant 'ALL PRIVILEGES' 116 | end 117 | end 118 | RUBY 119 | 120 | Timecop.freeze(Time.parse('2014/10/05 23:59:59')) do 121 | apply(subject) { dsl } 122 | end 123 | 124 | expect(show_grants).to match_array [ 125 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 126 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 127 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 128 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 129 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 130 | ].normalize 131 | end 132 | end 133 | 134 | context 'when enable_expired is false' do 135 | subject { client(enable_expired: false) } 136 | 137 | it do 138 | dsl = <<-RUBY 139 | user 'scott', 'localhost', identified: 'tiger', required: 'SSL', expired: '2014/10/06' do 140 | on '*.*' do 141 | grant 'SELECT' 142 | grant 'INSERT' 143 | end 144 | 145 | on 'test.*' do 146 | grant 'UPDATE' 147 | grant 'DELETE' 148 | end 149 | 150 | on 'mysql.user' do 151 | grant 'SELECT (user)' 152 | end 153 | end 154 | 155 | user 'bob', 'localhost' do 156 | on '*.*' do 157 | grant 'USAGE' 158 | end 159 | 160 | on 'test.*' do 161 | grant 'ALL PRIVILEGES' 162 | end 163 | end 164 | RUBY 165 | 166 | Timecop.freeze(Time.parse('2014/10/10')) do 167 | apply(subject) { dsl } 168 | end 169 | 170 | expect(show_grants).to match_array [ 171 | "GRANT ALL PRIVILEGES ON `test`.* TO 'bob'@'localhost'", 172 | "GRANT SELECT (user) ON `mysql`.`user` TO 'scott'@'localhost'", 173 | "GRANT SELECT, INSERT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40' REQUIRE SSL", 174 | "GRANT UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 175 | "GRANT USAGE ON *.* TO 'bob'@'localhost'", 176 | ].normalize 177 | end 178 | end 179 | end 180 | -------------------------------------------------------------------------------- /spec/export/export_chunk_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#export' do 2 | context 'when chunked by user' do 3 | subject { client(chunk_by_user: true) } 4 | 5 | before do 6 | apply(subject) do 7 | <<-RUBY 8 | user "scott", "%" do 9 | on "*.*" do 10 | grant "USAGE" 11 | end 12 | 13 | on "test.*" do 14 | grant "SELECT" 15 | end 16 | 17 | on "test3.*" do 18 | grant "UPDATE" 19 | end 20 | end 21 | 22 | user "scott", "localhost" do 23 | on "*.*" do 24 | grant "USAGE" 25 | end 26 | 27 | on "test2.*" do 28 | grant "INSERT" 29 | end 30 | 31 | on "test3.*" do 32 | grant "DELETE" 33 | end 34 | end 35 | RUBY 36 | end 37 | end 38 | 39 | it do 40 | expect(subject.export.strip).to eq <<-RUBY.strip 41 | user "scott", ["%", "localhost"] do 42 | on "*.*" do 43 | grant "USAGE" 44 | end 45 | 46 | on "test.*" do 47 | grant "SELECT" 48 | end 49 | 50 | on "test2.*" do 51 | grant "INSERT" 52 | end 53 | 54 | on "test3.*" do 55 | grant "DELETE" 56 | grant "UPDATE" 57 | end 58 | end 59 | RUBY 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/export/export_func_prcd_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#export' do 2 | context 'when function exists' do 3 | let(:grantfile) { 4 | <<-RUBY 5 | user "scott", "%" do 6 | on "*.*" do 7 | grant "USAGE" 8 | end 9 | 10 | on "FUNCTION #{TEST_DATABASE}.my_func" do 11 | grant "EXECUTE" 12 | end 13 | end 14 | RUBY 15 | } 16 | 17 | subject { client } 18 | 19 | before do 20 | mysql do |cli| 21 | drop_database(cli) 22 | create_database(cli) 23 | create_function(cli, :my_func) 24 | end 25 | 26 | apply(subject) do 27 | grantfile 28 | end 29 | end 30 | 31 | it do 32 | expect(subject.export.strip).to eq grantfile.strip 33 | end 34 | end 35 | 36 | context 'when procedure exists' do 37 | let(:grantfile) { 38 | <<-RUBY 39 | user "scott", "%" do 40 | on "*.*" do 41 | grant "USAGE" 42 | end 43 | 44 | on "PROCEDURE #{TEST_DATABASE}.my_prcd" do 45 | grant "EXECUTE" 46 | end 47 | end 48 | RUBY 49 | } 50 | 51 | subject { client } 52 | 53 | before do 54 | mysql do |cli| 55 | drop_database(cli) 56 | create_database(cli) 57 | create_procedure(cli, :my_prcd) 58 | end 59 | 60 | apply(subject) do 61 | grantfile 62 | end 63 | end 64 | 65 | it do 66 | expect(subject.export.strip).to eq grantfile.strip 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /spec/export/export_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#export' do 2 | context 'when user does not exist' do 3 | subject { client } 4 | 5 | it do 6 | expect(subject.export.strip).to eq '' 7 | end 8 | end 9 | 10 | context 'when user exists' do 11 | let(:grantfile) { 12 | <<-RUBY 13 | user "scott", "%" do 14 | on "*.*" do 15 | grant "USAGE" 16 | end 17 | 18 | on "test.*" do 19 | grant "SELECT" 20 | end 21 | end 22 | 23 | user "scott", "localhost" do 24 | on "*.*" do 25 | grant "USAGE" 26 | end 27 | 28 | on "test.*" do 29 | grant "SELECT" 30 | end 31 | end 32 | RUBY 33 | } 34 | 35 | subject { client } 36 | 37 | before do 38 | apply(subject) do 39 | grantfile 40 | end 41 | end 42 | 43 | it do 44 | expect(subject.export.strip).to eq grantfile.strip 45 | end 46 | end 47 | 48 | context 'when ignore user exists' do 49 | let(:grantfile) { 50 | <<-RUBY 51 | user "scott", "%" do 52 | on "*.*" do 53 | grant "USAGE" 54 | end 55 | 56 | on "test.*" do 57 | grant "SELECT" 58 | end 59 | end 60 | 61 | user "bob", "localhost" do 62 | on "*.*" do 63 | grant "USAGE" 64 | end 65 | 66 | on "test.*" do 67 | grant "SELECT" 68 | end 69 | end 70 | RUBY 71 | } 72 | 73 | subject { client(ignore_user: /\Abob\z/) } 74 | 75 | before do 76 | apply(subject) do 77 | grantfile 78 | end 79 | end 80 | 81 | it do 82 | expect(subject.export.strip).to eq <<-RUBY.strip 83 | user "scott", "%" do 84 | on "*.*" do 85 | grant "USAGE" 86 | end 87 | 88 | on "test.*" do 89 | grant "SELECT" 90 | end 91 | end 92 | RUBY 93 | end 94 | end 95 | 96 | context 'when with option exists' do 97 | let(:grantfile) { 98 | <<-RUBY 99 | user "scott", "%" do 100 | on "*.*", :with=>"GRANT OPTION" do 101 | grant "USAGE" 102 | end 103 | end 104 | RUBY 105 | } 106 | 107 | subject { client(ignore_user: /\Abob\z/) } 108 | 109 | before do 110 | apply(subject) do 111 | grantfile 112 | end 113 | end 114 | 115 | it do 116 | expect(subject.export.strip).to eq grantfile.strip 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /spec/export/export_with_ignore_object_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#export' do 2 | context 'when with ignore_object' do 3 | let(:grantfile) { 4 | <<-RUBY 5 | user "scott", "%" do 6 | on "*.*" do 7 | grant "USAGE" 8 | end 9 | 10 | on "test.*" do 11 | grant "SELECT" 12 | end 13 | end 14 | 15 | user "bob", "localhost" do 16 | on "*.*" do 17 | grant "USAGE" 18 | end 19 | 20 | on "test.*" do 21 | grant "SELECT" 22 | end 23 | end 24 | RUBY 25 | } 26 | 27 | subject { client(ignore_object: /test/) } 28 | 29 | before do 30 | apply(subject) do 31 | grantfile 32 | end 33 | end 34 | 35 | it do 36 | expect(subject.export.strip).to eq <<-RUBY.strip 37 | user "scott", "%" do 38 | on "*.*" do 39 | grant "USAGE" 40 | end 41 | end 42 | 43 | user "bob", "localhost" do 44 | on "*.*" do 45 | grant "USAGE" 46 | end 47 | end 48 | RUBY 49 | end 50 | end 51 | 52 | context 'when with ignore_object (2)' do 53 | let(:grantfile) { 54 | <<-RUBY 55 | user "scott", "%" do 56 | on "*.*" do 57 | grant "USAGE" 58 | end 59 | 60 | on "test.*" do 61 | grant "SELECT" 62 | end 63 | end 64 | 65 | user "bob", "localhost" do 66 | on "*.*" do 67 | grant "USAGE" 68 | end 69 | 70 | on "test.*" do 71 | grant "SELECT" 72 | end 73 | end 74 | RUBY 75 | } 76 | 77 | subject { client(ignore_object: /bob/) } 78 | 79 | before do 80 | apply(subject) do 81 | grantfile 82 | end 83 | end 84 | 85 | it do 86 | expect(subject.export.strip).to eq <<-RUBY.strip 87 | user "scott", "%" do 88 | on "*.*" do 89 | grant "USAGE" 90 | end 91 | 92 | on "test.*" do 93 | grant "SELECT" 94 | end 95 | end 96 | 97 | user "bob", "localhost" do 98 | on "*.*" do 99 | grant "USAGE" 100 | end 101 | 102 | on "test.*" do 103 | grant "SELECT" 104 | end 105 | end 106 | RUBY 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /spec/misc/misc_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when colorize is true' do 3 | subject { client } 4 | 5 | it do 6 | dsl = <<-RUBY 7 | user 'scott', 'localhost', identified: 'tiger' do 8 | on '*.*' do 9 | grant 'SELECT' 10 | grant 'INSERT' 11 | grant 'UPDATE' 12 | grant 'DELETE' 13 | end 14 | 15 | on 'test.*' do 16 | grant 'SELECT' 17 | grant 'INSERT' 18 | grant 'UPDATE' 19 | grant 'DELETE' 20 | end 21 | end 22 | RUBY 23 | 24 | begin 25 | String.colorize = true 26 | apply(subject) { dsl } 27 | ensure 28 | String.colorize = false 29 | end 30 | 31 | expect(show_grants).to match_array [ 32 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 33 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 34 | ].normalize 35 | end 36 | end 37 | 38 | context 'when set debug' do 39 | let(:logger) do 40 | logger = Gratan::Logger.send(:new) 41 | logger.set_debug(true) 42 | expect(logger).to receive(:debug).with("[DEBUG] SET SQL_LOG_BIN = 0") 43 | allow(logger).to receive(:debug).with('[DEBUG] SET SQL_MODE = ""') 44 | expect(logger).to receive(:debug).with("[DEBUG] SELECT user, host FROM mysql.user") 45 | expect(logger).to receive(:info).with("GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY 'tiger'") 46 | expect(logger).to receive(:info).with("GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost' IDENTIFIED BY 'tiger'") 47 | expect(logger).to receive(:info).with("FLUSH PRIVILEGES") 48 | logger 49 | end 50 | 51 | subject { client(logger: logger) } 52 | 53 | it do 54 | apply(subject) { 55 | <<-RUBY 56 | user 'scott', 'localhost', identified: 'tiger' do 57 | on '*.*' do 58 | grant 'SELECT' 59 | grant 'INSERT' 60 | grant 'UPDATE' 61 | grant 'DELETE' 62 | end 63 | 64 | on 'test.*' do 65 | grant 'SELECT' 66 | grant 'INSERT' 67 | grant 'UPDATE' 68 | grant 'DELETE' 69 | end 70 | end 71 | RUBY 72 | } 73 | 74 | expect(show_grants).to match_array [ 75 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 76 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 77 | ].normalize 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /spec/misc/require_spec.rb: -------------------------------------------------------------------------------- 1 | describe 'Gratan::Client#apply' do 2 | context 'when load group file' do 3 | subject { client } 4 | 5 | it do 6 | dsl = <<-RUBY 7 | user 'scott', 'localhost', identified: 'tiger' do 8 | on '*.*' do 9 | grant 'SELECT' 10 | grant 'INSERT' 11 | grant 'UPDATE' 12 | grant 'DELETE' 13 | end 14 | 15 | on 'test.*' do 16 | grant 'SELECT' 17 | grant 'INSERT' 18 | grant 'UPDATE' 19 | grant 'DELETE' 20 | end 21 | end 22 | RUBY 23 | 24 | tempfile(dsl) do |f| 25 | apply(subject) { 26 | <<-RUBY 27 | require 'time' 28 | require '#{f.path}' 29 | RUBY 30 | } 31 | end 32 | 33 | expect(show_grants).to match_array [ 34 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 35 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 36 | ].normalize 37 | end 38 | end 39 | 40 | context 'when load group file (.rb)' do 41 | subject { client } 42 | 43 | it do 44 | dsl = <<-RUBY 45 | user 'scott', 'localhost', identified: 'tiger' do 46 | on '*.*' do 47 | grant 'SELECT' 48 | grant 'INSERT' 49 | grant 'UPDATE' 50 | grant 'DELETE' 51 | end 52 | 53 | on 'test.*' do 54 | grant 'SELECT' 55 | grant 'INSERT' 56 | grant 'UPDATE' 57 | grant 'DELETE' 58 | end 59 | end 60 | RUBY 61 | 62 | tempfile(dsl, ext: '.rb') do |f| 63 | apply(subject) { 64 | <<-RUBY 65 | require 'time' 66 | require '#{f.path.sub(/\.rb\z/, '')}' 67 | RUBY 68 | } 69 | end 70 | 71 | expect(show_grants).to match_array [ 72 | "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", 73 | "GRANT SELECT, INSERT, UPDATE, DELETE ON `test`.* TO 'scott'@'localhost'", 74 | ].normalize 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $: << File.expand_path('..', __FILE__) 2 | 3 | require 'gratan' 4 | require 'tempfile' 5 | require 'timecop' 6 | 7 | IGNORE_USER = /\A(|root)\z/ 8 | TEST_DATABASE = 'gratan_test' 9 | 10 | RSpec.configure do |config| 11 | config.before(:each) do 12 | clean_grants 13 | end 14 | end 15 | 16 | def mysql57? 17 | ENV['MYSQL57'] == '1' 18 | end 19 | 20 | MYSQL_PORT = mysql57? ? 14407 : 14406 21 | 22 | def mysql 23 | client = nil 24 | retval = nil 25 | 26 | begin 27 | client = Mysql2::Client.new(host: '127.0.0.1', username: 'root', port: MYSQL_PORT) 28 | retval = yield(client) 29 | ensure 30 | client.close if client 31 | end 32 | 33 | retval 34 | end 35 | 36 | def create_database(client) 37 | client.query("CREATE DATABASE #{TEST_DATABASE}") 38 | end 39 | 40 | def drop_database(client) 41 | client.query("DROP DATABASE IF EXISTS #{TEST_DATABASE}") 42 | end 43 | 44 | def create_table(client, table) 45 | client.query("CREATE TABLE #{TEST_DATABASE}.#{table} (id INT)") 46 | end 47 | 48 | def create_function(client, func) 49 | client.query("CREATE FUNCTION #{TEST_DATABASE}.#{func}() RETURNS INT RETURN 1") 50 | end 51 | 52 | def create_procedure(client, prcd) 53 | client.query("CREATE PROCEDURE #{TEST_DATABASE}.#{prcd}() SELECT 1") 54 | end 55 | 56 | def create_tables(*tables) 57 | mysql do |client| 58 | begin 59 | drop_database(client) 60 | create_database(client) 61 | tables.each {|i| create_table(client, i) } 62 | yield 63 | ensure 64 | drop_database(client) 65 | end 66 | end 67 | end 68 | 69 | def create_functions(*funcs) 70 | mysql do |client| 71 | begin 72 | drop_database(client) 73 | create_database(client) 74 | funcs.each {|i| create_function(client, i) } 75 | yield 76 | ensure 77 | drop_database(client) 78 | end 79 | end 80 | end 81 | 82 | def create_procedures(*prcds) 83 | mysql do |client| 84 | begin 85 | drop_database(client) 86 | create_database(client) 87 | prcds.each {|i| create_procedure(client, i) } 88 | yield 89 | ensure 90 | drop_database(client) 91 | end 92 | end 93 | end 94 | 95 | def select_users(client) 96 | users = [] 97 | 98 | client.query('SELECT user, host FROM mysql.user').each do |row| 99 | users << [row['user'], row['host']] 100 | end 101 | 102 | users 103 | end 104 | 105 | def clean_grants 106 | mysql do |client| 107 | select_users(client).each do |user, host| 108 | next if IGNORE_USER =~ user 109 | user_host = "'%s'@'%s'" % [client.escape(user), client.escape(host)] 110 | client.query("DROP USER #{user_host}") 111 | end 112 | end 113 | end 114 | 115 | def show_grants 116 | grants = [] 117 | 118 | mysql do |client| 119 | select_users(client).each do |user, host| 120 | next if IGNORE_USER =~ user 121 | user_host = "'%s'@'%s'" % [client.escape(user), client.escape(host)] 122 | 123 | client.query("SHOW GRANTS FOR #{user_host}").each do |row| 124 | grants << row.values.first 125 | end 126 | 127 | end 128 | end 129 | 130 | if mysql57? 131 | grants.each do |grant| 132 | end 133 | end 134 | 135 | grants.sort 136 | end 137 | 138 | def client(user_options = {}) 139 | if user_options[:ignore_user] 140 | user_options[:ignore_user] = Regexp.union(IGNORE_USER, user_options[:ignore_user]) 141 | end 142 | 143 | options = { 144 | host: '127.0.0.1', 145 | username: 'root', 146 | port: MYSQL_PORT, 147 | ignore_user: IGNORE_USER, 148 | logger: Logger.new('/dev/null'), 149 | } 150 | 151 | if mysql57? 152 | options.update( 153 | override_sql_mode: true, 154 | use_show_create_user: true, 155 | ) 156 | end 157 | 158 | if ENV['DEBUG'] 159 | logger = Gratan::Logger.instance 160 | logger.set_debug(true) 161 | 162 | options.update( 163 | debug: true, 164 | logger: logger 165 | ) 166 | end 167 | 168 | options = options.merge(user_options) 169 | Gratan::Client.new(options) 170 | end 171 | 172 | def tempfile(content, options = {}) 173 | basename = "#{File.basename __FILE__}.#{$$}" 174 | basename = [basename, options[:ext]] if options[:ext] 175 | 176 | Tempfile.open(basename) do |f| 177 | f.puts(content) 178 | f.flush 179 | f.rewind 180 | yield(f) 181 | end 182 | end 183 | 184 | def apply(cli = client) 185 | tempfile(yield) do |f| 186 | cli.apply(f.path) 187 | end 188 | end 189 | 190 | class Array 191 | def normalize 192 | if mysql57? 193 | self.map do |i| 194 | i.sub(/ IDENTIFIED BY PASSWORD '[^']+'/, '') 195 | .sub(/ REQUIRE \w+\b/, '') 196 | .sub(/ WITH GRANT OPTION [\w ]+\z/, ' WITH GRANT OPTION') 197 | end 198 | else 199 | self 200 | end 201 | end 202 | end 203 | --------------------------------------------------------------------------------