From a57e72858f8cd723fd30ae8ad70f15077cc52000 Mon Sep 17 00:00:00 2001 From: Mikhail Titov Date: Fri, 12 Oct 2018 22:50:20 -0500 Subject: [PATCH] Google Cloud Storage uploader --- README.md | 35 +++++++++++++- capybara-screenshot.gemspec | 1 + lib/capybara-screenshot.rb | 9 ++++ lib/capybara-screenshot/gcs_saver.rb | 70 ++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 lib/capybara-screenshot/gcs_saver.rb diff --git a/README.md b/README.md index ddd8bb1..5b9a9bd 100644 --- a/README.md +++ b/README.md @@ -201,10 +201,12 @@ end ``` -Uploading screenshots to S3 +Uploading screenshots to AWS S3 or Google Cloud Storage -------------------------- -You can configure capybara-screenshot to automatically save your screenshots to an AWS S3 bucket. +You can configure capybara-screenshot to automatically save your screenshots to either AWS S3 +or Google Cloud Storage bucket but noth both. +### AWS S3 First, install the `aws-sdk-s3` gem or add it to your Gemfile ```ruby @@ -243,6 +245,35 @@ Capybara::Screenshot.s3_configuration = { } ``` +### GCS + +Google Cloud Storage configuration is very simiar to that of S3. +Install the `google-cloud-storage` gem or add it to your Gemfile. + +Next, configure capybara-screenshot with your GCS credentials either stored in a +[JSON file](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) +or as a Hash, and the bucket to save to. +Note that one may use [environment variables to set up credentials](https://googleapis.github.io/google-cloud-ruby/docs/google-cloud-storage/latest/file.AUTHENTICATION) as well. + +```ruby +Capybara::Screenshot.gcs_configuration = { + credentials: 'path-to-credentials.json', + bucket_name: 'my_screenshots', + key_prefix: "some/folder/" +} +``` + +It is also possible to specify object metadata. +If gzip content encoding is specified, uploaded files will be compressed for you to save on storage space. +Configure the capybara-screenshot with these options in this way: + +```ruby +Capybara::Screenshot.gcs_object_configuration = { + content_encoding: 'gzip' +} +``` + + Pruning old screenshots automatically -------------------------- By default screenshots are saved indefinitely, if you want them to be automatically pruned on a new failure, then you can specify one of the following prune strategies as follows: diff --git a/capybara-screenshot.gemspec b/capybara-screenshot.gemspec index 0db5fae..90987da 100644 --- a/capybara-screenshot.gemspec +++ b/capybara-screenshot.gemspec @@ -34,6 +34,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'spinach' s.add_development_dependency 'minitest' s.add_development_dependency 'aws-sdk-s3' + s.add_development_dependency 'google-cloud-storage' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") diff --git a/lib/capybara-screenshot.rb b/lib/capybara-screenshot.rb index fda5ce3..e19e74f 100644 --- a/lib/capybara-screenshot.rb +++ b/lib/capybara-screenshot.rb @@ -11,6 +11,8 @@ class << self attr_accessor :prune_strategy attr_accessor :s3_configuration attr_accessor :s3_object_configuration + attr_accessor :gcs_configuration + attr_accessor :gcs_object_configuration end self.autosave_on_failure = true @@ -22,6 +24,8 @@ class << self self.prune_strategy = :keep_all self.s3_configuration = {} self.s3_object_configuration = {} + self.gcs_configuration = {} + self.gcs_object_configuration = {} def self.append_screenshot_path=(value) $stderr.puts "WARNING: Capybara::Screenshot.append_screenshot_path is deprecated. " + @@ -102,6 +106,11 @@ def self.new_saver(*args) saver = S3Saver.new_with_configuration(saver, s3_configuration, s3_object_configuration) end + unless gcs_configuration.empty? + require 'capybara-screenshot/gcs_saver' + saver = GcsSaver.new_with_configuration(saver, gcs_configuration, gcs_object_configuration) + end + return saver end diff --git a/lib/capybara-screenshot/gcs_saver.rb b/lib/capybara-screenshot/gcs_saver.rb new file mode 100644 index 0000000..b0cedd6 --- /dev/null +++ b/lib/capybara-screenshot/gcs_saver.rb @@ -0,0 +1,70 @@ +require 'zlib' +require 'google/cloud/storage' + +module Capybara + module Screenshot + class GcsSaver + attr_accessor :html_path, :screenshot_path + + def initialize(saver, bucket, object_configuration, key_prefix) + @saver = saver + @bucket = bucket + @key_prefix = key_prefix + @object_configuration = object_configuration + end + + def self.new_with_configuration(saver, configuration, object_configuration) + conf = configuration.dup + bucket_name = conf.delete(:bucket_name) + key_prefix = conf.delete(:key_prefix) + storage = Google::Cloud::Storage.new conf + bucket = storage.bucket bucket_name, skip_lookup: true + + new(saver, bucket, object_configuration, key_prefix) + rescue KeyError + raise "Invalid GCS Configuration #{configuration}. Please refer to the documentation for the necessary configurations." + end + + def save_and_upload_screenshot + save_and do |type, local_file_path| + if object_configuration.fetch(:content_encoding, '').to_sym.eql?(:gzip) + compressed = StringIO.new "" + gz = Zlib::GzipWriter.new(compressed, Zlib::BEST_COMPRESSION) + gz.mtime = File.mtime(local_file_path) + gz.orig_name = local_file_path + gz.write IO.binread(local_file_path) + gz.close + data = StringIO.new compressed.string + else + data = local_file_path + end + gcs_upload_path = "#{@key_prefix}#{File.basename(local_file_path)}" + bucket.create_file data, gcs_upload_path, object_configuration + send("#{type}_path=", "https://storage.cloud.google.com/#{bucket.name}/#{gcs_upload_path}") + end + end + alias_method :save, :save_and_upload_screenshot + + def method_missing(method, *args) + # Need to use @saver instead of S3Saver#saver attr_reader method because + # using the method goes into infinite loop. Maybe attr_reader implements + # its methods via method_missing? + @saver.send(method, *args) + end + + private + attr_reader :saver, + :gcs_client, + :bucket, + :object_configuration + :key_prefix + + def save_and + saver.save + + yield(:html, saver.html_path) if block_given? && saver.html_saved? + yield(:screenshot, saver.screenshot_path) if block_given? && saver.screenshot_saved? + end + end + end +end