gratan 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +86 -0
- data/Rakefile +5 -0
- data/bin/gratan +132 -0
- data/gratan.gemspec +28 -0
- data/lib/gratan/client.rb +211 -0
- data/lib/gratan/driver.rb +166 -0
- data/lib/gratan/dsl/context/on.rb +19 -0
- data/lib/gratan/dsl/context/user.rb +25 -0
- data/lib/gratan/dsl/context.rb +57 -0
- data/lib/gratan/dsl/converter.rb +74 -0
- data/lib/gratan/dsl/validator.rb +13 -0
- data/lib/gratan/dsl.rb +9 -0
- data/lib/gratan/exporter.rb +49 -0
- data/lib/gratan/ext/string_ext.rb +25 -0
- data/lib/gratan/grant_parser.rb +68 -0
- data/lib/gratan/identifier/auto.rb +28 -0
- data/lib/gratan/identifier/csv.rb +25 -0
- data/lib/gratan/identifier/null.rb +5 -0
- data/lib/gratan/identifier.rb +2 -0
- data/lib/gratan/logger.rb +28 -0
- data/lib/gratan/version.rb +3 -0
- data/lib/gratan.rb +24 -0
- data/spec/change/change_grants_2_spec.rb +154 -0
- data/spec/change/change_grants_3_spec.rb +164 -0
- data/spec/change/change_grants_4_spec.rb +37 -0
- data/spec/change/change_grants_spec.rb +209 -0
- data/spec/create/create_user_2_spec.rb +139 -0
- data/spec/create/create_user_3_spec.rb +115 -0
- data/spec/create/create_user_spec.rb +194 -0
- data/spec/drop/drop_user_2_spec.rb +77 -0
- data/spec/drop/drop_user_spec.rb +67 -0
- data/spec/drop/expire_user_spec.rb +179 -0
- data/spec/export/export_spec.rb +119 -0
- data/spec/misc/misc_spec.rb +74 -0
- data/spec/misc/require_spec.rb +77 -0
- data/spec/spec_helper.rb +118 -0
- metadata +198 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
class Gratan::DSL::Context::On
|
2
|
+
include Gratan::DSL::Validator
|
3
|
+
|
4
|
+
attr_reader :result
|
5
|
+
|
6
|
+
def initialize(user, host, object, &block)
|
7
|
+
@error_identifier = "User `#{user}@#{host}` on `#{object}`"
|
8
|
+
@result = []
|
9
|
+
instance_eval(&block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def grant(name, options = {})
|
13
|
+
__validate("Grant `#{name}` is already defined") do
|
14
|
+
not @result.include?(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
@result << name
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Gratan::DSL::Context::User
|
2
|
+
include Gratan::DSL::Validator
|
3
|
+
|
4
|
+
attr_reader :result
|
5
|
+
|
6
|
+
def initialize(user, host, &block)
|
7
|
+
@error_identifier = "User `#{user}@#{host}`"
|
8
|
+
@user = user
|
9
|
+
@host = host
|
10
|
+
@result = {}
|
11
|
+
instance_eval(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
def on(name, options = {}, &block)
|
15
|
+
name = name.to_s
|
16
|
+
|
17
|
+
__validate("Object `#{name}` is already defined") do
|
18
|
+
not @result.has_key?(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
grant = {:privs => Gratan::DSL::Context::On.new(@user, @host, name, &block).result}
|
22
|
+
grant[:with] = options[:with] if options[:with]
|
23
|
+
@result[name] = grant
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Gratan::DSL::Context
|
2
|
+
include Gratan::DSL::Validator
|
3
|
+
include Gratan::Logger::Helper
|
4
|
+
|
5
|
+
def self.eval(dsl, path, options = {})
|
6
|
+
self.new(path, options) do
|
7
|
+
eval(dsl, binding, path)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :result
|
12
|
+
|
13
|
+
def initialize(path, options = {}, &block)
|
14
|
+
@path = path
|
15
|
+
@options = options
|
16
|
+
@result = {}
|
17
|
+
instance_eval(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def require(file)
|
23
|
+
grantfile = (file =~ %r|\A/|) ? file : File.expand_path(File.join(File.dirname(@path), file))
|
24
|
+
|
25
|
+
if File.exist?(grantfile)
|
26
|
+
instance_eval(File.read(grantfile), grantfile)
|
27
|
+
elsif File.exist?(grantfile + '.rb')
|
28
|
+
instance_eval(File.read(grantfile + '.rb'), grantfile + '.rb')
|
29
|
+
else
|
30
|
+
Kernel.require(file)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def user(name, host, options = {}, &block)
|
35
|
+
name = name.to_s
|
36
|
+
host = host.to_s
|
37
|
+
options ||= {}
|
38
|
+
|
39
|
+
__validate("User `#{name}@#{host}` is already defined") do
|
40
|
+
not @result.has_key?([name, host])
|
41
|
+
end
|
42
|
+
|
43
|
+
if @options[:enable_expired] and (expired = options.delete(:expired))
|
44
|
+
expired = Time.parse(expired)
|
45
|
+
|
46
|
+
if Time.new >= expired
|
47
|
+
log(:warn, "User `#{name}@#{host}` has expired", :yellow)
|
48
|
+
return
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@result[[name, host]] = {
|
53
|
+
:objects => Gratan::DSL::Context::User.new(name, host, &block).result,
|
54
|
+
:options => options,
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,74 @@
|
|
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.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].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
|
data/lib/gratan/dsl.rb
ADDED
@@ -0,0 +1,49 @@
|
|
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
|
+
@driver.show_grants(user, host) do |stmt|
|
18
|
+
grants << Gratan::GrantParser.parse(stmt)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
pack(grants)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def pack(grants)
|
28
|
+
packed = {}
|
29
|
+
|
30
|
+
grants.each do |grant|
|
31
|
+
user = grant.delete(:user)
|
32
|
+
host = grant.delete(:host)
|
33
|
+
user_host = [user, host]
|
34
|
+
object = grant.delete(:object)
|
35
|
+
identified = grant.delete(:identified)
|
36
|
+
required = grant.delete(:require)
|
37
|
+
|
38
|
+
packed[user_host] ||= {:objects => {}, :options => {}}
|
39
|
+
packed[user_host][:objects][object] = grant
|
40
|
+
packed[user_host][:options][:required] = required if required
|
41
|
+
|
42
|
+
if @options[:with_identifier] and identified
|
43
|
+
packed[user_host][:options][:identified] = identified
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
packed
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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
|
@@ -0,0 +1,68 @@
|
|
1
|
+
class Gratan::GrantParser
|
2
|
+
def initialize(stmt)
|
3
|
+
@stmt = stmt.strip
|
4
|
+
@parsed = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.parse(stmt)
|
8
|
+
parser = self.new(stmt)
|
9
|
+
parser.parse!
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse!
|
13
|
+
parse_grant
|
14
|
+
parse_require
|
15
|
+
parse_identified
|
16
|
+
parse_main
|
17
|
+
@parsed
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse_grant
|
23
|
+
@stmt.slice!(/\s+WITH\s+(.+?)\z/)
|
24
|
+
with_option = $1
|
25
|
+
|
26
|
+
if with_option
|
27
|
+
@parsed[:with] = with_option.strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_require
|
32
|
+
@stmt.slice!(/\s+REQUIRE\s+(.+?)\z/)
|
33
|
+
required = $1
|
34
|
+
|
35
|
+
if required
|
36
|
+
@parsed[:require] = required.strip
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_identified
|
41
|
+
@stmt.slice!(/\s+IDENTIFIED BY\s+(.+?)\z/)
|
42
|
+
identified = $1
|
43
|
+
|
44
|
+
if identified
|
45
|
+
@parsed[:identified] = identified.strip
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_main
|
50
|
+
md = /\AGRANT\s+(.+?)\s+ON\s+(.+?)\s+TO\s+'(.*)'@'(.+)'\z/.match(@stmt)
|
51
|
+
privs, object, user, host = md.captures
|
52
|
+
@parsed[:privs] = parse_privs(privs.strip)
|
53
|
+
@parsed[:object] = object.gsub('`', '').strip
|
54
|
+
@parsed[:user] = user
|
55
|
+
@parsed[:host] = host
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_privs(privs)
|
59
|
+
privs << ','
|
60
|
+
priv_list = []
|
61
|
+
|
62
|
+
while priv = privs.slice!(/\A[^,(]+(?:\([^)]+\))?\s*,\s*/)
|
63
|
+
priv_list << priv.strip.sub(/,\z/, '').strip
|
64
|
+
end
|
65
|
+
|
66
|
+
priv_list
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Gratan::Identifier::Auto
|
2
|
+
def initialize(output, options = {})
|
3
|
+
@options = options
|
4
|
+
|
5
|
+
if output == '-'
|
6
|
+
@output = $stdout
|
7
|
+
else
|
8
|
+
@output = open(output, 'w')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def identify(user, host)
|
13
|
+
password = mkpasswd
|
14
|
+
puts_password(user, host, password)
|
15
|
+
password
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def mkpasswd(len = 8)
|
21
|
+
[*1..9, *'A'..'Z', *'a'..'z'].shuffle.slice(0, len).join
|
22
|
+
end
|
23
|
+
|
24
|
+
def puts_password(user, host, password)
|
25
|
+
@output.puts("#{user}@#{host},#{password}")
|
26
|
+
@output.flush
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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", :yellow)
|
21
|
+
end
|
22
|
+
|
23
|
+
password
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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, color = nil)
|
20
|
+
options = @options || {}
|
21
|
+
message = "[#{level.to_s.upcase}] #{message}" unless level == :info
|
22
|
+
message << ' (dry-run)' if options[:dry_run]
|
23
|
+
message = message.send(color) if color
|
24
|
+
logger = options[:logger] || Gratan::Logger.instance
|
25
|
+
logger.send(level, message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/gratan.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'mysql2'
|
3
|
+
require 'singleton'
|
4
|
+
require 'term/ansicolor'
|
5
|
+
require 'time'
|
6
|
+
|
7
|
+
module Gratan; end
|
8
|
+
require 'gratan/logger'
|
9
|
+
require 'gratan/client'
|
10
|
+
require 'gratan/driver'
|
11
|
+
require 'gratan/dsl'
|
12
|
+
require 'gratan/dsl/validator'
|
13
|
+
require 'gratan/dsl/context'
|
14
|
+
require 'gratan/dsl/context/user'
|
15
|
+
require 'gratan/dsl/context/on'
|
16
|
+
require 'gratan/dsl/converter'
|
17
|
+
require 'gratan/exporter'
|
18
|
+
require 'gratan/ext/string_ext'
|
19
|
+
require 'gratan/grant_parser'
|
20
|
+
require 'gratan/identifier'
|
21
|
+
require 'gratan/identifier/auto'
|
22
|
+
require 'gratan/identifier/csv'
|
23
|
+
require 'gratan/identifier/null'
|
24
|
+
require 'gratan/version'
|
@@ -0,0 +1,154 @@
|
|
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
|
+
]
|
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
|
+
]
|
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
|
+
]
|
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
|
+
]
|
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
|
+
]
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|