masking 0.0.1
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/.codeclimate.yml +14 -0
- data/.gitignore +16 -0
- data/.mdlrc +1 -0
- data/.rubocop.yml +18 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +119 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/Rakefile +11 -0
- data/bin/console +11 -0
- data/bin/masking_profile +58 -0
- data/bin/setup +10 -0
- data/config/.keep +0 -0
- data/exe/masking +7 -0
- data/lib/masking.rb +31 -0
- data/lib/masking/cli.rb +42 -0
- data/lib/masking/cli/error_message.rb +36 -0
- data/lib/masking/cli/error_messages.yml +6 -0
- data/lib/masking/config.rb +33 -0
- data/lib/masking/config/target_columns.rb +52 -0
- data/lib/masking/config/target_columns/column.rb +32 -0
- data/lib/masking/config/target_columns/method.rb +41 -0
- data/lib/masking/config/target_columns/method/binary.rb +23 -0
- data/lib/masking/config/target_columns/method/boolean.rb +29 -0
- data/lib/masking/config/target_columns/method/date.rb +30 -0
- data/lib/masking/config/target_columns/method/float.rb +23 -0
- data/lib/masking/config/target_columns/method/integer.rb +23 -0
- data/lib/masking/config/target_columns/method/null.rb +17 -0
- data/lib/masking/config/target_columns/method/string.rb +33 -0
- data/lib/masking/config/target_columns/method/string_binary_distinctor.rb +31 -0
- data/lib/masking/config/target_columns/method/time.rb +28 -0
- data/lib/masking/config/target_columns/table.rb +24 -0
- data/lib/masking/data_mask_processor.rb +44 -0
- data/lib/masking/errors.rb +9 -0
- data/lib/masking/insert_statement.rb +74 -0
- data/lib/masking/insert_statement/sql_builder.rb +34 -0
- data/lib/masking/insert_statement/value.rb +30 -0
- data/lib/masking/sql_dump_line.rb +24 -0
- data/lib/masking/version.rb +5 -0
- data/masking.gemspec +46 -0
- data/masking.yml.sample +17 -0
- metadata +259 -0
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'rubocop/rake_task'
|
6
|
+
require 'rake/notes/rake_task'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
RuboCop::RakeTask.new
|
10
|
+
|
11
|
+
task default: %i[spec rubocop notes]
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'masking'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require 'pry'
|
11
|
+
Pry.start
|
data/bin/masking_profile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'masking'
|
6
|
+
require 'masking/cli'
|
7
|
+
|
8
|
+
require 'ruby-prof'
|
9
|
+
|
10
|
+
original_stdout = $stdout.clone
|
11
|
+
$stdout.reopen(File.new(File::NULL, 'w'))
|
12
|
+
|
13
|
+
result = RubyProf.profile do
|
14
|
+
Masking::Cli.new(ARGV).run
|
15
|
+
end
|
16
|
+
|
17
|
+
$stdout.reopen(original_stdout)
|
18
|
+
|
19
|
+
flat_result_file = Pathname(__dir__).join('../profile/flat.txt')
|
20
|
+
RubyProf::FlatPrinterWithLineNumbers.new(result).tap do |flat_printer|
|
21
|
+
flat_printer.print(File.new(flat_result_file, 'w'))
|
22
|
+
puts "flat result is saved at #{flat_result_file}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# it can produce big file. just comment out if you don't want to use it
|
26
|
+
graph_file = Pathname(__dir__).join('../profile/graph.txt')
|
27
|
+
RubyProf::GraphPrinter.new(result).tap do |graph_html_printer|
|
28
|
+
graph_html_printer.print(File.new(graph_file, 'w'))
|
29
|
+
puts "graph result is saved at #{graph_file}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# it can produce huge html file. just comment out if you don't want to use it
|
33
|
+
graph_html_file = Pathname(__dir__).join('../profile/graph.html')
|
34
|
+
RubyProf::GraphHtmlPrinter.new(result).tap do |graph_html_printer|
|
35
|
+
graph_html_printer.print(File.new(graph_html_file, 'w'))
|
36
|
+
puts "graph html is saved at #{graph_html_file}"
|
37
|
+
end
|
38
|
+
|
39
|
+
# # this is quite slow so commented out. but it's useful
|
40
|
+
# call_stack_html_file = Pathname(__dir__).join('../profile/call_stack.html')
|
41
|
+
# RubyProf::CallStackPrinter.new(result).tap do |call_stack_html_printer|
|
42
|
+
# call_stack_html_printer.print(File.new(call_stack_html_file, 'w'))
|
43
|
+
# puts "call stack html is saved at #{call_stack_html_file}"
|
44
|
+
# end
|
45
|
+
|
46
|
+
# # I'm not sure how to read this
|
47
|
+
# call_graph_dot_file = Pathname(__dir__).join('../profile/call_graph.dot')
|
48
|
+
# RubyProf::DotPrinter.new(result).tap do |dot_printer|
|
49
|
+
# dot_printer.print(File.new(call_graph_dot_file, 'w'))
|
50
|
+
# puts "call graph dot file is saved at #{call_graph_dot_file}"
|
51
|
+
# end
|
52
|
+
|
53
|
+
# # I'm not sure how to read this
|
54
|
+
# call_tree_dir = Pathname(__dir__).join('../profile/call_tree')
|
55
|
+
# RubyProf::CallTreePrinter.new(result).tap do |call_tree_printer|
|
56
|
+
# call_tree_printer.print(path: File.dirname(call_tree_dir) , profile: File.basename(call_tree_dir))
|
57
|
+
# puts "call tree file is saved at #{call_tree_dir}"
|
58
|
+
# end
|
data/bin/setup
ADDED
data/config/.keep
ADDED
File without changes
|
data/exe/masking
ADDED
data/lib/masking.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'masking/version'
|
4
|
+
require 'masking/cli'
|
5
|
+
require 'masking/config'
|
6
|
+
require 'masking/sql_dump_line'
|
7
|
+
|
8
|
+
module Masking
|
9
|
+
class << self
|
10
|
+
def run
|
11
|
+
Main.new.run
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Main
|
16
|
+
def initialize(input: $stdin, output: $stdout)
|
17
|
+
@input = input.set_encoding(Encoding::ASCII_8BIT, Encoding::ASCII_8BIT)
|
18
|
+
@output = output.set_encoding(Encoding::ASCII_8BIT, Encoding::ASCII_8BIT)
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
input.each_line do |line|
|
23
|
+
output.print SQLDumpLine.new(line).output
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :input, :output
|
30
|
+
end
|
31
|
+
end
|
data/lib/masking/cli.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'masking/config'
|
4
|
+
require 'masking/errors'
|
5
|
+
require 'masking/cli/error_message'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
module Masking
|
9
|
+
class Cli
|
10
|
+
def initialize(argv)
|
11
|
+
@argv = argv
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
option_parser.parse(argv)
|
16
|
+
Masking.run
|
17
|
+
rescue Masking::Error => error
|
18
|
+
warn(Masking::Cli::ErrorMessage.new(error).message(config_file_path: Masking.config.target_columns_file_path))
|
19
|
+
exit(false)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :argv
|
25
|
+
|
26
|
+
def option_parser
|
27
|
+
OptionParser.new do |parser|
|
28
|
+
parser.banner = 'Usage: masking [options]'
|
29
|
+
|
30
|
+
define_config_option(parser)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_config_option(parser)
|
35
|
+
parser.on('-cFILE_PATH', '--config=FILE_PATH', 'specify config file. default: masking.yml') do |file_path|
|
36
|
+
Masking.configure do |config|
|
37
|
+
config.target_columns_file_path = file_path
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erb'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module Masking
|
7
|
+
class Cli
|
8
|
+
class ErrorMessage
|
9
|
+
def initialize(error_class)
|
10
|
+
@error_class = error_class
|
11
|
+
end
|
12
|
+
|
13
|
+
def message(**keyword_args)
|
14
|
+
error_message(keyword_args)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :error_class
|
20
|
+
|
21
|
+
YAML_FILE_PATH = Pathname('lib/masking/cli/error_messages.yml')
|
22
|
+
|
23
|
+
def error_messages
|
24
|
+
@error_messages = YAML.safe_load(YAML_FILE_PATH.read)
|
25
|
+
end
|
26
|
+
|
27
|
+
def error_message(keyword_args)
|
28
|
+
ERB.new(
|
29
|
+
error_messages.fetch(error_class.to_s)
|
30
|
+
).result(
|
31
|
+
OpenStruct.new(keyword_args).instance_eval { binding }
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
Masking::Error::ConfigFileDoesNotExist:
|
2
|
+
"ERROR: config file (<%= config_file_path %>) does not exist"
|
3
|
+
Masking::Error::ConfigFileIsNotFile:
|
4
|
+
"ERROR: config file (<%= config_file_path %>) is not file"
|
5
|
+
Masking::Error::ConfigFileIsNotValidYaml:
|
6
|
+
"ERROR: config file (<%= config_file_path %>) is not valid yaml format"
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'masking/config/target_columns'
|
4
|
+
|
5
|
+
module Masking
|
6
|
+
class << self
|
7
|
+
def config
|
8
|
+
@config ||= Config.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure
|
12
|
+
yield config
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Config
|
17
|
+
DEFAULT_TARGET_COLUMNS_YAML_PATH = Pathname('masking.yml')
|
18
|
+
attr_reader :target_columns_file_path
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@target_columns_file_path = DEFAULT_TARGET_COLUMNS_YAML_PATH
|
22
|
+
end
|
23
|
+
|
24
|
+
def target_columns_file_path=(val)
|
25
|
+
@target_columns_file_path = Pathname(val)
|
26
|
+
@target_columns = TargetColumns.new(target_columns_file_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
def target_columns
|
30
|
+
@target_columns ||= TargetColumns.new(target_columns_file_path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'masking/config/target_columns/table'
|
5
|
+
require 'masking/config/target_columns/column'
|
6
|
+
require 'masking/errors'
|
7
|
+
|
8
|
+
module Masking
|
9
|
+
class Config
|
10
|
+
# TODO: find better naming of TargetColumns
|
11
|
+
class TargetColumns
|
12
|
+
attr_reader :file_path
|
13
|
+
|
14
|
+
def initialize(file_path)
|
15
|
+
@file_path = file_path
|
16
|
+
|
17
|
+
raise Masking::Error::ConfigFileDoesNotExist unless file_path.exist?
|
18
|
+
raise Masking::Error::ConfigFileIsNotFile unless file_path.file?
|
19
|
+
end
|
20
|
+
|
21
|
+
def contains?(table_name:)
|
22
|
+
data.key?(table_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: refactoring
|
26
|
+
def columns(table_name:)
|
27
|
+
tables.find { |table| table.name == table_name.to_sym }&.columns
|
28
|
+
end
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
file_path == other.file_path
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def data
|
37
|
+
@data ||= YAML.safe_load(file_path.read, [Date, Time])
|
38
|
+
rescue Psych::SyntaxError
|
39
|
+
raise Masking::Error::ConfigFileIsNotValidYaml
|
40
|
+
end
|
41
|
+
|
42
|
+
# TODO: extract to other class
|
43
|
+
def tables
|
44
|
+
@tables ||= [].tap do |arr|
|
45
|
+
data.each do |table_name, columns|
|
46
|
+
arr << Table.new(table_name, columns: columns)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'masking/config/target_columns/method'
|
4
|
+
|
5
|
+
module Masking
|
6
|
+
class Config
|
7
|
+
class TargetColumns
|
8
|
+
class Column
|
9
|
+
attr_reader :name, :table_name, :method_value
|
10
|
+
|
11
|
+
def initialize(name, table_name:, method_value:)
|
12
|
+
@name = name.to_sym
|
13
|
+
@table_name = table_name.to_sym
|
14
|
+
@method_value = method_value
|
15
|
+
@method = Method.new(method_value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def masked_value
|
19
|
+
method.call
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
name == other.name && table_name == other.table_name && method_value == other.method_value
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :method
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Dir[Pathname(__FILE__).dirname.join('method/*.rb').to_s].each(&method(:require))
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
module Masking
|
7
|
+
class Config
|
8
|
+
class TargetColumns
|
9
|
+
class Method
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize(method)
|
13
|
+
@method_type = mapping(method.class).new(method)
|
14
|
+
end
|
15
|
+
|
16
|
+
def_delegator :@method_type, :call
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# rubocop:disable Layout/AlignHash
|
21
|
+
MAPPING = {
|
22
|
+
::String => StringBinaryDistinctor,
|
23
|
+
::Integer => Integer,
|
24
|
+
::Float => Float,
|
25
|
+
::Date => Date,
|
26
|
+
::Time => Time,
|
27
|
+
::TrueClass => Boolean,
|
28
|
+
::FalseClass => Boolean,
|
29
|
+
::NilClass => Null
|
30
|
+
}.freeze
|
31
|
+
# rubocop:enable Layout/AlignHash
|
32
|
+
|
33
|
+
def mapping(klass)
|
34
|
+
MAPPING[klass] || raise(UnknownType)
|
35
|
+
end
|
36
|
+
|
37
|
+
class UnknownType < RuntimeError; end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Masking
|
4
|
+
class Config
|
5
|
+
class TargetColumns
|
6
|
+
class Method
|
7
|
+
class Binary
|
8
|
+
def initialize(value)
|
9
|
+
@binary = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
"_binary '#{binary}'".b
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :binary
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Masking
|
4
|
+
class Config
|
5
|
+
class TargetColumns
|
6
|
+
class Method
|
7
|
+
class Boolean
|
8
|
+
def initialize(value)
|
9
|
+
@boolean = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
boolean_format.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :boolean
|
19
|
+
|
20
|
+
# NOTE: 11.1.1 Numeric Type Overview, chapter BOOL, BOOLEAN
|
21
|
+
# https://dev.mysql.com/doc/refman/8.0/en/numeric-type-overview.html
|
22
|
+
def boolean_format
|
23
|
+
boolean ? 1 : 0
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|