流水线共享方法库

1、简介

kdop共享库与jenkins类似,但也不完全一样,目前kdop只能动态load一个groovy脚本。共享库主要解决多条流水线重复写相同的脚本,降低维护成本同时提高流水线稳定性。共享库存储在git上,koca-devops仓库ci分支下kdop目录

2、Git相关操作库

/**
 * git克隆
 * remoteAddr:仓库地址 如:http://ip:port/KOCA/koca-devops.git
 * branch:git分支 如:refs/heads/develop
 * credentialsId:git用户凭证
 * dirName:git克隆在dirName目录下
 */ 
def gitClone(String remoteAddr, String branch, String credentialsId,String dirName) {
    dir(dirName){
        checkout([$class: 'GitSCM',
            branches: [[name: (branch)]], doGenerateSubmoduleConfigurations: false, 
            extensions: [], submoduleCfg: [], 
            userRemoteConfigs: [[credentialsId: (credentialsId), url: (remoteAddr)]]])
    }
}

return this

3、数据库相关操作库

/**
 * 提供指定类型的增量数据脚本
 * sqlPathList: sql路径列表
 * dbType:数据库类型
 * 返回:增量sql脚本的路径
 */
def getUpdateSql(def sqlPathList,String dbType) {
    def locations = ''

    if (sqlPathList != null) {
        for (path in sqlPathList) {
            def singlePath = path
            def fullPath = "filesystem:$WORKSPACE/$singlePath,"
            locations += fullPath
        }
    }
    println(locations) 
    return locations
}


/**
 * 提供指定类型的全量数据脚本
 * sqlPathList: sql路径列表
 * dbType:数据库类型
 * 返回全量脚本的路径
 */
def getAllSql(def paths,String dbType) {
 
    sh """
        mkdir -p $WORKSPACE/${dbType}/all-sql
        mv -f $WORKSPACE/${dbType}/all-sql $WORKSPACE/${dbType}/${env.BUILD_NUMBER}
        mkdir -p $WORKSPACE/${dbType}/all-sql
    """
    if (paths != null) {
        for (path in paths) {
            def singlePath = path
            sh """
                for file in ` find $WORKSPACE/$singlePath -type f -name '*.sql' | grep 'ALL' `
                do 
                    echo Y |cp -f \$file $WORKSPACE/${dbType}/all-sql
                done
            """
        }
    }
    sh """
        cd $WORKSPACE/${dbType}/all-sql
        for file in `ls `
        do
            mv \$file V0\${file#ALL}
        done
    """
    return "filesystem:$WORKSPACE/${dbType}/all-sql"
}

/*
清空数据库
dbConnectMsg:数据库的连接信息
*/ 
def flywayCleanDb(def dbConnectMsg) {
    sh "flyway -driver=$dbConnectMsg.jdbcDriver -url='$dbConnectMsg.jdbcUrl' -user=$dbConnectMsg.jdbcUsername -password=$dbConnectMsg.jdbcPassword clean"
}

/*
增量更新数据库
dbConnectMsg:数据库连接信息
fromDatabase:包含数据库脚本与数据库类型
*/ 
def flywayUpdateDb(def dbConnectMsg, def fromDatabase) {
    def sqlPath = getUpdateSql(fromDatabase.sqlPath,fromDatabase.type)
    sh "flyway -locations=$sqlPath -driver=$dbConnectMsg.jdbcDriver -url='$dbConnectMsg.jdbcUrl' -user=$dbConnectMsg.jdbcUsername -password=$dbConnectMsg.jdbcPassword -validateOnMigrate='false' migrate"
}

/*
增量更新数据库,支持无序迁移
dbConnectMsg:数据库连接信息
fromDatabase:包含数据库脚本与数据库类型
*/ 
def flywayUpdateDbOutoforder(def dbConnectMsg, def fromDatabase) {
    def sqlPath = getUpdateSql(fromDatabase.sqlPath,fromDatabase.type)
    sh "flyway -locations=$sqlPath -driver=$dbConnectMsg.jdbcDriver -url='$dbConnectMsg.jdbcUrl' -user=$dbConnectMsg.jdbcUsername -password=$dbConnectMsg.jdbcPassword -validateOnMigrate='false' -outOfOrder='true' migrate"
}

/*
全量更新数据库
dbConnectMsg:数据库连接信息
fromDatabase:包含数据库脚本与数据库类型
*/ 
def flywayAllDb(def dbConnectMsg, def fromDatabase) {
    def sqlPath = getAllSql(fromDatabase.sqlPath,fromDatabase.type)
    sh "flyway -locations=$sqlPath -driver=$dbConnectMsg.jdbcDriver -url='$dbConnectMsg.jdbcUrl' -user=$dbConnectMsg.jdbcUsername -password=$dbConnectMsg.jdbcPassword -validateOnMigrate='false' migrate"
}


return this

4、KOCA标准门户相关操作库

def kocaGit

// 项目对象
class Project {
    // 项目名
    def name
    // 服务列表
    def servers
    // web模块
    def web
    // 数据库模块
    def fromDatabases
}
// 服务对象
class Server {
    String name
    String port
    def args
}

// web对象
class Web {
    def archetypeAndUis
    def args
    def modules
}

class ArchetypeAndUi {
    String env
    def archetype
    String uiVersion
}

// 数据库连接对象
class DbConnectMsg{
    String jdbcUrl
    String jdbcUsername
    String jdbcPassword
    String jdbcDriver
}

// 数据库对象
class FromDatabase {
    String type
    def jdbc
    def sqlPath
}


/*
读取yaml文件
filePath : project.yaml文件路径
**/ 
def readYaml(String filePath) {
    def data = readYaml file: "$filePath"
    return data
}

/*
初始化Project清单
yamlPath : project.yaml文件路径
**/ 
def initProject(String yamlPath){

    Project project = new Project()
    def data = readYaml(yamlPath)
    // 获取项目名
    project.name = data.project.name
    // 获取服务列表
    def servers = new ArrayList()
    for(item in data.project.server) {
        def server = new Server()
        server.name = item.name
        server.port = item.port
        server.args = item.args
        servers.add(server)
    }
    project.servers = servers
    // 获取web模块
    Web web = new Web()
    web.archetypeAndUis = data.project.web.archetypeAndUi
    web.args = data.project.web.args
    web.modules = data.project.web.modules
    project.web = web
    // 获取数据库模块
    def fromDatabases = new ArrayList()
    for(item in data.project.fromDatabase) {
        FromDatabase fromDatabase = new FromDatabase()
        fromDatabase.type = item.type
        fromDatabase.jdbc = item.jdbc
        fromDatabase.sqlPath = item.sqlPath
        fromDatabases.add(fromDatabase)
    }
    project.fromDatabases = fromDatabases
    
    return project
}

// 获取数据库驱动
String switchJdbcDriver(String type) {
    if (type == 'oracle') {
        return 'oracle.jdbc.OracleDriver'
    }else if (type == 'mysql') {
        return 'com.mysql.cj.jdbc.Driver'
    }else if (type == 'tdsql') {
        return 'com.mysql.cj.jdbc.Driver'
    }else if (type == 'mssql') {
        return 'com.microsoft.sqlserver.jdbc.SQLServerDriver'
    }else if (type == 'dm') {
        return 'dm.jdbc.driver.DmDriver'
    }
    return ''
}

/*
获取指定环境的数据库链接信息
fromDatabase : 指定类型的数据库信息
defaultBranch: 环境(开发、测试、演示等)
**/ 
def getDbConnectMsg(FromDatabase fromDatabase, String defaultBranch) {
    DbConnectMsg dbConnectMsg = new DbConnectMsg()
    dbConnectMsg.jdbcDriver = switchJdbcDriver(fromDatabase.type)
    dbConnectMsg.jdbcUrl = fromDatabase.jdbc."${defaultBranch}".jdbcUrl
    dbConnectMsg.jdbcUsername = fromDatabase.jdbc."${defaultBranch}".jdbcUsername
    dbConnectMsg.jdbcPassword = fromDatabase.jdbc."${defaultBranch}".jdbcPassword
    return dbConnectMsg
}


/**
获取指定数据库类型的FromDatabase对象
project :project.yaml配置文件对象
dbType: 数据库类型
*/ 
def getFromDatabase(def project,String dbType) {
    for(fromDatabase in project.fromDatabases) {
        if (fromDatabase.type == dbType) {
            return fromDatabase
        }
    }
}

/**
 编译后台
 path:后台pom.xml的路径
 op: install packge 等
 skip:是否跳过测试
 */
def compileServer(String path, String op,Boolean skip){
    def testSkip
    if (skip) {
        testSkip = "-Dmaven.test.skip=true"
    } else {
         testSkip = ""
    }
    sh "cd $path && mvn clean $op $testSkip"
}

/**
编译bootapp
servers:bootapp列表
 */
def compileBootApps(def servers) {
    for(serve in servers) {
        def name = serve.name
        compileServer(name, "package",true)
    }
}

/*
启动服务
dbConnectMsg: 数据库的连接信息
server: 服务的信息
appName: 项目名
**/ 
def startServer(DbConnectMsg dbConnectMsg, Server server, String appName) {
    sh """
        filename=`ls $server.name/target/|grep bin`
        tar -zxvf $server.name/target/\$filename -C /opt/application/$appName/
        foldername=\${filename%-bin.tar.gz}
        JENKINS_NODE_COOKIE=dontKillMe  && /opt/application/$appName/\$foldername/bin/startup.sh \
            --server.port=$server.port \
            --koca.jdbc.default-data-source-id=default \
            --koca.jdbc.data-sources[0].id=default \
            --koca.jdbc.data-sources[0].pool.url='$dbConnectMsg.jdbcUrl' \
            --koca.jdbc.data-sources[0].pool.username=$dbConnectMsg.jdbcUsername \
            --koca.jdbc.data-sources[0].pool.password=$dbConnectMsg.jdbcPassword \
            $server.args
        sleep 10
    """
}

/**
 启动bootapp
 project : project.yaml配置对象
 dbType: 数据库类型mysql/oracle/mssql
 env:环境develop/test/master
 */
def startBootApps(Project project, String dbType, String env) {
    FromDatabase fromDatabase = getFromDatabase(project, dbType) 
    DbConnectMsg dbConnectMsg = getDbConnectMsg(fromDatabase, env)

    def appName = project.name
    sh """
        mkdir -p /opt/application/$appName
        ps -ef|grep java | grep koca|grep $appName| awk '{print \$2}'|xargs -r kill -9
        rm -rf /opt/application/$appName/*
    """

    for(service in project.servers) {
        def arg
        for(i in service.args) {
            if (i.name == env) {
                arg = i.value
                break
            } else {
                arg = ''
            }
        }

        service.args = arg
        startServer(dbConnectMsg, service, appName)
    }
}


// 清除npm缓存
def cleanNpmCache() {
    sh "npm cache clean --force"
}

/*
获取环境的中文名
env:develop/test/master
**/ 
def getEnvName(String env) {
    def envName
    if (env == "dev") {
        envName = "开发环境"
    }
    else if (env == "test") {
        envName = "测试环境"
    }
    else {
        envName = "演示环境"
    }

    return envName
}

/*
获取前端的参数
web: web对象
env: 环境
**/
def setWebArgs(Web web,String env) {
    def envName = getEnvName(env)
    def astr = "--VUE_APP_ENV_MARK:'$envName' "
    for (a in web.args) {
        astr += "--'" + a + "' "
    }

    return astr
}

/*
前端模块代码clone
modulesList: 前端组件模块清单
env:环境
**/
def frontModuleCodeClone(def modulesList, String env) {
    kocaGit = load 'koca-devops/kdop/kocaGit.groovy'
    def keys = modulesList.keySet()
    for(key in keys) {
        def item = modulesList[key]
        def url = item["url"]
        kocaGit.gitClone(url,"refs/heads/${env}",'gitlabuser',"$key")
    } 
}

/*
前端模块代码编译打包
m :要构建的组件名
npmRegistry:本地的Npm仓库
key:内置的组件列表名
item:组件路径
version:vue版本
**/
def pack(String m,String npmRegistry, String key, String item, String version){
    def  path 
    if (version == "v2") {
        path = "$npmRegistry/szkingdom.yf.$m"
    } else {
        path = "$npmRegistry/szkingdom.koca-$m"
    }
    
    sh """ 
        mkdir -p $path
    """
    dir("$key") {
        sh """
            cd $item
            npm pack
            filename=`ls |grep .tgz`
            cd -
            rm -rf *.tgz
            mv -f $item/\$filename "../$path"
    """
    }
}

// 前端模块代码本地打包并返回拼接后模块列表字符串
def npmLocalPack(Web web, def modulesList, String npmRegistry, String version, String form) {
    def keys = modulesList.keySet()
    def mstr = ""
    for(m in web.modules) {
        def find = false
        for(key in keys) {
           def items = modulesList[key]
           def item = items["$m"]
           if(item) {
               mstr = mstr + m + ","
               find = true
               if (form != "npm") {
                    pack(m,npmRegistry, key, item, version)
               }
           }
        }
        if(find == false){
            println("application project文件指定了不存在的前端模块 $m !")
            continue
        } 
    }
    println("要构建的模块清单: $mstr")
    return mstr
}

/*
编译部署逻辑
name:项目名
nodePath:nodejs安装路径
webDeployPath:web包部署的路径
nvmPath:nvm包安装路径
from:依赖从源代码pack还是从npm拉
npmRegistry:本地Npm仓库路径 
archetypeAndUi:前端模板及UI组件
mstr:拼接后模块列表字符串
astr: 门户版本信息
version:vue2/vue3
deleteLockFile:是否删除lock文件
changeProduction:修改.env.production文件

*/
def compileAndDeployWeb(String name,String nodePath, String webDeployPath, String nvmPath,String from,String npmRegistry, ArchetypeAndUi archetypeAndUi, String mstr, String astr, String version, Boolean deleteLockFile,String changeProduction) {

    def preset
    def buildcmd
    def preBuildCmd=""
    def kcCmd
    def nodeVersion
    if (version == "v3") {
        nodeVersion = 16
        preset = 3
        // 判断是否删除pnpm-lock.yaml
        def rmLockFile =""
        if (deleteLockFile) {
            rmLockFile = "rm -rf pnpm-lock.yaml &&"
        }

        // 修改env.production
        def items = changeProduction.split(',')
        println("items:$items") 
        for(String item in items) {
            if (item.indexOf("=") == -1) {
                continue
            } 
            def result = item.split('=')
            if (result.size() == 1){
                continue
            }
            String key = result[0]
            String value = result[1]
        
            println("key:$key,value:$value")
            preBuildCmd = preBuildCmd + "sed -i \"s#$key\\s*=.*#$item#g\" ./.env.production && "
        }

        preBuildCmd = preBuildCmd+"cat ./.env.production"
        println("preBuildCmd:$preBuildCmd")

        buildcmd = "$rmLockFile $nodePath/npm install -g pnpm@6.35.1 && $nodePath/pnpm install --no-frozen-lockfile &&  $nodePath/pnpm build"
        
    } else {
        nodeVersion = 14
        preset = 2
        buildcmd = "$nodePath/npm install --legacy-peer-deps --ignore-scripts && $nodePath/npm rebuild node-sass && $nodePath/npm run build $astr"
    }

    if (from == "npm") {
        kcCmd = " kc create $name -f --archetype=$archetypeAndUi.archetype.name --version $archetypeAndUi.archetype.version --uiversion $archetypeAndUi.uiVersion --modules=$mstr --preset=$preset"
    } else {
        kcCmd = "kc create $name -f --source=$WORKSPACE/$npmRegistry --archetype=$archetypeAndUi.archetype.name --version $archetypeAndUi.archetype.version --uiversion $archetypeAndUi.uiVersion --modules=$mstr --preset=$preset"
    }

    sh """
        sh /home/npm-group.sh
        . $nvmPath/nvm.sh
        $nodePath/npm cache clean --force
        nvm use $nodeVersion
        $nodePath/npm -g install @szkingdom.koca/cli@latest --force

        mkdir -p $webDeployPath/$name
        cd koca-front 
        mkdir -p $name 
        $kcCmd
        cd $name
        $preBuildCmd
        $buildcmd
        rm -rf $webDeployPath/$name/*
        echo Y | cp -rf dist/* $webDeployPath/$name/
        cd -
    """
}

/*
构建前端
project: project.yml配置文件对应的对象
nodePath: nodejs的路径
webDeployPath:web包部署的路径
nvmPath:nvm的安装路径
form:依赖从源代码pack还是从npm拉
env:dev/test/master
version:vue2/vue3
modulesList: 内置的模块与代码路径
deleteLockFile:是否删除lock文件
changeProduction:修改.env.production文件
*/ 
def compileWebFormSource(Project project, String nodePath, String webDeployPath, String nvmPath,String form,String env, String version, def modulesList,Boolean deleteLockFile, String changeProduction) {
    String name = project.name
    Web web = project.web
    ArchetypeAndUi archetypeAndUi
    for(i in web.archetypeAndUis) {
        if (i.env == env) {
            archetypeAndUi = i
            break
        }
    }

    // 本地仓库路径
    def npmRegistry = 'koca-front/npm-registry'
    sh """
        rm -rf koca-front/npm-registry
        mkdir -p $npmRegistry
    """

    // 前端模块代码clone
    frontModuleCodeClone(modulesList,env)

    // 前端模块代码本地打包并返回拼接后模块列表字符串
    def mstr = npmLocalPack(web, modulesList,npmRegistry,version,form)

    def astr = ""
    if(version == "v2"){
        setWebArgs(web,env)
        for(arg in web.args) {
            if (arg.indexOf("VUE_APP_VERSION") != -1) {
                def argKeyValue = arg.split(':')
                def value = argKeyValue[1]
                astr = "--VUE_APP_VERSION:'$value'"
            }
        }
    }
   
    // 编译部署web
    compileAndDeployWeb(name, nodePath,webDeployPath,nvmPath,form,npmRegistry,archetypeAndUi,mstr,astr,version,deleteLockFile,changeProduction)
}


/*
更新nginx配置
portalFolder:源nginx配置文件路径
nginxPath: nginx安装路径
*/
def reloadNginx(String portalFolder, String nginxPath) {
    sh """
        cp -rf $portalFolder/nginx-config/* $nginxPath/conf/conf.d/
        $nginxPath/sbin/nginx -t
        $nginxPath/sbin/nginx -s reload
    """
}

return this