diff --git a/api_document.md b/api_document.md index 4a30f6d0..b39eb25a 100644 --- a/api_document.md +++ b/api_document.md @@ -3361,9 +3361,10 @@ http://localhost:3000/api/jasder/forge/get_trustie_pipeline.json | jq PUT /api/:owner/:repo/update_trustie_pipeline ``` *示例* + ```bash curl -X GET \ -http://localhost:3000/api/jasder/forge/update_trustie_pipeline.json | jq +http://localhost:3000/api/jasder/forge/update_trustie_pipeline.json?pipeline_id=1 | jq ``` *请求参数说明:* @@ -3940,6 +3941,48 @@ http://localhost:3000/api/users/ci/cloud_account | jq } } ``` +------ + +#### 绑定CI服务器-Trustie提供服务器 + +``` +POST /api/users/ci/cloud_account/trustie_bind +``` + +*示例* + +```bash +curl -X POST \ +-d "account=xx" \ +https://localhost:3000/api/users/ci/cloud_account/trustie_bind.json | jq +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ------- | ---- | ------ | ---------- | +| account | 是 | string | 登录用户名 | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------------ | ------ | --------------------------------------- | +| step | int | 0: 未绑定;1: 未认证(已绑定),2: 已认证 | +| ip | string | ci服务器ip | +| redirect_url | string | 认证地址 | + +返回值 + +```json +{ + "step": 0, + "cloud_account": { + "ip": "xxx.xxx.xxx.x", + "redirect_url": "http://localhost:3000/login" + } +} +``` + --- #### 绑定CI服务器 @@ -3982,10 +4025,616 @@ https://localhost:3000/api/users/ci/cloud_account/bind.json | jq } } ``` +------ + +#### 流水线查询 + +``` +GET /api/ci/pipelines/list?identifier={identifier} +``` + +*示例* + +```bash +curl -X GET \ +http://localhost:3000/api/ci/pipelines/list.json?identifier="xxx" | jq +``` + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------------- | ------ | --------------- | +| id | int | 流水线id | +| pipeline_name | string | 流水线名称 | +| file_name | string | 流水线文件名 | +| created_at | string | 创建时间 | +| sync | int | 是否同步到gitea | + +返回值 + +```json +{ + "pipelines": [ + { + "id": 1, + "pipeline_name": "2020-01-08 流水线", + "file_name": ".trustie.pipeline.yaml", + "created_at": "2021-01-08 04:16:24", + "updated_at": "2021-01-08 04:16:24" + } + ] +} +``` + --- +#### 流水线新增 -### 解除CI服务器绑定 +``` +POST /api/ci/pipelines +``` + +*示例* + +```bash +curl --location --request POST 'http://localhost:3000/api/ci/pipelines' \ +--header 'Content-Type: application/json' \ +--data-raw ' { + "pipeline_name": "流水线 2021-01-12", + "file_name": ".trustie.pipeline.yaml", + "identifier": "xxx" +}' +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ------------- | ---- | ------ | ---------------------------------------------- | +| pipeline_name | 是 | string | 流水线名称 | +| file_name | 是 | string | 文件名称(默认初始值:.trustie.pipeline.yaml) | +| identifier | 是 | string | 项目identifier | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | -------------- | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | +| id | int | 新增流水线的id | + +返回值 + +```json +{ + "status": 0, + "message": "success", + "id": 18 +} +``` + +------ + +#### 流水线更新 + +修改流水线名称时调用。 + +``` +PUT /api/ci/pipelines/{id} +``` + +*示例* + +```bash +curl --location --request PUT 'http://localhost:3000/api/ci/pipelines/3' \ +--header 'Content-Type: application/json' \ +--data-raw ' { + "pipeline_name": "2020-01-11 流水线" +}' +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ------------- | ---- | ------ | ---------- | +| id | 是 | id | 流水线id | +| pipeline_name | 是 | string | 流水线名称 | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 流水线删除 + +``` +DELETE /api/ci/pipelines/{id} +``` + +*示例* + +```bash +curl -X DELETE \ +https://localhost:3000/api/ci/pipelines/1 | jq +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ---- | -------- | +| id | 是 | int | 流水线id | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 流水线的阶段查询 + +``` +GET /api/ci/pipelines/{id}/stages +``` + +*示例* + +```bash +curl --location --request GET 'http://localhost:3000/api/ci/pipelines/19/stages.json' +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ------ | ---- | ---- | -------- | +| id | 是 | int | 流水线id | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ----------- | ------ | -------- | +| stages | arr | 阶段数组 | +| stage_name | string | 阶段名称 | +| stage_type | string | 阶段类型 | +| pipeline_id | int | 流水线id | +| show_index | int | 排序 | + +返回值 + +```json +{ + "stages": [ + { + "id": 37, + "stage_name": "初始化", + "stage_type": "init", + "pipeline_id": 19, + "show_index": 1, + "created_at": "2021-01-12T15:18:00.000+08:00", + "updated_at": "2021-01-12T15:18:00.000+08:00" + }, + { + "id": 38, + "stage_name": "编译构建", + "stage_type": "build", + "pipeline_id": 19, + "show_index": 2, + "created_at": "2021-01-12T15:18:00.000+08:00", + "updated_at": "2021-01-12T15:18:00.000+08:00" + } + ] +} +``` + +------ + +#### 确认阶段流水线完整内容查询 + +``` +GET /api/ci/pipelines/{id}/content?owner={owner}&repo={repo} +``` + +*示例* + +```bash +curl -X GET \ +http://localhost:3000/api/ci/pipelines/1/content.json?owner=xx&repo=xx | jq +``` + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ---------------- | +| content | String | 流水线内容 | +| sync | int | 同步状态 | +| owner | string | 用户登录名 | +| repo | string | 项目的identifier | + +返回值 + +```json +{ + "content": "#pipeline \nkind: pipeline\r\nname: maven项目-镜像仓库\r\n\r\nplatform:\r\n os: linux\r\n arch: arm64\nsteps:\n- name: Maven编译\r\n image: arm64v8/maven\r\n commands:\r\n - mvn install\n- name: 编译镜像-推送到仓库\r\n image: plugins/docker\r\n settings:\r\n username: moshenglv\r\n password: RL9UB5P7Jtzukka\r\n repo: docker.io/moshenglv/demo\r\n tags: latest\n", + "sync": 1, + "sha":"xxxxx" +} +``` + +------ + +#### 流水线阶段新增 + +``` +POST /api/ci/pipelines/{id}/create_stage +``` + +*示例* + +```bash +curl --location --request POST 'http://localhost:3000/api/ci/pipelines/19/create_stage.json' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "stage_name": "新阶段2", + "show_index": 2 +}' +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ---------- | ---- | ------ | -------- | +| id | 是 | int | 流水线id | +| show_index | 是 | int | 阶段排序 | +| stage_name | 是 | string | 阶段名称 | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 流水线阶段更新 + +``` +PUT /api/ci/pipelines/{id}/{stage_id}/update_stage +``` + +*示例* + +```bash +curl --location --request PUT 'http://localhost:3000/api/ci/pipelines/1/5/update_stage.json' \ +--header 'Content-Type: application/json' \ +--data-raw ' { + "stage_name": "新阶段-更新" +}' +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ---------- | ---- | ------ | -------------------------------- | +| id | 是 | int | 流水线id | +| stage_name | 是 | string | 阶段名称(默认为 阶段名-模板名) | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 流水线阶段删除 + +``` +DELETE /api/ci/pipelines/{id}/{stage_id}/delete_stage?show_index={index} +``` + +*示例* + +```bash +curl --location --request DELETE 'http://localhost:3000/api/ci/pipelines/19/42/delete_stage.json?show_index=2' \ +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ---------- | ---- | ---- | ---------------------- | +| id | 是 | int | 流水线id | +| stage_id | 是 | int | 阶段id | +| show_index | 是 | int | 被删除阶段的show_index | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 流水线阶段步骤查询 + +``` +GET /api/ci/pipelines/{id}/{stage_id}/steps.json +``` + +*示例* + +```bash +curl -X GET \ +http://localhost:3000/api/ci/pipelines/1/2/steps.json | jq +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ---- | ---- | -------- | +| id | 是 | int | 流水线id | +| stage_id | 是 | int | 阶段id | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ---------- | ------ | ------------------ | +| id | int | 步骤id | +| step_name | string | 步骤名称 | +| stage_id | int | 所属阶段id | +| show_index | int | 显示顺序 | +| content | String | 步骤内容 | +| template | Object | 步骤对应的模板对象 | + +返回值 + +```json +{ + "steps": [ + { + "id": 1, + "step_name": "编译构建-maven", + "stage_id": 2, + "show_index": 0, + "content": "- name: Maven编译\r\n image: arm64v8/maven\r\n", + "created_at": "2021-01-11T09:57:17.000+08:00", + "updated_at": "2021-01-11T09:57:17.000+08:00", + "template": { + "id": 3, + "template_name": "maven", + "stage_type": "build", + "category": "java", + "content": "- name: maven\r\n image: maven:3-jdk-10\r\n", + "created_at": "2021-01-11T17:28:34.000+08:00", + "updated_at": "2021-01-11T17:28:36.000+08:00" + } + } + ] +} +``` + +------ + +#### 流水线阶段步骤新增/更新 + +``` +POST /api/ci/pipelines/{id}/{stage_id}/stage_step +``` + +*示例* + +```bash +curl --location --request POST 'http://localhost:3000/api/ci/pipelines/1/2/stage_step.json' \ +--header 'Content-Type: application/json' \ +--data-raw ' {"steps":[{ + "id":7, + "step_name": "编译构建11-gradle", + "show_index": 1, + "content": "xxxxxxxxxxx", + "template_id":2 +} +] + }' +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ---------------- | ---- | ------ | -------------------------------- | +| steps | 是 | arr | 需要更新step数组 | +| id | 是 | int | 流水线id | +| stage_id | 是 | int | 阶段id | +| id(数组中的id) | 否 | int | 步骤id(存在则更新,不存在新增) | +| step_name | 是 | string | 阶段名称(阶段名-模板名) | +| content | 是 | string | 步骤内容 | +| template_id | 是 | int | 模板id | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 流水线阶段步骤删除 + +``` +DELETE /api/ci/pipelines/{id}/{stage_id}/{step_id}/delete_step +``` + +*示例* + +```bash +curl -X DELETE \ +https://localhost:3000/api/ci/pipelines/1/6/2/delete_stage.json | jq +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| -------- | ---- | ---- | -------- | +| id | 是 | int | 流水线id | +| stage_id | 是 | int | 阶段id | +| step_id | 是 | int | 步骤id | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------- | ------ | ------------ | +| status | int | 状态码 0成功 | +| message | string | 返回消息 | + +返回值 + +```json +{ + "status": 0, + "message": "success" +} +``` + +------ + +#### 阶段模板查询 + +``` +GET /api/ci/templates/templates_by_stage?stage_type={stage_type} +``` + +*示例* + +```bash +curl -X GET \ +http://localhost:3000/api/ci/templates/templates_by_stage.json?stage_type=build | jq +``` + +*请求参数说明:* + +| 参数名 | 必选 | 类型 | 说明 | +| ---------- | ---- | ------ | ------------------------------------- | +| stage_type | 是 | string | 阶段类型:init/build/deploy/customize | + +*返回参数说明:* + +| 参数名 | 类型 | 说明 | +| ------------- | ------ | ---------------- | +| category | string | 分类名称 | +| templates | arr | 分类下的模板列表 | +| id | int | 模板id | +| template_name | string | 模板名称 | +| content | String | 模板内容 | + +返回值 + +```json +[ + { + "category": "java", + "templates": [ + { + "id": 3, + "template_name": "maven", + "stage_type": "build", + "category": "java", + "content": "#maven", + "created_at": "2021-01-11T17:28:34.000+08:00", + "updated_at": "2021-01-11T17:28:36.000+08:00" + }, + { + "id": 4, + "template_name": "gradle", + "stage_type": "build", + "category": "java", + "content": "#gradle", + "created_at": "2021-01-11T17:28:34.000+08:00", + "updated_at": "2021-01-11T17:28:36.000+08:00" + } + ] + }, + { + "category": "c++", + "templates": [ + { + "id": 5, + "template_name": "make", + "stage_type": "build", + "category": "c++", + "content": "#make", + "created_at": "2021-01-11T17:29:17.000+08:00", + "updated_at": "2021-01-11T17:29:18.000+08:00" + } + ] + } +] +``` + +------ + + +#### 解除CI服务器绑定 ``` DELETE /api/users/ci/cloud_account/unbind ``` diff --git a/app/controllers/ci/base_controller.rb b/app/controllers/ci/base_controller.rb index c17105cd..5aedbddb 100644 --- a/app/controllers/ci/base_controller.rb +++ b/app/controllers/ci/base_controller.rb @@ -63,6 +63,7 @@ class Ci::BaseController < ApplicationController if current.ci_cloud_account.server_type == Ci::CloudAccount::SERVER_TYPE_TRUSTIE connect_to_trustie_ci_database(options) else + options = options.merge(db_name: current.login) connect_to_ci_database(options) end diff --git a/app/controllers/ci/pipelines_controller.rb b/app/controllers/ci/pipelines_controller.rb new file mode 100644 index 00000000..19460fde --- /dev/null +++ b/app/controllers/ci/pipelines_controller.rb @@ -0,0 +1,253 @@ +class Ci::PipelinesController < Ci::BaseController + + before_action :require_login, only: %i[list create] + skip_before_action :connect_to_ci_db + before_action :load_project, only: %i[content create_trustie_pipeline] + before_action :load_repository, only: %i[create_trustie_pipeline] + + # ======流水线相关接口========== # + def list + @pipelines = Ci::Pipeline.where('identifier=?', params[:identifier]) + end + + def create + ActiveRecord::Base.transaction do + pipeline = Ci::Pipeline.new(pipeline_name: params[:pipeline_name], file_name: params[:file_name], login: current_user.login, identifier: params[:identifier]) + pipeline.save! + + # 默认创建四个初始阶段 + init_stages = Ci::PipelineStage::INIT_STAGES + index = 1 + init_stages.each do |type, name| + pipeline.pipeline_stages.build( + stage_name: name, + stage_type: type, + show_index: index + ).save! + index += 1 + end + render_ok({id: pipeline.id}) + end + rescue Exception => ex + render_error(ex.message) + end + + def update + pipeline = Ci::Pipeline.find(params[:id]) + if pipeline + pipeline.update!(pipeline_name: params[:pipeline_name]) + end + render_ok + rescue Exception => ex + render_error(ex.message) + end + + def destroy + pipeline = Ci::Pipeline.find(params[:id]) + if pipeline + pipeline.destroy! + end + render_ok + rescue Exception => ex + render_error(ex.message) + end + + def content + @yaml = "#pipeline \n" + pipeline = Ci::Pipeline.find(params[:id]) + @sync = pipeline.sync + @sha = '' + stages = pipeline.pipeline_stages + if stages && !stages.empty? + init_step = stages.first.pipeline_stage_steps.first + @yaml += init_step.content + "\n" + "steps:\n" + stages = stages.slice(1, stages.size - 1) + unless stages.empty? + stages.each do |stage| + steps = stage.pipeline_stage_steps + next unless steps && !steps.empty? + steps.each do |step| + @yaml += step.content + "\n" + end + end + end + end + if @sync == 1 + @sha = get_pipeline_file_sha(pipeline.file_name) + end + end + + def get_pipeline_file_sha(file_name) + file_path_uri = URI.parse(file_name) + interactor = Repositories::EntriesInteractor.call(@project.owner, @project.identifier, file_path_uri, ref: params[:ref] || "master") + if interactor.success? + file = interactor.result + return file['sha'] + end + end + + def create_trustie_pipeline + pipeline = Ci::Pipeline.find(params[:id]) + sha = get_pipeline_file_sha(pipeline.file_name) + if sha + pipeline.update!(sync: 1) + interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, params[:owner], params.merge(identifier: @project.identifier,sha: sha)) + if interactor.success? + render_ok + else + render_error(interactor.error) + end + else + interactor = Gitea::CreateFileInteractor.call(current_user.gitea_token, @owner.login, content_params) + if interactor.success? + pipeline.update!(sync: 1) + render_ok + else + render_error(interactor.error) + end + end + end + + def content_params + { + filepath: params[:filepath], + branch: params[:branch], + new_branch: params[:new_branch], + content: params[:content], + message: params[:message], + committer: { + email: current_user.mail, + name: current_user.login + }, + identifier: @project.identifier + } + end + + # =========阶段相关接口========= # + def stages + pipeline_id = params[:id] + @pipeline_name = Ci::Pipeline.find(pipeline_id).pipeline_name + @pipeline_stages = Ci::PipelineStage.where('pipeline_id=?', pipeline_id).order('show_index asc') + end + + def create_stage + ActiveRecord::Base.transaction do + # 修改stage排序 + update_stage_index(params[:id], params[:show_index], 1) + pipeline_stage = Ci::PipelineStage.new(stage_name: params[:stage_name], + stage_type: params[:stage_type].blank? ? 'customize' : params[:stage_type], + pipeline_id: params[:id], show_index: params[:show_index]) + pipeline_stage.save! + render_ok + end + rescue Exception => ex + render_error(ex.message) + end + + def update_stage + pipeline_stage = Ci::PipelineStage.find(params[:stage_id]) + if pipeline_stage + pipeline_stage.update!(stage_name: params[:stage_name]) + end + render_ok + rescue Exception => ex + render_error(ex.message) + end + + def delete_stage + ActiveRecord::Base.transaction do + update_stage_index(params[:id], params[:show_index].to_i, -1) + pipeline_stage = Ci::PipelineStage.find(params[:stage_id]) + if pipeline_stage + pipeline_stage.destroy! + end + render_ok + end + rescue Exception => ex + render_error(ex.message) + end + + def update_stage_index(pipeline_id, show_index, diff) + stages = Ci::Pipeline.find(pipeline_id).pipeline_stages + stages.each do |stage| + if stage.show_index >= show_index + stage.update!(show_index: stage.show_index + diff) + end + end + end + + # ========步骤相关接口========= # + def steps + @stage_type = Ci::PipelineStage.find(params[:stage_id]).stage_type + @pipeline_stage_steps = Ci::PipelineStageStep.where('stage_id=?', params[:stage_id]).order('show_index asc') + end + + def stage_step + ActiveRecord::Base.transaction do + steps = params[:steps] + unless steps.empty? + steps.each do |step| + unless step[:template_id] + render_error("请选择模板!") + return + end + if !step[:id] + step = Ci::PipelineStageStep.new(step_name: step[:step_name], stage_id: params[:stage_id], + template_id: step[:template_id], content: step[:content], show_index: step[:show_index]) + step.save! + else + pipeline_stage_step = Ci::PipelineStageStep.find(step[:id]) + pipeline_stage_step.update(step_name: step[:step_name], content: step[:content], + show_index: step[:show_index], template_id: step[:template_id]) + end + end + end + render_ok + end + rescue Exception => ex + render_error(ex.message) + end + + def create_stage_step + ActiveRecord::Base.transaction do + steps = params[:steps] + unless steps.empty? + steps.each do |step| + step = Ci::PipelineStageStep.new(step_name: step[:step_name], stage_id: params[:stage_id], + template_id: step[:template_id], content: step[:content], show_index: step[:show_index]) + step.save! + end + end + render_ok + end + rescue Exception => ex + render_error(ex.message) + end + + def update_stage_step + ActiveRecord::Base.transaction do + steps = params[:steps] + unless steps.empty? + steps.each do |step| + pipeline_stage_step = Ci::PipelineStageStep.find(step[:id]) + if pipeline_stage_step + pipeline_stage_step.update(step_name: step[:step_name], content: step[:content], template_id: step[:template_id]) + end + end + end + render_ok + end + rescue Exception => ex + render_error(ex.message) + end + + def delete_stage_step + pipeline_stage_step = Ci::PipelineStageStep.find(params[:step_id]) + if pipeline_stage_step + pipeline_stage_step.destroy! + end + render_ok + rescue Exception => ex + render_error(ex.message) + end +end diff --git a/app/controllers/ci/templates_controller.rb b/app/controllers/ci/templates_controller.rb new file mode 100644 index 00000000..c77a9144 --- /dev/null +++ b/app/controllers/ci/templates_controller.rb @@ -0,0 +1,56 @@ +class Ci::TemplatesController < ApplicationController + + def list + @templates = Ci::Template.all + end + + def templates_by_stage + stage_type = params[:stage_type] + if stage_type != Ci::PipelineStage::CUSTOMIZE_STAGE_TYPE + @templates = Ci::Template.where("stage_type = ?", stage_type) + # 根据模板类别分组 + @category_templates = @templates.group_by{ |template| template.category } + else + # 自定义阶段,按阶段分类分类返回模板列表 + @templates = Ci::Template.where("stage_type != ?", Ci::PipelineStage::INIT_STAGE_TYPE) + @category_templates = @templates.group_by{ |template| template.parent_category } + end + end + + def create + template = Ci::Template.new(template_name: params[:template_name], + stage_type: params[:stage_type], + category: params[:category], + parent_category: params[:parent_category], + content: params[:content] + ) + template.save! + render_ok + rescue Exception => ex + render_error(ex.message) + end + + def update + template = Ci::Template.find(params[:id]) + template.update!(template_name: params[:template_name], + stage_type: params[:stage_type], + category: params[:category], + parent_category: params[:parent_category], + content: params[:content] + ) + render_ok + rescue Exception => ex + render_error(ex.message) + end + + def destroy + template = Ci::Template.find(params[:id]) + if template + template.destroy! + end + render_ok + rescue Exception => ex + render_error(ex.message) + end + +end diff --git a/app/controllers/concerns/ci/db_connectable.rb b/app/controllers/concerns/ci/db_connectable.rb index 559384a5..e2c8ffef 100644 --- a/app/controllers/concerns/ci/db_connectable.rb +++ b/app/controllers/concerns/ci/db_connectable.rb @@ -17,7 +17,8 @@ module Ci::DbConnectable password: db_config[:password], port: db_config[:port] } - req_params = req_params.merge(database: "#{current_user.login}_#{db_config[:database]}") unless master_db === true + db_name = options[:db_name].blank? ? current_user.login : options[:db_name] + req_params = req_params.merge(database: "#{db_name}_#{db_config[:database]}") unless master_db === true db_params = Ci::Database.get_connection_params(req_params) @connection = Ci::Database.set_connection(db_params).connection diff --git a/app/controllers/repositories_controller.rb b/app/controllers/repositories_controller.rb index 812b95d5..7e708f5e 100644 --- a/app/controllers/repositories_controller.rb +++ b/app/controllers/repositories_controller.rb @@ -95,11 +95,22 @@ class RepositoriesController < ApplicationController if interactor.success? @file = interactor.result # create_new_pr(params) + #如果是更新流水线文件 + if params[:pipeline_id] + update_pipeline(params[:pipeline_id]) + end else render_error(interactor.error) end end + def update_pipeline(pipeline_id) + pipeline = Ci::Pipeline.find(pipeline_id) + if pipeline + pipeline.update!(sync: 1) + end + end + def update_file interactor = Gitea::UpdateFileInteractor.call(current_user.gitea_token, @owner.login, params.merge(identifier: @project.identifier)) if interactor.success? diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb new file mode 100644 index 00000000..9448512b --- /dev/null +++ b/app/models/ci/pipeline.rb @@ -0,0 +1,23 @@ +# == Schema Information +# +# Table name: ci_pipelines +# +# id :integer not null, primary key +# pipeline_name :string(255) not null +# file_name :string(255) not null +# created_at :datetime not null +# updated_at :datetime not null +# pipeline_status :string(50) default("unknown"), not null +# login :string(255) +# sync :integer default("0"), not null +# project_id :integer +# + +class Ci::Pipeline < Ci::LocalBase + validates :pipeline_name, presence: {message: "流水线名称不能为空"} + validates :file_name, presence: {message: "流水线文件名称不能为空"} + validates :identifier, presence: {message: "项目identifier不能为空"} + + has_many :pipeline_stages, -> { reorder(show_index: :asc) }, foreign_key: "pipeline_id", :class_name => 'Ci::PipelineStage', dependent: :destroy + +end diff --git a/app/models/ci/pipeline_stage.rb b/app/models/ci/pipeline_stage.rb new file mode 100644 index 00000000..b34c3e71 --- /dev/null +++ b/app/models/ci/pipeline_stage.rb @@ -0,0 +1,25 @@ +# == Schema Information +# +# Table name: ci_pipeline_stages +# +# id :integer not null, primary key +# stage_name :string(255) not null +# stage_type :string(255) not null +# pipeline_id :integer not null +# show_index :integer default("0"), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Ci::PipelineStage < Ci::LocalBase + + validates :stage_name, presence: {message: "阶段名称不能为空"} + validates :stage_type, presence: {message: "阶段类型不能为空"} + + belongs_to :pipeline, foreign_key: :pipeline_id, :class_name => 'Ci::Pipeline' + has_many :pipeline_stage_steps, -> { reorder(show_index: :asc) }, foreign_key: "stage_id", :class_name => 'Ci::PipelineStageStep', dependent: :destroy + + INIT_STAGES = {init:"初始化", build:"编译构建", deploy:"部署", confirm:"确认"}.freeze + CUSTOMIZE_STAGE_TYPE = 'customize' + INIT_STAGE_TYPE = 'init' +end diff --git a/app/models/ci/pipeline_stage_step.rb b/app/models/ci/pipeline_stage_step.rb new file mode 100644 index 00000000..48a6b332 --- /dev/null +++ b/app/models/ci/pipeline_stage_step.rb @@ -0,0 +1,22 @@ +# == Schema Information +# +# Table name: ci_pipeline_stage_steps +# +# id :integer not null, primary key +# step_name :string(255) not null +# stage_id :integer not null +# template_id :integer +# content :text(65535) +# show_index :integer default("0"), not null +# created_at :datetime not null +# updated_at :datetime not null +# + +class Ci::PipelineStageStep < Ci::LocalBase + + validates :step_name, presence: {message: "步骤名称不能为空"} + validates :stage_id, presence: {message: "阶段id不能为空"} + + belongs_to :pipeline_stage, foreign_key: :stage_id, :class_name => 'Ci::PipelineStage' + +end diff --git a/app/models/ci/template.rb b/app/models/ci/template.rb new file mode 100644 index 00000000..e7be34a3 --- /dev/null +++ b/app/models/ci/template.rb @@ -0,0 +1,23 @@ +# == Schema Information +# +# Table name: ci_templates +# +# id :integer not null, primary key +# template_name :string(255) not null +# stage_type :string(255) not null +# category :string(255) not null +# content :text(65535) not null +# created_at :datetime not null +# updated_at :datetime not null +# parent_category :string(255) +# +# Indexes +# +# index_ci_templates_on_stage_type (stage_type) +# + +class Ci::Template < Ci::LocalBase + validates :template_name, presence: {message: "模板名称不能为空"} + validates :stage_type, presence: {message: "阶段类型不能为空"} + validates :category, presence: {message: "模板类型不能为空"} +end diff --git a/app/views/ci/pipeline_stage_steps/_list.json.jbuilder b/app/views/ci/pipeline_stage_steps/_list.json.jbuilder new file mode 100644 index 00000000..13378397 --- /dev/null +++ b/app/views/ci/pipeline_stage_steps/_list.json.jbuilder @@ -0,0 +1,11 @@ +json.id pipeline_stage_step.id +json.step_name pipeline_stage_step.step_name +json.stage_id pipeline_stage_step.stage_id +json.show_index pipeline_stage_step.show_index +json.content pipeline_stage_step.content +json.template_id pipeline_stage_step.template_id +json.category stage_type == 'customize' ? Ci::Template.find(pipeline_stage_step.template_id).parent_category : Ci::Template.find(pipeline_stage_step.template_id).category +json.created_at pipeline_stage_step.created_at +json.updated_at pipeline_stage_step.updated_at + + diff --git a/app/views/ci/pipeline_stages/_list.json.jbuilder b/app/views/ci/pipeline_stages/_list.json.jbuilder new file mode 100644 index 00000000..a0c8b314 --- /dev/null +++ b/app/views/ci/pipeline_stages/_list.json.jbuilder @@ -0,0 +1,9 @@ +json.id pipeline_stage.id +json.stage_name pipeline_stage.stage_name +json.stage_type pipeline_stage.stage_type +json.pipeline_id pipeline_stage.pipeline_id +json.pipeline_name pipeline_name +json.show_index pipeline_stage.show_index +json.created_at pipeline_stage.created_at +json.updated_at pipeline_stage.updated_at + diff --git a/app/views/ci/pipelines/_list.json.jbuilder b/app/views/ci/pipelines/_list.json.jbuilder new file mode 100644 index 00000000..0113da58 --- /dev/null +++ b/app/views/ci/pipelines/_list.json.jbuilder @@ -0,0 +1,8 @@ +json.id pipeline.id +json.pipeline_name pipeline.pipeline_name +json.pipeline_status pipeline.pipeline_status +json.file_name pipeline.file_name +json.sync pipeline.sync +json.identifier pipeline.identifier +json.created_at pipeline.created_at.strftime("%Y-%m-%d %H:%M:%S") +json.updated_at pipeline.updated_at.strftime("%Y-%m-%d %H:%M:%S") diff --git a/app/views/ci/pipelines/content.json.jbuilder b/app/views/ci/pipelines/content.json.jbuilder new file mode 100644 index 00000000..60d211b9 --- /dev/null +++ b/app/views/ci/pipelines/content.json.jbuilder @@ -0,0 +1,3 @@ +json.content @yaml +json.sync @sync +json.sha @sha \ No newline at end of file diff --git a/app/views/ci/pipelines/list.json.jbuilder b/app/views/ci/pipelines/list.json.jbuilder new file mode 100644 index 00000000..7cfbeb1a --- /dev/null +++ b/app/views/ci/pipelines/list.json.jbuilder @@ -0,0 +1,3 @@ +json.pipelines @pipelines do |pipeline| + json.partial! "/ci/pipelines/list", pipeline: pipeline +end \ No newline at end of file diff --git a/app/views/ci/pipelines/stages.json.jbuilder b/app/views/ci/pipelines/stages.json.jbuilder new file mode 100644 index 00000000..74f27358 --- /dev/null +++ b/app/views/ci/pipelines/stages.json.jbuilder @@ -0,0 +1,3 @@ +json.stages @pipeline_stages do |pipeline_stage| + json.partial! "/ci/pipeline_stages/list", pipeline_stage: pipeline_stage, pipeline_name: @pipeline_name +end \ No newline at end of file diff --git a/app/views/ci/pipelines/steps.json.jbuilder b/app/views/ci/pipelines/steps.json.jbuilder new file mode 100644 index 00000000..e2421e2a --- /dev/null +++ b/app/views/ci/pipelines/steps.json.jbuilder @@ -0,0 +1,3 @@ +json.steps @pipeline_stage_steps do |pipeline_stage_step| + json.partial! "/ci/pipeline_stage_steps/list", pipeline_stage_step: pipeline_stage_step, stage_type: @stage_type +end \ No newline at end of file diff --git a/app/views/ci/templates/_list.json.jbuilder b/app/views/ci/templates/_list.json.jbuilder new file mode 100644 index 00000000..1aff25a4 --- /dev/null +++ b/app/views/ci/templates/_list.json.jbuilder @@ -0,0 +1,8 @@ +json.id template.id +json.template_name template.template_name +json.stage_type template.stage_type +json.category template.category +json.content template.content +json.created_at template.created_at +json.updated_at template.updated_at + diff --git a/app/views/ci/templates/_templates_by_stage.json.jbuilder b/app/views/ci/templates/_templates_by_stage.json.jbuilder new file mode 100644 index 00000000..141e8d5b --- /dev/null +++ b/app/views/ci/templates/_templates_by_stage.json.jbuilder @@ -0,0 +1,4 @@ +json.category category +json.templates templates do |template| + json.partial! "/ci/templates/list", template: template +end \ No newline at end of file diff --git a/app/views/ci/templates/list.json.jbuilder b/app/views/ci/templates/list.json.jbuilder new file mode 100644 index 00000000..3b463057 --- /dev/null +++ b/app/views/ci/templates/list.json.jbuilder @@ -0,0 +1,3 @@ +json.templates @templates do |template| + json.partial! "/ci/templates/list", template: template +end \ No newline at end of file diff --git a/app/views/ci/templates/templates_by_stage.json.jbuilder b/app/views/ci/templates/templates_by_stage.json.jbuilder new file mode 100644 index 00000000..89820794 --- /dev/null +++ b/app/views/ci/templates/templates_by_stage.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @category_templates do |category, templates| + json.partial! "/ci/templates/templates_by_stage", category: category, templates: templates +end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index f2aa0a62..8af453cf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,6 +32,32 @@ Rails.application.routes.draw do end end + resources :templates, only: [:list,:templates_by_stage,:create,:update,:destroy] do + collection do + get :list + get :templates_by_stage + end + end + + resources :pipelines do + collection do + get :list + end + member do + get :content + get :stages + post :create_stage + post :create_trustie_pipeline + delete :delete_stage, :path => ":stage_id/delete_stage", to: 'pipelines#delete_stage' + put :update_stage, :path => ":stage_id/update_stage", to: 'pipelines#update_stage' + get :stage_steps, :path => ":stage_id/steps", to: 'pipelines#steps' + post :create_stage_step, :path => ":stage_id/create_step", to: 'pipelines#create_stage_step' + post :stage_step, :path => ":stage_id/stage_step", to: 'pipelines#stage_step' + delete :delete_stage_step, :path => ":stage_id/:step_id/delete_step", to: 'pipelines#delete_stage_step' + put :update_stage_step, :path => ":stage_id/update_step", to: 'pipelines#update_stage_step' + end + end + # resources :repos, only: :index do # collection do # get 'get_trustie_pipeline', to: 'builds#get_trustie_pipeline', as: 'get_trustie_pipeline' diff --git a/db/migrate/20210108015318_create_ci_templates.rb b/db/migrate/20210108015318_create_ci_templates.rb new file mode 100644 index 00000000..80594513 --- /dev/null +++ b/db/migrate/20210108015318_create_ci_templates.rb @@ -0,0 +1,14 @@ +class CreateCiTemplates < ActiveRecord::Migration[5.2] + def change + create_table :ci_templates do |t| + t.string :template_name, null: false, comment: '模板名称' + t.string :stage_type, null: false, comment: '模板所属阶段类型:init/build/deploy/customize/confirm' + t.string :category, null: false, comment: '模板分类' + t.text :content, null: false, comment: '模板yml内容' + + t.timestamps + end + add_index :ci_templates, [:stage_type] + end +end + diff --git a/db/migrate/20210108055023_create_ci_pipelines.rb b/db/migrate/20210108055023_create_ci_pipelines.rb new file mode 100644 index 00000000..d1427820 --- /dev/null +++ b/db/migrate/20210108055023_create_ci_pipelines.rb @@ -0,0 +1,11 @@ +class CreateCiPipelines < ActiveRecord::Migration[5.2] + def change + create_table :ci_pipelines do |t| + t.string :pipeline_name, null: false, comment: '流水线名称' + t.string :pipeline_status, null: false, comment: 'successed/failed/running/errored/pending/killed/unknown' , default: 'unknown' + t.string :file_name, null: false, comment: '文件名称' + + t.timestamps + end + end +end diff --git a/db/migrate/20210108062016_create_ci_pipeline_stages.rb b/db/migrate/20210108062016_create_ci_pipeline_stages.rb new file mode 100644 index 00000000..8f7365a8 --- /dev/null +++ b/db/migrate/20210108062016_create_ci_pipeline_stages.rb @@ -0,0 +1,12 @@ +class CreateCiPipelineStages < ActiveRecord::Migration[5.2] + def change + create_table :ci_pipeline_stages do |t| + t.string :stage_name, null: false, comment: '阶段名称' + t.string :stage_type, null: false, comment: '阶段类型:init/build/deploy/customize/confirm' + t.integer :pipeline_id, null: false, comment: '阶段所属流水线id' + t.integer :show_index, null: false, comment: '阶段排序', default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20210111013512_create_ci_pipeline_stage_steps.rb b/db/migrate/20210111013512_create_ci_pipeline_stage_steps.rb new file mode 100644 index 00000000..a4e38e13 --- /dev/null +++ b/db/migrate/20210111013512_create_ci_pipeline_stage_steps.rb @@ -0,0 +1,13 @@ +class CreateCiPipelineStageSteps < ActiveRecord::Migration[5.2] + def change + create_table :ci_pipeline_stage_steps do |t| + t.string :step_name, null: false, comment: '步骤名称' + t.integer :stage_id, null: false, comment: '阶段id' + t.integer :template_id, comment: '模板id' + t.text :content + t.integer :show_index, null: false, comment: '阶段排序', default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20210112053516_add_parent_category_to_ci_templates.rb b/db/migrate/20210112053516_add_parent_category_to_ci_templates.rb new file mode 100644 index 00000000..16bcb463 --- /dev/null +++ b/db/migrate/20210112053516_add_parent_category_to_ci_templates.rb @@ -0,0 +1,5 @@ +class AddParentCategoryToCiTemplates < ActiveRecord::Migration[5.2] + def change + add_column :ci_templates, :parent_category, :string + end +end diff --git a/db/migrate/20210118011710_add_login_to_ci_pipelines.rb b/db/migrate/20210118011710_add_login_to_ci_pipelines.rb new file mode 100644 index 00000000..e1e914e5 --- /dev/null +++ b/db/migrate/20210118011710_add_login_to_ci_pipelines.rb @@ -0,0 +1,5 @@ +class AddLoginToCiPipelines < ActiveRecord::Migration[5.2] + def change + add_column :ci_pipelines, :login, :string + end +end diff --git a/db/migrate/20210119025745_add_sync_and_project_id_to_ci_pipelines.rb b/db/migrate/20210119025745_add_sync_and_project_id_to_ci_pipelines.rb new file mode 100644 index 00000000..e0d0d250 --- /dev/null +++ b/db/migrate/20210119025745_add_sync_and_project_id_to_ci_pipelines.rb @@ -0,0 +1,6 @@ +class AddSyncAndProjectIdToCiPipelines < ActiveRecord::Migration[5.2] + def change + add_column :ci_pipelines, :sync, :integer, null: false, comment: '0 未同步到gitea,1 已同步', default: 0 + add_column :ci_pipelines, :identifier, :string + end +end \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 353407a4..dd8544fd 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -3445,4 +3445,12 @@ INSERT INTO `roles` (`id`, `name`, `position`, `assignable`, `builtin`, `permiss (15, 'Contestant', 13, 1, 0, '---\n- :add_project\n- :projects_attachments_download\n- :add_course\n- :course_attachments_download\n- :view_course_files\n- :select_contest_modules\n- :quote_project\n- :contest_attachments_download\n- :notificationcomment_contestnotifications\n- :add_messages\n- :edit_own_messages\n- :delete_own_messages\n- :view_calendar\n- :manage_files\n- :view_files\n- :view_gantt\n- :view_issues\n- :save_queries\n- :browse_repository\n- :view_changesets\n', 'default'); COMMIT; +-- ---------------------------- +-- Records of ci_templates +-- ---------------------------- +BEGIN; +INSERT INTO `ci_templates` VALUES (2,'linux/amd64','init','初始化','kind: pipeline\r\ntype: docker\r\nname: default\r\nplatform:\r\n os: linux\r\n arch: amd64','2021-01-12 02:44:23','2021-01-12 02:44:23',NULL),(3,'linux/arm64','init','初始化','kind: pipeline\r\ntype: docker\r\nname: default\r\nplatform:\r\n os: linux\r\n arch: arm64','2021-01-12 02:45:17','2021-01-12 02:45:17',NULL),(4,'maven','build','Java','- name: maven\r\n image: maven:3-jdk-10\r\n commands:\r\n - mvn install -DskipTests=true','2021-01-12 02:53:29','2021-01-12 02:53:29','编译构建'),(5,'maven单元测试','customize','Java','- name: maven\r\n image: maven:3-jdk-10\r\n commands:\r\n - mvn test','2021-01-12 02:53:29','2021-01-12 02:53:29','单元测试'),(6,'golang单元测试','customize','Golang','- name: golang单元测试\r\n image: golang\r\n commands:\r\n - go test','2021-01-12 03:03:35','2021-01-12 03:03:35','单元测试'),(9,'gradle单元测试','customize','Java','- name: gradle\r\n image: gradle:jdk10\r\n commands:\r\n - gradle test','2021-01-12 03:05:33','2021-01-12 03:05:33','单元测试'),(7,'golang编译','build','Golang','- name: golang编译\r\n image: golang\r\n commands:\r\n - go build','2021-01-12 03:03:35','2021-01-12 03:03:35','编译构建'),(8,'gradle','build','Java','- name: gradle\r\n image: gradle:jdk10\r\n commands:\r\n - gradle build -x test','2021-01-12 03:05:33','2021-01-12 03:05:33','编译构建'),(10,'远程主机部署','deploy','部署','# 根据实际情况修改主机ip、账号、密码\r\n# 需要将软件包与部署脚本提前上传到远程主机(见文件上传模板)\r\n\r\n- name: 远程主机部署\r\n image: appleboy/drone-ssh\r\n settings:\r\n host: 192.168.1.1\r\n username: username\r\n password: \'pasword\'\r\n port: 22\r\n commands:\r\n - chmod +x /home/deploy.sh\r\n - ./home/deploy.sh','2021-01-12 03:32:46','2021-01-12 03:32:46','部署'),(11,'远程命令','customize','工具','- name: 远程命令\r\n image: appleboy/drone-ssh\r\n settings:\r\n host: 192.168.0.1\r\n username: username\r\n password: \'pwd\'\r\n port: 22\r\n script:\r\n - echo \'hello world!\'','2021-01-12 03:40:38','2021-01-12 03:40:38','其他'),(12,'上传文件','customize','工具','# 修改目标服务器的ip、账号密码以及上传路径\r\n# 本模板示例为上传软件包和部署脚本到home目录\r\n\r\n- name: 上传文件\r\n image: appleboy/drone-scp\r\n settings:\r\n host: 192.168.1.1\r\n username: username\r\n password: \'password\'\r\n port: 22\r\n target: /home\r\n source: \r\n - target/*.jar\r\n - deploy.sh','2021-01-12 03:40:55','2021-01-12 03:40:55','其他'),(17,'make-c','build','C语言','- name: 编译\r\n image: gcc\r\n commands:\r\n - ./configure\r\n - make','2021-01-15 01:19:38','2021-01-15 01:19:38','编译构建'),(19,'make-c++','build','C++','- name: 编译构建\r\n image: gcc\r\n commands:\r\n - ./configure\r\n - make','2021-01-15 01:21:05','2021-01-15 01:21:05','编译构建'),(20,'python','build','Python','- name: 编译构建\r\n image: python\r\n commands:\r\n - pip install -r requirements.txt','2021-01-15 01:22:36','2021-01-15 01:22:36','编译构建'),(21,'Docker镜像构建','build','Docker镜像构建','# 构建Docker镜像并推送到仓库\r\n# 定义镜像Hub路径以及账号密码\r\n- name: Docker镜像构建\r\n image: plugins/docker\r\n settings:\r\n username: username\r\n password: pwd\r\n repo: repoUrl\r\n tags: latest','2021-01-15 01:23:16','2021-01-15 01:23:16','编译构建'),(22,'空白模板','customize','customize','','2021-01-15 02:53:02','2021-01-15 02:53:02','其他'),(23,'空白模板','build','自定义','','2021-01-15 03:27:33','2021-01-15 03:27:33','编译构建'); +COMMIT; + + SET FOREIGN_KEY_CHECKS = 1;