All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.orchestra.plugins.ansible.RunPlaybookTask Maven / Gradle / Ivy

There is a newer version: 0.1.6
Show newest version
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";

    @Override
    public String getKey() {
        return "ansible";
    }

    @Override
    public void execute(ExecutionContext ctx) throws Exception {
        Collection hosts = (Collection) getObject(ctx, HOSTS_KEY, null);
        if (hosts == null || hosts.isEmpty()) {
            log.warn("execution -> no hosts were specified for this task, skipping");
            return;
        }

        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);
        Collection extraKeys = (Collection) getObject(ctx, EXTRA_VARS_KEY, null);

        String extraVars = formatExtraVars(ctx, extraKeys);

        createAnsibleCfgFile(System.getProperty("user.home") + "/.ansible.cfg");

        // TODO secure store?
        PasswordCallback callback = (x) -> ((String) x.getVariable(USER_PASSWORD_KEY)).getBytes();

        // TODO prepare a script instead of passing everything as args
        String hostsDir = makeHostsDir(hostsFileTemplate, hosts, user, callback.getPassword(ctx));
        String[] cmd = formatCmd(hostsDir, playbooksPath, playbook, user, extraVars);
        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 String formatExtraVars(ExecutionContext ctx, Collection keys) throws IOException {
        if (keys == null || keys.isEmpty()) {
            return "";
        }

        Set ks = new HashSet<>(keys);
        Map m = new HashMap<>();

        for (Map.Entry e : ctx.getVariables().entrySet()) {
            String k = e.getKey();
            if (!ks.contains(k)) {
                continue;
            }

            Object v = e.getValue();
            if (v == null) {
                continue;
            }

            m.put(k, v.toString());
        }

        return new ObjectMapper().writeValueAsString(m);
    }

    private static String makeHostsDir(String template, Collection hosts, String user, byte[] passwd) throws IOException {
        String p = new String(passwd);

        StringBuilder hs = new StringBuilder();
        for (String h : hosts) {
            hs.append(h).append(" ansible_ssh_user=").append(user).append(" ansible_ssh_pass=").append(p).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) {
        return new String[]{
                "ansible-playbook", "-T", "30", "-i", hostsPath, playbooksPath + "/" + playbook, "-u", user, "-e", extraVars
        };
    }

    private static void createAnsibleCfgFile(String path) throws IOException {
        String template = "[defaults]\n" +
                "host_key_checking = False\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