mysql_users 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3f45a1d36a6bedd880564ff0cabf55045ace7ec4
4
+ data.tar.gz: 5a27cedec5a341b1604b9ac2ce6bdcd69d58401f
5
+ SHA512:
6
+ metadata.gz: b8bd466f430a87cee3753ac9925643f42d8f6aac0ea453530fd19c4c2a5a86a7338589c354261954348e1bd9d246fd70db4742d3366969f8bf3d430f86b5b1e4
7
+ data.tar.gz: ad9a32631befb2f544eae0beb8107ff9cdad89c109d62c22d7cb60e911da31c347e0d14f71a4a5598511d9bcbebe6d01070211847796a81a1463652c01db09f0
data/.gitignore ADDED
@@ -0,0 +1,16 @@
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
+ .vendor
16
+ /spec/examples.txt
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ simple mysql grant management inspired by [this chef recipe](https://github.com/opscode-cookbooks/database/blob/master/libraries/provider_database_mysql_user.rb)
2
+
3
+ # Installation
4
+
5
+ It's still being developed so it's not on rubygems right now. Add this to your
6
+ Gemfile:
7
+
8
+ ```
9
+ gem 'mysql_users', git: 'https://github.com/margueritepd/ruby_mysql_users.git'
10
+ ```
11
+
12
+ # Usage
13
+
14
+ The user object takes a database client as part of its constructor. You can use
15
+ the mysql2 database client, or you can write your own - as long as it responds
16
+ to `query` and `escape`, you're good.
17
+
18
+ If using the `mysql2` client, you should probably make sure you close the
19
+ connection when you're done manipulating the user object.
20
+
21
+ In the constructor, you want to also pass the user's username and hosts from
22
+ which it can log into.
23
+
24
+ You can optionally pass a password.
25
+
26
+ eg:
27
+
28
+ ```ruby
29
+ require 'mysql_users'
30
+ require 'mysql2'
31
+
32
+ begin
33
+ db = Mysql2::Client.new(
34
+ host: "localhost",
35
+ username: "root",
36
+ )
37
+
38
+ ['%', 'localhost'].each do |host|
39
+ user = MysqlUsers::User.new(
40
+ db,
41
+ {
42
+ username: 'marguerite',
43
+ host: host,
44
+ password: 'foo',
45
+ }
46
+ )
47
+ user.create # won't complain if user exists already
48
+ user.grant({
49
+ grants: ['ALL PRIVILEGES'],
50
+ database: 'web',
51
+ table: 'auth',
52
+ })
53
+ user.revoke({
54
+ grants: ['SELECT'],
55
+ database: 'web',
56
+ table: 'auth',
57
+ })
58
+ user.drop # won't complain if user doesn't exist
59
+ end
60
+ rescue => e
61
+ puts e
62
+ raise e
63
+ ensure
64
+ db.close unless db.nil?
65
+ end
66
+ ```
67
+
68
+ # Testing
69
+
70
+ ```
71
+ bundle exec rake
72
+ ```
73
+
74
+ # Creating a new package
75
+
76
+ ```
77
+ bundle exec rake build
78
+ # new gem is in pkg/
79
+ ```
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -0,0 +1 @@
1
+ require 'mysql_users/user'
@@ -0,0 +1,96 @@
1
+ module MysqlUsers
2
+ class User
3
+ attr_reader :db_client
4
+ attr_reader :e_username
5
+ attr_reader :e_host
6
+
7
+ def initialize(db_client, options={})
8
+ @db_client = db_client
9
+ @e_username = escape(options.fetch(:username))
10
+ @e_host = escape(options.fetch(:host))
11
+ p = options[:password]
12
+ @e_password = p ? escape(p) : nil
13
+ end
14
+
15
+ def exists?
16
+ query = "SELECT User, Host FROM mysql.user WHERE "\
17
+ "User='#{e_username}' AND Host='#{e_host}'"
18
+ result = db_client.query(query)
19
+ result.count != 0
20
+ end
21
+
22
+ def create
23
+ return if exists?
24
+ create_without_check
25
+ end
26
+
27
+ def drop
28
+ return unless exists?
29
+ sql = "DROP USER #{user_address}"
30
+ db_client.query(sql)
31
+ end
32
+
33
+ # TODO should this be its own class?
34
+ def grant(options)
35
+ db = backtick_or_star(options[:database], 'give grants to')
36
+ table = backtick_or_star(options[:table], 'give grants to')
37
+ grants = options.fetch(:grants)
38
+ verify_grants_sanitized(grants, 'give')
39
+
40
+ sql = "GRANT #{grants.join(',')}"
41
+ sql += " ON #{db}.#{table}"
42
+ sql += " TO #{user_address}"
43
+ sql += " WITH GRANT OPTION" if options.fetch(:with_grant_option, false)
44
+
45
+ db_client.query(sql)
46
+ end
47
+
48
+ def revoke(options)
49
+ db = backtick_or_star(options[:database], 'revoke grants from')
50
+ table = backtick_or_star(options[:table], 'revoke grants from')
51
+ grants = options.fetch(:grants)
52
+ verify_grants_sanitized(grants, 'revoke')
53
+ sql = "REVOKE #{grants.join(',')}"
54
+ sql += " ON #{db}.#{table}"
55
+ sql += " FROM #{user_address}"
56
+ db_client.query(sql)
57
+ end
58
+
59
+ private
60
+
61
+ def user_address
62
+ "'#{e_username}'@'#{e_host}'"
63
+ end
64
+
65
+ def verify_grants_sanitized(grants, verb)
66
+ unless grants.all?{ |grant| /^[a-zA-Z ]+$/.match(grant) }
67
+ raise "grants should match [a-zA-Z ]. Refusing to #{verb} grants"
68
+ end
69
+ if grants.empty?
70
+ raise 'provided list of grants must be non-empty'
71
+ end
72
+ end
73
+
74
+ def backtick_or_star(name, verb)
75
+ return '*' unless name
76
+ backtick_error = "refusing to #{verb} an entity "\
77
+ 'whose name contains backticks'
78
+ raise backtick_error if /`/.match(name)
79
+ "`#{escape(name)}`"
80
+ end
81
+
82
+ def has_password?
83
+ !@e_password.nil?
84
+ end
85
+
86
+ def create_without_check
87
+ sql = "CREATE USER #{user_address}"
88
+ sql += " IDENTIFIED BY '#{@e_password}'" if has_password?
89
+ db_client.query(sql)
90
+ end
91
+
92
+ def escape(string)
93
+ db_client.escape(string)
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,3 @@
1
+ module MysqlUsers
2
+ VERSION = '0.0.2'
3
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mysql_users/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'mysql_users'
8
+ spec.version = MysqlUsers::VERSION
9
+ spec.authors = ['marguerite']
10
+ spec.email = ['marguerite@pagerduty.com']
11
+ spec.summary = %q{Manage mysql users with minimal dependencies}
12
+ spec.homepage = ''
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_development_dependency 'bundler', '~> 1.7'
21
+ spec.add_development_dependency 'rake', '~> 10.0'
22
+ spec.add_development_dependency 'rspec', '~> 3.3.0'
23
+ end
@@ -0,0 +1,308 @@
1
+ require 'mysql_users'
2
+
3
+ RSpec.describe(:user) do
4
+ let(:database_client) do
5
+ db_client = double()
6
+ allow(db_client).to receive(:query).and_return([])
7
+ allow(db_client).to receive(:escape) do |string|
8
+ string.gsub('\\', '\\').gsub("'", { "'" => "\\'" })
9
+ end
10
+ db_client
11
+ end
12
+
13
+ let(:user) do
14
+ MysqlUsers::User.new(
15
+ database_client,
16
+ { username: 'marguerite', host: '%' },
17
+ )
18
+ end
19
+
20
+ let(:db_user_result) { [{'User' => 'marguerite', 'Host' => '%'}] }
21
+ let(:db_empty_result) { [] }
22
+ let(:user_select_regex) { /SELECT User, Host FROM mysql.user/ }
23
+ let(:bobby_tables) { "Robert'; DROP TABLE Students; --" }
24
+
25
+ def with_no_user_in_db
26
+ allow(database_client).to receive(:query).with(user_select_regex)
27
+ .and_return(db_empty_result)
28
+ end
29
+
30
+ def with_user_in_db
31
+ allow(database_client).to receive(:query).with(user_select_regex)
32
+ .and_return(db_user_result)
33
+ end
34
+
35
+ context('.new') do
36
+ it 'errors if username is missing' do
37
+ expect {
38
+ MysqlUsers::User.new(database_client, { host: '%' })
39
+ }.to raise_exception(KeyError)
40
+ end
41
+
42
+ it 'errors if host is missing' do
43
+ expect {
44
+ MysqlUsers::User.new(database_client, { username: 'marg' })
45
+ }.to raise_exception(KeyError)
46
+ end
47
+ end
48
+
49
+ context('#exists?') do
50
+
51
+ it 'exists? should return true if that username+host exists' do
52
+ with_user_in_db
53
+ expect(user.exists?).to eq(true)
54
+ end
55
+
56
+ it 'exists? should return false if that username+host doesn\'t exists' do
57
+ with_no_user_in_db
58
+ expect(user.exists?).to eq(false)
59
+ end
60
+
61
+ it 'should escape username before interpolating in sql string' do
62
+ user = MysqlUsers::User.new(
63
+ database_client,
64
+ { username: bobby_tables, host: '%' },
65
+ )
66
+
67
+ expect(database_client).to_not receive(:query).with(/bert'/)
68
+ expect(database_client).to receive(:query).with(/bert\\'/)
69
+
70
+ user.exists?
71
+ end
72
+
73
+ it 'should escape host before interpolating in sql string' do
74
+ user = MysqlUsers::User.new(
75
+ database_client,
76
+ { username: 'marguerite', host: bobby_tables },
77
+ )
78
+
79
+ expect(database_client).to_not receive(:query).with(/bert'/)
80
+ expect(database_client).to receive(:query).with(/bert\\'/)
81
+
82
+ user.exists?
83
+ end
84
+ end
85
+
86
+ context('#create') do
87
+ let(:create_user_regex) { /^CREATE USER 'marguerite'@'%'$/ }
88
+
89
+ it 'should create the user without password if no password given' do
90
+ with_no_user_in_db
91
+ expect(database_client).to receive(:query).with(create_user_regex)
92
+
93
+ user.create
94
+ end
95
+
96
+ it 'should not create the user if it does exist' do
97
+ with_user_in_db
98
+ expect(database_client).to_not receive(:query).with(create_user_regex)
99
+
100
+ user.create
101
+ end
102
+
103
+ it 'should create the user with password if password given' do
104
+ with_no_user_in_db
105
+ user = MysqlUsers::User.new(
106
+ database_client,
107
+ { username: 'u', host: '%', password: 'p' },
108
+ )
109
+
110
+ expect(database_client).to receive(:query).with(
111
+ /^CREATE USER 'u'@'%' IDENTIFIED BY 'p'$/
112
+ )
113
+
114
+ user.create
115
+ end
116
+
117
+ it 'should escape interpolated password when creating' do
118
+ with_no_user_in_db
119
+ user = MysqlUsers::User.new(
120
+ database_client,
121
+ { username: 'u', host: '%', password: bobby_tables },
122
+ )
123
+ expect(database_client).to_not receive(:query).with(/bert'/)
124
+ expect(database_client).to receive(:query).with(/^CREATE.*bert\\'/)
125
+
126
+ user.create
127
+ end
128
+ end
129
+
130
+ context('#drop') do
131
+ let(:drop_user_sql) { %q{DROP USER 'marguerite'@'%'} }
132
+
133
+ it 'should remove user from database if user exists' do
134
+ with_user_in_db
135
+ expect(database_client).to receive(:query).with(drop_user_sql)
136
+ user.drop
137
+ end
138
+
139
+ it 'should not drop the user if it doesn\'t exist' do
140
+ with_no_user_in_db
141
+ expect(database_client).to_not receive(:query).with(drop_user_sql)
142
+ user.drop
143
+ end
144
+ end
145
+
146
+ context('#grant') do
147
+ let(:grant_options) do
148
+ {
149
+ database: 'db',
150
+ table: 'tbl',
151
+ grants: [
152
+ :select
153
+ ]
154
+ }
155
+ end
156
+
157
+ it 'should grant with full correct query (happy path)' do
158
+ expect(database_client).to receive(:query).with(
159
+ "GRANT select ON `db`.`tbl` TO 'marguerite'@'%'"
160
+ )
161
+ user.grant(grant_options)
162
+ end
163
+
164
+ it 'should grant to * if no database provided' do
165
+ grant_options.delete(:database)
166
+ expect(database_client).to receive(:query).with(/GRANT .* ON \*\.`tbl`/)
167
+ user.grant(grant_options)
168
+ end
169
+
170
+ it 'should grant to * if no table provided' do
171
+ grant_options.delete(:table)
172
+ expect(database_client).to receive(:query).with(/GRANT .* ON `db`\.\*/)
173
+ user.grant(grant_options)
174
+ end
175
+
176
+ it 'should surround table and db name in backticks' do
177
+ expect(database_client).to receive(:query).with(/GRANT .* ON `db`\.`tbl`/)
178
+ user.grant(grant_options)
179
+ end
180
+
181
+ it 'should error if provided table name contains backticks' do
182
+ expect(database_client).to_not receive(:query).with(/stompy`/)
183
+ expect {
184
+ user.grant(grant_options.merge({table: 'stompy`'}))
185
+ }.to raise_error(/refusing to give grants/)
186
+ end
187
+
188
+ it 'should error if provided database name contains `' do
189
+ expect(database_client).to_not receive(:query).with(/stompy`/)
190
+ expect {
191
+ user.grant(grant_options.merge({database: 'stompy`'}))
192
+ }.to raise_error(/refusing to give grants/)
193
+ end
194
+
195
+ it 'should not run grant query if grant contains odd characters' do
196
+ expect {
197
+ user.grant(grant_options.merge({grants: ['&']}))
198
+ }.to raise_error('grants should match [a-zA-Z ]. Refusing to give grants')
199
+ end
200
+
201
+ it 'should raise error if no grants specified' do
202
+ grant_options.delete(:grants)
203
+ expect { user.grant(grant_options) }.to raise_error(KeyError)
204
+ end
205
+
206
+ it 'should raise error if empty grants specified' do
207
+ expect { user.grant(grant_options.merge({grants: []})) }
208
+ .to raise_error('provided list of grants must be non-empty')
209
+ end
210
+
211
+ it 'should give all grants' do
212
+ expect(database_client).to receive(:query).with(/^GRANT foo,bar/)
213
+ user.grant(grant_options.merge({grants: [:foo, :bar]}))
214
+ end
215
+
216
+ it 'should give grants to the user' do
217
+ expect(database_client).to receive(:query).with(
218
+ /^GRANT .* TO 'marguerite'@'%'$/
219
+ )
220
+ user.grant(grant_options)
221
+ end
222
+
223
+ it 'should give give "with grant option" if required' do
224
+ expect(database_client).to receive(:query).with(
225
+ /^GRANT .* WITH GRANT OPTION$/
226
+ )
227
+ user.grant(grant_options.merge({with_grant_option: true}))
228
+ end
229
+ end
230
+
231
+ context('#revoke') do
232
+ let(:grant_options) do
233
+ {
234
+ database: 'db',
235
+ table: 'tbl',
236
+ grants: [
237
+ :select
238
+ ]
239
+ }
240
+ end
241
+
242
+ it 'should revoke with full correct query (happy path)' do
243
+ expect(database_client).to receive(:query).with(
244
+ "REVOKE select ON `db`.`tbl` FROM 'marguerite'@'%'"
245
+ )
246
+ user.revoke(grant_options)
247
+ end
248
+
249
+ it 'should revoke from * if no database provided' do
250
+ grant_options.delete(:database)
251
+ expect(database_client).to receive(:query).with(/REVOKE .* ON \*\.`tbl`/)
252
+ user.revoke(grant_options)
253
+ end
254
+
255
+ it 'should revoke from * if no table provided' do
256
+ grant_options.delete(:table)
257
+ expect(database_client).to receive(:query).with(/REVOKE .* ON `db`\.\*/)
258
+ user.revoke(grant_options)
259
+ end
260
+
261
+ it 'should surround table and db name in backticks' do
262
+ expect(database_client).to receive(:query).with(/REVOKE.* ON `db`\.`tbl`/)
263
+ user.revoke(grant_options)
264
+ end
265
+
266
+ it 'should error if provided table name contains backticks' do
267
+ expect(database_client).to_not receive(:query).with(/stompy`/)
268
+ expect {
269
+ user.revoke(grant_options.merge({table: 'stompy`'}))
270
+ }.to raise_error(/refusing to revoke grants/)
271
+ end
272
+
273
+ it 'should error if provided database name contains `' do
274
+ expect(database_client).to_not receive(:query).with(/stompy`/)
275
+ expect {
276
+ user.revoke(grant_options.merge({database: 'stompy`'}))
277
+ }.to raise_error(/refusing to revoke grants/)
278
+ end
279
+
280
+ it 'should revoke all specified grants' do
281
+ expect(database_client).to receive(:query).with(/^REVOKE foo,bar/)
282
+ user.revoke(grant_options.merge({grants: [:foo, :bar]}))
283
+ end
284
+
285
+ it 'should not run revoke query if grant contains odd characters' do
286
+ expect {
287
+ user.revoke(grant_options.merge({grants: ['&']}))
288
+ }.to raise_error('grants should match [a-zA-Z ]. Refusing to revoke grants')
289
+ end
290
+
291
+ it 'should raise error if no grants specified' do
292
+ grant_options.delete(:grants)
293
+ expect { user.revoke(grant_options) }.to raise_error(KeyError)
294
+ end
295
+
296
+ it 'should raise error if empty grants specified' do
297
+ expect { user.revoke(grant_options.merge({grants: []})) }
298
+ .to raise_error('provided list of grants must be non-empty')
299
+ end
300
+
301
+ it 'should revoke grants from the user' do
302
+ expect(database_client).to receive(:query).with(
303
+ /^REVOKE .* FROM 'marguerite'@'%'$/
304
+ )
305
+ user.revoke(grant_options)
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,96 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # The settings below are suggested to provide a good initial experience
44
+ # with RSpec, but feel free to customize to your heart's content.
45
+ begin
46
+ # These two settings work together to allow you to limit a spec run
47
+ # to individual examples or groups you care about by tagging them with
48
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
49
+ # get run.
50
+ config.filter_run :focus
51
+ config.run_all_when_everything_filtered = true
52
+
53
+ # Allows RSpec to persist some state between runs in order to support
54
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
55
+ # you configure your source control system to ignore this file.
56
+ config.example_status_persistence_file_path = "spec/examples.txt"
57
+
58
+ # Limits the available syntax to the non-monkey patched syntax that is
59
+ # recommended. For more details, see:
60
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
61
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
62
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
63
+ config.disable_monkey_patching!
64
+
65
+ # This setting enables warnings. It's recommended, but in some cases may
66
+ # be too noisy due to issues in dependencies.
67
+ config.warnings = true
68
+
69
+ # Many RSpec users commonly either run the entire suite or an individual
70
+ # file, and it's useful to allow more verbose output when running an
71
+ # individual spec file.
72
+ if config.files_to_run.one?
73
+ # Use the documentation formatter for detailed output,
74
+ # unless a formatter has already been configured
75
+ # (e.g. via a command-line flag).
76
+ config.default_formatter = 'doc'
77
+ end
78
+
79
+ # Print the 10 slowest examples and example groups at the
80
+ # end of the spec run, to help surface which specs are running
81
+ # particularly slow.
82
+ #config.profile_examples = 10
83
+
84
+ # Run specs in random order to surface order dependencies. If you find an
85
+ # order dependency and want to debug it, you can fix the order by providing
86
+ # the seed, which is printed after each run.
87
+ # --seed 1234
88
+ config.order = :random
89
+
90
+ # Seed global randomization in this process using the `--seed` CLI option.
91
+ # Setting this allows you to use `--seed` to deterministically reproduce
92
+ # test failures related to randomization by passing the same `--seed` value
93
+ # as the one that triggered the failure.
94
+ Kernel.srand config.seed
95
+ end
96
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysql_users
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - marguerite
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.3.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.3.0
55
+ description:
56
+ email:
57
+ - marguerite@pagerduty.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - Gemfile
65
+ - README.md
66
+ - Rakefile
67
+ - lib/mysql_users.rb
68
+ - lib/mysql_users/user.rb
69
+ - lib/mysql_users/version.rb
70
+ - mysql_users.gemspec
71
+ - spec/mysql_users/user_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: ''
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.4.5
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Manage mysql users with minimal dependencies
97
+ test_files:
98
+ - spec/mysql_users/user_spec.rb
99
+ - spec/spec_helper.rb