public class DashboardSerializer {

    private final ObjectMapper mapper;

    /**
     * Default constructor, which initialises Jackson ObjectMapper with pretty print option.
     */
    public DashboardSerializer() {
        mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);
    }

    /**
     * Gets the String representation of the given dashboard.
     * The output is pretty printed.
     * @param dashboard dashboard to turn into String
     * @return String (JSON) representation of dashboard
     * @throws IllegalArgumentException when the dashboard cannot be serialised
     */
    public String toString(Dashboard dashboard) {
        try {
            return mapper.writeValueAsString(dashboard);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("dashboard cannot be serialized to JSON." +
                    "It might contain non-serializable values.", e);
        }
    }

    /**
     * Writes the pretty printed JSON representation of a dashboard with the given {@link Writer}.
     * @param dashboard dashboard to be serialised
     * @param writer writer to be written with
     * @throws IOException when "writer" throws IOException
     * @throws IllegalArgumentException when the dashboard cannot be serialised
     */
    public void write(Dashboard dashboard, Writer writer) throws IOException {
        try {
            mapper.writeValue(writer, dashboard);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("dashboard cannot be serialized to JSON." +
                    "It might contain non-serializable values.", e);
        }
    }

    /**
     * Writes the pretty printed JSON representation of a dashboard on the given stream.
     * @param dashboard dashboard to be serialised
     * @param stream stream to be written on
     * @throws IOException when "stream" throws IOException
     * @throws IllegalArgumentException when the dashboard cannot be serialised
     */
    public void write(Dashboard dashboard, OutputStream stream) throws IOException {
        try {
            mapper.writeValue(stream, dashboard);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("dashboard cannot be serialized to JSON." +
                    "It might contain non-serializable values.", e);
        }
    }

} public class DashboardUploader {

    private GrafanaClient client;
    private DashboardSerializer dashboardSerializer;

    /**
     * Constructor that sets the Grafana endpoint. It also creates the client,
     * but no connection is opened at this point.
     *
     * @param endpoint Grafana endpoint to be used
     */
    public DashboardUploader(GrafanaEndpoint endpoint) {
        client = new GrafanaClient(endpoint);
        dashboardSerializer = new DashboardSerializer();
    }

    /**
     * Serializes and uploads a dashboard.
     *
     * Throws exceptions if anything is wrong.
     *
     * @param dashboard dashboard to upload
     * @param overwrite should anything be overwritten?
     */
    public void upload(Dashboard dashboard, boolean overwrite) {
        if (dashboard.getTitle() == null) {
            throw new IllegalArgumentException("Dashboard must have a title.");
        }

        String dashboardJson = dashboardSerializer.toString(dashboard);
        try {
            client.uploadDashboard(dashboardJson, overwrite);
        } catch (IOException e) {
            throw new RuntimeException(String.format("Error happened during uploading [%s]", dashboard.getTitle()), e);
        }
    }
} public class GrafanaClient {

    private static final String USER_AGENT = "NaiveGrafanaClient";
    private static final String API_DASHBOARDS_PATH = "api/dashboards/db";
    private GrafanaEndpoint endpoint;

    /**
     * Constructor that sets the Grafana endpoint.
     *
     * @param endpoint Grafana endpoint to be used
     */
    public GrafanaClient(GrafanaEndpoint endpoint) {
        this.endpoint = endpoint;
    }

    /**
     * Uploads a dashboard to Grafana.
     *
     * If a dashboard with the same title exists, it will either be overwritten,
     * when {@code overwrite} parameter is {@code true}, or an UnexpectedGrafanaResponseException
     * is thrown.
     *
     * It also handles authentication based on the endpoint set by the constructor.
     *
     * It's thread-safe.
     *
     * @param dashboard JSON representation of the dashboard
     * @param overwrite should an existing dashboard with the same name be overwritten?
     * @throws IOException when some error happens, e.g., {@link java.net.MalformedURLException}
     * @throws UnexpectedGrafanaResponseException when response code is not in the 2xx zone
     */
    public void uploadDashboard(String dashboard, boolean overwrite) throws IOException {
        String url = getUrl(API_DASHBOARDS_PATH);

        // poor man's json builder
        String content = "{\"overwrite\": " + overwrite + ", \"dashboard\": " + dashboard + "}";
        postToGrafana(url, content);
    }

    /**
     * Do a Grafana tailored POST with the given content and returns response code.
     * Throws UnexpectedGrafanaResponseException if response code is not in the 2xx
     * domain.
     *
     * @param url full URL, with protocol and stuff
     * @param content POST payload to send
     * @return response code if it is 2xx
     * @throws IOException when some error happens, e.g., {@link java.net.MalformedURLException}
     * @throws UnexpectedGrafanaResponseException when response code is not in the 2xx zone
     */
    protected int postToGrafana(String url, String content) throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
        if (endpoint.isSkipSSLValidation() && conn instanceof HttpsURLConnection) {
            disableSslCertValidation((HttpsURLConnection) conn);
        }

        conn.setRequestMethod("POST");
        conn.setRequestProperty("User-Agent", USER_AGENT);
        conn.setRequestProperty("Accept", "application/json");
        conn.setRequestProperty("Content-Type", "application/json");
        addAuth(conn);
        conn.setDoOutput(true);
        try (OutputStream stream = conn.getOutputStream()) {
            stream.write(content.getBytes());
            stream.flush();
        }

        int responseCode = conn.getResponseCode();

        if (responseCode / 100 == 2) {
            return responseCode;
        } else {
            String response;
            try {
                response = readResponse(conn);
            } catch (IOException ex) {
                response = "Cannot read response; " + ex.getMessage();
            }
            throw new UnexpectedGrafanaResponseException(
                    String.format("Unexpected response from [%s]; responseCode: [%d]; response: [%s]", url, responseCode, response),
                    responseCode,
                    response
            );
        }
    }

    /**
     * Adds authentication headers to request if needed.
     * @param conn connection
     */
    protected void addAuth(HttpURLConnection conn) {
        if (endpoint.getApiKey() != null) {
            conn.setRequestProperty("Authorization", "Bearer " + endpoint.getApiKey());
        } else if (endpoint.getSessionCookie() != null) {
            conn.setRequestProperty("Cookie", "grafana_sess=" + endpoint.getSessionCookie());
        }
    }

    /**
     * Gets URL of the given path, relative to the Grafana endpoint.
     * @param path path, relative to the Grafana endpoint
     * @return absolute URL (as String) of the given path
     */
    protected String getUrl(String path) {
        String url = endpoint.getBaseUrl();
        if (!url.endsWith("/")) {
            url += "/";
        }
        url += path;
        return url;
    }

    private void disableSslCertValidation(HttpsURLConnection conn) {
        conn.setSSLSocketFactory(SslCertValidationSkipper.noCheckSslContext.getSocketFactory());
        conn.setHostnameVerifier(SslCertValidationSkipper.allValidHostname);
    }

    private String readResponse(HttpURLConnection conn) throws IOException {
        StringBuffer response = new StringBuffer();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                response.append(inputLine);
            }
        }
        return response.toString();
    }

    /**
     * Exception that is thrown when the status code of a Grafana request is outside of the
     * 200-299 range.
     */
    public static class UnexpectedGrafanaResponseException extends IllegalStateException {

        private int responseCode;
        private String response;

        /**
         * Constuctor that sets all required fields.
         * @param msg the message
         * @param responseCode the HTTP status code of the Grafana response
         * @param response the HTTP status line of the Grafana response
         */
        public UnexpectedGrafanaResponseException(String msg, int responseCode, String response) {
            super(msg);
            this.responseCode = responseCode;
            this.response = response;
        }

        /**
         * Gets HTTP status code of the triggering response.
         * @return HTTP status code
         */
        public int getResponseCode() {
            return responseCode;
        }

        /**
         * Gets HTTP status line of the triggering response.
         * @return HTTP status
         */
        public String getResponse() {
            return response;
        }
    }

    private static class SslCertValidationSkipper {
        private static SSLContext noCheckSslContext;
        private static HostnameVerifier allValidHostname;

        static {
            // Create a trust manager that does not validate certificate chains
            TrustManager[] trustAllCerts = new TrustManager[] {new X509TrustManager() {
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                }
                public void checkServerTrusted(X509Certificate[] certs, String authType) {
                }
            }
            };

            // Install the all-trusting trust manager
            try {
                noCheckSslContext = SSLContext.getInstance("SSL");
                noCheckSslContext.init(null, trustAllCerts, new java.security.SecureRandom());
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException("Cannot skip SSL cert validation", e);
            } catch (KeyManagementException e) {
                throw new RuntimeException("Cannot skip SSL cert validation", e);
            }

            // Create all-trusting host name verifier
            allValidHostname = new HostnameVerifier() {
                public boolean verify(String hostname, SSLSession session) {
                    return true;
                }
            };

        }
    }
} public class ClasspathGrafanaEndpointStore extends GrafanaEndpointStore {

    private String path;

    /**
     * Creates a ClasspathGrafanaEndpointStore.
     * @param path path of the endpoint yaml file on the classpath of this class
     */
    public ClasspathGrafanaEndpointStore(String path) {
        this.path = path;
    }

    @Override
    protected InputStream getStream() {
        return getClass().getResourceAsStream(path);
    }
} public class CommandLineArgs {

    private String forcedEndpoint;
    private DashboardFilter dashboardFilter;

    public void parse(String[] args, UploaderConfig config) throws IOException {
        OptionParser parser = new OptionParser();
        parser.acceptsAll(asList("B", "batch"), "Non-interactive, batch mode");
        parser.acceptsAll(asList("f", "force"), "Overwrite existing dashboards");
        parser.acceptsAll(asList("d", "dashboard"), "Dashboards to generate; can have multiple value")
                .withRequiredArg()
                .describedAs("title");
        parser.acceptsAll(asList("i", "include"), "Dashboards to include")
                .withRequiredArg()
                .describedAs("regexp");
        parser.acceptsAll(asList("exclude"), "Dashboards to exclude")
                .withRequiredArg()
                .describedAs("regexp");
        parser.accepts("endpoints", "Endpoints YAML file")
                .withRequiredArg()
                .ofType(File.class);
        parser.acceptsAll(asList("e", "endpoint"), "Name of the endpoint to use")
                //.availableUnless("output")
                .withRequiredArg()
                .describedAs("endpoint");
        parser.acceptsAll(asList("o", "output"), "output directory -- will skip upload")
                .availableUnless("endpoint")
                .withRequiredArg()
                .ofType(File.class);
        parser.acceptsAll(asList("h", "?", "help"), "show help" )
                .forHelp();

        OptionSet options = parser.parse(args);

        if (options.has("help")) {
            parser.printHelpOn(System.out);
            throw new ExitPlease(-3);
        }

        config.setBatchMode(options.has("batch"));

        if (options.has("endpoints")) {
            File endpointsFile = (File) options.valueOf("endpoints");
            config.setEndpointStore(new FileBasedGrafanaEndpointStore(endpointsFile));
        }

        if (options.has("force")) {
            config.setOverwrite(true);
        }

        if (options.has("output")) {
            config.setEndpoint(Optional.empty());
            config.setOutputDirectory((File) options.valueOf("output"));
        }

        if (options.has("endpoint")) {
            forcedEndpoint = (String) options.valueOf("endpoint");
        }

        if (options.has("dashboard") || options.has("include") || options.has("exclude"))
        dashboardFilter = new DashboardFilter(
                toStringList(options.valuesOf("dashboard")),
                toStringList(options.valuesOf("include")),
                toStringList(options.valuesOf("exclude")));
    }

    public void apply(UploaderConfig config) {
        if (forcedEndpoint != null) {
            GrafanaEndpoint endpoint = config.getEndpointStore().getEndpoints().get(forcedEndpoint);
            if (endpoint == null) {
                System.err.println("Endpoint not found: " + forcedEndpoint);
                throw new ExitPlease(-5);
            }
            config.setEndpoint(Optional.of(endpoint));
        }

        if (dashboardFilter != null) {
            List<Dashboard> dashboards;
            try {
                dashboards = dashboardFilter.filter(config.getDashboardStore().getDashboards());
            } catch (DashboardFilter.IncludedDashboardIsNotFound ex) {
                System.err.println(ex.getMessage());
                throw new ExitPlease(-6);
            }
            config.setDashboards(dashboards);
            System.out.println("Dashboards: " + dashboards.stream()
                    .map(Dashboard::getTitle)
                    .collect(Collectors.joining(", ")));
        }
    }

    private List<String> toStringList(List<?> list) {
        return (List<String>) list;
    }


} public class DashboardStore {

    private String rootPackage;

    private List<Dashboard> dashboards;

    /**
     * Creates a DashboardStore.
     * @param rootPackage root package of the annotation based spring context to be searched
     */
    public DashboardStore(String rootPackage) {
        this.rootPackage = rootPackage;
    }

    /**
     * Loads dashboards.
     *
     * <p>This will create the Spring context and look for
     * {@link DashboardFactory} and {@link Dashboard} implementations.
     */
    public void load() {
        List<Dashboard> dashboards = new ArrayList<>();
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(rootPackage);
        Map<String, DashboardFactory> factories = applicationContext.getBeansOfType(DashboardFactory.class);

        for (DashboardFactory dashboardFactory : factories.values()) {
            dashboards.addAll(dashboardFactory.create());
        }

        Map<String, Dashboard> dashboardBeans = applicationContext.getBeansOfType(Dashboard.class);
        dashboards.addAll(dashboardBeans.values());

        this.dashboards = unmodifiableList(dashboards);
    }

    /**
     * Gets a list of dashboard titles that presents
     * more than once among the dashboards.
     *
     * <p>Grafana treats them as equal, so there is no point
     * uploading them twice.
     *
     * @return list of dashboard titles
     */
    public Collection<String> getDuplicates() {
        Set<String> all = new HashSet<>();
        Set<String> dupes = new HashSet<>();

        for (Dashboard dashboard : dashboards) {
            String title = dashboard.getTitle();
            if (all.contains(title)) {
                dupes.add(title);
            } else {
                all.add(title);
            }
        }

        return dupes;
    }

    /**
     * Gets an error message with the list of duplicated dashboards if any.
     * @return an error message if there are duplicates, otherwise {@code Optional.empty()}
     */
    public Optional<String> getDuplicatesErrorMessage() {
        Collection<String> duplicates = getDuplicates();
        String message = null;
        if (!duplicates.isEmpty()) {
            message = String.format("%d duplicates found among dashboards:", duplicates.size());

            message += duplicates.stream().collect(Collectors.joining(", "));
        }
        return Optional.ofNullable(message);
    }

    /**
     * Gets all dashboards.
     * @return list of dashboards
     */
    public List<Dashboard> getDashboards() {
        return dashboards;
    }
} public class ExitPlease extends IllegalStateException {

    private int returnCode;

    public ExitPlease(int returnCode) {
        this.returnCode = returnCode;
    }

    public int getReturnCode() {
        return returnCode;
    }
} public abstract class GrafanaEndpointStore {

    private Map<String, GrafanaEndpoint> endpoints;

    /**
     * Loads endpoints.
     * @throws IOException when cannot read the underlying stream
     */
    public void load() throws IOException {
        try (InputStream stream = getStream()) {
            if (stream != null) {
                endpoints = new GrafanaEndpointParser().parse(stream);
            } else {
                endpoints = Collections.emptyMap();
            }
        }
    }

    /**
     * Gets endpoints by their names.
     * @return endpoints, indexed by their names
     */
    public Map<String, GrafanaEndpoint> getEndpoints() {
        return endpoints;
    }

    /**
     * Gets the stream to parse for endpoints.
     * @return an open InputStream, or {@code null} if there are no endpoints to read
     * @throws IOException when something goes wrong
     */
    protected abstract InputStream getStream() throws IOException;

} public class Interaction {

    public static void askUserForDetails(UploaderConfig config) throws IOException {
        List<Dashboard> dashboards = config.getDashboardStore().getDashboards();
        Map<String, GrafanaEndpoint> endpoints = config.getEndpointStore().getEndpoints();

        ConsolePrompt prompt = new ConsolePrompt();

        Map<String, ? extends PromtResultItemIF> results = Collections.emptyMap();
        do {
            PromptBuilder promptBuilder = prompt.getPromptBuilder();

            if (config.getDashboards() == null) {
                CheckboxPromptBuilder dashboardPrompt = promptBuilder.createCheckboxPrompt()
                        .name("dashboards")
                        .message("Which dashboard(s) would you like to work with?");
                for (int i = 0; i < dashboards.size(); i++) {
                    dashboardPrompt.newItem(Integer.toString(i)).text(dashboards.get(i).getTitle()).check().add();
                }
                dashboardPrompt.addPrompt();
            }

            if (config.getEndpoint() == null) {
                ListPromptBuilder endpointPrompt = promptBuilder.createListPrompt()
                        .name("endpoint")
                        .message("Which endpoint to upload to?");
                for (Map.Entry<String, GrafanaEndpoint> entry : endpoints.entrySet()) {
                    endpointPrompt
                            .newItem(entry.getKey())
                            .text(String.format("%s (%s)", entry.getKey(), entry.getValue().getBaseUrl()))
                            .add();
                }
                endpointPrompt
                        .newItem("-")
                        .text("Just generate the files to " + config.getOutputDirectory())
                        .add()
                        .addPrompt();
            }

            if (config.getOverwrite() == null) {
                promptBuilder.createConfirmPromp()
                        .name("overwrite")
                        .message("Should dashboards be overwritten if already exist?")
                        .defaultValue(ConfirmChoice.ConfirmationValue.YES)
                        .addPrompt();
            }

            promptBuilder.createConfirmPromp()
                    .name("confirmed")
                    .message("Are you ready?")
                    .defaultValue(ConfirmChoice.ConfirmationValue.YES)
                    .addPrompt();


            List<PromptableElementIF> questions = promptBuilder.build();
            if (questions.size() == 1) {
                // only the confirmation, we know everything
                break;
            }

            if (config.isBatchMode()) {
                System.err.println("Missing info for batch mode"); // TODO details
                throw new ExitPlease(-6);
            }

            results = prompt.prompt(questions);
        } while (!getBooleanResult(results, "confirmed"));

        if (results.containsKey("dashboards")) {
            CheckboxResult selection = (CheckboxResult) results.get("dashboards");
            List<Dashboard> selectedDashboards = new ArrayList<>();
            for (String i : selection.getSelectedIds()) {
                selectedDashboards.add(dashboards.get(Integer.parseInt(i, 10)));
            }
            config.setDashboards(selectedDashboards);
        }

        if (results.containsKey("endpoint")) {
            ListResult result = (ListResult) results.get("endpoint");
            if ("-".equals(result.getSelectedId())) {
                config.setEndpoint(Optional.empty());
            } else {
                config.setEndpoint(Optional.of(endpoints.get(result.getSelectedId())));
            }
        }

        if (results.containsKey("overwrite")) {
            config.setOverwrite(getBooleanResult(results, "overwrite"));
        }
    }

    private static boolean getBooleanResult(Map<String, ? extends PromtResultItemIF> results, String key) {
        return ((ConfirmResult) results.get(key)).getConfirmed() == ConfirmChoice.ConfirmationValue.YES;
    }
} public class UploaderConfig {

    private boolean batchMode;
    private DashboardStore dashboardStore;
    private GrafanaEndpointStore endpointStore;
    private Boolean overwrite;
    private Optional<GrafanaEndpoint> endpoint;
    private List<Dashboard> dashboards;
    private File outputDirectory;

    public boolean isBatchMode() {
        return batchMode;
    }

    public void setBatchMode(boolean batchMode) {
        this.batchMode = batchMode;
    }

    public DashboardStore getDashboardStore() {
        return dashboardStore;
    }

    public void setDashboardStore(DashboardStore dashboardStore) {
        this.dashboardStore = dashboardStore;
    }

    public GrafanaEndpointStore getEndpointStore() {
        return endpointStore;
    }

    public void setEndpointStore(GrafanaEndpointStore endpointStore) {
        this.endpointStore = endpointStore;
    }

    public Boolean getOverwrite() {
        return overwrite;
    }

    public void setOverwrite(Boolean overwrite) {
        this.overwrite = overwrite;
    }

    public Optional<GrafanaEndpoint> getEndpoint() {
        return endpoint;
    }

    public void setEndpoint(Optional<GrafanaEndpoint> endpoint) {
        this.endpoint = endpoint;
    }

    public List<Dashboard> getDashboards() {
        return dashboards;
    }

    public void setDashboards(List<Dashboard> dashboards) {
        this.dashboards = dashboards;
    }

    public File getOutputDirectory() {
        return outputDirectory;
    }

    public void setOutputDirectory(File outputDirectory) {
        this.outputDirectory = outputDirectory;
    }

}
import org.fusesource.jansi.AnsiConsole;
import uk.co.szmg.grafana.DashboardSerializer;
import uk.co.szmg.grafana.DashboardUploader;
import uk.co.szmg.grafana.GrafanaClient;
import uk.co.szmg.grafana.stores.ClasspathGrafanaEndpointStore;
import uk.co.szmg.grafana.cli.internal.CommandLineArgs;
import uk.co.szmg.grafana.stores.DashboardStore;
import uk.co.szmg.grafana.cli.internal.ExitPlease;
import uk.co.szmg.grafana.stores.GrafanaEndpointStore;
import uk.co.szmg.grafana.cli.internal.Interaction;
import uk.co.szmg.grafana.cli.internal.UploaderConfig;
import uk.co.szmg.grafana.domain.Dashboard;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static org.fusesource.jansi.Ansi.ansi;
package uk.co.szmg.grafana.cli;

/*-
 * #%L
 * grafana-dashboard-generator-cli
 * %%
 * Copyright (C) 2017 Mate Gabor Szvoboda
 * %%
 * 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.
 * #L%
 */




public class DashboardGeneratorApplication {

    private String rootPackage;

    public DashboardGeneratorApplication(String rootPackage) {
        this.rootPackage = rootPackage;
    }

    public int main(String[] args) throws IOException {
        try {
            return safeMain(args);
        } catch (ExitPlease ex) {
            return ex.getReturnCode();
        }
    }

    private int safeMain(String[] args) throws IOException, ExitPlease {
        AnsiConsole.systemInstall();

        // welcome
        System.out.println(ansi().render("\n@|blue Welcome to Grafana Dashboard Builder|@\n"));

        UploaderConfig config = new UploaderConfig();