Commit | Line | Data |
---|---|---|
c3c15dc7 KS |
1 | #!/usr/bin/python3 |
2 | ||
3 | import argparse | |
4 | import configparser | |
455bd8ef | 5 | import json |
c3c15dc7 KS |
6 | import sys |
7 | import xml.etree.ElementTree | |
8 | ||
9 | import jenkins | |
10 | ||
11 | def get_argument_parser(): | |
12 | parser = argparse.ArgumentParser( | |
13 | prog='update_jenkins_node.py', | |
14 | description='Create, update, or delete Jenkins nodes' | |
15 | ) | |
16 | parser.add_argument( | |
17 | '-u', '--url', default=None, | |
18 | help='Jenkins server URL including protocol' | |
19 | ) | |
20 | parser.add_argument( | |
21 | '--user', default=None, | |
22 | help='Jenkins username' | |
23 | ) | |
24 | parser.add_argument( | |
25 | '--password', default=None, | |
26 | help='Jenkins password' | |
27 | ) | |
28 | parser.add_argument( | |
29 | '-n', '--node', default=None, required=True, | |
30 | help='The name of the node to manage in Jenkins' | |
31 | ) | |
32 | parser.add_argument( | |
33 | '-c', '--node-config', default=[], action='append', | |
34 | help='An equals-separated set path=value[=attrib]. When attrib is not set, text is assumed' | |
35 | ) | |
36 | parser.add_argument( | |
37 | '-f', '--config-file', default=None, type=argparse.FileType('r'), | |
38 | help='An INI config file as used by jenkins_jobs' | |
39 | ) | |
40 | parser.add_argument( | |
41 | '-s', '--state', default='online', | |
42 | choices=['online', 'offline', 'absent'], | |
43 | help='The state of the Jenkins node' | |
44 | ) | |
45 | parser.add_argument( | |
46 | '-m', '--message', default='', | |
47 | help='A message to set for the offline reason of a node' | |
48 | ) | |
455bd8ef KS |
49 | parser.add_argument( |
50 | '-j', '--json', default='', | |
51 | help='Additional config in a json dictionary which will be appended after -c items', | |
52 | ) | |
c3c15dc7 KS |
53 | return parser |
54 | ||
55 | ||
56 | def manage_node(url, user, password, node, state, offline_message='', config={}): | |
57 | server = jenkins.Jenkins(url, username=user, password=password) | |
58 | exists = server.node_exists(node) | |
59 | node_info = {} | |
60 | changed = False | |
61 | if exists and state == 'absent': | |
62 | server.delete_node(node) | |
63 | changed = True | |
64 | if not exists and state != 'absent': | |
65 | server.create_node(node, numExecutors=1, remoteFS='/home/jenkins', | |
66 | launcher=jenkins.LAUNCHER_SSH) | |
67 | changed = True | |
68 | if state != 'absent': | |
69 | # Check configuration | |
70 | updated = False | |
71 | node_config = xml.etree.ElementTree.fromstring(server.get_node_config(node)) | |
72 | for key, value in config.items(): | |
73 | element = node_config.find(key) | |
74 | new_element = None | |
75 | current_key = key | |
76 | while element is None: | |
77 | head = key.rsplit('/', 1)[0] if '/' in current_key else None | |
78 | tail = key.rsplit('/', 1)[1] if '/' in current_key else current_key | |
79 | e = xml.etree.ElementTree.Element(tail) | |
80 | if new_element is not None: | |
81 | e.append(new_element) | |
82 | new_element = None | |
83 | if head is None: | |
84 | node_config.append(e) | |
85 | element = node_config.find(key) | |
86 | else: | |
87 | parent = node_config.find(head) | |
88 | if parent: | |
89 | parent.append(e) | |
90 | element = node_config.find(key) | |
91 | else: | |
92 | new_element = e | |
93 | current_key = head | |
94 | continue | |
95 | ||
96 | if value['attrib'] is None: | |
97 | if element.text != value['value']: | |
98 | updated = True | |
99 | element.text = value['value'] | |
100 | else: | |
101 | try: | |
102 | if element.attrib[value['attrib']] != value['value']: | |
103 | updated = True | |
104 | element.attrib[value['attrib']] = value['value'] | |
105 | except KeyError: | |
106 | element.attrib[value['attrib']] = value['value'] | |
107 | updated = True | |
108 | if updated: | |
455bd8ef KS |
109 | xml_string = xml.etree.ElementTree.tostring( |
110 | node_config, | |
111 | xml_declaration=True, | |
112 | encoding='unicode' | |
c3c15dc7 | 113 | ) |
455bd8ef | 114 | server.reconfig_node(node, xml_string) |
c3c15dc7 KS |
115 | changed = True |
116 | # Online/offline | |
117 | node_info = server.get_node_info(node) | |
118 | if node_info['offline'] and state == 'online': | |
119 | server.enable_node(node) | |
120 | changed = True | |
121 | if not node_info['offline'] and state == 'offline': | |
122 | server.disable_node(node, offline_message) | |
123 | changed = True | |
124 | return changed | |
125 | ||
126 | ||
127 | if __name__ == '__main__': | |
128 | parser = get_argument_parser() | |
129 | args = parser.parse_args() | |
130 | if args.config_file is not None: | |
131 | config = configparser.ConfigParser() | |
132 | config.read_file(args.config_file) | |
133 | if 'jenkins' not in config.sections(): | |
134 | print("[jenkins] section not found") | |
135 | sys.exit(1) | |
136 | if args.url is None: | |
137 | args.url = config.get('jenkins', 'url') | |
138 | if args.user is None: | |
139 | args.user = config['jenkins']['user'] | |
140 | if args.password is None: | |
141 | args.password = config['jenkins']['password'] | |
142 | assert(args.user is not None) | |
143 | assert(args.url is not None) | |
144 | assert(args.password is not None) | |
145 | node_config = {} | |
146 | for entry in args.node_config: | |
147 | key, value = entry.split('=', 1) | |
148 | node_config[key] = { | |
149 | 'attrib': value.split('=', 1)[1] if '=' in value else None, | |
150 | 'value': value.split('=', 1)[0] if '=' in value else value, | |
151 | } | |
455bd8ef KS |
152 | if args.json: |
153 | for key, value in json.loads(args.json).items(): | |
154 | node_config[key] = { | |
155 | 'attrib': value.split('=', 1)[1] if '=' in value else None, | |
156 | 'value': value.split('=', 1)[0] if '=' in value else value, | |
157 | } | |
c3c15dc7 KS |
158 | manage_node( |
159 | args.url, args.user, args.password, args.node, args.state, | |
160 | args.message, node_config | |
161 | ) |