Пример использования Jenkins REST API на Golang
Hi!
I’mсобираюсь goingописать toкак describe how to callвызывать Jenkins REST APIsAPI usingиспользуя Go.GO. WeСоздадим will create threeтри Go files:файла:
packages/helpers/helpers.go
packages/jenkins/jenkins.go
main.go
TheИсходный sourceкод codesдоступен areпо available atадресу: https://github.com/mohdnaim/jenkins_rest_api
helpers.go
containsсодержит functionфункции helpers.помощники. WeВ putнем functionsбудут insideрасположены thisфункции fileдля toчистоты maintainи cleanорганизации and organized codes.кода.
jenkins.go
containsсодержит functionsфункции relatedсвязанные toc Jenkins. SuchЭто functionsфункции are IsJobExist()
toдля checkпроверки whetherсуществования aконвеера задачи или билда, CopyJenkinsJob()
для копирования Jenkins jobзадания orи build pipeline exists, CopyJenkinsJob() to copy a Jenkins job and DownloadConfigXML()
toдля downloadскачивания theконфигурации configurationsзадачи ofв aформате job in XML format.XML.
main.go
isэто ourглавный mainфайл, Goточка file,старта theнашей starting point of our program.программы.
//main.go
package main
import (
"fmt"
"log"
"strings"
helpers "./packages/helpers"
jenkins "./packages/jenkins"
)
func main() {
// compulsoryОбязательные to setнастройки
jenkinsURL := "http://127.0.0.1/АДРЕС СЕРВЕРА"
jenkinsUsername := "putИМЯ your username here"ПОЛЬЗОВАТЕЛЯ"
jenkinsAPIToken := "put your API token here"ТОКЕР"
jenkins.JenkinsDetails = jenkins.Details{jenkinsURL, jenkinsUsername, jenkinsAPIToken}
xmlFolder := "xml"
// 1. getПолучаем allвсе existingдоступные projects / jenkins jobsпровекты\задачи
allProjectNames := jenkins.GetAllProjectNames()
// 2. filterФильтруем outнаши projectsпроекты thatкоторые weмы wantищем
filteredProjectNames := make([]string, 0)
for _, projectName := range allProjectNames {
// doЧто-то somethingделаем
// appendДобавляем toв anotherдругой sliceсрез basedосновываясь onна conditionусловии
if strings.HasPrefix(projectName, "prefix") {
filteredProjectNames = append(filteredProjectNames, projectName)
}
}
// 3. forДля eachкаждого project,проекта getполучаем itsего config.xml
for _, projectName := range filteredProjectNames {
xmlPath := fmt.Sprintf("%s/%s.xml", xmlFolder, projectName)
if err := jenkins.DownloadConfigXML(projectName, xmlPath); err != nil {
log.Println("error download config.xml for project:", projectName)
continue // skipпропускаем
}
}
// 4. modify itsИзменяем config.xml
files := helpers.GetFilenamesRecursively(xmlFolder)
for _, xmlFile := range files {
log.Println(xmlFile)
}
// 4b. rewriteПереписываем config.xml
// 5. http request POST updatedзапрос обновления config.xml
for _, xmlFile := range files {
tmpSlice := strings.Split(xmlFile, "/")
projectName := tmpSlice[len(tmpSlice)-1]
log.Println(projectName)
if err := jenkins.PostConfigXML(projectName, xmlFile); err != nil {
log.Println("error postconfigxml:", projectName)
}
}
}
WeНам needнужно toнастроить configure the details of theдетали Jenkins instance.объекта. SoУкажем setкорректное correctзначение valuesдля toследующих the following variables:переменных:
jenkinsURL := “http://127.0.0.1/”"АДРЕС СЕРВЕРА"
jenkinsUsername := “put"ИМЯ yourПОЛЬЗОВАТЕЛЯ"
username here”
jenkinsAPIToken := “put your "API tokenТОКЕР"
here”
Строка #18
Создаем структуру содержащую три переменных выше. Структура используется функции в jenkins.go
поэтому перед вызовом любой функции из jenkins.go
модуля, нам нужно настроить эти значения.
Line #18 creates a struct containing the three variables above. The struct is used by functions in jenkins.go so before we invoke any function in the jenkins.go module, we need to set the values.
Line #19
Укажем theпапку folderв nameкоторой weбудем store theхранить XML filesфайл, –который whichявляется are the configuration file ofконфигурацией Jenkins jobs.задачи.
Line #22
Получаем weимя retrieveвсех theпроектов namesсуществующих ofв allJenkins. projectsЕсли existмы inпосмотрим theна Jenkinsреализацию instance.функции If we look at the implementation of the function GetAllProjectNames()
inв jenkins.
, go,gotheфункция functionвызывает invokes DownloadFileToBytes()
. ItОна makesделаеет HTTP requestзапрос toна /api/json
withиспользуя theимя usernameпользователя andи passwordпароль inв theзаголовке requestзапроса. header.Точка Theдоступа endpointвернет returnsимя namesпроекта of project inв JSON format.формате. ItЗатем then converts theпреобразуется JSON formatв intoresult
‘result’словарь. map.Любое Forимя everyв nameсловаре inдобавляет theимя map,в appendallProjectNames
the name into ‘allProjectNames’ array.массив.
Line #24
Отсортируем Weнаши filterпроекты, outтак projectsкак thatмы weхотим. want.Пройдемся Weпо iterateallProjectNames
overмассива theи если проект встречает определенный критерий, мы добавляем имя проекта в filteredProjectNames
массив который мы будем использовать вместо allProjectNames
. array and if a project meets certain criteria, we append the project name into ‘filteredProjectNames’ array which we will use onwards instead of ‘allProjectNames’
Line #35
Для Forкаждого eachимени projectпроекта nameв infilteredProjectNames
‘filteredProjectNames’,мы weполучаем getего itsконфигурационный configuration file i.e. theфайл config.xml.
Line #45
Вызываем Weфукнкцию callGetFileNamesRecursively()
aв function GetFilenamesRecursively() in helpers.go
toдля readчтения allвсех theимен fileфайлов namesв inxmlFolder
.
Line #50
Мы делаем всё что хотим с config.xml
файлом, например переименовываем имя проекта, добавляем параметры сборки, изменяем права и т.д.
LineЯ #50написал –скрипт We do whatever we want with the config.xml file such as renaming the project name, adding build parameters, change permission and so on.
I write a script inна Python instead ofвместо Go toдля manipulateуправления the config.xml
filesфайлом, becauseтак Pythonкак hasPyehon moreимеет librariesбольше thanбиблиотек Goчем thatGo, canкоторые helpмогут usпомочь toнам manipulateдля stringsуправления andстроками files.и файлами.
Line #52
Наконец, Finallyпосле afterтого, weкак makeмы theсоздали changeизменения atв lineстроке #50,50, weмы submitотправляем theизменения change by executingвыполняя POST requestзапрос toв Jenkins.
//helpers.go
package helpers
import (
"os"
"path/filepath"
)
// StringInSlice ...
func StringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// GetFilenamesRecursively ...
func GetFilenamesRecursively(path string) []string {
var files []string
err := filepath.Walk("xml", func(path string, info os.FileInfo, err error) error {
files = append(files, path)
return nil
})
if err != nil {
panic(err)
}
return files
}
//jenkins.go
package jenkins
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"reflect"
)
// Details ...
type Details struct {
URL string
Username string
APIToken string
}
// JenkinsDetails ...
var JenkinsDetails = Details{}
// IsJobExist ...
func IsJobExist(projectName string) bool {
fullURL := fmt.Sprintf("%sjob/%s", JenkinsDetails.URL, projectName)
request, err := http.NewRequest("GET", fullURL, nil)
request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
log.Println("project doesn't exist:", projectName)
return false
}
if resp.StatusCode == 200 {
// log.Println("project exists:", projectName)
return true
}
log.Println("project doesn't exist:", projectName)
return false
}
// CopyJenkinsJob ...
func CopyJenkinsJob(srcJob string, dstJob string) error {
fullURL := fmt.Sprintf("%s%s?name=%s&mode=copy&from=%s", JenkinsDetails.URL, "createItem", dstJob, srcJob)
request, err := http.NewRequest("POST", fullURL, nil)
request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
return err
}
if resp.StatusCode == 200 {
return nil
}
return fmt.Errorf("error copying job: %s", srcJob)
}
// DownloadConfigXML ...
func DownloadConfigXML(projectName string, dstFilename string) error {
fullURL := fmt.Sprintf("%sjob/%s/config.xml", JenkinsDetails.URL, projectName)
if DownloadFile(fullURL, dstFilename) == nil {
return nil
}
return fmt.Errorf("error downloading file %s", fullURL)
}
// PostConfigXML ...
func PostConfigXML(projectName string, filename string) error {
// read content of file
data, err := os.Open(filename)
if err != nil {
log.Fatal(err)
return err
}
fullURL := fmt.Sprintf("%sjob/%s/config.xml", JenkinsDetails.URL, projectName)
request, err := http.NewRequest("POST", fullURL, data)
request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
client := &http.Client{}
// perform the request
resp, err := client.Do(request)
if err != nil {
return err
}
if resp.StatusCode == 200 {
// log.Printf("success postConfigXML: %s %s %s", projectName, filename, resp.Status)
return nil
}
return fmt.Errorf("error postConfigXML: %s %s %s", projectName, filename, resp.Status)
}
// DownloadFile ...
func DownloadFile(url string, filepath string) error {
// Create the file
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
// Get the data
request, err := http.NewRequest("GET", url, nil)
request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
return err
}
defer resp.Body.Close()
// Write the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
return err
}
return nil
}
// DownloadFileToBytes ...
func DownloadFileToBytes(url string) ([]byte, error) {
request, err := http.NewRequest("GET", url, nil)
request.SetBasicAuth(JenkinsDetails.Username, JenkinsDetails.APIToken)
client := &http.Client{}
resp, err := client.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err) // writes to standard error
}
return bodyBytes, nil
}
// GetAllProjectNames ...
func GetAllProjectNames() []string {
allProjectNames := make([]string, 0)
allProjectsURL := fmt.Sprintf("%sapi/json?pretty=true", JenkinsDetails.URL)
if respBytes, err := DownloadFileToBytes(allProjectsURL); err == nil {
// json --> map
var result map[string]interface{}
json.Unmarshal([]byte(respBytes), &result)
// if map has slice 'jobs', iterate over it
if jobs, keyIsPresent := result["jobs"]; keyIsPresent && reflect.TypeOf(jobs).Kind() == reflect.Slice {
jobs2 := result["jobs"].([]interface{})
for _, job := range jobs2 {
// log.Println(job)
_map, _ := job.(map[string]interface{}) // assert to map
allProjectNames = append(allProjectNames, _map["name"].(string))
}
}
}
return allProjectNames
}