From 4d7c3d5f0a61760d3105d44ca07c0c721e9c58e8 Mon Sep 17 00:00:00 2001 From: Kenneth Sternberg Date: Wed, 8 May 2013 14:34:05 -0700 Subject: [PATCH] Checking in initialization. --- .gitignore | 15 ++++ README.md | 13 +++ doc/intro.md | 3 + src/simple/core.clj | 161 ++++++++++++++++++++++++++++++++++++++ test/simple/core_test.clj | 7 ++ 5 files changed, 199 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 doc/intro.md create mode 100644 src/simple/core.clj create mode 100644 test/simple/core_test.clj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b21be17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +/target +/lib +/classes +/checkouts +pom.xml +pom.xml.asc +*~ +.#* +*# +*.jar +*.class +.lein-deps-sum +.lein-failures +.lein-plugins +.lein-repl-history diff --git a/README.md b/README.md new file mode 100644 index 0000000..f853220 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# simple + +A Clojure library designed to ... well, that part is up to you. + +## Usage + +FIXME + +## License + +Copyright © 2013 FIXME + +Distributed under the Eclipse Public License, the same as Clojure. diff --git a/doc/intro.md b/doc/intro.md new file mode 100644 index 0000000..ed9211c --- /dev/null +++ b/doc/intro.md @@ -0,0 +1,3 @@ +# Introduction to simple + +TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/) diff --git a/src/simple/core.clj b/src/simple/core.clj new file mode 100644 index 0000000..df21d36 --- /dev/null +++ b/src/simple/core.clj @@ -0,0 +1,161 @@ +(ns simple.core + (:require [clojure.java.io :refer :all]) + (:import (com.splunk Service ServiceArgs Args ResultsReaderJson ResultsReaderCsv ResultsReaderXml Event)) + (:import (com.splunk Command)) + (:import (java.io InputStreamReader OutputStreamWriter))) + + +; Given a set of command-line arguments, creates, populates and +; returns a splunk.Command instance. + +(defn build-splunk-command [args] + (let [command + (doto (Command/splunk "search") + (.addRule "count" Integer + "The Maximum Number of results to return (default: 100)") + (.addRule "earliest_time" String + "Search earliest time") + (.addRule "field_list" String + "A comma-separated list of the fields to return") + (.addRule "latest_time" String + "Search latest time") + (.addRule "offset" Integer + "The first result (inclusive) from which to begin returning data. (default: 0)") + (.addRule "output" String + "Which search results to output {events, results, preview, searchlog, summary, timeline} (default: results)") + (.addRule "output_mode" String + "Search output format {csv, raw, json, xml} (default: xml)") + (.addRule "reader" + "Use ResultsReader") + (.addRule "status_buckets" Integer + "Number of status buckets to use for search (default: 0)") + (.addRule "verbose" + "Display search progress") + (.parse (into-array String args)))] + (if (not= (count (.args command)) 1) + (Command/error "Search expression required" nil)) + command)) + + +; Given an initialized splunk.Command instance, returns a map of +; arguments commonly used to control search behavior. The repetition +; of names above is a giveaway that something is a bit off here. + +(defn build-argument-map [command] + (let [opts (.opts command) + ruleset [["count" 100] + ["earliest_time" nil ] + ["reader" false] + ["verbose" false] + ["field_list" nil ] + ["latest_time" nil ] + ["offset" 0] + ["output" "results"] + ["output_mode" "xml"]]] + doall (into {} (for [[k v] ruleset] [k (if (.containsKey opts k) (.get opts k) v)])))) + + +; From a map of commonly used control arguments, create, populate, and +; return a splunk.Args instance suitable for passing to a query + +(defn build-splunk-queryargs [argument-map] + (let [rulelist ["earliest_time" "field_list" "latest_time" "status_buckets" "output_mode"] + qa (Args.)] + (filter + (fn [a] (not= a nil)) + (for [fieldname rulelist] + (fn [fieldname] + (if (argument-map fieldname) + (.put qa fieldname (argument-map fieldname)) + nil)))) + qa)) + + +; Given a splunk.Command instance and a set of initialized arguments, +; generate an actual query; wait until the query is done. + +(defn build-splunk-job [command argument-map] + (let [queryargs (build-splunk-queryargs argument-map) + service (Service/connect (.opts command)) + job (.. service (getJobs) (create (first (.args command)) queryargs))] + (while (not (.isDone job)) + (if (argument-map "verbose") + (println (format "\n%03.1f%% done -- %d scanned -- %d matched -- %d results" + (* (.getDoneProgress job) 100.0) + (.getScanCount job) + (.getEventCount job) + (.getResultCount job)))) + (Thread/sleep 1000)) + job)) + +; From a map of commonly used control arguments, create, populate, and +; return a splunk.Args instance suitable for passing to a splunk.Job +; specifying desired output paramaters. + +(defn build-splunk-output-args [argument-map] + (let [rulelist ["count" "offset" "output_mode"] + args (Args.)] + (doseq [fieldname rulelist] + (if (argument-map fieldname) + (.put args fieldname (argument-map fieldname)))) + args)) + +; Given a job and a map of commonly use arguments, return an +; InputStream with the content of the response + +(defn get-splunk-stream [job argument-map] + (let [outputargs (build-splunk-output-args argument-map) + output (argument-map "output")] + (case output + "results" (.getResults job outputargs) + "preview" (.getPreview job outputargs) + "searchlog" (.getSearchLog job outputargs) + "summary" (.getSummary job outputargs) + "timeline" (.getTimeline job outputargs) + ))) + +(defn construct-reader [stream output-mode] + (case output-mode + "xml" (ResultsReaderXml. stream) + "json" (ResultsReaderJson. stream) + "csv" (ResultsReaderCsv. stream))) + +(defn get-streamtype-reader [output-mode] + (fn [stream] + (with-open [reader (construct-reader stream output-mode)] + (loop [e (.getNextEvent reader)] + (when (not= e nil) + (println "EVENT:********") + (doseq [k (seq (.keySet e))] + (printf "%s ---> %s\n" k (.get e k))) + (recur (.getNextEvent reader))))))) + +; A totally unnecessary level of indirection, but it maintains an +; aesthetic symmetry. + +(defn generic-reader [] + (fn [stream] + (with-open [reader (InputStreamReader. stream "UTF8") + writer (OutputStreamWriter. System/out)] + (try + (let [buffer (char-array 1024)] + (while true + (let [count (.read reader buffer)] + (if (== count -1) (throw (Exception. "EOF"))) + (.write writer buffer 0 count)))) + (catch Exception e nil))))) + +(defn get-reader [command argument-map] + (let [use-reader (.. command opts (containsKey "reader"))] + (if use-reader + (get-streamtype-reader (argument-map "output_mode")) + (generic-reader)))) + +(defn -main[& args] + (let [command (build-splunk-command args)] + (let [argument-map (build-argument-map command)] + (let [job (build-splunk-job command argument-map)] + (let [stream (get-splunk-stream job argument-map)] + (let [reader (get-reader command argument-map)] + (reader stream))))))) + diff --git a/test/simple/core_test.clj b/test/simple/core_test.clj new file mode 100644 index 0000000..e428c6d --- /dev/null +++ b/test/simple/core_test.clj @@ -0,0 +1,7 @@ +(ns simple.core-test + (:use clojure.test + simple.core)) + +(deftest a-test + (testing "FIXME, I fail." + (is (= 0 1))))