pgdump_scrambler 0.4.0 → 0.5.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.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'yaml'
3
4
  require 'erb'
4
5
  require 'set'
@@ -19,14 +20,14 @@ module PgdumpScrambler
19
20
  'prefix' => 'YOUR_S3_PATH_PREFIX',
20
21
  'access_key_id' => "<%= ENV['AWS_ACCESS_KEY_ID'] %>",
21
22
  'secret_key' => "<%= ENV['AWS_SECRET_KEY'] %>"
22
- }
23
+ }.freeze
23
24
  attr_reader :dump_path, :s3, :resolved_s3, :exclude_tables, :pgdump_args
24
25
 
25
- def initialize(tables, dump_path, s3, exclude_tables, pgdump_args)
26
- @table_hash = tables.sort_by(&:name).map { |table| [table.name, table] }.to_h
26
+ def initialize(tables, dump_path, s3, exclude_tables, pgdump_args) # rubocop:disable Naming/MethodParameterName
27
+ @table_hash = tables.sort_by(&:name).to_h { |table| [table.name, table] }
27
28
  @dump_path = dump_path
28
29
  @s3 = s3
29
- @resolved_s3 = s3.map { |k, v| [k, ERB.new(v).result] }.to_h if s3
30
+ @resolved_s3 = s3.transform_values { |v| ERB.new(v).result } if s3
30
31
  @exclude_tables = exclude_tables
31
32
  @pgdump_args = pgdump_args
32
33
  end
@@ -45,7 +46,7 @@ module PgdumpScrambler
45
46
 
46
47
  def update_with(other)
47
48
  new_tables = @table_hash.map do |_, table|
48
- if other_table = other.table(table.name)
49
+ if (other_table = other.table(table.name))
49
50
  table.update_with(other_table)
50
51
  else
51
52
  table
@@ -66,15 +67,15 @@ module PgdumpScrambler
66
67
  yml = {}
67
68
  yml[KEY_DUMP_PATH] = @dump_path
68
69
  yml[KEY_S3] = @s3 if @s3
69
- yml[KEY_EXCLUDE_TABLES] = @exclude_tables if @exclude_tables.size > 0
70
+ yml[KEY_EXCLUDE_TABLES] = @exclude_tables if @exclude_tables.size.positive?
70
71
  yml[KEY_TABLES] = @table_hash.map do |_, table|
71
72
  columns = table.columns
72
- unless columns.empty?
73
- [
74
- table.name,
75
- columns.map { |column| [column.name, column.scramble_method] }.to_h,
76
- ]
77
- end
73
+ next if columns.empty?
74
+
75
+ [
76
+ table.name,
77
+ columns.to_h { |column| [column.name, column.scramble_method] }
78
+ ]
78
79
  end.compact.to_h
79
80
  YAML.dump(yml, io)
80
81
  end
@@ -92,21 +93,22 @@ module PgdumpScrambler
92
93
  class << self
93
94
  def read(io)
94
95
  yml = YAML.safe_load(io, permitted_classes: [], permitted_symbols: [], aliases: true)
95
- if yml[KEY_TABLES]
96
- tables = yml[KEY_TABLES].map do |table_name, columns|
97
- Table.new(
98
- table_name,
99
- columns.map { |name, scramble_method| Column.new(name, scramble_method) }
100
- )
96
+ tables =
97
+ if yml[KEY_TABLES]
98
+ yml[KEY_TABLES].map do |table_name, columns|
99
+ Table.new(
100
+ table_name,
101
+ columns.map { |name, scramble_method| Column.new(name, scramble_method) }
102
+ )
103
+ end
104
+ else
105
+ []
101
106
  end
102
- else
103
- tables = []
104
- end
105
107
  Config.new(tables, yml[KEY_DUMP_PATH], yml[KEY_S3], yml[KEY_EXCLUDE_TABLES] || [], yml[KEY_PGDUMP_ARGS])
106
108
  end
107
109
 
108
110
  def read_file(path)
109
- open(path, 'r') do |f|
111
+ File.open(path, 'r') do |f|
110
112
  read(f)
111
113
  end
112
114
  end
@@ -118,18 +120,17 @@ module PgdumpScrambler
118
120
  else
119
121
  Rails.application.eager_load!
120
122
  end
121
- klasses_by_table = ActiveRecord::Base.descendants.map { |klass| [klass.table_name, klass] }.to_h
123
+ klasses_by_table = ActiveRecord::Base.descendants.to_h { |klass| [klass.table_name, klass] }
122
124
  table_names = ActiveRecord::Base.connection.tables.sort - IGNORED_ACTIVE_RECORD_TABLES
123
125
  tables = table_names.map do |table_name|
124
126
  klass = klasses_by_table[table_name]
125
- if klass
126
- columns = klass.columns.map(&:name).reject do |name|
127
- IGNORED_ACTIVE_RECORD_COLUMNS.member?(name)
128
- end.map do |name|
129
- Column.new(name)
130
- end
131
- Table.new(table_name, columns)
127
+ next unless klass
128
+
129
+ column_names = klass.columns.map(&:name).reject do |name|
130
+ IGNORED_ACTIVE_RECORD_COLUMNS.member?(name)
132
131
  end
132
+ columns = column_names.map { |name| Column.new(name) }
133
+ Table.new(table_name, columns)
133
134
  end.compact
134
135
  Config.new(tables, 'scrambled.dump.gz', Config::DEFAULT_S3_PROPERTIES, [], nil)
135
136
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'pgdump_scrambler/utils'
2
4
  require 'open3'
5
+
3
6
  module PgdumpScrambler
4
7
  class Dumper
5
8
  def initialize(config, db_config = {})
@@ -9,48 +12,53 @@ module PgdumpScrambler
9
12
  end
10
13
 
11
14
  def run
12
- puts "executing pg_dump..."
15
+ puts 'Executing pg_dump...'
13
16
  puts full_command
14
- if system(full_command)
15
- puts "done!"
16
- else
17
- raise "pg_dump failed!"
18
- end
17
+ raise 'pg_dump failed!' unless system(env_vars, full_command)
18
+
19
+ puts 'Done!'
19
20
  end
20
21
 
21
22
  private
22
23
 
24
+ def env_vars
25
+ vars = {}
26
+ vars['PGPASSWORD'] = @db_config['password'] if @db_config['password']
27
+ vars
28
+ end
29
+
23
30
  def full_command
24
31
  [pgdump_command, obfuscator_command, 'gzip -c'].compact.join(' | ') + "> #{@output_path}"
25
32
  end
26
33
 
27
34
  def obfuscator_command
28
- if options = @config.obfuscator_options
29
- command = File.expand_path('../../../bin/pgdump-obfuscator', __FILE__)
30
- "#{command} #{options}"
31
- end
35
+ return unless (options = @config.obfuscator_options)
36
+
37
+ command = File.expand_path('../../bin/pgdump-obfuscator', __dir__)
38
+ "#{command} #{options}"
32
39
  end
33
40
 
34
41
  def pgdump_command
35
42
  command = []
36
- command << "PGPASSWORD=#{Shellwords.escape(@db_config['password'])}" if @db_config['password']
37
43
  command << 'pg_dump'
38
44
  command << @config.pgdump_args if @config.pgdump_args
39
45
  command << "--username=#{Shellwords.escape(@db_config['username'])}" if @db_config['username']
40
46
  command << "--host='#{@db_config['host']}'" if @db_config['host']
41
47
  command << "--port='#{@db_config['port']}'" if @db_config['port']
42
- command << @config.exclude_tables.map { |exclude_table| "--exclude-table-data=#{exclude_table}" }.join(' ') if @config.exclude_tables.present?
48
+ if @config.exclude_tables.present?
49
+ command << @config.exclude_tables.map do |exclude_table|
50
+ "--exclude-table-data=#{exclude_table}"
51
+ end.join(' ')
52
+ end
43
53
  command << @db_config['database']
44
54
  command.join(' ')
45
55
  end
46
56
 
47
57
  def load_database_yml
48
- if defined?(Rails)
49
- db_config = open(Rails.root.join('config', 'database.yml'), 'r') do |f|
50
- YAML.safe_load(f, permitted_classes: [], permitted_symbols: [], aliases: true)
51
- end
52
- db_config[Rails.env]
53
- end
58
+ return unless defined?(Rails)
59
+
60
+ db_config = Utils.load_yaml_with_erb(Rails.root.join('config', 'database.yml'))
61
+ db_config[Rails.env]
54
62
  end
55
63
  end
56
64
  end
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module PgdumpScrambler
3
4
  class Railtie < ::Rails::Railtie
4
5
  rake_tasks do
5
- load File.expand_path('../../tasks/pgdump_scrambler_tasks.rake', __FILE__)
6
+ load File.expand_path('../tasks/pgdump_scrambler_tasks.rake', __dir__)
6
7
  end
7
8
  end
8
9
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'uri'
3
4
  require 'digest'
4
5
  require 'openssl'
5
6
 
6
7
  module PgdumpScrambler
7
8
  class S3Request
8
- def initialize(s3_path:, verb:, region:, bucket:, access_key_id:, secret_key:, time: nil)
9
+ def initialize(s3_path:, verb:, region:, bucket:, access_key_id:, secret_key:, time: nil) # rubocop:disable Metrics/ParameterLists
9
10
  @s3_path = s3_path.start_with?('/') ? s3_path : "/#{s3_path}"
10
11
  @verb = verb
11
12
  @time = time || Time.now.utc
@@ -18,7 +19,7 @@ module PgdumpScrambler
18
19
  def canonical_request
19
20
  [
20
21
  @verb,
21
- URI.encode(@s3_path),
22
+ self.class.uri_encode(@s3_path),
22
23
  canonical_query_string,
23
24
  "host:#{@bucket}.s3.amazonaws.com\n", # canonical headers
24
25
  'host', # signed headers
@@ -38,13 +39,15 @@ module PgdumpScrambler
38
39
  end
39
40
 
40
41
  def url
41
- File.join("https://#{@bucket}.s3.amazonaws.com/", "#{@s3_path}?#{canonical_query_string}&X-Amz-Signature=#{signature}")
42
+ encoded_path = self.class.uri_encode(@s3_path)
43
+ File.join("https://#{@bucket}.s3.amazonaws.com/",
44
+ "#{encoded_path}?#{canonical_query_string}&X-Amz-Signature=#{signature}")
42
45
  end
43
46
 
44
47
  private
45
48
 
46
49
  def iso_time
47
- @time.strftime("%Y%m%dT%H%M%SZ")
50
+ @time.strftime('%Y%m%dT%H%M%SZ')
48
51
  end
49
52
 
50
53
  def iso_date
@@ -52,15 +55,17 @@ module PgdumpScrambler
52
55
  end
53
56
 
54
57
  def hmac_sha256(key, message)
55
- OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, message)
58
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA256'), key, message)
56
59
  end
57
60
 
58
61
  def hmac_sha256_hex(key, message)
59
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, key, message)
62
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), key, message)
60
63
  end
61
64
 
62
65
  def canonical_query_string
66
+ # rubocop:disable Layout/LineLength
63
67
  "X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=#{@access_key_id}%2F#{iso_date}%2F#{@region}%2Fs3%2Faws4_request&X-Amz-Date=#{iso_time}&X-Amz-Expires=86400&X-Amz-SignedHeaders=host"
68
+ # rubocop:enable Layout/LineLength
64
69
  end
65
70
 
66
71
  def string_to_sign
@@ -68,39 +73,29 @@ module PgdumpScrambler
68
73
  'AWS4-HMAC-SHA256',
69
74
  iso_time,
70
75
  "#{iso_date}/#{@region}/s3/aws4_request",
71
- Digest::SHA256.hexdigest(canonical_request),
76
+ Digest::SHA256.hexdigest(canonical_request)
72
77
  ].join("\n")
73
78
  end
74
- end
75
- end
76
-
77
- if $0 == __FILE__
78
- # https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
79
- require "minitest/autorun"
80
- class TestS3Request < Minitest::Test
81
- def setup
82
- @s3_request = PgdumpScrambler::S3Request.new(verb: 'GET', s3_path: '/test.txt', region: 'us-east-1', bucket: 'examplebucket', access_key_id: 'AKIAIOSFODNN7EXAMPLE', secret_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', time: Time.utc(2013, 5, 24, 0, 0, 0))
83
- end
84
-
85
- def test_canonical_request
86
- assert_equal <<~EOS.chomp, @s3_request.canonical_request
87
- GET
88
- /test.txt
89
- X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host
90
- host:examplebucket.s3.amazonaws.com
91
-
92
- host
93
- UNSIGNED-PAYLOAD
94
- EOS
95
- end
96
-
97
- def test_signature
98
- assert_equal 'aeeed9bbccd4d02ee5c0109b86d86835f995330da4c265957d157751f604d404', @s3_request.signature
99
- end
100
79
 
101
- def test_url
102
- exected_url = 'https://examplebucket.s3.amazonaws.com/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=aeeed9bbccd4d02ee5c0109b86d86835f995330da4c265957d157751f604d404'
103
- assert_equal exected_url, @s3_request.url
80
+ class << self
81
+ # https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
82
+ # * URI encode every byte except the unreserved characters: 'A'-'Z', 'a'-'z', '0'-'9', '-', '.', '_', and '~'.
83
+ # * The space character is a reserved character and must be encoded as "%20" (and not as "+").
84
+ # * Each URI encoded byte is formed by a '%' and the two-digit hexadecimal value of the byte.
85
+ # * Letters in the hexadecimal value must be uppercase, for example "%1A".
86
+ # * Encode the forward slash character, '/', everywhere except in the object key name.
87
+ # For example, if the object key name is photos/Jan/sample.jpg,
88
+ # the forward slash in the key name is not encoded.
89
+ def uri_encode(str)
90
+ str.gsub(%r{[^A-Za-z0-9\-._~/]}) do
91
+ us = Regexp.last_match(0)
92
+ tmp = +''
93
+ us.each_byte do |uc|
94
+ tmp << sprintf('%%%02X', uc)
95
+ end
96
+ tmp
97
+ end.force_encoding(Encoding::US_ASCII)
98
+ end
104
99
  end
105
100
  end
106
101
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'net/http'
3
4
  require 'uri'
4
- require_relative './s3_request'
5
+ require_relative 's3_request'
5
6
 
6
7
  module PgdumpScrambler
7
8
  class S3UploadError < StandardError
8
9
  attr_reader :response
10
+
9
11
  def initialize(response)
10
12
  @response = response
11
13
  super "S3 upload failed: #{response.body}"
@@ -13,17 +15,25 @@ module PgdumpScrambler
13
15
  end
14
16
 
15
17
  class S3Uploader
16
- def initialize(s3_path:, local_path:, region:, bucket:, access_key_id:, secret_key:)
18
+ def initialize(s3_path:, local_path:, region:, bucket:, access_key_id:, secret_key:) # rubocop:disable Metrics/ParameterLists
17
19
  raise 'missing access_key_id' if access_key_id.nil? || access_key_id.empty?
18
20
  raise 'missing secret_key' if secret_key.nil? || secret_key.empty?
19
- @s3_request = S3Request.new(s3_path: s3_path, verb: 'PUT', region: region, bucket: bucket, access_key_id: access_key_id, secret_key: secret_key)
21
+
22
+ @s3_request = S3Request.new(
23
+ s3_path: s3_path,
24
+ verb: 'PUT',
25
+ region: region,
26
+ bucket: bucket,
27
+ access_key_id: access_key_id,
28
+ secret_key: secret_key
29
+ )
20
30
  @local_path = local_path
21
31
  end
22
32
 
23
33
  def run
24
34
  uri = URI.parse(@s3_request.url)
25
- puts "upload #{@local_path} to #{uri.host}#{uri.path}"
26
- open(@local_path, 'r') do |io|
35
+ puts "Uploading #{@local_path} to #{uri.host}#{uri.path}"
36
+ File.open(@local_path, 'r') do |io|
27
37
  uri_path = uri.path
28
38
  uri_path += "?#{uri.query}" if uri.query
29
39
  req = Net::HTTP::Put.new(uri_path)
@@ -33,10 +43,9 @@ module PgdumpScrambler
33
43
  http = Net::HTTP.new(uri.host, uri.port)
34
44
  http.use_ssl = true
35
45
  res = http.request(req)
36
- if res.code != '200'
37
- raise S3UploadError.new(res)
38
- end
46
+ raise S3UploadError, res if res.code != '200'
39
47
  end
48
+ puts 'Done.'
40
49
  end
41
50
  end
42
51
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'erb'
5
+
6
+ module PgdumpScrambler
7
+ module Utils
8
+ module_function
9
+
10
+ def load_yaml_with_erb(path)
11
+ yaml_content = File.read(path)
12
+ resolved = ERB.new(yaml_content).result
13
+ YAML.safe_load(
14
+ resolved,
15
+ permitted_classes: [],
16
+ permitted_symbols: [],
17
+ aliases: true
18
+ )
19
+ end
20
+ end
21
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module PgdumpScrambler
3
- VERSION = "0.4.0"
4
+ VERSION = '0.5.0'
4
5
  end
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
- require "pgdump_scrambler/version"
3
- require "pgdump_scrambler/config"
4
- require "pgdump_scrambler/dumper"
5
- require "pgdump_scrambler/s3_uploader"
6
- if defined?(Rails)
7
- require 'pgdump_scrambler/railtie'
8
- end
2
+
3
+ require 'pgdump_scrambler/version'
4
+ require 'pgdump_scrambler/config'
5
+ require 'pgdump_scrambler/dumper'
6
+ require 'pgdump_scrambler/s3_uploader'
7
+ require 'pgdump_scrambler/railtie' if defined?(Rails)
9
8
 
10
9
  module PgdumpScrambler
11
10
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  namespace :pgdump_scrambler do
3
4
  default_config_path = ENV['SCRAMBLER_CONFIG_PATH'] || 'config/pgdump_scrambler.yml'
4
5
 
@@ -6,15 +7,16 @@ namespace :pgdump_scrambler do
6
7
  task config_from_db: :environment do
7
8
  config =
8
9
  if File.exist?(default_config_path)
9
- puts "#{default_config_path} found!\nmerge existing config with config from database"
10
+ puts "#{default_config_path} found!\nMerging existing config with config from database"
10
11
  PgdumpScrambler::Config
11
12
  .read_file(default_config_path)
12
13
  .update_with(PgdumpScrambler::Config.from_db)
13
14
  else
14
- puts "craete config from database"
15
+ puts 'Creating a config file from database'
15
16
  PgdumpScrambler::Config.from_db
16
17
  end
17
18
  config.write_file(default_config_path)
19
+ puts "Wrote to #{default_config_path}"
18
20
  end
19
21
 
20
22
  desc 'check if new columns exist'
@@ -24,7 +26,7 @@ namespace :pgdump_scrambler do
24
26
  .update_with(PgdumpScrambler::Config.from_db)
25
27
  unspecified_columns = config.unspecified_columns
26
28
  count = unspecified_columns.sum { |_, columns| columns.size }
27
- if count > 0
29
+ if count.positive?
28
30
  unspecified_columns.each_key do |table_name|
29
31
  puts "#{table_name}:"
30
32
  unspecified_columns[table_name].each do |column_name|
@@ -34,7 +36,7 @@ namespace :pgdump_scrambler do
34
36
  puts "#{count} unspecified columns found!"
35
37
  exit 1
36
38
  else
37
- puts "No unspecified columns found."
39
+ puts 'No unspecified columns found.'
38
40
  end
39
41
  end
40
42
 
@@ -47,7 +49,7 @@ namespace :pgdump_scrambler do
47
49
  desc 'create scrambled dump'
48
50
  task clear_dump: :environment do
49
51
  config = PgdumpScrambler::Config.read_file(default_config_path)
50
- if File.exists? config.dump_path
52
+ if File.exist? config.dump_path
51
53
  File.delete(config.dump_path)
52
54
  puts "Dump file #{config.dump_path} has been deleted."
53
55
  end
@@ -57,7 +59,7 @@ namespace :pgdump_scrambler do
57
59
  task s3_upload: :environment do
58
60
  config = PgdumpScrambler::Config.read_file(default_config_path)
59
61
  uploader = PgdumpScrambler::S3Uploader.new(
60
- s3_path: File.join(config.resolved_s3['prefix'], File::basename(config.dump_path)),
62
+ s3_path: File.join(config.resolved_s3['prefix'], File.basename(config.dump_path)),
61
63
  local_path: config.dump_path,
62
64
  region: config.resolved_s3['region'],
63
65
  bucket: config.resolved_s3['bucket'],
@@ -1,37 +1,30 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "pgdump_scrambler/version"
5
+ require 'pgdump_scrambler/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "pgdump_scrambler"
8
+ spec.name = 'pgdump_scrambler'
8
9
  spec.version = PgdumpScrambler::VERSION
9
- spec.authors = ["Shunichi Ikegami"]
10
- spec.email = ["sike.tm@gmail.com"]
10
+ spec.authors = ['Shunichi Ikegami']
11
+ spec.email = ['sike.tm@gmail.com']
11
12
 
12
- spec.summary = %q{scramble pg_dump columns}
13
- spec.description = %q{scramble pg_dump columns.}
13
+ spec.summary = 'Scramble pg_dump columns'
14
+ spec.description = 'Scramble pg_dump columns.'
14
15
  spec.homepage = 'https://github.com/shunichi/pgdump_scrambler'
15
- spec.license = "MIT"
16
+ spec.license = 'MIT'
17
+ spec.required_ruby_version = '>= 3.0'
16
18
 
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
19
- if spec.respond_to?(:metadata)
20
- spec.metadata["allowed_push_host"] = 'https://rubygems.org'
21
- else
22
- raise "RubyGems 2.0 or newer is required to protect against " \
23
- "public gem pushes."
24
- end
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/shunichi/pgdump_scrambler'
21
+ spec.metadata['changelog_uri'] = 'https://github.com/shunichi/pgdump_scrambler/blob/main/CHANGELOG.md'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
25
23
 
26
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
27
25
  f.match(%r{^(test|spec|features)/})
28
26
  end
29
- spec.bindir = "exe"
27
+ spec.bindir = 'exe'
30
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
- spec.require_paths = ["lib"]
32
-
33
- spec.add_development_dependency "rake", "~> 13.0"
34
- spec.add_development_dependency "rspec", "~> 3.12"
35
- spec.add_development_dependency "rails", "~> 7.0"
36
- spec.add_development_dependency "rubocop"
29
+ spec.require_paths = ['lib']
37
30
  end