blob: f47c8379676948374b263c0ef2610ae4b6c63023 [file] [log] [blame]
Ilya Kharinaa03d4e2017-04-18 17:25:53 +04001import logging
2
3import salt.exceptions
4
5import requests
6from requests.compat import urljoin
7
8LOG = logging.getLogger(__name__)
9
10
Ilya Kharin9be40682017-05-02 16:43:51 +040011# Project
12
Ilya Kharinaa03d4e2017-04-18 17:25:53 +040013def get_project(name):
14 session, make_url = get_session()
15 resp = session.get(make_url("/api/18/project/{}".format(name)))
16 status_code = resp.status_code
17 if status_code == 200:
18 return resp.json()
19 elif status_code == 404:
20 return None
21 raise salt.exceptions.SaltInvocationError(
22 "Could not retrieve information about project {} from Rundeck {}: {}"
23 .format(name, make_url.base_url, status_code))
24
25
26def create_project(name, params):
27 session, make_url = get_session()
28 config = create_project_config(name, params)
29 LOG.debug("create_project: %s", name)
30 LOG.warning("create_project.config: %s/%s", name, config)
31 resp = session.post(
32 make_url("/api/18/projects"),
33 json={
34 'name': name,
35 'config': config,
36 },
37 allow_redirects=False,
38 )
39 if resp.status_code == 201:
40 return resp.json()
41 LOG.debug("create_project: %s", name)
42
43
Ilya Kharinaa03d4e2017-04-18 17:25:53 +040044def update_project_config(name, project, config):
45 session, make_url = get_session()
46 resp = session.put(
47 make_url("/api/18/project/{}/config".format(name)),
48 json=config,
49 allow_redirects=False,
50 )
51 if resp.status_code == 201:
52 return resp.json()
53 LOG.debug("update_project: %s", name)
54
55
56def delete_project(name):
57 session, make_url = get_session()
58 resp = session.delete(make_url("/api/18/project/{}".format(name)))
59 status_code = resp.status_code
60 if status_code != 204:
61 raise salt.exceptions.SaltInvocationError(
62 "Could not remove project {} from Rundeck {}: {}"
63 .format(name, make_url.base_url, status_code))
64
65
Ilya Kharin9be40682017-05-02 16:43:51 +040066# SCM
67
68def get_plugin(project_name, integration):
69 session, make_url = get_session()
70 resp = session.get(make_url("/api/18/project/{}/scm/{}/config"
71 .format(project_name, integration)))
72 if resp.status_code == 200:
73 return True, resp.json()
74 elif resp.status_code == 404:
75 return True, None
76 return False, (
77 "Could not get config for the {} plugin of the {} project"
78 .format(integration, project_name))
79
80
81def get_plugin_status(project_name, integration):
82 def get_plugin(plugins, plugin_type):
83 for plugin in plugins:
84 if plugin['type'] == plugin_type:
85 return plugin
86 raise salt.exceptions.SaltInvocationError(
87 "Could not find status for the {}/{} plugin of the {} project"
88 .format(integration, plugin_type, project_name))
89
90 session, make_url = get_session()
91 resp = session.get(make_url("/api/18/project/{}/scm/{}/plugins"
92 .format(project_name, integration)))
93 if resp.status_code == 200:
94 plugin_type = "git-{}".format(integration)
95 status = get_plugin(resp.json()['plugins'], plugin_type)
96 return True, status
97 return False, (
98 "Could not get status for the {} plugin of the {} project"
99 .format(integration, project_name))
100
101
102def get_plugin_state(project_name, integration):
103 session, make_url = get_session()
104 resp = session.get(make_url("/api/18/project/{}/scm/{}/status"
105 .format(project_name, integration)))
106 if resp.status_code == 200:
107 return True, resp.json()
108 return False, (
109 "Could not get state for the {} plugin of the {} project"
110 .format(integration, project_name))
111
112
113def disable_plugin(project_name, integration):
114 session, make_url = get_session()
115 resp = session.post(make_url(
116 "/api/15/project/{}/scm/{}/plugin/git-{}/disable"
117 .format(project_name, integration, integration)))
118 if resp.status_code == 200:
119 msg = resp.json()
120 return True, msg['message']
121 return False, (
122 "Could not disable the {} plugin for the {} project: {}"
123 .format(integration, project_name, resp.status_code))
124
125
126def enable_plugin(project_name, integration):
127 session, make_url = get_session()
128 resp = session.post(make_url(
129 "/api/15/project/{}/scm/{}/plugin/git-{}/enable"
130 .format(project_name, integration, integration)))
131 if resp.status_code == 200:
132 msg = resp.json()
133 return True, msg['message']
134 return False, (
135 "Could not enable the {} plugin for the {} project: {}"
136 .format(integration, project_name, resp.status_code))
137
138
139# SCM Import
140
141def setup_scm_import(project_name, params):
142 session, make_url = get_session()
143 config = create_scm_import_config(project_name, params)
144 resp = session.post(
145 make_url("/api/15/project/{}/scm/import/plugin/git-import/setup"
146 .format(project_name)),
147 json={
148 'config': config,
149 },
150 allow_redirects=False,
151 )
152 if resp.status_code == 200:
153 return True, resp.json()
154 return False, (
155 "Could not configure SCM Import for the {} project: {}/{}"
156 .format(project_name, resp.status_code, resp.text))
157
158
159def update_scm_import_config(project_name, plugin, config):
160 session, make_url = get_session()
161 resp = session.post(
162 make_url("/api/15/project/{}/scm/import/plugin/git-import/setup"
163 .format(project_name)),
164 json={
165 'config': config,
166 },
167 allow_redirects=False,
168 )
169 if resp.status_code == 200:
170 return True, resp.json()
171 return False, (
172 "Could not update SCM Import for the {} project: {}/{}"
173 .format(project_name, resp.status_code, resp.text))
174
175
176def perform_scm_import_tracking(project_name, plugin, params):
177 format = plugin['config']['format']
178 file_pattern = params.get('file_pattern')
179 if not file_pattern:
180 file_pattern = DEFAULT_FILE_PATTERNS[format]
181
182 session, make_url = get_session()
183 resp = session.post(
184 make_url("/api/15/project/{}/scm/import/action/initialize-tracking"
185 .format(project_name)),
186 json={
187 'input': {
188 'filePattern': file_pattern,
189 'useFilePattern': 'true',
190 },
191 'jobs': [],
192 'items': [],
193 'deleted': [],
194 },
195 allow_redirects=False,
196 )
197 if resp.status_code == 200:
198 return True, resp.json()
199 return False, (
200 "Could not update SCM Import for the {} project: {}/{}"
201 .format(project_name, resp.status_code, resp.text))
202
203DEFAULT_FILE_PATTERNS = {
204 'yaml': r'.*\.yaml',
205 'xml': r'.*\.xml',
206}
207
208
209def perform_scm_import_pull(project_name, plugin, params):
210 session, make_url = get_session()
211 resp = session.post(
212 make_url("/api/15/project/{}/scm/import/action/remote-pull"
213 .format(project_name)),
214 json={
215 'input': {},
216 'jobs': [],
217 'items': [],
218 'deleted': [],
219 },
220 allow_redirects=False,
221 )
222 if resp.status_code == 200:
223 return True, resp.json()
224 return False, (
225 "Could not pull remote changes for the {} project: {}/{}"
226 .format(project_name, resp.status_code, resp.text))
227
228
229def perform_scm_import(project_name, plugin, params):
230 session, make_url = get_session()
231 ok, inputs = get_plugin_action_inputs(
232 project_name, 'import', 'import-all')
233 if not ok:
234 return False, inputs
235 items = list(item['itemId'] for item in inputs['importItems'])
236 resp = session.post(
237 make_url("/api/15/project/{}/scm/import/action/import-all"
238 .format(project_name)),
239 json={
240 'input': {},
241 'jobs': [],
242 'items': items,
243 'deleted': [],
244 },
245 allow_redirects=False,
246 )
247 if resp.status_code == 200:
248 return True, resp.json()
249 return False, (
250 "Could not import jobs for the {} project: {}/{}"
251 .format(project_name, resp.status_code, resp.text))
252
253
254# Utils
255
256def create_project_config(project_name, params, config=None):
257 config = dict(config) if config else {}
258 if params['description']:
259 config['project.description'] = params['description']
260 else:
261 config.pop('project.description', None)
262 config.update({
263 'resources.source.1.config.file':
264 "/var/rundeck/projects/{}/etc/resources.yaml".format(project_name),
265 'resources.source.1.config.format': 'resourceyaml',
266 'resources.source.1.config.generateFileAutomatically': 'true',
267 'resources.source.1.config.includeServerNode': 'false',
268 'resources.source.1.config.requireFileExists': 'false',
269 'project.ssh-keypath': '/var/rundeck/.ssh/id_rsa',
270 'resources.source.1.type': 'file',
271 })
272 return config
273
274
275def create_scm_import_config(project_name, params, config=None):
276 config = dict(config) if config else {}
277
278 format = params.get('format', 'yaml')
279 if format not in DEFAULT_FILE_PATTERNS:
280 supported_formats = DEFAULT_FILE_PATTERNS.keys()
281 raise salt.exceptions.SaltInvocationError(
282 "Unsupported format {} for the {} SCM import module, should be {}"
283 .format(format, project_name, ','.join(supported_formats)))
284
285 config.update({
286 'dir': "/var/rundeck/projects/{}/scm".format(project_name),
287 'url': params['address'],
288 'branch': params.get('branch', 'master'),
289 'fetchAutomatically': 'true',
290 'format': format,
291 'pathTemplate': params.get(
292 'path_template', '${job.group}${job.name}.${config.format}'),
293 'importUuidBehavior': params.get('import_uuid_behavior', 'remove'),
294 'strictHostKeyChecking': 'yes',
295 })
296 return config
297
298
299def get_plugin_action_inputs(project_name, integration, action):
300 session, make_url = get_session()
301 resp = session.get(
302 make_url("/api/18/project/cicd/scm/import/action/import-all/input"))
303 if resp.status_code == 200:
304 return True, resp.json()
305 return False, (
306 "Could not get inputs for the {} action for the {} project: {}/{}"
307 .format(action, project_name, resp.status_code, resp.text))
308
309
310
Ilya Kharinaa03d4e2017-04-18 17:25:53 +0400311def get_session():
312 def make_url(url):
313 return urljoin(make_url.base_url, url)
314
315 rundeck_url = __salt__['config.get']('rundeck.url')
316 make_url.base_url = rundeck_url
317
318 api_token = __salt__['config.get']('rundeck.api_token')
319 username = __salt__['config.get']('rundeck.username')
320 password = __salt__['config.get']('rundeck.password')
321
322 session = requests.Session()
323
324 if api_token:
325 session.headers.update({
326 'Content-Type': 'application/json',
327 'X-Rundeck-Auth-Token': api_token,
328 })
329 else:
330 resp = session.post(make_url('/j_security_check'),
331 data={
332 'j_username': username,
333 'j_password': password,
334 },
335 )
336 if (resp.status_code != 200 or
337 '/user/error' in resp.url or
338 '/user/login' in resp.url):
339 raise salt.exceptions.SaltInvocationError(
340 "Username/passowrd authorization failed in Rundeck {} for "
341 "user {}".format(rundeck_url, username))
342 session.params.update({
343 'format': 'json',
344 })
345 return session, make_url