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:
David Taylor
2019-11-13 09:57:39 +00:00
committed by GitHub
parent e4df3792f6
commit 9fea43e46a
7 changed files with 86 additions and 55 deletions

View File

@@ -33,14 +33,14 @@ class ThemeStore::GitImporter
exporter = ThemeStore::ZipExporter.new(theme)
local_temp_folder = exporter.export_to_folder
Dir.chdir(@temp_folder) do
Discourse::Utils.execute_command("git", "checkout", local_version)
Discourse::Utils.execute_command("rm -rf ./*/")
Discourse::Utils.execute_command("cp", "-rf", "#{local_temp_folder}/#{exporter.export_name}/.", @temp_folder)
Discourse::Utils.execute_command("git", "checkout", "about.json")
Discourse::Utils.execute_command(chdir: @temp_folder) do |runner|
runner.exec("git", "checkout", local_version)
runner.exec("rm -rf ./*/")
runner.exec("cp", "-rf", "#{local_temp_folder}/#{exporter.export_name}/.", @temp_folder)
runner.exec("git", "checkout", "about.json")
# add + diff staged to catch uploads but exclude renamed assets
Discourse::Utils.execute_command("git", "add", "-A")
return Discourse::Utils.execute_command("git", "diff", "--staged", "--diff-filter=r")
runner.exec("git", "add", "-A")
return runner.exec("git", "diff", "--staged", "--diff-filter=r")
end
ensure
FileUtils.rm_rf local_temp_folder if local_temp_folder
@@ -49,18 +49,16 @@ class ThemeStore::GitImporter
def commits_since(hash)
commit_hash, commits_behind = nil
Dir.chdir(@temp_folder) do
commit_hash = Discourse::Utils.execute_command("git", "rev-parse", "HEAD").strip
commits_behind = Discourse::Utils.execute_command("git", "rev-list", "#{hash}..HEAD", "--count").strip
Discourse::Utils.execute_command(chdir: @temp_folder) do |runner|
commit_hash = runner.exec("git", "rev-parse", "HEAD").strip
commits_behind = runner.exec("git", "rev-list", "#{hash}..HEAD", "--count").strip
end
[commit_hash, commits_behind]
end
def version
Dir.chdir(@temp_folder) do
Discourse::Utils.execute_command("git", "rev-parse", "HEAD").strip
end
Discourse::Utils.execute_command("git", "rev-parse", "HEAD", chdir: @temp_folder).strip
end
def cleanup!
@@ -82,9 +80,7 @@ class ThemeStore::GitImporter
end
def all_files
Dir.chdir(@temp_folder) do
Dir.glob("**/*").reject { |f| File.directory?(f) }
end
Dir.glob("**/*", base: @temp_folder).reject { |f| File.directory?(f) }
end
def [](value)
@@ -111,10 +107,8 @@ class ThemeStore::GitImporter
ssh_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_ssh_#{SecureRandom.hex}"
FileUtils.mkdir_p ssh_folder
Dir.chdir(ssh_folder) do
File.write('id_rsa', @private_key.strip)
FileUtils.chmod(0600, 'id_rsa')
end
File.write("#{ssh_folder}/id_rsa", @private_key.strip)
FileUtils.chmod(0600, "#{ssh_folder}/id_rsa")
begin
git_ssh_command = { 'GIT_SSH_COMMAND' => "ssh -i #{ssh_folder}/id_rsa -o StrictHostKeyChecking=no" }

View File

@@ -26,34 +26,32 @@ class ThemeStore::ZipExporter
end
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|
next unless path = field.file_path
@theme.theme_fields.each do |field|
next unless path = field.file_path
# Belt and braces approach here. All the user input should already be
# sanitized, but check for attempts to leave the temp directory anyway
pathname = Pathname.new(File.join(destination_folder, path))
folder_path = pathname.parent.cleanpath
raise RuntimeError.new("Theme exporter tried to leave directory") unless folder_path.to_s.starts_with?(destination_folder)
pathname.parent.mkpath
path = pathname.realdirpath
raise RuntimeError.new("Theme exporter tried to leave directory") unless path.to_s.starts_with?(destination_folder)
# Belt and braces approach here. All the user input should already be
# sanitized, but check for attempts to leave the temp directory anyway
pathname = Pathname.new("#{@export_name}/#{path}")
folder_path = pathname.parent.cleanpath
raise RuntimeError.new("Theme exporter tried to leave directory") unless folder_path.to_s.starts_with?("#{@export_name}")
pathname.parent.mkpath
path = pathname.realdirpath
raise RuntimeError.new("Theme exporter tried to leave directory") unless path.to_s.starts_with?("#{@temp_folder}/#{@export_name}")
if ThemeField.types[field.type_id] == :theme_upload_var
filename = Discourse.store.path_for(field.upload)
content = filename ? File.read(filename) : Discourse.store.download(field.upload).read
else
content = field.value
end
File.write(path, content)
if ThemeField.types[field.type_id] == :theme_upload_var
filename = Discourse.store.path_for(field.upload)
content = filename ? File.read(filename) : Discourse.store.download(field.upload).read
else
content = field.value
end
File.write("#{@export_name}/about.json", JSON.pretty_generate(@theme.generate_metadata_hash))
File.write(path, content)
end
File.write(File.join(destination_folder, "about.json"), JSON.pretty_generate(@theme.generate_metadata_hash))
@temp_folder
end
@@ -62,6 +60,6 @@ class ThemeStore::ZipExporter
def export_package
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

View File

@@ -17,12 +17,10 @@ class ThemeStore::ZipImporter
def import!
FileUtils.mkdir(@temp_folder)
Dir.chdir(@temp_folder) do
available_size = SiteSetting.decompressed_theme_max_file_size_mb
Compression::Engine.engine_for(@original_filename).tap do |engine|
engine.decompress(@temp_folder, @filename, available_size)
engine.strip_directory(@temp_folder, @temp_folder, relative: true)
end
available_size = SiteSetting.decompressed_theme_max_file_size_mb
Compression::Engine.engine_for(@original_filename).tap do |engine|
engine.decompress(@temp_folder, @filename, available_size)
engine.strip_directory(@temp_folder, @temp_folder, relative: true)
end
rescue RuntimeError
raise RemoteTheme::ImportError, I18n.t("themes.import_error.unpack_failed")
@@ -53,9 +51,7 @@ class ThemeStore::ZipImporter
end
def all_files
Dir.chdir(@temp_folder) do
Dir.glob("**/**").reject { |f| File.directory?(f) }
end
Dir.glob("**/**", base: @temp_folder).reject { |f| File.directory?(f) }
end
def [](value)