diff --git a/bin/gratan b/bin/gratan index a4d5148..ad4d376 100755 --- a/bin/gratan +++ b/bin/gratan @@ -19,36 +19,38 @@ mysql_options = { } options = { - :dry_run => false, - :color => true, - :debug => false, + :dry_run => false, + :color => true, + :debug => false, + :ignore_unlisted_users => false, } ARGV.options do |opt| begin - opt.on('' , '--host HOST') {|v| mysql_options[:host] = v } - opt.on('' , '--port PORT', Integer) {|v| mysql_options[:port] = v } - opt.on('' , '--socket SOCKET') {|v| mysql_options[:socket] = v } - opt.on('' , '--username USERNAME') {|v| mysql_options[:username] = v } - opt.on('' , '--password PASSWORD') {|v| mysql_options[:password] = v } - opt.on('' , '--database DATABASE') {|v| mysql_options[:database] = v } - opt.on('-a', '--apply') { mode = :apply } - opt.on('-f', '--file FILE') {|v| file = v } - opt.on('' , '--dry-run') { options[:dry_run] = true } - opt.on('-e', '--export') { mode = :export } - opt.on('' , '--with-identifier') { options[:with_identifier] = true } - opt.on('' , '--split') { split = true } - opt.on('' , '--chunk-by-user') { options[:chunk_by_user] = true } - opt.on('-o', '--output FILE') {|v| output_file = v } - opt.on('' , '--ignore-user REGEXP') {|v| options[:ignore_user] = Regexp.new(v) } - opt.on('' , '--target-user REGEXP') {|v| options[:target_user] = Regexp.new(v) } - opt.on('' , '--ignore-object REGEXP') {|v| options[:ignore_object] = Regexp.new(v) } - opt.on('' , '--enable-expired') { options[:enable_expired] = true } - opt.on('' , '--ignore-not-exist') {|v| options[:ignore_not_exist] = true } - opt.on('' , '--no-color') { options[:color] = false } - opt.on('' , '--debug') { options[:debug] = true } - opt.on('' , '--auto-identify OUTPUT') {|v| options[:identifier] = Gratan::Identifier::Auto.new(v, options) } - opt.on('' , '--csv-identify CSV') {|v| options[:identifier] = Gratan::Identifier::CSV.new(v, options) } + opt.on('' , '--host HOST') {|v| mysql_options[:host] = v } + opt.on('' , '--port PORT', Integer) {|v| mysql_options[:port] = v } + opt.on('' , '--socket SOCKET') {|v| mysql_options[:socket] = v } + opt.on('' , '--username USERNAME') {|v| mysql_options[:username] = v } + opt.on('' , '--password PASSWORD') {|v| mysql_options[:password] = v } + opt.on('' , '--database DATABASE') {|v| mysql_options[:database] = v } + opt.on('-a', '--apply') { mode = :apply } + opt.on('-f', '--file FILE') {|v| file = v } + opt.on('' , '--dry-run') { options[:dry_run] = true } + opt.on('-e', '--export') { mode = :export } + opt.on('' , '--with-identifier') { options[:with_identifier] = true } + opt.on('' , '--split') { split = true } + opt.on('' , '--chunk-by-user') { options[:chunk_by_user] = true } + opt.on('-o', '--output FILE') {|v| output_file = v } + opt.on('' , '--ignore-user REGEXP') {|v| options[:ignore_user] = Regexp.new(v) } + opt.on('' , '--target-user REGEXP') {|v| options[:target_user] = Regexp.new(v) } + opt.on('' , '--ignore-object REGEXP') {|v| options[:ignore_object] = Regexp.new(v) } + opt.on('' , '--enable-expired') { options[:enable_expired] = true } + opt.on('' , '--ignore-not-exist') { options[:ignore_not_exist] = true } + opt.on('' , '--ignore-unlisted-users') { options[:ignore_unlisted_users] = true } + opt.on('' , '--no-color') { options[:color] = false } + opt.on('' , '--debug') { options[:debug] = true } + opt.on('' , '--auto-identify OUTPUT') {|v| options[:identifier] = Gratan::Identifier::Auto.new(v, options) } + opt.on('' , '--csv-identify CSV') {|v| options[:identifier] = Gratan::Identifier::CSV.new(v, options) } opt.on('-h', '--help') do puts opt.help diff --git a/lib/gratan/client.rb b/lib/gratan/client.rb index bb2c790..4e01779 100644 --- a/lib/gratan/client.rb +++ b/lib/gratan/client.rb @@ -56,15 +56,22 @@ def chunk_by_user(exported) def apply(file, options = {}) options = @options.merge(options) + apply_context( + load_file(file, options), + options) + end + + def apply_context(expected, options = {}) + options = @options.merge(options) + in_progress do - walk(file, options) + walk(expected, options) end end private - def walk(file, options) - expected = load_file(file, options) + def walk(expected, options) actual = Gratan::Exporter.export(@driver, options.merge(:with_identifier => true)) expected.each do |user_host, expected_attrs| @@ -76,21 +83,25 @@ def walk(file, options) actual_attrs = actual.delete(user_host) - if actual_attrs + if expected_attrs == :dropped + drop_user(*user_host) if actual_attrs + elsif actual_attrs walk_user(*user_host, expected_attrs, actual_attrs) else create_user(*user_host, expected_attrs) end end - actual.each do |user_host, attrs| - next if user_host[0] =~ options[:ignore_user] + unless options[:ignore_unlisted_users] + actual.each do |user_host, attrs| + next if user_host[0] =~ options[:ignore_user] - if options[:target_user] - next unless user_host[0] =~ options[:target_user] - end + if options[:target_user] + next unless user_host[0] =~ options[:target_user] + end - drop_user(*user_host) + drop_user(*user_host) + end end end diff --git a/lib/gratan/dsl/context.rb b/lib/gratan/dsl/context.rb index eff94c3..2892cf4 100644 --- a/lib/gratan/dsl/context.rb +++ b/lib/gratan/dsl/context.rb @@ -17,8 +17,6 @@ def initialize(path, options = {}, &block) instance_eval(&block) end - private - def require(file) grantfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file)) @@ -35,6 +33,11 @@ def user(name, host_or_array, options = {}, &block) name = name.to_s hosts = [host_or_array].flatten.map {|i| i.to_s } + expired = options.delete(:expired) + expired = Time.parse(expired) if expired + + dropped = options.delete(:dropped) + hosts.each do |host| options ||= {} @@ -42,15 +45,19 @@ def user(name, host_or_array, options = {}, &block) not @result.has_key?([name, host]) end - if @options[:enable_expired] and (expired = options.delete(:expired)) - expired = Time.parse(expired) - + if @options[:enable_expired] and expired if Time.new >= expired log(:warn, "User `#{name}@#{host}` has expired", :color => :yellow) - return + @result[[name, host]] = :dropped + next end end + if dropped + @result[[name, host]] = :dropped + next + end + @result[[name, host]] = { :objects => Gratan::DSL::Context::User.new(name, host, @options, &block).result, :options => options, diff --git a/spec/drop/drop_user_spec.rb b/spec/drop/drop_user_spec.rb index 544cbd7..a0077d8 100644 --- a/spec/drop/drop_user_spec.rb +++ b/spec/drop/drop_user_spec.rb @@ -27,6 +27,12 @@ grant 'ALL PRIVILEGES' end end + +user 'jimbob', ['localhost', 'bomb.com'] do + on '*.*' do + grant 'USAGE' + end +end RUBY } end @@ -46,6 +52,8 @@ grant 'ALL PRIVILEGES' end end + +user 'jimbob', ['localhost', 'bomb.com'], dropped: true RUBY } diff --git a/spec/drop/ignore_unlisted_users_spec.rb b/spec/drop/ignore_unlisted_users_spec.rb new file mode 100644 index 0000000..e77b3b8 --- /dev/null +++ b/spec/drop/ignore_unlisted_users_spec.rb @@ -0,0 +1,48 @@ +describe 'Gratan::Client#apply' do + before(:each) do + apply { + <<-RUBY +user 'scott', 'localhost', identified: 'tiger' do + on '*.*' do + grant 'SELECT' + end +end + +user 'bob', 'localhost' do + on '*.*' do + grant 'USAGE' + end +end + +user 'jimbob', 'localhost' do + on '*.*' do + grant 'USAGE' + end +end + RUBY + } + end + + context 'when grant privs with ignore_unlisted_users' do + subject { client(ignore_unlisted_users: true) } + + it do + apply(subject) { + <<-RUBY +user 'scott', 'localhost', identified: 'tiger' do + on '*.*' do + grant 'SELECT' + end +end + +user 'jimbob', 'localhost', dropped: true + RUBY + } + + expect(show_grants).to match_array [ + "GRANT SELECT ON *.* TO 'scott'@'localhost' IDENTIFIED BY PASSWORD '*F2F68D0BB27A773C1D944270E5FAFED515A3FA40'", + "GRANT USAGE ON *.* TO 'bob'@'localhost'", + ] + end + end +end