
com.tencent.devops.process.yaml.v2.utils.ScriptYmlUtils.kt Maven / Gradle / Ivy
/*
* Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available.
*
* Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
*
* BK-CI 蓝鲸持续集成平台 is licensed under the MIT license.
*
* A copy of the MIT License is included in this file.
*
*
* Terms of the MIT License:
* ---------------------------------------------------
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.tencent.devops.process.yaml.v2.utils
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.exc.MismatchedInputException
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.github.fge.jackson.JsonLoader
import com.github.fge.jsonschema.core.report.LogLevel
import com.github.fge.jsonschema.core.report.ProcessingMessage
import com.github.fge.jsonschema.main.JsonSchemaFactory
import com.tencent.devops.common.api.constant.CommonMessageCode.ERROR_YAML_FORMAT_EXCEPTION
import com.tencent.devops.common.api.constant.CommonMessageCode.ERROR_YAML_FORMAT_EXCEPTION_CHECK_STAGE_LABEL
import com.tencent.devops.common.api.constant.CommonMessageCode.ERROR_YAML_FORMAT_EXCEPTION_LENGTH_LIMIT_EXCEEDED
import com.tencent.devops.common.api.constant.CommonMessageCode.ERROR_YAML_FORMAT_EXCEPTION_NEED_PARAM
import com.tencent.devops.common.api.constant.CommonMessageCode.ERROR_YAML_FORMAT_EXCEPTION_SERVICE_IMAGE_FORMAT_ILLEGAL
import com.tencent.devops.common.api.constant.CommonMessageCode.ERROR_YAML_FORMAT_EXCEPTION_STEP_ID_UNIQUENESS
import com.tencent.devops.common.api.expression.ExpressionException
import com.tencent.devops.common.api.expression.Lex
import com.tencent.devops.common.api.expression.Word
import com.tencent.devops.common.api.util.JsonUtil
import com.tencent.devops.common.api.util.UUIDUtil
import com.tencent.devops.common.api.util.YamlUtil
import com.tencent.devops.common.web.utils.I18nUtil
import com.tencent.devops.process.yaml.v2.enums.StreamMrEventAction
import com.tencent.devops.process.yaml.v2.enums.TemplateType
import com.tencent.devops.process.yaml.v2.exception.YamlFormatException
import com.tencent.devops.process.yaml.v2.models.PreRepositoryHook
import com.tencent.devops.process.yaml.v2.models.PreScriptBuildYaml
import com.tencent.devops.process.yaml.v2.models.RepositoryHook
import com.tencent.devops.process.yaml.v2.models.ScriptBuildYaml
import com.tencent.devops.process.yaml.v2.models.YamlTransferData
import com.tencent.devops.process.yaml.v2.models.YmlName
import com.tencent.devops.process.yaml.v2.models.YmlVersion
import com.tencent.devops.process.yaml.v2.models.add
import com.tencent.devops.process.yaml.v2.models.job.Container
import com.tencent.devops.process.yaml.v2.models.job.Container2
import com.tencent.devops.process.yaml.v2.models.job.Job
import com.tencent.devops.process.yaml.v2.models.job.PreJob
import com.tencent.devops.process.yaml.v2.models.job.RunsOn
import com.tencent.devops.process.yaml.v2.models.job.Service
import com.tencent.devops.process.yaml.v2.models.on.DeleteRule
import com.tencent.devops.process.yaml.v2.models.on.EnableType
import com.tencent.devops.process.yaml.v2.models.on.IssueRule
import com.tencent.devops.process.yaml.v2.models.on.MrRule
import com.tencent.devops.process.yaml.v2.models.on.NoteRule
import com.tencent.devops.process.yaml.v2.models.on.PreTriggerOn
import com.tencent.devops.process.yaml.v2.models.on.PushRule
import com.tencent.devops.process.yaml.v2.models.on.ReviewRule
import com.tencent.devops.process.yaml.v2.models.on.SchedulesRule
import com.tencent.devops.process.yaml.v2.models.on.TagRule
import com.tencent.devops.process.yaml.v2.models.on.TriggerOn
import com.tencent.devops.process.yaml.v2.models.stage.PreStage
import com.tencent.devops.process.yaml.v2.models.stage.Stage
import com.tencent.devops.process.yaml.v2.models.stage.StageLabel
import com.tencent.devops.process.yaml.v2.models.step.PreStep
import com.tencent.devops.process.yaml.v2.models.step.Step
import com.tencent.devops.process.yaml.v2.parameter.ParametersType
import com.tencent.devops.process.yaml.v2.stageCheck.Flow
import com.tencent.devops.process.yaml.v2.stageCheck.PreStageCheck
import com.tencent.devops.process.yaml.v2.stageCheck.StageCheck
import com.tencent.devops.process.yaml.v2.stageCheck.StageReviews
import java.io.BufferedReader
import java.io.StringReader
import java.util.Random
import java.util.regex.Pattern
import org.apache.commons.text.StringEscapeUtils
import org.slf4j.LoggerFactory
@Suppress("MaximumLineLength", "ComplexCondition")
object ScriptYmlUtils {
private val logger = LoggerFactory.getLogger(ScriptYmlUtils::class.java)
private const val secretSeed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
private const val jobNamespace = "job-"
private const val stepNamespace = "step-"
// 用户编写的触发器语法和实际对象不一致
private const val userTrigger = "on"
private const val formatTrigger = "triggerOn"
/**
* 1、解决锚点
* 2、yml string层面的格式化填充
*/
@Throws(JsonProcessingException::class)
fun formatYaml(yamlStr: String): String {
// replace custom tag
val yamlNormal = formatYamlCustom(yamlStr)
// replace anchor tag
return YamlUtil.loadYamlRetryOnAccident(yamlNormal)
}
fun parseVersion(yamlStr: String?): YmlVersion? {
if (yamlStr == null) {
return null
}
return try {
val obj = YamlUtil.loadYamlRetryOnAccident(yamlStr)
YamlUtil.getObjectMapper().readValue(obj, YmlVersion::class.java)
} catch (e: Exception) {
null
}
}
fun parseName(yamlStr: String?): YmlName? {
if (yamlStr.isNullOrBlank()) {
return null
}
return try {
val obj = YamlUtil.loadYamlRetryOnAccident(yamlStr)
YamlUtil.getObjectMapper().readValue(obj, YmlName::class.java)
} catch (e: Exception) {
null
}
}
fun isV2Version(yamlStr: String?): Boolean {
if (yamlStr == null) {
return false
}
return try {
val obj = YamlUtil.loadYamlRetryOnAccident(yamlStr)
val version = YamlUtil.getObjectMapper().readValue(obj, YmlVersion::class.java)
version != null && version.version == "v2.0"
} catch (e: Exception) {
true
}
}
fun parseVariableValue(value: String?, settingMap: Map): String? {
if (value == null || value.isEmpty()) {
return ""
}
var newValue = value
val pattern = Pattern.compile("\\$\\{\\{([^{}]+?)}}")
val matcher = pattern.matcher(value)
while (matcher.find()) {
val realValue = settingMap[matcher.group(1).trim()] ?: continue
newValue = newValue!!.replace(matcher.group(), realValue)
}
return newValue
}
fun parseParameterValue(value: String?, settingMap: Map, paramType: ParametersType): String {
if (value.isNullOrBlank()) {
return ""
}
var newValue = value
// ScriptUtils.formatYaml会将所有的带上 "" 但替换时数组不需要"" 所以数组单独匹配
val pattern = when (paramType) {
ParametersType.ARRAY -> {
Pattern.compile("\"\\$\\{\\{([^{}]+?)}}\"")
}
else -> {
Pattern.compile("\\$\\{\\{([^{}]+?)}}")
}
}
val matcher = pattern.matcher(value)
while (matcher.find()) {
if (settingMap.containsKey(matcher.group(1).trim())) {
val realValue = settingMap[matcher.group(1).trim()]
if (realValue is List<*>) {
newValue = newValue!!.replace(matcher.group(), JsonUtil.toJson(realValue))
} else {
newValue = newValue!!.replace(matcher.group(), StringEscapeUtils.escapeJava(realValue.toString()))
}
}
}
// 替换if中没有加括号的,只替换string
val resultValue = if (paramType == ParametersType.STRING) {
replaceIfParameters(newValue, settingMap)
} else {
newValue
}
return resultValue.toString()
}
@Suppress("NestedBlockDepth")
private fun replaceIfParameters(
newValue: String?,
settingMap: Map
): StringBuffer {
val newValueLines = BufferedReader(StringReader(newValue!!))
val resultValue = StringBuffer()
var line = newValueLines.readLine()
while (line != null) {
val startString = line.trim().replace("\\s".toRegex(), "")
if (startString.startsWith("if:") || startString.startsWith("-if:")) {
val ifPrefix = line.substring(0 until line.indexOfFirst { it == ':' } + 1)
val condition = line.substring(line.indexOfFirst { it == '"' } + 1 until line.length).trimEnd()
.removeSuffix("\"")
// 去掉花括号
val baldExpress = condition.replace("\${{", "").replace("}}", "").trim()
val originItems: List
// 先语法分析
try {
originItems = Lex(baldExpress.toList().toMutableList()).getToken()
} catch (e: Exception) {
throw ExpressionException("expression=$baldExpress|reason=Grammar Invalid: ${e.message}")
}
// 替换变量
val items = mutableListOf()
originItems.forEach {
if (it.symbol == "ident") {
items.add(Word(replaceParameters(it, settingMap), it.symbol))
} else {
items.add(Word(it.str, it.symbol))
}
}
val itemsStr = items.joinToString(" ") { it.str }
resultValue.append("$ifPrefix \"${itemsStr}\"").append("\n")
} else {
resultValue.append(line).append("\n")
}
line = newValueLines.readLine()
}
return resultValue
}
private fun replaceParameters(
it: Word,
settingMap: Map
) = if (it.str.startsWith("parameters.")) {
val realValue = settingMap[it.str] ?: it.str
if (realValue is List<*>) {
// ["test"]->[test]
JsonUtil.toJson(realValue).replace("\"", "")
.replace("[ ", "[")
.replace(" ]", "]")
} else {
StringEscapeUtils.escapeJava(realValue.toString())
}
} else {
it.str
}
private fun formatYamlCustom(yamlStr: String): String {
val sb = StringBuilder()
val br = BufferedReader(StringReader(yamlStr))
var line: String? = br.readLine()
while (line != null) {
line = line.trimEnd()
if (line.startsWith("$userTrigger:")) {
sb.append("$formatTrigger:").append("\n")
} else {
sb.append(line).append("\n")
}
line = br.readLine()
}
return sb.toString()
}
fun formatStage(preScriptBuildYaml: PreScriptBuildYaml, transferData: YamlTransferData?): List {
return when {
preScriptBuildYaml.steps != null -> {
val jobId = randomString(jobNamespace)
listOf(
Stage(
name = "stage_1",
jobs = listOf(
Job(
id = jobId,
name = "job1",
runsOn = RunsOn(),
steps = preStepsToSteps(jobId, preScriptBuildYaml.steps, transferData)
)
),
checkIn = null,
checkOut = null
)
)
}
preScriptBuildYaml.jobs != null -> {
listOf(
Stage(
name = "stage_1",
jobs = preJobs2Jobs(preScriptBuildYaml.jobs, transferData),
checkIn = null,
checkOut = null
)
)
}
else -> {
preStages2Stages(preScriptBuildYaml.stages as List, transferData)
}
}
}
fun preJobs2Jobs(preJobs: Map?, transferData: YamlTransferData?): List {
if (preJobs == null) {
return emptyList()
}
val jobs = mutableListOf()
preJobs.forEach { (index, preJob) ->
// 校验id不能超过64,因为id可能为数字无法在schema支持,放到后台
if (index.length > 64) {
throw YamlFormatException(
I18nUtil.getCodeLanMessage(
messageCode = ERROR_YAML_FORMAT_EXCEPTION_LENGTH_LIMIT_EXCEEDED,
params = arrayOf("", index)
)
)
}
// 检测job env合法性
StreamEnvUtils.checkEnv(preJob.env)
val services = mutableListOf()
preJob.services?.forEach { (key, value) ->
services.add(
Service(
serviceId = key,
image = value.image,
with = value.with
)
)
}
jobs.add(
Job(
id = index,
name = preJob.name,
mutex = preJob.mutex,
runsOn = formatRunsOn(preJob.runsOn),
services = services,
ifField = preJob.ifField,
ifModify = preJob.ifModify,
steps = preStepsToSteps(index, preJob.steps, transferData),
timeoutMinutes = preJob.timeoutMinutes,
env = preJob.env,
continueOnError = preJob.continueOnError,
strategy = preJob.strategy,
dependOn = preJob.dependOn
)
)
// 为每个job增加可能的模板信息
transferData?.add(index, TemplateType.JOB, preJob.yamlMetaData?.templateInfo?.remoteTemplateProjectId)
}
return jobs
}
private fun formatRunsOn(preRunsOn: Any?): RunsOn {
if (preRunsOn == null) {
return RunsOn()
}
try {
val runsOn = YamlUtil.getObjectMapper().readValue(JsonUtil.toJson(preRunsOn), RunsOn::class.java)
return if (runsOn.container != null) {
try {
val container =
YamlUtil.getObjectMapper().readValue(JsonUtil.toJson(runsOn.container), Container::class.java)
runsOn.copy(container = container)
} catch (e: Exception) {
val container =
YamlUtil.getObjectMapper().readValue(JsonUtil.toJson(runsOn.container), Container2::class.java)
runsOn.copy(container = container)
}
} else {
runsOn
}
} catch (e: MismatchedInputException) {
if (preRunsOn is String || preRunsOn is List<*>) {
return RunsOn(
poolName = preRunsOn.toString()
)
}
throw YamlFormatException(
I18nUtil.getCodeLanMessage(
messageCode = ERROR_YAML_FORMAT_EXCEPTION,
params = arrayOf("runs-on", "${e.path[0]?.fieldName}", "${e.targetType?.name}", "${e.message}")
)
)
}
}
private fun preStepsToSteps(
jobId: String,
oldSteps: List?,
transferData: YamlTransferData?
): List {
if (oldSteps == null) {
return emptyList()
}
val stepList = mutableListOf()
val stepIdSet = mutableSetOf()
oldSteps.forEach { preStep ->
if (preStep.uses == null && preStep.run == null && preStep.checkout == null) {
throw YamlFormatException(
I18nUtil.getCodeLanMessage(
messageCode = ERROR_YAML_FORMAT_EXCEPTION_NEED_PARAM,
params = arrayOf("oldStep")
)
)
}
// 校验stepId唯一性
if (!preStep.id.isNullOrBlank() && stepIdSet.contains(preStep.id)) {
throw YamlFormatException(
I18nUtil.getCodeLanMessage(
messageCode = ERROR_YAML_FORMAT_EXCEPTION_STEP_ID_UNIQUENESS,
params = arrayOf(preStep.id)
)
)
} else if (!preStep.id.isNullOrBlank() && !stepIdSet.contains(preStep.id)) {
stepIdSet.add(preStep.id)
}
// 检测step env合法性
StreamEnvUtils.checkEnv(preStep.env)
val taskId = "e-${UUIDUtil.generate()}"
stepList.add(
Step(
name = preStep.name,
id = preStep.id,
ifFiled = preStep.ifFiled,
ifModify = preStep.ifModify,
uses = preStep.uses,
with = preStep.with,
timeoutMinutes = preStep.timeoutMinutes,
continueOnError = preStep.continueOnError,
retryTimes = preStep.retryTimes,
env = preStep.env,
run = preStep.run,
runAdditionalOptions = mapOf("shell" to preStep.shell),
checkout = preStep.checkout,
taskId = taskId
)
)
transferData?.add(taskId, TemplateType.STEP, preStep.yamlMetaData?.templateInfo?.remoteTemplateProjectId)
}
return stepList
}
private fun preStages2Stages(preStageList: List?, transferData: YamlTransferData?): List {
if (preStageList == null) {
return emptyList()
}
val stageList = mutableListOf()
preStageList.forEach {
stageList.add(
Stage(
name = it.name,
label = formatStageLabel(it.label),
ifField = it.ifField,
ifModify = it.ifModify,
fastKill = it.fastKill ?: false,
jobs = preJobs2Jobs(it.jobs, transferData),
checkIn = formatStageCheck(it.checkIn),
checkOut = formatStageCheck(it.checkOut)
)
)
}
return stageList
}
private fun formatStageCheck(preCheck: PreStageCheck?): StageCheck? {
if (preCheck == null) {
return null
}
return StageCheck(
reviews = if (preCheck.reviews != null) {
StageReviews(
flows = preCheck.reviews.flows?.map {
Flow(
name = it.name,
reviewers = anyToListString(it.reviewers)
)
},
variables = preCheck.reviews.variables,
description = preCheck.reviews.description
)
} else {
null
},
gates = preCheck.gates,
timeoutHours = preCheck.timeoutHours
)
}
private fun formatStageLabel(labels: Any?): List {
if (labels == null) {
return emptyList()
}
val transLabels = anyToListString(labels)
val newLabels = mutableListOf()
transLabels.forEach {
val stageLabel = getStageLabel(it)
if (stageLabel != null) {
newLabels.add(stageLabel.id)
} else {
throw YamlFormatException(
I18nUtil.getCodeLanMessage(ERROR_YAML_FORMAT_EXCEPTION_CHECK_STAGE_LABEL)
)
}
}
return newLabels
}
private fun getStageLabel(label: String): StageLabel? {
StageLabel.values().forEach {
if (it.value == label) {
return it
}
}
return null
}
/**
* 预处理对象转化为合法对象
*/
fun normalizeGitCiYaml(
preScriptBuildYaml: PreScriptBuildYaml,
filePath: String,
transferData: YamlTransferData = YamlTransferData()
): Pair {
val stages = formatStage(
preScriptBuildYaml,
transferData
)
val thisTriggerOn = formatTriggerOn(preScriptBuildYaml.triggerOn)
return Pair(
ScriptBuildYaml(
name = if (!preScriptBuildYaml.name.isNullOrBlank()) {
preScriptBuildYaml.name!!
} else {
filePath
},
version = preScriptBuildYaml.version,
triggerOn = thisTriggerOn,
variables = preScriptBuildYaml.variables,
extends = preScriptBuildYaml.extends,
resource = preScriptBuildYaml.resources,
notices = preScriptBuildYaml.notices,
stages = stages,
finally = preJobs2Jobs(preScriptBuildYaml.finally, transferData),
label = preScriptBuildYaml.label ?: emptyList(),
concurrency = preScriptBuildYaml.concurrency
),
transferData
)
}
fun formatRepoHookTriggerOn(preTriggerOn: PreTriggerOn?, name: String?): TriggerOn? {
if (preTriggerOn?.repoHook == null) {
return null
}
val repositoryHookList = try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(preTriggerOn.repoHook),
object : TypeReference>() {}
)
} catch (e: MismatchedInputException) {
return null
}
repositoryHookList.find { it.name == name }?.let { repositoryHook ->
if (repositoryHook.events == null) {
return TriggerOn(
push = PushRule(
branches = listOf("*")
),
tag = TagRule(
tags = listOf("*")
),
mr = MrRule(
targetBranches = listOf("*"),
action = listOf(
StreamMrEventAction.OPEN.value,
StreamMrEventAction.REOPEN.value,
StreamMrEventAction.PUSH_UPDATE.value
)
),
repoHook = repoHookRule(repositoryHook)
)
}
val repoPreTriggerOn = try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(repositoryHook.events),
PreTriggerOn::class.java
)
} catch (e: MismatchedInputException) {
return null
}
return TriggerOn(
push = pushRule(repoPreTriggerOn),
tag = tagRule(repoPreTriggerOn),
mr = mrRule(repoPreTriggerOn),
schedules = schedulesRule(repoPreTriggerOn),
delete = deleteRule(repoPreTriggerOn),
issue = issueRule(repoPreTriggerOn),
review = reviewRule(repoPreTriggerOn),
note = noteRule(repoPreTriggerOn),
repoHook = repoHookRule(repositoryHook),
manual = manualRule(repoPreTriggerOn),
openapi = openapiRule(repoPreTriggerOn)
)
}
logger.warn("repo hook has none effective TriggerOn in ($repositoryHookList)")
return null
}
fun formatTriggerOn(preTriggerOn: PreTriggerOn?): TriggerOn {
if (preTriggerOn == null) {
return TriggerOn(
push = PushRule(
branches = listOf("*")
),
tag = TagRule(
tags = listOf("*")
),
mr = MrRule(
targetBranches = listOf("*"),
action = listOf(
StreamMrEventAction.OPEN.value,
StreamMrEventAction.REOPEN.value,
StreamMrEventAction.PUSH_UPDATE.value
)
)
)
}
return TriggerOn(
push = pushRule(preTriggerOn),
tag = tagRule(preTriggerOn),
mr = mrRule(preTriggerOn),
schedules = schedulesRule(preTriggerOn),
delete = deleteRule(preTriggerOn),
issue = issueRule(preTriggerOn),
review = reviewRule(preTriggerOn),
note = noteRule(preTriggerOn),
manual = manualRule(preTriggerOn),
openapi = openapiRule(preTriggerOn)
)
}
fun repoHookRule(
preRepositoryHook: PreRepositoryHook
): RepositoryHook {
with(preRepositoryHook) {
when {
credentials is String -> return RepositoryHook(
name = name,
credentialsForTicketId = credentials,
reposIgnore = reposIgnore,
reposIgnoreCondition = reposIgnoreCondition
)
credentials is Map<*, *> && credentials["username"] != null && credentials["password"] != null -> {
return RepositoryHook(
name = name,
credentialsForUserName = credentials["username"].toString(),
credentialsForPassword = credentials["password"].toString(),
reposIgnore = reposIgnore,
reposIgnoreCondition = reposIgnoreCondition
)
}
credentials is Map<*, *> && credentials["token"] != null -> {
return RepositoryHook(
name = name,
credentialsForToken = credentials["token"].toString(),
reposIgnore = reposIgnore,
reposIgnoreCondition = reposIgnoreCondition
)
}
else -> return RepositoryHook(
name = name,
reposIgnore = reposIgnore,
reposIgnoreCondition = reposIgnoreCondition
)
}
}
}
private fun manualRule(
preTriggerOn: PreTriggerOn
): String? {
if (preTriggerOn.manual == null) {
return null
}
if (preTriggerOn.manual != EnableType.TRUE.value && preTriggerOn.manual != EnableType.FALSE.value) {
throw YamlFormatException("not allow manual type ${preTriggerOn.manual}")
}
return preTriggerOn.manual
}
private fun openapiRule(
preTriggerOn: PreTriggerOn
): String? {
if (preTriggerOn.openapi == null) {
return null
}
if (preTriggerOn.openapi != EnableType.TRUE.value && preTriggerOn.openapi != EnableType.FALSE.value) {
throw YamlFormatException("not allow openapi type ${preTriggerOn.openapi}")
}
return preTriggerOn.openapi
}
private fun noteRule(
preTriggerOn: PreTriggerOn
): NoteRule? {
if (preTriggerOn.note != null) {
val note = preTriggerOn.note
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(note),
NoteRule::class.java
)
} catch (e: MismatchedInputException) {
null
}
}
return null
}
private fun reviewRule(
preTriggerOn: PreTriggerOn
): ReviewRule? {
if (preTriggerOn.review != null) {
val issues = preTriggerOn.review
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(issues),
ReviewRule::class.java
)
} catch (e: MismatchedInputException) {
null
}
}
return null
}
private fun issueRule(
preTriggerOn: PreTriggerOn
): IssueRule? {
if (preTriggerOn.issue != null) {
val issues = preTriggerOn.issue
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(issues),
IssueRule::class.java
)
} catch (e: MismatchedInputException) {
null
}
}
return null
}
private fun deleteRule(
preTriggerOn: PreTriggerOn
): DeleteRule? {
if (preTriggerOn.delete != null) {
val delete = preTriggerOn.delete
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(delete),
DeleteRule::class.java
)
} catch (e: MismatchedInputException) {
null
}
}
return null
}
private fun schedulesRule(
preTriggerOn: PreTriggerOn
): SchedulesRule? {
if (preTriggerOn.schedules != null) {
val schedules = preTriggerOn.schedules
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(schedules),
SchedulesRule::class.java
)
} catch (e: MismatchedInputException) {
null
}
}
return null
}
private fun mrRule(
preTriggerOn: PreTriggerOn
): MrRule? {
if (preTriggerOn.mr != null) {
val mr = preTriggerOn.mr
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(mr),
MrRule::class.java
)
} catch (e: MismatchedInputException) {
try {
val mrList = YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(mr),
List::class.java
) as ArrayList
MrRule(
targetBranches = mrList,
sourceBranchesIgnore = null,
paths = null,
pathsIgnore = null,
users = null,
usersIgnore = null
)
} catch (e: Exception) {
null
}
}
}
return null
}
private fun tagRule(
preTriggerOn: PreTriggerOn
): TagRule? {
if (preTriggerOn.tag != null) {
val tag = preTriggerOn.tag
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(tag),
TagRule::class.java
)
} catch (e: MismatchedInputException) {
try {
val tagList = YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(tag),
List::class.java
) as ArrayList
TagRule(
tags = tagList,
tagsIgnore = null,
fromBranches = null,
users = null,
usersIgnore = null
)
} catch (e: Exception) {
null
}
}
}
return null
}
private fun pushRule(
preTriggerOn: PreTriggerOn
): PushRule? {
if (preTriggerOn.push != null) {
val push = preTriggerOn.push
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(push),
PushRule::class.java
)
} catch (e: MismatchedInputException) {
try {
val pushObj = YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(push),
List::class.java
) as ArrayList
PushRule(
branches = pushObj,
branchesIgnore = null,
paths = null,
pathsIgnore = null,
users = null,
usersIgnore = null
)
} catch (e: Exception) {
null
}
}
}
return null
}
fun validate(schema: String, yamlJson: String): Pair {
val schemaNode = jsonNodeFromString(schema)
val jsonNode = jsonNodeFromString(yamlJson)
val report = JsonSchemaFactory.byDefault().validator.validate(schemaNode, jsonNode)
val itr = report.iterator()
val sb = java.lang.StringBuilder()
while (itr.hasNext()) {
val message = itr.next() as ProcessingMessage
if (message.logLevel == LogLevel.ERROR || message.logLevel == LogLevel.FATAL) {
sb.append(message).append("\r\n")
}
}
return Pair(report.isSuccess, sb.toString())
}
fun jsonNodeFromString(json: String): JsonNode = JsonLoader.fromString(json)
fun validateJson(json: String): Boolean {
try {
jsonNodeFromString(json)
} catch (e: Exception) {
return false
}
return true
}
fun convertYamlToJson(yaml: String): String {
val yamlReader = ObjectMapper(YAMLFactory())
val obj = yamlReader.readValue(yaml, Any::class.java)
val jsonWriter = ObjectMapper()
return jsonWriter.writeValueAsString(obj)
}
fun parseServiceImage(image: String): Pair {
val list = image.split(":")
if (list.size != 2) {
throw YamlFormatException(
I18nUtil.getCodeLanMessage(ERROR_YAML_FORMAT_EXCEPTION_SERVICE_IMAGE_FORMAT_ILLEGAL)
)
}
return Pair(list[0], list[1])
}
private fun randomString(flag: String): String {
val random = Random()
val buf = StringBuffer(flag)
for (i in 0 until 7) {
val num = random.nextInt(secretSeed.length)
buf.append(secretSeed[num])
}
return buf.toString()
}
private fun anyToListString(value: Any): List {
return try {
YamlUtil.getObjectMapper().readValue(
JsonUtil.toJson(value),
List::class.java
) as ArrayList
} catch (e: MismatchedInputException) {
listOf(value.toString())
} catch (e: Exception) {
emptyList()
}
}
/**
* 标准化处理,并裁剪PreCI不支持的特性
*/
fun normalizePreCiYaml(preScriptBuildYaml: PreScriptBuildYaml): ScriptBuildYaml {
val stages = formatStage(
preScriptBuildYaml,
null
)
return ScriptBuildYaml(
name = preScriptBuildYaml.name,
version = preScriptBuildYaml.version,
triggerOn = null,
variables = preScriptBuildYaml.variables,
extends = preScriptBuildYaml.extends,
resource = preScriptBuildYaml.resources,
notices = preScriptBuildYaml.notices,
stages = stages,
finally = preJobs2Jobs(preScriptBuildYaml.finally, null),
label = preScriptBuildYaml.label ?: emptyList(),
concurrency = preScriptBuildYaml.concurrency
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy