Если вы видите что-то необычное, просто сообщите мне. Skip to main content

Пример использования 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:файла:

  1. packages/helpers/helpers.go

  2. packages/jenkins/jenkins.go

  3. 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

sets

Укажем 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,go, theфункция 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.

‘xmlFolder’

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
}