4.3_流水线任务扩展参考
一、数据库脚本定义实现
注意:不要和系统提供的主键冲突。
DPS_ENGINE_STAGE_TEMPLATE表: 流水线任务模板定义表,可以定义任务名称,任务类型,任务图标等。注意:任务名称要保证唯一,不能冲突。
create table DPS_ENGINE_STAGE_TEMPLATE ( STAGE_TP_ID varchar(64) not null comment '唯一标识', STAGE_TP_NAME varchar(64) comment '任务名称', STAGE_TP_LABEL varchar(64) comment '任务标签名称', STAGE_TP_TYPE varchar(64) comment '任务类型', STAGE_TP_TYPE_LABEL varchar(64) comment '任务类型标签名称', STAGE_TAGS varchar(512) comment '任务标签', STAGE_TP_ICON varchar(2048) comment '任务ICON路径', STAGE_HANDLER text comment '任务拦截器类名,实现接口com.primeton.devops.specs.api.engine.service.IStageHandler', ESTIMATED_DURATION integer comment '预估持续时间', DESCRIPTION text comment '任务描述', COMMON_STAGE_TPS varchar(1024) comment '公共任务模板列表,逗号分隔', primary key (STAGE_TP_ID) ) comment='任务模板表';
DPS_ENGINE_STAGE_ATTRIBUTE_DEFINITION表: 流水线任务属性定义表,定义流水线任务有哪些属性。其中disable(禁用)和failedContinue(失败后继续运行)是常规高级属性,必须要有。
create table DPS_ENGINE_STAGE_ATTRIBUTE_DEFINITION ( ATTR_DEF_ID varchar(64) not null comment '唯一标识', ATTR_DEF_NAME varchar(64) comment '任务属性定义名称', ATTR_DEF_LABEL varchar(64) comment '任务属性定义标签名称', STAGE_TP_ID varchar(64) comment '任务模板ID', SOURCE char default '0' comment '任务属性来源类型,是否是用户自定义, 默认是0,目前没用', SORT int comment '任务属性顺序', CATEGORY varchar(64) comment '任务属性类别', IS_REQUIRED boolean comment '是否必须', IS_INHERITABLE boolean comment '是否继承,目前没用', IS_ENCRYPTED boolean comment '是否加密,目前没用', IS_IMMUTABLE boolean comment '是否不可改变,目前没用', DISPLAY_FORMAT text comment '显示格式,目前没用', TIP text comment '提示信息', DEFAULT_VALUE text comment '默认值', FIELD_TYPE varchar(64) comment '字段类型,均设置为string', CONTROL_TYPE varchar(64) comment '控件类型', VALUE_PROVIDER text comment '值提供者', OPTIONS text comment '控件其它相关信息', CHECK_POLICY varchar(64) comment '校验策略,目前没用', primary key (ATTR_DEF_ID) ) comment='任务属性定义表';
示例如下:
/* 流水线任务定义 : 脚本执行 */ NSERT INTO DPS_ENGINE_STAGE_TEMPLATE (STAGE_TP_ID, STAGE_TP_NAME, STAGE_TP_LABEL, STAGE_TP_TYPE, STAGE_TP_TYPE_LABEL, STAGE_HANDLER, ESTIMATED_DURATION, DESCRIPTION, STAGE_TAGS, STAGE_TP_ICON, COMMON_STAGE_TPS) VALUES ('1000', 'script-exec', '脚本执行', 'tool', '工具', NULL, 1000, '运行脚本', 'build,deploy', '/static/images/component_logo/script-exec.svg', 'all-common'); /* 流水线任务属性定义: 脚本执行 */ INSERT INTO DPS_ENGINE_STAGE_ATTRIBUTE_DEFINITION (ATTR_DEF_ID, ATTR_DEF_NAME, ATTR_DEF_LABEL, STAGE_TP_ID, SORT, CATEGORY, IS_REQUIRED, TIP, DEFAULT_VALUE, CONTROL_TYPE, VALUE_PROVIDER, OPTIONS, CHECK_POLICY) VALUES ('10000000', 'scriptType', '脚本类型', '1000', 1, '1:基本信息', 1, NULL, 'shell', 'dict', '{"type":"dictcombobox","dictTypeId":"DPS_CD_PIPELINE_SCRIPT_TYPE","eventName":"scriptTypeChange"}', NULL, NULL), ('10000001', 'scriptUrl', '脚本URL', '1000', 2, '1:基本信息', NULL, '远程或者本地的脚本文件', NULL, 'large-select', '{"url":"api/ci/buildartifacts?projectId=:projectId","textField":"artifactUrl","valueField":"artifactUrl","value":"","multiSelect":false,"filters":[{"label":"名称","field":"artifactName"}],"tableColumnFields":[{"label":"名称","field":"artifactName"},{"label":"URL","field":"artifactUrl"}],"valueDetailSearchField":"artifactUrl"}', NULL, NULL), ('10000002', 'scriptContent', '脚本内容', '1000', 3, '1:基本信息', NULL, NULL, 'echo "hello"', 'editor', '{"type":"bat"}', NULL, NULL), ('10000010', 'scriptParams', '脚本参数', '1000', 0, '2:高级', NULL, 'Map格式的Json串,替换脚本中同名的变量,示例:{"name1": "value1"}', '{ }', 'editor', '{"type":"json"}', NULL, NULL), ('10000100', 'resources', '目标选择', '1000', 1, '2:高级', NULL, '脚本执行所在的机器,如果为空,默认是Jenkins本地', NULL, 'large-select', '{"url":"api/cd/project-resources?projectId=:projectId&envType=:envType&resourceType=host","textField":"resourceName,hostAddr","valueField":"resourceId","value":"","multiSelect":true,"filters":[{"label":"名称","field":"resourceName"}],"tableColumnFields":[{"label":"名称","field":"resourceName"},{"label":"IP地址","field":"hostAddr"}],"valueDetailSearchField":"resourceId"}', NULL, NULL), ('10000101', 'userDir', '目标用户目录', '1000', 2, '2:高级', NULL, '用户的权限目录', '/opt/idc/apps', 'textbox', NULL, NULL, NULL), ('10000104', 'evalAttrs', '计算属性值', '1000', 5, '2:高级', NULL, '是否使用变量计算属性值', 'true', 'checkbox', NULL, NULL, NULL);
VALUE_PROVIDER字段规则如下:
1、JSON字符串 2、dict控件类型 示例:{"type":"dictcombobox","dictTypeId":"DPS_CD_PIPELINE_DATABASE_TYPE"} 3、combox控件类型 1)示例:url中可以使用:varName进行变量引用拼串,现在默认的变量有projectId,envType,当前控件名字,以及valueFiled对应的名字等 {"url":"api/spm/components?projectId=:projectId&componentCategory=data","textField":"componentName","valueField":"componentName","value":"", "multiSelect":false, "dataField":"data"} 2)联动示例:childName是联动的控件的名字 {"url":"api/spm/components?projectId=:projectId&componentCategory=data","textField":"componentName","valueField":"componentName","value":"", "multiSelect":false, "dataField":"data", "childName":"artifactVersion", "childUrl":"api/ci/buildartifacts/:componentName?projectId=:projectId"} 4、editor控件类型 5、larget-select控件类型 示例:{"url":"api/cd/project-resources?projectId=:projectId&envType=:envType&resourceType=host","textField":"resourceName,hostAddr","valueField":"resourceId","value":"","multiSelect":true,"filters":[{"label":"名称","field":"resourceName"}],"tableColumnFields":[{"label":"名称","field":"resourceName"},{"label":"IP地址","field":"hostAddr"}],"valueDetailSearchField":"resourceId"}
二、groovy执行脚本实现
groovy执行脚本存放于构件包的META-INF/pipeline/stages目录下
脚本要放在任务类型名称命名的目录下,脚本名称要和任务模板名称相同,脚本中的方法名要和脚本名称类似,要把中杠变成下划线。注意:方法名不能冲突。
如果脚本名称和系统的重名,则会覆盖系统实现。
可以直接使用jenkins提供的groovy方法,也可以直接使用系统提供的common.groovy脚本中的方法。
可以使用//import_files("stages/tool/methods/upload.groovy")引用其他groovy脚本中的方法,要从stages目录开始。
示例如下:tool/script-exec.groovy脚本
def script_exec(pipelineContext, pipelineResult, stageAttrs, stageResult)> { if (isNullOrBlank(stageAttrs.scriptContent) && isNullOrBlank(stageAttrs.scriptUrl)) { return } if (isNullOrBlank(stageAttrs.scriptType)) { error "Script Type is null!" } try { this."script_exec_${stageAttrs.scriptType}"(pipelineContext, pipelineResult, stageAttrs, stageResult) } catch (e) { if (e instanceof MissingMethodException) { echo "$e" error "Not support script ${stageAttrs.scriptType}!" } else { throw e } } } def getScriptParams(stageAttrs) { if (isNullOrBlank(stageAttrs.scriptParams)) { return [:] } return parseJsonString(stageAttrs.scriptParams) } def getScriptContent(stageAttrs) { def scriptContent = "" if (!isNullOrBlank(stageAttrs.scriptContent)) { scriptContent = "${stageAttrs.scriptContent}" } else if (!isNullOrBlank(stageAttrs.scriptUrl)) { scriptContent = readUrl(stageAttrs.scriptUrl, "UTF-8") } if (!scriptContent.contains('${')) { return scriptContent } if (isNullOrBlank(stageAttrs.scriptParams)) { return scriptContent } def scriptParams = parseJsonString(stageAttrs.scriptParams) return replaceVars(scriptContent, scriptParams, true) } def script_exec_groovy(pipelineContext, pipelineResult, stageAttrs, stageResult) { def scriptContent = getScriptContent(stageAttrs) if (isNullOrBlank(scriptContent)) { return } def varMap = [:] varMap.pipelineContext = pipelineContext varMap.pipelineResult = pipelineResult varMap.stageAttrs = stageAttrs varMap.stageResult = stageResult evaluate(varMap, scriptContent) } def script_exec_ansible(pipelineContext, pipelineResult, stageAttrs, stageResult) { def scriptContent = getScriptContent(stageAttrs) if (isNullOrBlank(scriptContent)) { return } def tempAnsible = "__tempAnsible.${stageAttrs.stageIndex}.yml" try { writeFile file: tempAnsible, text: "${scriptContent}", encoding: 'UTF-8' if (isNullOrBlank(stageAttrs.targetIps)) { executeAnsibleScript pipelineContext: pipelineContext, stageAttrs: stageAttrs, playbook: tempAnsible } else { if (!stageAttrs.userDir.endsWith("/")) { stageAttrs.userDir = "${stageAttrs.userDir}/" } executeAnsibleScript pipelineContext: pipelineContext, stageAttrs: stageAttrs, playbook: tempAnsible, extras: """\ --extra-vars \"install_host=$stageAttrs.targetIps user_dir=$stageAttrs.userDir \"\ """ } } finally { deletePath(tempAnsible) } } def script_exec_shell(pipelineContext, pipelineResult, stageAttrs, stageResult) { def scriptContent = getScriptContent(stageAttrs) if (isNullOrBlank(scriptContent)) { return } if (isNullOrBlank(stageAttrs.targetIps)) { sh scriptContent return } execRemoteShell(pipelineContext, stageAttrs, scriptContent, stageAttrs.userDir) } //import_files("ansible/tool/exec-shell.yml") def execRemoteShell(pipelineContext, stageAttrs, scriptContent, userDir) { try { if (!userDir.endsWith("/")) { userDir = "${userDir}/" } writeFile file: "tool/files/__tempScript.${stageAttrs.stageIndex}.sh", text: "${scriptContent}", encoding: 'UTF-8' executeAnsibleScript pipelineContext: pipelineContext, stageAttrs: stageAttrs, playbook: 'tool/exec-shell.yml', extras: """\ --extra-vars \"install_host=$stageAttrs.targetIps shell_user_dir=$userDir stage_index=${stageAttrs.stageIndex} \"\ """ } finally { deletePath("tool/files/__tempScript.${stageAttrs.stageIndex}.sh") } } def script_exec_bat(pipelineContext, pipelineResult, stageAttrs, stageResult) { def scriptContent = getScriptContent(stageAttrs) if (isNullOrBlank(scriptContent)) { return } bat scriptContent } def script_exec_exe(pipelineContext, pipelineResult, stageAttrs, stageResult) { if (isNullOrBlank(stageAttrs.scriptUrl)) { return } def workspaceDir = getContext hudson.FilePath def scriptFileObj = null if (stageAttrs.scriptUrl.contains(":")) { def scriptUrlArray = stageAttrs.scriptUrl.split("/") def scriptFile = scriptUrlArray[scriptUrlArray.length - 1] executeScript("curl -O ${stageAttrs.scriptUrl}") scriptFileObj = workspaceDir.child("${scriptFile}") if (!scriptFileObj.exists()) { error "can not download ${stageAttrs.scriptUrl} !" } } else { scriptFileObj = workspaceDir.child(stageAttrs.scriptUrl) if (!scriptFileObj.exists()) { error "${stageAttrs.scriptUrl} not existed!" } if (scriptFileObj.isDirectory()) { error "${stageAttrs.scriptUrl} is Directory!" } } bat "${scriptFileObj}" }