Skip to content

Instantly share code, notes, and snippets.

@brand-it
Last active October 16, 2018 13:53
Show Gist options
  • Save brand-it/224c77b97e3fa7336602e56a9f3a837c to your computer and use it in GitHub Desktop.
Save brand-it/224c77b97e3fa7336602e56a9f3a837c to your computer and use it in GitHub Desktop.
class Config
attr_accessor :total_threads, :files_per_second, :logger
class << self
def info
@info ||= Config.new
end
end
def initialize
self.total_threads = 30
self.files_per_second = 1600
self.logger = MigrationLogger.new
logger.level = Logger.const_get('INFO')
end
end
$LOAD_PATH.push('/home/deploy/.gem/ruby/2.3.0/gems/ruby-progressbar-1.10.0/lib')
require 'ruby-progressbar'
class EncryptS3Files
attr_reader :s3, :total_threads, :executions_per_second, :config, :logger
attr_accessor :total_number_files_completed, :object_summaries, :started_at
delegate :total_threads, to: :config
delegate :files_per_second, to: :config
def initialize
@s3 = S3Util.new
@config = Config.info
# @semaphore = Mutex.new # don't need this but I keep forget what it is called
self.object_summaries = []
@logger = MigrationLogger.new
@started_at = nil
end
def perform
self.started_at = Time.now.to_i
puts "This is the setup process which downloads all the encryption info and"
puts "bucket details. We don't know all the info yet but once this is"
puts "done it will give us a full brake down of all the assets encription on s3"
puts "This could take some time depending on how many total pages there are in object list api"
get_all_object_summaries
puts 'Getting the encription information from all the object list info second to last step'
get_encription_info
puts 'Starting to encrypt the files'
encrypt_each_files_page
end
private
def progress_bar
return @progress_bar unless @progress_bar.nil? || @progress_bar.finished?
@progress_bar = ProgressBar.create(format: '%E |%B| %p%% Processed: %c from %u"', total: nil)
end
def rate_limit
seconds_passed = (Time.now.to_i - started_at)
return if progress_bar.progress.to_i.zero? || seconds_passed.zero?
while (progress_bar.progress / seconds_passed) > files_per_second
Config.info.logger.debug("Rate Limited (#{progress_bar.total / seconds_passed}) > #{files_per_second} | #{progress_bar.progress} \/ #{seconds_passed}")
sleep 1
seconds_passed = (Time.now.to_i - started_at)
end
end
def encrypt_each_files_page
threaded_loop(object_summaries) do |object|
begin
s3.turn_on_encryption(object.key)
Config.info.logger.info("Encrypted #{object.key}")
rescue StandardError => exception
Config.info.logger.info("encrypt_each_files_page failure: #{exception.message} #{object.key}")
end
end
end
def seahorse_response
s3.bucket.client.list_objects(bucket: s3.bucket.name)
end
def get_all_object_summaries
return if object_summaries.any?
progress_bar.start
seahorse_response.each_page do |page|
threaded_loop(page.data.contents, progress: nil) do |c|
object_summaries << Aws::S3::ObjectSummary.new(
bucket_name: s3.bucket.name,
key: c.key,
data: c,
client: s3.bucket.client
)
end
progress_bar.increment
end
progress_bar.finish
end
def get_encription_info
new_object_summaries = []
threaded_loop(object_summaries) do |summery|
begin
if summery.object.server_side_encryption != 'AES256'
new_object_summaries << summery
end
rescue StandardError => exception
logger.info("get_encription_info failure: #{exception.message} #{summery.key}")
end
end
self.object_summaries = new_object_summaries
end
def already_encrypted?(object_key)
s3.bucket.object(object_key).server_side_encryption == true
end
def threaded_loop(array, progress: progress_bar)
raise "Total threads is not a number/integer #{total_threads.inspect}" unless total_threads.is_a?(Integer)
raise "Array provided does not respond to each_slice - #{array.class}" unless array.respond_to?(:each_slice)
slice_size = array.size / total_threads
threads = []
progress.total = array.size if progress
slice_size = 1 if slice_size.zero?
array.each_slice(slice_size) do |grouped_array|
grouped_array = grouped_array.to_a # this is to insure that it is not using some other object type
threads << Thread.new do
while grouped_array.size > 0
value = grouped_array.pop
rate_limit
yield(value)
progress.increment if progress
end
end
end
threads.each(&:join)
progress.finish if progress
end
end
class MigrationLogger < Logger
def initialize(logdev = nil, shift_age = 0, shift_size = 1_048_576)
logdev ||= Rails.root.join('log', "#{Rails.env}_s3_encryption.log")
super
end
end
class OptParser
class << self
def parse_args
option_parser = OptionParser.new do |opts|
opts.banner = "Usage: #{opts.program_name} [options]"
opts.separator ''
opts.separator 'Specific options:'
opts.on('-t', '--total-thread [Integer]', Integer) do |value|
unless value.to_i > 0
puts "--total-thread is not a valid value. Needs to be greater than zero #{value.inspect}"
puts opts
exit 1
end
puts "Limting Threds to #{value.to_i}"
Config.info.total_threads = value.to_i
end
opts.on('-s', '--files-per-second [Integer]', Integer, 'Total number of files it can loop threw per second') do |value|
unless value.to_i > 0
puts "--files-per-second is not a valid value. Needs to be greater than zero #{value.inspect}"
puts opts
exit 1
end
Config.info.files_per_second = value.to_i
puts "Only Processing #{Config.info.files_per_second} files per second"
end
opts.on('-l', '--log-level [INFO]', String, '[DEBUG|INFO]') do |value|
puts "Setting log level to #{Logger.const_get(value)}"
Config.info.logger.level = Logger.const_get(value)
end
end
option_parser.parse!
end
end
end
require File.expand_path('migration_logger', __dir__).to_s
require File.expand_path('opt_parser', __dir__).to_s
require File.expand_path('encrypt_s3_files', __dir__).to_s
require File.expand_path('config', __dir__).to_s
OptParser.parse_args
EncryptS3Files.new.perform
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment