Jenkins – backup and restore jobs

Backup / Restore jobs

Jenkins provides a quick way for us to backup job definitions through its API. Using any compatible HTTP client (such as your browser), you can retrieve the job configuration in the .xml format. Note that the URL to the configuration file depends on the Jenkins file location.

Jenkins job not in sub-folders:
https://[jenkins_server]/job/[jenkins_job_name]/config.xml

Jenkins job in sub-folder "dev"
https://[jenkins_server]/job/dev/config.xml
Restore Jenkins Jobs

Using the same Jenkins API, we can restore a job using any HTTP client that is able to send POST requests. To restore a job, copy the content of the job definition file into the HTTP request body and make your request to the job you want to restore.

https://[jenkins_server]/job/[jenkins_job_name]/config.xml  -POST
<jenkins_job_xml_content>

Demo: Backup Jenkins job tool

JenkinsXmlRetriever is a tool build to retrieve all Jenkins job definition from a Jenkins server (including nested Jenkins folders). The tool is written in C# .Net Core and can be run in Windows and Linux environments.

using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;

namespace JenkinsXmlRetriever
{
    class Program
    {
        static void Main(string[] args)
        {
            var jenkinsDownloadManager = new JenkinsDownloadManager("jenkins_user_id:jenkins_api_token");
            jenkinsDownloadManager.BackupJenkinsJobs(
                jenkinsDownloadManager.GetDownloadListFrom()
                );
        }
    }
    public class BackupConfig
    {
        private readonly IConfigurationRoot ConfigRoot = null;
        public BackupConfig() : this("appsettings.json") { }
        public BackupConfig(string fileName)
        {
            var builder = new ConfigurationBuilder()
                   .SetBasePath(Directory.GetCurrentDirectory())
                   .AddJsonFile(fileName);

            ConfigRoot = builder.Build();

        }
        public string OuptutBasePath => GetValue("OuptutBasePath", Environment.CurrentDirectory);
        public string BackupListFilePath => GetValue("BackupListFilePath", string.Empty);
        private string GetValue(string key, string defaultValue)
        {
            return string.IsNullOrEmpty(ConfigRoot[key]) ? defaultValue : ConfigRoot[key];
        }
    }
    public class JenkinsDownloadManager
    {
        private readonly BackupConfig BackupConfig;
        private readonly string AuthInfo;
        string EncodedAuthInfo => Convert.ToBase64String(Encoding.ASCII.GetBytes(AuthInfo));
        public JenkinsDownloadManager(string jenkinsAuthTkn)
            : this(new BackupConfig())
        {
            this.AuthInfo = jenkinsAuthTkn;
        }
        public JenkinsDownloadManager(BackupConfig config)
        {
            BackupConfig = config;
        }
        public List<JenkinsPageData> GetDownloadListFrom(string filename = "")
        {
            // File name override if provided
            filename = string.IsNullOrWhiteSpace(filename) ? BackupConfig.BackupListFilePath : filename;
            if (!File.Exists(filename))
            {
                throw new FileNotFoundException(filename);
            }

            var jenkinsJobData = new List<JenkinsPageData>();
            var jobUrls = File.ReadAllLines(filename);
            foreach (var item in jobUrls)
            {
                jenkinsJobData.Add(new JenkinsPageData { DownloadUrl = item });
            }
            return jenkinsJobData;
        }
        public void BackupJenkinsJobs(List<JenkinsPageData> dataRows)
        {
            dataRows.ToList().ForEach(dataRow =>
            {
                var folderDefinition = this.BackupJenkinsJob(dataRow);
                var jenkinsDirectory = JsonConvert.DeserializeObject<JenkinsJson>(folderDefinition);
                var path = Path.Combine(BackupConfig.OuptutBasePath, jenkinsDirectory.JenkinsFolderName);
                GetNestedJobs(jenkinsDirectory.JenkinsJobs, path);
            });
        }
        protected string BackupJenkinsJob(JenkinsPageData jenkinsJob)
        {
            string rtnContent = string.Empty;
            try
            {
                var httpClient = new HttpClient();
                httpClient.DefaultRequestHeaders.Add("Accept", "text/xml");
                httpClient.DefaultRequestHeaders.Add("Authorization", string.Format("Basic {0}", EncodedAuthInfo));
                var httpContent = httpClient.GetStringAsync(jenkinsJob.DownloadUrl);
                rtnContent = httpContent.Result;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error retrieving:url='{0}', err='{1}'", jenkinsJob.DownloadUrl, ex.ToString());
            }

            return rtnContent;
        }
        protected void GetNestedJobs(JenkinsJob[] jenkinsJobs, string outputDirectory)
        {
            jenkinsJobs.ToList().ForEach(jenkinsJob =>
            {
                var newPath = Path.Combine(outputDirectory, jenkinsJob.Name);
                if (jenkinsJob.IsJenkinsFolder)
                {
                    var jpd = new JenkinsPageData { DownloadUrl = string.Format("{0}/api/json", jenkinsJob.Url) };
                    var x = this.BackupJenkinsJob(jpd);
                    var y = JsonConvert.DeserializeObject<JenkinsJson>(x);
                    GetNestedJobs(y.JenkinsJobs, newPath);
                }
                if (jenkinsJob.IsJenkinsJob)
                {
                    var jpd = new JenkinsPageData { DownloadUrl = string.Format("{0}/config.xml", jenkinsJob.Url) };
                    var jobDefinition = this.BackupJenkinsJob(jpd);

                    Directory.CreateDirectory(newPath);

                    File.AppendAllText(Path.Combine(outputDirectory, jenkinsJob.Name, jpd.TargetRelativePath), jobDefinition);
                }
            });
        }

    }
    public class JenkinsPageData
    {
        public bool IsVersioned { get; set; } = true;
        public string DownloadUrl { get; set; }
        public string TargetRelativePath
        {
            get
            {
                if (IsVersioned)
                {
                    var replacementStr = string.Concat("_", DateTime.UtcNow.ToString("yyyyMMddhhmmss"), ".xml");
                    return Path.Combine(JenkinsJobname, JenkinsJobDefinition.Replace(".xml", replacementStr));
                }

                return Path.Combine(JenkinsJobname, JenkinsJobDefinition);
            }
        }
        string JenkinsJobname
        {
            get
            {
                if (!string.IsNullOrEmpty(DownloadUrl))
                {
                    var request = new Uri(DownloadUrl);
                    if (request != null && request.Segments.Length > 2)
                    {
                        return request.Segments[request.Segments.Length - 2].TrimEnd('/');
                    }
                }

                throw new InvalidDataException(string.Format("Invalid download Url='{0}'", DownloadUrl));
            }
        }
        string JenkinsJobDefinition
        {
            get
            {
                if (!string.IsNullOrEmpty(DownloadUrl))
                {
                    var request = new Uri(DownloadUrl);
                    if (request != null && request.Segments.Length > 2)
                    {
                        return request.Segments.Last();
                    }
                }

                throw new InvalidDataException(string.Format("Invalid download Url='{0}'", DownloadUrl));
            }
        }
    }
    public class JenkinsJson
    {
        [JsonProperty("name")]
        public string JenkinsFolderName { get; set; }
        [JsonProperty("jobs")]
        public JenkinsJob[] JenkinsJobs { get; set; }
    }
    public class JenkinsJob
    {
        [JsonProperty("_class")]
        public string Class { get; set; }
        [JsonProperty("name")]
        public string Name { get; set; }
        [JsonProperty("url")]
        public string Url { get; set; }
        public bool IsJenkinsJob => string.Equals(this.Class, "hudson.model.FreeStyleProject", StringComparison.InvariantCultureIgnoreCase);
        public bool IsJenkinsFolder => string.Equals(this.Class, "com.cloudbees.hudson.plugins.folder.Folder", StringComparison.InvariantCultureIgnoreCase);
    }
}

appsettings.json contains two cofigurations:

{
  "OuptutBasePath": "C:\\JenkinsXmlRetriever\\",
  "BackupListFilePath": "C:\\JenkinsXmlRetriever\\JenkinsJobBackupList.txt"
}

JenkinsJobBackupList.txt, contains the top level Jenkins folders you would like to backup. api/json is added to the end of the URL. The URL should show JSON data when browsed:

https://[jenkins_server]/jenkins_directory/api/json 

Leave a Reply

Your email address will not be published. Required fields are marked *