blob: 4b83d332846e4ccf25ab89e9a7527cb83eb0fe3a [file] [log] [blame]
Jakub Josef79ecec32017-02-17 14:36:28 +01001package com.mirantis.mk
2
3/**
4 * Salt functions
5 *
6*/
7
8/**
9 * Salt connection and context parameters
10 *
11 * @param url Salt API server URL
12 * @param credentialsID ID of credentials store entry
13 */
14def connection(url, credentialsId = "salt") {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +010015 def common = new com.mirantis.mk.Common()
Jakub Josef79ecec32017-02-17 14:36:28 +010016 params = [
17 "url": url,
18 "credentialsId": credentialsId,
19 "authToken": null,
20 "creds": common.getCredentials(credentialsId)
21 ]
22 params["authToken"] = saltLogin(params)
23
24 return params
25}
26
27/**
28 * Login to Salt API, return auth token
29 *
30 * @param master Salt connection object
31 */
32def saltLogin(master) {
Tomáš Kukrál7bec0532017-02-20 15:39:31 +010033 def http = new com.mirantis.mk.Http()
Jakub Josef79ecec32017-02-17 14:36:28 +010034 data = [
35 'username': master.creds.username,
36 'password': master.creds.password.toString(),
37 'eauth': 'pam'
38 ]
Tomáš Kukrál7bec0532017-02-20 15:39:31 +010039 authToken = http.restGet(master, '/login', data)['return'][0]['token']
Jakub Josef79ecec32017-02-17 14:36:28 +010040 return authToken
41}
42
43/**
44 * Run action using Salt API
45 *
46 * @param master Salt connection object
47 * @param client Client type
48 * @param target Target specification, eg. for compound matches by Pillar
49 * data: ['expression': 'I@openssh:server', 'type': 'compound'])
50 * @param function Function to execute (eg. "state.sls")
Filip Pytlounf0435c02017-03-02 17:48:54 +010051 * @param batch
Jakub Josef79ecec32017-02-17 14:36:28 +010052 * @param args Additional arguments to function
53 * @param kwargs Additional key-value arguments to function
54 */
55@NonCPS
56def runSaltCommand(master, client, target, function, batch = null, args = null, kwargs = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +040057 def http = new com.mirantis.mk.Http()
Jakub Josef79ecec32017-02-17 14:36:28 +010058
59 data = [
60 'tgt': target.expression,
61 'fun': function,
62 'client': client,
63 'expr_form': target.type,
64 ]
65
Filip Pytlounf0435c02017-03-02 17:48:54 +010066 if (batch == true) {
67 data['batch'] = "local_batch"
Jakub Josef79ecec32017-02-17 14:36:28 +010068 }
69
70 if (args) {
71 data['arg'] = args
72 }
73
74 if (kwargs) {
75 data['kwarg'] = kwargs
76 }
77
78 headers = [
79 'X-Auth-Token': "${master.authToken}"
80 ]
81
82 return http.sendHttpPostRequest("${master.url}/", data, headers)
83}
84
Jakub Josef5ade54c2017-03-10 16:14:01 +010085/**
86 * Return pillar for given master and target
87 * @param master Salt connection object
88 * @param target Get pillar target
89 * @param pillar pillar name (optional)
90 * @return output of salt command
91 */
Ales Komarekcec24d42017-03-08 10:25:45 +010092def getPillar(master, target, pillar = null) {
Tomáš Kukráld2589702017-03-10 16:30:46 +010093 if (pillar != null) {
Jakub Josef5ade54c2017-03-10 16:14:01 +010094 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'pillar.get', null, [pillar.replace('.', ':')])
Tomáš Kukráld2589702017-03-10 16:30:46 +010095 } else {
Jakub Josef5ade54c2017-03-10 16:14:01 +010096 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'pillar.data')
Ales Komareka3c7e502017-03-13 11:20:44 +010097 }
Jakub Josef79ecec32017-02-17 14:36:28 +010098}
99
Jakub Josef5ade54c2017-03-10 16:14:01 +0100100/**
101 * Return grain for given master and target
102 * @param master Salt connection object
103 * @param target Get grain target
104 * @param grain grain name (optional)
105 * @return output of salt command
106 */
Ales Komarekcec24d42017-03-08 10:25:45 +0100107def getGrain(master, target, grain = null) {
108 if(grain != null) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100109 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'grain.item', null, [grain])
110 } else {
111 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'grain.items')
Ales Komarekcec24d42017-03-08 10:25:45 +0100112 }
Ales Komarekcec24d42017-03-08 10:25:45 +0100113}
114
Jakub Josef5ade54c2017-03-10 16:14:01 +0100115/**
116 * Enforces state on given master and target
117 * @param master Salt connection object
118 * @param target State enforcing target
119 * @param state Salt state
120 * @param output print output (optional, default true)
121 * @param failOnError throw exception on salt state result:false (optional, default true)
122 * @return output of salt command
123 */
124def enforceState(master, target, state, output = true, failOnError = true) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100125 def common = new com.mirantis.mk.Common()
Jakub Josef79ecec32017-02-17 14:36:28 +0100126 def run_states
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100127
Jakub Josef79ecec32017-02-17 14:36:28 +0100128 if (state instanceof String) {
129 run_states = state
130 } else {
131 run_states = state.join(',')
132 }
133
Tomáš Kukráldfd4b492017-03-02 12:08:50 +0100134 common.infoMsg("Enforcing state ${run_states} on ${target}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100135
Filip Pytloun5a7f7fd2017-02-27 18:50:25 +0100136 def out = runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'state.sls', null, [run_states])
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100137
Jakub Josefc952e5a2017-03-23 15:02:12 +0100138 if (output == true) {
139 printSaltStateResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100140 }
Jakub Josefc952e5a2017-03-23 15:02:12 +0100141 checkResult(out, failOnError)
Jakub Josef79ecec32017-02-17 14:36:28 +0100142 return out
143}
144
Jakub Josef5ade54c2017-03-10 16:14:01 +0100145/**
146 * Run command on salt minion (salt cmd.run wrapper)
147 * @param master Salt connection object
148 * @param target Get pillar target
149 * @param cmd command
150 * @return output of salt command
151 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100152def cmdRun(master, target, cmd) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100153 def common = new com.mirantis.mk.Common()
154
Tomáš Kukráldfd4b492017-03-02 12:08:50 +0100155 common.infoMsg("Running command ${cmd} on ${target}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100156
Jakub Josef5ade54c2017-03-10 16:14:01 +0100157 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'cmd.run', null, [cmd])
Jakub Josef79ecec32017-02-17 14:36:28 +0100158}
159
Jakub Josef5ade54c2017-03-10 16:14:01 +0100160/**
161 * Perform complete salt sync between master and target
162 * @param master Salt connection object
163 * @param target Get pillar target
164 * @return output of salt command
165 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100166def syncAll(master, target) {
Filip Pytloun5a7f7fd2017-02-27 18:50:25 +0100167 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'saltutil.sync_all')
Jakub Josef79ecec32017-02-17 14:36:28 +0100168}
169
Jakub Josef5ade54c2017-03-10 16:14:01 +0100170/**
171 * Enforce highstate on given targets
172 * @param master Salt connection object
173 * @param target Highstate enforcing target
174 * @param output print output (optional, default true)
175 * @param failOnError throw exception on salt state result:false (optional, default true)
176 * @return output of salt command
177 */
178def enforceHighstate(master, target, output = false, failOnError = true) {
Filip Pytloun5a7f7fd2017-02-27 18:50:25 +0100179 def out = runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'state.highstate')
Jakub Josefc952e5a2017-03-23 15:02:12 +0100180 if (output == true) {
181 printSaltStateResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100182 }
Jakub Josefc952e5a2017-03-23 15:02:12 +0100183 checkResult(out, failOnError)
Jakub Josef79ecec32017-02-17 14:36:28 +0100184 return out
185}
186
Jakub Josef5ade54c2017-03-10 16:14:01 +0100187/**
Ales Komarek5276ebe2017-03-16 08:46:34 +0100188 * Get running minions IDs according to the target
189 * @param master Salt connection object
190 * @param target Get minions target
191 * @return list of active minions fitin
192 */
193def getMinions(master, target) {
194 def minionsRaw = runSaltCommand(master, 'local', target, 'test.ping')
195 return new ArrayList<String>(minionsRaw['return'][0].keySet())
196}
197
198
199/**
Jakub Josef5ade54c2017-03-10 16:14:01 +0100200 * Generates node key using key.gen_accept call
201 * @param master Salt connection object
202 * @param target Key generating target
203 * @param host Key generating host
204 * @param keysize generated key size (optional, default 4096)
205 * @return output of salt command
206 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100207def generateNodeKey(master, target, host, keysize = 4096) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100208 return runSaltCommand(master, 'wheel', target, 'key.gen_accept', [host], ['keysize': keysize])
Jakub Josef79ecec32017-02-17 14:36:28 +0100209}
210
Jakub Josef5ade54c2017-03-10 16:14:01 +0100211/**
212 * Generates node reclass metadata
213 * @param master Salt connection object
214 * @param target Metadata generating target
215 * @param host Metadata generating host
216 * @param classes Reclass classes
217 * @param parameters Reclass parameters
218 * @return output of salt command
219 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100220def generateNodeMetadata(master, target, host, classes, parameters) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100221 return runSaltCommand(master, 'local', target, 'reclass.node_create', [host, '_generated'], ['classes': classes, 'parameters': parameters])
Jakub Josef79ecec32017-02-17 14:36:28 +0100222}
223
Jakub Josef5ade54c2017-03-10 16:14:01 +0100224/**
225 * Run salt orchestrate on given targets
226 * @param master Salt connection object
227 * @param target Orchestration target
228 * @param orchestrate Salt orchestrate params
229 * @return output of salt command
230 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100231def orchestrateSystem(master, target, orchestrate) {
232 return runSaltCommand(master, 'runner', target, 'state.orchestrate', [orchestrate])
233}
234
Jakub Josef5ade54c2017-03-10 16:14:01 +0100235/**
236 * Run salt process step
237 * @param master Salt connection object
238 * @param tgt Salt process step target
239 * @param fun Salt process step function
240 * @param arg process step arguments (optional, default [])
241 * @param batch using batch (optional, default false)
242 * @param output print output (optional, default false)
243 * @return output of salt command
244 */
Tomáš Kukrálf5dda642017-03-02 14:22:59 +0100245def runSaltProcessStep(master, tgt, fun, arg = [], batch = null, output = false) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100246 def common = new com.mirantis.mk.Common()
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100247 def out
248
Tomas Kukrale90bb342017-03-02 21:30:35 +0000249 common.infoMsg("Running step ${fun} on ${tgt}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100250
Filip Pytlounf0435c02017-03-02 17:48:54 +0100251 if (batch == true) {
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100252 out = runSaltCommand(master, 'local_batch', ['expression': tgt, 'type': 'compound'], fun, String.valueOf(batch), arg)
253 } else {
254 out = runSaltCommand(master, 'local', ['expression': tgt, 'type': 'compound'], fun, batch, arg)
Jakub Josef79ecec32017-02-17 14:36:28 +0100255 }
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100256
Tomáš Kukrálf5dda642017-03-02 14:22:59 +0100257 if (output == true) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100258 printSaltCommandResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100259 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100260}
261
262/**
263 * Check result for errors and throw exception if any found
264 *
265 * @param result Parsed response of Salt API
Jakub Josef5ade54c2017-03-10 16:14:01 +0100266 * @param failOnError Do you want to throw exception if salt-call fails
Jakub Josef79ecec32017-02-17 14:36:28 +0100267 */
Jakub Josef5ade54c2017-03-10 16:14:01 +0100268def checkResult(result, failOnError = true) {
269 def common = new com.mirantis.mk.Common()
Jakub Josefc952e5a2017-03-23 15:02:12 +0100270 def askOnError = false
271 try {
272 askOnError = env.ASK_ON_ERROR
273 } catch (MissingPropertyException e) {
274 askOnError = false
275 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100276 if(result != null){
277 if(result['return']){
278 for (int i=0;i<result['return'].size();i++) {
279 def entry = result['return'][i]
280 if (!entry) {
281 if (failOnError) {
282 throw new Exception("Salt API returned empty response: ${result}")
283 } else {
284 common.errorMsg("Salt API returned empty response: ${result}")
Jakub Josefece32af2017-03-14 19:20:08 +0100285 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100286 }
287 for (int j=0;j<entry.size();j++) {
288 def nodeKey = entry.keySet()[j]
289 def node=entry[nodeKey]
290 for (int k=0;k<node.size();k++) {
291 def resource;
292 def resKey;
293 if(node instanceof Map){
294 resKey = node.keySet()[k]
295 }else if(node instanceof List){
296 resKey = k
297 }
298 resource = node[resKey]
299 common.debugMsg("checkResult: checking resource: ${resource}")
300 if(resource instanceof String || !resource["result"] || (resource["result"] instanceof String && resource["result"] != "true")){
Jakub Josefc952e5a2017-03-23 15:02:12 +0100301 if(askOnError){
Jakub Josefb41c8d52017-03-24 13:52:24 +0100302 def prettyResource = common.prettyPrint(resource)
Jakub Josefc952e5a2017-03-23 15:02:12 +0100303 timeout(time:1, unit:'HOURS') {
Jakub Josef77ba3b22017-03-23 15:55:14 +0100304 input message: "False result on ${nodeKey} found, resource ${prettyResource}. \nDo you want to continue?"
Jakub Josefc952e5a2017-03-23 15:02:12 +0100305 }
306 }else{
307 if (failOnError) {
308 throw new Exception("Salt state on node ${nodeKey} failed: ${resource}. State output: ${node}")
309 } else {
310 common.errorMsg("Salt state on node ${nodeKey} failed: ${resource}. State output: ${node}")
311 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100312 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100313 }
Jakub Josef5ade54c2017-03-10 16:14:01 +0100314 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100315 }
316 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100317 }else{
318 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100319 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100320 }else{
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100321 common.errorMsg("Cannot check salt result, given result is null")
Jakub Josef79ecec32017-02-17 14:36:28 +0100322 }
323}
324
325/**
326 * Print Salt state run results in human-friendly form
327 *
328 * @param result Parsed response of Salt API
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100329 * @param onlyChanges If true (default), print only changed resources
Jakub Josef79ecec32017-02-17 14:36:28 +0100330 */
331def printSaltStateResult(result, onlyChanges = true) {
Tomáš Kukrál6eb45f82017-03-08 18:26:16 +0100332 def common = new com.mirantis.mk.Common()
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100333 if(result != null){
334 if(result['return']){
335 for (int i=0; i<result['return'].size(); i++) {
336 def entry = result['return'][i]
337 for (int j=0; j<entry.size(); j++) {
338 common.debugMsg("printSaltStateResult: printing salt command entry: ${entry}")
339 def nodeKey = entry.keySet()[j]
340 def node=entry[nodeKey]
341 common.infoMsg("Node ${nodeKey} changes:")
342 if(node instanceof Map || node instanceof List){
343 for (int k=0;k<node.size();k++) {
344 def resource;
345 def resKey;
346 if(node instanceof Map){
347 resKey = node.keySet()[k]
348 }else if(node instanceof List){
349 resKey = k
350 }
351 resource = node[resKey]
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100352 if(resource instanceof Map && resource.keySet().contains("result")){
Jakub Joseff1fb4472017-03-16 14:11:54 +0100353 //clean unnesaccary fields
354 if(resource.keySet().contains("__run_num__")){
355 resource.remove("__run_num__")
356 }
357 if(resource.keySet().contains("__id__")){
358 resource.remove("__id__")
359 }
360 if(resource.keySet().contains("pchanges")){
361 resource.remove("pchanges")
362 }
363
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100364 if(!resource["result"] || (resource["result"] instanceof String && resource["result"] != "true")){
Jakub Josef0e7bd632017-03-16 16:25:05 +0100365 if(resource["result"] != null){
Jakub Josefb41c8d52017-03-24 13:52:24 +0100366 common.errorMsg(String.format("Resource: %s\n%s", resKey, common.prettyPrint(resource)))
Jakub Josef0e7bd632017-03-16 16:25:05 +0100367 }else{
Jakub Josefb41c8d52017-03-24 13:52:24 +0100368 common.warningMsg(String.format("Resource: %s\n%s", resKey, common.prettyPrint(resource)))
Jakub Josef0e7bd632017-03-16 16:25:05 +0100369 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100370 }else{
371 if(!onlyChanges || resource.changes.size() > 0){
Jakub Josefb41c8d52017-03-24 13:52:24 +0100372 common.successMsg(String.format("Resource: %s\n%s", resKey, common.prettyPrint(resource)))
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100373 }
374 }
375 }else{
Jakub Josefb41c8d52017-03-24 13:52:24 +0100376 common.infoMsg(String.format("Resource: %s\n%s", resKey, common.prettyPrint(resource)))
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100377 }
378 }
379 }else{
Jakub Josefb41c8d52017-03-24 13:52:24 +0100380 common.infoMsg(common.prettyPrint(node))
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100381 }
382 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100383 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100384 }else{
385 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100386 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100387 }else{
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100388 common.errorMsg("Cannot print salt state result, given result is null")
Jakub Josef79ecec32017-02-17 14:36:28 +0100389 }
390}
391
392/**
Jakub Josef7852fe12017-03-15 16:02:41 +0100393 * Print salt command run results in human-friendly form
Jakub Josef79ecec32017-02-17 14:36:28 +0100394 *
395 * @param result Parsed response of Salt API
Jakub Josef79ecec32017-02-17 14:36:28 +0100396 */
Filip Pytlound2f1bbe2017-02-27 19:03:51 +0100397def printSaltCommandResult(result) {
Jakub Josef871bf152017-03-14 20:13:41 +0100398 def common = new com.mirantis.mk.Common()
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100399 if(result != null){
400 if(result['return']){
401 for (int i=0; i<result['return'].size(); i++) {
402 def entry = result['return'][i]
403 for (int j=0; j<entry.size(); j++) {
404 common.debugMsg("printSaltCommandResult: printing salt command entry: ${entry}")
405 def nodeKey = entry.keySet()[j]
406 def node=entry[nodeKey]
Jakub Josefb41c8d52017-03-24 13:52:24 +0100407 common.infoMsg(String.format("Node %s changes:\n%s",nodeKey, common.prettyPrint(node)))
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100408 }
Jakub Josef8a715bf2017-03-14 21:39:01 +0100409 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100410 }else{
411 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100412 }
Jakub Josef8a715bf2017-03-14 21:39:01 +0100413 }else{
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100414 common.errorMsg("Cannot print salt command result, given result is null")
Jakub Josef52f69f72017-03-14 15:18:08 +0100415 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100416}