MCP pipeline library merged with ccp-pipeline-libs repo.
Change-Id: I74080e18c5a482b7bf44b5516228b7bfe1fe3586
diff --git a/src/com/mirantis/mk/Salt.groovy b/src/com/mirantis/mk/Salt.groovy
new file mode 100644
index 0000000..8422cb3
--- /dev/null
+++ b/src/com/mirantis/mk/Salt.groovy
@@ -0,0 +1,233 @@
+package com.mirantis.mk
+
+/**
+ * Salt functions
+ *
+*/
+
+/**
+ * Salt connection and context parameters
+ *
+ * @param url Salt API server URL
+ * @param credentialsID ID of credentials store entry
+ */
+def connection(url, credentialsId = "salt") {
+ def common = new com.mirantis.mk.Common();
+ params = [
+ "url": url,
+ "credentialsId": credentialsId,
+ "authToken": null,
+ "creds": common.getCredentials(credentialsId)
+ ]
+ params["authToken"] = saltLogin(params)
+
+ return params
+}
+
+/**
+ * Login to Salt API, return auth token
+ *
+ * @param master Salt connection object
+ */
+def saltLogin(master) {
+ data = [
+ 'username': master.creds.username,
+ 'password': master.creds.password.toString(),
+ 'eauth': 'pam'
+ ]
+ authToken = restGet(master, '/login', data)['return'][0]['token']
+ return authToken
+}
+
+/**
+ * Run action using Salt API
+ *
+ * @param master Salt connection object
+ * @param client Client type
+ * @param target Target specification, eg. for compound matches by Pillar
+ * data: ['expression': 'I@openssh:server', 'type': 'compound'])
+ * @param function Function to execute (eg. "state.sls")
+ * @param batch
+ * @param args Additional arguments to function
+ * @param kwargs Additional key-value arguments to function
+ */
+@NonCPS
+def runSaltCommand(master, client, target, function, batch = null, args = null, kwargs = null) {
+ def http = new com.mirantis.mk.http()
+
+ data = [
+ 'tgt': target.expression,
+ 'fun': function,
+ 'client': client,
+ 'expr_form': target.type,
+ ]
+
+ if (batch) {
+ data['batch'] = batch
+ }
+
+ if (args) {
+ data['arg'] = args
+ }
+
+ if (kwargs) {
+ data['kwarg'] = kwargs
+ }
+
+ headers = [
+ 'X-Auth-Token': "${master.authToken}"
+ ]
+
+ return http.sendHttpPostRequest("${master.url}/", data, headers)
+}
+
+def pillarGet(master, target, pillar) {
+ def out = runSaltCommand(master, 'local', target, 'pillar.get', null, [pillar.replace('.', ':')])
+ return out
+}
+
+def enforceState(master, target, state, output = false) {
+ def run_states
+ if (state instanceof String) {
+ run_states = state
+ } else {
+ run_states = state.join(',')
+ }
+
+ def out = runSaltCommand(master, 'local', target, 'state.sls', null, [run_states])
+ try {
+ checkResult(out)
+ } finally {
+ if (output == true) {
+ printResult(out)
+ }
+ }
+ return out
+}
+
+def cmdRun(master, target, cmd) {
+ def out = runSaltCommand(master, 'local', target, 'cmd.run', null, [cmd])
+ return out
+}
+
+def syncAll(master, target) {
+ return runSaltCommand(master, 'local', target, 'saltutil.sync_all')
+}
+
+def enforceHighstate(master, target, output = false) {
+ def out = runSaltCommand(master, 'local', target, 'state.highstate')
+ try {
+ checkResult(out)
+ } finally {
+ if (output == true) {
+ printResult(out)
+ }
+ }
+ return out
+}
+
+def generateNodeKey(master, target, host, keysize = 4096) {
+ args = [host]
+ kwargs = ['keysize': keysize]
+ return runSaltCommand(master, 'wheel', target, 'key.gen_accept', args, kwargs)
+}
+
+def generateNodeMetadata(master, target, host, classes, parameters) {
+ args = [host, '_generated']
+ kwargs = ['classes': classes, 'parameters': parameters]
+ return runSaltCommand(master, 'local', target, 'reclass.node_create', args, kwargs)
+}
+
+def orchestrateSystem(master, target, orchestrate) {
+ return runSaltCommand(master, 'runner', target, 'state.orchestrate', [orchestrate])
+}
+
+def runSaltProcessStep(master, tgt, fun, arg = [], batch = null) {
+ if (batch) {
+ result = runSaltCommand(master, 'local_batch', ['expression': tgt, 'type': 'compound'], fun, String.valueOf(batch), arg)
+ }
+ else {
+ result = runSaltCommand(master, 'local', ['expression': tgt, 'type': 'compound'], fun, batch, arg)
+ }
+ echo("${result}")
+}
+
+/**
+ * Check result for errors and throw exception if any found
+ *
+ * @param result Parsed response of Salt API
+ */
+def checkResult(result) {
+ for (entry in result['return']) {
+ if (!entry) {
+ throw new Exception("Salt API returned empty response: ${result}")
+ }
+ for (node in entry) {
+ for (resource in node.value) {
+ if (resource instanceof String || resource.value.result.toString().toBoolean() != true) {
+ throw new Exception("Salt state on node ${node.key} failed: ${node.value}")
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Print Salt state run results in human-friendly form
+ *
+ * @param result Parsed response of Salt API
+ * @param onlyChanges If true (default), print only changed resources
+ * parsing
+ */
+def printSaltStateResult(result, onlyChanges = true) {
+ def out = [:]
+ for (entry in result['return']) {
+ for (node in entry) {
+ out[node.key] = [:]
+ for (resource in node.value) {
+ if (resource instanceof String) {
+ out[node.key] = node.value
+ } else if (resource.value.result.toString().toBoolean() == false || resource.value.changes || onlyChanges == false) {
+ out[node.key][resource.key] = resource.value
+ }
+ }
+ }
+ }
+
+ for (node in out) {
+ if (node.value) {
+ println "Node ${node.key} changes:"
+ print new groovy.json.JsonBuilder(node.value).toPrettyString()
+ } else {
+ println "No changes for node ${node.key}"
+ }
+ }
+}
+
+/**
+ * Print Salt state run results in human-friendly form
+ *
+ * @param result Parsed response of Salt API
+ * @param onlyChanges If true (default), print only changed resources
+ * parsing
+ */
+def printSaltCommandResult(result, onlyChanges = true) {
+ def out = [:]
+ for (entry in result['return']) {
+ for (node in entry) {
+ out[node.key] = [:]
+ for (resource in node.value) {
+ out[node.key] = node.value
+ }
+ }
+ }
+
+ for (node in out) {
+ if (node.value) {
+ println "Node ${node.key} changes:"
+ print new groovy.json.JsonBuilder(node.value).toPrettyString()
+ } else {
+ println "No changes for node ${node.key}"
+ }
+ }
+}