Jenkins – Managing multiple jobs parameters

Here is the problem: You have a large number of jobs on multiple Jenkins instances with the same parameter – let it be branch name where to pull source code from. Now you want to rename your branch from “dev” to “dev_core” and update configuration in your development workflow according to new changes.

What to do? At the first glance, plugins is the way to go, but wait…

While there are many Jenkins plugins exists out there to change parameters for multiple jobs at the same time, but not always you have permissions to install plugins or this is prohibited by corporate policy due to security concerns.

So, there is another way to automate this task using Jenkins API and Python, but make sure you have all needed permissions to modify Jenkins job configuration.

Here is the Python script to manage parameters for multiple Jenkins jobs:

"""
Set parameters for multiple Jenkins jobs
"""

import jenkins
import argparse
import configparser
import os
import xml.etree.ElementTree as xml_parser

from urllib.error import HTTPError
from http.client import HTTPException

# Get user credentials for jenkins.
# Create new module: jenkins_credentials.py with the following structure:
# credentials = {"user": "user name", "password": "your password"}

try:
    from jenkins_credentials import credentials
except ImportError:
    credentials = {"user": "", "password": ""}

CONFIG_SECTION = "configuration"
CONFIG_JENKINS = "url"
CONFIG_JOBS = "jobs"
CONFIG_PARAMETERS = "parameters"

OLD_PARAM_VALUE = "old_value"
NEW_PARAM_VALUE = "new_value"

def get_config_parser(config_file):
    config_parser = configparser.ConfigParser()
    config_parser.optionxform = str
    config_parser.read(config_file)
    return config_parser

def get_job_config(jenkins_instance, job):
    try:
        job_config = jenkins_instance.get_job_config(job)
    except HTTPException as e:
        job_config = None
        print("Error occurred while getting job configuration for {} due to {}".format(
            job, e))
    return job_config

def apply_configuration(jenkins_instance, jobs, parameters):
    for job in jobs:
        print("### Working with ", job)
        job_config = get_job_config(jenkins_instance, job)
        if job_config is not None:
            xml_root = xml_parser.fromstring(job_config)
            for name, value in parameters:
                match = './/hudson.model.StringParameterDefinition[name="{}"]'.format(name)
                param_element = xml_root.find(match)
                value_element = None
                if param_element is not None:
                    value_element = tuple(param_element.iterfind("defaultValue"))
                if value_element:
                    value_element, = value_element
                    print("Old value for parameter {} is {}, setting to {}".format(name,
                        value_element.text, value))
                    value_element.text = value
                else:
                    print("WARNING: parameter with name {} not found in job {}".format(name, job))
            # Upload configuration changes to Jenkins
            print("Applying configuration for ", job)
            jenkins_instance.reconfig_job(job,
                xml_parser.tostring(xml_root, encoding="unicode"))
            print()

def parse_configuration(config, section):
    result = {"jenkins_instance": None,
              CONFIG_JOBS: list(),
              CONFIG_PARAMETERS: list()}
    for param, value in config.items(section):
        if param == CONFIG_JENKINS:
            try:
                result["jenkins_instance"] = \
                    jenkins.Jenkins(value, credentials["user"], credentials["password"])
            except HTTPError as e:
                result["jenkins_instance"] = None
                print("Error occurred while making connection to {} due to {}".format(
                    value, e))
        elif param == CONFIG_JOBS:
            result[CONFIG_JOBS] = value.split(",")
        else:
            # configuration parameter to change
            result[CONFIG_PARAMETERS].append((param, value))
    return result

if __name__ == "__main__":
    usage = "\nSet parameters for multiple Jenkins jobs:\n" \
            "Usage: python jenkins_job_config --config config_file_name \n"
    parser = argparse.ArgumentParser(prog="Jenkins jobs runner", usage=usage)
    parser.add_argument("--config", type=str,
    	default=os.path.join(os.getcwd(), "jenkins_jobs.config"))
    args = parser.parse_args()
    config_reader = get_config_parser(args.config)
    for server_config in config_reader.sections():
        print("@@@ Jenkins server", server_config)
        config = parse_configuration(config_reader, server_config)
        if config["jenkins_instance"] is not None:
            apply_configuration(**config)
    print("Done.")

Configuration file template:

[server1]
url=http://server1.com/jenkins
jobs=build-job1,build-job2,build-job3
PARAM1=value1
PARAM2=value2
PARAM3=value3

[server2]
url=http://server2.com/jenkins
jobs=build-job1,build-job2,build-job3
PARAM1=value1
PARAM2=value2
PARAM3=value3