Last active
June 28, 2020 12:29
-
-
Save f0y/bf7ff6570699cc368613c0dd60cd9c69 to your computer and use it in GitHub Desktop.
Динамическая генерация сборок Jenkins
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Настройки доступа к Bitbucket | |
*/ | |
class BitbucketSettings { | |
private final URL url | |
private final String login | |
private final String password | |
BitbucketSettings(URL url, String login, String password) { | |
this.url = url | |
this.login = login | |
this.password = password | |
} | |
/** | |
* URL для доступа, пример - https://bitbucket.example.net | |
*/ | |
URL getUrl() { | |
return url | |
} | |
/** | |
* Логин пользователя | |
*/ | |
String getLogin() { | |
return login | |
} | |
/** | |
* Пароль пользователя | |
*/ | |
String getPassword() { | |
return password | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import JobParams | |
import JobFactory | |
@GrabResolver(name = 'central', root = 'http://nexus.example.net/content/repositories/central/') | |
@Grab('org.codehaus.groovy.modules.http-builder:http-builder:0.7.1') | |
import groovyx.net.http.HTTPBuilder | |
import groovyx.net.http.HttpResponseException | |
/** | |
* Класс с логикой синхронизации описания сборок в Bitbucket и сборками в Jenkins | |
* Метаданные это файлы в git репозитории: | |
* - с содержимым в формате Jenkins job DSL | |
* - должны начинаться с jenkins_ и иметь расширение groovy | |
* - должны располагаться в папке jenkins, которая должна лежать в корне репозитория | |
* | |
* Логика: | |
* 1) Динамически обойти все репозитории в указанном проекте в Bitbucket | |
* 2) Для каждого репозитрия получить весь список бранчей и тегов | |
* 3) В каждом бранче и теге найти метаописания сборок - скрипты генерации сборок | |
* 4) Динамически запустить все скрипты сборок, тем самым создав или обновив сборки в Jenkins | |
*/ | |
class BitbucketSynchronizer { | |
private static final int MAX_BRANCHES_IN_REPO = 200 | |
private static final int MAX_REPOS_IN_PROJECT = 200 | |
private static final int MAX_LINES_IN_JOB_SCRIPT = 1000 | |
/** | |
* Внутренний оббъект Jenkins для генерации job | |
*/ | |
private dslFactory | |
/** | |
* Http клиент для вызова Bitbucket по http API | |
*/ | |
private HTTPBuilder httpBuilder | |
/** | |
* Настройки для работы с Bitbucket http API | |
*/ | |
private BitbucketSettings bitbucketSettings | |
/** | |
* Класс с функционалом генерации типовых сборок | |
*/ | |
private JobFactory jobFactory | |
/** | |
* Конструктор | |
* @param dslFactory служебный объект Jenkins для генерации job | |
* @param bitbucketSettings настройки подключени к Bitbucket | |
*/ | |
BitbucketSynchronizer(dslFactory, BitbucketSettings bitbucketSettings) { | |
this.dslFactory = dslFactory | |
this.bitbucketSettings = bitbucketSettings | |
this.jobFactory = new JobFactory(dslFactory) | |
this.httpBuilder = createHttpBuilder(bitbucketSettings) | |
} | |
private HTTPBuilder createHttpBuilder(BitbucketSettings bitbucketSettings) { | |
HTTPBuilder builder = new HTTPBuilder(bitbucketSettings.url) | |
builder.ignoreSSLIssues() | |
String basicAuth = "$bitbucketSettings.login:$bitbucketSettings.password".toString().bytes.encodeBase64() | |
builder.headers += ["Content-Type" : "application/json", | |
"Authorization": "Basic $basicAuth"] | |
return builder | |
} | |
/** | |
* Синхронизация метаданных сборок по данным из проекта в Bitbucket | |
* @param projectKey ключ прооекта в Bitbucket | |
* @param jenkinsSynchRoot папка в Jenkins, относительно которой будут генериться сборки | |
*/ | |
void synchProjectFromBitbucket(String projectKey, String jenkinsSynchRoot) { | |
def reposUrl = "/rest/api/1.0/projects/$projectKey/repos" | |
dslFactory.out.println "Getting repos for project: project=$projectKey, URL=$bitbucketSettings.url$reposUrl" | |
httpBuilder.get(path: reposUrl, query: ["limit": MAX_REPOS_IN_PROJECT]).values.each { | |
synchRepository(it, jenkinsSynchRoot) | |
dslFactory.queue(getRepoSynchJobName(jenkinsSynchRoot, it.slug)) | |
} | |
} | |
/** | |
* Синхронизация сборок по репозиторию Bitbucket в указанную папку Jenkins | |
* @param repository данные репозитория Bitbucket из http API | |
* @param jenkinsSynchRoot папка в Jenkins, относительно которой будут генериться сборки | |
*/ | |
private void synchRepository(repository, String jenkinsSynchRoot) { | |
dslFactory.out.println "Synch repo: $repository.slug" | |
upsertSynchRepoJob(repository, jenkinsSynchRoot) | |
def repoUrl = "/rest/api/1.0/projects/$repository.project.key/repos/$repository.slug" | |
httpBuilder.get(path: "$repoUrl/branches", query: ["limit": MAX_BRANCHES_IN_REPO]).values.each { | |
String branch = it.id - "refs/heads/" | |
dslFactory.out.println "Synching branch: $branch" | |
def jenkinsDirUrl = "$repoUrl/browse/jenkins" | |
def jenkinsDirContent | |
try { | |
jenkinsDirContent = httpBuilder.get(path: jenkinsDirUrl, query: ["at": branch]) | |
} catch (HttpResponseException ignored) { | |
return | |
} | |
jenkinsDirContent.children.values.each { | |
if (it.path.extension != "groovy" || !it.path.name.startsWith("jenkins_")) { | |
dslFactory.out.println "$it.path.name will be skipped: should starts with with jenkins_ " + | |
"and have groovy extension." | |
return | |
} | |
String scriptFileUrl = "$jenkinsDirUrl/$it.path.name" | |
def jobScriptContent = httpBuilder.get(path: scriptFileUrl, query: ["at": branch, | |
"limit": MAX_LINES_IN_JOB_SCRIPT]) | |
String fileContent = jobScriptContent.lines.text.join('\n') | |
String branchForBuild = branch.replaceAll("/", "_") | |
String jenkinsDirectory = "$jenkinsSynchRoot/$repository.slug/$branchForBuild" | |
dslFactory.jobParams = JobParams.builder() | |
.withJenkinsDirectory(jenkinsDirectory) | |
.withGitRepoUrl(getCloneSshLink(repository)) | |
.withGitBranch(branch) | |
.build() | |
dslFactory.jobFactory = jobFactory | |
jobFactory.jobParams = dslFactory.jobParams | |
dslFactory.out.println "Executing script: $bitbucketSettings.url$scriptFileUrl?at=$branch" | |
Eval.x(dslFactory, "x.with{$fileContent}") | |
} | |
} | |
} | |
void synchRepoFromBitbucket(String projectKey, String repositorySlug, String jenkinsSynchRoot) { | |
synchRepository(httpBuilder.get(path: "/rest/api/1.0/projects/$projectKey/repos/$repositorySlug"), jenkinsSynchRoot) | |
} | |
/** | |
* Создание сборки для синхронной синхронизации метаданных сборок из репозитория Bitbucket и Jenkins | |
* @param repo данные репозитория Bitbucket | |
* @param jenkinsSynchRoot папка в Jenkins, относительно которой будут генериться сборки | |
*/ | |
private void upsertSynchRepoJob(repo, String jenkinsSynchRoot) { | |
dslFactory.folder("$jenkinsSynchRoot/$repo.slug") | |
dslFactory.job(getRepoSynchJobName(jenkinsSynchRoot, repo.slug)) { | |
configure { | |
it / 'buildWrappers' / 'EnvInjectPasswordWrapper' << { | |
injectGlobalPasswords('false') | |
maskPasswordParameters('true') | |
passwordEntries() << { | |
'EnvInjectPasswordEntry' { | |
name('BITBUCKET_USER') | |
value(bitbucketSettings.login) | |
} | |
'EnvInjectPasswordEntry' { | |
name('BITBUCKET_PASSWORD') | |
value(bitbucketSettings.password) | |
} | |
} | |
} | |
} | |
environmentVariables { | |
env('PROJECT_KEY', repo.project.key) | |
env('REPOSITORY_SLUG', repo.slug) | |
env('JENKINS_SYNCH_ROOT', jenkinsSynchRoot) | |
} | |
multiscm { | |
git { | |
remote { | |
url(getCloneSshLink(repo)) | |
refspec('refs/remotes/origin/*') | |
} | |
extensions { | |
relativeTargetDirectory('repo') | |
} | |
} | |
git { | |
remote { | |
url("ssh://[email protected]/jenkins-scripts.git") | |
branches("refs/heads/master") | |
} | |
} | |
} | |
steps { | |
dsl { | |
external("synchronizeRepo.groovy") | |
removeAction('DELETE') | |
} | |
} | |
} | |
} | |
/** | |
* Создание сборки для синхронизации метаданных сборок проекта Bitbucket и Jenkins | |
* @param projectKey ключ проекта Bitbucket | |
* @param jenkinsSynchRoot папка в Jenkins, относительно которой будут генериться сборки | |
*/ | |
def upsertSynchProjectJob(String projectKey, String jenkinsSynchRoot) { | |
dslFactory.folder(jenkinsSynchRoot) | |
dslFactory.job("$jenkinsSynchRoot/~synch") { | |
configure { | |
it / 'buildWrappers' / 'EnvInjectPasswordWrapper' << { | |
injectGlobalPasswords('false') | |
maskPasswordParameters('true') | |
passwordEntries() << { | |
'EnvInjectPasswordEntry' { | |
name('BITBUCKET_USER') | |
value(bitbucketSettings.login) | |
} | |
'EnvInjectPasswordEntry' { | |
name('BITBUCKET_PASSWORD') | |
value(bitbucketSettings.password) | |
} | |
} | |
} | |
} | |
environmentVariables { | |
env('PROJECT_KEY', projectKey) | |
env('JENKINS_SYNCH_ROOT', jenkinsSynchRoot) | |
} | |
triggers { | |
cron('H 3 * * *') | |
} | |
scm { | |
git { | |
remote { | |
url("ssh://[email protected]/jenkins-scripts.git") | |
branches("refs/heads/master") | |
} | |
} | |
} | |
steps { | |
dsl { | |
external("synchronizeProject.groovy") | |
removeAction('DELETE') | |
} | |
} | |
} | |
} | |
private static String getRepoSynchJobName(String jenkinsSynchRoot, String repoSlug) { | |
"$jenkinsSynchRoot/$repoSlug/~synchRepo" | |
} | |
private static String getCloneSshLink(repo) { | |
for (link in repo.links.clone) { | |
if (link.name == "ssh") { | |
return link.href | |
} | |
} | |
throw new IllegalArgumentException("No ssh link in: repo=$repo.slug, links=$repo.links") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
jobFactory.releaseJobs() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Библиотека типовых сборок | |
* Инстанс этого объекта доступен конечным скриптам генерации сборки | |
*/ | |
class JobFactory { | |
private dslFactory | |
JobParams jobParams | |
final ReleaseJobFactory releaseJobFactory | |
/** | |
* Конструктор | |
* @param dslFactory служебный объект Jenkins для генерации сборок | |
*/ | |
JobFactory(dslFactory) { | |
this.dslFactory = dslFactory | |
} | |
/** | |
* Генерация сборок Jenkins для автоматизации релизов | |
*/ | |
void releaseJobs() { | |
if (jobParams.gitBranch == "dev") { | |
releaseJobFactory.createReleaseBranchJob(jobParams) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Параметры сборки | |
* Инстанс этого объекта доступен конечным скриптам генерации сборки | |
*/ | |
class JobParams { | |
private final String jenkinsDirectory | |
private final String gitRepoUrl | |
private final String gitBranch | |
JobParams(String jenkinsDirectory, String gitRepoUrl, String gitBranch) { | |
this.jenkinsDirectory = jenkinsDirectory | |
this.gitRepoUrl = gitRepoUrl | |
this.gitBranch = gitBranch | |
} | |
/** | |
* Папка в Jenkins, в которой надо сформировать сборку | |
* Формируется по принципу <jenkins_synch_root>/<repo_name>/<branch_name> | |
*/ | |
String getJenkinsDirectory() { | |
return jenkinsDirectory | |
} | |
/** | |
* URL Git репозитория, для которого формируется сборка | |
*/ | |
String getGitRepoUrl() { | |
return gitRepoUrl | |
} | |
/** | |
* Ветка, для которой выполняется скрипт генерации сборки | |
*/ | |
String getGitBranch() { | |
return gitBranch | |
} | |
static builder() { | |
return new Builder() | |
} | |
static class Builder { | |
private String jenkinsDirectory | |
private String gitRepoUrl | |
private String gitBranch | |
Builder withJenkinsDirectory(String jenkinsDirectory) { | |
this.jenkinsDirectory = jenkinsDirectory | |
return this | |
} | |
Builder withGitRepoUrl(String gitRepoUrl) { | |
this.gitRepoUrl = gitRepoUrl | |
return this | |
} | |
Builder withGitBranch(String gitBranch) { | |
this.gitBranch = gitBranch | |
return this | |
} | |
JobParams build() { | |
return new JobParams(jenkinsDirectory, gitRepoUrl, gitBranch) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Фабрика генерация сборок для автоматизации релизного цикла | |
*/ | |
class ReleaseJobFactory { | |
private dslFactory | |
/** | |
* Конструктор | |
* @param dslFactory служебный объект Jenkins для генерации сборок | |
*/ | |
ReleaseJobFactory(dslFactory) { | |
this.dslFactory = dslFactory | |
} | |
/** | |
* Срезание релизной ветки | |
* @param jobParams параметры сборки | |
*/ | |
void createReleaseBranchJob(JobParams jobParams) { | |
dslFactory.folder(jobParams.jenkinsDirectory) | |
dslFactory.job(jobParams.jenkinsDirectory + "/createReleaseBranch") { | |
jdk('jdk1.8.0') | |
scm { | |
git { | |
remote { | |
url(jobParams.gitRepoUrl) | |
refspec('refs/remotes/origin/*') | |
branches("**/$jobParams.gitBranch") | |
} | |
} | |
} | |
steps { | |
gradle { | |
makeExecutable(true) | |
description('Срезание релизного бранча') | |
tasks(':createReleaseBranch') | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Скрипт создания корневых сборок, который будут синхронизировать Jenkins и Bitbucket на уровне проектов | |
*/ | |
import BitbucketSynchronizer | |
import BitbucketSettings | |
def bitbucketSettings = new BitbucketSettings(new URL("https://bitbucket.example.net"), | |
BITBUCKET_USER, BITBUCKET_PASSWORD) | |
BitbucketSynchronizer synchronizer = new BitbucketSynchronizer(this, bitbucketSettings) | |
synchronizer.upsertSynchProjectJob("BACKEND-SERVICES", "BACKEND/services") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Скрипт синхронизации Jenkins и Bitbucket на уровне проекта | |
*/ | |
import BitbucketSynchronizer | |
import BitbucketSettings | |
def bitbucketSettings = new BitbucketSettings(new URL("https://bitbucket.example.net"), | |
BITBUCKET_USER, BITBUCKET_PASSWORD) | |
new BitbucketSynchronizer(this, bitbucketSettings).synchProjectFromBitbucket(PROJECT_KEY, JENKINS_SYNCH_ROOT) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Скрипт синхронизации Jenkins и Bitbucket на уровне репозитория | |
*/ | |
import BitbucketSynchronizer | |
import BitbucketSettings | |
def bitbucketSettings = new BitbucketSettings( | |
new URL("https://bitbucket.example.net"), | |
BITBUCKET_USER, BITBUCKET_PASSWORD) | |
new BitbucketSynchronizer(this, bitbucketSettings).synchRepoFromBitbucket( | |
PROJECT_KEY, REPOSITORY_SLUG, JENKINS_SYNCH_ROOT) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment