diff --git a/app/controllers/api/v1/projects/pipelines_controller.rb b/app/controllers/api/v1/projects/pipelines_controller.rb index f6cf4d910..06a4c23e9 100644 --- a/app/controllers/api/v1/projects/pipelines_controller.rb +++ b/app/controllers/api/v1/projects/pipelines_controller.rb @@ -1,5 +1,6 @@ class Api::V1::Projects::PipelinesController < Api::V1::BaseController include RepositoriesHelper + include Api::ActionValidatorHelper before_action :require_operate_above, except: [:upload_results, :run_results] def index @@ -102,6 +103,11 @@ class Api::V1::Projects::PipelinesController < Api::V1::BaseController @pipeline.pipeline_name = params[:pipeline_name] if params[:pipeline_name].present? pipeline_yaml = params[:pipeline_yaml].present? ? params[:pipeline_yaml] : build_pipeline_yaml_new(@pipeline.pipeline_name, params[:pipeline_json]) tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank? + begin + validate_string(pipeline_yaml) + rescue + tip_exception("yaml格式有误") + end @pipeline.yaml = pipeline_yaml #Rails.logger.info "pipeline_yaml base64=========================#{Base64.encode64(@pipeline.yaml).gsub(/\n/, '')}" sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch) diff --git a/app/controllers/concerns/api/action_validator_helper.rb b/app/controllers/concerns/api/action_validator_helper.rb new file mode 100644 index 000000000..9a15b0da3 --- /dev/null +++ b/app/controllers/concerns/api/action_validator_helper.rb @@ -0,0 +1,165 @@ +require 'yaml' +require 'json' + +module Api::ActionValidatorHelper + extend ActiveSupport::Concern + + def validate_string(yaml_string) + begin + yaml_content = YAML.safe_load(yaml_string) + validate_content(yaml_content) + rescue Psych::SyntaxError => e + { valid: false, errors: ["YAML语法错误: #{e.message}"] } + rescue => e + { valid: false, errors: ["数据读取错误: #{e.message}"] } + end + end + + def validate_content(data) + errors = [] + warnings = [] + + # 基本结构验证 + errors.concat(validate_basic_structure(data)) + + # 触发器验证 + errors.concat(validate_trigger(data)) + + # 任务验证 + errors.concat(validate_job(data)) + + # 步骤验证 + if data['jobs'] + data['jobs'].each do |job_name, job_config| + errors.concat(validate_job_steps(job_name, job_config)) + end + end + + { + valid: errors.empty?, + errors: errors, + warnings: warnings, + job_count: data['jobs']&.size || 0 + } + end + + private + + def validate_basic_structure(data) + errors = [] + + unless data.is_a?(Hash) + errors << "Action文件必须是YAML对象" + return errors + end + + unless data.key?('name') + errors << "缺少必需字段: name" + end + + unless data.key?(true) + errors << "缺少触发器配置: on" + end + + unless data.key?('jobs') + errors << "缺少任务配置: jobs" + end + + errors + end + + def validate_trigger(data) + errors = [] + + if data[true] + if data[true]['push'] + unless data[true]['push'].key?('branches') + errors << "push触发器缺少branches配置" + end + + unless data[true]['push'].key?('paths-ignore') + errors << "push触发器缺少paths-ignore配置" + end + end + end + + errors + end + + def validate_job(data) + errors = [] + + if data['jobs'] + jobs = data['jobs'] + + unless jobs.is_a?(Hash) + errors << "jobs字段必须是对象" + return errors + end + + jobs.each do |job_name, job_config| + unless job_config.is_a?(Hash) + errors << "任务 #{job_name} 配置必须是对象" + next + end + + unless job_config.key?('name') + errors << "任务 #{job_name} 缺少name字段" + end + + unless job_config.key?('runs-on') + errors << "任务 #{job_name} 缺少runs-on字段" + end + + unless job_config.key?('steps') + errors << "任务 #{job_name} 缺少steps字段" + end + end + end + + errors + end + + def validate_job_steps(job_name, job_config) + errors = [] + steps = job_config['steps'] || [] + + unless steps.is_a?(Array) + errors << "任务 #{job_name} 的steps必须是数组" + return errors + end + # 验证每个步骤的详细配置 + steps.each_with_index do |step, index| + errors.concat(validate_step(job_name, step, index)) + end + + errors + end + + def validate_step(job_name, step, index) + errors = [] + + unless step.is_a?(Hash) + errors << "任务 #{job_name} 步骤 #{index + 1} 必须是对象" + return errors + end + + unless step.key?('name') + errors << "任务 #{job_name} 步骤 #{index + 1} 缺少name字段" + end + + # 验证uses或run字段 + has_uses = step.key?('uses') + has_run = step.key?('run') + + unless has_uses || has_run + errors << "任务 #{job_name} 步骤 #{index + 1} 必须包含uses或run字段" + end + + if has_uses && has_run + errors << "任务 #{job_name} 步骤 #{index + 1} 不能同时包含uses和run字段" + end + + errors + end +end \ No newline at end of file diff --git a/app/services/repositories/create_service.rb b/app/services/repositories/create_service.rb index 193ac6067..497e4d3bb 100644 --- a/app/services/repositories/create_service.rb +++ b/app/services/repositories/create_service.rb @@ -41,7 +41,7 @@ class Repositories::CreateService < ApplicationService begin @gitea_repository = $gitea_client.get_repos_by_owner_repo(project.owner.login, params[:identifier]) rescue Gitea::Api::ServerError => e - if e.http_code.to_i == 404 + if e.http_code.to_i == 404 || e.http_code.to_i == 307 if project.owner.is_a?(User) # @gitea_repository = Gitea::Repository::CreateProjectService.new(user.gitea_token, gitea_repository_params).call @gitea_repository = $gitea_client.post_user_repos({query: {token: user.gitea_token}, body: gitea_repository_params.to_json})