patorashのブログ

方向性はまだない

aws-sdk-s3を使いつつ、timecopで時間をずらす

ストレージを伴うテストのためにminioを使うようにしようとしたのですが、設定をしただけではうまく動きませんでした。

timecopを使っているところで、aws-sdkがエラーを起こしました。timecopは時間を固定したり過去・未来に移動したりするライブラリです。

https://github.com/travisjeffery/timecop

エラーメッセージは以下のようなものでした。

Aws::S3::Errors::RequestTimeTooSkewed: The difference between the request time and the server's time is too large.

このメッセージでググるとわかるのが、アプリケーションサーバとS3のサーバ(minio)の間で時刻が離れすぎているから起きる例外でした。timecopで3年前とかに移動しているため、当然起きました…😩

解決方法

解決方法は、aws-sdk-s3から時刻の確認をするときだけ、Time.nowが現在の時刻を返せばいいというアプローチでした。

参考にしたのは以下のページです。

qiita.com

このページの、例外に備えよう、という見出しのところに書いてあります。

まんまでは動かない

しかし、記事自体が3年前のものなためか、aws-sdkのバージョンが変わり、まんまでは動かなくなっていました。

# aws-sdk-s3では動かないので注意
class Time
  class << self
    def now_wrap
      if (caller || []).first.match('aws-sdk')
        now_without_mock_time # return real time
      else
        mock_time || now_without_mock_time # return mock time or real time
      end
    end
    alias_method :now, :now_wrap
  end
end

修正

置き換えなければならない箇所の呼び出し元のパス情報が、aws-sdkではなく、aws-sigを含むものに変わっていたので、修正しました。

class Time
  class << self
    def now_wrap
      # p caller.first if caller.first.match('aws') # awsという文字列でなんとなくあたりを付けた
      # すると、aws-sigというものを発見
      if (caller || []).first.match('aws-sig') # aws-sdkをaws-sigに変更
        now_without_mock_time # return real time
      else
        mock_time || now_without_mock_time # return mock time or real time
      end
    end
    alias_method :now, :now_wrap
  end
end

修正後、テストを実行したところ、timecopを使っている箇所でもテストが成功しました👍

他に試したこと

最初は、エラーメッセージから、アプリケーションサーバとS3サーバの時刻の比較をしないで済むオプションがあるのではないか?と考え、S3クライアントにそれっぽいものを設定してみたのですが、結局ダメでした。

s3_client = Aws::S3::Client.new(
    access_key_id: Rails.application.credentials.aws_access_key_id,
    secret_access_key: Rails.application.credentials.aws_secret_access_key,
    region: Rails.application.credentials.aws_region,
    correct_clock_skew: false, # default true
    retry_mode: 'standard', # default 'legacy'
)