com.floragunn.searchguard.tools.SearchGuardAdmin.java Source code

Java tutorial

Introduction

Here is the source code for com.floragunn.searchguard.tools.SearchGuardAdmin.java

Source

/*
 * Copyright 2015 floragunn UG (haftungsbeschrnkt)
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */

package com.floragunn.searchguard.tools;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Locale;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.elasticsearch.action.WriteConsistencyLevel;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest;
import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
import org.elasticsearch.action.admin.cluster.settings.ClusterUpdateSettingsRequest;
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksRequest;
import org.elasticsearch.action.admin.cluster.tasks.PendingClusterTasksResponse;
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsResponse;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.loader.JsonSettingsLoader;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.IndexNotFoundException;

import com.floragunn.searchguard.SearchGuardPlugin;
import com.floragunn.searchguard.action.configupdate.ConfigUpdateAction;
import com.floragunn.searchguard.action.configupdate.ConfigUpdateRequest;
import com.floragunn.searchguard.action.configupdate.ConfigUpdateResponse;
import com.floragunn.searchguard.ssl.SearchGuardSSLPlugin;
import com.floragunn.searchguard.ssl.util.SSLConfigConstants;
import com.floragunn.searchguard.support.ConfigConstants;
import com.google.common.io.Files;

public class SearchGuardAdmin {

    private static final String SG_TS_PASS = "SG_TS_PASS";
    private static final String SG_KS_PASS = "SG_KS_PASS";
    //not used in multithreaded fashion
    private static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MMM-dd_HH-mm-ss", Locale.ENGLISH);
    private static final Settings ENABLE_ALL_ALLOCATIONS_SETTINGS = Settings.builder()
            .put("cluster.routing.allocation.enable", "all").build();

    public static void main(final String[] args) {
        try {
            main0(args);
        } catch (NoNodeAvailableException e) {
            System.out.println(
                    "ERR: Cannot connect to elasticsearch. Please refer to elasticsearch logfile for more information");
            System.out.println("Trace:");
            e.printStackTrace();
            System.exit(-1);
        } catch (IndexNotFoundException e) {
            System.out.println(
                    "ERR: No searchguard configuartion index found. Pls. execute sgadmin with different command line parameters");
            System.out.println("When you run it for the first time to not specify -us, -era, -dra or -rl");
            System.out.println(
                    "For more informations look here: https://github.com/floragunncom/search-guard/issues/228");
            System.exit(-1);
        } catch (Exception e) {
            System.out
                    .println("ERR: An unexpected " + e.getClass().getSimpleName() + " occured: " + e.getMessage());
            System.out.println("Trace:");
            e.printStackTrace();
            System.exit(-1);
        }
    }

    private static void main0(final String[] args) throws Exception {

        System.setProperty("sg.nowarn.client", "true");

        final HelpFormatter formatter = new HelpFormatter();
        Options options = new Options();
        options.addOption("nhnv", "disable-host-name-verification", false, "Disable hostname verification");
        options.addOption("nrhn", "disable-resolve-hostname", false, "Disable hostname beeing resolved");
        options.addOption(Option.builder("ts").longOpt("truststore").hasArg().argName("file").required()
                .desc("Path to truststore (JKS/PKCS12 format)").build());
        options.addOption(Option.builder("ks").longOpt("keystore").hasArg().argName("file").required()
                .desc("Path to keystore (JKS/PKCS12 format").build());
        options.addOption(Option.builder("tst").longOpt("truststore-type").hasArg().argName("type")
                .desc("JKS or PKCS12, if not given use file ext. to dectect type").build());
        options.addOption(Option.builder("kst").longOpt("keystore-type").hasArg().argName("type")
                .desc("JKS or PKCS12, if not given use file ext. to dectect type").build());
        options.addOption(Option.builder("tspass").longOpt("truststore-password").hasArg().argName("password")
                .desc("Truststore password").build());
        options.addOption(Option.builder("kspass").longOpt("keystore-password").hasArg().argName("password")
                .desc("Keystore password").build());
        options.addOption(Option.builder("cd").longOpt("configdir").hasArg().argName("directory")
                .desc("Directory for config files").build());
        options.addOption(Option.builder("h").longOpt("hostname").hasArg().argName("host")
                .desc("Elasticsearch host").build());
        options.addOption(Option.builder("p").longOpt("port").hasArg().argName("port")
                .desc("Elasticsearch transport port (normally 9300)").build());
        options.addOption(Option.builder("cn").longOpt("clustername").hasArg().argName("clustername")
                .desc("Clustername").build());
        options.addOption("sniff", "enable-sniffing", false, "Enable client.transport.sniff");
        options.addOption("icl", "ignore-clustername", false, "Ignore clustername");
        options.addOption(Option.builder("r").longOpt("retrieve").desc("retrieve current config").build());
        options.addOption(Option.builder("f").longOpt("file").hasArg().argName("file").desc("file").build());
        options.addOption(
                Option.builder("t").longOpt("type").hasArg().argName("file-type").desc("file-type").build());
        options.addOption(Option.builder("tsalias").longOpt("truststore-alias").hasArg().argName("alias")
                .desc("Truststore alias").build());
        options.addOption(Option.builder("ksalias").longOpt("keystore-alias").hasArg().argName("alias")
                .desc("Keystore alias").build());
        options.addOption(Option.builder("ec").longOpt("enabled-ciphers").hasArg().argName("cipers")
                .desc("Comma separated list of TLS ciphers").build());
        options.addOption(Option.builder("ep").longOpt("enabled-protocols").hasArg().argName("protocols")
                .desc("Comma separated list of TLS protocols").build());
        //TODO mark as deprecated and replace it with "era" if "era" is mature enough
        options.addOption(Option.builder("us").longOpt("update_settings").hasArg().argName("number of replicas")
                .desc("update the number of replicas and reload configuration on all nodes and exit").build());
        options.addOption(Option.builder("i").longOpt("index").hasArg().argName("indexname")
                .desc("The index Searchguard uses to store its configs in").build());
        options.addOption(Option.builder("era").longOpt("enable-replica-autoexpand")
                .desc("enable replica auto expand and exit").build());
        options.addOption(Option.builder("dra").longOpt("disable-replica-autoexpand")
                .desc("disable replica auto expand and exit").build());
        options.addOption(
                Option.builder("rl").longOpt("reload").desc("reload configuration on all nodes and exit").build());
        options.addOption(
                Option.builder("ff").longOpt("fail-fast").desc("fail-fast if something goes wrong").build());
        options.addOption(
                Option.builder("dg").longOpt("diagnose").desc("Log diagnostic trace into a file").build());
        options.addOption(Option.builder("dci").longOpt("delete-config-index")
                .desc("Delete 'searchguard' config index and exit.").build());
        options.addOption(Option.builder("esa").longOpt("enable-shard-allocation")
                .desc("Enable all shard allocation and exit.").build());

        String hostname = "localhost";
        int port = 9300;
        String kspass = System.getenv(SG_KS_PASS) != null ? System.getenv(SG_KS_PASS) : "changeit";
        String tspass = System.getenv(SG_TS_PASS) != null ? System.getenv(SG_TS_PASS) : kspass;
        String cd = ".";
        String ks;
        String ts;
        String kst = null;
        String tst = null;
        boolean nhnv = false;
        boolean nrhn = false;
        boolean sniff = false;
        boolean icl = false;
        String clustername = "elasticsearch";
        String file = null;
        String type = null;
        boolean retrieve = false;
        String ksAlias = null;
        String tsAlias = null;
        String[] enabledProtocols = new String[0];
        String[] enabledCiphers = new String[0];
        Integer updateSettings = null;
        String index = ConfigConstants.SG_DEFAULT_CONFIG_INDEX;
        Boolean replicaAutoExpand = null;
        boolean reload = false;
        boolean failFast = false;
        boolean diagnose = false;
        boolean deleteConfigIndex = false;
        boolean enableShardAllocation = false;

        CommandLineParser parser = new DefaultParser();
        try {
            CommandLine line = parser.parse(options, args);
            hostname = line.getOptionValue("h", hostname);
            port = Integer.parseInt(line.getOptionValue("p", String.valueOf(port)));
            kspass = line.getOptionValue("kspass", kspass); //TODO null? //when no passwd is set
            tspass = line.getOptionValue("tspass", tspass); //TODO null? //when no passwd is set
            cd = line.getOptionValue("cd", cd);

            if (!cd.endsWith(File.separator)) {
                cd += File.separator;
            }

            ks = line.getOptionValue("ks");
            ts = line.getOptionValue("ts");
            kst = line.getOptionValue("kst", kst);
            tst = line.getOptionValue("tst", tst);
            nhnv = line.hasOption("nhnv");
            nrhn = line.hasOption("nrhn");
            clustername = line.getOptionValue("cn", clustername);
            sniff = line.hasOption("sniff");
            icl = line.hasOption("icl");
            file = line.getOptionValue("f", file);
            type = line.getOptionValue("t", type);
            retrieve = line.hasOption("r");
            ksAlias = line.getOptionValue("ksalias", ksAlias);
            tsAlias = line.getOptionValue("tsalias", tsAlias);
            index = line.getOptionValue("i", index);

            String enabledCiphersString = line.getOptionValue("ec", null);
            String enabledProtocolsString = line.getOptionValue("ep", null);

            if (enabledCiphersString != null) {
                enabledCiphers = enabledCiphersString.split(",");
            }

            if (enabledProtocolsString != null) {
                enabledProtocols = enabledProtocolsString.split(",");
            }

            updateSettings = line.hasOption("us") ? Integer.parseInt(line.getOptionValue("us")) : null;

            reload = line.hasOption("rl");

            if (line.hasOption("era")) {
                replicaAutoExpand = true;
            }

            if (line.hasOption("dra")) {
                replicaAutoExpand = false;
            }

            failFast = line.hasOption("ff");
            diagnose = line.hasOption("dg");
            deleteConfigIndex = line.hasOption("dci");
            enableShardAllocation = line.hasOption("esa");

        } catch (ParseException exp) {
            System.err.println("ERR: Parsing failed.  Reason: " + exp.getMessage());
            formatter.printHelp("sgadmin.sh", options, true);
            return;
        }

        if (port < 9300) {
            System.out.println("WARNING: Seems you want connect to the a HTTP port." + System.lineSeparator()
                    + "         sgadmin connect through the transport port which is normally 9300.");
        }

        System.out.print("Will connect to " + hostname + ":" + port);
        Socket socket = new Socket();

        try {

            socket.connect(new InetSocketAddress(hostname, port));

        } catch (java.net.ConnectException ex) {
            System.out.println();
            System.out.println(
                    "ERR: Seems there is no elasticsearch running on " + hostname + ":" + port + " - Will exit");
            System.exit(-1);
        } finally {
            try {
                socket.close();
            } catch (Exception e) {
                //ignore
            }
        }

        System.out.println(" ... done");

        final Settings.Builder settingsBuilder = Settings.builder().put("path.home", ".").put("path.conf", ".")
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_FILEPATH, ks)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_FILEPATH, ts)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_PASSWORD, kspass)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_PASSWORD, tspass)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION, !nhnv)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENFORCE_HOSTNAME_VERIFICATION_RESOLVE_HOST_NAME,
                        !nrhn)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENABLED, true)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_TYPE,
                        kst == null ? (ks.endsWith(".jks") ? "JKS" : "PKCS12") : kst)
                .put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_TYPE,
                        tst == null ? (ts.endsWith(".jks") ? "JKS" : "PKCS12") : tst)

                .putArray(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENABLED_CIPHERS, enabledCiphers)
                .putArray(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_ENABLED_PROTOCOLS, enabledProtocols)

                .put("cluster.name", clustername).put("client.transport.ignore_cluster_name", icl)
                .put("client.transport.sniff", sniff);

        if (ksAlias != null) {
            settingsBuilder.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_KEYSTORE_ALIAS, ksAlias);
        }

        if (tsAlias != null) {
            settingsBuilder.put(SSLConfigConstants.SEARCHGUARD_SSL_TRANSPORT_TRUSTSTORE_ALIAS, tsAlias);
        }

        Settings settings = settingsBuilder.build();

        try (TransportClient tc = TransportClient.builder().settings(settings).addPlugin(SearchGuardSSLPlugin.class)
                .addPlugin(SearchGuardPlugin.class) //needed for config update action only
                .build()
                .addTransportAddress(new InetSocketTransportAddress(new InetSocketAddress(hostname, port)))) {

            if (updateSettings != null) {
                Settings indexSettings = Settings.builder().put("index.number_of_replicas", updateSettings).build();
                tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(
                        new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }))
                        .actionGet();
                final UpdateSettingsResponse response = tc.admin().indices()
                        .updateSettings((new UpdateSettingsRequest(index).settings(indexSettings))).actionGet();
                System.out.println("Reload config on all nodes");
                System.out.println("Update number of replicas to " + (updateSettings) + " with result: "
                        + response.isAcknowledged());
                System.exit(response.isAcknowledged() ? 0 : -1);
            }

            if (reload) {
                tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(
                        new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }))
                        .actionGet();
                System.out.println("Reload config on all nodes");
                System.exit(0);
            }

            if (replicaAutoExpand != null) {
                Settings indexSettings = Settings.builder()
                        .put("index.auto_expand_replicas", replicaAutoExpand ? "0-all" : "false").build();
                tc.execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(
                        new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }))
                        .actionGet();
                final UpdateSettingsResponse response = tc.admin().indices()
                        .updateSettings((new UpdateSettingsRequest(index).settings(indexSettings))).actionGet();
                System.out.println("Reload config on all nodes");
                System.out.println("Auto-expand replicas " + (replicaAutoExpand ? "enabled" : "disabled"));
                System.exit(response.isAcknowledged() ? 0 : -1);
            }

            if (enableShardAllocation) {
                final boolean successful = tc.admin().cluster()
                        .updateSettings(new ClusterUpdateSettingsRequest()
                                .transientSettings(ENABLE_ALL_ALLOCATIONS_SETTINGS)
                                .persistentSettings(ENABLE_ALL_ALLOCATIONS_SETTINGS))
                        .actionGet().isAcknowledged();

                if (successful) {
                    System.out.println("Persistent and transient shard allocation enabled");
                } else {
                    System.out.println("ERR: Unable to enable shard allocation");
                }

                System.exit(successful ? 0 : -1);
            }

            if (failFast) {
                System.out.println("Failfast is activated");
            }

            if (diagnose) {
                generateDiagnoseTrace(tc);
            }

            System.out.println(
                    "Contacting elasticsearch cluster '" + clustername + "' and wait for YELLOW clusterstate ...");

            ClusterHealthResponse chr = null;

            while (chr == null) {
                try {
                    chr = tc.admin().cluster().health(
                            new ClusterHealthRequest().timeout(TimeValue.timeValueMinutes(5)).waitForYellowStatus())
                            .actionGet();
                } catch (Exception e) {
                    if (!failFast) {
                        System.out.println("Cannot retrieve cluster state due to: " + e.getMessage()
                                + ". This is not an error, will keep on trying ...");
                        System.out.println(
                                "   * Try running sgadmin.sh with -icl and -nhnv (If thats works you need to check your clustername as well as hostnames in your SSL certificates)");
                        System.out.println(
                                "   * If this is not working, try running sgadmin.sh with --diagnose and see diagnose trace log file)");

                    } else {
                        System.out.println("ERR: Cannot retrieve cluster state due to: " + e.getMessage() + ".");
                        System.out.println(
                                "   * Try running sgadmin.sh with -icl and -nhnv (If thats works you need to check your clustername as well as hostnames in your SSL certificates)");
                        System.out.println(
                                "   * If this is not working, try running sgadmin.sh with --diagnose and see diagnose trace log file)");

                        System.exit(-1);
                    }

                    Thread.sleep(3000);
                    continue;
                }
            }

            final boolean timedOut = chr.isTimedOut();

            if (timedOut) {
                System.out.println("ERR: Timed out while waiting for a green or yellow cluster state.");
                System.out.println(
                        "   * Try running sgadmin.sh with -icl and -nhnv (If thats works you need to check your clustername as well as hostnames in your SSL certificates)");
                System.exit(-1);
            }

            System.out.println("Clustername: " + chr.getClusterName());
            System.out.println("Clusterstate: " + chr.getStatus());
            System.out.println("Number of nodes: " + chr.getNumberOfNodes());
            System.out.println("Number of data nodes: " + chr.getNumberOfDataNodes());

            final boolean indexExists = tc.admin().indices().exists(new IndicesExistsRequest(index)).actionGet()
                    .isExists();

            final NodesInfoResponse nodesInfo = tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet();

            if (deleteConfigIndex) {

                boolean success = true;

                if (indexExists) {
                    success = tc.admin().indices().delete(new DeleteIndexRequest(index)).actionGet()
                            .isAcknowledged();
                    System.out.print("Deleted index '" + index + "'");
                } else {
                    System.out.print("No index '" + index + "' exists, so no need to delete it");
                }

                System.exit(success ? 0 : -1);
            }

            if (!indexExists) {
                System.out.print(index + " index does not exists, attempt to create it ... ");
                int replicas = chr.getNumberOfDataNodes() - 1;
                final boolean indexCreated = tc.admin().indices().create(new CreateIndexRequest(index)
                        // .mapping("config", source)
                        // .settings(settings)
                        //TODO "index.auto_expand_replicas", "0-all"
                        .settings("index.number_of_shards", 1, "index.number_of_replicas", replicas)).actionGet()
                        .isAcknowledged();

                if (indexCreated) {
                    System.out.println("done (with " + replicas + " replicas, auto expand replicas is off)");
                } else {
                    System.out.println("failed!");
                    System.out.println("FAIL: Unable to create the " + index
                            + " index. See elasticsearch logs for more details");
                    System.exit(-1);
                }

            } else {
                System.out.println(index + " index already exists, so we do not need to create one.");

                try {
                    ClusterHealthResponse chrsg = tc.admin().cluster().health(new ClusterHealthRequest(index))
                            .actionGet();

                    if (chrsg.isTimedOut()) {
                        System.out.println("ERR: Timed out while waiting for " + index + " index state.");
                    }

                    if (chrsg.getStatus() == ClusterHealthStatus.RED) {
                        System.out.println("ERR: " + index + " index state is RED.");
                    }

                    if (chrsg.getStatus() == ClusterHealthStatus.YELLOW) {
                        System.out.println(
                                "INFO: " + index + " index state is YELLOW, it seems you miss some replicas");
                    }

                } catch (Exception e) {
                    if (!failFast) {
                        System.out.println("Cannot retrieve " + index + " index state state due to "
                                + e.getMessage() + ". This is not an error, will keep on trying ...");
                    } else {
                        System.out.println("ERR: Cannot retrieve " + index + " index state state due to "
                                + e.getMessage() + ".");
                        System.exit(-1);
                    }
                }
            }

            if (retrieve) {
                String date = DATE_FORMAT.format(new Date());

                boolean success = retrieveFile(tc, cd + "sg_config_" + date + ".yml", index, "config");
                success = success & retrieveFile(tc, cd + "sg_roles_" + date + ".yml", index, "roles");
                success = success
                        & retrieveFile(tc, cd + "sg_roles_mapping_" + date + ".yml", index, "rolesmapping");
                success = success
                        & retrieveFile(tc, cd + "sg_internal_users_" + date + ".yml", index, "internalusers");
                success = success
                        & retrieveFile(tc, cd + "sg_action_groups_" + date + ".yml", index, "actiongroups");
                System.exit(success ? 0 : -1);
            }

            boolean isCdAbs = new File(cd).isAbsolute();

            System.out.println("Populate config from " + (isCdAbs ? cd : new File(".", cd).getCanonicalPath()));

            if (file != null) {
                if (type == null) {
                    System.out.println("ERR: type missing");
                    System.exit(-1);
                }

                if (!Arrays
                        .asList(new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" })
                        .contains(type)) {
                    System.out.println("ERR: Invalid type '" + type + "'");
                    System.exit(-1);
                }

                boolean success = uploadFile(tc, file, index, type);
                System.exit(success ? 0 : -1);
            }

            boolean success = uploadFile(tc, cd + "sg_config.yml", index, "config");
            success = success & uploadFile(tc, cd + "sg_roles.yml", index, "roles");
            success = success & uploadFile(tc, cd + "sg_roles_mapping.yml", index, "rolesmapping");
            success = success & uploadFile(tc, cd + "sg_internal_users.yml", index, "internalusers");
            success = success & uploadFile(tc, cd + "sg_action_groups.yml", index, "actiongroups");

            if (failFast && !success) {
                System.out.println("ERR: cannot upload configuration, see errors above");
                System.exit(-1);
            }

            ConfigUpdateResponse cur = tc
                    .execute(ConfigUpdateAction.INSTANCE, new ConfigUpdateRequest(
                            new String[] { "config", "roles", "rolesmapping", "internalusers", "actiongroups" }))
                    .actionGet();

            success = success & checkConfigUpdateResponse(cur, nodesInfo, 5);

            System.out.println("Done with " + (success ? "success" : "failures"));
            System.exit(success ? 0 : -1);
        }
        // TODO audit changes to searchguard index
    }

    private static boolean checkConfigUpdateResponse(ConfigUpdateResponse response, NodesInfoResponse nir,
            int expectedConfigCount) {

        int expectedNodeCount = 0;

        for (NodeInfo ni : nir) {
            Settings nodeSettings = ni.getSettings();

            //do not count tribe clients
            if (nodeSettings.get("tribe.name", null) == null) {
                expectedNodeCount++;
            }
        }

        boolean success = response.getNodes().length == expectedNodeCount;
        if (!success) {
            System.out.println("FAIL: Expected " + expectedNodeCount + " nodes to return response, but got only "
                    + response.getNodes().length);
        }

        for (String nodeId : response.getNodesMap().keySet()) {
            ConfigUpdateResponse.Node node = (ConfigUpdateResponse.Node) response.getNodesMap().get(nodeId);
            boolean successNode = (node.getUpdatedConfigTypes() != null
                    && node.getUpdatedConfigTypes().length == expectedConfigCount);

            if (!successNode) {
                System.out.println("FAIL: Expected " + expectedConfigCount + " config types for node " + nodeId
                        + " but got only " + Arrays.toString(node.getUpdatedConfigTypes()) + " due to: "
                        + node.getMessage() == null ? "unknown reason" : node.getMessage());
            }

            success = success & successNode;
        }

        return success;
    }

    private static boolean uploadFile(Client tc, String filepath, String index, String type) {
        System.out.println("Will update '" + type + "' with " + filepath);
        try (Reader reader = new FileReader(filepath)) {

            final String id = tc.index(new IndexRequest(index).type(type).id("0").refresh(true)
                    .consistencyLevel(WriteConsistencyLevel.DEFAULT)
                    .source(readXContent(reader, XContentType.YAML))).actionGet().getId();

            if ("0".equals(id)) {
                System.out.println("   SUCC: Configuration for '" + type + "' created or updated");
                return true;
            } else {
                System.out.println("   FAIL: Configuration for '" + type
                        + "' failed for unknown reasons. Pls. consult logfile of elasticsearch");
            }
        } catch (Exception e) {
            System.out.println("   FAIL: Configuration for '" + type + "' failed because of " + e.toString());
        }

        return false;
    }

    private static boolean retrieveFile(Client tc, String filepath, String index, String type) {
        System.out.println("Will retrieve '" + type + "' into " + filepath);
        try (Writer writer = new FileWriter(filepath)) {

            final GetResponse response = tc
                    .get(new GetRequest(index).type(type).id("0").refresh(true).realtime(false)).actionGet();

            if (response.isExists()) {
                if (response.isSourceEmpty()) {
                    System.out.println("   FAIL: Configuration for '" + type + "' failed because of empty source");
                    return false;
                }

                String yaml = convertToYaml(response.getSourceAsBytesRef(), true);
                writer.write(yaml);
                System.out.println("   SUCC: Configuration for '" + type + "' stored in " + filepath);
                return true;
            } else {
                System.out.println("   FAIL: Get configuration for '" + type + "' because it does not exist");
            }
        } catch (Exception e) {
            System.out.println("   FAIL: Get configuration for '" + type + "' failed because of " + e.toString());
        }

        return false;
    }

    private static BytesReference readXContent(final Reader reader, final XContentType xContentType)
            throws IOException {
        BytesReference retVal;
        XContentParser parser = null;
        try {
            parser = XContentFactory.xContent(xContentType).createParser(reader);
            parser.nextToken();
            final XContentBuilder builder = XContentFactory.jsonBuilder();
            builder.copyCurrentStructure(parser);
            retVal = builder.bytes();
        } finally {
            if (parser != null) {
                parser.close();
            }
        }

        //validate
        Settings.builder().put(new JsonSettingsLoader().load(XContentHelper.createParser(retVal))).build();
        return retVal;
    }

    private static String convertToYaml(BytesReference bytes, boolean prettyPrint) throws IOException {
        try (XContentParser parser = XContentFactory.xContent(XContentFactory.xContentType(bytes))
                .createParser(bytes.streamInput())) {
            parser.nextToken();
            XContentBuilder builder = XContentFactory.yamlBuilder();
            if (prettyPrint) {
                builder.prettyPrint();
            }
            builder.copyCurrentStructure(parser);
            return builder.string();
        }
    }

    protected static void generateDiagnoseTrace(final Client tc) {

        final String date = DATE_FORMAT.format(new Date());

        final StringBuilder sb = new StringBuilder();
        sb.append("Diagnostic sgadmin trace" + System.lineSeparator());
        sb.append("ES client version: " + Version.CURRENT + System.lineSeparator());
        sb.append("Client properties: " + System.getProperties() + System.lineSeparator());
        sb.append(date + System.lineSeparator());
        sb.append(System.lineSeparator());

        try {
            sb.append("ClusterHealthRequest:" + System.lineSeparator());
            ClusterHealthResponse nir = tc.admin().cluster().health(new ClusterHealthRequest()).actionGet();
            sb.append(XContentHelper.toString(nir));
        } catch (Exception e1) {
            sb.append(ExceptionsHelper.stackTrace(e1));
        }

        try {
            sb.append(System.lineSeparator() + "NodesInfoResponse:" + System.lineSeparator());
            NodesInfoResponse nir = tc.admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet();
            sb.append(XContentHelper.toString(nir));
        } catch (Exception e1) {
            sb.append(ExceptionsHelper.stackTrace(e1));
        }

        try {
            sb.append(System.lineSeparator() + "NodesStatsRequest:" + System.lineSeparator());
            NodesStatsResponse nir = tc.admin().cluster().nodesStats(new NodesStatsRequest()).actionGet();
            sb.append(XContentHelper.toString(nir));
        } catch (Exception e1) {
            sb.append(ExceptionsHelper.stackTrace(e1));
        }

        try {
            sb.append(System.lineSeparator() + "PendingClusterTasksRequest:" + System.lineSeparator());
            PendingClusterTasksResponse nir = tc.admin().cluster()
                    .pendingClusterTasks(new PendingClusterTasksRequest()).actionGet();
            sb.append(XContentHelper.toString(nir));
        } catch (Exception e1) {
            sb.append(ExceptionsHelper.stackTrace(e1));
        }

        try {
            sb.append(System.lineSeparator() + "ClusterStateRequest:" + System.lineSeparator());
            ClusterStateResponse nir = tc.admin().cluster().state(new ClusterStateRequest()).actionGet();
            sb.append(XContentHelper.toString(nir.getState()));
        } catch (Exception e1) {
            sb.append(ExceptionsHelper.stackTrace(e1));
        }

        try {
            sb.append(System.lineSeparator() + "IndicesStatsRequest:" + System.lineSeparator());
            IndicesStatsResponse nir = tc.admin().indices().stats(new IndicesStatsRequest()).actionGet();
            sb.append(XContentHelper.toString(nir));
        } catch (Exception e1) {
            sb.append(ExceptionsHelper.stackTrace(e1));
        }

        try {
            File dfile = new File("sgadmin_diag_trace_" + date + ".txt");
            Files.write(sb, dfile, Charset.forName("UTF-8"));
            System.out.println("Diagnostic trace written to: " + dfile.getAbsolutePath());
        } catch (Exception e1) {
            System.out.println("ERR: cannot write diag trace file due to " + e1);
        }
    }
}