warp_engine 0.2.0: pluggable storage adapter and publish notifications
Two seams the hosts needed, both backward compatible.
Storage: artifacts are served through WarpEngine::Storage.adapter instead of
raw filesystem calls. The default :local adapter keeps the previous behaviour
byte for byte, including the path traversal guard. A host can now set
config.storage_adapter to any object answering file?/directory?/locate and
serve builds from an object store - FileService and /api/download both honour
a Location.redirect, so a signing adapter turns them into redirects.
DownloadService#create still returns an absolute path (nil when missing) for
existing callers; #locate is the new entry point that can also return a
redirect. Ingestion (upload, extraction, file manager) stays local for now.
Publish: PublishService emits ActiveSupport::Notifications
("warp_engine.publish") with platform/name/version/software/release, so hosts
can react to a new build without hanging callbacks on the models.
WarpEngine.instruments_publish? lets a host feature-detect and keep its
fallback for older engine versions.
This commit is contained in:
@@ -9,26 +9,45 @@ module WarpEngine
|
||||
Pathname.new(container_base).realpath
|
||||
end
|
||||
|
||||
def create(path:, ip:, user_agent:, referer:)
|
||||
sanitized = path.to_s
|
||||
base_path = self.class.base_path
|
||||
full_path = base_path.join(sanitized).realpath
|
||||
return nil unless full_path.to_s.start_with?(base_path.to_s)
|
||||
return nil unless File.file?(full_path)
|
||||
# A letöltés helyét adja vissza (fájl vagy aláírt URL) és naplózza a
|
||||
# letöltést. A hely feloldása a storage adapteren megy — alapból :local,
|
||||
# tehát változatlanul lemezről.
|
||||
def locate(path:, ip:, user_agent:, referer:)
|
||||
relative = path.to_s
|
||||
return nil unless storage.file?(relative)
|
||||
|
||||
escaped = sanitized.gsub("%", "\\%").gsub("_", "\\_")
|
||||
asset = WarpEngine::ReleaseAsset.find_by(path: File.join(self.class.container_base, sanitized)) ||
|
||||
log_download(relative, ip: ip, user_agent: user_agent, referer: referer)
|
||||
|
||||
storage.locate(relative, filename: File.basename(relative))
|
||||
end
|
||||
|
||||
# Visszafelé kompatibilis felület: az abszolút fájlútvonalat adja vissza
|
||||
# (vagy nil-t). Nem lemezes adapternél nincs útvonal — ott a #locate való.
|
||||
def create(path:, ip:, user_agent:, referer:)
|
||||
location = locate(path: path, ip: ip, user_agent: user_agent, referer: referer)
|
||||
return nil if location.nil?
|
||||
|
||||
location.file? ? location.path : nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def storage
|
||||
WarpEngine.storage
|
||||
end
|
||||
|
||||
def log_download(relative, ip:, user_agent:, referer:)
|
||||
escaped = relative.gsub("%", "\\%").gsub("_", "\\_")
|
||||
asset = WarpEngine::ReleaseAsset.find_by(path: File.join(self.class.container_base, relative)) ||
|
||||
WarpEngine::ReleaseAsset.where("path LIKE ?", "%#{escaped}%").first
|
||||
|
||||
WarpEngine::Download.create!(
|
||||
file_path: sanitized,
|
||||
file_path: relative,
|
||||
release: asset&.release,
|
||||
ip_address: ip,
|
||||
user_agent: user_agent&.truncate(500),
|
||||
referer: referer&.truncate(500)
|
||||
)
|
||||
|
||||
full_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,31 +5,32 @@ module WarpEngine
|
||||
Pathname.new(WarpEngine.config.file_container_path).realpath
|
||||
end
|
||||
|
||||
# A fájlok helyét a storage adapter adja (alapból :local, azaz a lemez) —
|
||||
# így a host az objektumtárból is kiszolgálhat anélkül, hogy az engine-t
|
||||
# patchelné. Lásd WarpEngine::Storage.
|
||||
def show(input)
|
||||
full_path = base_path.join(input.path.to_s)
|
||||
return FileResultDto.not_found unless safe_path?(full_path)
|
||||
relative = input.path.to_s
|
||||
|
||||
if File.directory?(full_path)
|
||||
index_path = full_path.join("index.html")
|
||||
return FileResultDto.not_found unless File.file?(index_path)
|
||||
return FileResultDto.redirect("/file/#{input.path.to_s.chomp("/")}/index.html")
|
||||
if storage.directory?(relative)
|
||||
index = File.join(relative.chomp("/"), "index.html")
|
||||
return FileResultDto.not_found unless storage.file?(index)
|
||||
|
||||
return FileResultDto.redirect("/file/#{index}")
|
||||
end
|
||||
|
||||
if File.file?(full_path)
|
||||
FileResultDto.file(full_path)
|
||||
else
|
||||
FileResultDto.not_found
|
||||
end
|
||||
return FileResultDto.not_found unless storage.file?(relative)
|
||||
|
||||
to_result(storage.locate(relative, filename: File.basename(relative)))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base_path
|
||||
@base_path ||= self.class.base_path
|
||||
def storage
|
||||
WarpEngine.storage
|
||||
end
|
||||
|
||||
def safe_path?(path)
|
||||
File.exist?(path) && Pathname.new(path).realpath.to_s.start_with?(base_path.to_s)
|
||||
def to_result(location)
|
||||
location.redirect? ? FileResultDto.redirect(location.url) : FileResultDto.file(location.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
module WarpEngine
|
||||
class PublishService
|
||||
# Az esemény neve, amit a host lehallgathat. A payload:
|
||||
# platform: String, name: String, version: String,
|
||||
# software: WarpEngine::Software, release: WarpEngine::Release
|
||||
NOTIFICATION = "warp_engine.publish".freeze
|
||||
|
||||
def publish(input)
|
||||
unless WarpEngine::PlatformLink::SUPPORTED_PLATFORMS.include?(input.platform)
|
||||
raise ArgumentError, "Unsupported platform: #{input.platform}"
|
||||
end
|
||||
|
||||
"WarpEngine::Platforms::#{input.platform.camelize}::Service".constantize.new.update(input.name, input.version)
|
||||
release = "WarpEngine::Platforms::#{input.platform.camelize}::Service".constantize
|
||||
.new.update(input.name, input.version)
|
||||
|
||||
# A publikálás az egyetlen pont, ahol új build kerül a katalógusba —
|
||||
# a host innen tud rá reagálni (értesítés, feed, csatorna-előléptetés)
|
||||
# anélkül, hogy modell-callbackre kellene kapaszkodnia.
|
||||
ActiveSupport::Notifications.instrument(
|
||||
NOTIFICATION,
|
||||
platform: input.platform,
|
||||
name: input.name,
|
||||
version: input.version,
|
||||
software: release.respond_to?(:software) ? release.software : nil,
|
||||
release: release
|
||||
)
|
||||
|
||||
release
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user