You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

163 lines
5.2 KiB

  1. # frozen_string_literal: true
  2. require_relative '../../config/boot'
  3. require_relative '../../config/environment'
  4. require_relative 'cli_helper'
  5. module Mastodon
  6. class UpgradeCLI < Thor
  7. include CLIHelper
  8. def self.exit_on_failure?
  9. true
  10. end
  11. CURRENT_STORAGE_SCHEMA_VERSION = 1
  12. option :dry_run, type: :boolean, default: false
  13. option :verbose, type: :boolean, default: false, aliases: [:v]
  14. desc 'storage-schema', 'Upgrade storage schema of various file attachments to the latest version'
  15. long_desc <<~LONG_DESC
  16. Iterates over every file attachment of every record and, if its storage schema is outdated, performs the
  17. necessary upgrade to the latest one. In practice this means e.g. moving files to different directories.
  18. Will most likely take a long time.
  19. LONG_DESC
  20. def storage_schema
  21. progress = create_progress_bar(nil)
  22. dry_run = dry_run? ? ' (DRY RUN)' : ''
  23. records = 0
  24. klasses = [
  25. Account,
  26. CustomEmoji,
  27. MediaAttachment,
  28. PreviewCard,
  29. ]
  30. klasses.each do |klass|
  31. attachment_names = klass.attachment_definitions.keys
  32. klass.find_each do |record|
  33. attachment_names.each do |attachment_name|
  34. attachment = record.public_send(attachment_name)
  35. upgraded = false
  36. next if attachment.blank? || attachment.storage_schema_version >= CURRENT_STORAGE_SCHEMA_VERSION
  37. styles = attachment.styles.keys
  38. styles << :original unless styles.include?(:original)
  39. styles.each do |style|
  40. success = begin
  41. case Paperclip::Attachment.default_options[:storage]
  42. when :s3
  43. upgrade_storage_s3(progress, attachment, style)
  44. when :fog
  45. upgrade_storage_fog(progress, attachment, style)
  46. when :filesystem
  47. upgrade_storage_filesystem(progress, attachment, style)
  48. end
  49. end
  50. upgraded = true if style == :original && success
  51. progress.increment
  52. end
  53. attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION) if upgraded
  54. end
  55. if record.changed?
  56. record.save unless dry_run?
  57. records += 1
  58. end
  59. end
  60. end
  61. progress.total = progress.progress
  62. progress.finish
  63. say("Upgraded storage schema of #{records} records#{dry_run}", :green, true)
  64. end
  65. private
  66. def upgrade_storage_s3(progress, attachment, style)
  67. previous_storage_schema_version = attachment.storage_schema_version
  68. object = attachment.s3_object(style)
  69. success = true
  70. attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
  71. new_object = attachment.s3_object(style)
  72. if new_object.key != object.key && object.exists?
  73. progress.log("Moving #{object.key} to #{new_object.key}") if options[:verbose]
  74. begin
  75. object.move_to(new_object, acl: attachment.s3_permissions(style)) unless dry_run?
  76. rescue => e
  77. progress.log(pastel.red("Error processing #{object.key}: #{e}"))
  78. success = false
  79. end
  80. end
  81. # Because we move files style-by-style, it's important to restore
  82. # previous version at the end. The upgrade will be recorded after
  83. # all styles are updated
  84. attachment.instance_write(:storage_schema_version, previous_storage_schema_version)
  85. success
  86. end
  87. def upgrade_storage_fog(_progress, _attachment, _style)
  88. say('The fog storage driver is not supported for this operation at this time', :red)
  89. exit(1)
  90. end
  91. def upgrade_storage_filesystem(progress, attachment, style)
  92. previous_storage_schema_version = attachment.storage_schema_version
  93. previous_path = attachment.path(style)
  94. success = true
  95. attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
  96. upgraded_path = attachment.path(style)
  97. if upgraded_path != previous_path && File.exist?(previous_path)
  98. progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose]
  99. begin
  100. unless dry_run?
  101. FileUtils.mkdir_p(File.dirname(upgraded_path))
  102. FileUtils.mv(previous_path, upgraded_path)
  103. begin
  104. FileUtils.rmdir(File.dirname(previous_path), parents: true)
  105. rescue Errno::ENOTEMPTY
  106. # OK
  107. end
  108. end
  109. rescue => e
  110. progress.log(pastel.red("Error processing #{previous_path}: #{e}"))
  111. success = false
  112. unless dry_run?
  113. begin
  114. FileUtils.rmdir(File.dirname(upgraded_path), parents: true)
  115. rescue Errno::ENOTEMPTY
  116. # OK
  117. end
  118. end
  119. end
  120. end
  121. # Because we move files style-by-style, it's important to restore
  122. # previous version at the end. The upgrade will be recorded after
  123. # all styles are updated
  124. attachment.instance_write(:storage_schema_version, previous_storage_schema_version)
  125. success
  126. end
  127. end
  128. end