blob: cc982284fa4b2ef57fb7306aae3c883f1618b12c [file] [log] [blame]
Jakub Josef79ecec32017-02-17 14:36:28 +01001package com.mirantis.mk
2
Jakub Josefd9afd0e2017-03-15 19:19:23 +01003import static groovy.json.JsonOutput.prettyPrint
4import static groovy.json.JsonOutput.toJson
Jakub Josef935d2ef2017-03-15 17:35:51 +01005
Jakub Josef79ecec32017-02-17 14:36:28 +01006/**
7 * Salt functions
8 *
9*/
10
11/**
12 * Salt connection and context parameters
13 *
14 * @param url Salt API server URL
15 * @param credentialsID ID of credentials store entry
16 */
17def connection(url, credentialsId = "salt") {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +010018 def common = new com.mirantis.mk.Common()
Jakub Josef79ecec32017-02-17 14:36:28 +010019 params = [
20 "url": url,
21 "credentialsId": credentialsId,
22 "authToken": null,
23 "creds": common.getCredentials(credentialsId)
24 ]
25 params["authToken"] = saltLogin(params)
26
27 return params
28}
29
30/**
31 * Login to Salt API, return auth token
32 *
33 * @param master Salt connection object
34 */
35def saltLogin(master) {
Tomáš Kukrál7bec0532017-02-20 15:39:31 +010036 def http = new com.mirantis.mk.Http()
Jakub Josef79ecec32017-02-17 14:36:28 +010037 data = [
38 'username': master.creds.username,
39 'password': master.creds.password.toString(),
40 'eauth': 'pam'
41 ]
Tomáš Kukrál7bec0532017-02-20 15:39:31 +010042 authToken = http.restGet(master, '/login', data)['return'][0]['token']
Jakub Josef79ecec32017-02-17 14:36:28 +010043 return authToken
44}
45
46/**
47 * Run action using Salt API
48 *
49 * @param master Salt connection object
50 * @param client Client type
51 * @param target Target specification, eg. for compound matches by Pillar
52 * data: ['expression': 'I@openssh:server', 'type': 'compound'])
53 * @param function Function to execute (eg. "state.sls")
Filip Pytlounf0435c02017-03-02 17:48:54 +010054 * @param batch
Jakub Josef79ecec32017-02-17 14:36:28 +010055 * @param args Additional arguments to function
56 * @param kwargs Additional key-value arguments to function
57 */
58@NonCPS
59def runSaltCommand(master, client, target, function, batch = null, args = null, kwargs = null) {
iberezovskiyd4240b52017-02-20 17:18:28 +040060 def http = new com.mirantis.mk.Http()
Jakub Josef79ecec32017-02-17 14:36:28 +010061
62 data = [
63 'tgt': target.expression,
64 'fun': function,
65 'client': client,
66 'expr_form': target.type,
67 ]
68
Filip Pytlounf0435c02017-03-02 17:48:54 +010069 if (batch == true) {
70 data['batch'] = "local_batch"
Jakub Josef79ecec32017-02-17 14:36:28 +010071 }
72
73 if (args) {
74 data['arg'] = args
75 }
76
77 if (kwargs) {
78 data['kwarg'] = kwargs
79 }
80
81 headers = [
82 'X-Auth-Token': "${master.authToken}"
83 ]
84
85 return http.sendHttpPostRequest("${master.url}/", data, headers)
86}
87
Jakub Josef5ade54c2017-03-10 16:14:01 +010088/**
89 * Return pillar for given master and target
90 * @param master Salt connection object
91 * @param target Get pillar target
92 * @param pillar pillar name (optional)
93 * @return output of salt command
94 */
Ales Komarekcec24d42017-03-08 10:25:45 +010095def getPillar(master, target, pillar = null) {
Tomáš Kukráld2589702017-03-10 16:30:46 +010096 if (pillar != null) {
Jakub Josef5ade54c2017-03-10 16:14:01 +010097 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'pillar.get', null, [pillar.replace('.', ':')])
Tomáš Kukráld2589702017-03-10 16:30:46 +010098 } else {
Jakub Josef5ade54c2017-03-10 16:14:01 +010099 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'pillar.data')
Ales Komareka3c7e502017-03-13 11:20:44 +0100100 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100101}
102
Jakub Josef5ade54c2017-03-10 16:14:01 +0100103/**
104 * Return grain for given master and target
105 * @param master Salt connection object
106 * @param target Get grain target
107 * @param grain grain name (optional)
108 * @return output of salt command
109 */
Ales Komarekcec24d42017-03-08 10:25:45 +0100110def getGrain(master, target, grain = null) {
111 if(grain != null) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100112 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'grain.item', null, [grain])
113 } else {
114 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'grain.items')
Ales Komarekcec24d42017-03-08 10:25:45 +0100115 }
Ales Komarekcec24d42017-03-08 10:25:45 +0100116}
117
Jakub Josef5ade54c2017-03-10 16:14:01 +0100118/**
119 * Enforces state on given master and target
120 * @param master Salt connection object
121 * @param target State enforcing target
122 * @param state Salt state
123 * @param output print output (optional, default true)
124 * @param failOnError throw exception on salt state result:false (optional, default true)
125 * @return output of salt command
126 */
127def enforceState(master, target, state, output = true, failOnError = true) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100128 def common = new com.mirantis.mk.Common()
Jakub Josef79ecec32017-02-17 14:36:28 +0100129 def run_states
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100130
Jakub Josef79ecec32017-02-17 14:36:28 +0100131 if (state instanceof String) {
132 run_states = state
133 } else {
134 run_states = state.join(',')
135 }
136
Tomáš Kukráldfd4b492017-03-02 12:08:50 +0100137 common.infoMsg("Enforcing state ${run_states} on ${target}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100138
Filip Pytloun5a7f7fd2017-02-27 18:50:25 +0100139 def out = runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'state.sls', null, [run_states])
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100140
Jakub Josef79ecec32017-02-17 14:36:28 +0100141 try {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100142 checkResult(out, failOnError)
Jakub Josef79ecec32017-02-17 14:36:28 +0100143 } finally {
144 if (output == true) {
Filip Pytlound2f1bbe2017-02-27 19:03:51 +0100145 printSaltStateResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100146 }
147 }
148 return out
149}
150
Jakub Josef5ade54c2017-03-10 16:14:01 +0100151/**
152 * Run command on salt minion (salt cmd.run wrapper)
153 * @param master Salt connection object
154 * @param target Get pillar target
155 * @param cmd command
156 * @return output of salt command
157 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100158def cmdRun(master, target, cmd) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100159 def common = new com.mirantis.mk.Common()
160
Tomáš Kukráldfd4b492017-03-02 12:08:50 +0100161 common.infoMsg("Running command ${cmd} on ${target}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100162
Jakub Josef5ade54c2017-03-10 16:14:01 +0100163 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'cmd.run', null, [cmd])
Jakub Josef79ecec32017-02-17 14:36:28 +0100164}
165
Jakub Josef5ade54c2017-03-10 16:14:01 +0100166/**
167 * Perform complete salt sync between master and target
168 * @param master Salt connection object
169 * @param target Get pillar target
170 * @return output of salt command
171 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100172def syncAll(master, target) {
Filip Pytloun5a7f7fd2017-02-27 18:50:25 +0100173 return runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'saltutil.sync_all')
Jakub Josef79ecec32017-02-17 14:36:28 +0100174}
175
Jakub Josef5ade54c2017-03-10 16:14:01 +0100176/**
177 * Enforce highstate on given targets
178 * @param master Salt connection object
179 * @param target Highstate enforcing target
180 * @param output print output (optional, default true)
181 * @param failOnError throw exception on salt state result:false (optional, default true)
182 * @return output of salt command
183 */
184def enforceHighstate(master, target, output = false, failOnError = true) {
Filip Pytloun5a7f7fd2017-02-27 18:50:25 +0100185 def out = runSaltCommand(master, 'local', ['expression': target, 'type': 'compound'], 'state.highstate')
Jakub Josef79ecec32017-02-17 14:36:28 +0100186 try {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100187 checkResult(out, failOnError)
Jakub Josef79ecec32017-02-17 14:36:28 +0100188 } finally {
189 if (output == true) {
Filip Pytlound2f1bbe2017-02-27 19:03:51 +0100190 printSaltStateResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100191 }
192 }
193 return out
194}
195
Jakub Josef5ade54c2017-03-10 16:14:01 +0100196/**
Ales Komarek5276ebe2017-03-16 08:46:34 +0100197 * Get running minions IDs according to the target
198 * @param master Salt connection object
199 * @param target Get minions target
200 * @return list of active minions fitin
201 */
202def getMinions(master, target) {
203 def minionsRaw = runSaltCommand(master, 'local', target, 'test.ping')
204 return new ArrayList<String>(minionsRaw['return'][0].keySet())
205}
206
207
208/**
Jakub Josef5ade54c2017-03-10 16:14:01 +0100209 * Generates node key using key.gen_accept call
210 * @param master Salt connection object
211 * @param target Key generating target
212 * @param host Key generating host
213 * @param keysize generated key size (optional, default 4096)
214 * @return output of salt command
215 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100216def generateNodeKey(master, target, host, keysize = 4096) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100217 return runSaltCommand(master, 'wheel', target, 'key.gen_accept', [host], ['keysize': keysize])
Jakub Josef79ecec32017-02-17 14:36:28 +0100218}
219
Jakub Josef5ade54c2017-03-10 16:14:01 +0100220/**
221 * Generates node reclass metadata
222 * @param master Salt connection object
223 * @param target Metadata generating target
224 * @param host Metadata generating host
225 * @param classes Reclass classes
226 * @param parameters Reclass parameters
227 * @return output of salt command
228 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100229def generateNodeMetadata(master, target, host, classes, parameters) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100230 return runSaltCommand(master, 'local', target, 'reclass.node_create', [host, '_generated'], ['classes': classes, 'parameters': parameters])
Jakub Josef79ecec32017-02-17 14:36:28 +0100231}
232
Jakub Josef5ade54c2017-03-10 16:14:01 +0100233/**
234 * Run salt orchestrate on given targets
235 * @param master Salt connection object
236 * @param target Orchestration target
237 * @param orchestrate Salt orchestrate params
238 * @return output of salt command
239 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100240def orchestrateSystem(master, target, orchestrate) {
241 return runSaltCommand(master, 'runner', target, 'state.orchestrate', [orchestrate])
242}
243
Jakub Josef5ade54c2017-03-10 16:14:01 +0100244/**
245 * Run salt process step
246 * @param master Salt connection object
247 * @param tgt Salt process step target
248 * @param fun Salt process step function
249 * @param arg process step arguments (optional, default [])
250 * @param batch using batch (optional, default false)
251 * @param output print output (optional, default false)
252 * @return output of salt command
253 */
Tomáš Kukrálf5dda642017-03-02 14:22:59 +0100254def runSaltProcessStep(master, tgt, fun, arg = [], batch = null, output = false) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100255 def common = new com.mirantis.mk.Common()
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100256 def out
257
Tomas Kukrale90bb342017-03-02 21:30:35 +0000258 common.infoMsg("Running step ${fun} on ${tgt}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100259
Filip Pytlounf0435c02017-03-02 17:48:54 +0100260 if (batch == true) {
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100261 out = runSaltCommand(master, 'local_batch', ['expression': tgt, 'type': 'compound'], fun, String.valueOf(batch), arg)
262 } else {
263 out = runSaltCommand(master, 'local', ['expression': tgt, 'type': 'compound'], fun, batch, arg)
Jakub Josef79ecec32017-02-17 14:36:28 +0100264 }
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100265
Tomáš Kukrálf5dda642017-03-02 14:22:59 +0100266 if (output == true) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100267 printSaltCommandResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100268 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100269}
270
271/**
272 * Check result for errors and throw exception if any found
273 *
274 * @param result Parsed response of Salt API
Jakub Josef5ade54c2017-03-10 16:14:01 +0100275 * @param failOnError Do you want to throw exception if salt-call fails
Jakub Josef79ecec32017-02-17 14:36:28 +0100276 */
Jakub Josef5ade54c2017-03-10 16:14:01 +0100277def checkResult(result, failOnError = true) {
278 def common = new com.mirantis.mk.Common()
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100279 if(result != null){
280 if(result['return']){
281 for (int i=0;i<result['return'].size();i++) {
282 def entry = result['return'][i]
283 if (!entry) {
284 if (failOnError) {
285 throw new Exception("Salt API returned empty response: ${result}")
286 } else {
287 common.errorMsg("Salt API returned empty response: ${result}")
Jakub Josefece32af2017-03-14 19:20:08 +0100288 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100289 }
290 for (int j=0;j<entry.size();j++) {
291 def nodeKey = entry.keySet()[j]
292 def node=entry[nodeKey]
293 for (int k=0;k<node.size();k++) {
294 def resource;
295 def resKey;
296 if(node instanceof Map){
297 resKey = node.keySet()[k]
298 }else if(node instanceof List){
299 resKey = k
300 }
301 resource = node[resKey]
302 common.debugMsg("checkResult: checking resource: ${resource}")
303 if(resource instanceof String || !resource["result"] || (resource["result"] instanceof String && resource["result"] != "true")){
304 if (failOnError) {
305 throw new Exception("Salt state on node ${nodeKey} failed: ${resource}. State output: ${node}")
306 } else {
307 common.errorMsg("Salt state on node ${nodeKey} failed: ${resource}. State output: ${node}")
308 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100309 }
Jakub Josef5ade54c2017-03-10 16:14:01 +0100310 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100311 }
312 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100313 }else{
314 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100315 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100316 }else{
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100317 common.errorMsg("Cannot check salt result, given result is null")
Jakub Josef79ecec32017-02-17 14:36:28 +0100318 }
319}
320
321/**
322 * Print Salt state run results in human-friendly form
323 *
324 * @param result Parsed response of Salt API
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100325 * @param onlyChanges If true (default), print only changed resources
Jakub Josef79ecec32017-02-17 14:36:28 +0100326 */
327def printSaltStateResult(result, onlyChanges = true) {
Tomáš Kukrál6eb45f82017-03-08 18:26:16 +0100328 def common = new com.mirantis.mk.Common()
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100329 if(result != null){
330 if(result['return']){
331 for (int i=0; i<result['return'].size(); i++) {
332 def entry = result['return'][i]
333 for (int j=0; j<entry.size(); j++) {
334 common.debugMsg("printSaltStateResult: printing salt command entry: ${entry}")
335 def nodeKey = entry.keySet()[j]
336 def node=entry[nodeKey]
337 common.infoMsg("Node ${nodeKey} changes:")
338 if(node instanceof Map || node instanceof List){
339 for (int k=0;k<node.size();k++) {
340 def resource;
341 def resKey;
342 if(node instanceof Map){
343 resKey = node.keySet()[k]
344 }else if(node instanceof List){
345 resKey = k
346 }
347 resource = node[resKey]
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100348 if(resource instanceof Map && resource.keySet().contains("result")){
Jakub Joseff1fb4472017-03-16 14:11:54 +0100349 //clean unnesaccary fields
350 if(resource.keySet().contains("__run_num__")){
351 resource.remove("__run_num__")
352 }
353 if(resource.keySet().contains("__id__")){
354 resource.remove("__id__")
355 }
356 if(resource.keySet().contains("pchanges")){
357 resource.remove("pchanges")
358 }
359
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100360 if(!resource["result"] || (resource["result"] instanceof String && resource["result"] != "true")){
361 common.errorMsg(String.format("Resource: %s\n%s", resKey, prettyPrint(toJson(resource)).replace('\\n', System.getProperty('line.separator'))))
362 }else{
363 if(!onlyChanges || resource.changes.size() > 0){
364 common.successMsg(String.format("Resource: %s\n%s", resKey, prettyPrint(toJson(resource)).replace('\\n', System.getProperty('line.separator'))))
365 }
366 }
367 }else{
368 common.infoMsg(String.format("Resource: %s\n%s", resKey, prettyPrint(toJson(resource)).replace('\\n', System.getProperty('line.separator'))))
369 }
370 }
371 }else{
372 common.infoMsg(prettyPrint(toJson(node)))
373 }
374 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100375 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100376 }else{
377 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100378 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100379 }else{
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100380 common.errorMsg("Cannot print salt state result, given result is null")
Jakub Josef79ecec32017-02-17 14:36:28 +0100381 }
382}
383
384/**
Jakub Josef7852fe12017-03-15 16:02:41 +0100385 * Print salt command run results in human-friendly form
Jakub Josef79ecec32017-02-17 14:36:28 +0100386 *
387 * @param result Parsed response of Salt API
Jakub Josef79ecec32017-02-17 14:36:28 +0100388 */
Filip Pytlound2f1bbe2017-02-27 19:03:51 +0100389def printSaltCommandResult(result) {
Jakub Josef871bf152017-03-14 20:13:41 +0100390 def common = new com.mirantis.mk.Common()
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100391 if(result != null){
392 if(result['return']){
393 for (int i=0; i<result['return'].size(); i++) {
394 def entry = result['return'][i]
395 for (int j=0; j<entry.size(); j++) {
396 common.debugMsg("printSaltCommandResult: printing salt command entry: ${entry}")
397 def nodeKey = entry.keySet()[j]
398 def node=entry[nodeKey]
399 common.infoMsg(String.format("Node %s changes:\n%s",nodeKey,prettyPrint(toJson(node)).replace('\\n', System.getProperty('line.separator'))))
400 }
Jakub Josef8a715bf2017-03-14 21:39:01 +0100401 }
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100402 }else{
403 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100404 }
Jakub Josef8a715bf2017-03-14 21:39:01 +0100405 }else{
Jakub Josefd9afd0e2017-03-15 19:19:23 +0100406 common.errorMsg("Cannot print salt command result, given result is null")
Jakub Josef52f69f72017-03-14 15:18:08 +0100407 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100408}