Jenkins
Примеры Pipeline
и базовый синтаксис Groovy
.
docker run -d --name=jenkins -p 8080:8080 --restart=always -v jenkins_home:/var/jenkins_home jenkins/jenkins:latest
ls /var/lib/docker/volumes/jenkins_home/_data/jobs
директория хранящая историю сборок в хостовой системе
docker exec -u root -it jenkins /bin/bash
подключиться к контейнеру под root
cat /var/jenkins_home/secrets/initialAdminPassword
получить токен инициализации
apt-get update && apt-get install -y iputils-ping netcat-openbsd
установить ping и nc на машину сборщика (master slave)
jenkinsVolumePath=$(docker inspect jenkins | jq -r .[].Mounts.[].Source)
получить путь к директории Jenkins в хостовой системе
sudo tar -czf $HOME/jenkins-backup.tar.gz -C $jenkinsVolumePath .
резервная копия всех файлов
(crontab -l ; echo "0 23 * * * sudo tar -czf /home/lifailon/jenkins-backup.tar.gz -C /var/lib/docker/volumes/jenkins_home/_data .") | crontab -
sudo tar -xzf $HOME/jenkins-backup.tar.gz -C /var/lib/docker/volumes/jenkins_home/_data
восстановление
wget http://127.0.0.1:8080/jnlpJars/jenkins-cli.jar -P $HOME/
скачать jenkins-cli (http://127.0.0.1:8080/manage/cli)
apt install openjdk-17-jre-headless
установить java runtime
java -jar jenkins-cli.jar -auth lifailon:password -s http://127.0.0.1:8080 -webSocket help
получить список команд
java -jar jenkins-cli.jar -auth lifailon:password -s http://127.0.0.1:8080 groovysh
запустить консоль Groovy
java -jar jenkins-cli.jar -auth lifailon:password -s http://127.0.0.1:8080 install-plugin ssh-steps -deploy
устанавливаем плагин SSH Pipeline Steps
API
$username = "Lifailon"
$password = "password"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username,$password)))
$headers = @{Authorization=("Basic {0}" -f $base64AuthInfo)}
Invoke-RestMethod "http://192.168.3.101:8080/rssAll" -Headers $headers Invoke-RestMethod "http://192.168.3.101:8080/rssFailed" -Headers $headers
$jobs = Invoke-RestMethod "http://192.168.3.101:8080/api/json/job" -Headers $headers
$jobs.jobs.name $jobName = "Update SSH authorized_keys"
$job = Invoke-RestMethod "http://192.168.3.101:8080/job/${jobName}/api/json" -Headers $headers
$job.builds $buildNumber = $job.lastUnsuccessfulBuild.number Invoke-RestMethod "http://192.168.3.101:8080/job/${jobName}/${buildNumber}/consoleText" -Headers $headers
$lastCompletedBuild = $job.lastCompletedBuild.number $crumb = $(Invoke-RestMethod "http://192.168.3.101:8080/crumbIssuer/api/json" -Headers $headers).crumb $headers["Jenkins-Crumb"] = $crumb $body = @{".crumb" = $crumb} Invoke-RestMethod "http://192.168.3.101:8080/job/${jobName}/${lastCompletedBuild}/rebuild" -Headers $headers -Method POST -Body $body
SSH Steps and Artifacts
Устанавливаем плагин SSH Pipeline Steps
Добавляем логин и Private Key
для авторизации по ssh: Manage (Settings)
=> Credentials
=> Global
=> Add credentials
=> Kind: SSH Username with private key
Сценарий проверяет доступность удаленной машины, подключается к ней по ssh, выполняет скрипт hwstat для сбора метрик и выгружает json отчет в артефакты:
def remote = [:]
pipeline {
agent any
parameters {
string(name: 'address', defaultValue: '192.168.3.101', description: 'Адрес удаленного сервера')
string(name: 'port', defaultValue: '22', description: 'Порт ssh')
string(name: 'credentials', defaultValue: 'd5da50fc-5a98-44c4-8c55-d009081a861a', description: 'Идентификатор учетных данных из Jenkins')
booleanParam(name: "root", defaultValue: false, description: 'Запуск с повышенными привилегиями')
booleanParam(name: "report", defaultValue: true, description: 'Выгружать отчет в формате json')
}
triggers {
cron('H */6 * * 1-5') }
options {
timeout(time: 5, unit: 'MINUTES') retry(2) }
environment {
SSH_KEY_FILE = "/tmp/ssh_key_${UUID.randomUUID().toString()}"
}
stages {
stage('Проверка доступности хоста (icmp и tcp)') {
steps {
script {
def check = sh(
script: """
ping -c 1 ${params.address} > /dev/null || exit 1
nc -z ${params.address} ${params.port} || exit 2
""",
returnStatus: true )
if (check == 1) {
error("Сервер ${params.address} недоступен (icmp ping)")
} else if (check == 2) {
error("Порт ${params.address} закрыт (tcp check)")
} else {
echo "Сервер ${params.address} доступен и порт ${params.port} открыт"
}
}
}
}
stage('Извлечение данных для авторизации по ключу') {
steps {
script {
withCredentials([sshUserPrivateKey(credentialsId: params.credentials, usernameVariable: 'SSH_USER', keyFileVariable: 'SSH_KEY', passphraseVariable: '')]) {
writeFile(file: env.SSH_KEY_FILE, text: readFile(SSH_KEY))
sh "chmod 600 ${env.SSH_KEY_FILE}"
remote.name = params.address
remote.host = params.address
remote.port = params.port.toInteger()
remote.user = SSH_USER
remote.identityFile = env.SSH_KEY_FILE
remote.allowAnyHosts = true
}
}
}
}
stage('Запуск скрипта через через ssh') {
steps {
script {
def runCommand
if (params.root) {
runCommand = """
curl -sS https://raw.githubusercontent.com/Lifailon/hwstat/rsa/hwstat.sh | sudo bash -s -- "json" > "hwstat-report.json"
"""
} else {
runCommand = """
curl -sS https://raw.githubusercontent.com/Lifailon/hwstat/rsa/hwstat.sh | bash -s -- "json" > "hwstat-report.json"
"""
}
def jsonOutput = sshCommand remote: remote, command: runCommand
if (params.report) {
sshGet remote: remote, from: "hwstat-report.json", into: "${env.WORKSPACE}/hwstat-report.json", override: true
}
sshCommand remote: remote, command: "rm hwstat-report.json"
}
}
}
stage('Загрузка json отчета в Jenkins') {
when {
expression { params.report }
}
steps {
archiveArtifacts artifacts: 'hwstat-report.json', allowEmptyArchive: true
}
}
}
post {
always {
script {
sh "rm -f ${env.SSH_KEY_FILE}"
}
}
success { echo "Сборка завершена успешно" }
failure { echo "Сборка завершилась с ошибкой" }
unstable { echo "Сборка завершилась с предупреждениями" }
changed { echo "Текущий статус завершения изменился по сравнению с предыдущим запуском" }
fixed { echo "Сборка завершена успешно по сравнению с предыдущим запуском" }
aborted { echo "Запуск был прерван" }
}
}
Update SSH authorized_keys
Добавляем логин и пароль для авторизации по ssh: Manage (Settings)
=> Credentials
=> Global
=> Add credentials
=> Kind: Username with password
Сценарий обновляет параметр со списком текущих пользователей на машине и добавляет или заменяет ssh ключ для выбранного пользователя:
def remote = [:]
pipeline {
agent any
parameters {
string(name: 'address', defaultValue: '192.168.3.101', description: 'Адрес удаленного сервера')
string(name: 'port', defaultValue: '22', description: 'Порт ssh')
string(name: 'credentials', defaultValue: '15d05be6-682a-472b-9c1d-cf5080e98170', description: 'Идентификатор учетных данных из Jenkins')
booleanParam(name: "getUsers", defaultValue: true, description: 'Получить список текущих пользователей системы')
string(name: 'sshKey', defaultValue: '', description: 'Открытый ssh ключ для добавления в authorized_keys')
booleanParam(name: "rewriteKey", defaultValue: false, description: 'Перезаписать текущие ключи в файле authorized_keys')
}
stages {
stage('Извлекаем параметры для авторизации по ssh') {
steps {
script {
withCredentials([usernamePassword(credentialsId: params.credentials, usernameVariable: 'SSH_USER', passwordVariable: 'SSH_PASS')]) {
remote.name = params.address
remote.host = params.address
remote.port = params.port.toInteger()
remote.user = env.SSH_USER
remote.password = env.SSH_PASS
remote.allowAnyHosts = true
}
}
}
}
stage('Обновить список пользователей') {
when {
expression { params.getUsers }
}
steps {
script {
def mainCommand = "echo \$(ls /home)"
def users = sshCommand remote: remote, command: mainCommand
def usersList = users.trim().split("\\s")
usersList += 'root'
def usersListChoice = usersList.toList()
writeFile file: 'user_list.txt', text: usersList.join("\\s")
properties([
parameters([
string(name: 'address', defaultValue: params.address, description: 'Адрес удаленного сервера'),
string(name: 'port', defaultValue: params.port, description: 'Порт ssh'),
string(name: 'credentials', defaultValue: params.credentials, description: 'Идентификатор учетных данных из Jenkins'),
booleanParam(name: "getUsers", defaultValue: params.getUsers, description: 'Получить список текущих пользователей системы'),
string(name: 'sshKey', defaultValue: '', description: 'Открытый ssh ключ для добавления в authorized_keys'),
booleanParam(name: "rewriteKey", defaultValue: false, description: 'Перезаписать текущие ключи в файле authorized_keys'),
choice(
name: 'userList',
choices: usersListChoice,
description: 'Выбрать пользователя'
)
])
])
}
}
}
stage('Добавить новый SSH ключ') {
when {
expression { !params.getUsers && params.sshKey }
}
steps {
script {
def selectedUser = params.userList
def sshKey = params.sshKey
if (selectedUser == "root") {
path = "/root/.ssh/authorized_keys"
} else {
path= "/home/${selectedUser}/.ssh/authorized_keys"
}
if (params.rewriteKey) {
echo "Обновляем все SSH ключи для пользователя: ${selectedUser}"
teeCommand = "tee"
} else {
echo "Добавляем новый SSH ключ для пользователя: ${selectedUser}"
teeCommand = "tee -a"
}
def mainCommand = """
checkFile=\$(ls $path 2> /dev/null || echo false)
if [ \$checkFile == "false" ]; then
mkdir -p \$(dirname $path) && touch $path
fi
echo $sshKey | $teeCommand $path > /dev/null
"""
sshCommand remote: remote, command: mainCommand
}
}
}
}
}
Upload File Parameter
Установить плагин File Parameter и перезагрузить Jenkins для использования нового параметра.
Передача файла через параметр и чтение его содержимого:
pipeline {
agent any
parameters {
base64File 'UPLOAD_FILE'
}
stages {
stage('Читаем содержимое файла') {
steps {
withFileParameter('UPLOAD_FILE') {
sh """
echo "$UPLOAD_FILE" # выводим путь к временному файлу с содержимым переданного файла
cat "$UPLOAD_FILE" # читаем содержимое файла
"""
}
}
}
}
}
Input Text and File
Останавливает выполнение Pipeline
и заставляет пользователя передать текстовый параметр и файл:
pipeline {
agent any
stages {
stage('Input text') {
input {
message "Что бы продолжить, передайте текст в поле ввода"
ok "Передать"
parameters {
string(name: 'TEXT', defaultValue: 'test', description: 'Введите текст')
}
}
steps {
echo "Переданный текст в параметре input: ${TEXT}"
}
}
stage('Input file') {
steps {
script {
def fileBase64 = input message: "Передайте файл", parameters: [base64File('file')]
sh "echo $fileBase64 | base64 -d"
}
}
}
}
}
HttpURLConnection
Любой код Groovy возможно запустить и проверить через Script Console
(http://127.0.0.1:8080/manage/script)
Пример API
запроса к репозиторию PowerShell на GitHub для получения последней версии и всех доступных версий:
import groovy.json.JsonSlurper
def url = new URL("https://api.github.com/repos/PowerShell/PowerShell/tags")
def connection = url.openConnection()
connection.setRequestMethod("GET")
connection.setRequestProperty("Accept", "application/json")
def responseCode = connection.getResponseCode()
if (responseCode == 200) {
def response = connection.getInputStream().getText()
def jsonSlurper = new JsonSlurper()
def tags = jsonSlurper.parseText(response)
if (tags.size() > 0) {
def latestTag = tags[0].name
println("Latest version: " + latestTag)
println()
println("List of all versions:")
for (tag in tags) {
println(tag.name)
}
} else {
error("No tags found in the response")
}
} else {
error("Failed to call API, response code: ${responseCode}")
}
connection.disconnect()
Active Choices Parameter
Плагин Active Choices позволяет динамически обновлять содержимое параметров.
Пример выбора репозитория, получения списка доступных версий и содержимого файлов выбранного релиза.
Name: Repos
Groovy Script:
return [
'Lifailon/lazyjournal',
'jesseduffield/lazydocker'
]
- Active Choices Reactive Parameter
Name: Versions
Groovy Script:
import groovy.json.JsonSlurper
def selectedRepo = Repos
def apiUrl = "https://api.github.com/repos/${selectedRepo}/tags"
def conn = new URL(apiUrl).openConnection()
conn.setRequestProperty("User-Agent", "Jenkins")
def response = conn.getInputStream().getText()
def json = new JsonSlurper().parseText(response)
def versionsCount = json.size()
def data = []
for (int i = 0; i < versionsCount; i++) {
data += json.name[i]
}
return data
Настройки параметров: Choice Type: Single Select
Привязать параметр Repos
из Active Choices
в Reactive Parameter
через Referenced parameters
Включить фильтрацию через Enable filters
- Active Choices Reactive Parameter
Name: Files
Groovy Script:
import groovy.json.JsonSlurper
def selectedRepo = Repos
def selectedVer = Versions
def apiUrl = "https://api.github.com/repos/${selectedRepo}/releases/tags/${selectedVer}"
def conn = new URL(apiUrl).openConnection()
conn.setRequestProperty("User-Agent", "Jenkins")
def response = conn.getInputStream().getText()
def json = new JsonSlurper().parseText(response)
def data = []
for (file in json.assets) {
data += file.name
}
return data
Referenced parameters: Repos,Versions
Pipeline script:
pipeline {
agent any
stages {
stage('Selected parameters') {
steps {
script {
echo "Selected repository: https://github.com/${params.Repos}"
echo "Selected version: ${params.Versions}"
echo "Selected file: ${params.Files}"
echo "Url for download: https://github.com/${params.Repos}/releases/download/${params.Versions}/${params.Files}"
}
}
}
}
}
Vault
Интеграция HashiCorp Vault в Jenkins Pipeline через REST API
для получения содержимого секретов и использовая в последующих стадиях/этапах сборки:
def getVaultSecrets(
String address,
String path,
String token
) {
def url = new URL("${address}/${path}")
def connection = url.openConnection()
connection.setRequestMethod("GET")
connection.setRequestProperty("X-Vault-Token", token)
connection.setRequestProperty("Accept", "application/json")
def response = new groovy.json.JsonSlurper().parse(connection.inputStream)
def user = response.data.data.user
def password = response.data.data.password
return [
user: user,
password: password
]
}
def USER_NAME
def USER_PASS
pipeline {
agent any
parameters {
string(name: 'url', defaultValue: 'http://192.168.3.101:8200', description: 'Url адресс хранилища секретов')
string(name: 'path', defaultValue: 'v1/kv/data/ssh-auth', description: 'Путь для извлечения секретов')
password(name: 'token', defaultValue: 'hvs.bySybhyYOxSWEVk4FQDdcyyg', description: 'Токен доступа к API HashiCorp Vault')
}
stages {
stage('Get vault secrets') {
steps {
script {
def secrets = getVaultSecrets(
"${params.url}",
"${params.path}",
"${params.token}"
)
USER_NAME = secrets.user
USER_PASS = secrets.password
}
}
}
stage('Use secrets') {
steps {
script {
echo "User: ${USER_NAME}"
echo "Password: ${USER_PASS}"
}
}
}
}
}
Email Extension
Установить расширение Email Extension для отправки на почту и настроить SMTP сервер в настройках Jenkins (System
=> Extended E-mail Notification
)
SMTP server: smtp.yandex.ru
SMTP port: 587
Credentials: Username with password
(username@yandex.ru
и app-password
) Use TLS
Default Content Type: HTML (text/html)
Настройка логирования в System Log: emailDebug
+ фильтр hudson.plugins.emailext
и уровень ALL
pipeline {
agent any
parameters {
string(name: 'emailTo', defaultValue: 'test@yandex.ru', description: 'Почтовый адрес назначения')
}
stages {
stage('Вывод всех переменных окружения') {
steps {
script {
env.getEnvironment().each { key, value ->
echo "${key} = ${value}"
}
}
}
}
stage('Отправка на почту') {
options {
timeout(time: 1, unit: 'MINUTES')
}
steps {
script {
emailext (
to: "${params.emailTo}",
subject: "${env.JOB_NAME} - ${BUILD_NUMBER}",
mimeType: "text/html",
body: """
<html>
<body>
<div style="padding-left: 30px; padding-bottom: 15px;" color="blue">
<font name="Arial" color="#906090" size="3" font-weight="normal">
<b> ${env.JOB_NAME} - ${env.BUILD_NUMBER} </b>
</font>
<br>
<div style="padding-left: 30px; padding-bottom: 15px;" color="black">
<br>
<font name="Arial" color="black" size="2" font-weight="normal">
<pre> Build url: ${env.BUILD_URL} </pre>
</font>
</body>
</html>
"""
)
}
}
}
}
}
Parallel
pipeline {
agent any
stages {
stage('Parallel sleeps') {
parallel {
stage('Task 1') {
steps {
script {
def currentTime = new Date().format('yyyy-MM-dd HH:mm:ss')
echo "[${currentTime}] Начало задачи 1"
sh 'sleep 10'
currentTime = new Date().format('yyyy-MM-dd HH:mm:ss')
echo "[${currentTime}] Завершение задачи 1"
}
}
}
stage('Task 2') {
steps {
script {
def currentTime = new Date().format('yyyy-MM-dd HH:mm:ss')
echo "[${currentTime}] Начало задачи 2"
sh 'sleep 5'
currentTime = new Date().format('yyyy-MM-dd HH:mm:ss')
echo "[${currentTime}] Завершение задачи 2"
}
}
}
}
}
stage('Parallel tasks via loop') {
steps {
script {
def tasks = [:]
def taskNames = ['Task 1', 'Task 2', 'Task 3']
taskNames.each { taskName ->
tasks[taskName] = {
def currentTime = new Date().format('yyyy-MM-dd HH:mm:ss')
echo "[${currentTime}] Начало задачи: $taskName"
sh "sleep ${taskName == 'Task 1' ? 10 : taskName == 'Task 2' ? 5 : 3}"
currentTime = new Date().format('yyyy-MM-dd HH:mm:ss')
echo "[${currentTime}] Завершение задачи: $taskName"
}
}
parallel tasks
}
}
}
}
}
Groovy
Базовый синтаксис языка Groovy
javaString = 'java'
javaString
println javaString
javaString.class println 100.class j = '${javaString}' groovyString = "${javaString}"
bigGroovyString = """
${javaString}
${j}
${groovyString}
${2 + 2}
"""
a = "a" a + "123" a * 5
list =[1,2,3]
list[0] list[0..1] range = "0123456789"
range[1..5] map = [key1: true, key2: false]
map["key1"] server = [:]
server.ip = "192.168.3.1"
server.port = 22
println(server)
def sum(a,b) {
println a+b
}
sum(2,2)
def diff(x) {
if (x < 10) {
println("${x} < 10")
} else if (x == 10) {
println("${x} = 10")
} else {
println("${x} > 10")
}
}
diff(11)
list.each { l ->
print l
}
for (i in 0..5) {
print i
}
for (int i = 0; i < 10; i++) {
print i
}
i = 0
while (i < 3) {
println(i)
i++
}
Copy Codehttps://lifailon.github.io/search_index.en.json$MATCHES more matches