소유하지 않는 버킷에서 이미지를 가져오는 방법

CDCK 버킷에 이미지들이 많이 있는 사이트가 있습니다. 해당 버킷에서 이미지를 가져와 로컬 서버로 이동한 후(그리고 다른 버킷으로 이동) 아직 조금 더 시간이 남아 있습니다.

uploads:analyze_missing_s3와 uploads:fix_missing_s3 레이크 태스크는 약 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 

해당 레이크 태스크는 이미지 다운로드에 대한 메시지를 출력했지만, 하루 후 게시물이 다시 베이킹되었을 때 raw에서는 ![zawfI7C|346x500](upload://9L0PqY4QpqLOfexXHMMbv00EgaB.jpeg)처럼 보이지만, 링크는 https://test.literatecomputing.com/images/transparent.png으로 가리키고 있습니다.

아래와 같은 임시 스크립트를 작성해 보세요:

  1. 파일 목록을 나열합니다.
  2. wget로 해당 목록을 다운로드합니다.
  3. aws s3 sync를 사용해 버킷으로 파일을 동기화합니다.
  4. remap oldbucket newbucket을 실행합니다.

아, 짜증나. 그게 답일지도 모른다고 걱정했었거든.

그리고 @RGJ 도 그렇게 하는 것 같아. :crying_cat_face:

하지만 파일들은 상대방의 버킷에 있고, 목록을 가져오려면 자격 증명(credentials)이 필요할 것이라고 확신하기 때문에 파일 목록을 표시할 수 없습니다.

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:// 항목이 있는 게시물이 몇 개 있는 것 같습니다. 그런 게시물들을 다시 생성(rebake)하면 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

이것은 대부분 잘 작동했지만, 테스트 중에 short URL에서 추론한 sha로 올바른 S3 URL을 추론하지 못하는 경우가 간혹 있었습니다. 어떻게 수정해야 할지 모르겠습니다.

또한, 그중 하나는 s3 경로 파일명 속의 sha와 다른 sha가 된 것으로 보입니다.

현재 제 생각은 cooked 필드를 모두 순회하여 https://discourse-cloud-file-uploads URL을 모두 가져온 다음, 그것들을 참조하는 Upload 레코드를 업데이트하고 누락된 것을 생성하는 방식으로 시작하는 것입니다.

제가 뭘 놓치고 있는 건가요?

업로드 테이블이 파일 목록이 아닌가요?

저도 그렇게 생각했습니다! 하지만 raw에는 존재하지만 uploads 테이블에 항목이 없는 uploads://가 몇 가지 있습니다(적어도 Upload.sha1_from_short_url(short_url)가 제공하는 sha로 검색했을 때 그렇습니다).

대부분의 경우, 일부는 제외하고, sha에서 버킷 URL을 추론할 수 있었습니다(1X, 2X, 3X는 정확히 이해하지는 못했지만, 모두 3x 라인에 속하는 것 같습니다).

따라서, uploads 테이블이 해당 파일의 완전한 목록은 아닙니다.