0
0
Fork 0

Handle CLI failure exit status at the top-level script (#28322)

This commit is contained in:
Matt Jankowski 2024-01-26 03:53:44 -05:00 committed by GitHub
parent 881e8c113c
commit 0e0a94f483
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 150 additions and 320 deletions

View file

@ -39,8 +39,7 @@ module Mastodon::CLI
rotate_keys_for_account(Account.find_local(username))
say('OK', :green)
else
say('No account(s) given', :red)
exit(1)
fail_with_message 'No account(s) given'
end
end
@ -74,10 +73,7 @@ module Mastodon::CLI
if options[:role]
role = UserRole.find_by(name: options[:role])
if role.nil?
say('Cannot find user role with that name', :red)
exit(1)
end
fail_with_message 'Cannot find user role with that name' if role.nil?
role_id = role.id
end
@ -114,7 +110,6 @@ module Mastodon::CLI
say("New password: #{password}")
else
report_errors(user.errors)
exit(1)
end
end
@ -152,18 +147,12 @@ module Mastodon::CLI
def modify(username)
user = Account.find_local(username)&.user
if user.nil?
say('No user with such username', :red)
exit(1)
end
fail_with_message 'No user with such username' if user.nil?
if options[:role]
role = UserRole.find_by(name: options[:role])
if role.nil?
say('Cannot find user role with that name', :red)
exit(1)
end
fail_with_message 'Cannot find user role with that name' if role.nil?
user.role_id = role.id
elsif options[:remove_role]
@ -185,7 +174,6 @@ module Mastodon::CLI
say("New password: #{password}") if options[:reset_password]
else
report_errors(user.errors)
exit(1)
end
end
@ -200,27 +188,19 @@ module Mastodon::CLI
LONG_DESC
def delete(username = nil)
if username.present? && options[:email].present?
say('Use username or --email, not both', :red)
exit(1)
fail_with_message 'Use username or --email, not both'
elsif username.blank? && options[:email].blank?
say('No username provided', :red)
exit(1)
fail_with_message 'No username provided'
end
account = nil
if username.present?
account = Account.find_local(username)
if account.nil?
say('No user with such username', :red)
exit(1)
end
fail_with_message 'No user with such username' if account.nil?
else
account = Account.left_joins(:user).find_by(user: { email: options[:email] })
if account.nil?
say('No user with such email', :red)
exit(1)
end
fail_with_message 'No user with such email' if account.nil?
end
say("Deleting user with #{account.statuses_count} statuses, this might take a while...#{dry_run_mode_suffix}")
@ -243,23 +223,18 @@ module Mastodon::CLI
username, domain = from_acct.split('@')
from_account = Account.find_remote(username, domain)
if from_account.nil? || from_account.local?
say("No such account (#{from_acct})", :red)
exit(1)
end
fail_with_message "No such account (#{from_acct})" if from_account.nil? || from_account.local?
username, domain = to_acct.split('@')
to_account = Account.find_remote(username, domain)
if to_account.nil? || to_account.local?
say("No such account (#{to_acct})", :red)
exit(1)
end
fail_with_message "No such account (#{to_acct})" if to_account.nil? || to_account.local?
if from_account.public_key != to_account.public_key && !options[:force]
say("Accounts don't have the same public key, might not be duplicates!", :red)
say('Override with --force', :red)
exit(1)
fail_with_message <<~ERROR
Accounts don't have the same public key, might not be duplicates!
Override with --force
ERROR
end
to_account.merge_with!(from_account)
@ -298,10 +273,7 @@ module Mastodon::CLI
def backup(username)
account = Account.find_local(username)
if account.nil?
say('No user with such username', :red)
exit(1)
end
fail_with_message 'No user with such username' if account.nil?
backup = account.user.backups.create!
BackupWorker.perform_async(backup.id)
@ -387,10 +359,7 @@ module Mastodon::CLI
user, domain = user.split('@')
account = Account.find_remote(user, domain)
if account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if account.nil?
next if dry_run?
@ -405,8 +374,7 @@ module Mastodon::CLI
say("OK#{dry_run_mode_suffix}", :green)
else
say('No account(s) given', :red)
exit(1)
fail_with_message 'No account(s) given'
end
end
@ -416,10 +384,7 @@ module Mastodon::CLI
def follow(username)
target_account = Account.find_local(username)
if target_account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if target_account.nil?
processed, = parallelize_with_progress(Account.local.without_suspended) do |account|
FollowService.new.call(account, target_account, bypass_limit: true)
@ -435,10 +400,7 @@ module Mastodon::CLI
username, domain = acct.split('@')
target_account = Account.find_remote(username, domain)
if target_account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if target_account.nil?
processed, = parallelize_with_progress(target_account.followers.local) do |account|
UnfollowService.new.call(account, target_account)
@ -459,17 +421,11 @@ module Mastodon::CLI
With the --followers option, the command removes all followers of the account.
LONG_DESC
def reset_relationships(username)
unless options[:follows] || options[:followers]
say('Please specify either --follows or --followers, or both', :red)
exit(1)
end
fail_with_message 'Please specify either --follows or --followers, or both' unless options[:follows] || options[:followers]
account = Account.find_local(username)
if account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if account.nil?
total = 0
total += account.following.reorder(nil).count if options[:follows]
@ -515,6 +471,8 @@ module Mastodon::CLI
account identified by its username.
LONG_DESC
def approve(username = nil)
fail_with_message 'Number must be positive' if options[:number]&.negative?
if options[:all]
User.pending.find_each(&:approve!)
say('OK', :green)
@ -524,16 +482,10 @@ module Mastodon::CLI
elsif username.present?
account = Account.find_local(username)
if account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if account.nil?
account.user&.approve!
say('OK', :green)
else
say('Number must be positive', :red) if options[:number]
exit(1)
end
end
@ -587,56 +539,34 @@ module Mastodon::CLI
redirects to a different account that the one specified.
LONG_DESC
def migrate(username)
if options[:replay].present? && options[:target].present?
say('Use --replay or --target, not both', :red)
exit(1)
end
fail_with_message 'Use --replay or --target, not both' if options[:replay].present? && options[:target].present?
if options[:replay].blank? && options[:target].blank?
say('Use either --replay or --target', :red)
exit(1)
end
fail_with_message 'Use either --replay or --target' if options[:replay].blank? && options[:target].blank?
account = Account.find_local(username)
if account.nil?
say("No such account: #{username}", :red)
exit(1)
end
fail_with_message "No such account: #{username}" if account.nil?
migration = nil
if options[:replay]
migration = account.migrations.last
if migration.nil?
say('The specified account has not performed any migration', :red)
exit(1)
end
fail_with_message 'The specified account has not performed any migration' if migration.nil?
unless options[:force] || migration.target_account_id == account.moved_to_account_id
say('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway', :red)
exit(1)
end
fail_with_message 'The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway' unless options[:force] || migration.target_account_id == account.moved_to_account_id
end
if options[:target]
target_account = ResolveAccountService.new.call(options[:target])
if target_account.nil?
say("The specified target account could not be found: #{options[:target]}", :red)
exit(1)
end
fail_with_message "The specified target account could not be found: #{options[:target]}" if target_account.nil?
unless options[:force] || account.moved_to_account_id.nil? || account.moved_to_account_id == target_account.id
say('The specified account is redirecting to a different target account. Use --force if you want to change the migration target', :red)
exit(1)
end
fail_with_message 'The specified account is redirecting to a different target account. Use --force if you want to change the migration target' unless options[:force] || account.moved_to_account_id.nil? || account.moved_to_account_id == target_account.id
begin
migration = account.migrations.create!(acct: target_account.acct)
rescue ActiveRecord::RecordInvalid => e
say("Error: #{e.message}", :red)
exit(1)
fail_with_message "Error: #{e.message}"
end
end
@ -648,18 +578,18 @@ module Mastodon::CLI
private
def report_errors(errors)
errors.each do |error|
say('Failure/Error: ', :red)
say(error.attribute)
say(" #{error.type}", :red)
end
message = errors.map do |error|
<<~STRING
Failure/Error: #{error.attribute}
#{error.type}
STRING
end.join
fail_with_message message
end
def rotate_keys_for_account(account, delay = 0)
if account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if account.nil?
old_key = account.private_key
new_key = OpenSSL::PKey::RSA.new(2048)

View file

@ -18,6 +18,10 @@ module Mastodon
private
def fail_with_message(message)
raise Thor::Error, message
end
def pastel
@pastel ||= Pastel.new
end

View file

@ -31,8 +31,7 @@ module Mastodon::CLI
recount_status_stats(status)
end
else
say("Unknown type: #{type}", :red)
exit(1)
fail_with_message "Unknown type: #{type}"
end
say

View file

@ -41,11 +41,9 @@ module Mastodon::CLI
# Sanity check on command arguments
if options[:limited_federation_mode] && !domains.empty?
say('DOMAIN parameter not supported with --limited-federation-mode', :red)
exit(1)
fail_with_message 'DOMAIN parameter not supported with --limited-federation-mode'
elsif domains.empty? && !options[:limited_federation_mode]
say('No domain(s) given', :red)
exit(1)
fail_with_message 'No domain(s) given'
end
# Build scopes from command arguments

View file

@ -30,10 +30,7 @@ module Mastodon::CLI
it at the root.
LONG_DESC
def add(*domains)
if domains.empty?
say('No domain(s) given', :red)
exit(1)
end
fail_with_message 'No domain(s) given' if domains.empty?
skipped = 0
processed = 0
@ -76,10 +73,7 @@ module Mastodon::CLI
desc 'remove DOMAIN...', 'Remove e-mail domain blocks'
def remove(*domains)
if domains.empty?
say('No domain(s) given', :red)
exit(1)
end
fail_with_message 'No domain(s) given' if domains.empty?
skipped = 0
processed = 0

View file

@ -86,14 +86,8 @@ module Mastodon::CLI
category = CustomEmojiCategory.find_by(name: options[:category])
export_file_name = File.join(path, 'export.tar.gz')
if File.file?(export_file_name) && !options[:overwrite]
say("Archive already exists! Use '--overwrite' to overwrite it!")
exit 1
end
if category.nil? && options[:category]
say("Unable to find category '#{options[:category]}'!")
exit 1
end
fail_with_message "Archive already exists! Use '--overwrite' to overwrite it!" if File.file?(export_file_name) && !options[:overwrite]
fail_with_message "Unable to find category '#{options[:category]}'!" if category.nil? && options[:category]
File.open(export_file_name, 'wb') do |file|
Zlib::GzipWriter.wrap(file) do |gzip|

View file

@ -43,10 +43,10 @@ module Mastodon::CLI
say('Every deletion notice has been sent! You can safely delete all data and decomission your servers!', :green)
end
exit(0)
raise(SystemExit)
end
exit(1) unless ask('Type in the domain of the server to confirm:') == Rails.configuration.x.local_domain
fail_with_message 'Domains do not match. Stopping self-destruct initiation.' unless domain_match_confirmed?
say(<<~WARNING, :yellow)
This operation WILL NOT be reversible.
@ -54,19 +54,25 @@ module Mastodon::CLI
The deletion process itself may take a long time, and will be handled by Sidekiq, so do not shut it down until it has finished (you will be able to re-run this command to see the state of the self-destruct process).
WARNING
exit(1) if no?('Are you sure you want to proceed?')
fail_with_message 'Operation cancelled. Self-destruct will not begin.' if proceed_prompt_negative?
say(<<~INSTRUCTIONS, :green)
To switch Mastodon to self-destruct mode, add the following variable to your evironment (e.g. by adding a line to your `.env.production`) and restart all Mastodon processes:
SELF_DESTRUCT=#{self_destruct_value}
You can re-run this command to see the state of the self-destruct process.
INSTRUCTIONS
rescue Interrupt
exit(1)
end
private
def domain_match_confirmed?
ask('Type in the domain of the server to confirm:') == Rails.configuration.x.local_domain
end
def proceed_prompt_negative?
no?('Are you sure you want to proceed?')
end
def self_destruct_value
Rails
.application

View file

@ -27,17 +27,13 @@ module Mastodon::CLI
elsif username.present?
account = Account.find_local(username)
if account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if account.nil?
PrecomputeFeedService.new.call(account) unless dry_run?
say("OK #{dry_run_mode_suffix}", :green, true)
else
say('No account(s) given', :red)
exit(1)
fail_with_message 'No account(s) given'
end
end

View file

@ -20,10 +20,7 @@ module Mastodon::CLI
option to overwrite it.
LONG_DESC
def add(*addresses)
if addresses.empty?
say('No IP(s) given', :red)
exit(1)
end
fail_with_message 'No IP(s) given' if addresses.empty?
skipped = 0
processed = 0
@ -70,10 +67,7 @@ module Mastodon::CLI
cover the given IP(s).
LONG_DESC
def remove(*addresses)
if addresses.empty?
say('No IP(s) given', :red)
exit(1)
end
fail_with_message 'No IP(s) given' if addresses.empty?
processed = 0
skipped = 0

View file

@ -199,26 +199,24 @@ module Mastodon::CLI
def verify_schema_version!
if migrator_version < MIN_SUPPORTED_VERSION
say 'Your version of the database schema is too old and is not supported by this script.', :red
say 'Please update to at least Mastodon 3.0.0 before running this script.', :red
exit(1)
fail_with_message <<~ERROR
Your version of the database schema is too old and is not supported by this script.
Please update to at least Mastodon 3.0.0 before running this script.
ERROR
elsif migrator_version > MAX_SUPPORTED_VERSION
say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow
exit(1) unless yes?('Continue anyway? (Yes/No)')
fail_with_message 'Stopping maintenance script because data is more recent than script version.' unless yes?('Continue anyway? (Yes/No)')
end
end
def verify_sidekiq_not_active!
if Sidekiq::ProcessSet.new.any?
say 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.', :red
exit(1)
end
fail_with_message 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.' if Sidekiq::ProcessSet.new.any?
end
def verify_backup_warning!
say 'This task will take a long time to run and is potentially destructive.', :yellow
say 'Please make sure to stop Mastodon and have a backup.', :yellow
exit(1) unless yes?('Continue? (Yes/No)')
fail_with_message 'Maintenance process stopped.' unless yes?('Continue? (Yes/No)')
end
def deduplicate_accounts!

View file

@ -31,15 +31,9 @@ module Mastodon::CLI
following anyone locally are pruned.
DESC
def remove
if options[:prune_profiles] && options[:remove_headers]
say('--prune-profiles and --remove-headers should not be specified simultaneously', :red, true)
exit(1)
end
fail_with_message '--prune-profiles and --remove-headers should not be specified simultaneously' if options[:prune_profiles] && options[:remove_headers]
if options[:include_follows] && !(options[:prune_profiles] || options[:remove_headers])
say('--include-follows can only be used with --prune-profiles or --remove-headers', :red, true)
exit(1)
end
fail_with_message '--include-follows can only be used with --prune-profiles or --remove-headers' if options[:include_follows] && !(options[:prune_profiles] || options[:remove_headers])
time_ago = options[:days].days.ago
if options[:prune_profiles] || options[:remove_headers]
@ -156,11 +150,9 @@ module Mastodon::CLI
end
end
when :fog
say('The fog storage driver is not supported for this operation at this time', :red)
exit(1)
fail_with_message 'The fog storage driver is not supported for this operation at this time'
when :azure
say('The azure storage driver is not supported for this operation at this time', :red)
exit(1)
fail_with_message 'The azure storage driver is not supported for this operation at this time'
when :filesystem
require 'find'
@ -254,10 +246,7 @@ module Mastodon::CLI
username, domain = options[:account].split('@')
account = Account.find_remote(username, domain)
if account.nil?
say('No such account', :red)
exit(1)
end
fail_with_message 'No such account' if account.nil?
scope = MediaAttachment.where(account_id: account.id)
elsif options[:domain]
@ -265,8 +254,7 @@ module Mastodon::CLI
elsif options[:days].present?
scope = MediaAttachment.remote
else
say('Specify the source of media attachments', :red)
exit(1)
fail_with_message 'Specify the source of media attachments'
end
scope = scope.where('media_attachments.id > ?', Mastodon::Snowflake.id_at(options[:days].days.ago, with_random: false)) if options[:days].present?
@ -306,38 +294,25 @@ module Mastodon::CLI
path_segments = path.split('/')[2..]
path_segments.delete('cache')
unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
say('Not a media URL', :red)
exit(1)
end
fail_with_message 'Not a media URL' unless VALID_PATH_SEGMENTS_SIZE.include?(path_segments.size)
model_name = path_segments.first.classify
record_id = path_segments[2..-2].join.to_i
unless PRELOAD_MODEL_WHITELIST.include?(model_name)
say("Cannot find corresponding model: #{model_name}", :red)
exit(1)
end
fail_with_message "Cannot find corresponding model: #{model_name}" unless PRELOAD_MODEL_WHITELIST.include?(model_name)
record = model_name.constantize.find_by(id: record_id)
record = record.status if record.respond_to?(:status)
unless record
say('Cannot find corresponding record', :red)
exit(1)
end
fail_with_message 'Cannot find corresponding record' unless record
display_url = ActivityPub::TagManager.instance.url_for(record)
if display_url.blank?
say('No public URL for this type of record', :red)
exit(1)
end
fail_with_message 'No public URL for this type of record' if display_url.blank?
say(display_url, :blue)
rescue Addressable::URI::InvalidURIError
say('Invalid URL', :red)
exit(1)
fail_with_message 'Invalid URL'
end
private

View file

@ -25,10 +25,7 @@ module Mastodon::CLI
end
def parallelize_with_progress(scope)
if options[:concurrency] < 1
say('Cannot run with this concurrency setting, must be at least 1', :red)
exit(1)
end
fail_with_message 'Cannot run with this concurrency setting, must be at least 1' if options[:concurrency] < 1
reset_connection_pools!

View file

@ -110,17 +110,11 @@ module Mastodon::CLI
end
def verify_deploy_concurrency!
return unless options[:concurrency] < 1
say('Cannot run with this concurrency setting, must be at least 1', :red)
exit(1)
fail_with_message 'Cannot run with this concurrency setting, must be at least 1' if options[:concurrency] < 1
end
def verify_deploy_batch_size!
return unless options[:batch_size] < 1
say('Cannot run with this batch_size setting, must be at least 1', :red)
exit(1)
fail_with_message 'Cannot run with this batch_size setting, must be at least 1' if options[:batch_size] < 1
end
def progress_output_options

View file

@ -26,10 +26,7 @@ module Mastodon::CLI
indices before commencing, and removes them afterward.
LONG_DESC
def remove
if options[:batch_size] < 1
say('Cannot run with this batch_size setting, must be at least 1', :red)
exit(1)
end
fail_with_message 'Cannot run with this batch_size setting, must be at least 1' if options[:batch_size] < 1
remove_statuses
vacuum_and_analyze_statuses

View file

@ -103,13 +103,11 @@ module Mastodon::CLI
end
def upgrade_storage_fog(_progress, _attachment, _style)
say('The fog storage driver is not supported for this operation at this time', :red)
exit(1)
fail_with_message 'The fog storage driver is not supported for this operation at this time'
end
def upgrade_storage_azure(_progress, _attachment, _style)
say('The azure storage driver is not supported for this operation at this time', :red)
exit(1)
fail_with_message 'The azure storage driver is not supported for this operation at this time'
end
def upgrade_storage_filesystem(progress, attachment, style)