WarpEngine 0.7.0: a képtár és a CI is a hoszté, adapteren keresztül
Két dolog volt beépítve az engine-be, ami nem az övé. A **képtár** eddig `WarpEngine::Image` volt, pedig a modell teljesen általános: a teletypegames-ben a tagok arcképét is ez hordozza, nem csak a katalógus borítóit. Az `Image` modell, a feltöltött fájlok, az `/api/image/:id` végpont és az admin oldal ezért átkerült a hosztba, az engine pedig adapteren szól hozzá (`WarpEngine::Images`): `url_for` adja a katalógus JSON `imageUrl`-jét, `select_options` a software-form képválasztóját, `build_from_upload` a "tölts fel új képet" ágat. Az alapértelmezés az `Image` osztály, tehát a default útvonal bitre a régi. A `SoftwareImage` (a katalógus-kapcsolat) maradt az engine-ben, és **az `images` tábla nem mozdult**: az engine csak abbahagyta a létrehozását, a generátor írja meg hoszt-kódként. A **CI** eddig végig Woodpecker volt: kliens, aláírás-ellenőrzés, pipeline-receptek, repo-szinkron, secret-kiosztás. Mindez egy adapter mögé került (`WarpEngine.ci`), a Woodpecker-implementáció pedig az engine-ben maradt `WarpEngine::CI::Woodpecker` néven — kliens, adapter, httpsig-ellenőrző és a platformonkénti pipeline-receptek, mert a YAML-dialektus a szolgáltatóé. Az engine saját kódja már nem nevez szolgáltatót: `CI::Repo` és `CI::Run` értékeket kap, `CI::ConnectionError`/`ApiError`/`NotConfigured` hibákat dob, a `Pipeline` pedig `remote_repo_id`-t ad a történelmi `woodpecker_repo_id` kolumna fölött (a tábla itt sem mozdult). `c.ci_adapter = :none` azt jelenti, hogy ez a hoszt nem buildel: az `/api/ci/*` 503, a `/build/config` elutasít, az admin akciók elbújnak. Mindkét seam a hoszt initializerében van kimondva, nem alapértelmezésre hagyva — a hoszt megnevezi, mi a képtára és mi a CI-ja. Törés a 0.6-hoz képest: `image_container_path`, `image_owners`, `ci_platforms`, `ci_update_server`, `ci_extension_public_key(_url)`, `woodpecker_url`, `woodpecker_api_token`, `woodpecker_repo_owner` és a `WarpEngine.woodpecker_configured?` megszűnt; a helyük `c.image_class_name` / `c.image_adapter` és `c.ci_adapter`. Az `/api/ci/*` `latest_run`/`trigger` válasza a normalizált `CI::Run` alakot adja (number, status, branch, message, createdAt, url), a `pipelines` lista pedig `repo_id`-t is közöl a megtartott `woodpecker_repo_id` mellett. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@ module WarpEngine
|
||||
class CiController < ApiController
|
||||
include UpdateAuthentication
|
||||
|
||||
before_action :require_woodpecker!
|
||||
before_action :require_ci!
|
||||
|
||||
resource_description do
|
||||
short "CI pipeline management"
|
||||
@@ -11,7 +11,7 @@ module WarpEngine
|
||||
|
||||
api :GET, "/api/ci/pipelines", "List active pipelines"
|
||||
returns code: 200, desc: "JSON array of tracked pipelines"
|
||||
error code: 503, desc: "Woodpecker not configured"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def pipelines
|
||||
records = Pipeline.active.includes(:software)
|
||||
render json: records.map { |p| pipeline_json(p) }
|
||||
@@ -20,12 +20,12 @@ module WarpEngine
|
||||
api :GET, "/api/ci/pipelines/:id/status", "Get a pipeline with its latest run"
|
||||
param :id, :number, required: true, desc: "Pipeline id"
|
||||
returns code: 200, desc: "JSON with pipeline and latest run data"
|
||||
error code: 503, desc: "Woodpecker not configured"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def status
|
||||
pipeline = Pipeline.find(params[:id])
|
||||
latest_run = begin
|
||||
PipelineService.new.pipeline_detail(pipeline, "latest")
|
||||
rescue WoodpeckerClient::ApiError
|
||||
PipelineService.new.run(pipeline, "latest")
|
||||
rescue CI::Error
|
||||
nil
|
||||
end
|
||||
render json: { pipeline: pipeline_json(pipeline), latest_run: latest_run }
|
||||
@@ -37,7 +37,7 @@ module WarpEngine
|
||||
param :branch, String, required: false, desc: "Branch to build (default: main)"
|
||||
returns code: 200, desc: "JSON with triggered run data"
|
||||
error code: 401, desc: "Invalid secret"
|
||||
error code: 503, desc: "Woodpecker not configured"
|
||||
error code: 503, desc: "No CI provider configured"
|
||||
def trigger
|
||||
unless update_authorized?(required_scope: ApplicationToken::UPDATE_SCOPE)
|
||||
return render json: { error: "Unauthorized" }, status: :unauthorized
|
||||
@@ -50,16 +50,17 @@ module WarpEngine
|
||||
|
||||
private
|
||||
|
||||
def require_woodpecker!
|
||||
return if WarpEngine.woodpecker_configured?
|
||||
def require_ci!
|
||||
return if WarpEngine.ci.configured?
|
||||
|
||||
render json: { error: "Woodpecker not configured" }, status: :service_unavailable
|
||||
render json: { error: "CI not configured" }, status: :service_unavailable
|
||||
end
|
||||
|
||||
def pipeline_json(pipeline)
|
||||
{
|
||||
id: pipeline.id,
|
||||
woodpecker_repo_id: pipeline.woodpecker_repo_id,
|
||||
repo_id: pipeline.remote_repo_id,
|
||||
woodpecker_repo_id: pipeline.remote_repo_id,
|
||||
repo_owner: pipeline.repo_owner,
|
||||
repo_name: pipeline.repo_name,
|
||||
platform: pipeline.platform,
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
module WarpEngine
|
||||
class Api::ImagesController < ApiController
|
||||
resource_description do
|
||||
short "Images"
|
||||
formats [ "binary" ]
|
||||
end
|
||||
|
||||
api :GET, "/api/image/:id", "Get image by ID"
|
||||
param :id, :number, required: true, desc: "Image ID"
|
||||
returns code: 200, desc: "Image binary data"
|
||||
error code: 404, desc: "Image not found"
|
||||
def show
|
||||
image = WarpEngine::ImageService.new.show(WarpEngine::ImageShowInputDto.new(id: params[:id]))
|
||||
send_file image.file_path, type: image.content_type, disposition: "inline"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,76 +3,60 @@ module WarpEngine
|
||||
|
||||
class ConfigsController < ApiController
|
||||
resource_description do
|
||||
short "Woodpecker CI pipeline configs"
|
||||
short "CI pipeline configs"
|
||||
end
|
||||
|
||||
api :GET, "/build/config", "Preview the generated pipeline config for a platform"
|
||||
param :platform, String, required: true, desc: "Platform (a configured ci_platforms key, e.g. tic80)"
|
||||
param :platform, String, required: true, desc: "Platform (a platform the CI adapter serves, e.g. tic80)"
|
||||
param :name, String, required: false, desc: "Software name substituted into the pipeline (default: example)"
|
||||
returns code: 200, desc: "Pipeline YAML (text/yaml)"
|
||||
error code: 404, desc: "Unknown platform"
|
||||
def show
|
||||
yaml = render_config(platform: params[:platform], name: params[:name].presence || "example")
|
||||
return render json: { error: "Unknown platform" }, status: :not_found if yaml.nil?
|
||||
config = render_config(platform: params[:platform], name: params[:name].presence || "example")
|
||||
return render json: { error: "Unknown platform" }, status: :not_found if config.nil?
|
||||
|
||||
render plain: yaml, content_type: "text/yaml"
|
||||
render plain: config, content_type: "text/yaml"
|
||||
end
|
||||
|
||||
api :POST, "/build/config", "Woodpecker configuration extension endpoint"
|
||||
api :POST, "/build/config", "CI configuration extension endpoint"
|
||||
description <<~DESC
|
||||
Called by the Woodpecker server on every pipeline start (httpsig-signed request).
|
||||
If the repo's .woodpecker.yaml is a marker (has a `platform:` key), responds with
|
||||
Called by the CI server on every pipeline start (a signed request the CI adapter
|
||||
verifies). If the repo's config is a marker (has a `platform:` key), responds with
|
||||
the generated pipeline; otherwise responds 204 so the repo's own config runs.
|
||||
DESC
|
||||
returns code: 200, desc: %(JSON: {"configs": [{"name": ..., "data": "<pipeline YAML>"}]})
|
||||
returns code: 200, desc: %(JSON: the adapter's config response — for Woodpecker {"configs": [{"name": ..., "data": "<pipeline YAML>"}]})
|
||||
returns code: 204, desc: "Not a marker config — keep the repo's own configuration"
|
||||
error code: 403, desc: "Missing or invalid request signature"
|
||||
error code: 422, desc: "Marker requests an unknown platform"
|
||||
def create
|
||||
unless WarpEngine::CiSignatureVerifier.new(request).valid?
|
||||
unless ci.verify_config_request(request)
|
||||
return render json: { error: "Invalid signature" }, status: :forbidden
|
||||
end
|
||||
|
||||
marker = find_marker
|
||||
marker = ci.config_marker(params)
|
||||
return head :no_content if marker.nil?
|
||||
|
||||
platform = marker["platform"].to_s
|
||||
name = marker["name"].presence || repo_name
|
||||
yaml = render_config(platform: platform, name: name)
|
||||
if yaml.nil?
|
||||
return render json: { error: "Unknown platform: #{platform}" }, status: :unprocessable_entity
|
||||
config = render_config(platform: marker[:platform], name: marker[:name])
|
||||
if config.nil?
|
||||
return render json: { error: "Unknown platform: #{marker[:platform]}" }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
render json: { configs: [ { name: platform, data: yaml } ] }
|
||||
render json: ci.config_response(platform: marker[:platform], config: config)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ci
|
||||
WarpEngine.ci
|
||||
end
|
||||
|
||||
def render_config(platform:, name:)
|
||||
WarpEngine::CiConfigService.new.render(
|
||||
ci.pipeline_config(
|
||||
platform: platform,
|
||||
name: name,
|
||||
update_server: WarpEngine.config.ci_update_server.presence || request.base_url
|
||||
update_server: ci.update_server.presence || request.base_url
|
||||
)
|
||||
end
|
||||
|
||||
def find_marker
|
||||
configs = params[:configuration].presence || params[:configs].presence || []
|
||||
configs.each do |config|
|
||||
data = config[:data].to_s
|
||||
parsed = begin
|
||||
YAML.safe_load(data)
|
||||
rescue Psych::Exception
|
||||
nil
|
||||
end
|
||||
return parsed if parsed.is_a?(Hash) && parsed.key?("platform")
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def repo_name
|
||||
params.dig(:repo, :name).to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user