mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
DEV: Remove use of cd
in the app (#8337)
`FileUtils.cd` and `Dir.chdir` cause the working directory to change for the entire process. We run sidekiq jobs, hijacked requests and deferred jobs in threads, which can make working directory changes have unintended side-effects. - Add a rubocop rule to warn about usage of Dir.chdir and FileUtils.cd - Added rubocop:disable for scripts used outside the app - Refactored code using cd to use alternative methods - Temporarily skipped the rubocop check for lib/backup_restore. This will require more complex refactoring, so I will create a separate PR for review
This commit is contained in:
parent
e4df3792f6
commit
9fea43e46a
|
@ -1,3 +1,6 @@
|
||||||
|
require:
|
||||||
|
- ./lib/rubocop/cop/discourse_cops.rb
|
||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.4
|
TargetRubyVersion: 2.4
|
||||||
DisabledByDefault: true
|
DisabledByDefault: true
|
||||||
|
@ -125,3 +128,9 @@ Style/SingleLineMethods:
|
||||||
Style/Semicolon:
|
Style/Semicolon:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
AllowAsExpressionSeparator: true
|
AllowAsExpressionSeparator: true
|
||||||
|
|
||||||
|
DiscourseCops/NoChdir:
|
||||||
|
Enabled: true
|
||||||
|
Exclude:
|
||||||
|
- 'spec/**/*' # Specs are run sequentially, so chdir can be used
|
||||||
|
- 'lib/backup_restore/**/*' # TODO
|
|
@ -32,7 +32,7 @@ module Autospec
|
||||||
end
|
end
|
||||||
|
|
||||||
# launch rspec
|
# launch rspec
|
||||||
Dir.chdir(Rails.root) do
|
Dir.chdir(Rails.root) do # rubocop:disable DiscourseCops/NoChdir because this is not part of the app
|
||||||
env = { "RAILS_ENV" => "test" }
|
env = { "RAILS_ENV" => "test" }
|
||||||
if specs.split(' ').any? { |s| s =~ /^(.\/)?plugins/ }
|
if specs.split(' ').any? { |s| s =~ /^(.\/)?plugins/ }
|
||||||
env["LOAD_PLUGINS"] = "1"
|
env["LOAD_PLUGINS"] = "1"
|
||||||
|
|
34
lib/rubocop/cop/discourse_cops.rb
Normal file
34
lib/rubocop/cop/discourse_cops.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module RuboCop
|
||||||
|
module Cop
|
||||||
|
module DiscourseCops
|
||||||
|
# Avoid using chdir - it is not thread safe.
|
||||||
|
#
|
||||||
|
# Instead, you may be able to use:
|
||||||
|
# Discourse::Utils.execute_command(chdir: 'test') do |runner|
|
||||||
|
# runner.exec('pwd')
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# # bad
|
||||||
|
# Dir.chdir('test')
|
||||||
|
class NoChdir < Cop
|
||||||
|
MSG = 'Chdir is not thread safe.'
|
||||||
|
|
||||||
|
def_node_matcher :using_dir_chdir?, <<-MATCHER
|
||||||
|
(send (const nil? :Dir) :chdir ...)
|
||||||
|
MATCHER
|
||||||
|
|
||||||
|
def_node_matcher :using_fileutils_cd?, <<-MATCHER
|
||||||
|
(send (const nil? :FileUtils) :cd ...)
|
||||||
|
MATCHER
|
||||||
|
|
||||||
|
def on_send(node)
|
||||||
|
return if !(using_dir_chdir?(node) || using_fileutils_cd?(node))
|
||||||
|
add_offense(node, message: MSG)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -33,14 +33,14 @@ class ThemeStore::GitImporter
|
||||||
exporter = ThemeStore::ZipExporter.new(theme)
|
exporter = ThemeStore::ZipExporter.new(theme)
|
||||||
local_temp_folder = exporter.export_to_folder
|
local_temp_folder = exporter.export_to_folder
|
||||||
|
|
||||||
Dir.chdir(@temp_folder) do
|
Discourse::Utils.execute_command(chdir: @temp_folder) do |runner|
|
||||||
Discourse::Utils.execute_command("git", "checkout", local_version)
|
runner.exec("git", "checkout", local_version)
|
||||||
Discourse::Utils.execute_command("rm -rf ./*/")
|
runner.exec("rm -rf ./*/")
|
||||||
Discourse::Utils.execute_command("cp", "-rf", "#{local_temp_folder}/#{exporter.export_name}/.", @temp_folder)
|
runner.exec("cp", "-rf", "#{local_temp_folder}/#{exporter.export_name}/.", @temp_folder)
|
||||||
Discourse::Utils.execute_command("git", "checkout", "about.json")
|
runner.exec("git", "checkout", "about.json")
|
||||||
# add + diff staged to catch uploads but exclude renamed assets
|
# add + diff staged to catch uploads but exclude renamed assets
|
||||||
Discourse::Utils.execute_command("git", "add", "-A")
|
runner.exec("git", "add", "-A")
|
||||||
return Discourse::Utils.execute_command("git", "diff", "--staged", "--diff-filter=r")
|
return runner.exec("git", "diff", "--staged", "--diff-filter=r")
|
||||||
end
|
end
|
||||||
ensure
|
ensure
|
||||||
FileUtils.rm_rf local_temp_folder if local_temp_folder
|
FileUtils.rm_rf local_temp_folder if local_temp_folder
|
||||||
|
@ -49,18 +49,16 @@ class ThemeStore::GitImporter
|
||||||
def commits_since(hash)
|
def commits_since(hash)
|
||||||
commit_hash, commits_behind = nil
|
commit_hash, commits_behind = nil
|
||||||
|
|
||||||
Dir.chdir(@temp_folder) do
|
Discourse::Utils.execute_command(chdir: @temp_folder) do |runner|
|
||||||
commit_hash = Discourse::Utils.execute_command("git", "rev-parse", "HEAD").strip
|
commit_hash = runner.exec("git", "rev-parse", "HEAD").strip
|
||||||
commits_behind = Discourse::Utils.execute_command("git", "rev-list", "#{hash}..HEAD", "--count").strip
|
commits_behind = runner.exec("git", "rev-list", "#{hash}..HEAD", "--count").strip
|
||||||
end
|
end
|
||||||
|
|
||||||
[commit_hash, commits_behind]
|
[commit_hash, commits_behind]
|
||||||
end
|
end
|
||||||
|
|
||||||
def version
|
def version
|
||||||
Dir.chdir(@temp_folder) do
|
Discourse::Utils.execute_command("git", "rev-parse", "HEAD", chdir: @temp_folder).strip
|
||||||
Discourse::Utils.execute_command("git", "rev-parse", "HEAD").strip
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def cleanup!
|
def cleanup!
|
||||||
|
@ -82,9 +80,7 @@ class ThemeStore::GitImporter
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_files
|
def all_files
|
||||||
Dir.chdir(@temp_folder) do
|
Dir.glob("**/*", base: @temp_folder).reject { |f| File.directory?(f) }
|
||||||
Dir.glob("**/*").reject { |f| File.directory?(f) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](value)
|
def [](value)
|
||||||
|
@ -111,10 +107,8 @@ class ThemeStore::GitImporter
|
||||||
ssh_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_ssh_#{SecureRandom.hex}"
|
ssh_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_ssh_#{SecureRandom.hex}"
|
||||||
FileUtils.mkdir_p ssh_folder
|
FileUtils.mkdir_p ssh_folder
|
||||||
|
|
||||||
Dir.chdir(ssh_folder) do
|
File.write("#{ssh_folder}/id_rsa", @private_key.strip)
|
||||||
File.write('id_rsa', @private_key.strip)
|
FileUtils.chmod(0600, "#{ssh_folder}/id_rsa")
|
||||||
FileUtils.chmod(0600, 'id_rsa')
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
begin
|
||||||
git_ssh_command = { 'GIT_SSH_COMMAND' => "ssh -i #{ssh_folder}/id_rsa -o StrictHostKeyChecking=no" }
|
git_ssh_command = { 'GIT_SSH_COMMAND' => "ssh -i #{ssh_folder}/id_rsa -o StrictHostKeyChecking=no" }
|
||||||
|
|
|
@ -26,22 +26,20 @@ class ThemeStore::ZipExporter
|
||||||
end
|
end
|
||||||
|
|
||||||
def export_to_folder
|
def export_to_folder
|
||||||
FileUtils.mkdir(@temp_folder)
|
destination_folder = File.join(@temp_folder, @export_name)
|
||||||
|
FileUtils.mkdir_p(destination_folder)
|
||||||
Dir.chdir(@temp_folder) do
|
|
||||||
FileUtils.mkdir(@export_name)
|
|
||||||
|
|
||||||
@theme.theme_fields.each do |field|
|
@theme.theme_fields.each do |field|
|
||||||
next unless path = field.file_path
|
next unless path = field.file_path
|
||||||
|
|
||||||
# Belt and braces approach here. All the user input should already be
|
# Belt and braces approach here. All the user input should already be
|
||||||
# sanitized, but check for attempts to leave the temp directory anyway
|
# sanitized, but check for attempts to leave the temp directory anyway
|
||||||
pathname = Pathname.new("#{@export_name}/#{path}")
|
pathname = Pathname.new(File.join(destination_folder, path))
|
||||||
folder_path = pathname.parent.cleanpath
|
folder_path = pathname.parent.cleanpath
|
||||||
raise RuntimeError.new("Theme exporter tried to leave directory") unless folder_path.to_s.starts_with?("#{@export_name}")
|
raise RuntimeError.new("Theme exporter tried to leave directory") unless folder_path.to_s.starts_with?(destination_folder)
|
||||||
pathname.parent.mkpath
|
pathname.parent.mkpath
|
||||||
path = pathname.realdirpath
|
path = pathname.realdirpath
|
||||||
raise RuntimeError.new("Theme exporter tried to leave directory") unless path.to_s.starts_with?("#{@temp_folder}/#{@export_name}")
|
raise RuntimeError.new("Theme exporter tried to leave directory") unless path.to_s.starts_with?(destination_folder)
|
||||||
|
|
||||||
if ThemeField.types[field.type_id] == :theme_upload_var
|
if ThemeField.types[field.type_id] == :theme_upload_var
|
||||||
filename = Discourse.store.path_for(field.upload)
|
filename = Discourse.store.path_for(field.upload)
|
||||||
|
@ -52,8 +50,8 @@ class ThemeStore::ZipExporter
|
||||||
File.write(path, content)
|
File.write(path, content)
|
||||||
end
|
end
|
||||||
|
|
||||||
File.write("#{@export_name}/about.json", JSON.pretty_generate(@theme.generate_metadata_hash))
|
File.write(File.join(destination_folder, "about.json"), JSON.pretty_generate(@theme.generate_metadata_hash))
|
||||||
end
|
|
||||||
@temp_folder
|
@temp_folder
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -62,6 +60,6 @@ class ThemeStore::ZipExporter
|
||||||
def export_package
|
def export_package
|
||||||
export_to_folder
|
export_to_folder
|
||||||
|
|
||||||
Dir.chdir(@temp_folder) { Compression::Zip.new.compress(@temp_folder, @export_name) }
|
Compression::Zip.new.compress(@temp_folder, @export_name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,13 +17,11 @@ class ThemeStore::ZipImporter
|
||||||
def import!
|
def import!
|
||||||
FileUtils.mkdir(@temp_folder)
|
FileUtils.mkdir(@temp_folder)
|
||||||
|
|
||||||
Dir.chdir(@temp_folder) do
|
|
||||||
available_size = SiteSetting.decompressed_theme_max_file_size_mb
|
available_size = SiteSetting.decompressed_theme_max_file_size_mb
|
||||||
Compression::Engine.engine_for(@original_filename).tap do |engine|
|
Compression::Engine.engine_for(@original_filename).tap do |engine|
|
||||||
engine.decompress(@temp_folder, @filename, available_size)
|
engine.decompress(@temp_folder, @filename, available_size)
|
||||||
engine.strip_directory(@temp_folder, @temp_folder, relative: true)
|
engine.strip_directory(@temp_folder, @temp_folder, relative: true)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
rescue RuntimeError
|
rescue RuntimeError
|
||||||
raise RemoteTheme::ImportError, I18n.t("themes.import_error.unpack_failed")
|
raise RemoteTheme::ImportError, I18n.t("themes.import_error.unpack_failed")
|
||||||
rescue Compression::Zip::ExtractFailed
|
rescue Compression::Zip::ExtractFailed
|
||||||
|
@ -53,9 +51,7 @@ class ThemeStore::ZipImporter
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_files
|
def all_files
|
||||||
Dir.chdir(@temp_folder) do
|
Dir.glob("**/**", base: @temp_folder).reject { |f| File.directory?(f) }
|
||||||
Dir.glob("**/**").reject { |f| File.directory?(f) }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def [](value)
|
def [](value)
|
||||||
|
|
|
@ -42,7 +42,7 @@ class PluginTxUpdater
|
||||||
PLUGINS.each do |plugin_name|
|
PLUGINS.each do |plugin_name|
|
||||||
plugin_dir = File.join(@base_dir, plugin_name)
|
plugin_dir = File.join(@base_dir, plugin_name)
|
||||||
Bundler.with_clean_env do
|
Bundler.with_clean_env do
|
||||||
Dir.chdir(plugin_dir) do
|
Dir.chdir(plugin_dir) do # rubocop:disable DiscourseCops/NoChdir because this is not part of the app
|
||||||
puts '', plugin_dir, '-' * 80, ''
|
puts '', plugin_dir, '-' * 80, ''
|
||||||
|
|
||||||
begin
|
begin
|
||||||
|
|
Loading…
Reference in New Issue
Block a user