Jenkins Notlarım
- Jenkins Betiklerinde Tek Çift Tırnak Sorunu
- Jenkins içinde işçi düğüm olarak Jenkins’i, Docker konteyner çalıştırmak
- Declarative Pipeline içinde JSON dosyasını okumak
- JSON Verinin Map Türünde İçeri Alınması
- Jenkins İçinde Docker Çalıştırmak
- İstisna Yakalama
- setBuildResult(‘SUCCESS|UNSTABLE…’) & build.getResult()
- java.io.IOException: Cannot run program “nohup”
- Google Chrome’un Ubuntuya Kurulumu
- Webdriver-manager Kurulumu
- CasC ayarlarını otomatik yedeklemek
Jenkins Betiklerinde Tek Çift Tırnak Sorunu
Kaynak *
node {
echo 'Results included as an inline comment exactly how they are returned as of Jenkins 2.121, with $BUILD_NUMBER = 1'
echo 'No quotes, pipeline command in single quotes'
sh 'echo $BUILD_NUMBER' // 1
echo 'Double quotes are silently dropped'
sh 'echo "$BUILD_NUMBER"' // 1
echo 'Even escaped with a single backslash they are dropped'
sh 'echo \"$BUILD_NUMBER\"' // 1
echo 'Using two backslashes, the quotes are preserved'
sh 'echo \\"$BUILD_NUMBER\\"' // "1"
echo 'Using three backslashes still results in only preserving the quotes'
sh 'echo \\\"$BUILD_NUMBER\\\"' // "1"
echo 'To end up with \" use \\\\\\" (yes, six backslashes)'
sh 'echo \\\\\\"$BUILD_NUMBER\\\\\\"'
echo 'This is fine and all, but we cannot substitute Jenkins variables in single quote strings'
def foo = 'bar'
sh 'echo "${foo}"' // (returns nothing)
echo 'This does not interpolate the string but instead tries to look up "foo" on the command line, so use double quotes'
sh "echo \"${foo}\"" // bar
echo 'Great, more escaping is needed now. How about just concatenate the strings? Well that gets kind of ugly'
sh 'echo \\\\\\"' + foo + '\\\\\\"' // \"bar\"
echo 'We still needed all of that escaping and mixing concatenation is hideous!'
echo 'There must be a better way, enter dollar slashy strings (actual term)'
def command = $/echo \\\"${foo}\\\"/$
sh command // \"bar\"
echo 'String interpolation works out of the box as well as environment variables, escaped with double dollars'
def vash = $/echo \\\"$$BUILD_NUMBER\\\" ${foo}/$
sh vash // \"3\" bar
echo 'It still requires escaping the escape but that is just bash being bash at that point'
echo 'Slashy strings are the closest to raw shell input with Jenkins, although the non dollar variant seems to give an error but the dollar slash works fine'
}
Jenkins içinde işçi düğüm olarak Jenkins’i Docker konteyner çalıştırmak
Windows içinde Jenkins çalıştırmak için bu adresi takip edebilirsiniz: https://www.jenkins.io/doc/book/installing/windows/ veya https://medium.com/@thishantha17/installing-and-running-jenkins-as-a-standalone-application-c5e689cdb7f9
Çalıştırdıktan sonra aşağıdaki gibi bir komutla işçi Jenkins düğümü ayaklandırabilirsiniz:
docker run
-i
--rm
--privileged
--cpuset-cpus="0-3"
--name ajan
-v /c/Users/cem.topkaya/.ssh:/root/.ssh
-v /c/calismalar:/home/jenkins/calismalar
-v /c/calismalar/home_jenkins_agent:/home/jenkins/agent
--init
cemkins/agent
-i
stdIn ile bilgi girilebilecek halde çalıştırıyoruz--rm
İşimiz bitince (docker durunca) kaldır--privileged
root Kullanıcısıyla başlat--name
Konteyner adı “ajan” olsun--init
https://stackoverflow.com/a/44689700/104085
Declarative Pipeline içinde JSON dosyasını okumak (readJSON)
stage('readJSON angular.json') {
steps{
script {
def props = readJSON file: './angular.json'
def projects = props['projects']
projects.each { println it }
}
}
}
Bir JSON verisini Groovy ile işlerken ister jsonObjesi["anahtar_adı"]
ister jsonObjesi.anahtar_adı
ile elde edebilirsiniz. Bir dizi veya bir JSON nesnesinin özellikleri arasında jsonObjesi.each { özellikAdı, değeri -> … }
dönebilirsiniz.
Önemli kısım şöyle:
def json = readJSON file: './angular.json'
def projects = json['projects']
projects = json.projectsif(json.projects){
// sadece e2e testleri olan projeleri MAP içinde saklayalım
def e2eProjeleri = [:]
json.projects.each { key, value -> // value.architect.e2e değeri null olduğunda
// falsey değer olacaktır.
// Aksi halde truthy olacak ve if şartına girecek if(value.architect.e2e){
e2eProjeleri[key] = value.projectType
}
}}e2eProjeleri.each{ k, v -> println k }
Map
tipinde bir değişkeni [:]
ile oluşturuyoruz. İçine değer olarak xxx[anahtar] = değer
şeklinde veri yazıyoruz.
JSON Verinin Map Türünde İçeri Alınması
Groovy’de Map tipi Javada LinkedHashMap tipine dönüşür. Jenkins içinde readJSON ile okuduğumuz JSON metni Map türünde içeri alınır.
stages {
stage('LinkedHashMap tipinde saklar') {
steps{
script {
def map = [:]
map.bir = 1 // degisken.ozellik
map["iki"] = 2 // degisken["ozellik"]
def uc = "uc"
map["${uc}"] = 3 // degisken["${ozellik}"]
def dort = "dort"
map[(dort)] = 4
map[("bes")] = 5
map[6] = 6
map[[a:"aa"]] = "AA"
map[{js:"on"}]= readJSON text: '{"java":"script"}'
println map
/* {
* bir=1,
* iki=2,
* uc=3,
* dort=4,
* bes=5,
* 6=6,
* {a=aa}=AA,
* org.jenkinsci.plugins.workflow.cps.CpsClosure2@562437c9={"java":"script"}
* }
println map.getClass() //class java.util.LinkedHashMap
}
}
}
}
Dikkat! Anahtarların tırnak içinde olmadığına dikkat edin.
[n:"es", ne:"neee"]
def a = [:]
a["bir"] = 1
a["nesne"] = [n:"es", ne:"neee"]
println a
// {bir=1, nesne={n=es, ne=neee}}
println a.getClass()
// class java.util.LinkedHashMapdef jsonstring = '{ "key": null, "a": "b", "c": { "d":"dee", "e": [1,2] }, "dizi": [{ "di":"zi", "zi": 2 }] }'def pojoTrue = readJSON text: jsonstring, returnPojo: true
println pojoTrue
// {key=null, a=b, c={d=dee, e=[1, 2]}, dizi=[{di=zi, zi=2}]}
println pojoTrue.getClass()
// class java.util.LinkedHashMapdef pojoFalse = readJSON text: jsonstring, returnPojo: false
println pojoFalse
// {"key":null,"a":"b","c":{"d":"dee","e":[1,2]},"dizi":[{"di":"zi","zi":2}]}
println pojoFalse.getClass()
// class net.sf.json.JSONObject
Jenkins İçinde Docker Çalıştırmak
Docker dediğimiz işin arkasında LXC (Linux Containers) yatıyor ve doğal olarak Linux makinesi üstünde koşuyor.
Öncelikle Docker Toolbox olarak başladığım windows üstündeki docker kullanımımı Docker Desktop’a dönüştürdüm. Çünkü Docker Toolbox çalışmak için Virtual Box kuruyor ve içinde bir Linux sanal makinesini ayaklandırıyordu. Windows 10 belli bir sürümünde Windows Subsystem Linux (WSL) çalıştırabildiği için windows içinde ubuntu linux çalıştırıyor olduğum için docker’ı da WSL üstünde koşturmak istedim.
Yaşadığım sorunlardan bir tanesi Docker için sertifika yolunun Docker Toolbox ve Docker Desktop üstünde farklı adreslerde olmasıydı:
unable to resolve docker endpoint: open C:\Users\cem.topkaya\.docker\machine\machines\default\ca.pem: The system cannot find the path specified.
Sisteminizde çalışan docker
ile ilgili bilgileri aşağıdaki komutlarla alabilirsiniz:
$ docker system infoveya $ docker info
Sadece docker
komutunu çalıştırdığımda varsayılan dizinleri yardım bilgileri üstünde görüp neden farklı ayarların sürekli geldiğini anladım
$ dockerUsage: docker [OPTIONS] COMMANDA self-sufficient runtime for containersOptions:
--config string Location of client config files (default "/home/cemt/.docker")
-D, --debug Enable debug mode
-H, --host list Daemon socket(s) to connect to
-l, --log-level string Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")
(default "info")
--tls Use TLS; implied by --tlsverify
--tlscacert string Trust certs signed only by this CA (default "/home/cemt/.docker/ca.pem")
--tlscert string Path to TLS certificate file (default "/home/cemt/.docker/cert.pem")
--tlskey string Path to TLS key file (default "/home/cemt/.docker/key.pem")
--tlsverify Use TLS and verify the remote
-v, --version Print version information and quitManagement Commands:
builder Manage builds
config Manage Docker configs
/home/cemt/.docker
dizinine gittiğimde eski Docker ToolBox dizinini işaret ettiğini ve ayarları oradan okuduğunu gördüm.
context’in bağlandığı dizine gidince aslında /etc/docker/
ayar dosyasında belirtmiş olduğum insecure-registries
özelliğinin neden hep 127.0.0.1 okunduğunu ve ancak docker servisini wsl içinde tekrar başlatınca /etc/docker/daemon.json
dosyasını okuyarak başladığını gördüm:
Buradan çıkarılacak ders: Docker Toolbox’ı önce kaldırın sonra Docker Desktop kurun :)
Tüm Docker Desktop’ı kaldırıp, docker dizin kalıntılarını sildikten sonra tekrar Docker Desktop kurdum. %UserProfile%/.docker
dizininde daemon.json dosyası ve diğer dosyaları Docker Desktop oluşturdu ve buradan okuyarak WSL içinde docker başlatıldı.
WSL ile Linux ve Windows iç içeler. Eğer sudo ile Linux sistemine tam geçiş yaparsanız docker
bilgilerini bu kez root
dizini içinden çekmeye çalıştığını göreceksiniz.
İstisna Yakalama
Çok detaylı bilgilendirme olmasa bile, mesaj ve stack trace iş görüyor bu ikisini yazdırmak için:
err.getMessage()
err.getStackTrace()
Bir örnekle
try {
sh "hata fırlatması muhakkak"
}
catch (err) {
println "!!!!!!!!!!! istisna !!!!!!!!!!!!!!"
echo "Sadece mesajı yazdırır: $err"
echo "Mesajı yazdırır: ${err.getMessage()}"
echo "Yığını yazdırır: ${err.getStackTrace().join('\n')}" // Hali hazırdaki build'in hataya düştüğünü
// aşağıdaki gibi işaretlemek boynumuzun borcu
currentBuild.result = 'FAILURE'
}
Tabi istisnayı yakalıp devam etmek istemiyorsanız tekrar fırlatmanız gerekir
node {
stage('Example') {
try {
sh 'exit 1'
}
catch (exc) {
echo 'Something failed, I should sound the klaxons!'
throw
}
}
}
Bir de hatayı yakaladınız ve bir şey daha yapmak istiyorsanız (kaynağı serbest bırakmak, ele güne duyurmak vs.) finally
bloğu işinizi görecek
node {
sh './set-up.sh'
try {
sh 'might fail'
echo 'Succeeded!'
} catch (err) {
echo "Failed: ${err}"
} finally { // "bu böyle bitmez çorbacı" dediğimiz kısım
sh './tear-down.sh'
}
echo 'Printed whether above succeeded or failed.'
}
Eğer declarative script ile yazıyorsanız kolayı var (catchError
)
node {
catchError {
sh 'might fail'
}
step([$class: 'Mailer', recipients: 'admin@somewhere'])
}
Yukarıda hataya düşünce pipeline build
durumu “failed
” olarak değiştirilir otomatik olarak.
Ancak bazen hatanın etkileyeceği durumları biz seçmek isteriz. Mesela hata olduğunda stage ABORTED
olsun ama build süreci SUCCESS
devam etsin isteyebiliriz tıpkı aşağıdaki gibi. Zaman aşımı önce 3 saniye set ediliyor ancak steps içinde 5 saniye bekletiliyor. Doğal olarak zaman aşıyor ve catchError
’a düşüyor. Ancak catchError
stage durumunu ABORTED
diye işaretlerken build için SUCCESS
diye devam edecek şekilde ayarlandığını için yola devam ediliyor:
Scripted Pipeline
içinde yukarıdaki durumda A’nın da SUCCESS
ile devam edeceği şekli şöyle yazabiliyoruz:
setBuildResult(‘SUCCESS|UNSTABLE…’) & build.getResult()
setBuildResult(‘SUCCESS|UNSTABLE…’) metoduna geçeceğimiz parametreyle yapılandırma sürecini başarılı, başarısız, karasız gibi durumlarla işaretleyebilirsiniz. Yapılandırma (build) sürecinin durumunu ise build.getResult() ile elde edebiliriz.
Stage
ve bütün build
olayının result
durum bilgisini set etmemiz mümkün *:
buildResult = 'null' | 'SUCCESS' | 'UNSTABLE' | 'ABORTED'currentBuild.result = 'null' | 'SUCCESS' | 'FAILURE'
Ve buildResult
durumunu çekebilmek için:
build.getResult()
Referanslar:
java.io.IOException: Cannot run program "nohup"
Webdriver-manager Kurulumu
Exception: java.io.IOException: Cannot run program "nohup" (in directory "C:\_dev\jenkins\.jenkins\workspace\shared-lib-project-multi-repo\source_codes"): CreateProcess error=2, Sistem belirtilen dosyayı bulamıyor
sh
Komutu çalıştırmak istediğimiz sunucu windows olunca doğal olarak hata fırlatıyor. sh
Komutunu Windows üstünde koşturmak için Git for Windows kurulumu yaptıktan sonra nohup.exe dosyasına “C:\Program Files\git\usr\bin
” dizininde erişebileceğiz. Bu dosyaya erişimi genelleştirmek için mklink ile sembolik bağlantılar oluşturacağız.
Aşağıdaki dosyalara sembolik bağlantılar oluşturduktan sonra Jenkins hattınız windows üstünde sh koştursanız bile komutunuzu çalıştırabilecektir.
mklink "C:\Program Files\Git\bin\nohup.exe" "C:\Program Files\git\usr\bin\nohup.exe"mklink "C:\Program Files\Git\bin\msys-2.0.dll" "C:\Program Files\git\usr\bin\msys-2.0.dll"mklink "C:\Program Files\Git\bin\msys-iconv-2.dll" "C:\Program Files\git\usr\bin\msys-iconv-2.dll"mklink "C:\Program Files\Git\bin\msys-intl-8.dll" "C:\Program Files\git\usr\bin\msys-intl-8.dll"
Referanslar:
Google Chrome’un Ubuntuya Kurulumu
Yüklü mü? $ whereis google-chrome
İle kontrol edebiliriz
Yokmuş! Önce indirip:
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
Sonra kuruyoruz:
sudo apt install ./google-chrome-stable_current_amd64.deb
Tekrar kontrol edelim var mı diye?
Webdriver-manager Kurulumu
Uçtan uca testleri çalıştırdığımızda internet gezginini başlatıp komutları geçirebilmek için webdriver-manager en önemli aracımız olacak.
Kurmak için:
$ npx webdriver-manager update
CasC Ayarlarını Otomatik Yedeklemek
Jenkins ortamının ayaklandırılması için docker-compose.yaml
dosyası:
version: '3.7'
networks:
jenkins-cinar:
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "true"
ipam:
driver: default
config:
- subnet: 172.16.30.0/24
gateway: 172.16.30.1
services:
test:
image: cinar/jenkins:cinar-1.1
container_name: test
privileged: true
restart: unless-stopped
environment:
- CASC_JENKINS_CONFIG=/usr/share/jenkins/casc.yaml
networks:
jenkins-cinar:
ipv4_address: 172.16.30.2
ports:
- 88:8090
- 50008:50000
volumes:
# Yedekleme dizini
- ./volume/backup:/tmp/backup
- jenkins-test-home:/usr/share/jenkins
# CASC için ayarla
- ./volume/cascs/initial-casc.yaml:/usr/share/jenkins/casc.yaml
# Kullanıcılar ve credetials bu bölümde bağlanacak
- ./volume/secret/secrets:/usr/share/jenkins/secrets
- ./volume/secret/secret.key:/usr/share/jenkins/secret.key
# Docker istemcisi dışarıdan bağlanacaksa
- /var/run/docker.sock:/var/run/docker.sock
- /usr/local/bin/docker:/usr/local/bin/docker
volumes:
jenkins-test-home:
name: jenkins-test-home
external: true
initial-casc.yaml
Dosyası aşağıda olduğu gibidir. Otomatik yedekleme dizini /tmp/backup
ancak bu dizini konteyner dışına da bağlamak gerekiyor.
jenkins:
systemMessage: "MMMerhaba,\n Yazılım Tanımlı Ağ Teknolojileri Müdürlüğü'nün Jenkins sistemine hoş geldiniz."
jobs:
- script: >
folder('sistem'){
displayName('Sistem Dizini')
description('Jenkins başlatıldığında oluşturulur ve sisteme dair görevleri içerir')
}
- script: >
freeStyleJob('sistem/casc-yedekle') {
description('CasC bilgilerini otomatik yedekleyen görevdir.')
displayName('CasC Yedekleme')
triggers {
scm '* * * * *'
}
steps {
shell('''
curl -X POST 'http://localhost:8090/configuration-as-code/export' \
-H "Jenkins-Crumb:$(curl -s -X GET http://localhost:8090/crumbIssuer/api/json | python3 -c \'import sys, json; print(json.load(sys.stdin)["crumb"])\')" \
-s > /tmp/backup/casc-$(date +%Y%m%d-%H%M%S).yaml
''')
}
}
unclassified:
location:
adminAddress: "admin@ulakhaberlesme.com.tr"
url: "https://jenkins-test.ulakhaberlesme.com.tr/"