自分が所有していないバケットから画像をプルする方法

CDCK バケットに多数の画像があるサイトを運営しています。それらの画像をそのバケットから取得し、ローカルサーバーに移動する(その後、別のバケットに移動する)まで、あと少ししか時間がありません。

uploads:analyze_missing_s3 および uploads:fix_missing_s3 の rake タスクでは、約 25,000 枚の画像が別のバケットにあるにもかかわらず、問題は見つかりません。

機能提案: EnsureS3UploadsExistence は、アップロードが正しいバケットにない(つまり、そのバケットへのアクセス許可がない?)アップロードを invalid_etag としてマークするはずですが、そうしません。アップロードが現在のサイトが制御していないバケット、または単にサイトの想定バケットではないバケットにあることを、このジョブまたは他のジョブが認識するのが理にかなっていると思います。

verification_status が何を検証しているのかよくわかりません。良いものを検証しているようには見えません。

試したこと

ups=Upload.where("url like '//discourse-cloud-file-uploads.s3.dualstack.us-west-2.amazonaws.com/business6%'")
ups.update_all(verification_status: 3)

そして

rake uploads:fix_missing_s3

その rake タスクは画像のダウンロードに関するメッセージを出力しましたが、1 日後に投稿がリベイクされたとき、それらの画像は raw では ![zawfI7C|346x500](upload://9L0PqY4QpqLOfexXHMMbv00EgaB.jpeg) のように見えましたが、リンク先は https://test.literatecomputing.com/images/transparent.png でした。

「いいね!」 2

以下のようなアドホック スクリプト:

  1. ファイルを一覧表示する
  2. このリストを wget する
  3. それらをバケットに aws s3 sync する
  4. remap oldbucket newbucket する
「いいね!」 4

やれやれ。その答えになるだろうと思っていたよ。

そして、@RGJ もそうやっているのだろうね。:crying_cat_face:

「いいね!」 1

しかし、ファイルはあなたのバケットにあり、一覧表示には認証情報が必要だと確信しているので、ファイルを一覧表示できません。

rake uploads:fix_missing_s3 は、(ほとんどの?)ファイルをローカル ファイルシステムにプルしたようです(アップロードはまだこのサイトの S3 にはありません)。

そこで、アップロードを修正するためにこれを行いました。

def fix_bad_uploads(bad_uploads)
  fixed = 0
  retrieved = 0
  missing = 0
  bad_bucket="//discourse-cloud-file-uploads.s3.dualstack.us-west-2.amazonaws.com/business6/uploads/forumosa"
  bad_uploads.each do |upload|
    url = URI.parse("https:"+upload.url)
    upload.url=upload.url.gsub(bad_bucket,"/uploads/default")
    if File.exists?("/shared/#{upload.url}")
      fixed += 1
      print "1"
      upload.save
    # posts = Post.where("raw like '%#{upload.short_url}%'")
    # posts.each do |post|
    #   post.rebake!
    #   print "."
    # end
    else
      begin
        # retrieve missing
        filename = "/shared#{upload.url}"
        dirname = File.dirname(filename)
        unless File.directory?(dirname)
          FileUtils.mkdir_p(dirname)
        end
        file = File.new(filename, "w")
        Net::HTTP.start(url.host) do |http|
          resp = http.get(url.path)
          open(file, "wb") do |file|
            file.write(resp.body)
          end
        end
        file.close
        print "+"
        upload.save if File.exists?(filename)
      rescue => e
        puts "bad: #{e}"
        missing += 0
        sleep 1
        print "0"
      end
    end
  end
end

これでほとんど修正されました。しかし、データベースに Upload が存在しない uploads:// エントリを持つ投稿がいくつかあるようです。それらをリベイクすると、transparent.png になります。

そこで、次のようなものを試しました。

def get_missing_short_url(short_url)
  prefix = "https://discourse-cloud-file-uploads.s3.dualstack.us-west-2.amazonaws.com/business6/uploads/forumosa/original/3X"
  remove_url = "https://discourse-cloud-file-uploads.s3.dualstack.us-west-2.amazonaws.com/business6/uploads/forumosa/"
  sha1= Upload.sha1_from_short_url(short_url)
  extension = short_url.split(".").last
  upload = Upload.find_by(sha1: sha1)
  if !upload
    # try to find it in s3
    one = sha1[0]
    two=sha1[1]
    url_link = "#{prefix}/#{one}/#{two}/#{sha1}.#{extension}"
    puts "URL: #{url_link}"
    sleep 1
    url = URI.parse(url_link)
    full_filename = url_link.gsub(remove_url,"/shared/uploads/default/")
    filename = "/tmp/#{File.basename(url_link.gsub(remove_url,"/shared/uploads/default/"))}"
    dirname = File.dirname(filename)
    unless File.directory?(dirname)
      FileUtils.mkdir_p(dirname)
    end
    File.open(filename, "w") do |file|
      Net::HTTP.start(url.host) do |http|
        resp = http.get(url.path)
        open(file, "wb") do |file|
          file.write(resp.body)
        end
      end
    end
      # make upload for file
    File.open(filename, "r") do |file|
      upload = UploadCreator.new(
        file,
        File.basename(file),
      ).create_for(Discourse.system_user.id)
    end
    if upload.persisted?
      puts "We did it! #{upload.id}"
    else
      puts "darn. #{upload.errors.full_messages}"
      sleep 5
    end
    File.open(filename, "w") do |file|
      Net::HTTP.start(url.host) do |http|
        resp = http.get(url.path)
        open(file, "wb") do |file|
          file.write(resp.body)
        end
      end
    end
    end
  upload
end

これはほとんど機能しますが、テストでは、短い URL から推測した SHA から正しい S3 URL を推測できないことが時々あります。その修正方法がわかりません。

また、ファイル名の S3 パスにある SHA とは異なる sha を持つものがいくつかあります。

現在の考えは、まず cooked のすべてを調べ、すべての https://discourse-cloud-file-uploads URL を取得し、次にそれらを参照している Upload レコードを更新し、存在しないものを新規作成することです。

何か明らかなことを見落としていますか?

アップロードテーブルはファイルのリストではありませんか?

私もそう思っていました!しかし、raw には uploads:// がいくつか存在しますが、アップロードテーブルにはエントリがありません(少なくとも Upload.sha1_from_short_url(short_url) によって提供されるshaで検索した場合)。

ほとんど(すべてではない)は、shaからバケットURLを推測できましたが(1X2X3X はよくわかりませんが、すべて3xにあるようです)。

したがって、アップロードテーブルが問題のファイルの完全なリストであるとは限りません。