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