class Api::V1::Projects::PipelinesController < Api::V1::BaseController before_action :require_manager_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}.yaml" @pipeline.json = demo.to_json # @pipeline.json = params[:json] if params[:json] @pipeline.yaml = build_pipeline_yaml(@pipeline) @pipeline.save! sha = get_pipeline_file_sha(@pipeline.file_name, @pipeline.branch) tip_exception("#{@pipeline.file_name}已存在") if sha interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params) tip_exception(interactor.error) unless interactor.success? render_ok({ id: @pipeline.id }) end def update @pipeline = Action::Pipeline.find(params[:id]) @pipeline.update!(pipeline_name: params[:pipeline_name]) interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, content_params) tip_exception(interactor.error) unless interactor.success? render_ok end def destroy @pipeline = Action::Pipeline.find(params[:id]) if pipeline interactor = Gitea::DeleteFileInteractor.call(current_user.gitea_token, @owner.login, content_params) 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_yaml) if @pipeline.blank? end def build_pipeline_yaml(pipeline) if pipeline.json.present? @name = pipeline.pipeline_name params_nodes = JSON.parse(pipeline.json)["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["component_name"]) } on_nodes = JSON.parse(pipeline.json)["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["component_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[:yaml] end @pipeline_yaml end def build_yaml @name = "love me" params_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| !["on-push", "on-schedule"].include?(node["component_name"]) } on_nodes = JSON.parse(demo.to_json)["nodes"].select { |node| ["on-push", "on-schedule"].include?(node["component_name"]) } @on_nodes = build_nodes(on_nodes) @steps_nodes = [] params_nodes.each do |input_node| # Rails.logger.info "input_node=====0===#{input_node["component_name"]}======#{input_node["in_parameters"]}" node = Action::Node.find_by(name: input_node["component_name"]) node.cust_name = input_node["component_label"] if input_node["component_label"].present? input_values = {} if input_node["in_parameters"].present? # Rails.logger.info "@in_parameters=====11===#{input_node["component_name"]}======#{input_node["in_parameters"]}" input_node["in_parameters"].each_key do |input_key| # Rails.logger.info "@in_parameters.input_key===#{input_key}" # Rails.logger.info "@in_parameters.input_value===#{input_node["in_parameters"][input_key]["value"]}" input_values = input_values.merge({ "#{input_key.gsub("--", "")}": "#{input_node["in_parameters"][input_key]["value"]}" }) end node.input_values = input_values # Rails.logger.info "@input_values node===#{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(file_name) interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: branch || 'master') if interactor.success? file = interactor.result file['sha'] end end def content_params { filepath: ".gitea/workflows/#{@pipeline.pipeline_name}.yaml", branch: @pipeline.branch, new_branch: @pipeline.branch, content: build_pipeline_yaml(@pipeline), message: 'create 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["component_name"]) node.cust_name = input_node["component_label"] if input_node["component_label"].present? input_values = {} if input_node["in_parameters"].present? # Rails.logger.info "@in_parameters=====11===#{input_node["component_name"]}======#{input_node["in_parameters"]}" input_node["in_parameters"].each_key do |input_key| # Rails.logger.info "@in_parameters.input_key===#{input_key}" # Rails.logger.info "@in_parameters.input_value===#{input_node["in_parameters"][input_key]["value"]}" input_values = input_values.merge({ "#{input_key.gsub("--", "")}": "#{input_node["in_parameters"][input_key]["value"]}" }) end # Rails.logger.info "@input_values node1===#{input_values}" node.input_values = input_values # Rails.logger.info "@input_values node===#{node.input_values.to_json}" end steps_nodes.push(node) end steps_nodes end def demo { "nodes": [ { "id": "git-clone-245734ab", "category_id": 1, "component_name": "on-schedule", "component_label": "触发器", "working_directory": "", "command": "", "in_parameters": { "--cro": { "type": "str", "item_type": "", "label": "push代码", "require": 1, "choice": [], "default": "", "placeholder": "私有仓库填写ssh地址,公有仓库填写https git地址", "describe": "代码仓库地址", "editable": 1, "condition": "", "value": "15 4,5 * * *" }, "--paths-ignore": { "type": "str", "item_type": "", "label": "push代码", "require": 1, "choice": [], "default": "", "placeholder": "私有仓库填写ssh地址,公有仓库填写https git地址", "describe": "代码仓库地址", "editable": 1, "condition": "", "value": "**.md" } }, "out_parameters": { "--code_output": { "type": "str", "label": "代码输出路径", "path": "/code", "require": 1, "value": "/code" } }, "description": "代码拉取组件", "icon_path": "component-icon-1", "create_by": "admin", "create_time": "2024-03-02T05:41:25.000+00:00", "update_by": "admin", "update_time": "2024-03-02T05:41:25.000+00:00", "state": 1, "image": "172.20.32.187/pipeline-component/built-in/git:202312071000", "env_variables": "", "x": 532, "y": 202, "label": "代码拉取", "img": "/assets/images/component-icon-1.png", "isCluster": false, "type": "rect-node", "size": [110, 36], "--code_path": "https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git", "--branch": "train_ci_test", "--depth": "1", "--code_output": "/code" }, { "id": "git-clone-245734ab", "category_id": 1, "component_name": "git-clone", "component_label": "代码拉取", "working_directory": "", "command": "", "in_parameters": { }, "out_parameters": { "--code_output": { "type": "str", "label": "代码输出路径", "path": "/code", "require": 1, "value": "/code" } }, "description": "代码拉取组件", "icon_path": "component-icon-1", "create_by": "admin", "create_time": "2024-03-02T05:41:25.000+00:00", "update_by": "admin", "update_time": "2024-03-02T05:41:25.000+00:00", "state": 1, "image": "172.20.32.187/pipeline-component/built-in/git:202312071000", "env_variables": "", "x": 532, "y": 202, "label": "代码拉取", "img": "/assets/images/component-icon-1.png", "isCluster": false, "type": "rect-node", "size": [110, 36], "--code_path": "https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git", "--branch": "train_ci_test", "--depth": "1", "--code_output": "/code" }, { "id": "git-clone-245734ab", "category_id": 1, "component_name": "setup-java", "component_label": "安装java环境", "working_directory": "", "command": "", "in_parameters": { "--distribution": { "type": "str", "item_type": "", "label": "代码仓库地址", "require": 1, "choice": [], "default": "", "placeholder": "私有仓库填写ssh地址,公有仓库填写https git地址", "describe": "代码仓库地址", "editable": 1, "condition": "", "value": "jdkfile" }, "--java-version": { "type": "str", "item_type": "", "label": "代码分支/tag", "require": 1, "choice": [], "default": "master", "placeholder": "", "describe": "代码分支或者tag", "editable": 1, "condition": "", "value": "11.0.0" }, "--architecture": { "type": "str", "item_type": "", "label": "克隆深度", "require": 0, "choice": [], "default": "1", "placeholder": "", "describe": "代码克隆深度", "editable": 1, "condition": "", "value": "x64" }, "--mvn-toolchain-vendor": { "type": "str", "item_type": "", "label": "ssh私钥", "require": 0, "choice": [], "default": "1", "placeholder": "", "describe": "ssh私钥,确保ssh公钥已经托管到代码平台,否则可能拉取失败", "editable": 1, "value": "Oracle" } }, "out_parameters": { "--code_output": { "type": "str", "label": "代码输出路径", "path": "/code", "require": 1, "value": "/code" } }, "description": "代码拉取组件", "icon_path": "component-icon-1", "create_by": "admin", "create_time": "2024-03-02T05:41:25.000+00:00", "update_by": "admin", "update_time": "2024-03-02T05:41:25.000+00:00", "state": 1, "image": "172.20.32.187/pipeline-component/built-in/git:202312071000", "env_variables": "", "x": 532, "y": 202, "label": "代码拉取", "img": "/assets/images/component-icon-1.png", "isCluster": false, "type": "rect-node", "size": [110, 36], "--code_path": "https://openi.pcl.ac.cn/somunslotus/somun202304241505581.git", "--branch": "train_ci_test", "--depth": "1", "--code_output": "/code" }, { "id": "git-clone-245734ab", "category_id": 1, "component_name": "shell", "component_label": "执行shell命令", "working_directory": "", "command": "", "in_parameters": { "--run": { "type": "str", "item_type": "", "label": "代码仓库地址", "require": 1, "choice": [], "default": "", "placeholder": "私有仓库填写ssh地址,公有仓库填写https git地址", "describe": "代码仓库地址", "editable": 1, "condition": "", "value": "service nginx restart" } } }, { "id": "git-clone-245734ab", "category_id": 1, "component_name": "scp", "component_label": "scp", "working_directory": "", "command": "", "in_parameters": { "--run": { "type": "str", "item_type": "", "label": "代码仓库地址", "require": 1, "choice": [], "default": "", "placeholder": "私有仓库填写ssh地址,公有仓库填写https git地址", "describe": "代码仓库地址", "editable": 1, "condition": "", "value": "service nginx restart" } } } ], # "edges": [ # { # "source": "git-clone-245734ab", # "target": "model-train-09b1491", # "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.11773197923997381714446043619", # "startPoint": { # "x": 532, # "y": 220.25, # "anchorIndex": 1 # }, # "endPoint": { # "x": 530, # "y": 304.75, # "anchorIndex": 0 # }, # "targetAnchor": 0, # "type": "cubic-vertical", # "curveOffset": [0, 0], # "curvePosition": [0.5, 0.5], # "minCurveOffset": [0, 0], # "depth": 0 # }, # { # "source": "model-train-09b1491", # "target": "model-evaluate-b401ff0", # "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.28238605806531771714446047075", # "startPoint": { # "x": 530, # "y": 341.25, # "anchorIndex": 1 # }, # "endPoint": { # "x": 520, # "y": 431.75, # "anchorIndex": 0 # }, # "targetAnchor": 0, # "type": "cubic-vertical", # "curveOffset": [0, 0], # "curvePosition": [0.5, 0.5], # "minCurveOffset": [0, 0], # "depth": 0 # } # ] } end end