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

pull/28912/head
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

@ -96,12 +96,11 @@ Rails/FilePath:
Rails/HttpStatus: Rails/HttpStatus:
EnforcedStyle: numeric EnforcedStyle: numeric
# Reason: Allowed in `tootctl` CLI code and in boot ENV checker # Reason: Allowed in boot ENV checker
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsexit # https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsexit
Rails/Exit: Rails/Exit:
Exclude: Exclude:
- 'config/boot.rb' - 'config/boot.rb'
- 'lib/mastodon/cli/*.rb'
# Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions # Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter # https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter

View File

@ -6,8 +6,13 @@ require_relative '../lib/mastodon/cli/main'
begin begin
Chewy.strategy(:mastodon) do Chewy.strategy(:mastodon) do
Mastodon::CLI::Main.start(ARGV) Mastodon::CLI::Main.start(ARGV, debug: true) # Enables the script to rescue `Thor::Error`
end end
rescue Thor::Error => e
Thor::Shell::Color
.new
.say_error(e.message, :red)
exit(1)
rescue Interrupt rescue Interrupt
exit(130) exit(130)
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -86,14 +86,8 @@ module Mastodon::CLI
category = CustomEmojiCategory.find_by(name: options[:category]) category = CustomEmojiCategory.find_by(name: options[:category])
export_file_name = File.join(path, 'export.tar.gz') export_file_name = File.join(path, 'export.tar.gz')
if File.file?(export_file_name) && !options[:overwrite] fail_with_message "Archive already exists! Use '--overwrite' to overwrite it!" if File.file?(export_file_name) && !options[:overwrite]
say("Archive already exists! Use '--overwrite' to overwrite it!") fail_with_message "Unable to find category '#{options[:category]}'!" if category.nil? && options[:category]
exit 1
end
if category.nil? && options[:category]
say("Unable to find category '#{options[:category]}'!")
exit 1
end
File.open(export_file_name, 'wb') do |file| File.open(export_file_name, 'wb') do |file|
Zlib::GzipWriter.wrap(file) do |gzip| 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) say('Every deletion notice has been sent! You can safely delete all data and decomission your servers!', :green)
end end
exit(0) raise(SystemExit)
end 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) say(<<~WARNING, :yellow)
This operation WILL NOT be reversible. 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). 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 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) 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: 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} SELF_DESTRUCT=#{self_destruct_value}
You can re-run this command to see the state of the self-destruct process. You can re-run this command to see the state of the self-destruct process.
INSTRUCTIONS INSTRUCTIONS
rescue Interrupt
exit(1)
end end
private 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 def self_destruct_value
Rails Rails
.application .application

View File

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

View File

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

View File

@ -199,26 +199,24 @@ module Mastodon::CLI
def verify_schema_version! def verify_schema_version!
if migrator_version < MIN_SUPPORTED_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 fail_with_message <<~ERROR
say 'Please update to at least Mastodon 3.0.0 before running this script.', :red Your version of the database schema is too old and is not supported by this script.
exit(1) Please update to at least Mastodon 3.0.0 before running this script.
ERROR
elsif migrator_version > MAX_SUPPORTED_VERSION 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 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
end end
def verify_sidekiq_not_active! def verify_sidekiq_not_active!
if Sidekiq::ProcessSet.new.any? fail_with_message 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.' 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
end end
def verify_backup_warning! def verify_backup_warning!
say 'This task will take a long time to run and is potentially destructive.', :yellow 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 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 end
def deduplicate_accounts! def deduplicate_accounts!

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -65,8 +65,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('Failure/Error: email') .to raise_error(Thor::Error, %r{Failure/Error: email})
.and raise_error(SystemExit)
end end
end end
end end
@ -127,8 +126,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating the role name was not found' do it 'exits with an error message indicating the role name was not found' do
expect { subject } expect { subject }
.to output_results('Cannot find user role with that name') .to raise_error(Thor::Error, 'Cannot find user role with that name')
.and raise_error(SystemExit)
end end
end end
end end
@ -191,8 +189,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating the user was not found' do it 'exits with an error message indicating the user was not found' do
expect { subject } expect { subject }
.to output_results('No user with such username') .to raise_error(Thor::Error, 'No user with such username')
.and raise_error(SystemExit)
end end
end end
@ -214,8 +211,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating the role was not found' do it 'exits with an error message indicating the role was not found' do
expect { subject } expect { subject }
.to output_results('Cannot find user role with that name') .to raise_error(Thor::Error, 'Cannot find user role with that name')
.and raise_error(SystemExit)
end end
end end
@ -364,8 +360,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('Failure/Error: email') .to raise_error(Thor::Error, %r{Failure/Error: email})
.and raise_error(SystemExit)
end end
end end
end end
@ -387,16 +382,14 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that only one should be used' do it 'exits with an error message indicating that only one should be used' do
expect { subject } expect { subject }
.to output_results('Use username or --email, not both') .to raise_error(Thor::Error, 'Use username or --email, not both')
.and raise_error(SystemExit)
end end
end end
context 'when neither username nor --email are provided' do context 'when neither username nor --email are provided' do
it 'exits with an error message indicating that no username was provided' do it 'exits with an error message indicating that no username was provided' do
expect { subject } expect { subject }
.to output_results('No username provided') .to raise_error(Thor::Error, 'No username provided')
.and raise_error(SystemExit)
end end
end end
@ -425,8 +418,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that no user was found' do it 'exits with an error message indicating that no user was found' do
expect { subject } expect { subject }
.to output_results('No user with such username') .to raise_error(Thor::Error, 'No user with such username')
.and raise_error(SystemExit)
end end
end end
end end
@ -458,8 +450,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that no user was found' do it 'exits with an error message indicating that no user was found' do
expect { subject } expect { subject }
.to output_results('No user with such email') .to raise_error(Thor::Error, 'No user with such email')
.and raise_error(SystemExit)
end end
end end
end end
@ -511,8 +502,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that the number must be positive' do it 'exits with an error message indicating that the number must be positive' do
expect { subject } expect { subject }
.to output_results('Number must be positive') .to raise_error(Thor::Error, 'Number must be positive')
.and raise_error(SystemExit)
end end
end end
@ -545,8 +535,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that no such account was found' do it 'exits with an error message indicating that no such account was found' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
end end
@ -560,8 +549,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that no account with the given username was found' do it 'exits with an error message indicating that no account with the given username was found' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
@ -596,8 +584,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that no account with the given username was found' do it 'exits with an error message indicating that no account with the given username was found' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
@ -634,8 +621,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that there is no such account' do it 'exits with an error message indicating that there is no such account' do
expect { subject } expect { subject }
.to output_results('No user with such username') .to raise_error(Thor::Error, 'No user with such username')
.and raise_error(SystemExit)
end end
end end
@ -795,8 +781,7 @@ describe Mastodon::CLI::Accounts do
allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil) allow(Account).to receive(:find_remote).with(account_example_com_b.username, account_example_com_b.domain).and_return(nil)
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
@ -892,8 +877,7 @@ describe Mastodon::CLI::Accounts do
context 'when neither a list of accts nor options are provided' do context 'when neither a list of accts nor options are provided' do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('No account(s) given') .to raise_error(Thor::Error, 'No account(s) given')
.and raise_error(SystemExit)
end end
end end
end end
@ -904,8 +888,7 @@ describe Mastodon::CLI::Accounts do
context 'when neither username nor --all option are given' do context 'when neither username nor --all option are given' do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('No account(s) given') .to raise_error(Thor::Error, 'No account(s) given')
.and raise_error(SystemExit)
end end
end end
@ -940,8 +923,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message when the specified username is not found' do it 'exits with an error message when the specified username is not found' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
@ -980,8 +962,7 @@ describe Mastodon::CLI::Accounts do
shared_examples 'an account not found' do |acct| shared_examples 'an account not found' do |acct|
it 'exits with an error message indicating that there is no such account' do it 'exits with an error message indicating that there is no such account' do
expect { subject } expect { subject }
.to output_results("No such account (#{acct})") .to raise_error(Thor::Error, "No such account (#{acct})")
.and raise_error(SystemExit)
end end
end end
@ -1031,8 +1012,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that the accounts do not have the same pub key' do it 'exits with an error message indicating that the accounts do not have the same pub key' do
expect { subject } expect { subject }
.to output_results("Accounts don't have the same public key, might not be duplicates!\nOverride with --force") .to raise_error(Thor::Error, "Accounts don't have the same public key, might not be duplicates!\nOverride with --force\n")
.and raise_error(SystemExit)
end end
context 'with --force option' do context 'with --force option' do
@ -1200,8 +1180,7 @@ describe Mastodon::CLI::Accounts do
context 'when no option is given' do context 'when no option is given' do
it 'exits with an error message indicating that at least one option is required' do it 'exits with an error message indicating that at least one option is required' do
expect { subject } expect { subject }
.to output_results('Please specify either --follows or --followers, or both') .to raise_error(Thor::Error, 'Please specify either --follows or --followers, or both')
.and raise_error(SystemExit)
end end
end end
@ -1211,8 +1190,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that there is no such account' do it 'exits with an error message indicating that there is no such account' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
@ -1368,16 +1346,14 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that using both options is not possible' do it 'exits with an error message indicating that using both options is not possible' do
expect { subject } expect { subject }
.to output_results('Use --replay or --target, not both') .to raise_error(Thor::Error, 'Use --replay or --target, not both')
.and raise_error(SystemExit)
end end
end end
context 'when no option is given' do context 'when no option is given' do
it 'exits with an error message indicating that at least one option must be used' do it 'exits with an error message indicating that at least one option must be used' do
expect { subject } expect { subject }
.to output_results('Use either --replay or --target') .to raise_error(Thor::Error, 'Use either --replay or --target')
.and raise_error(SystemExit)
end end
end end
@ -1387,8 +1363,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that there is no such account' do it 'exits with an error message indicating that there is no such account' do
expect { subject } expect { subject }
.to output_results("No such account: #{arguments.first}") .to raise_error(Thor::Error, "No such account: #{arguments.first}")
.and raise_error(SystemExit)
end end
end end
@ -1398,8 +1373,7 @@ describe Mastodon::CLI::Accounts do
context 'when the specified account has no previous migrations' do context 'when the specified account has no previous migrations' do
it 'exits with an error message indicating that the given account has no previous migrations' do it 'exits with an error message indicating that the given account has no previous migrations' do
expect { subject } expect { subject }
.to output_results('The specified account has not performed any migration') .to raise_error(Thor::Error, 'The specified account has not performed any migration')
.and raise_error(SystemExit)
end end
end end
@ -1421,8 +1395,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway') .to raise_error(Thor::Error, 'The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway')
.and raise_error(SystemExit)
end end
end end
@ -1449,8 +1422,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message indicating that there is no such account' do it 'exits with an error message indicating that there is no such account' do
expect { subject } expect { subject }
.to output_results("The specified target account could not be found: #{options[:target]}") .to raise_error(Thor::Error, "The specified target account could not be found: #{options[:target]}")
.and raise_error(SystemExit)
end end
end end
@ -1474,8 +1446,7 @@ describe Mastodon::CLI::Accounts do
context 'when the migration record is invalid' do context 'when the migration record is invalid' do
it 'exits with an error indicating that the validation failed' do it 'exits with an error indicating that the validation failed' do
expect { subject } expect { subject }
.to output_results('Error: Validation failed') .to raise_error(Thor::Error, /Error: Validation failed/)
.and raise_error(SystemExit)
end end
end end
@ -1486,8 +1457,7 @@ describe Mastodon::CLI::Accounts do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('The specified account is redirecting to a different target account. Use --force if you want to change the migration target') .to raise_error(Thor::Error, 'The specified account is redirecting to a different target account. Use --force if you want to change the migration target')
.and raise_error(SystemExit)
end end
end end

View File

@ -64,8 +64,7 @@ describe Mastodon::CLI::Cache do
it 'Exits with an error message' do it 'Exits with an error message' do
expect { subject } expect { subject }
.to output_results('Unknown') .to raise_error(Thor::Error, /Unknown/)
.and raise_error(SystemExit)
end end
end end
end end

View File

@ -35,8 +35,7 @@ describe Mastodon::CLI::EmailDomainBlocks do
context 'without any options' do context 'without any options' do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('No domain(s) given') .to raise_error(Thor::Error, 'No domain(s) given')
.and raise_error(SystemExit)
end end
end end
@ -72,8 +71,7 @@ describe Mastodon::CLI::EmailDomainBlocks do
context 'without any options' do context 'without any options' do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('No domain(s) given') .to raise_error(Thor::Error, 'No domain(s) given')
.and raise_error(SystemExit)
end end
end end

View File

@ -42,8 +42,7 @@ describe Mastodon::CLI::Feeds do
it 'displays an error and exits' do it 'displays an error and exits' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
end end

View File

@ -144,8 +144,7 @@ describe Mastodon::CLI::IpBlocks do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('No IP(s) given') .to raise_error(Thor::Error, 'No IP(s) given')
.and raise_error(SystemExit)
end end
end end
end end
@ -235,8 +234,7 @@ describe Mastodon::CLI::IpBlocks do
context 'when no IP address is provided' do context 'when no IP address is provided' do
it 'exits with an error message' do it 'exits with an error message' do
expect { subject } expect { subject }
.to output_results('No IP(s) given') .to raise_error(Thor::Error, 'No IP(s) given')
.and raise_error(SystemExit)
end end
end end
end end

View File

@ -104,9 +104,9 @@ describe Mastodon::CLI::Main do
answer_hostname_incorrectly answer_hostname_incorrectly
end end
it 'exits silently' do it 'exits with mismatch error message' do
expect { subject } expect { subject }
.to raise_error(SystemExit) .to raise_error(Thor::Error, /Domains do not match/)
end end
end end
@ -119,7 +119,7 @@ describe Mastodon::CLI::Main do
it 'passes first step but stops before instructions' do it 'passes first step but stops before instructions' do
expect { subject } expect { subject }
.to output_results('operation WILL NOT') .to output_results('operation WILL NOT')
.and raise_error(SystemExit) .and raise_error(Thor::Error, /Self-destruct will not begin/)
end end
end end

View File

@ -22,8 +22,7 @@ describe Mastodon::CLI::Maintenance do
it 'Exits with error message' do it 'Exits with error message' do
expect { subject } expect { subject }
.to output_results('is too old') .to raise_error(Thor::Error, /is too old/)
.and raise_error(SystemExit)
end end
end end
@ -36,7 +35,7 @@ describe Mastodon::CLI::Maintenance do
it 'Exits with error message' do it 'Exits with error message' do
expect { subject } expect { subject }
.to output_results('more recent') .to output_results('more recent')
.and raise_error(SystemExit) .and raise_error(Thor::Error, /more recent/)
end end
end end
@ -48,8 +47,7 @@ describe Mastodon::CLI::Maintenance do
it 'Exits with error message' do it 'Exits with error message' do
expect { subject } expect { subject }
.to output_results('Sidekiq is running') .to raise_error(Thor::Error, /Sidekiq is running/)
.and raise_error(SystemExit)
end end
end end

View File

@ -20,8 +20,7 @@ describe Mastodon::CLI::Media do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('--prune-profiles and --remove-headers should not be specified simultaneously') .to raise_error(Thor::Error, '--prune-profiles and --remove-headers should not be specified simultaneously')
.and raise_error(SystemExit)
end end
end end
@ -30,8 +29,7 @@ describe Mastodon::CLI::Media do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('--include-follows can only be used with --prune-profiles or --remove-headers') .to raise_error(Thor::Error, '--include-follows can only be used with --prune-profiles or --remove-headers')
.and raise_error(SystemExit)
end end
end end
@ -98,8 +96,7 @@ describe Mastodon::CLI::Media do
it 'warns about url and exits' do it 'warns about url and exits' do
expect { subject } expect { subject }
.to output_results('Not a media URL') .to raise_error(Thor::Error, 'Not a media URL')
.and raise_error(SystemExit)
end end
end end
@ -121,8 +118,7 @@ describe Mastodon::CLI::Media do
context 'without any options' do context 'without any options' do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('Specify the source') .to raise_error(Thor::Error, /Specify the source/)
.and raise_error(SystemExit)
end end
end end
@ -147,8 +143,7 @@ describe Mastodon::CLI::Media do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('No such account') .to raise_error(Thor::Error, 'No such account')
.and raise_error(SystemExit)
end end
end end
@ -221,8 +216,7 @@ describe Mastodon::CLI::Media do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('azure storage driver is not supported') .to raise_error(Thor::Error, /azure storage driver is not supported/)
.and raise_error(SystemExit)
end end
end end
@ -233,8 +227,7 @@ describe Mastodon::CLI::Media do
it 'warns about usage and exits' do it 'warns about usage and exits' do
expect { subject } expect { subject }
.to output_results('fog storage driver is not supported') .to raise_error(Thor::Error, /fog storage driver is not supported/)
.and raise_error(SystemExit)
end end
end end

View File

@ -20,8 +20,7 @@ describe Mastodon::CLI::Search do
it 'Exits with error message' do it 'Exits with error message' do
expect { subject } expect { subject }
.to output_results('this concurrency setting') .to raise_error(Thor::Error, /this concurrency setting/)
.and raise_error(SystemExit)
end end
end end
@ -30,8 +29,7 @@ describe Mastodon::CLI::Search do
it 'Exits with error message' do it 'Exits with error message' do
expect { subject } expect { subject }
.to output_results('this batch_size setting') .to raise_error(Thor::Error, /this batch_size setting/)
.and raise_error(SystemExit)
end end
end end

View File

@ -20,8 +20,7 @@ describe Mastodon::CLI::Statuses do
it 'exits with error message' do it 'exits with error message' do
expect { subject } expect { subject }
.to output_results('Cannot run') .to raise_error(Thor::Error, /Cannot run/)
.and raise_error(SystemExit)
end end
end end