blob: f6f55540a328e234cf33adef59b366b9f41cd9d1 [file] [log] [blame]
Jakub Josef79ecec32017-02-17 14:36:28 +01001package com.mirantis.mk
2
Jakub Josef935d2ef2017-03-15 17:35:51 +01003import static groovy.json.JsonOutput.prettyPrint;
4import static groovy.json.JsonOutput.toJson;
5
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/**
197 * Generates node key using key.gen_accept call
198 * @param master Salt connection object
199 * @param target Key generating target
200 * @param host Key generating host
201 * @param keysize generated key size (optional, default 4096)
202 * @return output of salt command
203 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100204def generateNodeKey(master, target, host, keysize = 4096) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100205 return runSaltCommand(master, 'wheel', target, 'key.gen_accept', [host], ['keysize': keysize])
Jakub Josef79ecec32017-02-17 14:36:28 +0100206}
207
Jakub Josef5ade54c2017-03-10 16:14:01 +0100208/**
209 * Generates node reclass metadata
210 * @param master Salt connection object
211 * @param target Metadata generating target
212 * @param host Metadata generating host
213 * @param classes Reclass classes
214 * @param parameters Reclass parameters
215 * @return output of salt command
216 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100217def generateNodeMetadata(master, target, host, classes, parameters) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100218 return runSaltCommand(master, 'local', target, 'reclass.node_create', [host, '_generated'], ['classes': classes, 'parameters': parameters])
Jakub Josef79ecec32017-02-17 14:36:28 +0100219}
220
Jakub Josef5ade54c2017-03-10 16:14:01 +0100221/**
222 * Run salt orchestrate on given targets
223 * @param master Salt connection object
224 * @param target Orchestration target
225 * @param orchestrate Salt orchestrate params
226 * @return output of salt command
227 */
Jakub Josef79ecec32017-02-17 14:36:28 +0100228def orchestrateSystem(master, target, orchestrate) {
229 return runSaltCommand(master, 'runner', target, 'state.orchestrate', [orchestrate])
230}
231
Jakub Josef5ade54c2017-03-10 16:14:01 +0100232/**
233 * Run salt process step
234 * @param master Salt connection object
235 * @param tgt Salt process step target
236 * @param fun Salt process step function
237 * @param arg process step arguments (optional, default [])
238 * @param batch using batch (optional, default false)
239 * @param output print output (optional, default false)
240 * @return output of salt command
241 */
Tomáš Kukrálf5dda642017-03-02 14:22:59 +0100242def runSaltProcessStep(master, tgt, fun, arg = [], batch = null, output = false) {
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100243 def common = new com.mirantis.mk.Common()
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100244 def out
245
Tomas Kukrale90bb342017-03-02 21:30:35 +0000246 common.infoMsg("Running step ${fun} on ${tgt}")
Tomáš Kukrál6c04bd02017-03-01 22:18:52 +0100247
Filip Pytlounf0435c02017-03-02 17:48:54 +0100248 if (batch == true) {
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100249 out = runSaltCommand(master, 'local_batch', ['expression': tgt, 'type': 'compound'], fun, String.valueOf(batch), arg)
250 } else {
251 out = runSaltCommand(master, 'local', ['expression': tgt, 'type': 'compound'], fun, batch, arg)
Jakub Josef79ecec32017-02-17 14:36:28 +0100252 }
Tomáš Kukráladb4ecd2017-03-02 10:06:36 +0100253
Tomáš Kukrálf5dda642017-03-02 14:22:59 +0100254 if (output == true) {
Jakub Josef5ade54c2017-03-10 16:14:01 +0100255 printSaltCommandResult(out)
Jakub Josef79ecec32017-02-17 14:36:28 +0100256 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100257}
258
259/**
260 * Check result for errors and throw exception if any found
261 *
262 * @param result Parsed response of Salt API
Jakub Josef5ade54c2017-03-10 16:14:01 +0100263 * @param failOnError Do you want to throw exception if salt-call fails
Jakub Josef79ecec32017-02-17 14:36:28 +0100264 */
Jakub Josef5ade54c2017-03-10 16:14:01 +0100265def checkResult(result, failOnError = true) {
266 def common = new com.mirantis.mk.Common()
Jakub Josef52f69f72017-03-14 15:18:08 +0100267 if(result['return']){
268 for (int i=0;i<result['return'].size();i++) {
269 def entry = result['return'][i]
270 if (!entry) {
271 if (failOnError) {
272 throw new Exception("Salt API returned empty response: ${result}")
273 } else {
274 common.errorMsg("Salt API returned empty response: ${result}")
Jakub Josefa532efc2017-03-13 14:51:40 +0100275 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100276 }
277 for (int j=0;j<entry.size();j++) {
Jakub Josef26546bd2017-03-14 18:38:00 +0100278 def nodeKey = entry.keySet()[j]
279 def node=entry[nodeKey]
280 for (int k=0;k<node.size();k++) {
Jakub Josefece32af2017-03-14 19:20:08 +0100281 def resource;
282 def resKey;
283 if(node instanceof Map){
284 resKey = node.keySet()[k]
285 }else if(node instanceof List){
286 resKey = k
287 }
288 resource = node[resKey]
Jakub Josef74b34692017-03-15 12:10:57 +0100289 common.debugMsg("checkResult: checking resource: ${resource}")
Jakub Josef871bf152017-03-14 20:13:41 +0100290 if(resource instanceof String || !resource["result"] || (resource["result"] instanceof String && resource["result"] != "true")){
Jakub Josef52f69f72017-03-14 15:18:08 +0100291 if (failOnError) {
Jakub Josef9873dae2017-03-14 19:23:19 +0100292 throw new Exception("Salt state on node ${nodeKey} failed: ${resource}. State output: ${node}")
Jakub Josef52f69f72017-03-14 15:18:08 +0100293 } else {
Jakub Josef9873dae2017-03-14 19:23:19 +0100294 common.errorMsg("Salt state on node ${nodeKey} failed: ${resource}. State output: ${node}")
Jakub Josef52f69f72017-03-14 15:18:08 +0100295 }
Jakub Josef5ade54c2017-03-10 16:14:01 +0100296 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100297 }
298 }
299 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100300 }else{
301 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100302 }
303}
304
305/**
306 * Print Salt state run results in human-friendly form
307 *
308 * @param result Parsed response of Salt API
309 * @param onlyChanges If true (default), print only changed resources
310 * parsing
311 */
312def printSaltStateResult(result, onlyChanges = true) {
Tomáš Kukrál6eb45f82017-03-08 18:26:16 +0100313 def common = new com.mirantis.mk.Common()
Jakub Josef935d2ef2017-03-15 17:35:51 +0100314 if(result['return']){
Jakub Josef52f69f72017-03-14 15:18:08 +0100315 for (int i=0; i<result['return'].size(); i++) {
Jakub Joseff17c5342017-03-14 18:06:50 +0100316 def entry = result['return'][i]
Jakub Josef52f69f72017-03-14 15:18:08 +0100317 for (int j=0; j<entry.size(); j++) {
Jakub Josef74b34692017-03-15 12:10:57 +0100318 common.debugMsg("printSaltStateResult: printing salt state entry: ${entry}")
Jakub Josefce90b102017-03-14 16:22:55 +0100319 def nodeKey = entry.keySet()[j]
320 def node=entry[nodeKey]
Jakub Josef935d2ef2017-03-15 17:35:51 +0100321 common.infoMsg(String.format("Node %s changes:\n%s",nodeKey,prettyPrint(toJson(node))))
Jakub Josef52f69f72017-03-14 15:18:08 +0100322 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100323 }
Jakub Josef52f69f72017-03-14 15:18:08 +0100324 }else{
325 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
Jakub Josef79ecec32017-02-17 14:36:28 +0100326 }
327}
328
329/**
Jakub Josef7852fe12017-03-15 16:02:41 +0100330 * Print salt command run results in human-friendly form
Jakub Josef79ecec32017-02-17 14:36:28 +0100331 *
332 * @param result Parsed response of Salt API
Jakub Josef79ecec32017-02-17 14:36:28 +0100333 */
Filip Pytlound2f1bbe2017-02-27 19:03:51 +0100334def printSaltCommandResult(result) {
Jakub Josef871bf152017-03-14 20:13:41 +0100335 def common = new com.mirantis.mk.Common()
Jakub Josef52f69f72017-03-14 15:18:08 +0100336 if(result['return']){
337 for (int i=0; i<result['return'].size(); i++) {
Jakub Joseff17c5342017-03-14 18:06:50 +0100338 def entry = result['return'][i]
Jakub Josef52f69f72017-03-14 15:18:08 +0100339 for (int j=0; j<entry.size(); j++) {
Jakub Josef74b34692017-03-15 12:10:57 +0100340 common.debugMsg("printSaltCommandResult: printing salt command entry: ${entry}")
Jakub Josefce90b102017-03-14 16:22:55 +0100341 def nodeKey = entry.keySet()[j]
342 def node=entry[nodeKey]
Jakub Josef935d2ef2017-03-15 17:35:51 +0100343 common.infoMsg(String.format("Node %s changes:\n%s", nodeKey, prettyPrint(toJson(node))))
Jakub Josef8a715bf2017-03-14 21:39:01 +0100344 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100345 }
Jakub Josef8a715bf2017-03-14 21:39:01 +0100346 }else{
Jakub Josef52f69f72017-03-14 15:18:08 +0100347 common.errorMsg("Salt result hasn't return attribute! Result: ${result}")
348 }
Jakub Josef79ecec32017-02-17 14:36:28 +0100349}