Jenkins Notlarım

Cem Topkaya
9 min readApr 24, 2020

--

  • 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.projects
if(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.LinkedHashMap
def 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.LinkedHashMap
def 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.

LXC vs Docker flockport.com

Ö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 quit
Management 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:

A fail oluyor ama B success ile sona ulaşıyor

Scripted Pipeline içinde yukarıdaki durumda A’nın da SUCCESS ile devam edeceği şekli şöyle yazabiliyoruz:

catc içinde currentBuild.result = ‘SUCCESS’

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.

setBuildResult(….)

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

$ whereis google-chrome

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?

$ whereis google-chrome

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/"

--

--

Cem Topkaya
Cem Topkaya

Written by Cem Topkaya

Evlat, kardeş, ağabey, eş, baba, müzik sever, öğrenmek ister, paylaşmaya can atar, iyi biri olmaya çalışır, hakkı geçenlerden helallik ister vs.

No responses yet