mysqlman 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f1ffe62bdd3498e4363295323ead2cfa3de380ae
4
+ data.tar.gz: 4a661762abc2a84051d75e1a63bcc9cbd0dbca1f
5
+ SHA512:
6
+ metadata.gz: 6eef9e1a8a3c873d1d1762d403792c4da4cd422be35574ea16c800ebd2280f94effebb9ab1abc4724fe579cc4e851c5283973a0c0b0d104aaad0b713541ad7b0
7
+ data.tar.gz: d32c5d397b2e5ddf293e04606ba024e3b18f74c1f3683fc81f3595c018d9e01c2a46a73e628079489cbef53d859cb54dd01643fba0ae1a46e4bb035f48166a0a
@@ -0,0 +1,36 @@
1
+ version: 2
2
+ jobs:
3
+ build:
4
+ parallelism: 1
5
+ working_directory: ~/mysqlman
6
+
7
+ docker:
8
+ - image: circleci/ruby:2.4.1
9
+ - image: circleci/mysql:5.7
10
+ environment:
11
+ - MYSQL_ALLOW_EMPTY_PASSWORD=true
12
+ - MYSQL_DATABASE=test
13
+ - MYSQL_USER=root
14
+ - MYSQL_ROOT_HOST=%
15
+
16
+ steps:
17
+ - checkout
18
+ - run:
19
+ name: deps
20
+ command: |
21
+ sudo apt-get update --fix-missing
22
+ sudo apt-get install -y --force-yes --no-install-recommends mysql-client
23
+ - run:
24
+ name: Wait for db
25
+ command: dockerize -wait tcp://localhost:3306 -timeout 1m
26
+
27
+
28
+ # Bundle install dependencies
29
+ - run: bundle install --jobs=4 --retry=3 --path=vendor/bundle
30
+
31
+ - run:
32
+ name: Run RSpec
33
+ command: cd spec/dummy && bundle exec rspec ../* --format documentation --require ../spec_helper.rb
34
+ - run:
35
+ name: Run rubocop
36
+ command: bundle exec rubocop ./lib/**/*
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /bin/console
10
+ /bin/setup
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ Style/Documentation:
2
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in mysqlman.gemspec
6
+ gemspec
7
+ gem 'pry'
8
+ gem 'rspec'
9
+ gem 'rubocop'
data/Gemfile.lock ADDED
@@ -0,0 +1,61 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ mysqlman (0.1.0)
5
+ mysql2
6
+ thor
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.0)
12
+ coderay (1.1.2)
13
+ diff-lcs (1.3)
14
+ method_source (0.9.0)
15
+ mysql2 (0.4.10)
16
+ parallel (1.12.1)
17
+ parser (2.5.0.4)
18
+ ast (~> 2.4.0)
19
+ powerpack (0.1.1)
20
+ pry (0.11.3)
21
+ coderay (~> 1.1.0)
22
+ method_source (~> 0.9.0)
23
+ rainbow (3.0.0)
24
+ rake (10.5.0)
25
+ rspec (3.7.0)
26
+ rspec-core (~> 3.7.0)
27
+ rspec-expectations (~> 3.7.0)
28
+ rspec-mocks (~> 3.7.0)
29
+ rspec-core (3.7.1)
30
+ rspec-support (~> 3.7.0)
31
+ rspec-expectations (3.7.0)
32
+ diff-lcs (>= 1.2.0, < 2.0)
33
+ rspec-support (~> 3.7.0)
34
+ rspec-mocks (3.7.0)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.7.0)
37
+ rspec-support (3.7.0)
38
+ rubocop (0.53.0)
39
+ parallel (~> 1.10)
40
+ parser (>= 2.5)
41
+ powerpack (~> 0.1)
42
+ rainbow (>= 2.2.2, < 4.0)
43
+ ruby-progressbar (~> 1.7)
44
+ unicode-display_width (~> 1.0, >= 1.0.1)
45
+ ruby-progressbar (1.9.0)
46
+ thor (0.20.0)
47
+ unicode-display_width (1.3.0)
48
+
49
+ PLATFORMS
50
+ ruby
51
+
52
+ DEPENDENCIES
53
+ bundler (~> 1.16.a)
54
+ mysqlman!
55
+ pry
56
+ rake (~> 10.0)
57
+ rspec
58
+ rubocop
59
+
60
+ BUNDLED WITH
61
+ 1.16.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 onunu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # Mysqlman
2
+ Mysqlman is a tool manage users for MySQL.
3
+ You can start management with writing some yaml files and executing some commands.
4
+ And mysqlman provide feature to manage privileges(global, schema, table, but column)
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'mysqlman'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ```
17
+ $ bundle
18
+ ```
19
+
20
+ Or install it yourself as:
21
+
22
+ ```
23
+ $ gem install mysqlman
24
+ ```
25
+
26
+ ## Usage
27
+ ### 1. Setup
28
+ Firstly, please create file for connecting MySQL.
29
+ Please set the file in executing dir `config/manager.yml`
30
+
31
+ ```yml
32
+ ---
33
+ host: 127.0.0.1
34
+ username: root
35
+ password: passw0rd
36
+ ```
37
+
38
+ #### Caution
39
+ The manager needs some privileges.
40
+ The read privileges to manage other users are following.
41
+
42
+ |schema|table|columns|
43
+ |:-----|:----|:------|
44
+ |mysql | user|User, Host|
45
+ |information_schema|USER_PRIVILEGES|PRIVILEGE_TYPE, IS_GRANTABLE|
46
+ |information_schema|SCHEMA_PRIVILEGES|TABLE_SCHEMA, PRIVILEGE_TYPE, IS_GRANTABLE|
47
+ |information_schema|TABLE_PRIVILEGES|TABLE_SCHEMA, TABLE_NAME, PRIVILEGE_TYPE, IS_GRANTABLE|
48
+
49
+ And ofcourse, the manager needs privileges that you want to manage with grant option.
50
+
51
+ ### 2. Initialize
52
+ Second, initialize the config.
53
+ In initializing, mysqlman do followings.
54
+
55
+ - Create each directories(roles.d, users.d, excludes.d)
56
+ - Create exclude users config
57
+
58
+ Execute:
59
+
60
+ ```
61
+ $ mysqlman init
62
+ ```
63
+
64
+ Exclude users (=Unmanaged users) are that users are already exist in MySQL.
65
+ Exclude users are written in `excludes.d/default.yml` by default.
66
+ If you want to add unmanaged user, or to manage user written in excludes config, please edit the file by yourself.
67
+
68
+ ### 3. Write config
69
+ Write user, role settings.
70
+ please confirm how to write them.
71
+
72
+ #### 3-1 Role
73
+ Role is config of database privileges.
74
+ All users are belong to one of roles.
75
+
76
+ In `roles.d/engineer.yml` as example:
77
+
78
+ ```yml
79
+ ---
80
+ engineer: # require: as a role name
81
+ global: # optional: global privileges
82
+ - select
83
+ schema: # optional: schema privileges
84
+ example_schema1: # requrie: schema name
85
+ - update
86
+ - insert
87
+ example_schema2:
88
+ - update
89
+ table: # optional: table privileges
90
+ example_schema1: # require: schema name
91
+ example_table: # require: table name
92
+ - delete:
93
+ ```
94
+
95
+ You can write privilege type in format of followings.
96
+
97
+ - OK:
98
+ - CREATE USER
99
+ - create user
100
+ - CREATE_USER
101
+ - create_user
102
+ - NG:
103
+ - CREATEUSER
104
+ - createuser
105
+
106
+ ##### Special privileges
107
+ ###### ALL
108
+ `ALL` type privileges alias of some some privileges of the target level.
109
+ Please confirm following.
110
+
111
+ (WIP)
112
+ [All privileges](https://github.com/onunu/mysqlman/blob/master/lib/mysqlman/all_privileges.yml)
113
+
114
+ ###### GRANT OPTION
115
+ `GRANT OPTION` is not included in `ALL` privileges.
116
+ If you want add the privileges, please set bot of them.
117
+
118
+ #### 3-2 User
119
+ User is config of information to connect Mysql.
120
+ All users are belong one of roles.
121
+
122
+ In `users.d/engineers.yml`:
123
+
124
+ ```yml
125
+ ---
126
+ engineer: # require: the role name
127
+ - onunu: # require: user name
128
+ - application_user:
129
+ host: 10.0.0.1 # optional: connectable host(default '%')
130
+ ```
131
+
132
+ ### 4. Apply settings
133
+ After writing settings, please apply them.
134
+
135
+ #### dry-run
136
+ You can confirm changes witout appling settings.
137
+
138
+ ```
139
+ $ mysqlman dryrun
140
+ ```
141
+
142
+ #### apply
143
+ If the changes are same as your plan, please execute apply command.
144
+
145
+ ```
146
+ $ mysqlman apply
147
+ ```
148
+
149
+ Changes are put in STDOUT.
150
+
151
+ ## Contributing
152
+
153
+ Bug reports and pull requests are welcome on GitHub at https://github.com/onunu/mysqlman.
154
+
155
+ ## License
156
+
157
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
data/bin/mysqlman ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'mysqlman'
3
+
4
+ Mysqlman::CLI.start
@@ -0,0 +1,33 @@
1
+ ---
2
+ table: &table
3
+ ALTER:
4
+ CREATE VIEW:
5
+ CREATE:
6
+ DELETE:
7
+ DROP:
8
+ INDEX:
9
+ INSERT:
10
+ SELECT:
11
+ SHOW VIEW:
12
+ TRIGGER:
13
+ UPDATE:
14
+
15
+ schema: &schema
16
+ <<: *table
17
+ CREATE:
18
+ DROP:
19
+ EVENT:
20
+ LOCK TABLES:
21
+
22
+ global:
23
+ <<: *schema
24
+ CREATE TABLESPACE:
25
+ CREATE USER:
26
+ FILE:
27
+ PROCESS:
28
+ RELOAD:
29
+ REPLICATION CLIENT:
30
+ REPLICATION SLAVE:
31
+ SHOW DATABASES:
32
+ SHUTDOWN:
33
+ SUPER:
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+
3
+ module Mysqlman
4
+ class CLI < Thor
5
+ desc 'init', 'initialize settings'
6
+ long_desc <<-LONGDESC
7
+ Require: `config/manager.yml` : to connect MySQL.
8
+ Create: `excludes.d/default.yml`, `roles.d/`, `users.d/`
9
+ When you want see how to write or roles some files, please confirm README on Github.
10
+ LONGDESC
11
+ def init
12
+ Initializer.new.init
13
+ end
14
+
15
+ desc 'apply', 'apply settings'
16
+ def apply
17
+ Processor.new.apply
18
+ end
19
+
20
+ desc 'dryrun', 'confirm settings, with dry-run'
21
+ def dryrun
22
+ Processor.new.apply(true)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'mysql2'
2
+ require 'yaml'
3
+ require 'singleton'
4
+ require 'logger'
5
+
6
+ module Mysqlman
7
+ class Connection
8
+ include Singleton
9
+
10
+ attr_accessor :conn
11
+
12
+ def initialize
13
+ config = YAML.load_file(MANAGER_CONFIG).map { |k, v| [k.to_sym, v] }.to_h
14
+ config.merge(database: 'mysql')
15
+ @conn = Mysql2::Client.new(config)
16
+ end
17
+
18
+ def query(query_string)
19
+ @conn.query(query_string)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ require 'logger'
2
+
3
+ module Mysqlman
4
+ class Initializer
5
+ def initialize
6
+ @conn = Connection.instance
7
+ @logger = Logger.new(STDOUT)
8
+ end
9
+
10
+ # rubocop:disable LineLength
11
+ def init
12
+ File.exist?(EXCLUDE_FILE) ? @logger.info('skip: creation excludes.d') : create_exclude_config
13
+ Dir.exist?(ROLE_DIR) ? @logger.info('skip: creation roles.d') : create_roles_dir
14
+ Dir.exist?(USER_DIR) ? @logger.info('skip: creation users.d') : create_users_dir
15
+ end
16
+ # rubocop:enable LineLength
17
+
18
+ private
19
+
20
+ def create_exclude_config
21
+ unless Dir.exist?(EXCLUDE_DIR)
22
+ Dir.mkdir(EXCLUDE_DIR)
23
+ @logger.info("created: #{EXCLUDE_DIR}")
24
+ end
25
+ File.open(EXCLUDE_FILE, 'w') do |file|
26
+ file.puts(User.all.map(&:name_with_host).to_yaml)
27
+ end
28
+ @logger.info("created: #{EXCLUDE_FILE}")
29
+ end
30
+
31
+ def create_roles_dir
32
+ Dir.mkdir(ROLE_DIR)
33
+ @logger.info("created: #{ROLE_DIR}")
34
+ end
35
+
36
+ def create_users_dir
37
+ Dir.mkdir(USER_DIR)
38
+ @logger.info("created: #{USER_DIR}")
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,95 @@
1
+ require 'logger'
2
+ require 'mysqlman/privs_util'
3
+ require 'mysqlman/privs_grant'
4
+
5
+ module Mysqlman
6
+ class Privs
7
+ extend PrivsUtil
8
+ include PrivsGrant
9
+
10
+ def initialize(user)
11
+ @user = user
12
+ @conn = Connection.instance
13
+ @logger = Logger.new(STDOUT)
14
+ end
15
+
16
+ def fetch
17
+ reload_privs
18
+ end
19
+
20
+ private
21
+
22
+ def reload_privs
23
+ [global_privs, schema_privs, table_privs].compact.flatten
24
+ end
25
+
26
+ def global_privs
27
+ privs = fetch_privs(
28
+ 'information_schema.USER_PRIVILEGES',
29
+ %w[PRIVILEGE_TYPE IS_GRANTABLE]
30
+ )
31
+ format_privs(add_grantable(privs))
32
+ end
33
+
34
+ def schema_privs
35
+ privs = fetch_privs(
36
+ 'information_schema.SCHEMA_PRIVILEGES',
37
+ %w[TABLE_SCHEMA PRIVILEGE_TYPE IS_GRANTABLE]
38
+ )
39
+ format_privs(add_grantable(privs))
40
+ end
41
+
42
+ def table_privs
43
+ privs = fetch_privs(
44
+ 'information_schema.TABLE_PRIVILEGES',
45
+ %w[TABLE_NAME TABLE_SCHEMA PRIVILEGE_TYPE IS_GRANTABLE]
46
+ )
47
+ format_privs(add_grantable(privs))
48
+ end
49
+
50
+ def fetch_privs(table, columns)
51
+ @conn.query(fetch_query(table, columns)).map do |row|
52
+ {
53
+ schema: row['TABLE_SCHEMA'],
54
+ table: row['TABLE_NAME'],
55
+ type: row['PRIVILEGE_TYPE'],
56
+ grant: row['IS_GRANTABLE'] == 'YES'
57
+ }
58
+ end
59
+ end
60
+
61
+ def fetch_query(table, columns)
62
+ <<-SQL
63
+ SELECT #{columns.join(',')}
64
+ FROM #{table}
65
+ WHERE
66
+ GRANTEE = '\\\'#{@user.user}\\\'@\\\'#{@user.host}\\\''
67
+ SQL
68
+ end
69
+
70
+ def add_grantable(privs)
71
+ privs.uniq { |priv| [priv[:schema], priv[:table]] }.each do |names|
72
+ is_grant = grantable_collection?(privs, names)
73
+ next unless is_grant
74
+ privs.push(
75
+ schema: names[:schema], table: names[:table], type: 'GRANT OPTION'
76
+ )
77
+ end
78
+ privs
79
+ end
80
+
81
+ def grantable_collection?(privs, names)
82
+ collection = privs.select do |priv|
83
+ priv[:schema] == names[:schema] && priv[:table] == names[:table]
84
+ end
85
+ collection.all? { |priv| priv[:grant] }
86
+ end
87
+
88
+ def format_privs(privs)
89
+ privs.reject { |p| p[:type] == 'USAGE' }.map do |priv|
90
+ priv.delete(:grant)
91
+ priv
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,25 @@
1
+ module Mysqlman
2
+ module PrivsGrant
3
+ def revoke(priv, debug = false)
4
+ query = "REVOKE #{priv[:type]} ON #{target_lebel(priv)} FROM #{user_info}"
5
+ @conn.query(query) unless debug
6
+ @logger.info(query)
7
+ end
8
+
9
+ def grant(priv, debug = false)
10
+ query = "GRANT #{priv[:type]} ON #{target_lebel(priv)} TO #{user_info}"
11
+ @conn.query(query) unless debug
12
+ @logger.info(query)
13
+ end
14
+
15
+ private
16
+
17
+ def user_info
18
+ "'#{@user.user}'@'#{@user.host}'"
19
+ end
20
+
21
+ def target_lebel(priv)
22
+ "#{priv[:schema] || '*'}.#{priv[:table] || '*'}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,36 @@
1
+ require 'yaml'
2
+
3
+ module Mysqlman
4
+ module PrivsUtil
5
+ def all(schema, table, grantable)
6
+ privs = all_privs(schema, table, lebel(schema, table))
7
+ grantable ? privs.push(grant_option(schema, table)) : privs
8
+ end
9
+
10
+ private
11
+
12
+ def all_privs(schema, table, key)
13
+ load_privs(key).map do |priv|
14
+ { schema: schema, table: table, type: priv }
15
+ end
16
+ end
17
+
18
+ def lebel(schema, table)
19
+ if schema && table
20
+ 'table'
21
+ elsif schema
22
+ 'schema'
23
+ else
24
+ 'global'
25
+ end
26
+ end
27
+
28
+ def load_privs(key)
29
+ YAML.load_file(File.join(__dir__, 'all_privileges.yml'))[key].keys
30
+ end
31
+
32
+ def grant_option(schema = nil, table = nil)
33
+ { schema: schema, table: table, type: 'GRANT OPTION' }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ module Mysqlman
2
+ class Processor
3
+ def initialize
4
+ @current_users = current_users
5
+ @managed_users = managed_users
6
+ end
7
+
8
+ def apply(debug = false)
9
+ delete_unknown_user(debug)
10
+ create_shortage_user(debug)
11
+ revoke_extra_privileges(debug)
12
+ grant_shortage_privileges(debug)
13
+ end
14
+
15
+ private
16
+
17
+ def current_users
18
+ User.all.map do |user|
19
+ exclude_users.include?(user: user.user, host: user.host) ? nil : user
20
+ end.compact
21
+ end
22
+
23
+ def exclude_users
24
+ @exclude_users ||= Dir.glob("#{EXCLUDE_DIR}/*.yml").map do |file|
25
+ YAML.load_file(file).map do |u|
26
+ { user: u['user'], host: u['host'] || '%' }
27
+ end
28
+ end.flatten
29
+ end
30
+
31
+ # rubocop:disable Metrics/MethodLength
32
+ def managed_users
33
+ Dir.glob("#{USER_DIR}/*.yml").map do |file|
34
+ YAML.load_file(file).map do |role, users|
35
+ users.map do |user|
36
+ User.new(
37
+ role: role,
38
+ user: user.keys.first,
39
+ host: user['host'] || HOST_ALL
40
+ )
41
+ end
42
+ end
43
+ end.flatten
44
+ end
45
+ # rubocop:enable Metrics/MethodLength
46
+
47
+ def delete_unknown_user(_debug)
48
+ @current_users.each do |cu|
49
+ cu.drop unless @managed_users.any? do |mu|
50
+ cu.user == mu.user && cu.host == mu.host
51
+ end
52
+ end
53
+ end
54
+
55
+ def create_shortage_user(_debug)
56
+ @managed_users.each do |user|
57
+ user.create unless user.exists?
58
+ end
59
+ end
60
+
61
+ def revoke_extra_privileges(debug)
62
+ @managed_users.each do |user|
63
+ (user.privs.fetch - user.role.privs).each do |priv|
64
+ user.privs.revoke(priv, debug)
65
+ end
66
+ end
67
+ end
68
+
69
+ def grant_shortage_privileges(debug)
70
+ @managed_users.each do |user|
71
+ (user.role.privs - user.privs.fetch).each do |priv|
72
+ user.privs.grant(priv, debug)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,74 @@
1
+ require 'yaml'
2
+
3
+ module Mysqlman
4
+ class Role
5
+ class << self
6
+ def all
7
+ files = Dir.glob("#{ROLE_DIR}/*.yml")
8
+ files.map do |file|
9
+ YAML.load_file(file).map do |name, config|
10
+ new(name, config)
11
+ end
12
+ end.flatten
13
+ end
14
+
15
+ def find(name)
16
+ roles = all
17
+ roles.select { |role| role.name == name }.first
18
+ end
19
+ end
20
+
21
+ attr_reader :name
22
+
23
+ def initialize(name, config)
24
+ @name = name
25
+ @config = config
26
+ end
27
+
28
+ def privs
29
+ [global_privs, schema_privs, table_privs].compact.flatten
30
+ end
31
+
32
+ def global_privs
33
+ return if @config['global'].nil?
34
+ parse_privs(@config['global'])
35
+ end
36
+
37
+ def schema_privs
38
+ return if @config['schema'].nil?
39
+ @config['schema'].map do |schema_name, privs|
40
+ parse_privs(privs, schema_name)
41
+ end
42
+ end
43
+
44
+ def table_privs
45
+ return if @config['table'].nil?
46
+ @config['table'].map do |schema_name, table_config|
47
+ table_config.map do |table_name, privs|
48
+ parse_privs(privs, schema_name, table_name)
49
+ end
50
+ end
51
+ end
52
+
53
+ def parse_privs(privs, schema = nil, table = nil)
54
+ return Privs.all(schema, table, grantable?(privs)) if all_priv?(privs)
55
+ privs.map do |priv|
56
+ {
57
+ schema: schema,
58
+ table: table,
59
+ type: priv.upcase.tr('_', ' ')
60
+ }
61
+ end
62
+ end
63
+
64
+ def grantable?(privs)
65
+ privs.map(&:upcase).any? do |priv|
66
+ priv.tr('_', ' ') == 'GRANT OPTION'
67
+ end
68
+ end
69
+
70
+ def all_priv?(privs)
71
+ privs.map(&:upcase).include?('ALL')
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,69 @@
1
+ require 'securerandom'
2
+ require 'logger'
3
+
4
+ module Mysqlman
5
+ class User
6
+ PASSWORD_LENGTH = 8
7
+ class << self
8
+ def all
9
+ conn = Connection.instance
10
+ conn.query('SELECT Host, User FROM mysql.user').map do |row|
11
+ new(host: row['Host'], user: row['User'])
12
+ end
13
+ end
14
+
15
+ def find(user, host = HOST_ALL)
16
+ conn = Connection.instance
17
+ user = conn.query(<<-QUERY
18
+ SELECT Host, User
19
+ FROM mysql.user
20
+ WHERE Host = '#{host}' AND User = '#{user}'
21
+ QUERY
22
+ ).first
23
+ new(host: user['Host'], user: user['User']) unless user.nil?
24
+ end
25
+ end
26
+
27
+ attr_reader :user, :host, :role, :privs
28
+
29
+ def initialize(user:, host: HOST_ALL, role: nil)
30
+ @host = host
31
+ @user = user
32
+ @role = Role.find(role) unless role.nil?
33
+ @privs = Privs.new(self)
34
+ @conn = Connection.instance
35
+ end
36
+
37
+ def name_with_host
38
+ { 'user' => @user, 'host' => @host }
39
+ end
40
+
41
+ def exists?
42
+ user = @conn.query(<<-QUERY
43
+ SELECT Host, User
44
+ FROM mysql.user
45
+ WHERE Host = '#{@host}' AND User = '#{@user}'
46
+ QUERY
47
+ ).first
48
+ !user.nil?
49
+ end
50
+
51
+ def create(debug = false)
52
+ password = debug ? '******' : SecureRandom.urlsafe_base64(PASSWORD_LENGTH)
53
+ @conn.query(create_user_query(password)) unless debug
54
+ Logger.new(STDOUT).info(
55
+ "Create user: '#{@user}'@'#{@host}', password is '#{password}'"
56
+ )
57
+ self
58
+ end
59
+
60
+ def create_user_query(password)
61
+ "CREATE USER '#{@user}'@'#{@host}' IDENTIFIED BY '#{password}'"
62
+ end
63
+
64
+ def drop(debug = false)
65
+ @conn.query("DROP USER '#{@user}'@'#{@host}'") unless debug
66
+ Logger.new(STDOUT).info("Delete user: '#{@user}'@'#{@host}'")
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Mysqlman
2
+ VERSION = '0.1.0'.freeze
3
+ end
data/lib/mysqlman.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'mysqlman/version'
2
+ require 'mysqlman/user'
3
+ require 'mysqlman/role'
4
+ require 'mysqlman/initializer'
5
+ require 'mysqlman/processor'
6
+ require 'mysqlman/privs'
7
+ require 'mysqlman/connection'
8
+ require 'mysqlman/cli'
9
+
10
+ module Mysqlman
11
+ EXE_DIR = Dir.pwd
12
+ EXCLUDE_DIR = File.join(EXE_DIR, 'excludes.d')
13
+ EXCLUDE_FILE = File.join(EXCLUDE_DIR, 'default.yml')
14
+
15
+ ROLE_DIR = File.join(EXE_DIR, 'roles.d')
16
+ USER_DIR = File.join(EXE_DIR, 'users.d')
17
+
18
+ MANAGER_CONFIG = File.join(EXE_DIR, 'config', 'manager.yml')
19
+
20
+ HOST_ALL = '%'.freeze
21
+ end
data/mysqlman.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'mysqlman/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'mysqlman'
7
+ spec.version = Mysqlman::VERSION
8
+ spec.authors = ['onunu']
9
+ spec.email = ['riku.onuma@livesense.co.jp', 'onunu@zeals.co.jp']
10
+
11
+ spec.summary = 'Management your mysql users.'
12
+ spec.description = 'Management your mysql users. You can do that by simple settings written by yaml'
13
+ spec.homepage = 'https://github.com/onunu/mysqlman'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = 'exe'
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.add_dependency 'mysql2'
24
+ spec.add_dependency 'thor'
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.16.a'
27
+ spec.add_development_dependency 'rake', '~> 10.0'
28
+ spec.add_development_dependency 'rspec', '~> 3.0'
29
+ end
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mysqlman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - onunu
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mysql2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 1.16.a
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 1.16.a
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: Management your mysql users. You can do that by simple settings written
84
+ by yaml
85
+ email:
86
+ - riku.onuma@livesense.co.jp
87
+ - onunu@zeals.co.jp
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".circleci/config.yml"
93
+ - ".gitignore"
94
+ - ".rspec"
95
+ - ".rubocop.yml"
96
+ - Gemfile
97
+ - Gemfile.lock
98
+ - LICENSE.txt
99
+ - README.md
100
+ - Rakefile
101
+ - bin/mysqlman
102
+ - lib/mysqlman.rb
103
+ - lib/mysqlman/all_privileges.yml
104
+ - lib/mysqlman/cli.rb
105
+ - lib/mysqlman/connection.rb
106
+ - lib/mysqlman/initializer.rb
107
+ - lib/mysqlman/privs.rb
108
+ - lib/mysqlman/privs_grant.rb
109
+ - lib/mysqlman/privs_util.rb
110
+ - lib/mysqlman/processor.rb
111
+ - lib/mysqlman/role.rb
112
+ - lib/mysqlman/user.rb
113
+ - lib/mysqlman/version.rb
114
+ - mysqlman.gemspec
115
+ homepage: https://github.com/onunu/mysqlman
116
+ licenses:
117
+ - MIT
118
+ metadata: {}
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ required_rubygems_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ requirements: []
134
+ rubyforge_project:
135
+ rubygems_version: 2.6.11
136
+ signing_key:
137
+ specification_version: 4
138
+ summary: Management your mysql users.
139
+ test_files: []