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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +14 -0
  3. data/.gitignore +16 -0
  4. data/.mdlrc +1 -0
  5. data/.rubocop.yml +18 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/CODE_OF_CONDUCT.md +74 -0
  9. data/Gemfile +8 -0
  10. data/Gemfile.lock +119 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +166 -0
  13. data/Rakefile +11 -0
  14. data/bin/console +11 -0
  15. data/bin/masking_profile +58 -0
  16. data/bin/setup +10 -0
  17. data/config/.keep +0 -0
  18. data/exe/masking +7 -0
  19. data/lib/masking.rb +31 -0
  20. data/lib/masking/cli.rb +42 -0
  21. data/lib/masking/cli/error_message.rb +36 -0
  22. data/lib/masking/cli/error_messages.yml +6 -0
  23. data/lib/masking/config.rb +33 -0
  24. data/lib/masking/config/target_columns.rb +52 -0
  25. data/lib/masking/config/target_columns/column.rb +32 -0
  26. data/lib/masking/config/target_columns/method.rb +41 -0
  27. data/lib/masking/config/target_columns/method/binary.rb +23 -0
  28. data/lib/masking/config/target_columns/method/boolean.rb +29 -0
  29. data/lib/masking/config/target_columns/method/date.rb +30 -0
  30. data/lib/masking/config/target_columns/method/float.rb +23 -0
  31. data/lib/masking/config/target_columns/method/integer.rb +23 -0
  32. data/lib/masking/config/target_columns/method/null.rb +17 -0
  33. data/lib/masking/config/target_columns/method/string.rb +33 -0
  34. data/lib/masking/config/target_columns/method/string_binary_distinctor.rb +31 -0
  35. data/lib/masking/config/target_columns/method/time.rb +28 -0
  36. data/lib/masking/config/target_columns/table.rb +24 -0
  37. data/lib/masking/data_mask_processor.rb +44 -0
  38. data/lib/masking/errors.rb +9 -0
  39. data/lib/masking/insert_statement.rb +74 -0
  40. data/lib/masking/insert_statement/sql_builder.rb +34 -0
  41. data/lib/masking/insert_statement/value.rb +30 -0
  42. data/lib/masking/sql_dump_line.rb +24 -0
  43. data/lib/masking/version.rb +5 -0
  44. data/masking.gemspec +46 -0
  45. data/masking.yml.sample +17 -0
  46. metadata +259 -0
@@ -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]
@@ -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
@@ -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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ if [[ ! -a masking.yml ]]; then
9
+ cp masking.yml.sample masking.yml
10
+ fi
File without changes
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'masking'
5
+ require 'masking/cli'
6
+
7
+ Masking::Cli.new(ARGV).run
@@ -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
@@ -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