feat(pipeline): add Bandit SAST scanner and enhance linting configurations
Signed-off-by: 孙振宇 <>
This commit is contained in:
parent
18f4ae6f9a
commit
38bfc418c4
@ -20,7 +20,6 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
"type-case": [2, "always", "lower-case"], // Type must be in lower case
|
"type-case": [2, "always", "lower-case"], // Type must be in lower case
|
||||||
"type-empty": [2, "never"], // Type must not be empty
|
"type-empty": [2, "never"], // Type must not be empty
|
||||||
"scope-empty": [2, "never"], // Scope must not be empty
|
|
||||||
"scope-case": [2, "always", "lower-case"], // Scope must be in lower case
|
"scope-case": [2, "always", "lower-case"], // Scope must be in lower case
|
||||||
"subject-empty": [2, "never"], // Subject must not be empty
|
"subject-empty": [2, "never"], // Subject must not be empty
|
||||||
"subject-case": [2, "never", []], // Subject must be in sentence case
|
"subject-case": [2, "never", []], // Subject must be in sentence case
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"branches": ["master"],
|
||||||
|
"plugins": [
|
||||||
|
[
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
{
|
||||||
|
"preset": "angular",
|
||||||
|
"parserOpts": {
|
||||||
|
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
"@semantic-release/changelog",
|
||||||
|
[
|
||||||
|
"@semantic-release/git",
|
||||||
|
{
|
||||||
|
"assets": ["VERSION", "CHANGELOG.md"],
|
||||||
|
"message": "chore(release): bump version and upload release assets [ci skip]"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -33,8 +33,8 @@ class CodeLintExecutor {
|
|||||||
steps.log.error("CodeLintExecutor", "Unknown linter type")
|
steps.log.error("CodeLintExecutor", "Unknown linter type")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
steps.writeFile file: "${workspace}/.lintconfig", text: steps.libraryResource(configFilePath)
|
steps.writeFile file: "${workspace}.lintconfig", text: steps.libraryResource(configFilePath)
|
||||||
configs = "${workspace}/.lintconfig"
|
configs = "${workspace}.lintconfig"
|
||||||
}
|
}
|
||||||
|
|
||||||
Linter linter
|
Linter linter
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class CommitMessageLinter {
|
|||||||
steps.log.info("Commit Message Linter","Custom commit lint rules found, using custom rules files: ${configurations.commitLintRules}")
|
steps.log.info("Commit Message Linter","Custom commit lint rules found, using custom rules files: ${configurations.commitLintRules}")
|
||||||
rules = configurations.commitLintRules
|
rules = configurations.commitLintRules
|
||||||
} else {
|
} else {
|
||||||
steps.dir(steps.env.workspace) {
|
steps.dir(steps.env.workroot) {
|
||||||
steps.log.info("Commit Message Linter","No custom commit lint rules found, using built-in rules at: ${defaultRule}")
|
steps.log.info("Commit Message Linter","No custom commit lint rules found, using built-in rules at: ${defaultRule}")
|
||||||
steps.writeFile file: '.commitlintrc.js', text: steps.libraryResource(defaultRule)
|
steps.writeFile file: '.commitlintrc.js', text: steps.libraryResource(defaultRule)
|
||||||
steps.log.info("Commit Message Linter","Built-in commit lint rules requires @commitlint/config-angular, ready to install it...")
|
steps.log.info("Commit Message Linter","Built-in commit lint rules requires @commitlint/config-angular, ready to install it...")
|
||||||
@ -27,9 +27,9 @@ class CommitMessageLinter {
|
|||||||
|
|
||||||
steps.log.info("Commit Message Linter","Linting commit messages from HEAD...")
|
steps.log.info("Commit Message Linter","Linting commit messages from HEAD...")
|
||||||
|
|
||||||
steps.dir(steps.env.workspace) {
|
steps.dir(steps.env.workroot) {
|
||||||
// commit lint cli requires a git repository to lint commit messages, so we need make sure the workspace is a trusted git repository
|
// commit lint cli requires a git repository to lint commit messages, so we need make sure the workspace is a trusted git repository
|
||||||
steps.sh "git config --global --add safe.directory ${steps.env.workspace}"
|
steps.sh "git config --global --add safe.directory ${steps.env.workroot}"
|
||||||
steps.sh "commitlint --verbose -g ${rules} -f HEAD^"
|
steps.sh "commitlint --verbose -g ${rules} -f HEAD^"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,57 @@
|
|||||||
|
package com.freeleaps.devops
|
||||||
|
|
||||||
|
import com.freeleaps.devops.enums.ImageBuilderTypes
|
||||||
|
|
||||||
|
class ImageBuilder {
|
||||||
|
def steps
|
||||||
|
def workspace
|
||||||
|
def contextRoot
|
||||||
|
def dockerfile
|
||||||
|
def builderType
|
||||||
|
|
||||||
|
ImageBuilder(steps, workspace, contextRoot, dockerfile, builderType) {
|
||||||
|
this.steps = steps
|
||||||
|
this.workspace = workspace
|
||||||
|
this.contextRoot = contextRoot
|
||||||
|
this.dockerfile = dockerfile
|
||||||
|
this.builderType = builderType
|
||||||
|
}
|
||||||
|
|
||||||
|
def build(name, repository, registry, architectures, version) {
|
||||||
|
steps.log.info("ImageBuilder", "Building image with ${builderType.builder}")
|
||||||
|
steps.log.info("ImageBuilder", "Workspace sets to: ${workspace}")
|
||||||
|
steps.log.info("ImageBuilder", "Using dockerfile at: ${dockerfile}, context root sets to: ${contextRoot}")
|
||||||
|
|
||||||
|
if (architectures == null || architectures.isEmpty()) {
|
||||||
|
steps.log.warn("ImageBuilder", "No architectures specified, using default amd64")
|
||||||
|
architectures = ['linux/amd64']
|
||||||
|
}
|
||||||
|
|
||||||
|
steps.log.info("ImageBuilder", "Login to ${registry}")
|
||||||
|
steps.sh "docker login ${registry}"
|
||||||
|
|
||||||
|
switch(builderType) {
|
||||||
|
case ImageBuilderTypes.DOCKER_IN_DOCKER:
|
||||||
|
steps.dir(workspace) {
|
||||||
|
architectures.each { architecture ->
|
||||||
|
def archTag = architecture.split("/")[1]
|
||||||
|
steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}-${archTag}")
|
||||||
|
steps.sh "docker build -t ${registry}/${repository}/${name}:${version}-${archTag} --platform ${architecture} -f ${dockerfile} ${contextRoot}"
|
||||||
|
steps.sh "docker push ${registry}/${repository}/${name}:${version}-${archTag}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case ImageBuilderTypes.KANIKO:
|
||||||
|
steps.dir(workspace) {
|
||||||
|
architectures.each { architecture ->
|
||||||
|
def archTag = architecture.split("/")[1]
|
||||||
|
steps.log.info("ImageBuilder", "Building image ${registry}/${repository}/${name} with architectures: ${architectures}, tag sets to ${version}-${archTag}")
|
||||||
|
steps.sh "/kaniko/executor --log-format text --context ${contextRoot} --dockerfile ${dockerfile} --destination ${registry}/${repository}/${name}:${version}-${archTag} --custom-platform ${architecture}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
steps.error("Unsupported builder type: ${builderType.builder}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,31 @@
|
|||||||
package com.freeleaps.devops
|
package com.freeleaps.devops
|
||||||
|
|
||||||
import com.freeleaps.devops.enums.SASTScannerTypes
|
import com.freeleaps.devops.enums.SASTScannerTypes
|
||||||
|
import com.freeleaps.devops.sast.SASTScanner
|
||||||
|
|
||||||
class SASTExecutor {
|
class SASTExecutor {
|
||||||
def steps
|
def steps
|
||||||
def workspace
|
def workspace
|
||||||
def configs
|
|
||||||
def scannerType
|
def scannerType
|
||||||
|
|
||||||
|
SASTExecutor(steps, workspace, scannerType) {
|
||||||
|
this.steps = steps
|
||||||
|
this.workspace = workspace
|
||||||
|
this.scannerType = scannerType
|
||||||
|
}
|
||||||
|
|
||||||
|
def execute() {
|
||||||
|
SASTScanner scanner
|
||||||
|
|
||||||
|
switch (scannerType) {
|
||||||
|
case SASTScannerTypes.BANDIT:
|
||||||
|
scanner = new Bandit(steps, workspace)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
steps.error("Unsupported SAST scanner type: ${scannerType}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner.scan()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
package com.freeleaps.devops
|
||||||
|
|
||||||
|
class SemanticReleasingExecutor {
|
||||||
|
def steps
|
||||||
|
def workspace
|
||||||
|
def config
|
||||||
|
def plugins = [
|
||||||
|
'@semantic-release/git',
|
||||||
|
'@semantic-release/changelog',
|
||||||
|
'@semantic-release/exec',
|
||||||
|
'conventional-changelog-conventionalcommits'
|
||||||
|
]
|
||||||
|
|
||||||
|
SemanticReleasingExecutor(steps, workspace) {
|
||||||
|
this.steps = steps
|
||||||
|
this.workspace = workspace
|
||||||
|
// TODO: This should be a parameter, not hardcoded
|
||||||
|
this.config = 'com/freeleaps/devops/builtins/semantic-release/releaserc.json'
|
||||||
|
}
|
||||||
|
|
||||||
|
def release() {
|
||||||
|
steps.log.warn("SemanticReleasingExecutor", "Configuration file customization is not supported yet, using builtin release rules as fallback")
|
||||||
|
steps.log.info("SemanticReleasingExecutor", "Releasing with config: ${config}")
|
||||||
|
|
||||||
|
steps.dir(steps.env.workroot) {
|
||||||
|
steps.writeFile file: 'releaserc.json', text: steps.libraryResource(config)
|
||||||
|
steps.log.info("SemanticReleasingExecutor", "Installing semantic-release plugins...")
|
||||||
|
steps.sh "npm install -g ${plugins.join(' ')}"
|
||||||
|
steps.sh "semantic-release"
|
||||||
|
steps.log.info("SemanticReleasingExecutor", "Semantic release completed, read latest version from VERSION file")
|
||||||
|
steps.env.LATEST_VERSION = steps.readFile('VERSION').trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,9 +16,9 @@ class SourceFetcher {
|
|||||||
steps.error("serviceGitBranch is required")
|
steps.error("serviceGitBranch is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
steps.env.workspace = "${steps.env.WORKSPACE}/devops-workspace/${configurations.serviceName}"
|
steps.env.workroot = "${steps.env.WORKSPACE}/devops-workspace/${configurations.serviceName}"
|
||||||
|
|
||||||
steps.dir(steps.env.workspace) {
|
steps.dir(steps.env.workroot) {
|
||||||
steps.git branch: configurations.serviceGitBranch, credentialsId: 'git-bot-credentials', url: configurations.serviceGitRepo
|
steps.git branch: configurations.serviceGitBranch, credentialsId: 'git-bot-credentials', url: configurations.serviceGitRepo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
package com.freeleaps.devops.enums
|
||||||
|
|
||||||
|
enum ImageBuilderTypes {
|
||||||
|
DOCKER_IN_DOCKER("dind", "docker:dind"),
|
||||||
|
KANIKO("kaniko", "gcr.io/kaniko-project/executor:latest")
|
||||||
|
|
||||||
|
final String builder
|
||||||
|
final String image
|
||||||
|
|
||||||
|
ImageBuilderTypes(String builder, String image) {
|
||||||
|
this.builder = builder
|
||||||
|
this.image = image
|
||||||
|
}
|
||||||
|
|
||||||
|
static ImageBuilderTypes parse(String builder) {
|
||||||
|
switch (builder) {
|
||||||
|
case 'dind':
|
||||||
|
return ImageBuilderTypes.DOCKER_IN_DOCKER
|
||||||
|
case 'kaniko':
|
||||||
|
return ImageBuilderTypes.KANIKO
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,11 +4,25 @@ import com.freeleaps.devops.enums.CodeLinterTypes
|
|||||||
import com.freeleaps.devops.lint.LinterBase
|
import com.freeleaps.devops.lint.LinterBase
|
||||||
|
|
||||||
class ESLint extends LinterBase {
|
class ESLint extends LinterBase {
|
||||||
|
|
||||||
|
deps = [
|
||||||
|
'eslint-define-config',
|
||||||
|
'eslint-config-prettier',
|
||||||
|
'eslint-plugin-prettier',
|
||||||
|
'eslint-plugin-vue',
|
||||||
|
'@typescript-eslint/eslint-plugin',
|
||||||
|
'@typescript-eslint/parser',
|
||||||
|
'typescript'
|
||||||
|
]
|
||||||
|
|
||||||
ESLint(steps, workspace, configs) {
|
ESLint(steps, workspace, configs) {
|
||||||
super(steps, workspace, configs, CodeLinterTypes.ESLINT)
|
super(steps, workspace, configs, CodeLinterTypes.ESLINT)
|
||||||
}
|
}
|
||||||
|
|
||||||
def doLint() {
|
def doLint() {
|
||||||
|
steps.log.info("${linterType.linter}", "Install eslint dependencies...")
|
||||||
|
steps.sh("npm install -g ${deps.join(' ')}")
|
||||||
|
steps.log.info("${linterType.linter}", "Running eslint...")
|
||||||
steps.sh("eslint --config ${configs} ${workspace}")
|
steps.sh("eslint --config ${configs} ${workspace}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package com.freeleaps.devops.sast
|
||||||
|
|
||||||
|
import com.freeleaps.devops.enums.SASTScannerTypes
|
||||||
|
import com.freeleaps.devops.sast.SASTScannerBase
|
||||||
|
|
||||||
|
class Bandit extends SASTScannerBase {
|
||||||
|
Bandit(steps, workspace, configs) {
|
||||||
|
super(steps, workspace, configs, SASTScannerTypes.BANDIT)
|
||||||
|
}
|
||||||
|
|
||||||
|
def doScan() {
|
||||||
|
steps.sh("bandit -r ${workspace}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
package com.freeleaps.devops.sast
|
||||||
|
|
||||||
|
interface SASTScanner {
|
||||||
|
def scan()
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class SASTScannerBase implements SASTScanner {
|
||||||
|
def steps
|
||||||
|
def workspace
|
||||||
|
def configs // TODO: add configurations file support
|
||||||
|
def scannerType
|
||||||
|
|
||||||
|
SASTScannerBase(steps, workspace, configs, scannerType) {
|
||||||
|
this.steps = steps
|
||||||
|
this.workspace = workspace
|
||||||
|
this.configs = configs
|
||||||
|
this.scannerType = scannerType
|
||||||
|
}
|
||||||
|
|
||||||
|
def scan() {
|
||||||
|
steps.log.info("${scannerType.scanner}", "Scanning ${scannerType.language.language} code")
|
||||||
|
steps.log.info("${scannerType.scanner}", "Workspace sets to: ${workspace}")
|
||||||
|
|
||||||
|
if (configs != null || !configs.isEmpty()) {
|
||||||
|
steps.log.warn("${scannerType.scanner}", "Configurations file is not supported for ${scannerType.scanner} yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
doScan()
|
||||||
|
steps.log.info("${scannerType.scanner}", "Code scanning has been completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract def doScan()
|
||||||
|
}
|
||||||
34
first-class-pipeline/tests/Jenkinsfile
vendored
34
first-class-pipeline/tests/Jenkinsfile
vendored
@ -2,13 +2,13 @@ library 'first-class-pipeline'
|
|||||||
|
|
||||||
executeFreeleapsPipeline {
|
executeFreeleapsPipeline {
|
||||||
// serviceName is the name of the service, which is used to identify the service
|
// serviceName is the name of the service, which is used to identify the service
|
||||||
serviceName = 'magicleaps'
|
serviceName = 'gitops-mvp-app'
|
||||||
// serviceSlug used to identify the service environment
|
// serviceSlug used to identify the service environment
|
||||||
environmentSlug = 'alpha'
|
environmentSlug = 'alpha'
|
||||||
// serviceGitBranch used to specify the git repo branch of the service codes
|
// serviceGitBranch used to specify the git repo branch of the service codes
|
||||||
serviceGitBranch = 'master'
|
serviceGitBranch = 'master'
|
||||||
// serviceGitRepo used to specify the git repo of the service codes
|
// serviceGitRepo used to specify the git repo of the service codes
|
||||||
serviceGitRepo = "https://freeleaps@dev.azure.com/freeleaps/magicleaps/_git/magicleaps"
|
serviceGitRepo = "https://zhenyus@dev.azure.com/zhenyus/gitops-mvp-app/_git/gitops-mvp-app"
|
||||||
// serviceGitRepoType used to specify the git repo type of the service codes
|
// serviceGitRepoType used to specify the git repo type of the service codes
|
||||||
// monorepo: all services codes are in the same repo and using sub-folders to separate them
|
// monorepo: all services codes are in the same repo and using sub-folders to separate them
|
||||||
// separated: each service has its own repo
|
// separated: each service has its own repo
|
||||||
@ -18,7 +18,7 @@ executeFreeleapsPipeline {
|
|||||||
// fully: the pipeline will be triggered without code changes, all components will be executed
|
// fully: the pipeline will be triggered without code changes, all components will be executed
|
||||||
executeMode = 'fully' // on-demand, fully
|
executeMode = 'fully' // on-demand, fully
|
||||||
// commitMessageLintEnabled used to specify whether to enable commit message lint
|
// commitMessageLintEnabled used to specify whether to enable commit message lint
|
||||||
commitMessageLintEnabled = false
|
commitMessageLintEnabled = true
|
||||||
// components used to specify the service components
|
// components used to specify the service components
|
||||||
components = [
|
components = [
|
||||||
[
|
[
|
||||||
@ -34,8 +34,12 @@ executeFreeleapsPipeline {
|
|||||||
npmPackageJsonFile: 'package.json',
|
npmPackageJsonFile: 'package.json',
|
||||||
// buildCacheEnabled used to specify whether to enable build dependencies cache
|
// buildCacheEnabled used to specify whether to enable build dependencies cache
|
||||||
buildCacheEnabled: true,
|
buildCacheEnabled: true,
|
||||||
|
// buildAgentImage used to specify the build environment container image
|
||||||
|
buildAgentImage: 'node:lts',
|
||||||
// buildCommand used to specify the build command of the component
|
// buildCommand used to specify the build command of the component
|
||||||
buildCommand: 'npm run build',
|
buildCommand: 'npm run build',
|
||||||
|
// buildArtifacts used to specify the build artifacts that needs to be stores and shares between components
|
||||||
|
buildArtifacts: ['dist']
|
||||||
// lintEnabled used to specify whether to enable code lint
|
// lintEnabled used to specify whether to enable code lint
|
||||||
lintEnabled: true,
|
lintEnabled: true,
|
||||||
// linter used to specify the code linter
|
// linter used to specify the code linter
|
||||||
@ -52,7 +56,7 @@ executeFreeleapsPipeline {
|
|||||||
// imageRepository used to specify the image repository
|
// imageRepository used to specify the image repository
|
||||||
imageRepository: 'sunzhenyucn',
|
imageRepository: 'sunzhenyucn',
|
||||||
// imageName used to specify the image name
|
// imageName used to specify the image name
|
||||||
imageName: 'magicleaps-frontend',
|
imageName: 'gitops-mvp-app-frontend',
|
||||||
// imageBuilder used to specify the image builder
|
// imageBuilder used to specify the image builder
|
||||||
// dind: using docker-in-docker to build the image
|
// dind: using docker-in-docker to build the image
|
||||||
// kaniko: using Kaniko to build the image
|
// kaniko: using Kaniko to build the image
|
||||||
@ -62,13 +66,11 @@ executeFreeleapsPipeline {
|
|||||||
// imageBuildRoot used to specify the image build context root
|
// imageBuildRoot used to specify the image build context root
|
||||||
imageBuildRoot: '.',
|
imageBuildRoot: '.',
|
||||||
// imageReleaseArchitectures used to specify the released image architectures
|
// imageReleaseArchitectures used to specify the released image architectures
|
||||||
imageReleaseArchitectures: ['amd64', 'arm64'],
|
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64'],
|
||||||
// registryCredentialName used to specify the registry credential that stored in Freeleaps Kubernetes Cluster
|
// registryCredentialName used to specify the registry credential that stored in Freeleaps Kubernetes Cluster
|
||||||
registryCredentialName: 'first-class-pipeline-dev-secret',
|
registryCredentialName: 'gitops-mvp-app-secret',
|
||||||
// semanticReleaseEnabled used to specify whether to enable semantic release
|
// semanticReleaseEnabled used to specify whether to enable semantic release
|
||||||
semanticReleaseEnabled: true,
|
semanticReleaseEnabled: true
|
||||||
// semanticReleaseBranch used to specify the which branch to publish release notes
|
|
||||||
semanticReleaseBranch: 'master'
|
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
// name is the name of the component, which is used to identify the component
|
// name is the name of the component, which is used to identify the component
|
||||||
@ -79,6 +81,10 @@ executeFreeleapsPipeline {
|
|||||||
language: 'python',
|
language: 'python',
|
||||||
// dependenciesManager used to specify which dependencies manager to use
|
// dependenciesManager used to specify which dependencies manager to use
|
||||||
dependenciesManager: 'pip',
|
dependenciesManager: 'pip',
|
||||||
|
// buildAgentImage used to specify the build environment container image
|
||||||
|
buildAgentImage: 'python:3.8-slim-buster',
|
||||||
|
// buildArtifacts used to specify the build artifacts that needs to be stores and shares between components
|
||||||
|
buildArtifacts: ['.']
|
||||||
// buildCacheEnabled used to specify whether to enable build dependencies cache
|
// buildCacheEnabled used to specify whether to enable build dependencies cache
|
||||||
buildCacheEnabled: true,
|
buildCacheEnabled: true,
|
||||||
// buildCommand used to specify the build command of the component
|
// buildCommand used to specify the build command of the component
|
||||||
@ -97,7 +103,7 @@ executeFreeleapsPipeline {
|
|||||||
// imageRepository used to specify the image repository
|
// imageRepository used to specify the image repository
|
||||||
imageRepository: 'sunzhenyucn',
|
imageRepository: 'sunzhenyucn',
|
||||||
// imageName used to specify the image name
|
// imageName used to specify the image name
|
||||||
imageName: 'magicleaps-backend',
|
imageName: 'gitops-mvp-app-backend',
|
||||||
// imageBuilder used to specify the image builder
|
// imageBuilder used to specify the image builder
|
||||||
// dind: using docker-in-docker to build the image
|
// dind: using docker-in-docker to build the image
|
||||||
// kaniko: using Kaniko to build the image
|
// kaniko: using Kaniko to build the image
|
||||||
@ -107,13 +113,11 @@ executeFreeleapsPipeline {
|
|||||||
// imageBuildRoot used to specify the image build context root
|
// imageBuildRoot used to specify the image build context root
|
||||||
imageBuildRoot: '.',
|
imageBuildRoot: '.',
|
||||||
// imageReleaseArchitectures used to specify the released image architectures
|
// imageReleaseArchitectures used to specify the released image architectures
|
||||||
imageReleaseArchitectures: ['amd64', 'arm64'],
|
imageReleaseArchitectures: ['linux/amd64', 'linux/arm64'],
|
||||||
// registryCredentialName used to specify the registry credential that stored in Freeleaps Kubernetes Cluster
|
// registryCredentialName used to specify the registry credential that stored in Freeleaps Kubernetes Cluster
|
||||||
registryCredentialName: 'first-class-pipeline-dev-secret',
|
registryCredentialName: 'gitops-mvp-app-secret',
|
||||||
// semanticReleaseEnabled used to specify whether to enable semantic release
|
// semanticReleaseEnabled used to specify whether to enable semantic release
|
||||||
semanticReleaseEnabled: true,
|
semanticReleaseEnabled: true
|
||||||
// semanticReleaseBranch used to specify the which branch to publish release notes
|
|
||||||
semanticReleaseBranch: 'master'
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -5,10 +5,13 @@ import com.freeleaps.devops.DependenciesResolver
|
|||||||
import com.freeleaps.devops.CommitMessageLinter
|
import com.freeleaps.devops.CommitMessageLinter
|
||||||
import com.freeleaps.devops.ChangedComponentsDetector
|
import com.freeleaps.devops.ChangedComponentsDetector
|
||||||
import com.freeleaps.devops.CodeLintExecutor
|
import com.freeleaps.devops.CodeLintExecutor
|
||||||
|
import com.freeleaps.devops.SASTExecutor
|
||||||
|
import com.freeleaps.devops.ImageBuilder
|
||||||
|
|
||||||
import com.freeleaps.devops.enums.DependenciesManager
|
import com.freeleaps.devops.enums.DependenciesManager
|
||||||
import com.freeleaps.devops.enums.ServiceLanguage
|
import com.freeleaps.devops.enums.ServiceLanguage
|
||||||
import com.freeleaps.devops.enums.CodeLinterTypes
|
import com.freeleaps.devops.enums.CodeLinterTypes
|
||||||
|
import com.freeleaps.devops.enums.ImageBuilderTypes
|
||||||
|
|
||||||
def generateComponentStages(component, configurations) {
|
def generateComponentStages(component, configurations) {
|
||||||
return [
|
return [
|
||||||
@ -61,7 +64,7 @@ def generateComponentStages(component, configurations) {
|
|||||||
def language = ServiceLanguage.parse(component.language)
|
def language = ServiceLanguage.parse(component.language)
|
||||||
def depManager = DependenciesManager.parse(component.dependenciesManager)
|
def depManager = DependenciesManager.parse(component.dependenciesManager)
|
||||||
|
|
||||||
def dependenciesResolver = new DependenciesResolver(this, language, env.workspace + "/" + component.root + "/")
|
def dependenciesResolver = new DependenciesResolver(this, language, env.workroot + "/" + component.root + "/")
|
||||||
dependenciesResolver.useManager(depManager)
|
dependenciesResolver.useManager(depManager)
|
||||||
|
|
||||||
if (component.buildCacheEnabled) {
|
if (component.buildCacheEnabled) {
|
||||||
@ -100,13 +103,19 @@ def generateComponentStages(component, configurations) {
|
|||||||
|
|
||||||
log.info("Pipeline", "Using ${linter.linter} with image ${linter.containerImage} as linter for ${component.name}")
|
log.info("Pipeline", "Using ${linter.linter} with image ${linter.containerImage} as linter for ${component.name}")
|
||||||
env.linterContainerImage = linter.containerImage
|
env.linterContainerImage = linter.containerImage
|
||||||
|
} else {
|
||||||
|
log.info("Pipeline", "Code linting is not enabled for ${component.name}, skipping...")
|
||||||
}
|
}
|
||||||
log.info("Pipeline", "Code linting is not enabled for ${component.name}, skipping...")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: Code Linting If Enabled") {
|
stage("${component.name} :: Code Linting If Enabled") {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return (env.executeMode == "fully" || env.changedComponents.contains(component.name)) && env.linterContainerImage != null && !env.linterContainerImage.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
podTemplate(
|
podTemplate(
|
||||||
label: "code-linter-${component.name}",
|
label: "code-linter-${component.name}",
|
||||||
containers: [
|
containers: [
|
||||||
@ -131,7 +140,7 @@ def generateComponentStages(component, configurations) {
|
|||||||
|
|
||||||
def linterType = CodeLinterTypes.parse(component.linter)
|
def linterType = CodeLinterTypes.parse(component.linter)
|
||||||
|
|
||||||
def codeLintExecutor = new CodeLintExecutor(this, env.workspace + "/" + component.root + "/", component.linterConfig, linterType)
|
def codeLintExecutor = new CodeLintExecutor(this, env.workroot + "/" + component.root + "/", component.linterConfig, linterType)
|
||||||
codeLintExecutor.execute()
|
codeLintExecutor.execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,40 +160,248 @@ def generateComponentStages(component, configurations) {
|
|||||||
log.error("Pipeline", "Not set sastScanner for ${component.name}")
|
log.error("Pipeline", "Not set sastScanner for ${component.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info("Pipeline", "Using ${sastScanner} as SAST scanner for ${component.name}")
|
def sastScannerType = SASTScannerTypes.parse(component.sastScanner)
|
||||||
env.sastScanner = sastScanner
|
if (sastScannerType == null) {
|
||||||
|
log.error("Pipeline", "Unknown SAST scanner for ${component.name}, skipping SAST scanning")
|
||||||
|
} else if (sastScannerType.language != ServiceLanguage.parse(component.language)) {
|
||||||
|
log.error("Pipeline", "SAST scanner ${sastScannerType.scanner} is not supported for ${component.language}, skipping SAST scanning")
|
||||||
|
} else {
|
||||||
|
log.info("Pipeline", "Using ${sastScanner} as SAST scanner for ${component.name}")
|
||||||
|
env.sastScannerContainerImage = sastScannerType.containerImage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: SAST Scanning If Enabled") {
|
stage("${component.name} :: SAST Scanning If Enabled") {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return (env.executeMode == "fully" || env.changedComponents.contains(component.name)) && env.sastScannerContainerImage != null && !env.sastScannerContainerImage.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
podTemplate(
|
||||||
|
label: "sast-scanner-${component.name}",
|
||||||
|
containers: [
|
||||||
|
containerTemplate(
|
||||||
|
name: 'sast-scanner',
|
||||||
|
image: env.sastScannerContainerImage,
|
||||||
|
ttyEnabled: true,
|
||||||
|
command: 'sleep',
|
||||||
|
args: 'infinity'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
node("sast-scanner-${component.name}") {
|
||||||
|
container('sast-scanner') {
|
||||||
|
script {
|
||||||
|
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
|
||||||
|
if (component.sastEnabled != null && component.sastEnabled) {
|
||||||
|
log.info("Pipeline", "SAST scanning has enabled, scanning code...")
|
||||||
|
|
||||||
|
def sourceFetcher = new SourceFetcher(this)
|
||||||
|
sourceFetcher.fetch(configurations)
|
||||||
|
|
||||||
|
def sastScannerType = SASTScannerTypes.parse(component.sastScanner)
|
||||||
|
|
||||||
|
def sastScanner = new SASTExecutor(this, env.workroot + "/" + component.root + "/", sastScannerType)
|
||||||
|
sastScanner.scan()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: Semantic Release Preparation") {
|
stage("${component.name} :: Semantic Release Preparation") {
|
||||||
|
script {
|
||||||
|
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
|
||||||
|
if (component.semanticReleaseEnabled != null && component.semanticReleaseEnabled) {
|
||||||
|
log.info("Pipeline", "Semantic releasing has enabled, preparing semantic release...")
|
||||||
|
|
||||||
|
if (component.semanticReleaseBranch == null || component.semanticReleaseBranch.isEmpty()) {
|
||||||
|
log.error("Pipeline", "Not set semanticReleaseBranch for ${component.name}, please set it to enable semantic release")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Pipeline", "Using ${component.semanticReleaseBranch} as semantic release branch for ${component.name}")
|
||||||
|
env.semanticReleasingContainerImage = "docker.io/semantic-release/semantic-release:latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: Semantic Releasing If Enabled") {
|
stage("${component.name} :: Semantic Releasing If Enabled") {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return (env.executeMode == "fully" || env.changedComponents.contains(component.name)) && env.semanticReleasingContainerImage != null && !env.semanticReleasingContainerImage.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
podTemplate(
|
||||||
|
label: "semantic-releasing-${component.name}",
|
||||||
|
containers: [
|
||||||
|
containerTemplate(
|
||||||
|
name: 'semantic-releasing',
|
||||||
|
image: env.semanticReleasingContainerImage,
|
||||||
|
ttyEnabled: true,
|
||||||
|
command: 'sleep',
|
||||||
|
args: 'infinity'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
node("semantic-releasing-${component.name}") {
|
||||||
|
container('semantic-releasing') {
|
||||||
|
script {
|
||||||
|
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
|
||||||
|
if (component.semanticReleaseEnabled != null && component.semanticReleaseEnabled) {
|
||||||
|
log.info("Pipeline", "Semantic releasing has enabled, releasing...")
|
||||||
|
|
||||||
|
def sourceFetcher = new SourceFetcher(this)
|
||||||
|
sourceFetcher.fetch(configurations)
|
||||||
|
|
||||||
|
def semanticReleasingExecutor = new SemanticReleasingExecutor(this, env.workroot + "/" + component.root + "/")
|
||||||
|
semanticReleasingExecutor.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: Compilation & Packaging") {
|
stage("${component.name} :: Compilation & Packaging") {
|
||||||
|
podTemplate(
|
||||||
|
label: "build-agent-${component.name}",
|
||||||
|
containers: [
|
||||||
|
containerTemplate(
|
||||||
|
name: 'build-agent',
|
||||||
|
image: env.buildAgentImage,
|
||||||
|
ttyEnabled: true,
|
||||||
|
command: 'sleep',
|
||||||
|
args: 'infinity'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
node("build-agent-${component.name}") {
|
||||||
|
container('build-agent') {
|
||||||
|
script {
|
||||||
|
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
|
||||||
|
log.info("Pipeline", "Using ${env.buildAgentImage} as build agent image for compilation & packaging")
|
||||||
|
|
||||||
|
def sourceFetcher = new SourceFetcher(this)
|
||||||
|
sourceFetcher.fetch(configurations)
|
||||||
|
|
||||||
|
def language = ServiceLanguage.parse(component.language)
|
||||||
|
def depManager = DependenciesManager.parse(component.dependenciesManager)
|
||||||
|
|
||||||
|
def dependenciesResolver = new DependenciesResolver(this, language, env.workroot + "/" + component.root + "/")
|
||||||
|
dependenciesResolver.useManager(depManager)
|
||||||
|
if (component.buildCacheEnabled) {
|
||||||
|
dependenciesResolver.enableCachingSupport()
|
||||||
|
} else {
|
||||||
|
dependenciesResolver.disableCachingSupport()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependenciesResolver.resolve(component)
|
||||||
|
|
||||||
|
dir(env.workroot + "/" + component.root) {
|
||||||
|
if (component.buildCommand != null && !component.buildCommand.isEmpty()) {
|
||||||
|
sh component.buildCommand
|
||||||
|
}
|
||||||
|
component.buildArtifacts.each { artifact ->
|
||||||
|
stash includes: artifact, name: "${component.name}-${artifact}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: Image Builder Setup") {
|
stage("${component.name} :: Image Builder Setup") {
|
||||||
|
script {
|
||||||
|
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
|
||||||
|
log.info("Pipeline", "Ready to setup image builder for ${component.name}")
|
||||||
|
def imageBuilder
|
||||||
|
if (component.imageBuilder == null || component.imageBuilder.isEmpty()) {
|
||||||
|
log.info("Pipeline", "imageBuilder not set for ${component.name}, using kaniko as default image builder")
|
||||||
|
imageBuilder = ImageBuilderTypes.KANIKO
|
||||||
|
} else {
|
||||||
|
imageBuilder = ImageBuilderTypes.parse(component.imageBuilder)
|
||||||
|
if (imageBuilder == null) {
|
||||||
|
log.error("Pipeline", "Unknown image builder for ${component.name}, skipping image building")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
env.imageBuilderImage = imageBuilder.image
|
||||||
|
log.info("Pipeline", "Using ${imageBuilder.builder} (image: ${imageBuilder.image}) as image builder for ${component.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stage("${component.name} :: Image Building & Publishing") {
|
stage("${component.name} :: Image Building & Publishing") {
|
||||||
|
when {
|
||||||
|
expression {
|
||||||
|
return (env.executeMode == "fully" || env.changedComponents.contains(component.name)) && env.imageBuilderImage != null && !env.imageBuilderImage.isEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
podTemplate(
|
||||||
|
label: "image-builder-${component.name}",
|
||||||
|
containers: [
|
||||||
|
containerTemplate(
|
||||||
|
name: 'image-builder',
|
||||||
|
image: env.imageBuilderImage,
|
||||||
|
ttyEnabled: true,
|
||||||
|
command: 'sleep',
|
||||||
|
args: 'infinity'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
node("image-builder-${component.name}") {
|
||||||
|
container('image-builder') {
|
||||||
|
script {
|
||||||
|
if (env.executeMode == "fully" || env.changedComponents.contains(component.name)) {
|
||||||
|
def sourceFetcher = new SourceFetcher(this)
|
||||||
|
sourceFetcher.fetch(configurations)
|
||||||
|
|
||||||
},
|
dir(env.workroot + "/" + component.root) {
|
||||||
|
component.buildArtifacts.each { artifact ->
|
||||||
|
unstash "${component.name}-${artifact}"
|
||||||
|
}
|
||||||
|
|
||||||
stage("${component.name} :: Argo Application Version Updating (Deploying)") {
|
if (component.dockerfile != null && !component.dockerfile.isEmpty()) {
|
||||||
|
log.error("Pipeline", "Component ${component.name} dockerfile not set!")
|
||||||
|
}
|
||||||
|
|
||||||
|
def imageBuilderType = ImageBuilderTypes.parse(component.imageBuilder)
|
||||||
|
if (imageBuilderType == null) {
|
||||||
|
log.error("Pipeline", "Unknown image builder for ${component.name}, skipping image building")
|
||||||
|
}
|
||||||
|
|
||||||
|
def imageBuilder = new ImageBuilder(this, env.workroot + "/" + component.root + "/", component.imageBuildRoot, component.dockerfilePath, imageBuilderType)
|
||||||
|
|
||||||
|
log.info("Pipeline", "Retrieve version of image from pervious stage...")
|
||||||
|
if (env.LATEST_VERSION == null || env.LATEST_VERSION.isEmpty()) {
|
||||||
|
log.warn("Pipeline", "LATEST_VERSION environment value not set, using 'snapshot-<BUILD_COMMIT_HASH>' as default version")
|
||||||
|
|
||||||
|
def commitHash = sh(script: 'git rev-parse HEAD', returnStdout: true).trim()
|
||||||
|
def shortCommitHash = commitHash.take(7)
|
||||||
|
env.LATEST_VERSION = "snapshot-${shortCommitHash}"
|
||||||
|
}
|
||||||
|
def version
|
||||||
|
|
||||||
|
imageBuilder.build(component.imageName, component.imageRepository, component.imageRegistry, component.imageReleaseArchitectures, env.LATEST_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stage("${component.name} :: Argo Application Version Updating (Deploying)") {
|
||||||
|
|
||||||
|
// }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +420,7 @@ def call(Closure closure) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stages {
|
stages {
|
||||||
stage("Commit Linting If Enabled") {
|
stage("Pipeline :: Commit Linting If Enabled") {
|
||||||
when {
|
when {
|
||||||
expression {
|
expression {
|
||||||
return configurations.commitMessageLintEnabled != null && configurations.commitMessageLintEnabled
|
return configurations.commitMessageLintEnabled != null && configurations.commitMessageLintEnabled
|
||||||
@ -246,7 +463,7 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage("Execute Mode Detection") {
|
stage("Pipeline :: Execute Mode Detection") {
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
def executeMode = configurations.executeMode
|
def executeMode = configurations.executeMode
|
||||||
@ -264,7 +481,7 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage("Code Changes Detection") {
|
stage("Pipeline :: Code Changes Detection") {
|
||||||
when {
|
when {
|
||||||
expression {
|
expression {
|
||||||
return env.executeMode == "on-demand"
|
return env.executeMode == "on-demand"
|
||||||
@ -276,7 +493,7 @@ spec:
|
|||||||
sourceFetcher.fetch(configurations)
|
sourceFetcher.fetch(configurations)
|
||||||
|
|
||||||
def changedComponentsDetector = new ChangedComponentsDetector(this)
|
def changedComponentsDetector = new ChangedComponentsDetector(this)
|
||||||
def changedComponents = changedComponentsDetector.detect(env.workspace, configurations.components)
|
def changedComponents = changedComponentsDetector.detect(env.workroot, configurations.components)
|
||||||
|
|
||||||
log.info("Pipeline","Changed components: ${changedComponents}")
|
log.info("Pipeline","Changed components: ${changedComponents}")
|
||||||
env.changedComponents = changedComponents.join(' ')
|
env.changedComponents = changedComponents.join(' ')
|
||||||
@ -284,7 +501,7 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stage("Components Build (Dynamic Generated Stages)") {
|
stage("Pipeline :: Components Build (Dynamic Generated Stages)") {
|
||||||
when {
|
when {
|
||||||
expression {
|
expression {
|
||||||
return env.executeMode == "fully" || env.changedComponents.size() > 0
|
return env.executeMode == "fully" || env.changedComponents.size() > 0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user