class Api::V1::Projects::PipelinesController < Api::V1::BaseController before_action :require_operate_above def index @pipelines = Action::Pipeline.where(project_id: @project.id).order(updated_at: :desc) @pipelines = paginate @pipelines end def create size = Action::Pipeline.where(pipeline_name: params[:pipeline_name], project_id: @project.id).size tip_exception("已经存在#{params[:pipeline_name]}流水线!") if size > 0 @pipeline = Action::Pipeline.new(pipeline_name: params[:pipeline_name], project_id: @project.id) @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml" @pipeline.branch = params[:branch] || @project.default_branch @pipeline.json = params[:pipeline_json].to_json pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json]) tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank? @pipeline.yaml = pipeline_yaml @pipeline.save! sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch) tip_exception("#{@pipeline.file_name}已存在") if sha interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create")) tip_exception(interactor.error) unless interactor.success? render_ok({ id: @pipeline.id }) end def save_yaml @pipeline = Action::Pipeline.new(pipeline_name: params[:pipeline_name], project_id: @project.id) @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml" @pipeline.branch = params[:branch] || @project.default_branch @pipeline.json = params[:pipeline_json].to_json pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json]) tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank? @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) interactor = sha.present? ? Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update").merge(sha: sha)) : Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create")) tip_exception(interactor.error) unless interactor.success? file = interactor.result render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: params[:pipeline_name], file_name: @pipeline.file_name, sha: sha.present? ? sha : file['content']['sha'] }) end def build_yaml if params[:pipeline_json].present? pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json]) else pipeline_yaml = build_test_yaml end # render plain: pipeline_yaml render_ok({ pipeline_yaml: pipeline_yaml }) end def update @pipeline = Action::Pipeline.find(params[:id]) @pipeline.pipeline_name = params[:pipeline_name] @pipeline.file_name = ".gitea/workflows/#{@pipeline.pipeline_name}.yml" @pipeline.branch = params[:branch] || @project.default_branch @pipeline.json = params[:pipeline_json].to_json pipeline_yaml = build_pipeline_yaml(params[:pipeline_name], params[:pipeline_json]) tip_exception("流水线yaml内空不能为空") if pipeline_yaml.blank? @pipeline.yaml = pipeline_yaml @pipeline.save sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch) interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params("create").merge(sha: sha)) tip_exception(interactor.error) unless interactor.success? file = interactor.result render_ok({ pipeline_yaml: pipeline_yaml, pipeline_name: params[:pipeline_name], file_name: @pipeline.file_name, sha: file['content']['sha'] }) end def destroy @pipeline = Action::Pipeline.find(params[:id]) if pipeline interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, content_params("update")) tip_exception(interactor.error) unless interactor.success? @pipeline.destroy! end render_ok end def show @pipeline = Action::Pipeline.find_by(id: params[:id]) @pipeline = Action::Pipeline.new(id: 0, pipeline_name: "test-ss", yaml: build_test_yaml) if @pipeline.blank? end def build_pipeline_yaml(pipeline_name, pipeline_json) if pipeline_json.present? && pipeline_json.present? @pipeline_name = pipeline_name params_nodes = pipeline_json["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["name"]) } on_nodes = pipeline_json["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["name"]) } @on_nodes = build_nodes(on_nodes) @steps_nodes = build_nodes(params_nodes) yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_pipeline.yaml.erb"))).result(binding) # 删除空行内容 pipeline_yaml = yaml.gsub(/^\s*\n/, "") else pipeline_yaml = params[:pipeline_yaml] end Rails.logger.info "pipeline_yaml=========================" Rails.logger.info pipeline_yaml pipeline_yaml end def build_test_yaml @pipeline_name = "I like it" params_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["name"]) } on_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["name"]) } @on_nodes = build_nodes(on_nodes) @steps_nodes = [] params_nodes.each do |input_node| # Rails.logger.info "input_node=====0===#{input_node["name"]}======#{input_node["inputs"]}" node = Action::Node.find_by(name: input_node["name"]) next if node.blank? node.label = input_node["label"] if input_node["label"].present? run_values = {} input_values = {} if input_node["inputs"].present? Rails.logger.info "@inputs=====11===#{input_node["name"]}======#{input_node["inputs"]}" input_node["inputs"].each do |input| # Rails.logger.info "@inputs.input_name===#{input[:name]}" # Rails.logger.info "@inputs.input_value===#{input["value"]}" if input[:name].to_s.gsub("--", "") == "run" run_values = run_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" }) else input_values = input_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" }) end end node.run_values = run_values node.input_values = input_values # Rails.logger.info "@input_values run_values===#{node.run_values.to_json}" # Rails.logger.info "@input_values input_values===#{node.input_values.to_json}" end @steps_nodes.push(node) end Rails.logger.info "@@on_nodes===#{@on_nodes.to_json}" Rails.logger.info "@steps_nodes===#{@steps_nodes.to_json}" yaml = ERB.new(File.read(File.join(Rails.root, "app/views/api/v1/projects/pipelines", "build_pipeline.yaml.erb"))).result(binding) pipeline_yaml = yaml.gsub(/^\s*\n/, "") Rails.logger.info "=========================" Rails.logger.info pipeline_yaml pipeline_yaml end private def get_pipeline_file_sha(file_name, branch) file_path_uri = URI.parse(URI.encode(file_name)) interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: branch || @project.default_branch) if interactor.success? file = interactor.result file['sha'] else nil end end def content_params(opt) { filepath: ".gitea/workflows/#{@pipeline.pipeline_name}.yml", branch: @pipeline.branch, new_branch: @pipeline.branch, content: opt == "create" ? Base64.encode64(@pipeline.yaml).gsub(/\n/, '') : @pipeline.yaml, message: "#{opt} pipeline", committer: { email: current_user.mail, name: current_user.login }, identifier: @project.identifier } end def build_nodes(params_nodes) steps_nodes = [] params_nodes.each do |input_node| node = Action::Node.find_by(name: input_node["name"]) next if node.blank? node.label = input_node["label"] if input_node["label"].present? run_values = {} input_values = {} if input_node["inputs"].present? Rails.logger.info "@inputs=====11===#{input_node["name"]}======#{input_node["inputs"]}" input_node["inputs"].each do |input| # Rails.logger.info "@inputs.input_name===#{input[:name]}" # Rails.logger.info "@inputs.input_value===#{input["value"]}" if input[:name].to_s.gsub("--", "") == "run" run_values = run_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" }) else input_values = input_values.merge({ "#{input[:name].gsub("--", "")}": "#{input["value"]}" }) end end node.run_values = run_values node.input_values = input_values end steps_nodes.push(node) end steps_nodes end def demo { "nodes": [{ "id": "on-schedule-2fcf505", "name": "on-schedule", "full_name": "on-schedule", "description": " 定时器计划器", "icon": "https://testforgeplus.trustie.net/api/attachments/0445403c-5d9e-4495-8414-339f87981ca1", "action_node_types_id": 3, "yaml": "", "sort_no": 0, "use_count": 0, "inputs": [{ "id": 8, "name": "cron", "input_type": "input", "description": "示例:\r\n- cron: '20 8 * * *'", "is_required": true, "value": "- corn: '0 10 * * *'" }], "x": 586, "y": 165.328125, "label": "on-schedule", "img": "https://testforgeplus.trustie.net/api/attachments/0445403c-5d9e-4495-8414-339f87981ca1", "isCluster": false, "type": "rect-node", "size": [110, 36], "labelCfg": { "style": { "fill": "transparent", "fontSize": 0, "boxShadow": "0px 0px 12px rgba(75, 84, 137, 0.05)", "overflow": "hidden", "x": -20, "y": 0, "textAlign": "left", "textBaseline": "middle" } }, "style": { "active": { "fill": "rgb(247, 250, 255)", "stroke": "rgb(95, 149, 255)", "lineWidth": 2, "shadowColor": "rgb(95, 149, 255)", "shadowBlur": 10 }, "selected": { "fill": "rgb(255, 255, 255)", "stroke": "rgb(95, 149, 255)", "lineWidth": 4, "shadowColor": "rgb(95, 149, 255)", "shadowBlur": 10, "text-shape": { "fontWeight": 500 } }, "highlight": { "fill": "rgb(223, 234, 255)", "stroke": "#4572d9", "lineWidth": 2, "text-shape": { "fontWeight": 500 } }, "inactive": { "fill": "rgb(247, 250, 255)", "stroke": "rgb(191, 213, 255)", "lineWidth": 1 }, "disable": { "fill": "rgb(250, 250, 250)", "stroke": "rgb(224, 224, 224)", "lineWidth": 1 }, "nodeSelected": { "fill": "red", "shadowColor": "red", "stroke": "red", "text-shape": { "fill": "red", "stroke": "red" } }, "fill": "#fff", "stroke": "transparent", "cursor": "pointer", "radius": 10, "overflow": "hidden", "lineWidth": 0.5, "shadowColor": "rgba(75,84,137,0.05)", "shadowBlur": 12 }, "cron": "- corn: '0 10 * * *'", "depth": 0 }, { "id": "actions/setup-node@v3-257f29d", "name": "node", "full_name": "actions/setup-node@v3", "description": "", "icon": "https://testforgeplus.trustie.net/api/attachments/c4774fc1-ecd9-47fd-9878-1847bdaf98f6", "action_node_types_id": 1, "yaml": "", "sort_no": 0, "use_count": 0, "inputs": [{ "id": 2, "name": "node-version", "input_type": "select", "is_required": false, "value": 55 }], "x": 608, "y": 357.328125, "label": "node", "img": "https://testforgeplus.trustie.net/api/attachments/c4774fc1-ecd9-47fd-9878-1847bdaf98f6", "isCluster": false, "type": "rect-node", "size": [110, 36], "labelCfg": { "style": { "fill": "transparent", "fontSize": 0, "boxShadow": "0px 0px 12px rgba(75, 84, 137, 0.05)", "overflow": "hidden", "x": -20, "y": 0, "textAlign": "left", "textBaseline": "middle" } }, "style": { "active": { "fill": "rgb(247, 250, 255)", "stroke": "rgb(95, 149, 255)", "lineWidth": 2, "shadowColor": "rgb(95, 149, 255)", "shadowBlur": 10 }, "selected": { "fill": "rgb(255, 255, 255)", "stroke": "rgb(95, 149, 255)", "lineWidth": 4, "shadowColor": "rgb(95, 149, 255)", "shadowBlur": 10, "text-shape": { "fontWeight": 500 } }, "highlight": { "fill": "rgb(223, 234, 255)", "stroke": "#4572d9", "lineWidth": 2, "text-shape": { "fontWeight": 500 } }, "inactive": { "fill": "rgb(247, 250, 255)", "stroke": "rgb(191, 213, 255)", "lineWidth": 1 }, "disable": { "fill": "rgb(250, 250, 250)", "stroke": "rgb(224, 224, 224)", "lineWidth": 1 }, "nodeSelected": { "fill": "red", "shadowColor": "red", "stroke": "red", "text-shape": { "fill": "red", "stroke": "red" } }, "fill": "#fff", "stroke": "transparent", "cursor": "pointer", "radius": 10, "overflow": "hidden", "lineWidth": 0.5, "shadowColor": "rgba(75,84,137,0.05)", "shadowBlur": 12 }, "depth": 0, "node-version": 55 }], "edges": [{ "source": "on-schedule-2fcf505", "target": "actions/setup-node@v3-257f29d", "style": { "active": { "stroke": "rgb(95, 149, 255)", "lineWidth": 1 }, "selected": { "stroke": "rgb(95, 149, 255)", "lineWidth": 2, "shadowColor": "rgb(95, 149, 255)", "shadowBlur": 10, "text-shape": { "fontWeight": 500 } }, "highlight": { "stroke": "rgb(95, 149, 255)", "lineWidth": 2, "text-shape": { "fontWeight": 500 } }, "inactive": { "stroke": "rgb(234, 234, 234)", "lineWidth": 1 }, "disable": { "stroke": "rgb(245, 245, 245)", "lineWidth": 1 }, "endArrow": { "path": "M 6,0 L 9,-1.5 L 9,1.5 Z", "d": 4.5, "fill": "#CDD0DC" }, "cursor": "pointer", "lineWidth": 1, "opacity": 1, "stroke": "#CDD0DC", "radius": 1 }, "nodeStateStyle": { "hover": { "opacity": 1, "stroke": "#8fe8ff" } }, "labelCfg": { "autoRotate": true, "style": { "fontSize": 10, "fill": "#FFF" } }, "id": "edge-0.96904321945951241716516719464", "startPoint": { "x": 586, "y": 183.578125, "anchorIndex": 1 }, "endPoint": { "x": 608, "y": 339.078125, "anchorIndex": 0 }, "sourceAnchor": 1, "targetAnchor": 0, "type": "cubic-vertical", "curveOffset": [0, 0], "curvePosition": [0.5, 0.5], "minCurveOffset": [0, 0] }], "combos": [] } end end