
io.takari.orchestra.plugins.ansible.RunPlaybookTask Maven / Gradle / Ivy
package io.takari.orchestra.plugins.ansible;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.takari.bpm.api.BpmnError;
import io.takari.bpm.api.ExecutionContext;
import io.takari.bpm.api.JavaDelegate;
import io.takari.orchestra.common.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Named;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
@Named
public class RunPlaybookTask implements JavaDelegate, Task {
private static final Logger log = LoggerFactory.getLogger(RunPlaybookTask.class);
private static final int SUCCESS_EXIT_CODE = 0;
public static final String PLAYBOOKS_PATH_KEY = "ansiblePlaybooksPath";
private static final String DEFAULT_PLAYBOOKS_PATH = "/opt/orchestra/ansible/playbooks";
public static final String PLAYBOOK_KEY = "ansiblePlaybook";
private static final String DEFAULT_PLAYBOOK = "playbook.yml";
public static final String USER_KEY = "ansibleUser";
private static final String DEFAULT_USER = "ansible";
public static final String USER_PASSWORD_KEY = "ansiblePassword";
public static final String HOSTS_FILE_TEMPLATE_KEY = "ansibleHostsFileTemplate";
private static final String DEFAULT_HOSTS_FILE_TEMPLATE = "[all]\n%s";
public static final String HOSTS_KEY = "ansibleHosts";
public static final String EXTRA_VARS_KEY = "ansibleExtraVars";
public static final String TAGS_KEY = "ansibleTags";
@Override
public String getKey() {
return "ansible";
}
@Override
public void execute(ExecutionContext ctx) throws Exception {
Collection hosts = (Collection) getObject(ctx, HOSTS_KEY, null);
String playbooksPath = getString(ctx, PLAYBOOKS_PATH_KEY, DEFAULT_PLAYBOOKS_PATH);
String playbook = getString(ctx, PLAYBOOK_KEY, DEFAULT_PLAYBOOK);
String user = getString(ctx, USER_KEY, DEFAULT_USER);
String hostsFileTemplate = getString(ctx, HOSTS_FILE_TEMPLATE_KEY, DEFAULT_HOSTS_FILE_TEMPLATE);
// TODO secure store?
PasswordCallback callback = (x) -> ((String) x.getVariable(USER_PASSWORD_KEY)).getBytes();
String tags = getString(ctx, TAGS_KEY, null);
run(hosts, playbooksPath, playbook, user, new String(callback.getPassword(ctx)), hostsFileTemplate, getExtraVars(ctx), tags);
}
// TODO redo this as a builder
public void run(Collection hosts, String playbooksPath, String playbook, String user, String password, String hostsFileTemplate, Map extraVars, String tags) throws Exception {
if (hosts == null || hosts.isEmpty()) {
log.warn("execution -> no hosts were specified for this task, skipping");
return;
}
String evs = formatExtraVars(extraVars);
// TODO move into a task's initializer
createAnsibleCfgFile(System.getProperty("user.home") + "/.ansible.cfg");
// TODO prepare a script instead of passing everything as args
String hostsDir = makeHostsDir(hostsFileTemplate, hosts, user, password);
String[] cmd = formatCmd(hostsDir, playbooksPath, playbook, user, evs, tags);
log.info("execution -> cmd: {}", String.join(" ", cmd));
Process p = new ProcessBuilder()
.command(cmd)
.redirectErrorStream(true)
.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
log.info("OUTPUT: {}", line);
}
int code = p.waitFor();
if (code == SUCCESS_EXIT_CODE) {
log.info("execution -> done");
} else {
log.error("execution -> finished with exit code {}", code);
throw new BpmnError("ansibleError", new IllegalStateException("Process finished with with exit code " + code));
}
}
private static String getString(ExecutionContext ctx, String k, String defaultValue) {
String s = (String) ctx.getVariable(k);
if (s == null) {
return defaultValue;
}
return s;
}
private static Object getObject(ExecutionContext ctx, String k, Object defaultValue) {
Object o = ctx.getVariable(k);
if (o == null) {
return defaultValue;
}
return o;
}
private static Map getExtraVars(ExecutionContext ctx) {
Map m = new HashMap<>();
Collection keys = (Collection) getObject(ctx, EXTRA_VARS_KEY, null);
for (String k : keys) {
Object v = ctx.getVariable(k);
if (v == null) {
continue;
}
m.put(k, v.toString());
}
return m;
}
private static String formatExtraVars(Map m) throws IOException {
return new ObjectMapper().writeValueAsString(m);
}
private static String makeHostsDir(String template, Collection hosts, String user, String passwd) throws IOException {
StringBuilder hs = new StringBuilder();
for (String h : hosts) {
hs.append(h).append(" ansible_ssh_user=").append(user)
.append(" ansible_ssh_pass=").append(passwd)
.append(" ansible_sudo_pass=").append(passwd)
.append('\n');
}
String s = String.format(template.replaceAll("\\n", "\n"), hs.toString());
Path tmpDir = Files.createTempDirectory("inventory");
File f = new File(tmpDir.toFile(), "hosts");
try (FileWriter w = new FileWriter(f)) {
w.append(s);
w.flush();
}
return tmpDir.toAbsolutePath().toString();
}
private static String[] formatCmd(String hostsPath, String playbooksPath, String playbook, String user, String extraVars, String tags) {
List l = new ArrayList<>(Arrays.asList(
"ansible-playbook", "-T", "30", "-i", hostsPath, playbooksPath + "/" + playbook, "-u", user, "-e", extraVars));
if (tags != null) {
l.add("-t");
l.add(tags);
}
return l.toArray(new String[l.size()]);
}
private static void createAnsibleCfgFile(String path) throws IOException {
String template = "[defaults]\n" +
"host_key_checking = False\n" +
"remote_tmp = /tmp/ansible/$USER\n" +
"[ssh_connection]\n" +
"control_path = %(directory)s/%%h-%%p-%%r\n" +
"pipelining=true";
try (OutputStream out = new FileOutputStream(path)) {
out.write(template.getBytes());
}
log.info("createAnsibleCfgFile ['{}'] -> done", path);
}
private static class Echo implements Appendable {
private StringBuilder buffer = new StringBuilder();
@Override
public Appendable append(CharSequence csq) throws IOException {
buffer.append(csq);
maybePrint();
return this;
}
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
buffer.append(csq, start, end);
maybePrint();
return this;
}
@Override
public Appendable append(char c) throws IOException {
buffer.append(c);
maybePrint();
return this;
}
private void maybePrint() {
int len = buffer.length();
int idx = buffer.indexOf("\n");
if (idx < 0) {
return;
}
log.info("OUTPUT: {}", buffer.subSequence(0, idx));
int start = idx + 1;
if (start < len) {
buffer = new StringBuilder(buffer.subSequence(start, len));
} else {
buffer = new StringBuilder();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy