diff --git a/.env b/.env new file mode 100644 index 000000000..c80803e8e --- /dev/null +++ b/.env @@ -0,0 +1 @@ +SPLUNK_HOME="/opt/splunk" \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..bd710dca7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,18 @@ +name: Create Release + +on: + release: + types: [published] + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 14 + registry-url: https://registry.npmjs.org/ + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..136601a6e --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,57 @@ +name: Node.js CI + +on: [push, pull_request] + +jobs: + build: + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + node: + - 14 + - 8.17.0 + splunk-version: + - "8.0" + - "latest" + + services: + splunk: + image: splunk/splunk:${{matrix.splunk-version}} + env: + SPLUNK_START_ARGS: --accept-license + SPLUNK_HEC_TOKEN: 11111111-1111-1111-1111-1111111111113 + SPLUNK_PASSWORD: changed! + SPLUNK_APPS_URL: https://github.com/splunk/sdk-app-collection/releases/download/v1.0.0/sdk-app-collection.tgz + ports: + - 8000:8000 + - 8088:8088 + - 8089:8089 + + steps: + - uses: actions/checkout@v2 + + - name: Use node ${{ matrix.node }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node }} + + - name: Create .splunkrc file + run: | + cd ~ + echo host=localhost > .splunkrc + echo port=8089 >> .splunkrc + echo username=admin >> .splunkrc + echo password=changed! >> .splunkrc + echo scheme=https >> .splunkrc + echo version=${{ matrix.splunk }} >> .splunkrc + + - name: Run npm install + run: npm install + + - name: Run make test + run: make test + env: + SPLUNK_HOME: /opt/splunk diff --git a/CREDITS.md b/CREDITS.md index a4e2830d4..f847d8706 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -14,3 +14,7 @@ Some of the components included in the Splunk Enterprise SDK for JavaScript are | [commander](https://github.com/visionmedia/commander.js/) | Node.js command-line interfaces | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-COMMANDER) | | [script.js](https://github.com/ded/script.js/) | Asyncronous JavaScript loader and dependency manager | [Apache](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-SCRIPTJS) | | [base64.js](http://code.google.com/p/javascriptbase64/) | Fast base64 encoding/decoding | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-BASE64) | +| [dotenv](https://github.com/motdotla/dotenv) | Loads environment varibles from .env file | [BSD 2-Clause](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-DOTENV) | +| [cookie](https://github.com/jshttp/cookie) | HTTP cookie parser and serializer for HTTP servers | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-COOKIE) | +| [elementtree](https://github.com/racker/node-elementtree) | Node.js XML parserer and serializer | [Apache-2.0](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-ELEMENTTREE) | +| [needle](https://github.com/tomas/needle) | Node.js http client | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-NEEDLE) | \ No newline at end of file diff --git a/Makefile b/Makefile index 9eb5adb20..d81da8274 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ init: .PHONY: test test: @echo "$(ATTN_COLOR)==> test $(NO_COLOR)" - @node sdkdo tests + @node sdkdo tests ${arg} .PHONY: test_specific test_specific: diff --git a/README.md b/README.md index ccbc38ee6..8a8948cea 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ For more information, see [Splunk Enterprise SDK for JavaScript](https://dev.spl ## Requirements -* Node.js v 0.12, or v4 or later +* Node.js v 8.17.0, or v14 or later - The Splunk Enterprise SDK for JavaScript was tested with Node.js v.0.12, v4.2, and v10.0. + The Splunk Enterprise SDK for JavaScript was tested with Node.js v8.17.0, v14. -* Splunk Enterprise 6.3.0 or later, or Splunk Cloud +* Splunk Enterprise 8.2.0 or later, or Splunk Cloud - The Splunk Enterprise SDK for JavaScript was tested with Splunk Enterprise 7.0 and 7.2. + The Splunk Enterprise SDK for JavaScript was tested with Splunk Enterprise 8.0 or 8.2, or Splunk Cloud. * Splunk Enterprise SDK for JavaScript @@ -129,7 +129,7 @@ To use this convenience file, create a text file with the following format: # Access scheme (default: https) scheme=https # Your version of Splunk Enterprise - version=7.2 + version=8.2 Save the file as **.splunkrc** in the current user's home directory. diff --git a/bin/cli.js b/bin/cli.js index 81cbadea3..7cc955948 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -12,9 +12,9 @@ // License for the specific language governing permissions and limitations // under the License. -(function() { - var utils = require('../lib/utils'); - var Async = require('../lib/async'); +(function () { + var utils = require('../lib/utils'); + var Async = require('../lib/async'); var staticResource = require('../contrib/static-resource/index'); var dox = require('../contrib/dox/dox'); var doc_builder = require('../contrib/dox/doc_builder'); @@ -25,25 +25,26 @@ var browserify = require('browserify'); var http = require('http'); var url = require('url'); - var request = require('request'); + var needle = require('needle'); + /** * Constants */ - var DEFAULT_PORT = 6969; - var DOC_DIRECTORY = "docs"; - var REFDOC_DIRECTORY = "refs"; - var CLIENT_DIRECTORY = "client"; - var TEST_DIRECTORY = "tests"; - var TEST_PREFIX = "test_"; - var ALL_TESTS = "tests.js"; - var SDK_BROWSER_ENTRY = "./lib/entries/browser.entry.js"; - var TEST_BROWSER_ENTRY = "./lib/entries/browser.test.entry.js"; - var UI_BROWSER_ENTRY = "./lib/entries/browser.ui.entry.js"; - var DOC_FILE = "index.html"; - var BUILD_CACHE_FILE = ".buildcache"; - var SDK_VERSION = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../package.json")).toString("utf-8")).version; - var IGNORED_MODULES = [ + var DEFAULT_PORT = 6969; + var DOC_DIRECTORY = "docs"; + var REFDOC_DIRECTORY = "refs"; + var CLIENT_DIRECTORY = "client"; + var TEST_DIRECTORY = "tests"; + var TEST_PREFIX = "test_"; + var ALL_TESTS = "tests.js"; + var SDK_BROWSER_ENTRY = "./lib/entries/browser.entry.js"; + var TEST_BROWSER_ENTRY = "./lib/entries/browser.test.entry.js"; + var UI_BROWSER_ENTRY = "./lib/entries/browser.ui.entry.js"; + var DOC_FILE = "index.html"; + var BUILD_CACHE_FILE = ".buildcache"; + var SDK_VERSION = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../package.json")).toString("utf-8")).version; + var IGNORED_MODULES = [ "../contrib/nodeunit/test_reporter", "../contrib/nodeunit/junit_reporter", "../contrib/commander", @@ -56,7 +57,7 @@ /** * UI Component Entry Points (for async loading) */ - var UI_COMPONENT_BROWSER_ENTRY = { + var UI_COMPONENT_BROWSER_ENTRY = { timeline: "./lib/entries/browser.ui.timeline.entry.js", charting: "./lib/entries/browser.ui.charting.entry.js" }; @@ -64,13 +65,13 @@ /** * Generated files */ - var COMPILED_SDK = path.join(CLIENT_DIRECTORY, "splunk.js"); - var COMPILED_SDK_MIN = path.join(CLIENT_DIRECTORY, "splunk.min.js"); - var COMPILED_TEST = path.join(CLIENT_DIRECTORY, "splunk.test.js"); - var COMPILED_TEST_MIN = path.join(CLIENT_DIRECTORY, "splunk.test.min.js"); - var COMPILED_UI = path.join(CLIENT_DIRECTORY, "splunk.ui.js"); - var COMPILED_UI_MIN = path.join(CLIENT_DIRECTORY, "splunk.ui.min.js"); - var GENERATED_DOCS = path.join(DOC_DIRECTORY, SDK_VERSION, DOC_FILE); + var COMPILED_SDK = path.join(CLIENT_DIRECTORY, "splunk.js"); + var COMPILED_SDK_MIN = path.join(CLIENT_DIRECTORY, "splunk.min.js"); + var COMPILED_TEST = path.join(CLIENT_DIRECTORY, "splunk.test.js"); + var COMPILED_TEST_MIN = path.join(CLIENT_DIRECTORY, "splunk.test.min.js"); + var COMPILED_UI = path.join(CLIENT_DIRECTORY, "splunk.ui.js"); + var COMPILED_UI_MIN = path.join(CLIENT_DIRECTORY, "splunk.ui.min.js"); + var GENERATED_DOCS = path.join(DOC_DIRECTORY, SDK_VERSION, DOC_FILE); var GENERATED_REF_DOCS = path.join(DOC_DIRECTORY, SDK_VERSION, REFDOC_DIRECTORY, DOC_FILE); var GENERATED_DOCS_DIR = path.join(DOC_DIRECTORY, SDK_VERSION); @@ -78,10 +79,10 @@ * Helpers */ - var serverProxy = function(req, res) { - var error = {d: { __messages: [{ type: "ERROR", text: "Proxy Error", code: "PROXY"}] }}; + var serverProxy = function (req, res) { + var error = { d: { __messages: [{ type: "ERROR", text: "Proxy Error", code: "PROXY" }] } }; - var writeError = function() { + var writeError = function () { res.writeHead(500, {}); res.write(JSON.stringify(error)); res.end(); @@ -89,11 +90,11 @@ try { var body = ""; - req.on('data', function(data) { + req.on('data', function (data) { body += data.toString("utf-8"); }); - req.on('end', function() { + req.on('end', function () { var destination = req.headers["X-ProxyDestination".toLowerCase()]; var options = { @@ -101,29 +102,32 @@ method: req.method, headers: { "Content-Length": req.headers["content-length"] || 0, - "Content-Type": req.headers["content-type"], - "Authorization": req.headers["authorization"] + "Content-Type": req.headers["content-type"] || '', + "Authorization": req.headers["authorization"] || '' }, followAllRedirects: true, - body: body, + body: body || '', jar: false, - strictSSL: false + strictSSL: false, + rejectUnauthorized: false, + parse_response: false }; try { - request(options, function(err, response, data) { + needle.request(options.method, options.url, options.body, options, function (err, response, body) { try { var statusCode = (response ? response.statusCode : 500) || 500; var headers = (response ? response.headers : {}) || {}; res.writeHead(statusCode, headers); - res.write(data || JSON.stringify(err)); + res.write(body || JSON.stringify(err)); res.end(); } catch (ex) { writeError(); } }); + } catch (ex) { writeError(); @@ -136,11 +140,11 @@ } }; - var createServer = function(port) { + var createServer = function (port) { // passing where is going to be the document root of resources. var handler = staticResource.createHandler(fs.realpathSync(path.resolve(__dirname, ".."))); - var server = http.createServer(function(request, response) { + var server = http.createServer(function (request, response) { var path = url.parse(request.url).pathname; if (utils.startsWith(path, "/proxy")) { @@ -150,7 +154,7 @@ // handle method returns true if a resource specified with the path // has been handled by handler and returns false otherwise. - if(!handler.handle(path, request, response)) { + if (!handler.handle(path, request, response)) { response.writeHead(404); response.write('404'); response.end(); @@ -162,11 +166,11 @@ console.log("Running server on port: " + (port) + " -- Hit CTRL+C to exit"); }; - var makeOption = function(name, value) { + var makeOption = function (name, value) { return ["--" + name, value]; }; - var makeURL = function(file, port) { + var makeURL = function (file, port) { return "http://localhost:" + (port ? port : DEFAULT_PORT) + "/" + file; }; @@ -174,8 +178,8 @@ _defaultDirectory: '/tmp', _environmentVariables: ['TMPDIR', 'TMP', 'TEMP'], - _findDirectory: function() { - for(var i = 0; i < temp._environmentVariables.length; i++) { + _findDirectory: function () { + for (var i = 0; i < temp._environmentVariables.length; i++) { var value = process.env[temp._environmentVariables[i]]; if (value) { return fs.realpathSync(value); @@ -185,19 +189,19 @@ return fs.realpathSync(temp._defaultDirectory); }, - _generateName: function() { + _generateName: function () { var now = new Date(); var name = ["__", - now.getYear(), now.getMonth(), now.getDay(), - '-', - process.pid, - '-', - (Math.random() * 0x100000000 + 1).toString(36), - "__"].join(''); + now.getYear(), now.getMonth(), now.getDay(), + '-', + process.pid, + '-', + (Math.random() * 0x100000000 + 1).toString(36), + "__"].join(''); return path.join(temp._findDirectory(), name); }, - mkdirSync: function() { + mkdirSync: function () { var tempDirPath = temp._generateName(); fs.mkdirSync(tempDirPath, "755"); return tempDirPath; @@ -205,15 +209,15 @@ }; // Taken from wrench.js - var copyDirectoryRecursiveSync = function(sourceDir, newDirLocation, opts) { + var copyDirectoryRecursiveSync = function (sourceDir, newDirLocation, opts) { if (!opts || !opts.preserve) { try { - if(fs.statSync(newDirLocation).isDirectory()) { + if (fs.statSync(newDirLocation).isDirectory()) { exports.rmdirSyncRecursive(newDirLocation); } } - catch(e) { } + catch (e) { } } /* Create the directory where all our junk is moving to; read the mode of the source directory and mirror it */ @@ -230,14 +234,14 @@ var files = fs.readdirSync(sourceDir); - for(var i = 0; i < files.length; i++) { + for (var i = 0; i < files.length; i++) { var currFile = fs.lstatSync(sourceDir + "/" + files[i]); - if(currFile.isDirectory()) { + if (currFile.isDirectory()) { /* recursion this thing right on back. */ copyDirectoryRecursiveSync(sourceDir + "/" + files[i], newDirLocation + "/" + files[i], opts); } - else if(currFile.isSymbolicLink()) { + else if (currFile.isSymbolicLink()) { var symlinkFull = fs.readlinkSync(sourceDir + "/" + files[i]); fs.symlinkSync(symlinkFull, newDirLocation + "/" + files[i]); } @@ -249,27 +253,27 @@ } }; - var rmdirRecursiveSync = function(path, failSilent) { + var rmdirRecursiveSync = function (path, failSilent) { var files; try { files = fs.readdirSync(path); } catch (err) { - if(failSilent) { + if (failSilent) { return; } throw new Error(err.message); } /* Loop through and delete everything in the sub-tree after checking it */ - for(var i = 0; i < files.length; i++) { + for (var i = 0; i < files.length; i++) { var currFile = fs.lstatSync(path + "/" + files[i]); - if(currFile.isDirectory()) {// Recursive function back to the beginning + if (currFile.isDirectory()) {// Recursive function back to the beginning rmdirRecursiveSync(path + "/" + files[i]); } - else if(currFile.isSymbolicLink()) {// Unlink symlinks + else if (currFile.isSymbolicLink()) {// Unlink symlinks fs.unlinkSync(path + "/" + files[i]); } else { // Assume it's a file - perhaps a try/catch belongs here? @@ -283,24 +287,24 @@ }; var git = { - execute: function(args, callback) { + execute: function (args, callback) { var program = spawn("git", args); - process.on("exit", function() { + process.on("exit", function () { program.kill(); }); - program.stderr.on("data", function(data) { + program.stderr.on("data", function (data) { process.stderr.write(data); }); return program; }, - stash: function(callback) { + stash: function (callback) { var program = git.execute(["stash"], callback); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("Stash error"); } @@ -310,10 +314,10 @@ }); }, - unstash: function(callback) { + unstash: function (callback) { var program = git.execute(["stash", "pop"], callback); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("Unstash error"); } @@ -323,10 +327,10 @@ }); }, - switchBranch: function(toBranch, callback) { + switchBranch: function (toBranch, callback) { var program = git.execute(["checkout", toBranch], callback); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("Switch branch error error"); } @@ -336,15 +340,15 @@ }); }, - currentBranch: function(callback) { - var program = git.execute(["symbolic-ref", "HEAD"], callback); + currentBranch: function (callback) { + var program = git.execute(["symbolic-ref", "HEAD"], callback); var buffer = ""; - program.stdout.on("data", function(data) { + program.stdout.on("data", function (data) { buffer = data.toString("utf-8"); }); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("Couldn't determine current branch name"); } @@ -355,10 +359,10 @@ }); }, - add: function(filename, callback) { + add: function (filename, callback) { var program = git.execute(["add", filename], callback); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("Add error"); } @@ -368,10 +372,10 @@ }); }, - commit: function(msg, callback) { + commit: function (msg, callback) { var program = git.execute(["commit", "-m", msg], callback); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("Commit error"); } @@ -381,10 +385,10 @@ }); }, - push: function(branch, callback) { + push: function (branch, callback) { var program = git.execute(["push", "origin", branch], callback); - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { throw new Error("push error"); } @@ -395,29 +399,34 @@ } }; - var launch = function(file, args, done) { - done = done || function() {}; + var launch = function (file, args, done) { + done = done || function () { }; // Add the file to the arguments args = args || []; args = args.slice(); args.unshift(file); + args.unshift("./node_modules/mocha/bin/mocha"); + args.unshift("./node_modules/nyc/bin/nyc") + args.push("--color=always"); + + args = args.filter(arg => arg); // Spawn var program = spawn("node", args); - program.stdout.on("data", function(data) { + program.stdout.on("data", function (data) { var str = data.toString("utf-8"); process.stdout.write(str); }); - program.stderr.on("data", function(data) { + program.stderr.on("data", function (data) { var str = data.toString("utf-8"); process.stderr.write(str); }); var exitCode = 0; - program.on("exit", function(code) { + program.on("exit", function (code) { if (code) { exitCode = code; done(code); @@ -427,7 +436,7 @@ } }); - process.on("exit", function() { + process.on("exit", function () { program.kill(); process.reallyExit(exitCode); }); @@ -435,7 +444,7 @@ return program; }; - var getDependencies = function(entry) { + var getDependencies = function (entry) { var bundle = browserify({ entry: entry, ignore: IGNORED_MODULES, @@ -443,7 +452,7 @@ }); var dependencies = [entry]; - for(var file in bundle.files) { + for (var file in bundle.files) { if (bundle.files.hasOwnProperty(file)) { dependencies.push(file); } @@ -452,7 +461,7 @@ return dependencies; }; - var compile = function(entry, path, shouldUglify, watch, exportName) { + var compile = function (entry, path, shouldUglify, watch, exportName) { exportName = exportName || "splunkjs"; // Compile/combine all the files into the package @@ -460,7 +469,7 @@ entry: entry, ignore: IGNORED_MODULES, cache: BUILD_CACHE_FILE, - filter: function(code) { + filter: function (code) { if (shouldUglify) { var uglifyjs = require("uglify-js"), parser = uglifyjs.parser, @@ -490,27 +499,27 @@ console.log("Compiled " + path); }; - var outOfDate = function(dependencies, compiled, compiledMin) { + var outOfDate = function (dependencies, compiled, compiledMin) { if (!fs.existsSync(compiled) || !fs.existsSync(compiledMin)) { return true; } var compiledTime = fs.statSync(compiled).mtime; var compiledMinTime = fs.statSync(compiledMin).mtime; - var latestDependencyTime = Math.max.apply(null, dependencies.map(function(path) { + var latestDependencyTime = Math.max.apply(null, dependencies.map(function (path) { return fs.statSync(path).mtime; })); return latestDependencyTime > compiledTime || latestDependencyTime > compiledMinTime; }; - var ensureDirectoryExists = function(dir) { + var ensureDirectoryExists = function (dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, "755"); } }; - var ensureClientDirectory = function() { + var ensureClientDirectory = function () { ensureDirectoryExists(CLIENT_DIRECTORY); }; @@ -518,7 +527,7 @@ * Tasks */ - var compileSDK = function(watch, exportName) { + var compileSDK = function (watch, exportName) { ensureClientDirectory(); var dependencies = getDependencies(SDK_BROWSER_ENTRY); @@ -531,7 +540,7 @@ compile(SDK_BROWSER_ENTRY, COMPILED_SDK_MIN, true, watch, exportName); }; - var compileTests = function(watch, exportName) { + var compileTests = function (watch, exportName) { ensureClientDirectory(); var dependencies = getDependencies(TEST_BROWSER_ENTRY); @@ -544,7 +553,7 @@ compile(TEST_BROWSER_ENTRY, COMPILED_TEST_MIN, true, watch); }; - var compileUI = function(watch, exportName) { + var compileUI = function (watch, exportName) { ensureClientDirectory(); var dependencies = getDependencies(UI_BROWSER_ENTRY); @@ -556,7 +565,7 @@ console.log("Compiled UI is not out of date -- skipping..."); } - for(var component in UI_COMPONENT_BROWSER_ENTRY) { + for (var component in UI_COMPONENT_BROWSER_ENTRY) { if (!UI_COMPONENT_BROWSER_ENTRY.hasOwnProperty(component)) { continue; } @@ -576,19 +585,19 @@ } }; - var compileAll = function(watch, exportName) { + var compileAll = function (watch, exportName) { compileSDK(watch, exportName); compileTests(watch); compileUI(watch, exportName); }; - var runServer = function(port) { + var runServer = function (port) { // TODO: compile doesn't work on Windows, so lets not // make runServer depend on it createServer(port); }; - var launchBrowser = function(file, port) { + var launchBrowser = function (file, port) { if (!fs.existsSync(file)) { throw new Error("File does not exist: " + file); } @@ -601,18 +610,18 @@ } }; - var launchBrowserTests = function(port) { + var launchBrowserTests = function (port) { runServer(port); launchBrowser("tests/tests.browser.html", port); }; - var launchBrowserExamples = function(port) { + var launchBrowserExamples = function (port) { runServer(port); launchBrowser("examples/browser/index.html", port); }; - var generateDocs = function(callback) { - callback = (callback && utils.isFunction(callback)) ? callback : (function() {}); + var generateDocs = function (callback) { + callback = (callback && utils.isFunction(callback)) ? callback : (function () { }); var files = [ "lib/log.js", @@ -633,14 +642,14 @@ ]; var comments = []; - files.forEach(function(file) { - var contents = fs.readFileSync(file).toString("utf-8"); + files.forEach(function (file) { + var contents = fs.readFileSync(file).toString("utf-8"); - var obj = dox.parseComments(contents, file); - comments = comments.concat(obj); + var obj = dox.parseComments(contents, file); + comments = comments.concat(obj); }); - doc_builder.generate(comments, SDK_VERSION, function(err, data) { + doc_builder.generate(comments, SDK_VERSION, function (err, data) { if (err) { throw err; } @@ -649,7 +658,7 @@ ensureDirectoryExists(path.join(DOC_DIRECTORY, SDK_VERSION)); ensureDirectoryExists(path.join(DOC_DIRECTORY, SDK_VERSION, REFDOC_DIRECTORY)); - for(var name in data) { + for (var name in data) { var htmlPath = path.join(DOC_DIRECTORY, SDK_VERSION, REFDOC_DIRECTORY, name + ".html"); fs.writeFileSync(htmlPath, data[name]); } @@ -658,19 +667,19 @@ }); }; - var uploadDocs = function() { + var uploadDocs = function () { var originalBranch = "master"; var tempPath = ""; Async.chain([ - function(done) { + function (done) { git.currentBranch(done); }, - function(branchName, done) { + function (branchName, done) { originalBranch = branchName; generateDocs(done); }, - function(done) { + function (done) { var tempDirPath = temp.mkdirSync(); tempPath = tempDirPath; @@ -678,13 +687,13 @@ done(); }, - function(done) { + function (done) { git.stash(done); }, - function(done) { + function (done) { git.switchBranch("gh-pages", done); }, - function(done) { + function (done) { if (fs.existsSync(GENERATED_DOCS_DIR)) { rmdirRecursiveSync(GENERATED_DOCS_DIR); } @@ -696,38 +705,45 @@ done(); }, - function(done) { + function (done) { git.add(GENERATED_DOCS_DIR, done); }, - function(done) { + function (done) { git.commit("Updating v" + SDK_VERSION + " docs: " + (new Date()), done); }, - function(done) { + function (done) { git.push("gh-pages", done); }, - function(done) { + function (done) { git.switchBranch(originalBranch, done); }, - function(done) { + function (done) { git.unstash(done); }], - function(err) { - if (err) { - console.log(err); - } + function (err) { + if (err) { + console.log(err); + } } ); }; - var runTests = function(tests, cmdline) { - cmdline = cmdline || {opts: {}}; - var args = (tests || "").split(",").map(function(arg) { return arg.trim(); }); + var runTests = function (tests, cmdline) { + cmdline = cmdline || { opts: {} }; + var args = (tests || "").split(",").map(function (arg) { return arg.trim(); }); - var files = args.map(function(arg) { - return path.join(TEST_DIRECTORY, TEST_PREFIX + arg + ".js"); - }).filter(function(file) { - return fs.existsSync(file); - }); + var files = args + .map(arg => { + if (arg.indexOf('modularinputs') >= 0) { + return path.join(TEST_DIRECTORY, 'modularinputs', TEST_PREFIX + arg.split('/')[1] + ".js"); + } + else if (arg.indexOf('service_tests') >= 0) { + return path.join(TEST_DIRECTORY, 'service_tests', arg.split('/')[1] + ".js"); + } + else { + return path.join(TEST_DIRECTORY, TEST_PREFIX + arg + ".js"); + } + }).filter(file => fs.existsSync(file)); if (files.length === 0) { if (args.length > 0 && args[0].length !== 0) { @@ -736,19 +752,24 @@ } files.push(path.join(TEST_DIRECTORY, ALL_TESTS)); } + var cmdlineArgs = [] - .concat(cmdline.opts.username ? makeOption("username", cmdline.opts.username) : "") - .concat(cmdline.opts.scheme ? makeOption("scheme", cmdline.opts.scheme) : "") - .concat(cmdline.opts.host ? makeOption("host", cmdline.opts.host) : "") - .concat(cmdline.opts.port ? makeOption("port", cmdline.opts.port) : "") - .concat(cmdline.opts.app ? makeOption("app", cmdline.opts.app) : "") - .concat(cmdline.opts.version ? makeOption("version", cmdline.opts.version) : "") - .concat(cmdline.opts.password ? makeOption("password", cmdline.opts.password) : "") - .concat(cmdline.opts.reporter ? makeOption("reporter", cmdline.opts.reporter.toLowerCase()) : "") - .concat(cmdline.opts.quiet ? "--quiet" : ""); - - var testFunctions = files.map(function(file) { - return function(done) { + .concat(cmdline.opts.username ? makeOption("username", cmdline.opts.username) : "") + .concat(cmdline.opts.scheme ? makeOption("scheme", cmdline.opts.scheme) : "") + .concat(cmdline.opts.host ? makeOption("host", cmdline.opts.host) : "") + .concat(cmdline.opts.port ? makeOption("port", cmdline.opts.port) : "") + .concat(cmdline.opts.app ? makeOption("app", cmdline.opts.app) : "") + .concat(cmdline.opts.version ? makeOption("version", cmdline.opts.version) : "") + .concat(cmdline.opts.password ? makeOption("password", cmdline.opts.password) : "") + .concat(cmdline.opts.reporter ? makeOption("reporter", cmdline.opts.reporter.toLowerCase()) : "") + .concat(cmdline.opts.ui ? makeOption("ui", cmdline.opts.ui) : ["--ui", "bdd"]) + .concat(cmdline.opts.timeout ? makeOption("timeout", cmdline.opts.timeout) : ["--timeout", "5000"]) + .concat(cmdline.opts.grep ? makeOption("grep", cmdline.opts.grep) : "") + .concat(cmdline.opts.exit ? "--exit" : "--exit") + .concat(cmdline.opts.quiet ? "--quiet" : ""); + + var testFunctions = files.map(function (file) { + return function (done) { launch(file, cmdlineArgs, done); }; }); @@ -756,7 +777,7 @@ Async.series(testFunctions); }; - var hint = function() { + var hint = function () { var hintRequirePath = path.join(path.resolve(require.resolve('jshint'), './../../../'), 'lib', 'cli'); var jshint = require(hintRequirePath); jshint.interpret(['node', 'jshint', '.']); @@ -768,7 +789,7 @@ program .command('compile-sdk [global]') .description('Compile all SDK files into a single, browser-includable file.') - .action(function(globalName) { + .action(function (globalName) { compileSDK(false, globalName); }); @@ -785,7 +806,7 @@ program .command('compile [global]') .description('Compile all files into several single, browser-includable files.') - .action(function(globalName) { + .action(function (globalName) { compileAll(false, globalName); }); @@ -810,6 +831,10 @@ .option('--version ', 'Splunk version') .option('--namespace ', 'Splunk namespace (in the form of owner:app)') .option('--reporter ', '(optional) How to report results, currently "junit" is a valid reporter.') + .option('--ui ', 'Specify user interface') + .option('--timeout ', 'Specify test timeout threshold (in milliseconds)') + .option('--grep ', 'Only run tests matching this string or regexp') + .option('--exit', '(optional) Force Mocha to quit after tests complete') .option('--quiet', '(optional) Hides splunkd output.') .action(runTests); diff --git a/client/browser_async.js b/client/browser_async.js new file mode 100644 index 000000000..33229edfb --- /dev/null +++ b/client/browser_async.js @@ -0,0 +1,510 @@ +splunkjs.Logger.setLevel("ALL"); +var Async = splunkjs.Async; +var isBrowser = typeof "window" !== "undefined"; +assert = chai.assert; + +describe('Async Tests', function() { + it("While success", function(done) { + var i = 0; + Async.whilst( + function() { return i++ < 3; }, + function(done) { + Async.sleep(0, function() { done(); }); + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("While success deep", function(done) { + var i = 0; + Async.whilst( + function() { return i++ < (isBrowser ? 100 : 10000); }, + function(done) { + Async.sleep(0, function() { done(); }); + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("While error", function(done) { + var i = 0; + Async.whilst( + function() { return i++ < (isBrowser ? 100 : 10000); }, + function(done) { + Async.sleep(0, function() { done(i === (isBrowser ? 50 : 10000) ? 1 : null); }); + }, + function(err) { + assert.ok(err); + assert.strictEqual(err, 1); + done(); + } + ); + }); + + it("Whilst sans condition is never", function(done) { + var i = false; + Async.whilst( + undefined, + function(done) { i = true; done();}, + function(err) { + assert.strictEqual(i, false); + done(); + } + ); + }); + + it("Whilst with empty body does nothing", function(done) { + var i = true; + Async.whilst( + function() { + if (i) { + i = false; + return true; + } + else { + return i; + } + }, + undefined, + function (err) { + done(); + } + ); + }); + + it("Parallel success", function(done) { + Async.parallel([ + function(done) { + done(null, 1); + }, + function(done) { + done(null, 2, 3); + }], + function(err, one, two) { + assert.ok(!err); + assert.strictEqual(one, 1); + assert.strictEqual(two[0], 2); + assert.strictEqual(two[1], 3); + done(); + } + ); + }); + + it("Parallel success - outside of arrays", function(done) { + Async.parallel( + function(done) { done(null, 1);}, + function(done) { done(null, 2, 3); }, + function(err, one, two) { + assert.ok(!err); + assert.strictEqual(one, 1); + assert.strictEqual(two[0], 2); + assert.strictEqual(two[1], 3); + done(); + }); + }); + + it("Parallel success - no reordering", function(done) { + Async.parallel([ + function(done) { + Async.sleep(1, function() { done(null, 1); }); + }, + function(done) { + done(null, 2, 3); + }], + function(err, one, two) { + assert.ok(!err); + assert.strictEqual(one, 1); + assert.strictEqual(two[0], 2); + assert.strictEqual(two[1], 3); + done(); + } + ); + }); + + it("Parallel error", function(done) { + Async.parallel([ + function(done) { + done(null, 1); + }, + function(done) { + done(null, 2, 3); + }, + function(done) { + Async.sleep(0, function() { + done("ERROR"); + }); + }], + function(err, one, two) { + assert.ok(err === "ERROR"); + assert.ok(!one); + assert.ok(!two); + done(); + } + ); + }); + + it("Parallel no tasks", function(done) { + Async.parallel( + [], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Series success", function(done) { + Async.series([ + function(done) { + done(null, 1); + }, + function(done) { + done(null, 2, 3); + }], + function(err, one, two) { + assert.ok(!err); + assert.strictEqual(one, 1); + assert.strictEqual(two[0], 2); + assert.strictEqual(two[1], 3); + done(); + } + ); + }); + + it("Series success - outside of array", function(done) { + Async.series( + function(done) { + done(null, 1); + }, + function(done) { + done(null, 2, 3); + }, + function(err, one, two) { + assert.ok(!err); + assert.strictEqual(one, 1); + assert.strictEqual(two[0], 2); + assert.strictEqual(two[1], 3); + done(); + } + ); + }); + + it("Series reordering success", function(done) { + var keeper = 0; + Async.series([ + function(done) { + Async.sleep(10, function() { + assert.strictEqual(keeper++, 0); + done(null, 1); + }); + }, + function(done) { + assert.strictEqual(keeper++, 1); + done(null, 2, 3); + }], + function(err, one, two) { + assert.ok(!err); + assert.strictEqual(keeper, 2); + assert.strictEqual(one, 1); + assert.strictEqual(two[0], 2); + assert.strictEqual(two[1], 3); + done(); + } + ); + }); + + it("Series error", function(done) { + Async.series([ + function(done) { + done(null, 1); + }, + function(done) { + done("ERROR", 2, 3); + }], + function(err, one, two) { + assert.strictEqual(err, "ERROR"); + assert.ok(!one); + assert.ok(!two); + done(); + } + ); + }); + + it("Series no tasks", function(done) { + Async.series( + [], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Parallel map success", function(done) { + Async.parallelMap( + [1, 2, 3], + function(val, idx, done) { + done(null, val + 1); + }, + function(err, vals) { + assert.ok(!err); + assert.strictEqual(vals[0], 2); + assert.strictEqual(vals[1], 3); + assert.strictEqual(vals[2], 4); + done(); + } + ); + }); + + it("Parallel map reorder success", function(done) { + Async.parallelMap( + [1, 2, 3], + function(val, idx, done) { + if (val === 2) { + Async.sleep(100, function() { done(null, val+1); }); + } + else { + done(null, val + 1); + } + }, + function(err, vals) { + assert.strictEqual(vals[0], 2); + assert.strictEqual(vals[1], 3); + assert.strictEqual(vals[2], 4); + done(); + } + ); + }); + + it("Parallel map error", function(done) { + Async.parallelMap( + [1, 2, 3], + function(val, idx, done) { + if (val === 2) { + done(5); + } + else { + done(null, val + 1); + } + }, + function(err, vals) { + assert.ok(err); + assert.ok(!vals); + assert.strictEqual(err, 5); + done(); + } + ); + }); + + it("Series map success", function(done) { + var keeper = 1; + Async.seriesMap( + [1, 2, 3], + function(val, idx, done) { + assert.strictEqual(keeper++, val); + done(null, val + 1); + }, + function(err, vals) { + assert.ok(!err); + assert.strictEqual(vals[0], 2); + assert.strictEqual(vals[1], 3); + assert.strictEqual(vals[2], 4); + assert.strictEqual(vals[2], keeper); + done(); + } + ); + }); + + it("Series map error", function(done) { + Async.seriesMap( + [1, 2, 3], + function(val, idx, done) { + if (val === 2) { + done(5); + } + else { + done(null, val + 1); + } + }, + function(err, vals) { + assert.ok(err); + assert.ok(!vals); + assert.strictEqual(err, 5); + done(); + } + ); + }); + + it("Chain single success", function(done) { + Async.chain([ + function(callback) { + callback(null, 1); + }, + function(val, callback) { + callback(null, val + 1); + }, + function(val, callback) { + callback(null, val + 1); + }], + function(err, val) { + assert.ok(!err); + assert.strictEqual(val, 3); + done(); + } + ); + }); + + it("Chain flat single success", function(done) { + Async.chain( + function(callback) { + callback(null, 1); + }, + function(val, callback) { + callback(null, val + 1); + }, + function(val, callback) { + callback(null, val + 1); + }, + function(err, val) { + assert.ok(!err); + assert.strictEqual(val, 3); + done(); + } + ); + }); + + it("Chain flat multiple success", function(done) { + Async.chain( + function(callback) { + callback(null, 1, 2); + }, + function(val1, val2, callback) { + callback(null, val1 + 1, val2 + 1); + }, + function(val1, val2, callback) { + callback(null, val1 + 1, val2 + 1); + }, + function(err, val1, val2) { + assert.ok(!err); + assert.strictEqual(val1, 3); + assert.strictEqual(val2, 4); + done(); + } + ); + }); + + it("Chain flat arity change success", function(done) { + Async.chain( + function(callback) { + callback(null, 1, 2); + }, + function(val1, val2, callback) { + callback(null, val1 + 1); + }, + function(val1, callback) { + callback(null, val1 + 1, 5); + }, + function(err, val1, val2) { + assert.ok(!err); + assert.strictEqual(val1, 3); + assert.strictEqual(val2, 5); + done(); + } + ); + }); + + it("Chain error", function(done) { + Async.chain([ + function(callback) { + callback(null, 1, 2); + }, + function(val1, val2, callback) { + callback(5, val1 + 1); + }, + function(val1, callback) { + callback(null, val1 + 1, 5); + }], + function(err, val1, val2) { + assert.ok(err); + assert.ok(!val1); + assert.ok(!val2); + assert.strictEqual(err, 5); + done(); + } + ); + }); + + it("Chain no tasks", function(done) { + Async.chain([], + function(err, val1, val2) { + assert.ok(!err); + assert.ok(!val1); + assert.ok(!val2); + done(); + } + ); + }); + + it("Parallel each reodrder success", function(done) { + var total = 0; + Async.parallelEach( + [1, 2, 3], + function(val, idx, done) { + var go = function() { + total += val; + done(); + }; + + if (idx === 1) { + Async.sleep(100, go); + } + else { + go(); + } + }, + function(err) { + assert.ok(!err); + assert.strictEqual(total, 6); + done(); + } + ); + }); + + it("Series each success", function(done) { + var results = [1, 3, 6]; + var total = 0; + Async.seriesEach( + [1, 2, 3], + function(val, idx, done) { + total += val; + assert.strictEqual(total, results[idx]); + done(); + }, + function(err) { + assert.ok(!err); + assert.strictEqual(total, 6); + done(); + } + ); + }); + + it("Augment callback", function(done) { + var callback = function(a, b) { + assert.ok(a); + assert.ok(b); + assert.strictEqual(a, 1); + assert.strictEqual(b, 2); + + done(); + }; + + var augmented = Async.augment(callback, 2); + augmented(1); + }); +}); \ No newline at end of file diff --git a/client/browser_context.js b/client/browser_context.js new file mode 100644 index 000000000..a1b7d815c --- /dev/null +++ b/client/browser_context.js @@ -0,0 +1,1069 @@ +// var tutils = require('./utils'); +var Async = splunkjs.Async; +var utils = splunkjs.Utils; + +splunkjs.Logger.setLevel("ALL"); +var isBrowser = typeof window !== "undefined"; + +// (function() { +// "use strict"; +// var Async = require('../lib/async'); + +// var root = exports || this; + +// root.DummyHttp = { +// // Required by Context.init() +// _setSplunkVersion: function(version) { +// // nothing +// } +// }; +// })(); + +describe('Context tests', function() { + before(function(){ + this.service = svc; + }) + describe('General Context Test', function() { + before(function(){ + console.log(svc); + this.service = svc; + }) + + it("Service exists", function(done) { + assert.ok(this.service); + done(); + }); + + it("Create test search", function(done) { + // The search created here is used by several of the following tests, specifically those using get() + var searchID = "DELETEME_JSSDK_UNITTEST"; + this.service.post("search/jobs", {search: "search index=_internal | head 1", exec_mode: "blocking", id: searchID}, function(err, res) { + assert.ok(res.data.sid); + done(); + }); + }); + + it("Callback#login", function(done) { + var newService = new splunkjs.Service(svc.http, { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password, + version: svc.version + }); + + newService.login(function(err, success) { + assert.ok(success); + done(); + }); + }); + + it("Callback#login fail", function(done) { + var newService = new splunkjs.Service(svc.http, { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password + "wrong_password", + version: svc.version + }); + if (!isBrowser) { + newService.login(function(err, success) { + assert.ok(err); + assert.ok(!success); + done(); + }); + } + else { + done(); + } + }); + + it("Callback#get", function(done) { + this.service.get("search/jobs", {count: 1}, function(err, res) { + assert.strictEqual(res.data.paging.offset, 0); + assert.ok(res.data.entry.length <= res.data.paging.total); + assert.strictEqual(res.data.entry.length, 1); + assert.ok(res.data.entry[0].content.sid); + done(); + }); + }); + + it("Callback#get error", function(done) { + this.service.get("search/jobs/1234_nosuchjob", {}, function(res) { + assert.ok(!!res); + assert.strictEqual(res.status, 404); + done(); + }); + }); + + it("Callback#get autologin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + version: svc.version + } + ); + + service.get("search/jobs", {count: 1}, function(err, res) { + assert.strictEqual(res.data.paging.offset, 0); + assert.ok(res.data.entry.length <= res.data.paging.total); + assert.strictEqual(res.data.entry.length, 1); + assert.ok(res.data.entry[0].content.sid); + done(); + }); + }); + + it("Callback#get autologin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + version: svc.version + } + ); + + service.get("search/jobs", {count: 1}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + + it("Callback#get autologin - disabled", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + autologin: false, + version: svc.version + } + ); + + service.get("search/jobs", {count: 1}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#get relogin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + service.get("search/jobs", {count: 1}, function(err, res) { + assert.ok(!err); + assert.strictEqual(res.data.paging.offset, 0); + assert.ok(res.data.entry.length <= res.data.paging.total); + assert.strictEqual(res.data.entry.length, 1); + assert.ok(res.data.entry[0].content.sid); + done(); + }); + }); + + it("Callback#get relogin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + service.get("search/jobs", {count: 1}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#post", function(done) { + var service = this.service; + this.service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + var endpoint = "search/jobs/" + sid + "/control"; + service.post(endpoint, {action: "cancel"}, function(err, res) { + done(); + } + ); + } + ); + }); + + it("Callback#post error", function(done) { + this.service.post("search/jobs", {search: "index_internal | head 1"}, function(res) { + assert.ok(!!res); + assert.strictEqual(res.status, 400); + done(); + }); + }); + + it("Callback#post autologin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + var endpoint = "search/jobs/" + sid + "/control"; + service.post(endpoint, {action: "cancel"}, function(err, res) { + done(); + } + ); + } + ); + }); + + it("Callback#post autologin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#post autologin - disabled", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + autologin: false, + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#post relogin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + var endpoint = "search/jobs/" + sid + "/control"; + service.post(endpoint, {action: "cancel"}, function(err, res) { + done(); + } + ); + } + ); + }); + + it("Callback#post relogin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#delete", function(done) { + var service = this.service; + this.service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + var endpoint = "search/jobs/" + sid; + service.del(endpoint, {}, function(err, res) { + done(); + }); + }); + }); + + it("Callback#delete error", function(done) { + this.service.del("search/jobs/1234_nosuchjob", {}, function(res) { + assert.ok(!!res); + assert.strictEqual(res.status, 404); + done(); + }); + }); + + it("Callback#delete autologin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + service.sessionKey = null; + var endpoint = "search/jobs/" + sid; + service.del(endpoint, {}, function(err, res) { + done(); + }); + }); + }); + + it("Callback#delete autologin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + version: svc.version + } + ); + + service.del("search/jobs/NO_SUCH_SID", {}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#delete autologin - disabled", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + autologin: false, + version: svc.version + } + ); + + service.del("search/jobs/NO_SUCH_SID", {}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#delete relogin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + service.post("search/jobs", {search: "search index=_internal | head 1"}, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + service.sessionKey = "ABCDEF-not-real"; + var endpoint = "search/jobs/" + sid; + service.del(endpoint, {}, function(err, res) { + done(); + }); + }); + }); + + it("Callback#delete relogin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + service.del("search/jobs/NO_SUCH_SID", {}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#request get", function(done) { + var get = {count: 1}; + var post = null; + var body = null; + this.service.request("search/jobs", "GET", get, post, body, {"X-TestHeader": 1}, function(err, res) { + assert.strictEqual(res.data.paging.offset, 0); + assert.ok(res.data.entry.length <= res.data.paging.total); + assert.strictEqual(res.data.entry.length, 1); + assert.ok(res.data.entry[0].content.sid); + + if (res.response.request) { + assert.strictEqual(res.response.request.headers["X-TestHeader"], 1); + } + + done(); + }); + }); + + it("Callback#request post", function(done) { + var body = "search="+encodeURIComponent("search index=_internal | head 1"); + var headers = { + "Content-Type": "application/x-www-form-urlencoded" + }; + var service = this.service; + this.service.request("search/jobs", "POST", null, null, body, headers, function(err, res) { + var sid = res.data.sid; + assert.ok(sid); + + var endpoint = "search/jobs/" + sid + "/control"; + service.post(endpoint, {action: "cancel"}, function(err, res) { + done(); + }); + }); + }); + + it("Callback#request error", function(done) { + this.service.request("search/jobs/1234_nosuchjob", "GET", null, null, null, {"X-TestHeader": 1}, function(res) { + assert.ok(!!res); + + if (res.response.request) { + assert.strictEqual(res.response.request.headers["X-TestHeader"], 1); + } + + assert.strictEqual(res.status, 404); + done(); + }); + }); + + it("Callback#request autologin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + version: svc.version + } + ); + + var get = {count: 1}; + var post = null; + var body = null; + service.request("search/jobs", "GET", get, post, body, {"X-TestHeader": 1}, function(err, res) { + assert.strictEqual(res.data.paging.offset, 0); + assert.ok(res.data.entry.length <= res.data.paging.total); + assert.strictEqual(res.data.entry.length, 1); + assert.ok(res.data.entry[0].content.sid); + + if (res.response.request) { + assert.strictEqual(res.response.request.headers["X-TestHeader"], 1); + } + + done(); + }); + }); + + it("Callback#request autologin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + version: svc.version + } + ); + + var get = {count: 1}; + var post = null; + var body = null; + service.request("search/jobs", "GET", get, post, body, {"X-TestHeader": 1}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#request autologin - disabled", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + autologin: false, + version: svc.version + } + ); + + var get = {count: 1}; + var post = null; + var body = null; + service.request("search/jobs", "GET", get, post, body, {"X-TestHeader": 1}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#request relogin - success", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + var get = {count: 1}; + var post = null; + var body = null; + service.request("search/jobs", "GET", get, post, body, {"X-TestHeader": 1}, function(err, res) { + assert.strictEqual(res.data.paging.offset, 0); + assert.ok(res.data.entry.length <= res.data.paging.total); + assert.strictEqual(res.data.entry.length, 1); + assert.ok(res.data.entry[0].content.sid); + + if (res.response.request) { + assert.strictEqual(res.response.request.headers["X-TestHeader"], 1); + } + + done(); + }); + }); + + it("Callback#request relogin - error", function(done) { + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password + "ABC", + sessionKey: "ABCDEF-not-real", + version: svc.version + } + ); + + var get = {count: 1}; + var post = null; + var body = null; + service.request("search/jobs", "GET", get, post, body, {"X-TestHeader": 1}, function(err, res) { + assert.ok(err); + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("Callback#abort", function(done) { + var req = this.service.get("search/jobs", {count: 1}, function(err, res) { + assert.ok(!res); + assert.ok(err); + assert.strictEqual(err.error, "abort"); + assert.strictEqual(err.status, "abort"); + done(); + }); + + req.abort(); + }); + + it("Callback#timeout default test", function(done){ + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + version: svc.version + } + ); + + assert.strictEqual(0, service.timeout); + service.request("search/jobs", "GET", {count:1}, null, null, {"X-TestHeader":1}, function(err, res){ + assert.ok(res); + done(); + }); + }); + + it("Callback#timeout timed test", function(done){ + var service = new splunkjs.Service(svc.http, + { + scheme: this.service.scheme, + host: this.service.host, + port: this.service.port, + username: this.service.username, + password: this.service.password, + version: svc.version, + timeout: 10000 + } + ); + + assert.strictEqual(service.timeout, 10000); + service.request("search/jobs", "GET", {count:1}, null, null, {"X-TestHeader":1}, function(err, res){ + assert.ok(res); + done(); + }); + }); + + // This test is not stable, commenting it out until we figure it out + // "Callback#timeout fail -- FAILS INTERMITTENTLY", function(done){ + // var service = new splunkjs.Service( + // { + // scheme: this.service.scheme, + // host: this.service.host, + // port: this.service.port, + // username: this.service.username, + // password: this.service.password, + // version: svc.version, + // timeout: 3000 + // } + // ); + + // // Having a timeout of 3 seconds, a max_time of 5 seconds with a blocking mode and searching realtime should involve a timeout error. + // service.get("search/jobs/export", {search:"search index=_internal", timeout:2, max_time:5, search_mode:"realtime", exec_mode:"blocking"}, function(err, res){ + // assert.ok(err); + // // Prevent test suite from erroring out if `err` is null, just fail the test + // if (err) { + // assert.strictEqual(err.status, 600); + // } + // done(); + // }); + // }, + + it("Cancel test search", function(done) { + // Here, the search created for several of the previous tests is terminated, it is no longer necessary + var endpoint = "search/jobs/DELETEME_JSSDK_UNITTEST/control"; + this.service.post(endpoint, {action: "cancel"}, function(err, res) { + done(); + }); + }); + + it("fullpath gets its owner/app from the right places", function(done) { + var http = DummyHttp; + var ctx = new splunkjs.Context(http, { /*nothing*/ }); + + // Absolute paths are unchanged + assert.strictEqual(ctx.fullpath("/a/b/c"), "/a/b/c"); + // Fall through to /services if there is no app + assert.strictEqual(ctx.fullpath("meep"), "/services/meep"); + // Are username and app set properly? + var ctx2 = new splunkjs.Context(http, {owner: "alpha", app: "beta"}); + assert.strictEqual(ctx2.fullpath("meep"), "/servicesNS/alpha/beta/meep"); + assert.strictEqual(ctx2.fullpath("meep", {owner: "boris"}), "/servicesNS/boris/beta/meep"); + assert.strictEqual(ctx2.fullpath("meep", {app: "factory"}), "/servicesNS/alpha/factory/meep"); + assert.strictEqual(ctx2.fullpath("meep", {owner: "boris", app: "factory"}), "/servicesNS/boris/factory/meep"); + // Sharing settings + assert.strictEqual(ctx2.fullpath("meep", {sharing: "app"}), "/servicesNS/nobody/beta/meep"); + assert.strictEqual(ctx2.fullpath("meep", {sharing: "global"}), "/servicesNS/nobody/beta/meep"); + assert.strictEqual(ctx2.fullpath("meep", {sharing: "system"}), "/servicesNS/nobody/system/meep"); + // Do special characters get encoded? + var ctx3 = new splunkjs.Context(http, {owner: "alpha@beta.com", app: "beta"}); + assert.strictEqual(ctx3.fullpath("meep"), "/servicesNS/alpha%40beta.com/beta/meep"); + done(); + }); + + it("version check", function(done) { + var http = DummyHttp; + var ctx; + + ctx = new splunkjs.Context(http, { "version": "4.0" }); + assert.ok(ctx.version === "4.0"); + + ctx = new splunkjs.Context(http, { "version": "4.0" }); + assert.ok(ctx.versionCompare("5.0") === -1); + ctx = new splunkjs.Context(http, { "version": "4" }); + assert.ok(ctx.versionCompare("5.0") === -1); + ctx = new splunkjs.Context(http, { "version": "4.0" }); + assert.ok(ctx.versionCompare("5") === -1); + ctx = new splunkjs.Context(http, { "version": "4.1" }); + assert.ok(ctx.versionCompare("4.9") === -1); + + ctx = new splunkjs.Context(http, { "version": "4.0" }); + assert.ok(ctx.versionCompare("4.0") === 0); + ctx = new splunkjs.Context(http, { "version": "4" }); + assert.ok(ctx.versionCompare("4.0") === 0); + ctx = new splunkjs.Context(http, { "version": "4.0" }); + assert.ok(ctx.versionCompare("4") === 0); + + ctx = new splunkjs.Context(http, { "version": "5.0" }); + assert.ok(ctx.versionCompare("4.0") === 1); + ctx = new splunkjs.Context(http, { "version": "5.0" }); + assert.ok(ctx.versionCompare("4") === 1); + ctx = new splunkjs.Context(http, { "version": "5" }); + assert.ok(ctx.versionCompare("4.0") === 1); + ctx = new splunkjs.Context(http, { "version": "4.9" }); + assert.ok(ctx.versionCompare("4.1") === 1); + + ctx = new splunkjs.Context(http, { /*nothing*/ }); + assert.ok(ctx.versionCompare("5.0") === 0); + + done(); + }); + }); + + describe('Cookie Tests', function() { + before(function(){ + this.service = svc; + this.skip = false; + var that = this; + svc.serverInfo(function(err, info) { + var majorVersion = parseInt(info.properties().version.split(".")[0], 10); + var minorVersion = parseInt(info.properties().version.split(".")[1], 10); + // Skip cookie tests if Splunk older than 6.2 + if(majorVersion < 6 || (majorVersion === 6 && minorVersion < 2)) { + that.skip = true; + splunkjs.Logger.log("Skipping cookie tests..."); + } + done(); + }); + }) + + afterEach(function(){ + this.service.logout(); + }) + + it("_getCookieString works as expected", function(done){ + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port + }); + + service.http._cookieStore = { + 'cookie' : 'format', + 'another' : 'one' + }; + + var expectedCookieString = 'cookie=format; another=one; '; + var cookieString = service.http._getCookieString(); + + assert.strictEqual(cookieString, expectedCookieString); + done(); + }); + + it("login and store cookie", function(done){ + if(this.skip){ + done(); + return; + } + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password, + version: svc.version + }); + + // Check that there are no cookies + assert.ok(utils.isEmpty(service.http._cookieStore)); + + + service.login(function(err, success) { + // Check that cookies were saved + assert.ok(!utils.isEmpty(service.http._cookieStore)); + assert.notStrictEqual(service.http._getCookieString(), ''); + done(); + }); + }); + + it("request with cookie", function(done) { + if(this.skip){ + done(); + return; + } + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password, + version: svc.version + }); + // Create another service to put valid cookie into, give no other authentication information + var service2 = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + version: svc.version + }); + + // Login to service to get a valid cookie + Async.chain([ + function (done) { + service.login(done); + }, + function (job, done) { + // Save the cookie store + var cookieStore = service.http._cookieStore; + // Test that there are cookies + assert.ok(!utils.isEmpty(cookieStore)); + // Add the cookies to a service with no other authentication information + service2.http._cookieStore = cookieStore; + // Make a request that requires authentication + service2.get("search/jobs", {count: 1}, done); + }, + function (res, done) { + // Test that a response was returned + assert.ok(res); + done(); + } + ], + function(err) { + // Test that no errors were returned + assert.ok(!err); + done(); + } + ); + }); + + it("request fails with bad cookie", function(done) { + if(this.skip){ + done(); + return; + } + // Create a service with no login information + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + version: svc.version + }); + + // Put a bad cookie into the service + service.http._cookieStore = { "bad" : "cookie" }; + + // Try requesting something that requires authentication + service.get("search/jobs", {count: 1}, function(err, res) { + // Test if an error is returned + assert.ok(err); + // Check that it is an unauthorized error + assert.strictEqual(err.status, 401); + done(); + }); + }); + + it("autologin with cookie", function(done) { + if(this.skip){ + done(); + return; + } + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password, + version: svc.version + }); + + // Test if service has no cookies + assert.ok(utils.isEmpty(service.http._cookieStore)); + + service.get("search/jobs", {count: 1}, function(err, res) { + // Test if service now has a cookie + assert.ok(service.http._cookieStore); + done(); + }); + }); + + it("login fails with no cookie and no sessionKey", function(done) { + if(this.skip){ + done(); + return; + } + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + version: svc.version + }); + + // Test there is no authentication information + assert.ok(utils.isEmpty(service.http._cookieStore)); + assert.strictEqual(service.sessionKey, ''); + assert.ok(!service.username); + assert.ok(!service.password); + + service.get("search/jobs", {count: 1}, function(err, res) { + // Test if an error is returned + assert.ok(err); + done(); + }); + }); + + it("login with multiple cookies", function(done) { + if(this.skip){ + done(); + return; + } + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password, + version: svc.version + }); + // Create another service to put valid cookie into, give no other authentication information + var service2 = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + version: svc.version + }); + + // Login to service to get a valid cookie + Async.chain([ + function (done) { + service.login(done); + }, + function (job, done) { + // Save the cookie store + var cookieStore = service.http._cookieStore; + // Test that there are cookies + assert.ok(!utils.isEmpty(cookieStore)); + + // Add a bad cookie to the cookieStore + cookieStore['bad'] = 'cookie'; + + // Add the cookies to a service with no other authenitcation information + service2.http._cookieStore = cookieStore; + + // Make a request that requires authentication + service2.get("search/jobs", {count: 1}, done); + }, + function (res, done) { + // Test that a response was returned + assert.ok(res); + done(); + } + ], + function(err) { + // Test that no errors were returned + assert.ok(!err); + done(); + } + ); + }); + + it("autologin with cookie and bad sessionKey", function(done) { + if(this.skip){ + done(); + return; + } + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, port: svc.port, + username: svc.username, + password: svc.password, + sessionKey: 'ABC-BADKEY', + version: svc.version + }); + + // Test if service has no cookies + assert.ok(utils.isEmpty(service.http._cookieStore)); + + service.get("search/jobs", {count: 1}, function(err, res) { + // Test if service now has a cookie + assert.ok(service.http._cookieStore); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/client/browser_service.js b/client/browser_service.js new file mode 100644 index 000000000..fc0e9ef44 --- /dev/null +++ b/client/browser_service.js @@ -0,0 +1,6744 @@ + +var Async = splunkjs.Async; +var utils = splunkjs.Utils; +assert = chai.assert; + +var idCounter = 0; +var getNextId = function() { + return "id" + (idCounter++) + "_" + ((new Date()).valueOf()); +}; + +describe("Service Tests ", function(){ + + describe("Namespace Tests",function () { + before(function (finished) { + this.service = svc; + var that = this; + + var appName1 = "jssdk_testapp_" + getNextId(); + var appName2 = "jssdk_testapp_" + getNextId(); + + var userName1 = "jssdk_testuser_" + getNextId(); + var userName2 = "jssdk_testuser_" + getNextId(); + + var apps = this.service.apps(); + var users = this.service.users(); + + this.namespace11 = {owner: userName1, app: appName1}; + this.namespace12 = {owner: userName1, app: appName2}; + this.namespace21 = {owner: userName2, app: appName1}; + this.namespace22 = {owner: userName2, app: appName2}; + + Async.chain([ + function (done) { + apps.create({name: appName1}, done); + }, + function (app1, done) { + that.app1 = app1; + that.appName1 = appName1; + apps.create({name: appName2}, done); + }, + function (app2, done) { + that.app2 = app2; + that.appName2 = appName2; + users.create({name: userName1, password: "abcdefg!", roles: ["user"]}, done); + }, + function (user1, done) { + that.user1 = user1; + that.userName1 = userName1; + users.create({name: userName2, password: "abcdefg!", roles: ["user"]}, done); + }, + function (user2, done) { + that.user2 = user2; + that.userName2 = userName2; + + done(); + } + ], + function (err) { + finished(err); + } + ); + }); + + it("Callback#Namespace protection", function(done) { + var searchName = "jssdk_search_" + getNextId(); + var search = "search *"; + var service = this.service; + + var savedSearches11 = service.savedSearches(this.namespace11); + var savedSearches21 = service.savedSearches(this.namespace21); + + var that = this; + Async.chain([ + function(done) { + // Create the saved search only in the 11 namespace + savedSearches11.create({name: searchName, search: search}, done); + }, + function(savedSearch, done) { + // Refresh the 11 saved searches + savedSearches11.fetch(done); + }, + function(savedSearches, done) { + // Refresh the 21 saved searches + savedSearches21.fetch(done); + }, + function(savedSearches, done) { + var entity11 = savedSearches11.item(searchName); + var entity21 = savedSearches21.item(searchName); + + // Make sure the saved search exists in the 11 namespace + assert.ok(entity11); + assert.strictEqual(entity11.name, searchName); + assert.strictEqual(entity11.properties().search, search); + + // Make sure the saved search doesn't exist in the 11 namespace + assert.ok(!entity21); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Namespace item", function(done) { + var searchName = "jssdk_search_" + getNextId(); + var search = "search *"; + var service = this.service; + + var namespace_1 = {owner: "-", app: this.appName1}; + var namespace_nobody1 = {owner: "nobody", app: this.appName1}; + + var savedSearches11 = service.savedSearches(this.namespace11); + var savedSearches21 = service.savedSearches(this.namespace21); + var savedSearches_1 = service.savedSearches(namespace_1); + var savedSearches_nobody1 = service.savedSearches(namespace_nobody1); + + var that = this; + Async.chain([ + function(done) { + // Create a saved search in the 11 namespace + savedSearches11.create({name: searchName, search: search}, done); + }, + function(savedSearch, done) { + // Create a saved search in the 21 namespace + savedSearches21.create({name: searchName, search: search}, done); + }, + function(savedSearch, done) { + // Refresh the -/1 namespace + savedSearches_1.fetch(done); + }, + function(savedSearches, done) { + // Refresh the 1/1 namespace + savedSearches11.fetch(done); + }, + function(savedSearches, done) { + // Refresh the 2/1 namespace + savedSearches21.fetch(done); + }, + function(savedSearches, done) { + var entity11 = savedSearches11.item(searchName, that.namespace11); + var entity21 = savedSearches21.item(searchName, that.namespace21); + + // Ensure that the saved search exists in the 11 namespace + assert.ok(entity11); + assert.strictEqual(entity11.name, searchName); + assert.strictEqual(entity11.properties().search, search); + assert.strictEqual(entity11.namespace.owner, that.namespace11.owner); + assert.strictEqual(entity11.namespace.app, that.namespace11.app); + + // Ensure that the saved search exists in the 21 namespace + assert.ok(entity21); + assert.strictEqual(entity21.name, searchName); + assert.strictEqual(entity21.properties().search, search); + assert.strictEqual(entity21.namespace.owner, that.namespace21.owner); + assert.strictEqual(entity21.namespace.app, that.namespace21.app); + + done(); + }, + function(done) { + // Create a saved search in the nobody/1 namespace + savedSearches_nobody1.create({name: searchName, search: search}, done); + }, + function(savedSearch, done) { + // Refresh the 1/1 namespace + savedSearches11.fetch(done); + }, + function(savedSearches, done) { + // Refresh the 2/1 namespace + savedSearches21.fetch(done); + }, + function(savedSearches, done) { + // Ensure that we can't get the item from the generic + // namespace without specifying a namespace + try { + savedSearches_1.item(searchName); + assert.ok(false); + } + catch(err) { + assert.ok(err); + } + + // Ensure that we can't get the item using wildcard namespaces. + try{ + savedSearches_1.item(searchName, {owner:'-'}); + assert.ok(false); + } + catch(err){ + assert.ok(err); + } + + try{ + savedSearches_1.item(searchName, {app:'-'}); + assert.ok(false); + } + catch(err){ + assert.ok(err); + } + + try{ + savedSearches_1.item(searchName, {app:'-', owner:'-'}); + assert.ok(false); + } + catch(err){ + assert.ok(err); + } + + // Ensure we get the right entities from the -/1 namespace when we + // specify it. + var entity11 = savedSearches_1.item(searchName, that.namespace11); + var entity21 = savedSearches_1.item(searchName, that.namespace21); + + assert.ok(entity11); + assert.strictEqual(entity11.name, searchName); + assert.strictEqual(entity11.properties().search, search); + assert.strictEqual(entity11.namespace.owner, that.namespace11.owner); + assert.strictEqual(entity11.namespace.app, that.namespace11.app); + + assert.ok(entity21); + assert.strictEqual(entity21.name, searchName); + assert.strictEqual(entity21.properties().search, search); + assert.strictEqual(entity21.namespace.owner, that.namespace21.owner); + assert.strictEqual(entity21.namespace.app, that.namespace21.app); + + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#delete test applications", function(done) { + var apps = this.service.apps(); + apps.fetch(function(err, apps) { + assert.ok(!err); + assert.ok(apps); + var appList = apps.list(); + + Async.parallelEach( + appList, + function(app, idx, callback) { + if (utils.startsWith(app.name, "jssdk_")) { + app.remove(callback); + } + else { + callback(); + } + }, function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + it("Callback#delete test users", function(done) { + var users = this.service.users(); + users.fetch(function(err, users) { + var userList = users.list(); + + Async.parallelEach( + userList, + function(user, idx, callback) { + if (utils.startsWith(user.name, "jssdk_")) { + user.remove(callback); + } + else { + callback(); + } + }, function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + }); + + describe("Job Tests", function() { + before (function(done) { + idCounter=0; + this.service = svc; + done(); + }); + + // Disabling the test for now because the apps/appinstall endpoint have been deprecated from Splunk 8.2 + // + // "Callback#Create+abort job", function(done) { + // var service = this.service; + // Async.chain([ + // function(done){ + // var app_name = path.join(process.env.SPLUNK_HOME, ('/etc/apps/sdk-app-collection/build/sleep_command.tar')); + // // Fix path on Windows if $SPLUNK_HOME contains a space (ex: C:/Program%20Files/Splunk) + // app_name = app_name.replace("%20", " "); + // service.post("apps/appinstall", {update:1, name:app_name}, done); + // }, + // function(done){ + // var sid = getNextId(); + // var options = {id: sid}; + // var jobs = service.jobs({app: "sdk-app-collection"}); + // var req = jobs.oneshotSearch('search index=_internal | head 1 | sleep 10', options, function(err, job) { + // assert.ok(err); + // assert.ok(!job); + // assert.strictEqual(err.error, "abort"); + // done(); + // }); + + // Async.sleep(1000, function(){ + // req.abort(); + // }); + // } + // ], + // function(err){ + // assert.ok(!err); + // done(); + // }); + // }, + + it("Callback#Create+cancel job", function(done) { + var sid = getNextId(); + this.service.jobs().search('search index=_internal | head 1', {id: sid}, function(err, job) { + assert.ok(job); + assert.strictEqual(job.sid, sid); + + job.cancel(function() { + done(); + }); + }); + }); + + it("Callback#Create job error", function(done) { + var sid = getNextId(); + this.service.jobs().search({search: 'index=_internal | head 1', id: sid}, function(err) { + assert.ok(!!err); + done(); + }); + }); + + it("Callback#List jobs", function(done) { + this.service.jobs().fetch(function(err, jobs) { + assert.ok(!err); + assert.ok(jobs); + + var jobsList = jobs.list(); + assert.ok(jobsList.length > 0); + + for(var i = 0; i < jobsList.length; i++) { + assert.ok(jobsList[i]); + } + + done(); + }); + }); + + it("Callback#Contains job", function(done) { + var that = this; + var sid = getNextId(); + var jobs = this.service.jobs(); + + jobs.search('search index=_internal | head 1', {id: sid}, function(err, job) { + assert.ok(!err); + assert.ok(job); + assert.strictEqual(job.sid, sid); + + jobs.fetch(function(err, jobs) { + assert.ok(!err); + var job = jobs.item(sid); + assert.ok(job); + + job.cancel(function() { + done(); + }); + }); + }); + }); + + it("Callback#job results", function(done) { + var sid = getNextId(); + var service = this.service; + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 1 | stats count', {id: sid}, done); + }, + function(job, done) { + assert.strictEqual(job.sid, sid); + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + job.results({}, done); + }, + function(results, job, done) { + assert.strictEqual(results.rows.length, 1); + assert.strictEqual(results.fields.length, 1); + assert.strictEqual(results.fields[0], "count"); + assert.strictEqual(results.rows[0][0], "1"); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#job events", function(done) { + var sid = getNextId(); + var service = this.service; + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 1', {id: sid}, done); + }, + function(job, done) { + assert.strictEqual(job.sid, sid); + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + job.events({}, done); + }, + function(results, job, done) { + assert.strictEqual(results.rows.length, 1); + assert.strictEqual(results.fields.length, results.rows[0].length); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#job results preview", function(done) { + var sid = getNextId(); + var service = this.service; + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 1 | stats count', {id: sid}, done); + }, + function(job, done) { + assert.strictEqual(job.sid, sid); + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + job.preview({}, done); + }, + function(results, job, done) { + assert.strictEqual(results.rows.length, 1); + assert.strictEqual(results.fields.length, 1); + assert.strictEqual(results.fields[0], "count"); + assert.strictEqual(results.rows[0][0], "1"); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#job results iterator", function(done) { + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 10', {}, done); + }, + function(job, done) { + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + var iterator = job.iterator("results", { pagesize: 4 }); + var hasMore = true; + var numElements = 0; + var pageSizes = []; + Async.whilst( + function() { return hasMore; }, + function(nextIteration) { + iterator.next(function(err, results, _hasMore) { + if (err) { + nextIteration(err); + return; + } + + hasMore = _hasMore; + if (hasMore) { + pageSizes.push(results.rows.length); + } + nextIteration(); + }); + }, + function(err) { + assert.deepEqual(pageSizes, [4,4,2]); + done(err); + } + ); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + // Disabling the test for now because the apps/appinstall endpoint have been deprecated from Splunk 8.2 + // + // "Callback#Enable + disable preview", function(done) { + // var that = this; + // var sid = getNextId(); + + // var service = this.service.specialize("nobody", "sdk-app-collection"); + + // Async.chain([ + // function(done) { + // service.jobs().search('search index=_internal | head 1 | sleep 60', {id: sid}, done); + // }, + // function(job, done) { + // job.enablePreview(done); + + // }, + // function(job, done) { + // job.disablePreview(done); + // }, + // function(job, done) { + // job.cancel(done); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // }, + + // Disabling the test for now because the apps/appinstall endpoint have been deprecated from Splunk 8.2 + // + // "Callback#Pause + unpause + finalize preview", function(done) { + // var that = this; + // var sid = getNextId(); + + // var service = this.service.specialize("nobody", "sdk-app-collection"); + + // Async.chain([ + // function(done) { + // service.jobs().search('search index=_internal | head 1 | sleep 5', {id: sid}, done); + // }, + // function(job, done) { + // job.pause(done); + // }, + // function(job, done) { + // tutils.pollUntil( + // job, + // function(j) { + // return j.properties()["isPaused"]; + // }, + // 10, + // done + // ); + // }, + // function(job, done) { + // assert.ok(job.properties()["isPaused"]); + // job.unpause(done); + // }, + // function(job, done) { + // tutils.pollUntil( + // job, + // function(j) { + // return !j.properties()["isPaused"]; + // }, + // 10, + // done + // ); + // }, + // function(job, done) { + // assert.ok(!job.properties()["isPaused"]); + // job.finalize(done); + // }, + // function(job, done) { + // job.cancel(done); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // }, + + it("Callback#Set TTL", function(done) { + var sid = getNextId(); + var originalTTL = 0; + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 1', {id: sid}, done); + }, + function(job, done) { + job.fetch(done); + }, + function(job, done) { + var ttl = job.properties()["ttl"]; + originalTTL = ttl; + + job.setTTL(ttl*2, done); + }, + function(job, done) { + job.fetch(done); + }, + function(job, done) { + var ttl = job.properties()["ttl"]; + assert.ok(ttl > originalTTL); + assert.ok(ttl <= (originalTTL*2)); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + // Disabling the test for now because the apps/appinstall endpoint have been deprecated from Splunk 8.2 + // + // "Callback#Set priority", function(done) { + // var sid = getNextId(); + // var originalPriority = 0; + // var that = this; + + // var service = this.service.specialize("nobody", "sdk-app-collection"); + + // Async.chain([ + // function(done) { + // service.jobs().search('search index=_internal | head 1 | sleep 5', {id: sid}, done); + // }, + // function(job, done) { + // job.track({}, { + // ready: function(job) { + // done(null, job); + // } + // }); + // }, + // function(job, done) { + // var priority = job.properties()["priority"]; + // assert.ok(priority, 5); + // job.setPriority(priority + 1, done); + // }, + // function(job, done) { + // job.fetch(done); + // }, + // function(job, done) { + // job.cancel(done); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // }, + + it("Callback#Search log", function(done) { + var sid = getNextId(); + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 1', {id: sid, exec_mode: "blocking"}, done); + }, + function(job, done) { + job.searchlog(done); + }, + function(log, job, done) { + assert.ok(job); + assert.ok(log); + assert.ok(log.length > 0); + assert.ok(log.split("\r\n").length > 0); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Search summary", function(done) { + var sid = getNextId(); + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search( + 'search index=_internal | head 1 | eval foo="bar" | fields foo', + { + id: sid, + status_buckets: 300, + rf: ["foo"] + }, + done); + }, + function(job, done) { + // Let's sleep for 2 second so + // we let the server catch up + Async.sleep(2000, function() { + job.summary({}, done); + }); + }, + function(summary, job, done) { + assert.ok(job); + assert.ok(summary); + assert.strictEqual(summary.event_count, 1); + assert.strictEqual(summary.fields.foo.count, 1); + assert.strictEqual(summary.fields.foo.distinct_count, 1); + assert.ok(summary.fields.foo.is_exact, 1); + assert.strictEqual(summary.fields.foo.modes.length, 1); + assert.strictEqual(summary.fields.foo.modes[0].count, 1); + assert.strictEqual(summary.fields.foo.modes[0].value, "bar"); + assert.ok(summary.fields.foo.modes[0].is_exact); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Search timeline", function(done) { + var sid = getNextId(); + var that = this; + + Async.chain([ + function(done) { + that.service.jobs().search( + 'search index=_internal | head 1 | eval foo="bar" | fields foo', + { + id: sid, + status_buckets: 300, + rf: ["foo"], + exec_mode: "blocking" + }, + done); + }, + function(job, done) { + job.timeline({}, done); + }, + function(timeline, job, done) { + assert.ok(job); + assert.ok(timeline); + assert.strictEqual(timeline.buckets.length, 1); + assert.strictEqual(timeline.event_count, 1); + assert.strictEqual(timeline.buckets[0].available_count, 1); + assert.strictEqual(timeline.buckets[0].duration, 0.001); + assert.strictEqual(timeline.buckets[0].earliest_time_offset, timeline.buckets[0].latest_time_offset); + assert.strictEqual(timeline.buckets[0].total_count, 1); + assert.ok(timeline.buckets[0].is_finalized); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Touch", function(done) { + var sid = getNextId(); + var that = this; + var originalTime = ""; + + Async.chain([ + function(done) { + that.service.jobs().search('search index=_internal | head 1', {id: sid}, done); + }, + function(job, done) { + job.fetch(done); + }, + function(job, done) { + assert.ok(job); + originalTime = job.properties().updated; + Async.sleep(1200, function() { job.touch(done); }); + }, + function(job, done) { + job.fetch(done); + }, + function(job, done) { + assert.ok(originalTime !== job.updated()); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create failure", function(done) { + var name = "jssdk_savedsearch_" + getNextId(); + var originalSearch = "search index=_internal | head 1"; + + var jobs = this.service.jobs(); + assert.throws(function() {jobs.create({search: originalSearch, name: name, exec_mode: "oneshot"}, function() {});}); + done(); + }); + + it("Callback#Create fails with no search string", function(done) { + var jobs = this.service.jobs(); + jobs.create( + "", {}, + function(err) { + assert.ok(err); + done(); + } + ); + }); + + it("Callback#Oneshot search", function(done) { + var sid = getNextId(); + var that = this; + var originalTime = ""; + + Async.chain([ + function(done) { + that.service.jobs().oneshotSearch('search index=_internal | head 1 | stats count', {id: sid}, done); + }, + function(results, done) { + assert.ok(results); + assert.ok(results.fields); + assert.strictEqual(results.fields.length, 1); + assert.strictEqual(results.fields[0], "count"); + assert.ok(results.rows); + assert.strictEqual(results.rows.length, 1); + assert.strictEqual(results.rows[0].length, 1); + assert.strictEqual(results.rows[0][0], "1"); + + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Oneshot search with no results", function(done) { + var sid = getNextId(); + var that = this; + var originalTime = ""; + + Async.chain([ + function(done) { + var query = 'search index=history MUST_NOT_EXISTABCDEF'; + that.service.jobs().oneshotSearch(query, {id: sid}, done); + }, + function(results, done) { + assert.ok(results); + assert.strictEqual(results.fields.length, 0); + assert.strictEqual(results.rows.length, 0); + assert.ok(!results.preview); + + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + // Disabling the test for now because the messages field is missing in results object from Splunk 8.2 + // so assert.ok(results.messages[1].text.indexOf('owner="admin"')); + // and assert.ok(results.messages[1].text.indexOf('app="search"')); assertions will fail. + // + // "Callback#Service oneshot search", function(done) { + // var sid = getNextId(); + // var that = this; + // var namespace = {owner: "admin", app: "search"}; + // var splunkVersion = 6.1; // Default to pre-6.2 version + // var originalLoggerLevel = "DEBUG"; + + // Async.chain([ + // function(done) { + // // If running on Splunk 6.2+, first set the search logger level to DEBUG + // Async.chain([ + // function(done1) { + // that.service.serverInfo(done1); + // }, + // function(info, done1) { + // splunkVersion = parseFloat(info.properties().version); + // if (splunkVersion < 6.2) { + // done(); // Exit the inner Async.chain + // } + // else { + // done1(); + // } + // }, + // function(done1) { + // that.service.configurations({owner: "admin", app: "search"}).fetch(done1); + // }, + // function(confs, done1) { + // try { + // confs.item("limits").fetch(done1); + // } + // catch(e) { + // done1(e); + // } + // }, + // function(conf, done1) { + // var searchInfo = conf.item("search_info"); + // // Save this so it can be restored later + // originalLoggerLevel = searchInfo.properties()["infocsv_log_level"]; + // searchInfo.update({"infocsv_log_level": "DEBUG"}, done1); + // }, + // function(conf, done1) { + // assert.strictEqual("DEBUG", conf.properties()["infocsv_log_level"]); + // done1(); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // }, + // function(done) { + // that.service.oneshotSearch('search index=_internal | head 1 | stats count', {id: sid}, namespace, done); + // }, + // function(results, done) { + // assert.ok(results); + // assert.ok(results.fields); + // assert.strictEqual(results.fields.length, 1); + // assert.strictEqual(results.fields[0], "count"); + // assert.ok(results.rows); + // assert.strictEqual(results.rows.length, 1); + // assert.strictEqual(results.rows[0].length, 1); + // assert.strictEqual(results.rows[0][0], "1"); + // assert.ok(results.messages[1].text.indexOf('owner="admin"')); + // assert.ok(results.messages[1].text.indexOf('app="search"')); + + // done(); + // }, + // function(done) { + // Async.chain([ + // function(done1) { + // if (splunkVersion < 6.2) { + // done(); // Exit the inner Async.chain + // } + // else { + // done1(); + // } + // }, + // function(done1) { + // that.service.configurations({owner: "admin", app: "search"}).fetch(done1); + // }, + // function(confs, done1) { + // try { + // confs.item("limits").fetch(done1); + // } + // catch(e) { + // done1(e); + // } + // }, + // function(conf, done1) { + // var searchInfo = conf.item("search_info"); + // // Restore the logger level from before + // searchInfo.update({"infocsv_log_level": originalLoggerLevel}, done1); + // }, + // function(conf, done1) { + // assert.strictEqual(originalLoggerLevel, conf.properties()["infocsv_log_level"]); + // done1(); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // }, + + it("Callback#Service search", function(done) { + var sid = getNextId(); + var service = this.service; + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { + that.service.search('search index=_internal | head 1 | stats count', {id: sid}, namespace, done); + }, + function(job, done) { + assert.strictEqual(job.sid, sid); + assert.strictEqual(job.namespace, namespace); + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + job.results({}, done); + }, + function(results, job, done) { + assert.strictEqual(results.rows.length, 1); + assert.strictEqual(results.fields.length, 1); + assert.strictEqual(results.fields[0], "count"); + assert.strictEqual(results.rows[0][0], "1"); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Wait until job done", function(done) { + this.service.search('search index=_internal | head 1000', {}, function(err, job) { + assert.ok(!err); + + var numReadyEvents = 0; + var numProgressEvents = 0; + job.track({ period: 200 }, { + ready: function(job) { + assert.ok(job); + + numReadyEvents++; + }, + progress: function(job) { + assert.ok(job); + + numProgressEvents++; + }, + done: function(job) { + assert.ok(job); + + assert.ok(numReadyEvents === 1); // all done jobs must have become ready + assert.ok(numProgressEvents >= 1); // a job that becomes ready has progress + done(); + }, + failed: function(job) { + assert.ok(job); + + assert.ok(false, "Job failed unexpectedly."); + done(); + }, + error: function(err) { + assert.ok(err); + + assert.ok(false, "Error while tracking job."); + done(); + } + }); + }); + }); + + it("Callback#Wait until job failed", function(done) { + this.service.search('search index=_internal | head bogusarg', {}, function(err, job) { + if (err) { + assert.ok(!err); + done(); + return; + } + + var numReadyEvents = 0; + var numProgressEvents = 0; + job.track({ period: 200 }, { + ready: function(job) { + assert.ok(job); + + numReadyEvents++; + }, + progress: function(job) { + assert.ok(job); + + numProgressEvents++; + }, + done: function(job) { + assert.ok(job); + + assert.ok(false, "Job became done unexpectedly."); + done(); + }, + failed: function(job) { + assert.ok(job); + + assert.ok(numReadyEvents === 1); // even failed jobs become ready + assert.ok(numProgressEvents >= 1); // a job that becomes ready has progress + done(); + }, + error: function(err) { + assert.ok(err); + + assert.ok(false, "Error while tracking job."); + done(); + } + }); + }); + }); + + it("Callback#track() with default params and one function", function(done) { + this.service.search('search index=_internal | head 1', {}, function(err, job) { + if (err) { + assert.ok(!err); + done(); + return; + } + + job.track({}, function(job) { + assert.ok(job); + done(); + }); + }); + }); + + it("Callback#track() should stop polling if only the ready callback is specified", function(done) { + this.service.search('search index=_internal | head 1', {}, function(err, job) { + if (err) { + assert.ok(!err); + done(); + return; + } + + job.track({}, { + ready: function(job) { + assert.ok(job); + }, + + _stoppedAfterReady: function(job) { + done(); + } + }); + }); + }); + + it("Callback#track() a job that is not immediately ready", function(done) { + /*jshint loopfunc:true */ + var numJobs = 20; + var numJobsLeft = numJobs; + var gotJobNotImmediatelyReady = false; + for (var i = 0; i < numJobs; i++) { + this.service.search('search index=_internal | head 10000', {}, function(err, job) { + if (err) { + assert.ok(!err); + done(); + return; + } + + job.track({}, { + _preready: function(job) { + gotJobNotImmediatelyReady = true; + }, + + ready: function(job) { + numJobsLeft--; + + if (numJobsLeft === 0) { + if (!gotJobNotImmediatelyReady) { + splunkjs.Logger.error("", "WARNING: Couldn't test code path in track() where job wasn't ready immediately."); + } + done(); + } + } + }); + }); + } + }); + + it("Callback#Service.getJob() works", function(done) { + var that = this; + var sidsMatch = false; + this.service.search('search index=_internal | head 1', {}, function(err, job){ + if (err) { + assert.ok(!err); + done(); + return; + } + var sid = job.sid; + return Async.chain([ + function(done) { + that.service.getJob(sid, done); + }, + function(innerJob, done) { + assert.strictEqual(sid, innerJob.sid); + sidsMatch = sid === innerJob.sid; + done(); + } + ], + function(err) { + assert.ok(!err); + assert.ok(sidsMatch); + done(); + } + ); + }); + }); + }); + + describe("Data Model tests", function() { + before( function(done) { + this.service = svc; + this.dataModels = svc.dataModels(); + this.skip = false; + var that = this; + this.service.serverInfo(function(err, info) { + if (parseInt(info.properties().version.split(".")[0], 10) < 6) { + that.skip = true; + splunkjs.Logger.log("Skipping data model tests..."); + } + done(err); + }); + }); + + it("Callback#DataModels - fetch a built-in data model", function(done) { + if (this.skip) { + done(); + return; + } + var that = this; + Async.chain([ + function(done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + var dm = dataModels.item("internal_audit_logs"); + // Check for the 3 objects we expect + assert.ok(dm.objectByName("Audit")); + assert.ok(dm.objectByName("searches")); + assert.ok(dm.objectByName("modify")); + + // Check for an object that shouldn't exist + assert.strictEqual(null, dm.objectByName(getNextId())); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#DataModels - create & delete an empty data model", function(done) { + if (this.skip) { + done(); + return; + } + var args; + var name = "delete-me-" + getNextId(); + var initialSize; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/empty_data_model.json")); + fetch('./data/empty_data_model.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + initialSize = dataModels.list().length; + dataModels.create(name, args, done); + }, + function(dataModel, done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + // Make sure we have 1 more data model than we started with + assert.strictEqual(initialSize + 1, dataModels.list().length); + // Delete the data model we just created, by name. + dataModels.item(name).remove(done); + }, + function(done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + // Make sure we have as many data models as we started with + assert.strictEqual(initialSize, dataModels.list().length); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create a data model with spaces in the name, which are swapped for -'s", function(done) { + if (this.skip) { + done(); + return; + } + var args; + var name = "delete-me- " + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/empty_data_model.json")); + fetch('./data/empty_data_model.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + assert.strictEqual(name.replace(" ", "_"), dataModel.name); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create a data model with 0 objects", function(done) { + if (this.skip) { + done(); + return; + } + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/empty_data_model.json")); + fetch('./data/empty_data_model.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + // Check for 0 objects before fetch + assert.strictEqual(0, dataModel.objects.length); + that.dataModels.fetch(done); + }, + function(dataModels, done) { + // Check for 0 objects after fetch + assert.strictEqual(0, dataModels.item(name).objects.length); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create a data model with 1 search object", function(done) { + if (this.skip) { + done(); + return; + } + + var dataModels = this.service.dataModels(); + var name = "delete-me-" + getNextId(); + var that = this; + var args; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/object_with_one_search.json")); + fetch('./data/object_with_one_search.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + // Check for 1 object before fetch + assert.strictEqual(1, dataModel.objects.length); + that.dataModels.fetch(done); + }, + function(dataModels, done) { + // Check for 1 object after fetch + assert.strictEqual(1, dataModels.item(name).objects.length); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create a data model with 2 search objects", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/object_with_two_searches.json")); + fetch('./data/object_with_two_searches.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + // Check for 2 objects before fetch + assert.strictEqual(2, dataModel.objects.length); + that.dataModels.fetch(done); + }, + function(dataModels, done) { + // Check for 2 objects after fetch + assert.strictEqual(2, dataModels.item(name).objects.length); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - data model objects are created correctly", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/object_with_two_searches.json")); + fetch('./data/object_with_two_searches.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + assert.ok(dataModel.hasObject("search1")); + assert.ok(dataModel.hasObject("search2")); + + var search1 = dataModel.objectByName("search1"); + assert.ok(search1); + assert.strictEqual(decodeURI("%E2%80%A1%C3%98%C2%B5%E2%80%A1%C3%98%C2%B1%E2%80%A1%C3%98%E2%88%9E%E2%80%A1%C3%98%C3%98%20-%20search%201"), search1.displayName); + + var search2 = dataModel.objectByName("search2"); + assert.ok(search2); + assert.strictEqual(decodeURI("%E2%80%A1%C3%98%C2%B5%E2%80%A1%C3%98%C2%B1%E2%80%A1%C3%98%E2%88%9E%E2%80%A1%C3%98%C3%98%20-%20search%202"), search2.displayName); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - data model handles unicode characters", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/model_with_unicode_headers.json")); + fetch('./data/model_with_unicode_headers.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + assert.strictEqual(name, dataModel.name); + assert.strictEqual(decodeURI("%C2%B7%C3%84%C2%A9%C2%B7%C3%B6%C3%B4%E2%80%A1%C3%98%C2%B5"), dataModel.displayName); + assert.strictEqual(decodeURI("%E2%80%A1%C3%98%C2%B5%E2%80%A1%C3%98%C2%B1%E2%80%A1%C3%98%E2%88%9E%E2%80%A1%C3%98%C3%98"), dataModel.description); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create data model with empty headers", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/model_with_empty_headers.json")); + fetch('./data/model_with_empty_headers.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + assert.strictEqual(name, dataModel.name); + assert.strictEqual("", dataModel.displayName); + assert.strictEqual("", dataModel.description); + + // Make sure we're not getting a summary of the data model + assert.strictEqual("0", dataModel.concise); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test acceleration settings", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_with_test_objects.json")); + fetch('./data/data_model_with_test_objects.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + dataModel.acceleration.enabled = true; + dataModel.acceleration.earliestTime = "-2mon"; + dataModel.acceleration.cronSchedule = "5/* * * * *"; + + assert.strictEqual(true, dataModel.isAccelerated()); + assert.strictEqual(true, dataModel.acceleration.enabled); + assert.strictEqual("-2mon", dataModel.acceleration.earliestTime); + assert.strictEqual("5/* * * * *", dataModel.acceleration.cronSchedule); + + dataModel.acceleration.enabled = false; + dataModel.acceleration.earliestTime = "-1mon"; + dataModel.acceleration.cronSchedule = "* * * * *"; + + assert.strictEqual(false, dataModel.isAccelerated()); + assert.strictEqual(false, dataModel.acceleration.enabled); + assert.strictEqual("-1mon", dataModel.acceleration.earliestTime); + assert.strictEqual("* * * * *", dataModel.acceleration.cronSchedule); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model object metadata", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_with_test_objects.json")); + fetch('./data/data_model_with_test_objects.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("event1"); + assert.ok(obj); + + assert.strictEqual(decodeURI("event1%20%C2%B7%C3%84%C2%A9%C2%B7%C3%B6%C3%B4"), obj.displayName); + assert.strictEqual("event1", obj.name); + assert.equal(dataModel, obj.dataModel); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model object parent", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_with_test_objects.json")); + fetch('./data/data_model_with_test_objects.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("event1"); + assert.ok(obj); + assert.ok(!obj.parent()); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model object lineage", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/inheritance_test_data.json")); + fetch('./data/inheritance_test_data.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("level_0"); + assert.ok(obj); + assert.strictEqual(1, obj.lineage.length); + assert.strictEqual("level_0", obj.lineage[0]); + assert.strictEqual("BaseEvent", obj.parentName); + + obj = dataModel.objectByName("level_1"); + assert.ok(obj); + assert.strictEqual(2, obj.lineage.length); + assert.sameMembers(["level_0", "level_1"], obj.lineage, 'same members'); + assert.strictEqual("level_0", obj.parentName); + + obj = dataModel.objectByName("level_2"); + assert.ok(obj); + assert.strictEqual(3, obj.lineage.length); + assert.sameMembers(["level_0", "level_1", "level_2"], obj.lineage, 'same members'); + assert.strictEqual("level_1", obj.parentName); + + // Make sure there's no extra children + assert.ok(!dataModel.objectByName("level_3")); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model object fields", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/inheritance_test_data.json")); + fetch('./data/inheritance_test_data.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("level_2"); + assert.ok(obj); + + var timeField = obj.fieldByName("_time"); + assert.ok(timeField); + assert.strictEqual("timestamp", timeField.type); + assert.ok(timeField.isTimestamp()); + assert.ok(!timeField.isNumber()); + assert.ok(!timeField.isString()); + assert.ok(!timeField.isObjectcount()); + assert.ok(!timeField.isChildcount()); + assert.ok(!timeField.isIPv4()); + assert.sameMembers(["BaseEvent"], timeField.lineage, 'same members'); + assert.strictEqual("_time", timeField.name); + assert.strictEqual(false, timeField.required); + assert.strictEqual(false, timeField.multivalued); + assert.strictEqual(false, timeField.hidden); + assert.strictEqual(false, timeField.editable); + assert.strictEqual(null, timeField.comment); + + var lvl2 = obj.fieldByName("level_2"); + assert.strictEqual("level_2", lvl2.owner); + assert.sameMembers(["level_0", "level_1", "level_2"], lvl2.lineage, 'same members'); + assert.strictEqual("objectCount", lvl2.type); + assert.ok(!lvl2.isTimestamp()); + assert.ok(!lvl2.isNumber()); + assert.ok(!lvl2.isString()); + assert.ok(lvl2.isObjectcount()); + assert.ok(!lvl2.isChildcount()); + assert.ok(!lvl2.isIPv4()); + assert.strictEqual("level_2", lvl2.name); + assert.strictEqual("level 2", lvl2.displayName); + assert.strictEqual(false, lvl2.required); + assert.strictEqual(false, lvl2.multivalued); + assert.strictEqual(false, lvl2.hidden); + assert.strictEqual(false, lvl2.editable); + assert.strictEqual(null, lvl2.comment); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model object properties", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + assert.strictEqual(5, obj.fieldNames().length); + assert.strictEqual(10, obj.allFieldNames().length); + assert.ok(obj.fieldByName("has_boris")); + assert.ok(obj.hasField("has_boris")); + assert.ok(obj.fieldByName("_time")); + assert.ok(obj.hasField("_time")); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create local acceleration job", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var obj; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/inheritance_test_data.json")); + fetch('./data/inheritance_test_data.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("level_2"); + assert.ok(obj); + + obj.createLocalAccelerationJob(null, done); + }, + function(job, done) { + assert.ok(job); + + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + assert.strictEqual("| datamodel \"" + name + "\" level_2 search | tscollect", job.properties().request.search); + job.cancel(done); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - create local acceleration job with earliest time", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var obj; + var oldNow = Date.now(); + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/inheritance_test_data.json")); + fetch('./data/inheritance_test_data.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("level_2"); + assert.ok(obj); + obj.createLocalAccelerationJob("-1d", done); + }, + function(job, done) { + assert.ok(job); + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + assert.strictEqual("| datamodel \"" + name + "\" level_2 search | tscollect", job.properties().request.search); + + // Make sure the earliest time is 1 day behind + var yesterday = new Date(Date.now() - (1000 * 60 * 60 * 24)); + var month = (yesterday.getMonth() + 1); + if (month <= 9) { + month = "0" + month; + } + var date = yesterday.getDate(); + if (date <= 9) { + date = "0" + date; + } + var expectedDate = yesterday.getFullYear() + "-" + month + "-" + date; + assert.ok(utils.startsWith(job._state.content.earliestTime, expectedDate)); + + job.cancel(done); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model constraints", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var obj; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_with_test_objects.json")); + fetch('./data/data_model_with_test_objects.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("event1"); + assert.ok(obj); + var constraints = obj.constraints; + assert.ok(constraints); + var onlyOne = true; + + for (var i = 0; i < constraints.length; i++) { + var constraint = constraints[i]; + assert.ok(!!onlyOne); + + assert.strictEqual("event1", constraint.owner); + assert.strictEqual("uri=\"*.php\" OR uri=\"*.py\"\nNOT (referer=null OR referer=\"-\")", constraint.query); + } + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - test data model calculations, and the different types", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var obj; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_with_test_objects.json")); + fetch('./data/data_model_with_test_objects.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("event1"); + assert.ok(obj); + + var calculations = obj.calculations; + assert.strictEqual(4, Object.keys(calculations).length); + assert.strictEqual(4, obj.calculationIDs().length); + + var evalCalculation = calculations["93fzsv03wa7"]; + assert.ok(evalCalculation); + assert.strictEqual("event1", evalCalculation.owner); + assert.sameMembers(["event1"], evalCalculation.lineage, 'same members'); + assert.strictEqual("Eval", evalCalculation.type); + assert.ok(evalCalculation.isEval()); + assert.ok(!evalCalculation.isLookup()); + assert.ok(!evalCalculation.isGeoIP()); + assert.ok(!evalCalculation.isRex()); + assert.strictEqual(null, evalCalculation.comment); + assert.strictEqual(true, evalCalculation.isEditable()); + assert.strictEqual("if(cidrmatch(\"192.0.0.0/16\", clientip), \"local\", \"other\")", evalCalculation.expression); + + assert.strictEqual(1, Object.keys(evalCalculation.outputFields).length); + assert.strictEqual(1, evalCalculation.outputFieldNames().length); + + var field = evalCalculation.outputFields["new_field"]; + assert.ok(field); + assert.strictEqual("My New Field", field.displayName); + + var lookupCalculation = calculations["sr3mc8o3mjr"]; + assert.ok(lookupCalculation); + assert.strictEqual("event1", lookupCalculation.owner); + assert.sameMembers(["event1"], lookupCalculation.lineage, 'same members'); + assert.strictEqual("Lookup", lookupCalculation.type); + assert.ok(lookupCalculation.isLookup()); + assert.ok(!lookupCalculation.isEval()); + assert.ok(!lookupCalculation.isGeoIP()); + assert.ok(!lookupCalculation.isRex()); + assert.strictEqual(null, lookupCalculation.comment); + assert.strictEqual(true, lookupCalculation.isEditable()); + assert.deepEqual({lookupField: "a_lookup_field", inputField: "host"}, lookupCalculation.inputFieldMappings); + assert.strictEqual(2, Object.keys(lookupCalculation.inputFieldMappings).length); + assert.strictEqual("a_lookup_field", lookupCalculation.inputFieldMappings.lookupField); + assert.strictEqual("host", lookupCalculation.inputFieldMappings.inputField); + assert.strictEqual("dnslookup", lookupCalculation.lookupName); + + var regexpCalculation = calculations["a5v1k82ymic"]; + assert.ok(regexpCalculation); + assert.strictEqual("event1", regexpCalculation.owner); + assert.sameMembers(["event1"], regexpCalculation.lineage, 'same members'); + assert.strictEqual("Rex", regexpCalculation.type); + assert.ok(regexpCalculation.isRex()); + assert.ok(!regexpCalculation.isLookup()); + assert.ok(!regexpCalculation.isEval()); + assert.ok(!regexpCalculation.isGeoIP()); + assert.strictEqual(2, regexpCalculation.outputFieldNames().length); + assert.strictEqual("_raw", regexpCalculation.inputField); + assert.strictEqual(" From: (?.*) To: (?.*) ", regexpCalculation.expression); + + var geoIPCalculation = calculations["pbe9bd0rp4"]; + assert.ok(geoIPCalculation); + assert.strictEqual("event1", geoIPCalculation.owner); + assert.sameMembers(["event1"], geoIPCalculation.lineage, 'same members'); + assert.strictEqual("GeoIP", geoIPCalculation.type); + assert.ok(geoIPCalculation.isGeoIP()); + assert.ok(!geoIPCalculation.isLookup()); + assert.ok(!geoIPCalculation.isEval()); + assert.ok(!geoIPCalculation.isRex()); + assert.strictEqual(decodeURI("%C2%B7%C3%84%C2%A9%C2%B7%C3%B6%C3%B4%E2%80%A1%C3%98%C2%B5%20comment%20of%20pbe9bd0rp4"), geoIPCalculation.comment); + assert.strictEqual(5, geoIPCalculation.outputFieldNames().length); + assert.strictEqual("output_from_reverse_hostname", geoIPCalculation.inputField); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - run queries", function(done) { + if (this.skip) { + done(); + return; + } + var obj; + var that = this; + Async.chain([ + function(done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + var dm = dataModels.item("internal_audit_logs"); + obj = dm.objectByName("searches"); + obj.startSearch({}, "", done); + }, + function(job, done) { + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + assert.strictEqual("| datamodel internal_audit_logs searches search", job.properties().request.search); + job.cancel(done); + }, + function(response, done) { + obj.startSearch({status_buckets: 5, enable_lookups: false}, "| head 3", done); + }, + function(job, done) { + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + done + ); + }, + function(job, done) { + assert.strictEqual("| datamodel internal_audit_logs searches search | head 3", job.properties().request.search); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#DataModels - baseSearch is parsed correctly", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var obj; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/model_with_multiple_types.json")); + fetch('./data/model_with_multiple_types.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("search1"); + assert.ok(obj); + assert.strictEqual("BaseSearch", obj.parentName); + assert.ok(obj.isBaseSearch()); + assert.ok(!obj.isBaseTransaction()); + assert.strictEqual("search index=_internal | head 10", obj.baseSearch); + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#DataModels - baseTransaction is parsed correctly", function(done) { + if (this.skip) { + done(); + return; + } + + var args; + var name = "delete-me-" + getNextId(); + var obj; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/model_with_multiple_types.json")); + fetch('./data/model_with_multiple_types.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("transaction1"); + assert.ok(obj); + assert.strictEqual("BaseTransaction", obj.parentName); + assert.ok(obj.isBaseTransaction()); + assert.ok(!obj.isBaseSearch()); + assert.sameMembers(["event1"], obj.objectsToGroup, 'same members'); + assert.sameMembers(["host", "from"], obj.groupByFields, 'same members'); + assert.strictEqual("25s", obj.maxPause); + assert.strictEqual("100m", obj.maxSpan); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + }); + + describe("Pivot tests", function() { + before( function(done) { + this.service = svc; + this.dataModels = svc.dataModels({owner: "nobody", app: "search"}); + this.skip = false; + var that = this; + this.service.serverInfo(function(err, info) { + if (parseInt(info.properties().version.split(".")[0], 10) < 6) { + that.skip = true; + splunkjs.Logger.log("Skipping pivot tests..."); + } + done(err); + }); + }); + + it("Callback#Pivot - test constructor args", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + assert.ok(dataModel.objectByName("test_data")); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test acceleration, then pivot", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + dataModel.objectByName("test_data"); + assert.ok(dataModel); + + dataModel.acceleration.enabled = true; + dataModel.acceleration.earliestTime = "-2mon"; + dataModel.acceleration.cronSchedule = "0 */12 * * *"; + dataModel.update(done); + }, + function(dataModel, done) { + var props = dataModel.properties(); + + assert.strictEqual(true, dataModel.isAccelerated()); + assert.strictEqual(true, !!dataModel.acceleration.enabled); + assert.strictEqual("-2mon", dataModel.acceleration.earliest_time); + assert.strictEqual("0 */12 * * *", dataModel.acceleration.cron_schedule); + + var dataModelObject = dataModel.objectByName("test_data"); + var pivotSpecification = dataModelObject.createPivotSpecification(); + + assert.strictEqual(dataModelObject.dataModel.name, pivotSpecification.accelerationNamespace); + + var name1 = "delete-me-" + getNextId(); + pivotSpecification.setAccelerationJob(name1); + assert.strictEqual("sid=" + name1, pivotSpecification.accelerationNamespace); + + var namespaceTemp = "delete-me-" + getNextId(); + pivotSpecification.accelerationNamespace = namespaceTemp; + assert.strictEqual(namespaceTemp, pivotSpecification.accelerationNamespace); + + pivotSpecification + .addCellValue("test_data", "Source Value", "count") + .run(done); + }, + function(job, pivot, done) { + assert.ok(job); + assert.ok(pivot); + assert.notStrictEqual("FAILED", job.properties().dispatchState); + + job.track({}, function(job) { + assert.ok(pivot.tstatsSearch); + assert.strictEqual(0, job.properties().request.search.indexOf("| tstats")); + assert.strictEqual("| tstats", job.properties().request.search.match("^\\| tstats")[0]); + assert.strictEqual(1, job.properties().request.search.match("^\\| tstats").length); + + assert.strictEqual(pivot.tstatsSearch, job.properties().request.search); + done(null, job); + }); + }, + function(job, done) { + assert.ok(job); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test illegal filtering (all types)", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + + // Boolean comparisons + try { + pivotSpecification.addFilter(getNextId(), "boolean", "=", true); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add filter on a nonexistent field."); + } + try { + pivotSpecification.addFilter("_time", "boolean", "=", true); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add boolean filter on _time because it is of type timestamp"); + } + + // String comparisons + try { + pivotSpecification.addFilter("has_boris", "string", "contains", "abc"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add string filter on has_boris because it is of type boolean"); + } + try { + pivotSpecification.addFilter(getNextId(), "string", "contains", "abc"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add filter on a nonexistent field."); + } + + // IPv4 comparisons + try { + pivotSpecification.addFilter("has_boris", "ipv4", "startsWith", "192.168"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add ipv4 filter on has_boris because it is of type boolean"); + } + try { + pivotSpecification.addFilter(getNextId(), "ipv4", "startsWith", "192.168"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add filter on a nonexistent field."); + } + + // Number comparisons + try { + pivotSpecification.addFilter("has_boris", "number", "atLeast", 2.3); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add number filter on has_boris because it is of type boolean"); + } + try { + pivotSpecification.addFilter(getNextId(), "number", "atLeast", 2.3); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add filter on a nonexistent field."); + } + + // Limit filter + try { + pivotSpecification.addLimitFilter("has_boris", "host", "DEFAULT", 50, "count"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add limit filter on has_boris because it is of type boolean"); + } + try { + pivotSpecification.addLimitFilter(getNextId(), "host", "DEFAULT", 50, "count"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot add limit filter on a nonexistent field."); + } + try { + pivotSpecification.addLimitFilter("source", "host", "DEFAULT", 50, "sum"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, + "Stats function for fields of type string must be COUNT or DISTINCT_COUNT; found sum"); + } + try { + pivotSpecification.addLimitFilter("epsilon", "host", "DEFAULT", 50, "duration"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, + "Stats function for fields of type number must be one of COUNT, DISTINCT_COUNT, SUM, or AVERAGE; found duration"); + } + try { + pivotSpecification.addLimitFilter("test_data", "host", "DEFAULT", 50, "list"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, + "Stats function for fields of type object count must be COUNT; found list"); + } + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test boolean filtering", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + try { + pivotSpecification.addFilter("has_boris", "boolean", "=", true); + assert.strictEqual(1, pivotSpecification.filters.length); + + //Test the individual parts of the filter + var filter = pivotSpecification.filters[0]; + + assert.ok(filter.hasOwnProperty("fieldName")); + assert.ok(filter.hasOwnProperty("type")); + assert.ok(filter.hasOwnProperty("rule")); + assert.ok(filter.hasOwnProperty("owner")); + + assert.strictEqual("has_boris", filter.fieldName); + assert.strictEqual("boolean", filter.type); + assert.strictEqual("=", filter.rule.comparator); + assert.strictEqual(true, filter.rule.compareTo); + assert.strictEqual("test_data", filter.owner); + } + catch (e) { + assert.ok(false); + } + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test string filtering", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + try { + pivotSpecification.addFilter("host", "string", "contains", "abc"); + assert.strictEqual(1, pivotSpecification.filters.length); + + //Test the individual parts of the filter + var filter = pivotSpecification.filters[0]; + + assert.ok(filter.hasOwnProperty("fieldName")); + assert.ok(filter.hasOwnProperty("type")); + assert.ok(filter.hasOwnProperty("rule")); + assert.ok(filter.hasOwnProperty("owner")); + + assert.strictEqual("host", filter.fieldName); + assert.strictEqual("string", filter.type); + assert.strictEqual("contains", filter.rule.comparator); + assert.strictEqual("abc", filter.rule.compareTo); + assert.strictEqual("BaseEvent", filter.owner); + } + catch (e) { + assert.ok(false); + } + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test IPv4 filtering", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + try { + pivotSpecification.addFilter("hostip", "ipv4", "startsWith", "192.168"); + assert.strictEqual(1, pivotSpecification.filters.length); + + //Test the individual parts of the filter + var filter = pivotSpecification.filters[0]; + + assert.ok(filter.hasOwnProperty("fieldName")); + assert.ok(filter.hasOwnProperty("type")); + assert.ok(filter.hasOwnProperty("rule")); + assert.ok(filter.hasOwnProperty("owner")); + + assert.strictEqual("hostip", filter.fieldName); + assert.strictEqual("ipv4", filter.type); + assert.strictEqual("startsWith", filter.rule.comparator); + assert.strictEqual("192.168", filter.rule.compareTo); + assert.strictEqual("test_data", filter.owner); + } + catch (e) { + assert.ok(false); + } + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test number filtering", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + try { + pivotSpecification.addFilter("epsilon", "number", ">=", 2.3); + assert.strictEqual(1, pivotSpecification.filters.length); + + //Test the individual parts of the filter + var filter = pivotSpecification.filters[0]; + + assert.ok(filter.hasOwnProperty("fieldName")); + assert.ok(filter.hasOwnProperty("type")); + assert.ok(filter.hasOwnProperty("rule")); + assert.ok(filter.hasOwnProperty("owner")); + + assert.strictEqual("epsilon", filter.fieldName); + assert.strictEqual("number", filter.type); + assert.strictEqual(">=", filter.rule.comparator); + assert.strictEqual(2.3, filter.rule.compareTo); + assert.strictEqual("test_data", filter.owner); + } + catch (e) { + assert.ok(false); + } + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test limit filtering", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + try { + pivotSpecification.addLimitFilter("epsilon", "host", "ASCENDING", 500, "average"); + assert.strictEqual(1, pivotSpecification.filters.length); + + //Test the individual parts of the filter + var filter = pivotSpecification.filters[0]; + + assert.ok(filter.hasOwnProperty("fieldName")); + assert.ok(filter.hasOwnProperty("type")); + assert.ok(filter.hasOwnProperty("owner")); + assert.ok(filter.hasOwnProperty("attributeName")); + assert.ok(filter.hasOwnProperty("attributeOwner")); + assert.ok(filter.hasOwnProperty("limitType")); + assert.ok(filter.hasOwnProperty("limitAmount")); + assert.ok(filter.hasOwnProperty("statsFn")); + + assert.strictEqual("epsilon", filter.fieldName); + assert.strictEqual("number", filter.type); + assert.strictEqual("test_data", filter.owner); + assert.strictEqual("host", filter.attributeName); + assert.strictEqual("BaseEvent", filter.attributeOwner); + assert.strictEqual("lowest", filter.limitType); + assert.strictEqual(500, filter.limitAmount); + assert.strictEqual("average", filter.statsFn); + } + catch (e) { + assert.ok(false); + } + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test row split", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + + // Test error handling for row split + try { + pivotSpecification.addRowSplit("has_boris", "Wrong type here"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("has_boris").type + ", expected number or string."); + } + var field = getNextId(); + try { + + pivotSpecification.addRowSplit(field, "Break Me!"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + + // Test row split, number + pivotSpecification.addRowSplit("epsilon", "My Label"); + assert.strictEqual(1, pivotSpecification.rows.length); + + var row = pivotSpecification.rows[0]; + assert.ok(row.hasOwnProperty("fieldName")); + assert.ok(row.hasOwnProperty("owner")); + assert.ok(row.hasOwnProperty("type")); + assert.ok(row.hasOwnProperty("label")); + assert.ok(row.hasOwnProperty("display")); + + assert.strictEqual("epsilon", row.fieldName); + assert.strictEqual("test_data", row.owner); + assert.strictEqual("number", row.type); + assert.strictEqual("My Label", row.label); + assert.strictEqual("all", row.display); + assert.deepEqual({ + fieldName: "epsilon", + owner: "test_data", + type: "number", + label: "My Label", + display: "all" + }, + row); + + // Test row split, string + pivotSpecification.addRowSplit("host", "My Label"); + assert.strictEqual(2, pivotSpecification.rows.length); + + row = pivotSpecification.rows[pivotSpecification.rows.length - 1]; + assert.ok(row.hasOwnProperty("fieldName")); + assert.ok(row.hasOwnProperty("owner")); + assert.ok(row.hasOwnProperty("type")); + assert.ok(row.hasOwnProperty("label")); + assert.ok(!row.hasOwnProperty("display")); + + assert.strictEqual("host", row.fieldName); + assert.strictEqual("BaseEvent", row.owner); + assert.strictEqual("string", row.type); + assert.strictEqual("My Label", row.label); + assert.deepEqual({ + fieldName: "host", + owner: "BaseEvent", + type: "string", + label: "My Label" + }, + row); + + // Test error handling on range row split + try { + pivotSpecification.addRangeRowSplit("has_boris", "Wrong type here", {start: 0, end: 100, step:20, limit:5}); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("has_boris").type + ", expected number."); + } + try { + pivotSpecification.addRangeRowSplit(field, "Break Me!", {start: 0, end: 100, step:20, limit:5}); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + + // Test range row split + pivotSpecification.addRangeRowSplit("epsilon", "My Label", {start: 0, end: 100, step:20, limit:5}); + assert.strictEqual(3, pivotSpecification.rows.length); + + row = pivotSpecification.rows[pivotSpecification.rows.length - 1]; + assert.ok(row.hasOwnProperty("fieldName")); + assert.ok(row.hasOwnProperty("owner")); + assert.ok(row.hasOwnProperty("type")); + assert.ok(row.hasOwnProperty("label")); + assert.ok(row.hasOwnProperty("display")); + assert.ok(row.hasOwnProperty("ranges")); + + assert.strictEqual("epsilon", row.fieldName); + assert.strictEqual("test_data", row.owner); + assert.strictEqual("number", row.type); + assert.strictEqual("My Label", row.label); + assert.strictEqual("ranges", row.display); + + var ranges = { + start: 0, + end: 100, + size: 20, + maxNumberOf: 5 + }; + assert.deepEqual(ranges, row.ranges); + assert.deepEqual({ + fieldName: "epsilon", + owner: "test_data", + type: "number", + label: "My Label", + display: "ranges", + ranges: ranges + }, + row); + + // Test error handling on boolean row split + try { + pivotSpecification.addBooleanRowSplit("epsilon", "Wrong type here", "t", "f"); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("epsilon").type + ", expected boolean."); + } + try { + pivotSpecification.addBooleanRowSplit(field, "Break Me!", "t", "f"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + + // Test boolean row split + pivotSpecification.addBooleanRowSplit("has_boris", "My Label", "is_true", "is_false"); + assert.strictEqual(4, pivotSpecification.rows.length); + + row = pivotSpecification.rows[pivotSpecification.rows.length - 1]; + assert.ok(row.hasOwnProperty("fieldName")); + assert.ok(row.hasOwnProperty("owner")); + assert.ok(row.hasOwnProperty("type")); + assert.ok(row.hasOwnProperty("label")); + assert.ok(row.hasOwnProperty("trueLabel")); + assert.ok(row.hasOwnProperty("falseLabel")); + + assert.strictEqual("has_boris", row.fieldName); + assert.strictEqual("My Label", row.label); + assert.strictEqual("test_data", row.owner); + assert.strictEqual("boolean", row.type); + assert.strictEqual("is_true", row.trueLabel); + assert.strictEqual("is_false", row.falseLabel); + assert.deepEqual({ + fieldName: "has_boris", + label: "My Label", + owner: "test_data", + type: "boolean", + trueLabel: "is_true", + falseLabel: "is_false" + }, + row); + + // Test error handling on timestamp row split + try { + pivotSpecification.addTimestampRowSplit("epsilon", "Wrong type here", "some binning"); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("epsilon").type + ", expected timestamp."); + } + try { + pivotSpecification.addTimestampRowSplit(field, "Break Me!", "some binning"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + try { + pivotSpecification.addTimestampRowSplit("_time", "some label", "Bogus binning value"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Invalid binning Bogus binning value found. Valid values are: " + pivotSpecification._binning.join(", ")); + } + + // Test timestamp row split + pivotSpecification.addTimestampRowSplit("_time", "My Label", "day"); + assert.strictEqual(5, pivotSpecification.rows.length); + + row = pivotSpecification.rows[pivotSpecification.rows.length - 1]; + assert.ok(row.hasOwnProperty("fieldName")); + assert.ok(row.hasOwnProperty("owner")); + assert.ok(row.hasOwnProperty("type")); + assert.ok(row.hasOwnProperty("label")); + assert.ok(row.hasOwnProperty("period")); + + assert.strictEqual("_time", row.fieldName); + assert.strictEqual("My Label", row.label); + assert.strictEqual("BaseEvent", row.owner); + assert.strictEqual("timestamp", row.type); + assert.strictEqual("day", row.period); + assert.deepEqual({ + fieldName: "_time", + label: "My Label", + owner: "BaseEvent", + type: "timestamp", + period: "day" + }, + row); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test column split", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + + // Test error handling for column split + try { + pivotSpecification.addColumnSplit("has_boris", "Wrong type here"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("has_boris").type + ", expected number or string."); + } + var field = getNextId(); + try { + + pivotSpecification.addColumnSplit(field, "Break Me!"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + + // Test column split, number + pivotSpecification.addColumnSplit("epsilon"); + assert.strictEqual(1, pivotSpecification.columns.length); + + var col = pivotSpecification.columns[pivotSpecification.columns.length - 1]; + assert.ok(col.hasOwnProperty("fieldName")); + assert.ok(col.hasOwnProperty("owner")); + assert.ok(col.hasOwnProperty("type")); + assert.ok(col.hasOwnProperty("display")); + + assert.strictEqual("epsilon", col.fieldName); + assert.strictEqual("test_data", col.owner); + assert.strictEqual("number", col.type); + assert.strictEqual("all", col.display); + assert.deepEqual({ + fieldName: "epsilon", + owner: "test_data", + type: "number", + display: "all" + }, + col); + + // Test column split, string + pivotSpecification.addColumnSplit("host"); + assert.strictEqual(2, pivotSpecification.columns.length); + + col = pivotSpecification.columns[pivotSpecification.columns.length - 1]; + assert.ok(col.hasOwnProperty("fieldName")); + assert.ok(col.hasOwnProperty("owner")); + assert.ok(col.hasOwnProperty("type")); + assert.ok(!col.hasOwnProperty("display")); + + assert.strictEqual("host", col.fieldName); + assert.strictEqual("BaseEvent", col.owner); + assert.strictEqual("string", col.type); + assert.deepEqual({ + fieldName: "host", + owner: "BaseEvent", + type: "string" + }, + col); + + done(); + + // Test error handling for range column split + try { + pivotSpecification.addRangeColumnSplit("has_boris", "Wrong type here", {start: 0, end: 100, step:20, limit:5}); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("has_boris").type + ", expected number."); + } + try { + pivotSpecification.addRangeColumnSplit(field, {start: 0, end: 100, step:20, limit:5}); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + + // Test range column split + pivotSpecification.addRangeColumnSplit("epsilon", {start: 0, end: 100, step:20, limit:5}); + assert.strictEqual(3, pivotSpecification.columns.length); + + col = pivotSpecification.columns[pivotSpecification.columns.length - 1]; + assert.ok(col.hasOwnProperty("fieldName")); + assert.ok(col.hasOwnProperty("owner")); + assert.ok(col.hasOwnProperty("type")); + assert.ok(col.hasOwnProperty("display")); + assert.ok(col.hasOwnProperty("ranges")); + + assert.strictEqual("epsilon", col.fieldName); + assert.strictEqual("test_data", col.owner); + assert.strictEqual("number", col.type); + assert.strictEqual("ranges", col.display); + var ranges = { + start: 0, + end: 100, + size: 20, + maxNumberOf: 5 + }; + assert.deepEqual(ranges, col.ranges); + assert.deepEqual({ + fieldName: "epsilon", + owner: "test_data", + type: "number", + display: "ranges", + ranges: ranges + }, + col); + + // Test error handling on boolean column split + try { + pivotSpecification.addBooleanColumnSplit("epsilon", "t", "f"); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("epsilon").type + ", expected boolean."); + } + try { + pivotSpecification.addBooleanColumnSplit(field, "t", "f"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + + // Test boolean column split + pivotSpecification.addBooleanColumnSplit("has_boris", "is_true", "is_false"); + assert.strictEqual(4, pivotSpecification.columns.length); + + col = pivotSpecification.columns[pivotSpecification.columns.length - 1]; + assert.ok(col.hasOwnProperty("fieldName")); + assert.ok(col.hasOwnProperty("owner")); + assert.ok(col.hasOwnProperty("type")); + assert.ok(!col.hasOwnProperty("label")); + assert.ok(col.hasOwnProperty("trueLabel")); + assert.ok(col.hasOwnProperty("falseLabel")); + + assert.strictEqual("has_boris", col.fieldName); + assert.strictEqual("test_data", col.owner); + assert.strictEqual("boolean", col.type); + assert.strictEqual("is_true", col.trueLabel); + assert.strictEqual("is_false", col.falseLabel); + assert.deepEqual({ + fieldName: "has_boris", + owner: "test_data", + type: "boolean", + trueLabel: "is_true", + falseLabel: "is_false" + }, + col); + + // Test error handling on timestamp column split + try { + pivotSpecification.addTimestampColumnSplit("epsilon", "Wrong type here"); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Field was of type " + obj.fieldByName("epsilon").type + ", expected timestamp."); + } + try { + pivotSpecification.addTimestampColumnSplit(field, "Break Me!"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field " + field); + } + try { + pivotSpecification.addTimestampColumnSplit("_time", "Bogus binning value"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Invalid binning Bogus binning value found. Valid values are: " + pivotSpecification._binning.join(", ")); + } + + // Test timestamp column split + pivotSpecification.addTimestampColumnSplit("_time", "day"); + assert.strictEqual(5, pivotSpecification.columns.length); + + col = pivotSpecification.columns[pivotSpecification.columns.length - 1]; + assert.ok(col.hasOwnProperty("fieldName")); + assert.ok(col.hasOwnProperty("owner")); + assert.ok(col.hasOwnProperty("type")); + assert.ok(!col.hasOwnProperty("label")); + assert.ok(col.hasOwnProperty("period")); + + assert.strictEqual("_time", col.fieldName); + assert.strictEqual("BaseEvent", col.owner); + assert.strictEqual("timestamp", col.type); + assert.strictEqual("day", col.period); + assert.deepEqual({ + fieldName: "_time", + owner: "BaseEvent", + type: "timestamp", + period: "day" + }, + col); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test cell value", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + var pivotSpecification = obj.createPivotSpecification(); + + // Test error handling for cell value, string + try { + pivotSpecification.addCellValue("iDontExist", "Break Me!", "explosion"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Did not find field iDontExist"); + } + try { + pivotSpecification.addCellValue("source", "Wrong Stats Function", "stdev"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Stats function on string and IPv4 fields must be one of:" + + " list, distinct_values, first, last, count, or distinct_count; found stdev"); + } + + // Add cell value, string + pivotSpecification.addCellValue("source", "Source Value", "dc"); + assert.strictEqual(1, pivotSpecification.cells.length); + + var cell = pivotSpecification.cells[pivotSpecification.cells.length - 1]; + assert.ok(cell.hasOwnProperty("fieldName")); + assert.ok(cell.hasOwnProperty("owner")); + assert.ok(cell.hasOwnProperty("type")); + assert.ok(cell.hasOwnProperty("label")); + assert.ok(cell.hasOwnProperty("value")); + assert.ok(cell.hasOwnProperty("sparkline")); + + assert.strictEqual("source", cell.fieldName); + assert.strictEqual("BaseEvent", cell.owner); + assert.strictEqual("string", cell.type); + assert.strictEqual("Source Value", cell.label); + assert.strictEqual("dc", cell.value); + assert.strictEqual(false, cell.sparkline); + assert.deepEqual({ + fieldName: "source", + owner: "BaseEvent", + type: "string", + label: "Source Value", + value: "dc", + sparkline: false + }, cell); + + // Test error handling for cell value, IPv4 + try { + pivotSpecification.addCellValue("hostip", "Wrong Stats Function", "stdev"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Stats function on string and IPv4 fields must be one of:" + + " list, distinct_values, first, last, count, or distinct_count; found stdev"); + } + + // Add cell value, IPv4 + pivotSpecification.addCellValue("hostip", "Source Value", "dc"); + assert.strictEqual(2, pivotSpecification.cells.length); + + cell = pivotSpecification.cells[pivotSpecification.cells.length - 1]; + assert.ok(cell.hasOwnProperty("fieldName")); + assert.ok(cell.hasOwnProperty("owner")); + assert.ok(cell.hasOwnProperty("type")); + assert.ok(cell.hasOwnProperty("label")); + assert.ok(cell.hasOwnProperty("value")); + assert.ok(cell.hasOwnProperty("sparkline")); + + assert.strictEqual("hostip", cell.fieldName); + assert.strictEqual("test_data", cell.owner); + assert.strictEqual("ipv4", cell.type); + assert.strictEqual("Source Value", cell.label); + assert.strictEqual("dc", cell.value); + assert.strictEqual(false, cell.sparkline); + assert.deepEqual({ + fieldName: "hostip", + owner: "test_data", + type: "ipv4", + label: "Source Value", + value: "dc", + sparkline: false + }, cell); + + // Test error handling for cell value, boolean + try { + pivotSpecification.addCellValue("has_boris", "Booleans not allowed", "sum"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Cannot use boolean valued fields as cell values."); + } + + // Test error handling for cell value, number + try { + pivotSpecification.addCellValue("epsilon", "Wrong Stats Function", "latest"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Stats function on number field must be must be one of:" + + " sum, count, average, max, min, stdev, list, or distinct_values; found latest"); + } + + // Add cell value, number + pivotSpecification.addCellValue("epsilon", "Source Value", "average"); + assert.strictEqual(3, pivotSpecification.cells.length); + + cell = pivotSpecification.cells[pivotSpecification.cells.length - 1]; + assert.ok(cell.hasOwnProperty("fieldName")); + assert.ok(cell.hasOwnProperty("owner")); + assert.ok(cell.hasOwnProperty("type")); + assert.ok(cell.hasOwnProperty("label")); + assert.ok(cell.hasOwnProperty("value")); + assert.ok(cell.hasOwnProperty("sparkline")); + + assert.strictEqual("epsilon", cell.fieldName); + assert.strictEqual("test_data", cell.owner); + assert.strictEqual("number", cell.type); + assert.strictEqual("Source Value", cell.label); + assert.strictEqual("average", cell.value); + assert.strictEqual(false, cell.sparkline); + assert.deepEqual({ + fieldName: "epsilon", + owner: "test_data", + type: "number", + label: "Source Value", + value: "average", + sparkline: false + }, cell); + + // Test error handling for cell value, timestamp + try { + pivotSpecification.addCellValue("_time", "Wrong Stats Function", "max"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Stats function on timestamp field must be one of:" + + " duration, earliest, latest, list, or distinct values; found max"); + } + + // Add cell value, timestamp + pivotSpecification.addCellValue("_time", "Source Value", "earliest"); + assert.strictEqual(4, pivotSpecification.cells.length); + + cell = pivotSpecification.cells[pivotSpecification.cells.length - 1]; + assert.ok(cell.hasOwnProperty("fieldName")); + assert.ok(cell.hasOwnProperty("owner")); + assert.ok(cell.hasOwnProperty("type")); + assert.ok(cell.hasOwnProperty("label")); + assert.ok(cell.hasOwnProperty("value")); + assert.ok(cell.hasOwnProperty("sparkline")); + + assert.strictEqual("_time", cell.fieldName); + assert.strictEqual("BaseEvent", cell.owner); + assert.strictEqual("timestamp", cell.type); + assert.strictEqual("Source Value", cell.label); + assert.strictEqual("earliest", cell.value); + assert.strictEqual(false, cell.sparkline); + assert.deepEqual({ + fieldName: "_time", + owner: "BaseEvent", + type: "timestamp", + label: "Source Value", + value: "earliest", + sparkline: false + }, cell); + + // Test error handling for cell value, count + try { + pivotSpecification.addCellValue("test_data", "Wrong Stats Function", "min"); + assert.ok(false); + } + catch (e) { + assert.ok(e); + assert.strictEqual(e.message, "Stats function on childcount and objectcount fields " + + "must be count; found " + "min"); + } + + // Add cell value, count + pivotSpecification.addCellValue("test_data", "Source Value", "count"); + assert.strictEqual(5, pivotSpecification.cells.length); + + cell = pivotSpecification.cells[pivotSpecification.cells.length - 1]; + assert.ok(cell.hasOwnProperty("fieldName")); + assert.ok(cell.hasOwnProperty("owner")); + assert.ok(cell.hasOwnProperty("type")); + assert.ok(cell.hasOwnProperty("label")); + assert.ok(cell.hasOwnProperty("value")); + assert.ok(cell.hasOwnProperty("sparkline")); + + assert.strictEqual("test_data", cell.fieldName); + assert.strictEqual("test_data", cell.owner); + assert.strictEqual("objectCount", cell.type); + assert.strictEqual("Source Value", cell.label); + assert.strictEqual("count", cell.value); + assert.strictEqual(false, cell.sparkline); + assert.deepEqual({ + fieldName: "test_data", + owner: "test_data", + type: "objectCount", + label: "Source Value", + value: "count", + sparkline: false + }, cell); + + done(); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test pivot throws HTTP exception", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + var obj = dataModel.objectByName("test_data"); + assert.ok(obj); + + obj.createPivotSpecification().pivot(done); + }, + function(pivot, done) { + assert.ok(false); + }], + function(err) { + assert.ok(err); + var expectedErr = "In handler 'datamodelpivot': Error in 'PivotReport': Must have non-empty cells or non-empty rows."; + assert.ok(utils.endsWith(err.message, expectedErr)); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test pivot with simple namespace", function(done) { + if (this.skip) { + done(); + return; + } + var name = "delete-me-" + getNextId(); + var args; + var that = this; + var obj; + var pivotSpecification; + var adhocjob; + try { + // args = JSON.parse(utils.readFile(__filename, "../data/data_model_for_pivot.json")); + fetch('./data/data_model_for_pivot.json') + .then(response => response.json()) + .then(json => { + args = json; + Async.chain([ + function(done) { + that.dataModels.create(name, args, done); + }, + function(dataModel, done) { + obj = dataModel.objectByName("test_data"); + assert.ok(obj); + obj.createLocalAccelerationJob(null, done); + }, + function(job, done) { + adhocjob = job; + assert.ok(job); + pivotSpecification = obj.createPivotSpecification(); + + pivotSpecification.addBooleanRowSplit("has_boris", "Has Boris", "meep", "hilda"); + pivotSpecification.addCellValue("hostip", "Distinct IPs", "count"); + + // Test setting a job + pivotSpecification.setAccelerationJob(job); + assert.strictEqual("string", typeof pivotSpecification.accelerationNamespace); + assert.strictEqual("sid=" + job.sid, pivotSpecification.accelerationNamespace); + + // Test setting a job's SID + pivotSpecification.setAccelerationJob(job.sid); + assert.strictEqual("string", typeof pivotSpecification.accelerationNamespace); + assert.strictEqual("sid=" + job.sid, pivotSpecification.accelerationNamespace); + + pivotSpecification.pivot(done); + }, + function(pivot, done) { + assert.ok(pivot.tstatsSearch); + assert.ok(pivot.tstatsSearch.length > 0); + assert.strictEqual(0, pivot.tstatsSearch.indexOf("| tstats")); + // This test won't work with utils.startsWith due to the regex escaping + assert.strictEqual("| tstats", pivot.tstatsSearch.match("^\\| tstats")[0]); + assert.strictEqual(1, pivot.tstatsSearch.match("^\\| tstats").length); + + pivot.run(done); + }, + function(job, done) { + pollUntil( + job, + function(j) { + return job.properties().isDone; + }, + 10, + done + ); + }, + function(job, done) { + assert.ok("FAILED" !== job.properties().dispatchState); + + assert.strictEqual(0, job.properties().request.search.indexOf("| tstats")); + // This test won't work with utils.startsWith due to the regex escaping + assert.strictEqual("| tstats", job.properties().request.search.match("^\\| tstats")[0]); + assert.strictEqual(1, job.properties().request.search.match("^\\| tstats").length); + + adhocjob.cancel(done); + }], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + } + catch(err) { + // Fail if we can't read the file, likely to occur in the browser + assert.ok(!err); + done(); + } + }); + + it("Callback#Pivot - test pivot column range split", function(done) { + // This test is here because we had a problem with fields that were supposed to be + // numbers being expected as strings in Splunk 6.0. This was fixed in Splunk 6.1, and accepts + // either strings or numbers. + + if (this.skip) { + done(); + return; + } + var that = this; + var search; + Async.chain([ + function(done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + var dm = dataModels.item("internal_audit_logs"); + var obj = dm.objectByName("searches"); + var pivotSpecification = obj.createPivotSpecification(); + + pivotSpecification.addRowSplit("user", "Executing user"); + pivotSpecification.addRangeColumnSplit("exec_time", {start: 0, end: 12, step: 5, limit: 4}); + pivotSpecification.addCellValue("search", "Search Query", "values"); + pivotSpecification.pivot(done); + }, + function(pivot, done) { + // If tstats is undefined, use pivotSearch + search = pivot.tstatsSearch || pivot.pivotSearch; + pivot.run(done); + }, + function(job, done) { + pollUntil( + job, + function(j) { + return job.properties().isDone; + }, + 10, + done + ); + }, + function(job, done) { + assert.notStrictEqual("FAILED", job.properties().dispatchState); + // Make sure the job is run with the correct search query + assert.strictEqual(search, job.properties().request.search); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Pivot - test pivot with PivotSpecification.run and Job.track", function(done) { + if (this.skip) { + done(); + return; + } + var that = this; + Async.chain([ + function(done) { + that.dataModels.fetch(done); + }, + function(dataModels, done) { + var dm = dataModels.item("internal_audit_logs"); + var obj = dm.objectByName("searches"); + var pivotSpecification = obj.createPivotSpecification(); + + pivotSpecification.addRowSplit("user", "Executing user"); + pivotSpecification.addRangeColumnSplit("exec_time", {start: 0, end: 12, step: 5, limit: 4}); + pivotSpecification.addCellValue("search", "Search Query", "values"); + + pivotSpecification.run({}, done); + }, + function(job, pivot, done) { + job.track({}, function(job) { + assert.strictEqual(pivot.tstatsSearch || pivot.pivotSearch, job.properties().request.search); + done(null, job); + }); + }, + function(job, done) { + assert.notStrictEqual("FAILED", job.properties().dispatchState); + job.cancel(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#DataModels - delete any remaining data models created by the SDK tests", function(done) { + if (this.skip) { + done(); + return; + } + svc.dataModels().fetch(function(err, dataModels) { + if (err) { + assert.ok(!err); + } + + var dms = dataModels.list(); + Async.seriesEach( + dms, + function(datamodel, i, done) { + // Delete any test data models that we created + if (utils.startsWith(datamodel.name, "delete-me")) { + datamodel.remove(done); + } + else { + done(); + } + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + }); + + describe("App Tests", function(){ + + before(function (done) { + idCounter = 0; + this.service = svc; + done(); + }) + + it("Callback#list applications", function(done) { + var apps = this.service.apps(); + apps.fetch(function(err, apps) { + var appList = apps.list(); + assert.ok(appList.length > 0); + done(); + }); + }); + + it("Callback#contains applications", function(done) { + var apps = this.service.apps(); + apps.fetch(function(err, apps) { + var app = apps.item("search"); + assert.ok(app); + done(); + }); + }); + + it("Callback#create + contains app", function(done) { + var name = "jssdk_testapp_" + getNextId(); + var apps = this.service.apps(); + + apps.create({name: name}, function(err, app) { + var appName = app.name; + apps.fetch(function(err, apps) { + var entity = apps.item(appName); + assert.ok(entity); + app.remove(function() { + done(); + }); + }); + }); + }); + + it("Callback#create + modify app", function(done) { + var DESCRIPTION = "TEST DESCRIPTION"; + var VERSION = "1.1.0"; + + var name = "jssdk_testapp_" + getNextId(); + var apps = this.service.apps(); + + Async.chain([ + function(callback) { + apps.create({name: name}, callback); + }, + function(app, callback) { + assert.ok(app); + assert.strictEqual(app.name, name); + var versionMatches = app.properties().version === "1.0" || + app.properties().version === "1.0.0"; + assert.ok(versionMatches); + + app.update({ + description: DESCRIPTION, + version: VERSION + }, callback); + }, + function(app, callback) { + assert.ok(app); + var properties = app.properties(); + + assert.strictEqual(properties.description, DESCRIPTION); + assert.strictEqual(properties.version, VERSION); + + app.remove(callback); + } + ], function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#delete test applications", function(done) { + var apps = this.service.apps(); + apps.fetch(function(err, apps) { + var appList = apps.list(); + + Async.parallelEach( + appList, + function(app, idx, callback) { + if (utils.startsWith(app.name, "jssdk_")) { + app.remove(callback); + } + else { + callback(); + } + }, function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + it("list applications with cookies as authentication", function(done) { + this.service.serverInfo(function (err, info) { + // Cookie authentication was added in splunk 6.2 + var majorVersion = parseInt(info.properties().version.split(".")[0], 10); + var minorVersion = parseInt(info.properties().version.split(".")[1], 10); + // Skip cookie test if Splunk older than 6.2 + if(majorVersion < 6 || (majorVersion === 6 && minorVersion < 2)) { + splunkjs.Logger.log("Skipping cookie test..."); + done(); + return; + } + + var service = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + username: svc.username, + password: svc.password, + version: svc.version + }); + + var service2 = new splunkjs.Service(svc.http, + { + scheme: svc.scheme, + host: svc.host, + port: svc.port, + version: svc.version + }); + + Async.chain([ + function (done) { + service.login(done); + }, + function (job, done) { + // Save the cookie store + var cookieStore = service.http._cookieStore; + // Test that there are cookies + assert.ok(!utils.isEmpty(cookieStore)); + + // Add the cookies to a service with no other authenitcation information + service2.http._cookieStore = cookieStore; + + var apps = service2.apps(); + apps.fetch(done); + }, + function (apps, done) { + var appList = apps.list(); + assert.ok(appList.length > 0); + assert.ok(!utils.isEmpty(service2.http._cookieStore)); + done(); + } + ], + function(err) { + // Test that no errors were returned + assert.ok(!err); + done(); + }); + }); + }); + + }); + + describe("Saved Search Tests", function() { + before( function(done) { + this.service = svc; + this.loggedOutService = loggedOutSvc; + done(); + }); + + it("Callback#list", function(done) { + var searches = this.service.savedSearches(); + searches.fetch(function(err, searches) { + var savedSearches = searches.list(); + assert.ok(savedSearches.length > 0); + + for(var i = 0; i < savedSearches.length; i++) { + assert.ok(savedSearches[i]); + } + + done(); + }); + }); + + it("Callback#contains", function(done) { + var searches = this.service.savedSearches(); + searches.fetch(function(err, searches) { + var search = searches.item("Errors in the last hour"); + assert.ok(search); + + done(); + }); + }); + + it("Callback#suppress", function(done) { + var searches = this.service.savedSearches(); + searches.fetch(function(err, searches) { + var search = searches.item("Errors in the last hour"); + assert.ok(search); + + search.suppressInfo(function(err, info, search) { + assert.ok(!err); + done(); + }); + }); + }); + + it("Callback#list limit count", function(done) { + var searches = this.service.savedSearches(); + searches.fetch({count: 2}, function(err, searches) { + var savedSearches = searches.list(); + assert.strictEqual(savedSearches.length, 2); + + for(var i = 0; i < savedSearches.length; i++) { + assert.ok(savedSearches[i]); + } + + done(); + }); + }); + + it("Callback#list filter", function(done) { + var searches = this.service.savedSearches(); + searches.fetch({search: "Error"}, function(err, searches) { + var savedSearches = searches.list(); + assert.ok(savedSearches.length > 0); + + for(var i = 0; i < savedSearches.length; i++) { + assert.ok(savedSearches[i]); + } + + done(); + }); + }); + + it("Callback#list offset", function(done) { + var searches = this.service.savedSearches(); + searches.fetch({offset: 2, count: 1}, function(err, searches) { + var savedSearches = searches.list(); + assert.strictEqual(searches.paging().offset, 2); + assert.strictEqual(searches.paging().perPage, 1); + assert.strictEqual(savedSearches.length, 1); + + for(var i = 0; i < savedSearches.length; i++) { + assert.ok(savedSearches[i]); + } + + done(); + }); + }); + + it("Callback#create + modify + delete saved search", function(done) { + var name = "jssdk_savedsearch"; + var originalSearch = "search * | head 1"; + var updatedSearch = "search * | head 10"; + var updatedDescription = "description"; + + var searches = this.service.savedSearches({owner: this.service.username, app: "sdk-app-collection"}); + + Async.chain([ + function(done) { + searches.create({search: originalSearch, name: name}, done); + }, + function(search, done) { + assert.ok(search); + + assert.strictEqual(search.name, name); + assert.strictEqual(search.properties().search, originalSearch); + assert.ok(!search.properties().description); + + search.update({search: updatedSearch}, done); + }, + function(search, done) { + assert.ok(search); + assert.ok(search); + + assert.strictEqual(search.name, name); + assert.strictEqual(search.properties().search, updatedSearch); + assert.ok(!search.properties().description); + + search.update({description: updatedDescription}, done); + }, + function(search, done) { + assert.ok(search); + assert.ok(search); + + assert.strictEqual(search.name, name); + assert.strictEqual(search.properties().search, updatedSearch); + assert.strictEqual(search.properties().description, updatedDescription); + + search.fetch(done); + }, + function(search, done) { + // Verify that we have the required fields + assert.ok(search.fields().optional.length > 1); + assert.ok(utils.indexOf(search.fields().optional, "disabled") > -1); + + search.remove(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#dispatch error", function(done) { + var name = "jssdk_savedsearch_" + getNextId(); + var originalSearch = "search index=_internal | head 1"; + var search = new splunkjs.Service.SavedSearch( + this.loggedOutService, + name, + {owner: "nobody", app: "search"} + ); + search.dispatch(function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#dispatch omitting optional arguments", function(done) { + var name = "jssdk_savedsearch_" + getNextId(); + var originalSearch = "search index=_internal | head 1"; + + var searches = this.service.savedSearches({owner: this.service.username, app: "sdk-app-collection"}); + + Async.chain( + [function(done) { + searches.create({search: originalSearch, name: name}, done); + }, + function(search, done) { + assert.ok(search); + + assert.strictEqual(search.name, name); + assert.strictEqual(search.properties().search, originalSearch); + assert.ok(!search.properties().description); + + search.dispatch(done); + }, + function(job, search, done) { + assert.ok(job); + assert.ok(search); + done(); + }] + ); + }); + + it("Callback#history error", function(done) { + var name = "jssdk_savedsearch_" + getNextId(); + var originalSearch = "search index=_internal | head 1"; + var search = new splunkjs.Service.SavedSearch( + this.loggedOutService, + name, + {owner: "nobody", app: "search", sharing: "system"} + ); + search.history(function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#Update error", function(done) { + var name = "jssdk_savedsearch_" + getNextId(); + var originalSearch = "search index=_internal | head 1"; + var search = new splunkjs.Service.SavedSearch( + this.loggedOutService, + name, + {owner: "nobody", app: "search", sharing: "system"} + ); + search.update( + {}, + function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#oneshot requires search string", function(done) { + assert.throws(function() { this.service.oneshotSearch({name: "jssdk_oneshot_" + getNextId()}, function(err) {});}); + done(); + }); + + it("Callback#Create + dispatch + history", function(done) { + var name = "jssdk_savedsearch_" + getNextId(); + var originalSearch = "search index=_internal | head 1"; + + var searches = this.service.savedSearches({owner: this.service.username, app: "sdk-app-collection"}); + + Async.chain( + function(done) { + searches.create({search: originalSearch, name: name}, done); + }, + function(search, done) { + assert.ok(search); + + assert.strictEqual(search.name, name); + assert.strictEqual(search.properties().search, originalSearch); + assert.ok(!search.properties().description); + + search.dispatch({force_dispatch: false, "dispatch.buckets": 295}, done); + }, + function(job, search, done) { + assert.ok(job); + assert.ok(search); + + pollUntil( + job, + function(j) { + return job.properties()["isDone"]; + }, + 10, + Async.augment(done, search) + ); + }, + function(job, search, done) { + assert.strictEqual(job.properties().statusBuckets, 295); + search.history(Async.augment(done, job)); + }, + function(jobs, search, originalJob, done) { + assert.ok(jobs); + assert.ok(jobs.length > 0); + assert.ok(search); + assert.ok(originalJob); + + var cancel = function(job) { + return function(cb) { + job.cancel(cb); + }; + }; + + var found = false; + var cancellations = []; + for(var i = 0; i < jobs.length; i++) { + cancellations.push(cancel(jobs[i])); + found = found || (jobs[i].sid === originalJob.sid); + } + + assert.ok(found); + + search.remove(function(err) { + if (err) { + done(err); + } + else { + Async.parallel(cancellations, done); + } + }); + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#job events fails", function(done) { + var job = new splunkjs.Service.Job(this.loggedOutService, "abc", {}); + job.events({}, function (err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#job preview fails", function(done) { + var job = new splunkjs.Service.Job(this.loggedOutService, "abc", {}); + job.preview({}, function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#job results fails", function(done) { + var job = new splunkjs.Service.Job(this.loggedOutService, "abc", {}); + job.results({}, function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#job searchlog fails", function(done) { + var job = new splunkjs.Service.Job(this.loggedOutService, "abc", {}); + job.searchlog(function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#job summary fails", function(done) { + var job = new splunkjs.Service.Job(this.loggedOutService, "abc", {}); + job.summary({}, function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#job timeline fails", function(done) { + var job = new splunkjs.Service.Job(this.loggedOutService, "abc", {}); + job.timeline({}, function(err) { + assert.ok(err); + done(); + }); + }); + + it("Callback#delete test saved searches", function(done) { + var searches = this.service.savedSearches({owner: this.service.username, app: "sdk-app-collection"}); + searches.fetch(function(err, searches) { + var searchList = searches.list(); + Async.parallelEach( + searchList, + function(search, idx, callback) { + if (utils.startsWith(search.name, "jssdk_")) { + search.remove(callback); + } + else { + callback(); + } + }, function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + it("Callback#setupInfo fails", function(done) { + var searches = new splunkjs.Service.Application(this.loggedOutService, "search"); + searches.setupInfo(function(err, content, that) { + assert.ok(err); + done(); + }); + }); + + it("Callback#setupInfo succeeds", function(done) { + var app = new splunkjs.Service.Application(this.service, "sdk-app-collection"); + app.setupInfo(function(err, content, app) { + // This error message was removed in modern versions of Splunk + if (err) { + assert.ok(err.data.messages[0].text.match("Setup configuration file does not")); + splunkjs.Logger.log("ERR ---", err.data.messages[0].text); + } + else { + assert.ok(app); + } + done(); + }); + }); + + it("Callback#updateInfo", function(done) { + var app = new splunkjs.Service.Application(this.service, "search"); + app.updateInfo(function(err, info, app) { + assert.ok(!err); + assert.ok(app); + assert.strictEqual(app.name, 'search'); + done(); + }); + }); + + it("Callback#updateInfo failure", function(done) { + var app = new splunkjs.Service.Application(this.loggedOutService, "sdk-app-collection"); + app.updateInfo(function(err, info, app) { + assert.ok(err); + done(); + }); + }); + }); + + describe("Fired Alerts Tests", function() { + before( function(done) { + this.service = svc; + this.loggedOutService = loggedOutSvc; + + var indexes = this.service.indexes(); + done(); + }); + + it("Callback#create + verify emptiness + delete new alert group", function(done) { + var searches = this.service.savedSearches({owner: this.service.username}); + + var name = "jssdk_savedsearch_alert_" + getNextId(); + var searchConfig = { + "name": name, + "search": "index=_internal | head 1", + "alert_type": "always", + "alert.severity": "2", + "alert.suppress": "0", + "alert.track": "1", + "dispatch.earliest_time": "-1h", + "dispatch.latest_time": "now", + "is_scheduled": "1", + "cron_schedule": "* * * * *" + }; + + Async.chain([ + function(done) { + searches.create(searchConfig, done); + }, + function(search, done) { + assert.ok(search); + assert.strictEqual(search.alertCount(), 0); + search.history(done); + }, + function(jobs, search, done) { + assert.strictEqual(jobs.length, 0); + assert.strictEqual(search.firedAlertGroup().count(), 0); + searches.service.firedAlertGroups().fetch( Async.augment(done, search) ); + }, + function(firedAlertGroups, originalSearch, done) { + assert.strictEqual(firedAlertGroups.list().indexOf(originalSearch.name), -1); + done(null, originalSearch); + }, + function(originalSearch, done) { + originalSearch.remove(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + // This test is not stable, commenting it out until we figure it out + // "Callback#alert is triggered + test firedAlert entity -- FAILS INTERMITTENTLY", function(done) { + // var searches = this.service.savedSearches({owner: this.service.username}); + // var indexName = "sdk-tests-alerts"; + // var name = "jssdk_savedsearch_alert_" + getNextId(); + + // // Real-time search config + // var searchConfig = { + // "name": name, + // "search": "index="+indexName+" sourcetype=sdk-tests-alerts | head 1", + // "alert_type": "always", + // "alert.severity": "2", + // "alert.suppress": "0", + // "alert.track": "1", + // "dispatch.earliest_time": "rt-1s", + // "dispatch.latest_time": "rt", + // "is_scheduled": "1", + // "cron_schedule": "* * * * *" + // }; + + // Async.chain([ + // function(done) { + // searches.create(searchConfig, done); + // }, + // function(search, done) { + // assert.ok(search); + // assert.strictEqual(search.alertCount(), 0); + // assert.strictEqual(search.firedAlertGroup().count(), 0); + + // var indexes = search.service.indexes(); + // indexes.create(indexName, {}, function(err, index) { + // if (err && err.status !== 409) { + // done(new Error("Index creation failed for an unknown reason")); + // } + // done(null, search); + // }); + // }, + // function(originalSearch, done) { + // var indexes = originalSearch.service.indexes(); + // indexes.fetch(function(err, indexes) { + // if (err) { + // done(err); + // } + // else { + // var index = indexes.item(indexName); + // assert.ok(index); + // index.enable(Async.augment(done, originalSearch)); + // } + // }); + // }, + // function(index, originalSearch, done) { + // //Is the index enabled? + // assert.ok(!index.properties().disabled); + // //refresh the index + // index.fetch(Async.augment(done, originalSearch)); + // }, + // function(index, originalSearch, done) { + // //Store the current event count for a later comparison + // var eventCount = index.properties().totalEventCount; + + // assert.strictEqual(index.properties().sync, 0); + // assert.ok(!index.properties().disabled); + + // index.fetch(Async.augment(done, originalSearch, eventCount)); + // }, + // function(index, originalSearch, eventCount, done) { + // // submit an event + // index.submitEvent( + // "JS SDK: testing alerts", + // { + // sourcetype: "sdk-tests-alerts" + // }, + // Async.augment(done, originalSearch, eventCount) + // ); + // }, + // function(result, index, originalSearch, eventCount, done) { + // Async.sleep(1000, function(){ + // //refresh the search + // index.fetch(Async.augment(done, originalSearch, eventCount)); + // }); + // }, + // function(index, originalSearch, eventCount, done) { + // // Did the event get submitted + // assert.strictEqual(index.properties().totalEventCount, eventCount+1); + // // Refresh the search + // originalSearch.fetch(Async.augment(done, index)); + // }, + // function(originalSearch, index, done) { + // splunkjs.Logger.log("\tAlert count pre-fetch", originalSearch.alertCount()); + // var attemptNum = 1; + // var maxAttempts = 20; + // Async.whilst( + // function() { + // // When this returns false, it hits the final function in the chain + // splunkjs.Logger.log("\tFetch attempt", attemptNum, "of", maxAttempts, "alertCount", originalSearch.alertCount()); + // if (originalSearch.alertCount() !== 0) { + // return false; + // } + // else { + // attemptNum++; + // return attemptNum < maxAttempts; + // } + // }, + // function(callback) { + // Async.sleep(500, function() { + // originalSearch.fetch(callback); + // }); + // }, + // function(err) { + // splunkjs.Logger.log("Attempted fetching", attemptNum, "of", maxAttempts, "result is", originalSearch.alertCount() !== 0); + // originalSearch.fetch(Async.augment(done, index)); + // } + // ); + // }, + // function(originalSearch, index, done) { + // splunkjs.Logger.log("about to fetch"); + // splunkjs.Logger.log("SavedSearch name was: " + originalSearch.name); + // svc.firedAlertGroups({username: svc.username}).fetch(Async.augment(done, index, originalSearch)); + // }, + // function(firedAlertGroups, index, originalSearch, done) { + // Async.seriesEach( + // firedAlertGroups.list(), + // function(firedAlertGroup, innerIndex, seriescallback) { + // Async.chain([ + // function(insideChainCallback) { + // firedAlertGroup.list(insideChainCallback); + // }, + // function(firedAlerts, firedAlertGroup, insideChainCallback) { + // for(var i = 0; i < firedAlerts.length; i++) { + // var firedAlert = firedAlerts[i]; + // firedAlert.actions(); + // firedAlert.alertType(); + // firedAlert.isDigestMode(); + // firedAlert.expirationTime(); + // firedAlert.savedSearchName(); + // firedAlert.severity(); + // firedAlert.sid(); + // firedAlert.triggerTime(); + // firedAlert.triggerTimeRendered(); + // firedAlert.triggeredAlertCount(); + // } + // insideChainCallback(null); + // } + // ], + // function(err) { + // if (err) { + // seriescallback(err); + // } + // seriescallback(null); + // } + // ); + // }, + // function(err) { + // if (err) { + // done(err, originalSearch, index); + // } + // done(null, originalSearch, index); + // } + // ); + // }, + // function(originalSearch, index, done) { + // // Make sure the event count has incremented, as expected + // assert.strictEqual(originalSearch.alertCount(), 1); + // // Remove the search, especially because it's a real-time search + // originalSearch.remove(Async.augment(done, index)); + // }, + // function(index, done) { + // Async.sleep(500, function() { + // index.remove(done); + // }); + // } + // ], + // function(err) { + // assert.ok(!err); + // done(); + // } + // ); + // }, + + it("Callback#delete all alerts", function(done) { + var namePrefix = "jssdk_savedsearch_alert_"; + var alertList = this.service.savedSearches().list(); + + Async.parallelEach( + alertList, + function(alert, idx, callback) { + if (utils.startsWith(alert.name, namePrefix)) { + splunkjs.Logger.log("ALERT ---", alert.name); + alert.remove(callback); + } + else { + callback(); + } + }, function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + describe("Properties Tests", function() { + before( function(done) { + idCounter=0; + this.service = svc; + done(); + }); + + it("Callback#list", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { + that.service.configurations(namespace).fetch(done); + }, + function(props, done) { + var files = props.list(); + assert.ok(files.length > 0); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#item", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var file = props.item("web"); + assert.ok(file); + file.fetch(done); + }, + function(file, done) { + assert.strictEqual(file.name, "web"); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#contains stanza", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var file = props.item("web"); + assert.ok(file); + file.fetch(done); + }, + function(file, done) { + assert.strictEqual(file.name, "web"); + var stanza = file.item("settings"); + assert.ok(stanza); + stanza.fetch(done); + }, + function(stanza, done) { + assert.ok(stanza.properties().hasOwnProperty("httpport")); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#create file + create stanza + update stanza", function(done) { + var that = this; + var fileName = "jssdk_file_" + getNextId(); + var value = "barfoo_" + getNextId(); + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { + var properties = that.service.configurations(namespace); + properties.fetch(done); + }, + function(properties, done) { + properties.create(fileName, done); + }, + function(file, done) { + file.create("stanza", done); + }, + function(stanza, done) { + stanza.update({"jssdk_foobar": value}, done); + }, + function(stanza, done) { + assert.strictEqual(stanza.properties()["jssdk_foobar"], value); + done(); + }, + function(done) { + var file = new splunkjs.Service.ConfigurationFile(svc, fileName); + file.fetch(done); + }, + function(file, done) { + var stanza = file.item("stanza"); + assert.ok(stanza); + stanza.remove(done); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + }); + + describe("Configuration Tests", function() { + before(function(done) { + idCounter=0; + this.service = svc; + done(); + }); + + it("Callback#list", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var files = props.list(); + assert.ok(files.length > 0); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#contains", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var file = props.item("web"); + assert.ok(file); + file.fetch(done); + }, + function(file, done) { + assert.strictEqual(file.name, "web"); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#contains stanza", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var file = props.item("web"); + assert.ok(file); + file.fetch(done); + }, + function(file, done) { + assert.strictEqual(file.name, "web"); + + var stanza = file.item("settings"); + assert.ok(stanza); + stanza.fetch(done); + }, + function(stanza, done) { + assert.ok(stanza.properties().hasOwnProperty("httpport")); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#configurations init", function(done) { + assert.throws(function() { + var confs = new splunkjs.Service.Configurations( + this.service, + {owner: "-", app: "-", sharing: "system"} + ); + }); + done(); + }); + + it("Callback#create file + create stanza + update stanza", function(done) { + var that = this; + var namespace = {owner: "nobody", app: "system"}; + var fileName = "jssdk_file_" + getNextId(); + var value = "barfoo_" + getNextId(); + + Async.chain([ + function(done) { + var configs = svc.configurations(namespace); + configs.fetch(done); + }, + function(configs, done) { + configs.create({__conf: fileName}, done); + }, + function(file, done) { + if (file.item("stanza")) { + file.item("stanza").remove(); + } + file.create("stanza", done); + }, + function(stanza, done) { + stanza.update({"jssdk_foobar": value}, done); + }, + function(stanza, done) { + assert.strictEqual(stanza.properties()["jssdk_foobar"], value); + done(); + }, + function(done) { + var file = new splunkjs.Service.ConfigurationFile(svc, fileName); + file.fetch(done); + }, + function(file, done) { + var stanza = file.item("stanza"); + assert.ok(stanza); + stanza.remove(done); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#can get default stanza", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var file = props.item("savedsearches"); + assert.strictEqual(namespace, file.namespace); + assert.ok(file); + file.fetch(done); + }, + function(file, done) { + assert.strictEqual(namespace, file.namespace); + file.getDefaultStanza().fetch(done); + }, + function(stanza, done) { + assert.strictEqual(stanza.name, "default"); + assert.strictEqual(namespace, stanza.namespace); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + + it("Callback#updating default stanza is noop", function(done) { + var that = this; + var namespace = {owner: "admin", app: "search"}; + var backup = null; + var invalid = "this won't work"; + + Async.chain([ + function(done) { that.service.configurations(namespace).fetch(done); }, + function(props, done) { + var file = props.item("savedsearches"); + assert.strictEqual(namespace, file.namespace); + assert.ok(file); + file.fetch(done); + }, + function(file, done) { + assert.strictEqual(namespace, file.namespace); + file.getDefaultStanza().fetch(done); + }, + function(stanza, done) { + assert.ok(stanza._properties.hasOwnProperty("max_concurrent")); + assert.strictEqual(namespace, stanza.namespace); + backup = stanza._properties.max_concurrent; + stanza.update({"max_concurrent": invalid}, done); + }, + function(stanza, done) { + assert.ok(stanza.properties().hasOwnProperty("max_concurrent")); + assert.strictEqual(stanza.properties()["max_concurrent"], backup); + assert.notStrictEqual(stanza.properties()["max_concurrent"], invalid); + stanza.fetch(done); + }, + function(stanza, done) { + assert.ok(stanza.properties().hasOwnProperty("max_concurrent")); + assert.strictEqual(stanza.properties()["max_concurrent"], backup); + assert.notStrictEqual(stanza.properties()["max_concurrent"], invalid); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + }); + }); + }); + + describe('Storage Passwords Test', function(){ + before(function (done) { + idCounter=0; + this.service=svc; + done(); + }) + it("Callback#Create", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var realm = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create with backslashes", function(done) { + var startcount = -1; + var name = "\\delete-me-" + getNextId(); + var realm = "\\delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual("\\" + realm + ":\\" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create with slashes", function(done) { + var startcount = -1; + var name = "/delete-me-" + getNextId(); + var realm = "/delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create without realm", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual("", storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create should fail without user, or realm", function(done) { + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + storagePasswords.create({name: null, password: "changed!"}, done); + } + ], + function(err) { + assert.ok(err); + done(); + } + ); + }); + + it("Callback#Create should fail without password", function(done) { + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + storagePasswords.create({name: "something", password: null}, done); + } + ], + function(err) { + assert.ok(err); + done(); + } + ); + }); + + it("Callback#Create should fail without user, realm, or password", function(done) { + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + storagePasswords.create({name: null, password: null}, done); + } + ], + function(err) { + assert.ok(err); + done(); + } + ); + }); + + it("Callback#Create with colons", function(done) { + var startcount = -1; + var name = ":delete-me-" + getNextId(); + var realm = ":delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual("\\" + realm + ":\\" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create crazy", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var realm = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({ + name: name + ":end!@#$%^&*()_+{}:|<>?", + realm: ":start::!@#$%^&*()_+{}:|<>?" + realm, + password: "changed!"}, + done); + }, + function(storagePassword, done) { + assert.strictEqual(name + ":end!@#$%^&*()_+{}:|<>?", storagePassword.properties().username); + assert.strictEqual("\\:start\\:\\:!@#$%^&*()_+{}\\:|<>?" + realm + ":" + name + "\\:end!@#$%^&*()_+{}\\:|<>?:", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(":start::!@#$%^&*()_+{}:|<>?" + realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Create with unicode chars", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var realm = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({ + name: name + ":end!@#$%^&*()_+{}:|<>?쎼 and 쎶 and <&> für", + realm: ":start::!@#$%^&*()_+{}:|<>?" + encodeURIComponent("쎼 and 쎶 and <&> für") + realm, + password: decodeURIComponent(encodeURIComponent("쎼 and 쎶 and <&> für"))}, + done); + }, + function(storagePassword, done) { + assert.strictEqual(name + ":end!@#$%^&*()_+{}:|<>?쎼 and 쎶 and <&> für", storagePassword.properties().username); + assert.strictEqual("\\:start\\:\\:!@#$%^&*()_+{}\\:|<>?" + encodeURIComponent("쎼 and 쎶 and <&> für") + realm + ":" + name + "\\:end!@#$%^&*()_+{}\\:|<>?쎼 and 쎶 and <&> für:", storagePassword.name); + //assert.strictEqual(decodeURIComponent(encodeURIComponent("쎼 and 쎶 and <&> für")), storagePassword.properties().clear_password); + assert.strictEqual(":start::!@#$%^&*()_+{}:|<>?" + encodeURIComponent("쎼 and 쎶 and <&> für") + realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Read", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var realm = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + try { + assert.ok(!!storagePasswords.item(realm + ":" + name + ":")); + } + catch (e) { + assert.ok(false); + } + + var list = storagePasswords.list(); + var found = false; + + assert.strictEqual(startcount + 1, list.length); + for (var i = 0; i < list.length; i ++) { + if (realm + ":" + name + ":" === list[i].name) { + found = true; + } + } + assert.ok(found); + + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Read with slashes", function(done) { + var startcount = -1; + var name = "/delete-me-" + getNextId(); + var realm = "/delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + try { + assert.ok(!!storagePasswords.item(realm + ":" + name + ":")); + } + catch (e) { + assert.ok(false); + } + + var list = storagePasswords.list(); + var found = false; + + assert.strictEqual(startcount + 1, list.length); + for (var i = 0; i < list.length; i ++) { + if (realm + ":" + name + ":" === list[i].name) { + found = true; + } + } + assert.ok(found); + + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Update", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var realm = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.update({password: "changed"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + var list = storagePasswords.list(); + var found = false; + var index = -1; + + assert.strictEqual(startcount + 1, list.length); + for (var i = 0; i < list.length; i ++) { + if (realm + ":" + name + ":" === list[i].name) { + found = true; + index = i; + assert.strictEqual(name, list[i].properties().username); + assert.strictEqual(realm + ":" + name + ":", list[i].name); + assert.strictEqual("changed", list[i].properties().clear_password); + assert.strictEqual(realm, list[i].properties().realm); + } + } + assert.ok(found); + + if (!found) { + done(new Error("Didn't find the created password")); + } + else { + list[index].remove(done); + } + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Delete", function(done) { + var startcount = -1; + var name = "delete-me-" + getNextId(); + var realm = "delete-me-" + getNextId(); + var that = this; + Async.chain([ + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + startcount = storagePasswords.list().length; + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + assert.strictEqual(realm + ":" + name + ":", storagePassword.name); + //assert.strictEqual("changed!", storagePassword.properties().clear_password); + assert.strictEqual(realm, storagePassword.properties().realm); + that.service.storagePasswords().fetch(Async.augment(done, storagePassword)); + }, + function(storagePasswords, storagePassword, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + storagePassword.remove(done); + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + storagePasswords.create({name: name, realm: realm, password: "changed!"}, done); + }, + function(storagePassword, done) { + assert.strictEqual(name, storagePassword.properties().username); + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount + 1, storagePasswords.list().length); + var list = storagePasswords.list(); + var found = false; + var index = -1; + + assert.strictEqual(startcount + 1, list.length); + for (var i = 0; i < list.length; i ++) { + if (realm + ":" + name + ":" === list[i].name) { + found = true; + index = i; + assert.strictEqual(name, list[i].properties().username); + assert.strictEqual(realm + ":" + name + ":", list[i].name); + assert.strictEqual("changed!", list[i].properties().clear_password); + assert.strictEqual(realm, list[i].properties().realm); + } + } + assert.ok(found); + + if (!found) { + done(new Error("Didn't find the created password")); + } + else { + list[index].remove(done); + } + }, + function(done) { + that.service.storagePasswords().fetch(done); + }, + function(storagePasswords, done) { + assert.strictEqual(startcount, storagePasswords.list().length); + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + describe('Index Tests', function() { + before(function(done){ + idCounter = 0; + this.service = svc; + this.loggedOutService = loggedOutSvc; + + // Create the index for everyone to use + var name = this.indexName = "sdk-tests"; + var indexes = this.service.indexes(); + indexes.create(name, {}, function(err, index) { + if (err && err.status !== 409) { + throw new Error("Index creation failed for an unknown reason"); + } + done(); + }); + + }) + + it("Callback#remove index fails on Splunk 4.x", function(done) { + var original_version = this.service.version; + this.service.version = "4.0"; + + var index = this.service.indexes().item(this.indexName); + assert.throws(function() { index.remove(function(err) {}); }); + + this.service.version = original_version; + done(); + }); + + it("Callback#remove index", function(done) { + var indexes = this.service.indexes(); + + // Must generate a private index because an index cannot + // be recreated with the same name as a deleted index + // for a certain period of time after the deletion. + var salt = Math.floor(Math.random() * 65536); + var myIndexName = this.indexName + '-' + salt; + + if (this.service.versionCompare("5.0") < 0) { + splunkjs.Logger.info("", "Must be running Splunk 5.0+ for this test to work."); + done(); + return; + } + + Async.chain([ + function(callback) { + indexes.create(myIndexName, {}, callback); + }, + function(index, callback) { + index.remove(callback); + }, + function(callback) { + var numTriesLeft = 50; + var delayPerTry = 100; // ms + + Async.whilst( + function() { return indexes.item(myIndexName) && ((numTriesLeft--) > 0); }, + function(iterDone) { + Async.sleep(delayPerTry, function() { indexes.fetch(iterDone); }); + }, + function(err) { + if (err) { + callback(err); + } + else { + callback(numTriesLeft <= 0 ? "Timed out" : null); + } + } + ); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#list indexes", function(done) { + var indexes = this.service.indexes(); + indexes.fetch(function(err, indexes) { + var indexList = indexes.list(); + assert.ok(indexList.length > 0); + done(); + }); + }); + + it("Callback#contains index", function(done) { + var indexes = this.service.indexes(); + var indexName = this.indexName; + + indexes.fetch(function(err, indexes) { + var index = indexes.item(indexName); + assert.ok(index); + done(); + }); + }); + + it("Callback#modify index", function(done) { + + var name = this.indexName; + var indexes = this.service.indexes(); + var originalSyncMeta = false; + + Async.chain([ + function(callback) { + indexes.fetch(callback); + }, + function(indexes, callback) { + var index = indexes.item(name); + assert.ok(index); + + originalSyncMeta = index.properties().syncMeta; + index.update({ + syncMeta: !originalSyncMeta + }, callback); + }, + function(index, callback) { + assert.ok(index); + var properties = index.properties(); + + assert.strictEqual(!originalSyncMeta, properties.syncMeta); + + index.update({ + syncMeta: !properties.syncMeta + }, callback); + }, + function(index, callback) { + assert.ok(index); + var properties = index.properties(); + + assert.strictEqual(originalSyncMeta, properties.syncMeta); + callback(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Enable+disable index", function(done) { + + var name = this.indexName; + var indexes = this.service.indexes(); + + Async.chain([ + function(callback) { + indexes.fetch(callback); + }, + function(indexes, callback) { + var index = indexes.item(name); + assert.ok(index); + + index.disable(callback); + }, + function(index, callback) { + assert.ok(index); + index.fetch(callback); + }, + function(index, callback) { + assert.ok(index); + assert.ok(index.properties().disabled); + + index.enable(callback); + }, + function(index, callback) { + assert.ok(index); + index.fetch(callback); + }, + function(index, callback) { + assert.ok(index); + assert.ok(!index.properties().disabled); + + callback(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Service submit event", function(done) { + var message = "Hello World -- " + getNextId(); + var sourcetype = "sdk-tests"; + + var service = this.service; + var indexName = this.indexName; + Async.chain( + function(done) { + service.log(message, {sourcetype: sourcetype, index: indexName}, done); + }, + function(eventInfo, done) { + assert.ok(eventInfo); + assert.strictEqual(eventInfo.sourcetype, sourcetype); + assert.strictEqual(eventInfo.bytes, message.length); + assert.strictEqual(eventInfo.index, indexName); + + // We could poll to make sure the index has eaten up the event, + // but unfortunately this can take an unbounded amount of time. + // As such, since we got a good response, we'll just be done with it. + done(); + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Service submit event, omitting optional arguments", function(done) { + var message = "Hello World -- " + getNextId(); + var sourcetype = "sdk-tests"; + + var service = this.service; + var indexName = this.indexName; + Async.chain( + function(done) { + service.log(message, done); + }, + function(eventInfo, done) { + assert.ok(eventInfo); + assert.strictEqual(eventInfo.bytes, message.length); + + // We could poll to make sure the index has eaten up the event, + // but unfortunately this can take an unbounded amount of time. + // As such, since we got a good response, we'll just be done with it. + done(); + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Service submit events with multi-byte chars", function(done) { + var service = this.service; + var messages = [ + "Ummelner Straße 6", + "Ümmelner Straße 6", + "Iԉtérԉátíòлåɭìƶåtiòл", + "Intérnâtì߀лàɭíƶɑtïòл", + "ãϻét dòner turƙëy ѵ߀lù", + "ptãte ìԉ rëρrèhënԁérit ", + "ϻ߀lɭit fìɭèt ϻìǥnoԉ ɭäb߀ríѕ", + " êӽ cɦùck cüpïᏧåtåt Ꮷèѕëruлt. ", + "D߀ɭor ѵélít ìrurè, sèᏧ ѕhòr", + "t riƅѕ c߀ɰ ɭãnԁյàéɢêr drúmst", + "ícƙ. Minïm ƃàɭl tip ѕհòrt rìƃѕ,", + " ïԁ aɭïqúìρ ѕɦànƙ ρ߀rcɦéttɑ. Pìǥ", + " hãm ɦòck ìлcídíԁùԉt séԁ cüpïϻ ", + "ƙèviл láborê. Et taiɭ ѕtriρ", + " steák út üllãϻc߀ rump d߀ɭore.", + "٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃).", + "Lɑƅòré ƃrësãòlá d߀лèr ѕâlámí ", + "cíllûm ìn ѕɯìлe ϻêàtɭ߀àf dûìs ", + "ρãncettä ƅrìsƙét ԁèsêrûлt áútè", + " յòɰɭ. Lɑbòrìѕ ƙìêɭ", + "básá ԁòlòré fatƃɑck ƅêéf. Pɑѕtr", + "ämì piɢ ѕհàлƙ ùɭɭamcò ѕaû", + "ѕäǥë sɦànƙlë.", + " Cúpím ɭäƃorum drumstïcƙ jerkϒ veli", + " pïcåԉɦɑ ƙíéɭƅãsa. Alïqû", + "iρ írürë cûpíϻ, äɭìɋuâ ǥròûлd ", + "roúлᏧ toԉgüè ρàrìãtùr ", + "briѕkèt ԉostruᏧ cûɭpɑ", + " ìd còлѕèqûât làƅ߀rìs." + ]; + + var counter = 0; + Async.seriesMap( + messages, + function(val, idx, done) { + counter++; + service.log(val, done); + }, + function(err, vals) { + assert.ok(!err); + assert.strictEqual(counter, messages.length); + + // Verify that the full byte-length was sent for each message + for (var m in messages) { + assert.notStrictEqual(messages[m].length, vals[m].bytes); + try { + assert.strictEqual(Buffer.byteLength(messages[m]), vals[m].bytes); + } + catch (err) { + // Assume Buffer isn't defined, we're probably in the browser + assert.strictEqual(new Blob([messages[m]]).size, vals[m].bytes); + } + } + + done(); + } + ); + }); + + it("Callback#Service submit event, failure", function(done) { + var message = "Hello World -- " + getNextId(); + var sourcetype = "sdk-tests"; + + var service = this.loggedOutService; + var indexName = this.indexName; + Async.chain( + function(done) { + assert.ok(service); + service.log(message, done); + }, + function(err) { + assert.ok(err); + done(); + } + ); + }); + + it("Callback#remove throws an error", function(done) { + var index = this.service.indexes().item("_internal"); + assert.throws(function() { + index.remove(); + }); + done(); + }); + + it("Callback#create an index with alternate argument format", function(done) { + var indexes = this.service.indexes(); + indexes.create( + {name: "_internal"}, + function(err, newIndex) { + assert.ok(err.data.messages[0].text.match("name=_internal already exists")); + done(); + } + ); + }); + + it("Callback#Index submit event with omitted optional arguments", function(done) { + var message = "Hello world -- " + getNextId(); + + var indexName = this.indexName; + var indexes = this.service.indexes(); + + Async.chain( + [ + function(done) { + indexes.fetch(done); + }, + function(indexes, done) { + var index = indexes.item(indexName); + assert.ok(index); + assert.strictEqual(index.name, indexName); + index.submitEvent(message, done); + }, + function(eventInfo, index, done) { + assert.ok(eventInfo); + assert.strictEqual(eventInfo.bytes, message.length); + assert.strictEqual(eventInfo.index, indexName); + + // We could poll to make sure the index has eaten up the event, + // but unfortunately this can take an unbounded amount of time. + // As such, since we got a good response, we'll just be done with it. + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Index submit event", function(done) { + var message = "Hello World -- " + getNextId(); + var sourcetype = "sdk-tests"; + + var indexName = this.indexName; + var indexes = this.service.indexes(); + Async.chain([ + function(done) { + indexes.fetch(done); + }, + function(indexes, done) { + var index = indexes.item(indexName); + assert.ok(index); + assert.strictEqual(index.name, indexName); + index.submitEvent(message, {sourcetype: sourcetype}, done); + }, + function(eventInfo, index, done) { + assert.ok(eventInfo); + assert.strictEqual(eventInfo.sourcetype, sourcetype); + assert.strictEqual(eventInfo.bytes, message.length); + assert.strictEqual(eventInfo.index, indexName); + + // We could poll to make sure the index has eaten up the event, + // but unfortunately this can take an unbounded amount of time. + // As such, since we got a good response, we'll just be done with it. + done(); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + describe('User Tests', function() { + before(function(){ + this.service = svc; + this.loggedOutService = loggedOutSvc; + }) + + afterEach(function(){ + this.service.logout(); + }) + + it("Callback#Current user", function(done) { + var service = this.service; + + service.currentUser(function(err, user) { + assert.ok(!err); + assert.ok(user); + assert.strictEqual(user.name, service.username); + done(); + }); + }); + + it("Callback#Current user fails", function(done) { + var service = this.loggedOutService; + + service.currentUser(function(err, user) { + assert.ok(err); + done(); + }); + }); + + it("Callback#List users", function(done) { + var service = this.service; + + service.users().fetch(function(err, users) { + var userList = users.list(); + assert.ok(!err); + assert.ok(users); + + assert.ok(userList); + assert.ok(userList.length > 0); + done(); + }); + }); + + it("Callback#create user failure", function(done) { + this.loggedOutService.users().create( + {name: "jssdk_testuser", password: "abcdefg!", roles: "user"}, + function(err, response) { + assert.ok(err); + done(); + } + ); + }); + + it("Callback#Create + update + delete user", function(done) { + var service = this.service; + var name = "jssdk_testuser"; + + Async.chain([ + function(done) { + service.users().create({name: "jssdk_testuser", password: "abcdefg!", roles: "user"}, done); + }, + function(user, done) { + assert.ok(user); + assert.strictEqual(user.name, name); + assert.strictEqual(user.properties().roles.length, 1); + assert.strictEqual(user.properties().roles[0], "user"); + + user.update({realname: "JS SDK", roles: ["admin", "user"]}, done); + }, + function(user, done) { + assert.ok(user); + assert.strictEqual(user.properties().realname, "JS SDK"); + assert.strictEqual(user.properties().roles.length, 2); + assert.strictEqual(user.properties().roles[0], "admin"); + assert.strictEqual(user.properties().roles[1], "user"); + + user.remove(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + it("Callback#Roles", function(done) { + var service = this.service; + var name = "jssdk_testuser_" + getNextId(); + + Async.chain([ + function(done) { + service.users().create({name: name, password: "abcdefg!", roles: "user"}, done); + }, + function(user, done) { + assert.ok(user); + assert.strictEqual(user.name, name); + assert.strictEqual(user.properties().roles.length, 1); + assert.strictEqual(user.properties().roles[0], "user"); + + user.update({roles: ["admin", "user"]}, done); + }, + function(user, done) { + assert.ok(user); + assert.strictEqual(user.properties().roles.length, 2); + assert.strictEqual(user.properties().roles[0], "admin"); + assert.strictEqual(user.properties().roles[1], "user"); + + user.update({roles: "user"}, done); + }, + function(user, done) { + assert.ok(user); + assert.strictEqual(user.properties().roles.length, 1); + assert.strictEqual(user.properties().roles[0], "user"); + + user.update({roles: "__unknown__"}, done); + } + ], + function(err) { + assert.ok(err); + assert.strictEqual(err.status, 400); + done(); + } + ); + }); + + it("Callback#Passwords", function(done) { + var service = this.service; + var newService = null; + var name = "jssdk_testuser_" + getNextId(); + + var firstPassword = "abcdefg!"; + var secondPassword = "hijklmn!"; + + var useOldPassword = false; + + Async.chain([ + function (done) { + service.serverInfo(done); + }, + function (info, done) { + var versionParts = info.properties().version.split("."); + + var isDevBuild = versionParts.length === 1; + var newerThan72 = (parseInt(versionParts[0], 10) > 7 || + (parseInt(versionParts[0], 10) === 7 && parseInt(versionParts[1], 10) >= 2)); + + if (isDevBuild || newerThan72) { + useOldPassword = true; + } + done(); + }, + function(done) { + service.users().create({name: name, password: firstPassword, roles: "user"}, done); + }, + function(user, done) { + assert.ok(user); + assert.strictEqual(user.name, name); + assert.strictEqual(user.properties().roles.length, 1); + assert.strictEqual(user.properties().roles[0], "user"); + + newService = new splunkjs.Service(service.http, { + username: name, + password: firstPassword, + host: service.host, + port: service.port, + scheme: service.scheme, + version: service.version + }); + + newService.login(Async.augment(done, user)); + }, + function(success, user, done) { + assert.ok(success); + assert.ok(user); + + var body = { + password: secondPassword + }; + if (useOldPassword) { + body['oldpassword'] = firstPassword; + } + + user.update(body, done); + }, + function(user, done) { + newService.login(function(err, success) { + assert.ok(err); + assert.ok(!success); + + var body = { + password: firstPassword + }; + if (useOldPassword) { + body['oldpassword'] = secondPassword; + } + + user.update(body, done); + }); + }, + function(user, done) { + assert.ok(user); + newService.login(done); + } + ], + function(err) { + assert.ok(!err, JSON.stringify(err)); + done(); + } + ); + }); + + it("Callback#delete test users", function(done) { + var users = this.service.users(); + users.fetch(function(err, users) { + var userList = users.list(); + + Async.parallelEach( + userList, + function(user, idx, callback) { + if (utils.startsWith(user.name, "jssdk_")) { + user.remove(callback); + } + else { + callback(); + } + }, function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + }); + + describe('Server Info Tests', function() { + before(function(){ + this.service = svc; + }) + + afterEach(function(){ + this.service.logout(); + }) + + it("Callback#Basic", function(done) { + var service = this.service; + + service.serverInfo(function(err, info) { + assert.ok(!err); + assert.ok(info); + assert.strictEqual(info.name, "server-info"); + assert.ok(info.properties().hasOwnProperty("version")); + assert.ok(info.properties().hasOwnProperty("serverName")); + assert.ok(info.properties().hasOwnProperty("os_version")); + + done(); + }); + }); + }); + + describe('View Info Tests', function() { + before(function(){ + this.service = svc; + }) + + it("Callback#List views", function(done) { + var service = this.service; + + service.views({owner: "admin", app: "search"}).fetch(function(err, views) { + assert.ok(!err); + assert.ok(views); + + var viewsList = views.list(); + assert.ok(viewsList); + assert.ok(viewsList.length > 0); + + for(var i = 0; i < viewsList.length; i++) { + assert.ok(viewsList[i]); + } + + done(); + }); + }); + + it("Callback#Create + update + delete view", function(done) { + var service = this.service; + var name = "jssdk_testview"; + var originalData = ""; + var newData = ""; + + Async.chain([ + function(done) { + service.views({owner: "admin", app: "sdk-app-collection"}).create({name: name, "eai:data": originalData}, done); + }, + function(view, done) { + assert.ok(view); + + assert.strictEqual(view.name, name); + assert.strictEqual(view.properties()["eai:data"], originalData); + + view.update({"eai:data": newData}, done); + }, + function(view, done) { + assert.ok(view); + assert.strictEqual(view.properties()["eai:data"], newData); + + view.remove(done); + } + ], + function(err) { + assert.ok(!err); + done(); + } + ); + }); + }); + + describe('Parser Tests', function() { + before(function(){ + this.service = svc; + }) + + it("Callback#Basic parse", function(done) { + var service = this.service; + + service.parse("search index=_internal | head 1", function(err, parse) { + assert.ok(!err); + assert.ok(parse); + assert.ok(parse.commands.length > 0); + done(); + }); + }); + + it("Callback#Parse error", function(done) { + var service = this.service; + + service.parse("ABCXYZ", function(err, parse) { + assert.ok(err); + assert.strictEqual(err.status, 400); + done(); + }); + }); + + }); + + describe('Typeheads Tests', function() { + before(function(){ + this.service = svc; + this.loggedOutService = loggedOutSvc; + }) + + it("Callback#Typeahead failure", function(done) { + var service = this.loggedOutService; + service.typeahead("index=", 1, function(err, options) { + assert.ok(err); + done(); + }); + }); + + it("Callback#Basic typeahead", function(done) { + var service = this.service; + + service.typeahead("index=", 1, function(err, options) { + assert.ok(!err); + assert.ok(options); + assert.strictEqual(options.length, 1); + assert.ok(options[0]); + done(); + }); + }); + + it("Typeahead with omitted optional arguments", function(done) { + var service = this.service; + service.typeahead("index=", function(err, options) { + assert.ok(!err); + assert.ok(options); + done(); + }); + }); + + }); + + describe('Endpoints Tests', function() { + before(function(){ + this.service = svc; + }) + + it("Throws on null arguments to init", function(done) { + var service = this.service; + assert.throws(function() { + var endpoint = new splunkjs.Service.Endpoint(null, "a/b"); + }); + assert.throws(function() { + var endpoint = new splunkjs.Service.Endpoint(service, null); + }); + done(); + }); + + it("Endpoint delete on a relative path", function(done) { + var service = this.service; + var endpoint = new splunkjs.Service.Endpoint(service, "/search/jobs/12345"); + endpoint.del("search/jobs/12345", {}, function() { done();}); + }); + + it("Methods of Resource to be overridden", function(done) { + var service = this.service; + var resource = new splunkjs.Service.Resource(service, "/search/jobs/12345"); + assert.throws(function() { resource.path(); }); + assert.throws(function() { resource.fetch(); }); + assert.ok(splunkjs.Utils.isEmpty(resource.state())); + done(); + }); + }); + + describe('Entity Tests', function() { + before(function(){ + this.service = svc; + this.loggedOutService = loggedOutSvc; + }) + + it("Accessors function properly", function(done) { + var entity = new splunkjs.Service.Entity( + this.service, + "/search/jobs/12345", + {owner: "boris", app: "factory", sharing: "app"} + ); + entity._load( + {acl: {owner: "boris", app: "factory", sharing: "app"}, + links: {link1: 35}, + published: "meep", + author: "Hilda"} + ); + assert.ok(entity.acl().owner === "boris"); + assert.ok(entity.acl().app === "factory"); + assert.ok(entity.acl().sharing === "app"); + assert.ok(entity.links().link1 === 35); + assert.strictEqual(entity.author(), "Hilda"); + assert.strictEqual(entity.published(), "meep"); + done(); + }); + + it("Refresh throws error correctly", function(done) { + var entity = new splunkjs.Service.Entity(this.loggedOutService, "/search/jobs/12345", {owner: "boris", app: "factory", sharing: "app"}); + entity.fetch({}, function(err) { assert.ok(err); done();}); + }); + + it("Cannot update name of entity", function(done) { + var entity = new splunkjs.Service.Entity(this.service, "/search/jobs/12345", {owner: "boris", app: "factory", sharing: "app"}); + assert.throws(function() { entity.update({name: "asdf"});}); + done(); + }); + + it("Disable throws error correctly", function(done) { + var entity = new splunkjs.Service.Entity( + this.loggedOutService, + "/search/jobs/12345", + {owner: "boris", app: "factory", sharing: "app"} + ); + entity.disable(function(err) { assert.ok(err); done();}); + }); + + it("Enable throws error correctly", function(done) { + var entity = new splunkjs.Service.Entity( + this.loggedOutService, + "/search/jobs/12345", + {owner: "boris", app: "factory", sharing: "app"} + ); + entity.enable(function(err) { assert.ok(err); done();}); + }); + + it("Does reload work?", function(done) { + var idx = new splunkjs.Service.Index( + this.service, + "data/indexes/sdk-test", + { + owner: "admin", + app: "search", + sharing: "app" + } + ); + var name = "jssdk_testapp_" + getNextId(); + var apps = this.service.apps(); + + var that = this; + Async.chain( + function(done) { + apps.create({name: name}, done); + }, + function(app, done) { + app.reload(function(err) { + assert.ok(!err); + done(null, app); + }); + }, + function(app, done) { + var app2 = new splunkjs.Service.Application(that.loggedOutService, app.name); + app2.reload(function(err) { + assert.ok(err); + done(null, app); + }); + }, + function(app, done) { + app.remove(done); + }, + function(err) { + assert.ok(!err); + done(); + } + ); + }); + + }); + + describe('Collections Tests', function() { + before(function(){ + this.service = svc; + this.loggedOutService = loggedOutSvc; + }) + + it("Methods to be overridden throw", function(done) { + var coll = new splunkjs.Service.Collection( + this.service, + "/data/indexes", + {owner: "admin", + app: "search", + sharing: "app"} + ); + assert.throws(function() { + coll.instantiateEntity({}); + }); + done(); + }); + + it("Accessors work", function(done) { + var coll = new splunkjs.Service.Collection( + this.service, + "/data/indexes", + {owner: "admin", + app: "search", + sharing: "app"} + ); + coll._load({links: "Hilda", updated: true}); + assert.strictEqual(coll.links(), "Hilda"); + assert.ok(coll.updated()); + done(); + }); + + it("Contains throws without a good id", function(done) { + var coll = new splunkjs.Service.Collection( + this.service, + "/data/indexes", + { + owner: "admin", + app: "search", + sharing: "app" + } + ); + assert.throws(function() { coll.item(null);}); + done(); + }); + + }); + + +}); \ No newline at end of file diff --git a/client/browser_utils.js b/client/browser_utils.js new file mode 100644 index 000000000..d515df696 --- /dev/null +++ b/client/browser_utils.js @@ -0,0 +1,242 @@ +splunkjs.Logger.setLevel("ALL"); +assert = chai.assert; + +describe('Utils Tests', function() { +it("Callback#callback to object success", function(done) { + var successfulFunction = function(callback) { + callback(null, "one", "two"); + }; + + successfulFunction(function(err, one, two) { + assert.strictEqual(one, "one"); + assert.strictEqual(two, "two"); + done(); + }); +}); + +it("Callback#callback to object error - single argument", function(done) { + var successfulFunction = function(callback) { + callback("one"); + }; + + successfulFunction(function(err, one, two) { + assert.strictEqual(err, "one"); + assert.ok(!one); + assert.ok(!two); + done(); + }); +}); + +it("Callback#callback to object error - multi argument", function(done) { + var successfulFunction = function(callback) { + callback(["one", "two"]); + }; + + successfulFunction(function(err, one, two) { + assert.strictEqual(err[0], "one"); + assert.strictEqual(err[1], "two"); + assert.ok(!one); + assert.ok(!two); + done(); + }); +}); + +it("keyOf works", function(done) { + assert.ok(splunkjs.Utils.keyOf(3, {a: 3, b: 5})); + assert.ok(!splunkjs.Utils.keyOf(3, {a: 12, b: 6})); + done(); +}); + +it("bind", function(done) { + var f; + (function() { + f = function(a) { + this.a = a; + }; + })(); + var q = {}; + var g = splunkjs.Utils.bind(q, f); + g(12); + assert.strictEqual(q.a, 12); + done(); +}); + +it("trim", function(done) { + assert.strictEqual(splunkjs.Utils.trim(" test of something \n\r \t"), "test of something"); + + var realTrim = String.prototype.trim; + String.prototype.trim = null; + assert.strictEqual(splunkjs.Utils.trim(" test of something \n\r \t"), "test of something"); + String.prototype.trim = realTrim; + + done(); +}); + +it("indexOf", function(done) { + assert.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,5], 3), 2); + assert.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,3], 3), 2); + assert.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,5], 12), -1); + done(); +}); + +it("contains", function(done) { + assert.ok(splunkjs.Utils.contains([1,2,3,4,5], 3)); + assert.ok(splunkjs.Utils.contains([1,2,3,4,3], 3)); + assert.ok(!splunkjs.Utils.contains([1,2,3,4,5], 12)); + done(); +}); + +it("startsWith", function(done) { + assert.ok(splunkjs.Utils.startsWith("abcdefg", "abc")); + assert.ok(!splunkjs.Utils.startsWith("bcdefg", "abc")); + done(); +}); + +it("endsWith", function(done) { + assert.ok(splunkjs.Utils.endsWith("abcdef", "def")); + assert.ok(!splunkjs.Utils.endsWith("abcdef", "bcd")); + done(); +}); + +it("toArray", function(done) { + (function() { + var found = splunkjs.Utils.toArray(arguments); + var expected = [1,2,3,4,5]; + for (var i = 0; i < found.length; i++) { + assert.strictEqual(found[i], expected[i]); + } + })(1,2,3,4,5); + done(); +}); + +it("isArray", function(done) { + var a = [1,2,3,4,5]; + assert.ok(splunkjs.Utils.isArray(a)); + done(); +}); + +it("isFunction", function(done) { + assert.ok(splunkjs.Utils.isFunction(function() {})); + assert.ok(!splunkjs.Utils.isFunction(3)); + assert.ok(!splunkjs.Utils.isFunction("abc")); + assert.ok(!splunkjs.Utils.isFunction({})); + done(); +}); + +it("isNumber", function(done) { + assert.ok(splunkjs.Utils.isNumber(3)); + assert.ok(splunkjs.Utils.isNumber(-2.55113e12)); + assert.ok(!splunkjs.Utils.isNumber("3")); + assert.ok(!splunkjs.Utils.isNumber({3: 5})); + done(); +}); + +it("isObject", function(done) { + assert.ok(splunkjs.Utils.isObject({})); + assert.ok(!splunkjs.Utils.isObject(3)); + assert.ok(!splunkjs.Utils.isObject("3")); + done(); +}); + +it("isEmpty", function(done) { + assert.ok(splunkjs.Utils.isEmpty({})); + assert.ok(splunkjs.Utils.isEmpty([])); + assert.ok(splunkjs.Utils.isEmpty("")); + assert.ok(!splunkjs.Utils.isEmpty({a: 3})); + assert.ok(!splunkjs.Utils.isEmpty([1,2])); + assert.ok(!splunkjs.Utils.isEmpty("abc")); + done(); +}); + +it("forEach", function(done) { + var a = [1,2,3,4,5]; + splunkjs.Utils.forEach( + a, + function(elem, index, list) { + assert.strictEqual(a[index], elem); + } + ); + var b = {1: 2, 2: 4, 3: 6}; + splunkjs.Utils.forEach( + b, + function(elem, key, obj) { + assert.strictEqual(b[key], elem); + } + ); + splunkjs.Utils.forEach(null, function(elem, key, obj) {}); + var c = {length: 5, 1: 12, 2: 15, 3: 8}; + splunkjs.Utils.forEach( + c, + function(elem, key, obj) { + assert.strictEqual(c[key], elem); + } + ); + done(); +}); + +it("extend", function(done) { + var found = splunkjs.Utils.extend({}, {a: 1, b: 2}, {c: 3, b: 4}); + var expected = {a: 1, b: 4, c:3}; + for (var k in found) { + if (found.hasOwnProperty(k)) { + assert.strictEqual(found[k], expected[k]); + } + } + done(); +}); + +it("clone", function(done) { + var a = {a: 1, b: 2, c: {p: 5, q: 6}}; + var b = splunkjs.Utils.clone(a); + splunkjs.Utils.forEach(a, function(val, key, obj) { assert.strictEqual(val, b[key]); }); + a.a = 5; + assert.strictEqual(b.a, 1); + a.c.p = 4; + assert.strictEqual(b.c.p, 4); + done(); + assert.strictEqual(splunkjs.Utils.clone(3), 3); + assert.strictEqual(splunkjs.Utils.clone("asdf"), "asdf"); + var p = [1,2,[3,4],3]; + var q = splunkjs.Utils.clone(p); + splunkjs.Utils.forEach(p, function(val, index, arr) { assert.strictEqual(p[index], q[index]); }); + p[0] = 3; + assert.strictEqual(q[0], 1); + p[2][0] = 7; + assert.strictEqual(q[2][0], 7); +}); + +it("namespaceFromProperties", function(done) { + var a = splunkjs.Utils.namespaceFromProperties( + {acl: {owner: "boris", + app: "factory", + sharing: "system", + other: 3}, + more: 12} + ); + splunkjs.Utils.forEach( + a, + function(val, key, obj) { + assert.ok((key === "owner" && val === "boris") || + (key === "app" && val === "factory") || + (key === "sharing" && val === "system")); + } + ); + done(); + +}); + +it("namespaceFromProperties - bad data", function(done) { + var undefinedProps; + var a = splunkjs.Utils.namespaceFromProperties(undefinedProps); + assert.strictEqual(a.owner, ''); + assert.strictEqual(a.app, ''); + assert.strictEqual(a.sharing, ''); + + var undefinedAcl = {}; + var b = splunkjs.Utils.namespaceFromProperties(undefinedProps); + assert.strictEqual(b.owner, ''); + assert.strictEqual(b.app, ''); + assert.strictEqual(b.sharing, ''); + done(); +}); +}); \ No newline at end of file diff --git a/client/splunk.js b/client/splunk.js index 7648b0729..526d7dd37 100644 --- a/client/splunk.js +++ b/client/splunk.js @@ -2103,128 +2103,128 @@ module.exports = {} }); require.define("/node_modules/cookie/index.js", function (require, module, exports, __dirname, __filename) { -/*! - * cookie - * Copyright(c) 2012-2014 Roman Shtylman - * MIT Licensed - */ - -/** - * Module exports. - * @public - */ - -exports.parse = parse; -exports.serialize = serialize; - -/** - * Module variables. - * @private - */ - -var decode = decodeURIComponent; -var encode = encodeURIComponent; -var pairSplitRegExp = /; */; - -/** - * Parse a cookie header. - * - * Parse the given cookie header string into an object - * The object has the various cookies as keys(names) => values - * - * @param {string} str - * @param {object} [options] - * @return {object} - * @public - */ - -function parse(str, options) { - if (typeof str !== 'string') { - throw new TypeError('argument str must be a string'); - } - - var obj = {} - var opt = options || {}; - var pairs = str.split(pairSplitRegExp); - var dec = opt.decode || decode; - - pairs.forEach(function(pair) { - var eq_idx = pair.indexOf('=') - - // skip things that don't look like key=value - if (eq_idx < 0) { - return; - } - - var key = pair.substr(0, eq_idx).trim() - var val = pair.substr(++eq_idx, pair.length).trim(); - - // quoted values - if ('"' == val[0]) { - val = val.slice(1, -1); - } - - // only assign once - if (undefined == obj[key]) { - obj[key] = tryDecode(val, dec); - } - }); - - return obj; -} - -/** - * Serialize data into a cookie header. - * - * Serialize the a name value pair into a cookie string suitable for - * http headers. An optional options object specified cookie parameters. - * - * serialize('foo', 'bar', { httpOnly: true }) - * => "foo=bar; httpOnly" - * - * @param {string} name - * @param {string} val - * @param {object} [options] - * @return {string} - * @public - */ - -function serialize(name, val, options) { - var opt = options || {}; - var enc = opt.encode || encode; - var pairs = [name + '=' + enc(val)]; - - if (null != opt.maxAge) { - var maxAge = opt.maxAge - 0; - if (isNaN(maxAge)) throw new Error('maxAge should be a Number'); - pairs.push('Max-Age=' + maxAge); - } - - if (opt.domain) pairs.push('Domain=' + opt.domain); - if (opt.path) pairs.push('Path=' + opt.path); - if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString()); - if (opt.httpOnly) pairs.push('HttpOnly'); - if (opt.secure) pairs.push('Secure'); - if (opt.firstPartyOnly) pairs.push('First-Party-Only'); - - return pairs.join('; '); -} - -/** - * Try decoding a string using a decoding function. - * - * @param {string} str - * @param {function} decode - * @private - */ - -function tryDecode(str, decode) { - try { - return decode(str); - } catch (e) { - return str; - } -} +/*! + * cookie + * Copyright(c) 2012-2014 Roman Shtylman + * MIT Licensed + */ + +/** + * Module exports. + * @public + */ + +exports.parse = parse; +exports.serialize = serialize; + +/** + * Module variables. + * @private + */ + +var decode = decodeURIComponent; +var encode = encodeURIComponent; +var pairSplitRegExp = /; */; + +/** + * Parse a cookie header. + * + * Parse the given cookie header string into an object + * The object has the various cookies as keys(names) => values + * + * @param {string} str + * @param {object} [options] + * @return {object} + * @public + */ + +function parse(str, options) { + if (typeof str !== 'string') { + throw new TypeError('argument str must be a string'); + } + + var obj = {} + var opt = options || {}; + var pairs = str.split(pairSplitRegExp); + var dec = opt.decode || decode; + + pairs.forEach(function(pair) { + var eq_idx = pair.indexOf('=') + + // skip things that don't look like key=value + if (eq_idx < 0) { + return; + } + + var key = pair.substr(0, eq_idx).trim() + var val = pair.substr(++eq_idx, pair.length).trim(); + + // quoted values + if ('"' == val[0]) { + val = val.slice(1, -1); + } + + // only assign once + if (undefined == obj[key]) { + obj[key] = tryDecode(val, dec); + } + }); + + return obj; +} + +/** + * Serialize data into a cookie header. + * + * Serialize the a name value pair into a cookie string suitable for + * http headers. An optional options object specified cookie parameters. + * + * serialize('foo', 'bar', { httpOnly: true }) + * => "foo=bar; httpOnly" + * + * @param {string} name + * @param {string} val + * @param {object} [options] + * @return {string} + * @public + */ + +function serialize(name, val, options) { + var opt = options || {}; + var enc = opt.encode || encode; + var pairs = [name + '=' + enc(val)]; + + if (null != opt.maxAge) { + var maxAge = opt.maxAge - 0; + if (isNaN(maxAge)) throw new Error('maxAge should be a Number'); + pairs.push('Max-Age=' + maxAge); + } + + if (opt.domain) pairs.push('Domain=' + opt.domain); + if (opt.path) pairs.push('Path=' + opt.path); + if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString()); + if (opt.httpOnly) pairs.push('HttpOnly'); + if (opt.secure) pairs.push('Secure'); + if (opt.firstPartyOnly) pairs.push('First-Party-Only'); + + return pairs.join('; '); +} + +/** + * Try decoding a string using a decoding function. + * + * @param {string} str + * @param {function} decode + * @private + */ + +function tryDecode(str, decode) { + try { + return decode(str); + } catch (e) { + return str; + } +} }); @@ -12525,7 +12525,7 @@ require.define("/lib/modularinputs/modularinput.js", function (require, module, if (inputStream.resume && !inputStream.isTTY) { inputStream.resume(); } - var bigBuff = new Buffer(0); + var bigBuff = Buffer.alloc(0); // When streaming events... if (args.length === 1) { diff --git a/client/splunk.test.js b/client/splunk.test.js index e84d7f864..00164a8ff 100644 --- a/client/splunk.test.js +++ b/client/splunk.test.js @@ -1,4015 +1,5063 @@ (function() { -var __exportName = 'splunkjs'; - -var require = function (file, cwd) { - var resolved = require.resolve(file, cwd || '/'); - var mod = require.modules[resolved]; - if (!mod) throw new Error( - 'Failed to resolve module ' + file + ', tried ' + resolved - ); - var res = mod._cached ? mod._cached : mod(); - return res; -} - -require.paths = []; -require.modules = {}; -require.extensions = [".js",".coffee"]; - -require._core = { - 'assert': true, - 'events': true, - 'fs': true, - 'path': true, - 'vm': true -}; - -require.resolve = (function () { - return function (x, cwd) { - if (!cwd) cwd = '/'; - - if (require._core[x]) return x; - var path = require.modules.path(); - cwd = path.resolve('/', cwd); - var y = cwd || '/'; - - if (x.match(/^(?:\.\.?\/|\/)/)) { - var m = loadAsFileSync(path.resolve(y, x)) - || loadAsDirectorySync(path.resolve(y, x)); - if (m) return m; - } - - var n = loadNodeModulesSync(x, y); - if (n) return n; - - throw new Error("Cannot find module '" + x + "'"); - - function loadAsFileSync (x) { - if (require.modules[x]) { - return x; - } + var __exportName = 'splunkjs'; + + var require = function (file, cwd) { + var resolved = require.resolve(file, cwd || '/'); + var mod = require.modules[resolved]; + if (!mod) throw new Error( + 'Failed to resolve module ' + file + ', tried ' + resolved + ); + var res = mod._cached ? mod._cached : mod(); + return res; + } + + require.paths = []; + require.modules = {}; + require.extensions = [".js",".coffee"]; + + require._core = { + 'assert': true, + 'events': true, + 'fs': true, + 'path': true, + 'vm': true + }; + + require.resolve = (function () { + return function (x, cwd) { + if (!cwd) cwd = '/'; + + if (require._core[x]) return x; + var path = require.modules.path(); + cwd = path.resolve('/', cwd); + var y = cwd || '/'; - for (var i = 0; i < require.extensions.length; i++) { - var ext = require.extensions[i]; - if (require.modules[x + ext]) return x + ext; + if (x.match(/^(?:\.\.?\/|\/)/)) { + var m = loadAsFileSync(path.resolve(y, x)) + || loadAsDirectorySync(path.resolve(y, x)); + if (m) return m; } - } - - function loadAsDirectorySync (x) { - x = x.replace(/\/+$/, ''); - var pkgfile = x + '/package.json'; - if (require.modules[pkgfile]) { - var pkg = require.modules[pkgfile](); - var b = pkg.browserify; - if (typeof b === 'object' && b.main) { - var m = loadAsFileSync(path.resolve(x, b.main)); - if (m) return m; + + var n = loadNodeModulesSync(x, y); + if (n) return n; + + throw new Error("Cannot find module '" + x + "'"); + + function loadAsFileSync (x) { + if (require.modules[x]) { + return x; } - else if (typeof b === 'string') { - var m = loadAsFileSync(path.resolve(x, b)); - if (m) return m; + + for (var i = 0; i < require.extensions.length; i++) { + var ext = require.extensions[i]; + if (require.modules[x + ext]) return x + ext; } - else if (pkg.main) { - var m = loadAsFileSync(path.resolve(x, pkg.main)); - if (m) return m; + } + + function loadAsDirectorySync (x) { + x = x.replace(/\/+$/, ''); + var pkgfile = x + '/package.json'; + if (require.modules[pkgfile]) { + var pkg = require.modules[pkgfile](); + var b = pkg.browserify; + if (typeof b === 'object' && b.main) { + var m = loadAsFileSync(path.resolve(x, b.main)); + if (m) return m; + } + else if (typeof b === 'string') { + var m = loadAsFileSync(path.resolve(x, b)); + if (m) return m; + } + else if (pkg.main) { + var m = loadAsFileSync(path.resolve(x, pkg.main)); + if (m) return m; + } } + + return loadAsFileSync(x + '/index'); } - return loadAsFileSync(x + '/index'); - } - - function loadNodeModulesSync (x, start) { - var dirs = nodeModulesPathsSync(start); - for (var i = 0; i < dirs.length; i++) { - var dir = dirs[i]; - var m = loadAsFileSync(dir + '/' + x); + function loadNodeModulesSync (x, start) { + var dirs = nodeModulesPathsSync(start); + for (var i = 0; i < dirs.length; i++) { + var dir = dirs[i]; + var m = loadAsFileSync(dir + '/' + x); + if (m) return m; + var n = loadAsDirectorySync(dir + '/' + x); + if (n) return n; + } + + var m = loadAsFileSync(x); if (m) return m; - var n = loadAsDirectorySync(dir + '/' + x); - if (n) return n; } - var m = loadAsFileSync(x); - if (m) return m; + function nodeModulesPathsSync (start) { + var parts; + if (start === '/') parts = [ '' ]; + else parts = path.normalize(start).split('/'); + + var dirs = []; + for (var i = parts.length - 1; i >= 0; i--) { + if (parts[i] === 'node_modules') continue; + var dir = parts.slice(0, i + 1).join('/') + '/node_modules'; + dirs.push(dir); + } + + return dirs; + } + }; + })(); + + require.alias = function (from, to) { + var path = require.modules.path(); + var res = null; + try { + res = require.resolve(from + '/package.json', '/'); + } + catch (err) { + res = require.resolve(from, '/'); } + var basedir = path.dirname(res); - function nodeModulesPathsSync (start) { - var parts; - if (start === '/') parts = [ '' ]; - else parts = path.normalize(start).split('/'); - - var dirs = []; - for (var i = parts.length - 1; i >= 0; i--) { - if (parts[i] === 'node_modules') continue; - var dir = parts.slice(0, i + 1).join('/') + '/node_modules'; - dirs.push(dir); + var keys = (Object.keys || function (obj) { + var res = []; + for (var key in obj) res.push(key) + return res; + })(require.modules); + + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + if (key.slice(0, basedir.length + 1) === basedir + '/') { + var f = key.slice(basedir.length); + require.modules[to + f] = require.modules[basedir + f]; + } + else if (key === basedir) { + require.modules[to] = require.modules[basedir]; } - - return dirs; } }; -})(); - -require.alias = function (from, to) { - var path = require.modules.path(); - var res = null; - try { - res = require.resolve(from + '/package.json', '/'); - } - catch (err) { - res = require.resolve(from, '/'); - } - var basedir = path.dirname(res); - var keys = (Object.keys || function (obj) { + require.define = function (filename, fn) { + var dirname = require._core[filename] + ? '' + : require.modules.path().dirname(filename) + ; + + var require_ = function (file) { + return require(file, dirname) + }; + require_.resolve = function (name) { + return require.resolve(name, dirname); + }; + require_.modules = require.modules; + require_.define = require.define; + var module_ = { exports : {} }; + + require.modules[filename] = function () { + require.modules[filename]._cached = module_.exports; + fn.call( + module_.exports, + require_, + module_, + module_.exports, + dirname, + filename + ); + require.modules[filename]._cached = module_.exports; + return module_.exports; + }; + }; + + if (typeof process === 'undefined') process = {}; + + if (!process.nextTick) process.nextTick = (function () { + var queue = []; + var canPost = typeof window !== 'undefined' + && window.postMessage && window.addEventListener + ; + + if (canPost) { + window.addEventListener('message', function (ev) { + if (ev.source === window && ev.data === 'browserify-tick') { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, true); + } + + return function (fn) { + if (canPost) { + queue.push(fn); + window.postMessage('browserify-tick', '*'); + } + else setTimeout(fn, 0); + }; + })(); + + if (!process.title) process.title = 'browser'; + + if (!process.binding) process.binding = function (name) { + if (name === 'evals') return require('vm') + else throw new Error('No such module') + }; + + if (!process.cwd) process.cwd = function () { return '.' }; + + require.define("path", function (require, module, exports, __dirname, __filename) { + function filter (xs, fn) { var res = []; - for (var key in obj) res.push(key) + for (var i = 0; i < xs.length; i++) { + if (fn(xs[i], i, xs)) res.push(xs[i]); + } return res; - })(require.modules); + } - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (key.slice(0, basedir.length + 1) === basedir + '/') { - var f = key.slice(basedir.length); - require.modules[to + f] = require.modules[basedir + f]; + // resolves . and .. elements in a path array with directory names there + // must be no slashes, empty elements, or device names (c:\) in the array + // (so also no leading and trailing slashes - it does not distinguish + // relative and absolute paths) + function normalizeArray(parts, allowAboveRoot) { + // if the path tries to go above the root, `up` ends up > 0 + var up = 0; + for (var i = parts.length; i >= 0; i--) { + var last = parts[i]; + if (last == '.') { + parts.splice(i, 1); + } else if (last === '..') { + parts.splice(i, 1); + up++; + } else if (up) { + parts.splice(i, 1); + up--; } - else if (key === basedir) { - require.modules[to] = require.modules[basedir]; + } + + // if the path is allowed to go above the root, restore leading ..s + if (allowAboveRoot) { + for (; up--; up) { + parts.unshift('..'); } + } + + return parts; } -}; - -require.define = function (filename, fn) { - var dirname = require._core[filename] - ? '' - : require.modules.path().dirname(filename) - ; - var require_ = function (file) { - return require(file, dirname) + // Regex to split a filename into [*, dir, basename, ext] + // posix version + var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; + + // path.resolve([from ...], to) + // posix version + exports.resolve = function() { + var resolvedPath = '', + resolvedAbsolute = false; + + for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { + var path = (i >= 0) + ? arguments[i] + : process.cwd(); + + // Skip empty and invalid entries + if (typeof path !== 'string' || !path) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charAt(0) === '/'; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { + return !!p; + }), !resolvedAbsolute).join('/'); + + return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; }; - require_.resolve = function (name) { - return require.resolve(name, dirname); + + // path.normalize(path) + // posix version + exports.normalize = function(path) { + var isAbsolute = path.charAt(0) === '/', + trailingSlash = path.slice(-1) === '/'; + + // Normalize the path + path = normalizeArray(filter(path.split('/'), function(p) { + return !!p; + }), !isAbsolute).join('/'); + + if (!path && !isAbsolute) { + path = '.'; + } + if (path && trailingSlash) { + path += '/'; + } + + return (isAbsolute ? '/' : '') + path; }; - require_.modules = require.modules; - require_.define = require.define; - var module_ = { exports : {} }; - - require.modules[filename] = function () { - require.modules[filename]._cached = module_.exports; - fn.call( - module_.exports, - require_, - module_, - module_.exports, - dirname, - filename - ); - require.modules[filename]._cached = module_.exports; - return module_.exports; + + + // posix version + exports.join = function() { + var paths = Array.prototype.slice.call(arguments, 0); + return exports.normalize(filter(paths, function(p, index) { + return p && typeof p === 'string'; + }).join('/')); }; -}; - -if (typeof process === 'undefined') process = {}; - -if (!process.nextTick) process.nextTick = (function () { - var queue = []; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - if (canPost) { - window.addEventListener('message', function (ev) { - if (ev.source === window && ev.data === 'browserify-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - } - return function (fn) { - if (canPost) { - queue.push(fn); - window.postMessage('browserify-tick', '*'); - } - else setTimeout(fn, 0); + exports.dirname = function(path) { + var dir = splitPathRe.exec(path)[1] || ''; + var isWindows = false; + if (!dir) { + // No dirname + return '.'; + } else if (dir.length === 1 || + (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { + // It is just a slash or a drive letter with a slash + return dir; + } else { + // It is a full dirname, strip trailing slash + return dir.substring(0, dir.length - 1); + } }; -})(); - -if (!process.title) process.title = 'browser'; - -if (!process.binding) process.binding = function (name) { - if (name === 'evals') return require('vm') - else throw new Error('No such module') -}; - -if (!process.cwd) process.cwd = function () { return '.' }; - -require.define("path", function (require, module, exports, __dirname, __filename) { -function filter (xs, fn) { - var res = []; - for (var i = 0; i < xs.length; i++) { - if (fn(xs[i], i, xs)) res.push(xs[i]); - } - return res; -} - -// resolves . and .. elements in a path array with directory names there -// must be no slashes, empty elements, or device names (c:\) in the array -// (so also no leading and trailing slashes - it does not distinguish -// relative and absolute paths) -function normalizeArray(parts, allowAboveRoot) { - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = parts.length; i >= 0; i--) { - var last = parts[i]; - if (last == '.') { - parts.splice(i, 1); - } else if (last === '..') { - parts.splice(i, 1); - up++; - } else if (up) { - parts.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (allowAboveRoot) { - for (; up--; up) { - parts.unshift('..'); - } - } - - return parts; -} - -// Regex to split a filename into [*, dir, basename, ext] -// posix version -var splitPathRe = /^(.+\/(?!$)|\/)?((?:.+?)?(\.[^.]*)?)$/; - -// path.resolve([from ...], to) -// posix version -exports.resolve = function() { -var resolvedPath = '', - resolvedAbsolute = false; - -for (var i = arguments.length; i >= -1 && !resolvedAbsolute; i--) { - var path = (i >= 0) - ? arguments[i] - : process.cwd(); - - // Skip empty and invalid entries - if (typeof path !== 'string' || !path) { - continue; - } - - resolvedPath = path + '/' + resolvedPath; - resolvedAbsolute = path.charAt(0) === '/'; -} - -// At this point the path should be resolved to a full absolute path, but -// handle relative paths to be safe (might happen when process.cwd() fails) - -// Normalize the path -resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) { - return !!p; - }), !resolvedAbsolute).join('/'); - - return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.'; -}; - -// path.normalize(path) -// posix version -exports.normalize = function(path) { -var isAbsolute = path.charAt(0) === '/', - trailingSlash = path.slice(-1) === '/'; - -// Normalize the path -path = normalizeArray(filter(path.split('/'), function(p) { - return !!p; - }), !isAbsolute).join('/'); - - if (!path && !isAbsolute) { - path = '.'; - } - if (path && trailingSlash) { - path += '/'; - } - - return (isAbsolute ? '/' : '') + path; -}; - - -// posix version -exports.join = function() { - var paths = Array.prototype.slice.call(arguments, 0); - return exports.normalize(filter(paths, function(p, index) { - return p && typeof p === 'string'; - }).join('/')); -}; - - -exports.dirname = function(path) { - var dir = splitPathRe.exec(path)[1] || ''; - var isWindows = false; - if (!dir) { - // No dirname - return '.'; - } else if (dir.length === 1 || - (isWindows && dir.length <= 3 && dir.charAt(1) === ':')) { - // It is just a slash or a drive letter with a slash - return dir; - } else { - // It is a full dirname, strip trailing slash - return dir.substring(0, dir.length - 1); - } -}; - - -exports.basename = function(path, ext) { - var f = splitPathRe.exec(path)[2] || ''; - // TODO: make this comparison case-insensitive on windows? - if (ext && f.substr(-1 * ext.length) === ext) { - f = f.substr(0, f.length - ext.length); - } - return f; -}; - - -exports.extname = function(path) { - return splitPathRe.exec(path)[3] || ''; -}; - -}); - -require.define("/tests/test_utils.js", function (require, module, exports, __dirname, __filename) { - -// Copyright 2011 Splunk, Inc. -// -// 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. - -exports.setup = function() { - var splunkjs = require('../index'); - - splunkjs.Logger.setLevel("ALL"); - return { - "Callback#callback to object success": function(test) { - var successfulFunction = function(callback) { - callback(null, "one", "two"); - }; + + + exports.basename = function(path, ext) { + var f = splitPathRe.exec(path)[2] || ''; + // TODO: make this comparison case-insensitive on windows? + if (ext && f.substr(-1 * ext.length) === ext) { + f = f.substr(0, f.length - ext.length); + } + return f; + }; + + + exports.extname = function(path) { + return splitPathRe.exec(path)[3] || ''; + }; + + }); + + require.define("/tests/test_utils.js", function (require, module, exports, __dirname, __filename) { + + // Copyright 2011 Splunk, Inc. + // + // 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. + + exports.setup = function() { + var splunkjs = require('../index'); + + splunkjs.Logger.setLevel("ALL"); + return { + "Callback#callback to object success": function(test) { + var successfulFunction = function(callback) { + callback(null, "one", "two"); + }; + + successfulFunction(function(err, one, two) { + test.strictEqual(one, "one"); + test.strictEqual(two, "two"); + test.done(); + }); + }, - successfulFunction(function(err, one, two) { - test.strictEqual(one, "one"); - test.strictEqual(two, "two"); - test.done(); - }); - }, - - "Callback#callback to object error - single argument": function(test) { - var successfulFunction = function(callback) { - callback("one"); - }; + "Callback#callback to object error - single argument": function(test) { + var successfulFunction = function(callback) { + callback("one"); + }; + + successfulFunction(function(err, one, two) { + test.strictEqual(err, "one"); + test.ok(!one); + test.ok(!two); + test.done(); + }); + }, - successfulFunction(function(err, one, two) { - test.strictEqual(err, "one"); - test.ok(!one); - test.ok(!two); + "Callback#callback to object error - multi argument": function(test) { + var successfulFunction = function(callback) { + callback(["one", "two"]); + }; + + successfulFunction(function(err, one, two) { + test.strictEqual(err[0], "one"); + test.strictEqual(err[1], "two"); + test.ok(!one); + test.ok(!two); + test.done(); + }); + }, + + "keyOf works": function(test) { + test.ok(splunkjs.Utils.keyOf(3, {a: 3, b: 5})); + test.ok(!splunkjs.Utils.keyOf(3, {a: 12, b: 6})); test.done(); - }); - }, - - "Callback#callback to object error - multi argument": function(test) { - var successfulFunction = function(callback) { - callback(["one", "two"]); - }; - - successfulFunction(function(err, one, two) { - test.strictEqual(err[0], "one"); - test.strictEqual(err[1], "two"); - test.ok(!one); - test.ok(!two); + }, + + "bind": function(test) { + var f; + (function() { + f = function(a) { + this.a = a; + }; + })(); + var q = {}; + var g = splunkjs.Utils.bind(q, f); + g(12); + test.strictEqual(q.a, 12); test.done(); - }); - }, - - "keyOf works": function(test) { - test.ok(splunkjs.Utils.keyOf(3, {a: 3, b: 5})); - test.ok(!splunkjs.Utils.keyOf(3, {a: 12, b: 6})); - test.done(); - }, - - "bind": function(test) { - var f; - (function() { - f = function(a) { - this.a = a; - }; - })(); - var q = {}; - var g = splunkjs.Utils.bind(q, f); - g(12); - test.strictEqual(q.a, 12); - test.done(); - }, - - "trim": function(test) { - test.strictEqual(splunkjs.Utils.trim(" test of something \n\r \t"), "test of something"); - - var realTrim = String.prototype.trim; - String.prototype.trim = null; - test.strictEqual(splunkjs.Utils.trim(" test of something \n\r \t"), "test of something"); - String.prototype.trim = realTrim; - - test.done(); - }, - - "indexOf": function(test) { - test.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,5], 3), 2); - test.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,3], 3), 2); - test.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,5], 12), -1); - test.done(); - }, - - "contains": function(test) { - test.ok(splunkjs.Utils.contains([1,2,3,4,5], 3)); - test.ok(splunkjs.Utils.contains([1,2,3,4,3], 3)); - test.ok(!splunkjs.Utils.contains([1,2,3,4,5], 12)); - test.done(); - }, - - "startsWith": function(test) { - test.ok(splunkjs.Utils.startsWith("abcdefg", "abc")); - test.ok(!splunkjs.Utils.startsWith("bcdefg", "abc")); - test.done(); - }, - - "endsWith": function(test) { - test.ok(splunkjs.Utils.endsWith("abcdef", "def")); - test.ok(!splunkjs.Utils.endsWith("abcdef", "bcd")); - test.done(); - }, - - "toArray": function(test) { - (function() { - var found = splunkjs.Utils.toArray(arguments); - var expected = [1,2,3,4,5]; - for (var i = 0; i < found.length; i++) { - test.strictEqual(found[i], expected[i]); - } - })(1,2,3,4,5); - test.done(); - }, - - "isArray": function(test) { - var a = [1,2,3,4,5]; - test.ok(splunkjs.Utils.isArray(a)); - test.done(); - }, - - "isFunction": function(test) { - test.ok(splunkjs.Utils.isFunction(function() {})); - test.ok(!splunkjs.Utils.isFunction(3)); - test.ok(!splunkjs.Utils.isFunction("abc")); - test.ok(!splunkjs.Utils.isFunction({})); - test.done(); - }, - - "isNumber": function(test) { - test.ok(splunkjs.Utils.isNumber(3)); - test.ok(splunkjs.Utils.isNumber(-2.55113e12)); - test.ok(!splunkjs.Utils.isNumber("3")); - test.ok(!splunkjs.Utils.isNumber({3: 5})); - test.done(); - }, - - "isObject": function(test) { - test.ok(splunkjs.Utils.isObject({})); - test.ok(!splunkjs.Utils.isObject(3)); - test.ok(!splunkjs.Utils.isObject("3")); - test.done(); - }, - - "isEmpty": function(test) { - test.ok(splunkjs.Utils.isEmpty({})); - test.ok(splunkjs.Utils.isEmpty([])); - test.ok(splunkjs.Utils.isEmpty("")); - test.ok(!splunkjs.Utils.isEmpty({a: 3})); - test.ok(!splunkjs.Utils.isEmpty([1,2])); - test.ok(!splunkjs.Utils.isEmpty("abc")); - test.done(); - }, - - "forEach": function(test) { - var a = [1,2,3,4,5]; - splunkjs.Utils.forEach( - a, - function(elem, index, list) { - test.strictEqual(a[index], elem); - } - ); - var b = {1: 2, 2: 4, 3: 6}; - splunkjs.Utils.forEach( - b, - function(elem, key, obj) { - test.strictEqual(b[key], elem); - } - ); - splunkjs.Utils.forEach(null, function(elem, key, obj) {}); - var c = {length: 5, 1: 12, 2: 15, 3: 8}; - splunkjs.Utils.forEach( - c, - function(elem, key, obj) { - test.strictEqual(c[key], elem); - } - ); - test.done(); - }, - - "extend": function(test) { - var found = splunkjs.Utils.extend({}, {a: 1, b: 2}, {c: 3, b: 4}); - var expected = {a: 1, b: 4, c:3}; - for (var k in found) { - if (found.hasOwnProperty(k)) { - test.strictEqual(found[k], expected[k]); - } - } - test.done(); - }, - - "clone": function(test) { - var a = {a: 1, b: 2, c: {p: 5, q: 6}}; - var b = splunkjs.Utils.clone(a); - splunkjs.Utils.forEach(a, function(val, key, obj) { test.strictEqual(val, b[key]); }); - a.a = 5; - test.strictEqual(b.a, 1); - a.c.p = 4; - test.strictEqual(b.c.p, 4); - test.done(); - test.strictEqual(splunkjs.Utils.clone(3), 3); - test.strictEqual(splunkjs.Utils.clone("asdf"), "asdf"); - var p = [1,2,[3,4],3]; - var q = splunkjs.Utils.clone(p); - splunkjs.Utils.forEach(p, function(val, index, arr) { test.strictEqual(p[index], q[index]); }); - p[0] = 3; - test.strictEqual(q[0], 1); - p[2][0] = 7; - test.strictEqual(q[2][0], 7); - }, - - "namespaceFromProperties": function(test) { - var a = splunkjs.Utils.namespaceFromProperties( - {acl: {owner: "boris", - app: "factory", - sharing: "system", - other: 3}, - more: 12} - ); - splunkjs.Utils.forEach( - a, - function(val, key, obj) { - test.ok((key === "owner" && val === "boris") || - (key === "app" && val === "factory") || - (key === "sharing" && val === "system")); - } - ); - test.done(); + }, - }, - - "namespaceFromProperties - bad data": function(test) { - var undefinedProps; - var a = splunkjs.Utils.namespaceFromProperties(undefinedProps); - test.strictEqual(a.owner, ''); - test.strictEqual(a.app, ''); - test.strictEqual(a.sharing, ''); - - var undefinedAcl = {}; - var b = splunkjs.Utils.namespaceFromProperties(undefinedProps); - test.strictEqual(b.owner, ''); - test.strictEqual(b.app, ''); - test.strictEqual(b.sharing, ''); - test.done(); - } - }; -}; - -if (module === require.main) { - var test = require('../contrib/nodeunit/test_reporter'); + "trim": function(test) { + test.strictEqual(splunkjs.Utils.trim(" test of something \n\r \t"), "test of something"); - var suite = exports.setup(); - test.run([{"Tests": suite}]); -} - -}); - -require.define("/package.json", function (require, module, exports, __dirname, __filename) { -module.exports = {"main":"index.js"} -}); - -require.define("/index.js", function (require, module, exports, __dirname, __filename) { - -// Copyright 2011 Splunk, Inc. -// -// 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. - -(function() { - var root = exports || this; - - // Declare a process environment so that we can set - // some globals here and have interop with node - process.env = process.env || {}; - - module.exports = root = { - Logger : require('./lib/log').Logger, - Context : require('./lib/context'), - Service : require('./lib/service'), - Http : require('./lib/http'), - Utils : require('./lib/utils'), - Async : require('./lib/async'), - Paths : require('./lib/paths').Paths, - Class : require('./lib/jquery.class').Class, - ModularInputs : require('./lib/modularinputs') + var realTrim = String.prototype.trim; + String.prototype.trim = null; + test.strictEqual(splunkjs.Utils.trim(" test of something \n\r \t"), "test of something"); + String.prototype.trim = realTrim; + + test.done(); + }, + + "indexOf": function(test) { + test.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,5], 3), 2); + test.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,3], 3), 2); + test.strictEqual(splunkjs.Utils.indexOf([1,2,3,4,5], 12), -1); + test.done(); + }, + + "contains": function(test) { + test.ok(splunkjs.Utils.contains([1,2,3,4,5], 3)); + test.ok(splunkjs.Utils.contains([1,2,3,4,3], 3)); + test.ok(!splunkjs.Utils.contains([1,2,3,4,5], 12)); + test.done(); + }, + + "startsWith": function(test) { + test.ok(splunkjs.Utils.startsWith("abcdefg", "abc")); + test.ok(!splunkjs.Utils.startsWith("bcdefg", "abc")); + test.done(); + }, + + "endsWith": function(test) { + test.ok(splunkjs.Utils.endsWith("abcdef", "def")); + test.ok(!splunkjs.Utils.endsWith("abcdef", "bcd")); + test.done(); + }, + + "toArray": function(test) { + (function() { + var found = splunkjs.Utils.toArray(arguments); + var expected = [1,2,3,4,5]; + for (var i = 0; i < found.length; i++) { + test.strictEqual(found[i], expected[i]); + } + })(1,2,3,4,5); + test.done(); + }, + + "isArray": function(test) { + var a = [1,2,3,4,5]; + test.ok(splunkjs.Utils.isArray(a)); + test.done(); + }, + + "isFunction": function(test) { + test.ok(splunkjs.Utils.isFunction(function() {})); + test.ok(!splunkjs.Utils.isFunction(3)); + test.ok(!splunkjs.Utils.isFunction("abc")); + test.ok(!splunkjs.Utils.isFunction({})); + test.done(); + }, + + "isNumber": function(test) { + test.ok(splunkjs.Utils.isNumber(3)); + test.ok(splunkjs.Utils.isNumber(-2.55113e12)); + test.ok(!splunkjs.Utils.isNumber("3")); + test.ok(!splunkjs.Utils.isNumber({3: 5})); + test.done(); + }, + + "isObject": function(test) { + test.ok(splunkjs.Utils.isObject({})); + test.ok(!splunkjs.Utils.isObject(3)); + test.ok(!splunkjs.Utils.isObject("3")); + test.done(); + }, + + "isEmpty": function(test) { + test.ok(splunkjs.Utils.isEmpty({})); + test.ok(splunkjs.Utils.isEmpty([])); + test.ok(splunkjs.Utils.isEmpty("")); + test.ok(!splunkjs.Utils.isEmpty({a: 3})); + test.ok(!splunkjs.Utils.isEmpty([1,2])); + test.ok(!splunkjs.Utils.isEmpty("abc")); + test.done(); + }, + + "forEach": function(test) { + var a = [1,2,3,4,5]; + splunkjs.Utils.forEach( + a, + function(elem, index, list) { + test.strictEqual(a[index], elem); + } + ); + var b = {1: 2, 2: 4, 3: 6}; + splunkjs.Utils.forEach( + b, + function(elem, key, obj) { + test.strictEqual(b[key], elem); + } + ); + splunkjs.Utils.forEach(null, function(elem, key, obj) {}); + var c = {length: 5, 1: 12, 2: 15, 3: 8}; + splunkjs.Utils.forEach( + c, + function(elem, key, obj) { + test.strictEqual(c[key], elem); + } + ); + test.done(); + }, + + "extend": function(test) { + var found = splunkjs.Utils.extend({}, {a: 1, b: 2}, {c: 3, b: 4}); + var expected = {a: 1, b: 4, c:3}; + for (var k in found) { + if (found.hasOwnProperty(k)) { + test.strictEqual(found[k], expected[k]); + } + } + test.done(); + }, + + "clone": function(test) { + var a = {a: 1, b: 2, c: {p: 5, q: 6}}; + var b = splunkjs.Utils.clone(a); + splunkjs.Utils.forEach(a, function(val, key, obj) { test.strictEqual(val, b[key]); }); + a.a = 5; + test.strictEqual(b.a, 1); + a.c.p = 4; + test.strictEqual(b.c.p, 4); + test.done(); + test.strictEqual(splunkjs.Utils.clone(3), 3); + test.strictEqual(splunkjs.Utils.clone("asdf"), "asdf"); + var p = [1,2,[3,4],3]; + var q = splunkjs.Utils.clone(p); + splunkjs.Utils.forEach(p, function(val, index, arr) { test.strictEqual(p[index], q[index]); }); + p[0] = 3; + test.strictEqual(q[0], 1); + p[2][0] = 7; + test.strictEqual(q[2][0], 7); + }, + + "namespaceFromProperties": function(test) { + var a = splunkjs.Utils.namespaceFromProperties( + {acl: {owner: "boris", + app: "factory", + sharing: "system", + other: 3}, + more: 12} + ); + splunkjs.Utils.forEach( + a, + function(val, key, obj) { + test.ok((key === "owner" && val === "boris") || + (key === "app" && val === "factory") || + (key === "sharing" && val === "system")); + } + ); + test.done(); + + }, + + "namespaceFromProperties - bad data": function(test) { + var undefinedProps; + var a = splunkjs.Utils.namespaceFromProperties(undefinedProps); + test.strictEqual(a.owner, ''); + test.strictEqual(a.app, ''); + test.strictEqual(a.sharing, ''); + + var undefinedAcl = {}; + var b = splunkjs.Utils.namespaceFromProperties(undefinedProps); + test.strictEqual(b.owner, ''); + test.strictEqual(b.app, ''); + test.strictEqual(b.sharing, ''); + test.done(); + } + }; }; - if (typeof(window) === 'undefined') { - root.NodeHttp = require('./lib/platform/node/node_http').NodeHttp; + if (module === require.main) { + var test = require('../contrib/nodeunit/test_reporter'); + + var suite = exports.setup(); + test.run([{"Tests": suite}]); } -})(); -}); - -require.define("/lib/log.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2012 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; - var utils = require('./utils'); - var root = exports || this; - - var levels = { - "ALL": 4, - "INFO": 3, - "WARN": 2, - "ERROR": 1, - "NONE": 0 - }; - - // Normalize the value of the environment variable $LOG_LEVEL to - // an integer (look up named levels like "ERROR" in levels above), - // and default to "ERROR" if there is no value or an invalid value - // set. - var setLevel = function(level) { - if (utils.isString(level) && levels.hasOwnProperty(level)) { - process.env.LOG_LEVEL = levels[level]; - } - else if (!isNaN(parseInt(level, 10)) && - utils.keyOf(parseInt(level, 10), levels)) { - process.env.LOG_LEVEL = level; + }); + + require.define("/package.json", function (require, module, exports, __dirname, __filename) { + module.exports = {"main":"index.js"} + }); + + require.define("/index.js", function (require, module, exports, __dirname, __filename) { + + // Copyright 2011 Splunk, Inc. + // + // 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. + + (function() { + var root = exports || this; + + // Declare a process environment so that we can set + // some globals here and have interop with node + process.env = process.env || {}; + + module.exports = root = { + Logger : require('./lib/log').Logger, + Context : require('./lib/context'), + Service : require('./lib/service'), + Http : require('./lib/http'), + Utils : require('./lib/utils'), + Async : require('./lib/async'), + Paths : require('./lib/paths').Paths, + Class : require('./lib/jquery.class').Class, + ModularInputs : require('./lib/modularinputs') + }; + + if (typeof(window) === 'undefined') { + root.NodeHttp = require('./lib/platform/node/node_http').NodeHttp; + } + })(); + }); + + require.define("/lib/log.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2012 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; + var utils = require('./utils'); + + var root = exports || this; + + var levels = { + "ALL": 4, + "INFO": 3, + "WARN": 2, + "ERROR": 1, + "NONE": 0 + }; + + // Normalize the value of the environment variable $LOG_LEVEL to + // an integer (look up named levels like "ERROR" in levels above), + // and default to "ERROR" if there is no value or an invalid value + // set. + var setLevel = function(level) { + if (utils.isString(level) && levels.hasOwnProperty(level)) { + process.env.LOG_LEVEL = levels[level]; + } + else if (!isNaN(parseInt(level, 10)) && + utils.keyOf(parseInt(level, 10), levels)) { + process.env.LOG_LEVEL = level; + } + else { + process.env.LOG_LEVEL = levels["ERROR"]; + } + }; + + if (process.env.LOG_LEVEL) { + setLevel(process.env.LOG_LEVEL); } else { - process.env.LOG_LEVEL = levels["ERROR"]; + process.env.LOG_LEVEL = levels["ERROR"]; } - }; - - if (process.env.LOG_LEVEL) { - setLevel(process.env.LOG_LEVEL); - } - else { - process.env.LOG_LEVEL = levels["ERROR"]; - } - - // Set the actual output functions - // This section is not covered by unit tests, since there's no - // straightforward way to control what the console object will be. - var _log, _warn, _error, _info; - _log = _warn = _error = _info = function() {}; - if (typeof(console) !== "undefined") { - - var logAs = function(level) { - return function(str) { - try { - console[level].apply(console, arguments); + + // Set the actual output functions + // This section is not covered by unit tests, since there's no + // straightforward way to control what the console object will be. + var _log, _warn, _error, _info; + _log = _warn = _error = _info = function() {}; + if (typeof(console) !== "undefined") { + + var logAs = function(level) { + return function(str) { + try { + console[level].apply(console, arguments); + } + catch(ex) { + console[level](str); + } + }; + }; + + if (console.log) { _log = logAs("log"); } + if (console.error) { _error = logAs("error"); } + if (console.warn) { _warn = logAs("warn"); } + if (console.info) { _info = logAs("info"); } + } + + /** + * A controllable logging module that lets you display different types of + * debugging information to the console. + * + * @module splunkjs.Logger + */ + exports.Logger = { + /** + * Logs debug messages to the console. This function is the same as + * `console.log`. + * + * @function splunkjs.Logger + */ + log: function() { + if (process.env.LOG_LEVEL >= levels.ALL) { + _log.apply(null, arguments); } - catch(ex) { - console[level](str); + }, + + /** + * Logs debug errors to the console. This function is the same as + * `console.error`. + * + * @function splunkjs.Logger + */ + error: function() { + if (process.env.LOG_LEVEL >= levels.ERROR) { + _error.apply(null, arguments); } - }; + }, + + /** + * Logs debug warnings to the console. This function is the same as + * `console.warn`. + * + * @function splunkjs.Logger + */ + warn: function() { + if (process.env.LOG_LEVEL >= levels.WARN) { + _warn.apply(null, arguments); + } + }, + + /** + * Logs debug info to the console. This function is the same as + * `console.info`. + * + * @function splunkjs.Logger + */ + info: function() { + if (process.env.LOG_LEVEL >= levels.INFO) { + _info.apply(null, arguments); + } + }, + + /** + * Prints all messages that are retrieved from the splunkd server to the + * console. + * + * @function splunkjs.Logger + */ + printMessages: function(allMessages) { + allMessages = allMessages || []; + + for(var i = 0; i < allMessages.length; i++) { + var message = allMessages[i]; + var type = message["type"]; + var text = message["text"]; + var msg = '[SPLUNKD] ' + text; + switch (type) { + case 'HTTP': + case 'FATAL': + case 'ERROR': + this.error(msg); + break; + case 'WARN': + this.warn(msg); + break; + case 'INFO': + this.info(msg); + break; + case 'HTTP': + this.error(msg); + break; + default: + this.info(msg); + break; + } + } + }, + + /** + * Sets the global logging level to indicate which information to log. + * + * @example + * + * splunkjs.Logger.setLevel("WARN"); + * splunkjs.Logger.setLevel(0); // equivalent to NONE + * + * @param {String|Number} level A string or number ("ALL" = 4 | "INFO" = 3 | "WARN" = 2 | "ERROR" = 1 | "NONE" = 0) indicating the logging level. + * + * @function splunkjs.Logger + */ + setLevel: function(level) { setLevel.apply(this, arguments); }, + + /*!*/ + levels: levels }; - - if (console.log) { _log = logAs("log"); } - if (console.error) { _error = logAs("error"); } - if (console.warn) { _warn = logAs("warn"); } - if (console.info) { _info = logAs("info"); } - } - - /** - * A controllable logging module that lets you display different types of - * debugging information to the console. - * - * @module splunkjs.Logger - */ - exports.Logger = { + })(); + + }); + + require.define("/lib/utils.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2012 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; + + var fs = require("fs"); + var path = require("path"); + var root = exports || this; + /** - * Logs debug messages to the console. This function is the same as - * `console.log`. + * Provides various utility functions, which are mostly modeled after + * [Underscore.js](http://documentcloud.github.com/underscore/). * - * @function splunkjs.Logger + * @module splunkjs.Utils */ - log: function() { - if (process.env.LOG_LEVEL >= levels.ALL) { - _log.apply(null, arguments); - } - }, + + /** + * Binds a function to a specific object. + * + * @example + * + * var obj = {a: 1, b: function() { console.log(a); }}; + * var bound = splunkjs.Utils.bind(obj, obj.b); + * bound(); // prints 1 + * + * @param {Object} me The object to bind to. + * @param {Function} fn The function to bind. + * @return {Function} The bound function. + * + * @function splunkjs.Utils + */ + root.bind = function(me, fn) { + return function() { + return fn.apply(me, arguments); + }; + }; /** - * Logs debug errors to the console. This function is the same as - * `console.error`. + * Strips a string of all leading and trailing whitespace characters. + * + * @example + * + * var a = " aaa "; + * var b = splunkjs.Utils.trim(a); //== "aaa" * - * @function splunkjs.Logger + * @param {String} str The string to trim. + * @return {String} The trimmed string. + * + * @function splunkjs.Utils */ - error: function() { - if (process.env.LOG_LEVEL >= levels.ERROR) { - _error.apply(null, arguments); + root.trim = function(str) { + str = str || ""; + + if (String.prototype.trim) { + return String.prototype.trim.call(str); } - }, + else { + return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + } + }; /** - * Logs debug warnings to the console. This function is the same as - * `console.warn`. + * Searches an array for a specific object and returns its location. + * + * @example + * + * var a = ["a", "b', "c"]; + * console.log(splunkjs.Utils.indexOf(a, "b")) //== 1 + * console.log(splunkjs.Utils.indexOf(a, "d")) //== -1 + * + * @param {Array} arr The array to search in. + * @param {Anything} search The object to search for. + * @return {Number} The index of the object (`search`), or `-1` if the object wasn't found. * - * @function splunkjs.Logger + * @function splunkjs.Utils */ - warn: function() { - if (process.env.LOG_LEVEL >= levels.WARN) { - _warn.apply(null, arguments); + root.indexOf = function(arr, search) { + for(var i=0; i= 0); + }; + + /** + * Indicates whether a string starts with a specific prefix. + * + * @example + * + * var starts = splunkjs.Utils.startsWith("splunk-foo", "splunk-"); + * + * @param {String} original The string to search in. + * @param {String} prefix The prefix to search for. + * @return {Boolean} `true` if the string starts with the prefix, `false` if not. + * + * @function splunkjs.Utils + */ + root.startsWith = function(original, prefix) { + var matches = original.match("^" + prefix); + return matches && matches.length > 0 && matches[0] === prefix; + }; + + /** + * Indicates whether a string ends with a specific suffix. + * + * @example + * + * var ends = splunkjs.Utils.endsWith("foo-splunk", "-splunk"); + * + * @param {String} original The string to search in. + * @param {String} suffix The suffix to search for. + * @return {Boolean} `true` if the string ends with the suffix, `false` if not. + * + * @function splunkjs.Utils + */ + root.endsWith = function(original, suffix) { + var matches = original.match(suffix + "$"); + return matches && matches.length > 0 && matches[0] === suffix; + }; + + var toString = Object.prototype.toString; /** - * Logs debug info to the console. This function is the same as - * `console.info`. + * Converts an iterable to an array. + * + * @example + * + * function() { + * console.log(arguments instanceof Array); // false + * var arr = console.log(splunkjs.Utils.toArray(arguments) instanceof Array); // true + * } * - * @function splunkjs.Logger + * @param {Arguments} iterable The iterable to convert. + * @return {Array} The converted array. + * + * @function splunkjs.Utils */ - info: function() { - if (process.env.LOG_LEVEL >= levels.INFO) { - _info.apply(null, arguments); - } - }, + root.toArray = function(iterable) { + return Array.prototype.slice.call(iterable); + }; /** - * Prints all messages that are retrieved from the splunkd server to the - * console. + * Indicates whether an argument is an array. + * + * @example + * + * function() { + * console.log(splunkjs.Utils.isArray(arguments)); // false + * console.log(splunkjs.Utils.isArray([1,2,3])); // true + * } + * + * @param {Anything} obj The argument to evaluate. + * @return {Boolean} `true` if the argument is an array, `false` if not. * - * @function splunkjs.Logger + * @function splunkjs.Utils */ - printMessages: function(allMessages) { - allMessages = allMessages || []; - - for(var i = 0; i < allMessages.length; i++) { - var message = allMessages[i]; - var type = message["type"]; - var text = message["text"]; - var msg = '[SPLUNKD] ' + text; - switch (type) { - case 'HTTP': - case 'FATAL': - case 'ERROR': - this.error(msg); - break; - case 'WARN': - this.warn(msg); - break; - case 'INFO': - this.info(msg); - break; - case 'HTTP': - this.error(msg); - break; - default: - this.info(msg); - break; - } - } - }, + root.isArray = Array.isArray || function(obj) { + return toString.call(obj) === '[object Array]'; + }; + + /** + * Indicates whether an argument is a function. + * + * @example + * + * function() { + * console.log(splunkjs.Utils.isFunction([1,2,3]); // false + * console.log(splunkjs.Utils.isFunction(function() {})); // true + * } + * + * @param {Anything} obj The argument to evaluate. + * @return {Boolean} `true` if the argument is a function, `false` if not. + * + * @function splunkjs.Utils + */ + root.isFunction = function(obj) { + return !!(obj && obj.constructor && obj.call && obj.apply); + }; + + /** + * Indicates whether an argument is a number. + * + * @example + * + * function() { + * console.log(splunkjs.Utils.isNumber(1); // true + * console.log(splunkjs.Utils.isNumber(function() {})); // false + * } + * + * @param {Anything} obj The argument to evaluate. + * @return {Boolean} `true` if the argument is a number, `false` if not. + * + * @function splunkjs.Utils + */ + root.isNumber = function(obj) { + return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); + }; /** - * Sets the global logging level to indicate which information to log. + * Indicates whether an argument is a string. * * @example + * + * function() { + * console.log(splunkjs.Utils.isString("abc"); // true + * console.log(splunkjs.Utils.isString(function() {})); // false + * } * - * splunkjs.Logger.setLevel("WARN"); - * splunkjs.Logger.setLevel(0); // equivalent to NONE + * @param {Anything} obj The argument to evaluate. + * @return {Boolean} `true` if the argument is a string, `false` if not. * - * @param {String|Number} level A string or number ("ALL" = 4 | "INFO" = 3 | "WARN" = 2 | "ERROR" = 1 | "NONE" = 0) indicating the logging level. + * @function splunkjs.Utils + */ + root.isString = function(obj) { + return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); + }; + + /** + * Indicates whether an argument is an object. + * + * @example + * + * function() { + * console.log(splunkjs.Utils.isObject({abc: "abc"}); // true + * console.log(splunkjs.Utils.isObject("abc"); // false + * } + * + * @param {Anything} obj The argument to evaluate. + * @return {Boolean} `true` if the argument is an object, `false` if not. * - * @function splunkjs.Logger + * @function splunkjs.Utils */ - setLevel: function(level) { setLevel.apply(this, arguments); }, + root.isObject = function(obj) { + /*jslint newcap:false */ + return obj === Object(obj); + }; - /*!*/ - levels: levels - }; -})(); - -}); - -require.define("/lib/utils.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2012 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; - - var fs = require("fs"); - var path = require("path"); - var root = exports || this; - - /** - * Provides various utility functions, which are mostly modeled after - * [Underscore.js](http://documentcloud.github.com/underscore/). - * - * @module splunkjs.Utils - */ - - /** - * Binds a function to a specific object. - * - * @example - * - * var obj = {a: 1, b: function() { console.log(a); }}; - * var bound = splunkjs.Utils.bind(obj, obj.b); - * bound(); // prints 1 - * - * @param {Object} me The object to bind to. - * @param {Function} fn The function to bind. - * @return {Function} The bound function. - * - * @function splunkjs.Utils - */ - root.bind = function(me, fn) { - return function() { - return fn.apply(me, arguments); - }; - }; - - /** - * Strips a string of all leading and trailing whitespace characters. - * - * @example - * - * var a = " aaa "; - * var b = splunkjs.Utils.trim(a); //== "aaa" - * - * @param {String} str The string to trim. - * @return {String} The trimmed string. - * - * @function splunkjs.Utils - */ - root.trim = function(str) { - str = str || ""; - - if (String.prototype.trim) { - return String.prototype.trim.call(str); - } - else { - return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - } - }; - - /** - * Searches an array for a specific object and returns its location. - * - * @example - * - * var a = ["a", "b', "c"]; - * console.log(splunkjs.Utils.indexOf(a, "b")) //== 1 - * console.log(splunkjs.Utils.indexOf(a, "d")) //== -1 - * - * @param {Array} arr The array to search in. - * @param {Anything} search The object to search for. - * @return {Number} The index of the object (`search`), or `-1` if the object wasn't found. - * - * @function splunkjs.Utils - */ - root.indexOf = function(arr, search) { - for(var i=0; i= 0); - }; - - /** - * Indicates whether a string starts with a specific prefix. - * - * @example - * - * var starts = splunkjs.Utils.startsWith("splunk-foo", "splunk-"); - * - * @param {String} original The string to search in. - * @param {String} prefix The prefix to search for. - * @return {Boolean} `true` if the string starts with the prefix, `false` if not. - * - * @function splunkjs.Utils - */ - root.startsWith = function(original, prefix) { - var matches = original.match("^" + prefix); - return matches && matches.length > 0 && matches[0] === prefix; - }; - - /** - * Indicates whether a string ends with a specific suffix. - * - * @example - * - * var ends = splunkjs.Utils.endsWith("foo-splunk", "-splunk"); - * - * @param {String} original The string to search in. - * @param {String} suffix The suffix to search for. - * @return {Boolean} `true` if the string ends with the suffix, `false` if not. - * - * @function splunkjs.Utils - */ - root.endsWith = function(original, suffix) { - var matches = original.match(suffix + "$"); - return matches && matches.length > 0 && matches[0] === suffix; - }; - - var toString = Object.prototype.toString; - - /** - * Converts an iterable to an array. - * - * @example - * - * function() { - * console.log(arguments instanceof Array); // false - * var arr = console.log(splunkjs.Utils.toArray(arguments) instanceof Array); // true - * } - * - * @param {Arguments} iterable The iterable to convert. - * @return {Array} The converted array. - * - * @function splunkjs.Utils - */ - root.toArray = function(iterable) { - return Array.prototype.slice.call(iterable); - }; - - /** - * Indicates whether an argument is an array. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.isArray(arguments)); // false - * console.log(splunkjs.Utils.isArray([1,2,3])); // true - * } - * - * @param {Anything} obj The argument to evaluate. - * @return {Boolean} `true` if the argument is an array, `false` if not. - * - * @function splunkjs.Utils - */ - root.isArray = Array.isArray || function(obj) { - return toString.call(obj) === '[object Array]'; - }; - - /** - * Indicates whether an argument is a function. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.isFunction([1,2,3]); // false - * console.log(splunkjs.Utils.isFunction(function() {})); // true - * } - * - * @param {Anything} obj The argument to evaluate. - * @return {Boolean} `true` if the argument is a function, `false` if not. - * - * @function splunkjs.Utils - */ - root.isFunction = function(obj) { - return !!(obj && obj.constructor && obj.call && obj.apply); - }; - - /** - * Indicates whether an argument is a number. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.isNumber(1); // true - * console.log(splunkjs.Utils.isNumber(function() {})); // false - * } - * - * @param {Anything} obj The argument to evaluate. - * @return {Boolean} `true` if the argument is a number, `false` if not. - * - * @function splunkjs.Utils - */ - root.isNumber = function(obj) { - return !!(obj === 0 || (obj && obj.toExponential && obj.toFixed)); - }; - - /** - * Indicates whether an argument is a string. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.isString("abc"); // true - * console.log(splunkjs.Utils.isString(function() {})); // false - * } - * - * @param {Anything} obj The argument to evaluate. - * @return {Boolean} `true` if the argument is a string, `false` if not. - * - * @function splunkjs.Utils - */ - root.isString = function(obj) { - return !!(obj === '' || (obj && obj.charCodeAt && obj.substr)); - }; - - /** - * Indicates whether an argument is an object. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.isObject({abc: "abc"}); // true - * console.log(splunkjs.Utils.isObject("abc"); // false - * } - * - * @param {Anything} obj The argument to evaluate. - * @return {Boolean} `true` if the argument is an object, `false` if not. - * - * @function splunkjs.Utils - */ - root.isObject = function(obj) { - /*jslint newcap:false */ - return obj === Object(obj); - }; - - /** - * Indicates whether an argument is empty. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.isEmpty({})); // true - * console.log(splunkjs.Utils.isEmpty({a: 1})); // false - * } - * - * @param {Anything} obj The argument to evaluate. - * @return {Boolean} `true` if the argument is empty, `false` if not. - * - * @function splunkjs.Utils - */ - root.isEmpty = function(obj) { - if (root.isArray(obj) || root.isString(obj)) { - return obj.length === 0; - } - - for (var key in obj) { - if (this.hasOwnProperty.call(obj, key)) { - return false; + + for (var key in obj) { + if (this.hasOwnProperty.call(obj, key)) { + return false; + } } - } + + return true; + }; - return true; - }; - - /** - * Applies an iterator function to each element in an object. - * - * @example - * - * splunkjs.Utils.forEach([1,2,3], function(el) { console.log(el); }); // 1,2,3 - * - * @param {Object|Array} obj An object or array. - * @param {Function} iterator The function to apply to each element: `(element, list, index)`. - * @param {Object} context A context to apply to the function (optional). - * - * @function splunkjs.Utils - */ - root.forEach = function(obj, iterator, context) { - if (obj === null) { - return; - } - if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { - obj.forEach(iterator, context); - } - else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (i in obj && iterator.call(context, obj[i], i, obj) === {}) { - return; - } + /** + * Applies an iterator function to each element in an object. + * + * @example + * + * splunkjs.Utils.forEach([1,2,3], function(el) { console.log(el); }); // 1,2,3 + * + * @param {Object|Array} obj An object or array. + * @param {Function} iterator The function to apply to each element: `(element, list, index)`. + * @param {Object} context A context to apply to the function (optional). + * + * @function splunkjs.Utils + */ + root.forEach = function(obj, iterator, context) { + if (obj === null) { + return; } - } - else { - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - if (iterator.call(context, obj[key], key, obj) === {}) { + if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { + obj.forEach(iterator, context); + } + else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === {}) { return; } } - } - } - }; - - /** - * Extends a given object with all the properties from other source objects. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.extend({foo: "bar"}, {a: 2})); // {foo: "bar", a: 2} - * } - * - * @param {Object} obj The object to extend. - * @param {Object...} sources The source objects from which to take properties. - * @return {Object} The extended object. - * - * @function splunkjs.Utils - */ - root.extend = function(obj) { - root.forEach(Array.prototype.slice.call(arguments, 1), function(source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - }); - return obj; - }; - - /** - * Creates a shallow-cloned copy of an object or array. - * - * @example - * - * function() { - * console.log(splunkjs.Utils.clone({foo: "bar"})); // {foo: "bar"} - * console.log(splunkjs.Utils.clone([1,2,3])); // [1,2,3] - * } - * - * @param {Object|Array} obj The object or array to clone. - * @return {Object|Array} The cloned object or array. - * - * @function splunkjs.Utils - */ - root.clone = function(obj) { - if (!root.isObject(obj)) { - return obj; - } - return root.isArray(obj) ? obj.slice() : root.extend({}, obj); - }; - - /** - * Extracts namespace information from a dictionary of properties. Namespace - * information includes values for _owner_, _app_, and _sharing_. - * - * @param {Object} props The dictionary of properties. - * @return {Object} Namespace information from the properties dictionary. - * - * @function splunkjs.Utils - */ - root.namespaceFromProperties = function(props) { - if (root.isUndefined(props) || root.isUndefined(props.acl)) { - return { - owner: '', - app: '', - sharing: '' - }; - } - return { - owner: props.acl.owner, - app: props.acl.app, - sharing: props.acl.sharing - }; - }; - - /** - * Tests whether a value appears in a given object. - * - * @param {Anything} val The value to search for. - * @param {Object} obj The object to search in. - * - * @function splunkjs.Utils - */ - root.keyOf = function(val, obj) { - for (var k in obj) { - if (obj.hasOwnProperty(k) && obj[k] === val) { - return k; - } - } - return undefined; - }; - - /** - * Finds a version in a dictionary. - * - * @param {String} version The version to search for. - * @param {Object} map The dictionary to search. - * @return {Anything} The value of the dictionary at the closest version match. - * - * @function splunkjs.Utils - */ - root.getWithVersion = function(version, map) { - map = map || {}; - var currentVersion = (version + "") || ""; - while (currentVersion !== "") { - if (map.hasOwnProperty(currentVersion)) { - return map[currentVersion]; - } + } else { - currentVersion = currentVersion.slice( - 0, - currentVersion.lastIndexOf(".") - ); + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + if (iterator.call(context, obj[key], key, obj) === {}) { + return; + } + } + } } - } + }; - return map["default"]; - }; - - /** - * Checks if an object is undefined. - * - * @param {Object} obj An object. - * @return {Boolean} `true` if the object is undefined, `false` if not. - */ - root.isUndefined = function (obj) { - return (typeof obj === "undefined"); - }; - - /** - * Read files in a way that makes unit tests work as well. - * - * @example - * - * // To read `splunk-sdk-javascript/tests/data/empty_data_model.json` - * // from `splunk-sdk-javascript/tests/test_service.js` - * var fileContents = utils.readFile(__filename, "../data/empty_data_model.json"); - * - * @param {String} __filename of the script calling this function. - * @param {String} a path relative to the script calling this function. - * @return {String} The contents of the file. - */ - root.readFile = function(filename, relativePath) { - return fs.readFileSync(path.resolve(filename, relativePath)).toString(); - }; - -})(); -}); - -require.define("fs", function (require, module, exports, __dirname, __filename) { -// nothing to see here... no file methods for the browser - -}); - -require.define("/lib/context.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2012 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; - - var Paths = require('./paths').Paths; - var Class = require('./jquery.class').Class; - var Http = require('./http'); - var utils = require('./utils'); - - var root = exports || this; - - var prefixMap = { - "5": "", - "4.3": "/services/json/v2", - "default": "" - }; - - /** - * An abstraction over the Splunk HTTP-wire protocol that provides the basic - * functionality for communicating with a Splunk instance over HTTP, handles - * authentication and authorization, and formats HTTP requests (GET, POST, - * and DELETE) in the format that Splunk expects. - * - * @class splunkjs.Context - */ - module.exports = root = Class.extend({ - /** - * Constructor for `splunkjs.Context`. + * Extends a given object with all the properties from other source objects. + * + * @example + * + * function() { + * console.log(splunkjs.Utils.extend({foo: "bar"}, {a: 2})); // {foo: "bar", a: 2} + * } * - * @constructor - * @param {splunkjs.Http} http An instance of a `splunkjs.Http` class. - * @param {Object} params A dictionary of optional parameters: - * - `scheme` (_string_): The scheme ("http" or "https") for accessing Splunk. - * - `host` (_string_): The host name (the default is "localhost"). - * - `port` (_integer_): The port number (the default is 8089). - * - `username` (_string_): The Splunk account username, which is used to authenticate the Splunk instance. - * - `password` (_string_): The password, which is used to authenticate the Splunk instance. - * - `owner` (_string_): The owner (username) component of the namespace. - * - `app` (_string_): The app component of the namespace. - * - `sessionKey` (_string_): The current session token. - * - `autologin` (_boolean_): `true` to automatically try to log in again if the session terminates, `false` if not (`true` by default). - * - 'timeout' (_integer): The connection timeout in milliseconds. ('0' by default). - * - `version` (_string_): The version string for Splunk, for example "4.3.2" (the default is "5.0"). - * @return {splunkjs.Context} A new `splunkjs.Context` instance. + * @param {Object} obj The object to extend. + * @param {Object...} sources The source objects from which to take properties. + * @return {Object} The extended object. * - * @method splunkjs.Context + * @function splunkjs.Utils */ - init: function(http, params) { - if (!(http instanceof Http) && !params) { - // Move over the params - params = http; - http = null; - } - - params = params || {}; - - this.scheme = params.scheme || "https"; - this.host = params.host || "localhost"; - this.port = params.port || 8089; - this.username = params.username || null; - this.password = params.password || null; - this.owner = params.owner; - this.app = params.app; - this.sessionKey = params.sessionKey || ""; - this.authorization = params.authorization || "Splunk"; - this.paths = params.paths || Paths; - this.version = params.version || "default"; - this.timeout = params.timeout || 0; - this.autologin = true; - - // Initialize autologin - // The reason we explicitly check to see if 'autologin' - // is actually set is because we need to distinguish the - // case of it being set to 'false', and it not being set. - // Unfortunately, in JavaScript, these are both false-y - if (params.hasOwnProperty("autologin")) { - this.autologin = params.autologin; - } - - if (!http) { - // If there is no HTTP implementation set, we check what platform - // we're running on. If we're running in the browser, then complain, - // else, we instantiate NodeHttp. - if (typeof(window) !== 'undefined') { - throw new Error("Http instance required when creating a Context within a browser."); - } - else { - var NodeHttp = require('./platform/node/node_http').NodeHttp; - http = new NodeHttp(); + root.extend = function(obj) { + root.forEach(Array.prototype.slice.call(arguments, 1), function(source) { + for (var prop in source) { + obj[prop] = source[prop]; } - } - - // Store the HTTP implementation - this.http = http; - this.http._setSplunkVersion(this.version); - - // Store our full prefix, which is just combining together - // the scheme with the host - var versionPrefix = utils.getWithVersion(this.version, prefixMap); - this.prefix = this.scheme + "://" + this.host + ":" + this.port + versionPrefix; - - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this._headers = utils.bind(this, this._headers); - this.fullpath = utils.bind(this, this.fullpath); - this.urlify = utils.bind(this, this.urlify); - this.get = utils.bind(this, this.get); - this.del = utils.bind(this, this.del); - this.post = utils.bind(this, this.post); - this.login = utils.bind(this, this.login); - this._shouldAutoLogin = utils.bind(this, this._shouldAutoLogin); - this._requestWrapper = utils.bind(this, this._requestWrapper); - }, - + }); + return obj; + }; + /** - * Appends Splunk-specific headers. + * Creates a shallow-cloned copy of an object or array. + * + * @example + * + * function() { + * console.log(splunkjs.Utils.clone({foo: "bar"})); // {foo: "bar"} + * console.log(splunkjs.Utils.clone([1,2,3])); // [1,2,3] + * } * - * @param {Object} headers A dictionary of headers (optional). - * @return {Object} An augmented dictionary of headers. + * @param {Object|Array} obj The object or array to clone. + * @return {Object|Array} The cloned object or array. * - * @method splunkjs.Context - * @private + * @function splunkjs.Utils */ - _headers: function (headers) { - headers = headers || {}; - if (this.sessionKey) { - headers["Authorization"] = this.authorization + " " + this.sessionKey; + root.clone = function(obj) { + if (!root.isObject(obj)) { + return obj; } - return headers; - }, - - /*!*/ - _shouldAutoLogin: function() { - return this.username && this.password && this.autologin; - }, - - /*!*/ + return root.isArray(obj) ? obj.slice() : root.extend({}, obj); + }; + /** - * This internal function aids with the autologin feature. - * It takes two parameters: `task`, which is a function describing an - * HTTP request, and `callback`, to be invoked when all is said - * and done. + * Extracts namespace information from a dictionary of properties. Namespace + * information includes values for _owner_, _app_, and _sharing_. * - * @param {Function} task A function taking a single argument: `(callback)`. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * @param {Object} props The dictionary of properties. + * @return {Object} Namespace information from the properties dictionary. + * + * @function splunkjs.Utils */ - _requestWrapper: function(task, callback) { - callback = callback || function() {}; - - var that = this; - var req = null; - - // This is the callback that will be invoked - // if we are currently logged in but our session key - // expired (i.e. we get a 401 response from the server). - // We will only retry once. - var reloginIfNecessary = function(err) { - // If we aborted, ignore it - if (req.wasAborted) { - return; - } - - if (err && err.status === 401 && that._shouldAutoLogin()) { - // If we had an authorization error, we'll try and login - // again, but only once - that.sessionKey = null; - that.login(function(err, success) { - // If we've already aborted the request, - // just do nothing - if (req.wasAborted) { - return; - } - - if (err) { - // If there was an error logging in, send it through - callback(err); - } - else { - // Relogging in was successful, so we execute - // our task again. - task(callback); - } - }); - } - else { - callback.apply(null, arguments); - } - }; - - if (!this._shouldAutoLogin() || this.sessionKey) { - // Since we are not auto-logging in, just execute our task, - // but intercept any 401s so we can login then - req = task(reloginIfNecessary); - return req; - } - - // OK, so we know that we should try and autologin, - // so we try and login, and if we succeed, execute - // the original task - req = this.login(function(err, success) { - // If we've already aborted the request, - // just do nothing - if (req.wasAborted) { - return; - } - - if (err) { - // If there was an error logging in, send it through - callback(err); - } - else { - // Logging in was successful, so we execute - // our task. - task(callback); - } - }); - - return req; - }, - - /** - * Converts a partial path to a fully-qualified path to a REST endpoint, - * and if necessary includes the namespace owner and app. - * - * @param {String} path The partial path. - * @param {String} namespace The namespace, in the format "_owner_/_app_". - * @return {String} The fully-qualified path. - * - * @method splunkjs.Context - */ - fullpath: function(path, namespace) { - namespace = namespace || {}; - - if (utils.startsWith(path, "/")) { - return path; - } - - // If we don't have an app name (explicitly or implicitly), we default to /services/ - if (!namespace.app && !this.app && namespace.sharing !== root.Sharing.SYSTEM) { - return "/services/" + path; - } - - // Get the app and owner, first from the passed in namespace, then the service, - // finally defaulting to wild cards - var owner = namespace.owner || this.owner || "-"; - var app = namespace.app || this.app || "-"; - - namespace.sharing = (namespace.sharing || "").toLowerCase(); - - // Modify the owner and app appropriately based on the sharing parameter - if (namespace.sharing === root.Sharing.APP || namespace.sharing === root.Sharing.GLOBAL) { - owner = "nobody"; - } - else if (namespace.sharing === root.Sharing.SYSTEM) { - owner = "nobody"; - app = "system"; + root.namespaceFromProperties = function(props) { + if (root.isUndefined(props) || root.isUndefined(props.acl)) { + return { + owner: '', + app: '', + sharing: '' + }; } - - return utils.trim("/servicesNS/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app) + "/" + path); - }, - + return { + owner: props.acl.owner, + app: props.acl.app, + sharing: props.acl.sharing + }; + }; + /** - * Converts a partial path to a fully-qualified URL. - * - * @param {String} path The partial path. - * @return {String} The fully-qualified URL. - * - * @method splunkjs.Context - * @private - */ - urlify: function(path) { - return this.prefix + this.fullpath(path); - }, - + * Tests whether a value appears in a given object. + * + * @param {Anything} val The value to search for. + * @param {Object} obj The object to search in. + * + * @function splunkjs.Utils + */ + root.keyOf = function(val, obj) { + for (var k in obj) { + if (obj.hasOwnProperty(k) && obj[k] === val) { + return k; + } + } + return undefined; + }; + /** - * Authenticates and logs in to a Splunk instance, then stores the - * resulting session key. + * Finds a version in a dictionary. * - * @param {Function} callback The function to call when login has finished: `(err, wasSuccessful)`. + * @param {String} version The version to search for. + * @param {Object} map The dictionary to search. + * @return {Anything} The value of the dictionary at the closest version match. * - * @method splunkjs.Context - * @private + * @function splunkjs.Utils */ - login: function(callback) { - var that = this; - var url = this.paths.login; - var params = { - username: this.username, - password: this.password, - cookie : '1' - }; - - callback = callback || function() {}; - var wrappedCallback = function(err, response) { - // Let's make sure that not only did the request succeed, but - // we actually got a non-empty session key back. - var hasSessionKey = !!(!err && response.data && response.data.sessionKey); - - if (err || !hasSessionKey) { - callback(err || "No session key available", false); + root.getWithVersion = function(version, map) { + map = map || {}; + var currentVersion = (version + "") || ""; + while (currentVersion !== "") { + if (map.hasOwnProperty(currentVersion)) { + return map[currentVersion]; } else { - that.sessionKey = response.data.sessionKey; - callback(null, true); + currentVersion = currentVersion.slice( + 0, + currentVersion.lastIndexOf(".") + ); } - }; - - return this.http.post( - this.urlify(url), - this._headers(), - params, - this.timeout, - wrappedCallback - ); - }, - - - /** - * Logs the session out resulting in the removal of all cookies and the - * session key. - * - * @param {Function} callback The function to call when logout has finished: `()`. - * - * @method splunkjs.Context - * @private - */ - logout: function(callback) { - callback = callback || function() {}; - - this.sessionKey = null; - this.http._cookieStore = {}; - callback(); - }, - - /** - * Performs a GET request. - * - * @param {String} path The REST endpoint path of the GET request. - * @param {Object} params The entity-specific parameters for this request. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Context - */ - get: function(path, params, callback) { - var that = this; - var request = function(callback) { - return that.http.get( - that.urlify(path), - that._headers(), - params, - that.timeout, - callback - ); - }; - - return this._requestWrapper(request, callback); - }, - - /** - * Performs a DELETE request. - * - * @param {String} path The REST endpoint path of the DELETE request. - * @param {Object} params The entity-specific parameters for this request. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Context - */ - del: function(path, params, callback) { - var that = this; - var request = function(callback) { - return that.http.del( - that.urlify(path), - that._headers(), - params, - that.timeout, - callback - ); - }; - - return this._requestWrapper(request, callback); - }, - - /** - * Performs a POST request. - * - * @param {String} path The REST endpoint path of the POST request. - * @param {Object} params The entity-specific parameters for this request. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Context - */ - post: function(path, params, callback) { - var that = this; - var request = function(callback) { - return that.http.post( - that.urlify(path), - that._headers(), - params, - that.timeout, - callback - ); - }; - - return this._requestWrapper(request, callback); - }, - + } + + return map["default"]; + }; + /** - * Issues an arbitrary HTTP request to the REST endpoint path segment. - * - * @param {String} path The REST endpoint path segment (with any query parameters already appended and encoded). - * @param {String} method The HTTP method (can be `GET`, `POST`, or `DELETE`). - * @param {Object} query The entity-specific parameters for this request. - * @param {Object} post A dictionary of POST argument that will get form encoded. - * @param {Object} body The body of the request, mutually exclusive with `post`. - * @param {Object} headers Headers for this request. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * Checks if an object is undefined. * - * @method splunkjs.Context + * @param {Object} obj An object. + * @return {Boolean} `true` if the object is undefined, `false` if not. */ - request: function(path, method, query, post, body, headers, callback) { - var that = this; - var request = function(callback) { - return that.http.request( - that.urlify(path), - { - method: method, - headers: that._headers(headers), - query: query, - post: post, - body: body, - timeout: that.timeout - }, - callback - ); - }; - - return this._requestWrapper(request, callback); - }, - + root.isUndefined = function (obj) { + return (typeof obj === "undefined"); + }; + /** - * Compares the Splunk server's version to the specified version string. - * Returns -1 if (this.version < otherVersion), - * 0 if (this.version == otherVersion), - * 1 if (this.version > otherVersion). + * Read files in a way that makes unit tests work as well. * - * @param {String} otherVersion The other version string, for example "5.0". + * @example * - * @method splunkjs.Context + * // To read `splunk-sdk-javascript/tests/data/empty_data_model.json` + * // from `splunk-sdk-javascript/tests/test_service.js` + * var fileContents = utils.readFile(__filename, "../data/empty_data_model.json"); + * + * @param {String} __filename of the script calling this function. + * @param {String} a path relative to the script calling this function. + * @return {String} The contents of the file. */ - versionCompare: function(otherVersion) { - var thisVersion = this.version; - if (thisVersion === "default") { - thisVersion = "5.0"; - } - - var components1 = thisVersion.split("."); - var components2 = otherVersion.split("."); - var numComponents = Math.max(components1.length, components2.length); - - for (var i = 0; i < numComponents; i++) { - var c1 = (i < components1.length) ? parseInt(components1[i], 10) : 0; - var c2 = (i < components2.length) ? parseInt(components2[i], 10) : 0; - if (c1 < c2) { - return -1; - } else if (c1 > c2) { - return 1; - } - } - return 0; - } + root.readFile = function(filename, relativePath) { + return fs.readFileSync(path.resolve(filename, relativePath)).toString(); + }; + + })(); }); - + + require.define("fs", function (require, module, exports, __dirname, __filename) { + // nothing to see here... no file methods for the browser + + }); + + require.define("/lib/context.js", function (require, module, exports, __dirname, __filename) { /*!*/ - root.Sharing = { - USER: "user", - APP: "app", - GLOBAL: "global", - SYSTEM: "system" - }; -})(); - -}); - -require.define("/lib/paths.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2012 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; + // Copyright 2012 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; + + var Paths = require('./paths').Paths; + var Class = require('./jquery.class').Class; + var Http = require('./http'); + var utils = require('./utils'); + + var root = exports || this; + + var prefixMap = { + "5": "", + "4.3": "/services/json/v2", + "default": "" + }; - var root = exports || this; - - // A list of the Splunk REST API endpoint paths - root.Paths = { - apps: "/services/apps/local", - capabilities: "authorization/capabilities", - configurations: "configs", - dataModels: "datamodel/model", - deploymentClient: "deployment/client", - deploymentServers: "deployment/server", - deploymentServerClasses: "deployment/serverclass", - deploymentTenants: "deployment/tenants", - eventTypes: "saved/eventtypes", - firedAlerts: "alerts/fired_alerts", - indexes: "data/indexes", - info: "/services/server/info", - inputs: null, - jobs: "search/jobs", - licenseGroups: "licenser/groups", - licenseMessages: "licenser/messages", - licensePools: "licenser/pools", - licenseSlaves: "licenser/slaves", - licenseStacks: "licenser/stacks", - licenses: "licenser/licenses", - loggers: "server/logger", - login: "/services/auth/login", - messages: "messages", - passwords: "admin/passwords", - parser: "search/parser", - pivot: "datamodel/pivot", - properties: "properties", - roles: "authorization/roles", - savedSearches: "saved/searches", - settings: "server/settings", - storagePasswords: "storage/passwords", - users: "/services/authentication/users", - typeahead: "search/typeahead", - views: "data/ui/views", - - currentUser: "/services/authentication/current-context", - submitEvent: "/services/receivers/simple" - }; -})(); - -}); - -require.define("/lib/jquery.class.js", function (require, module, exports, __dirname, __filename) { -/*! Simple JavaScript Inheritance - * By John Resig http://ejohn.org/ - * MIT Licensed. - * Inspired by base2 and Prototype - */ -(function(){ - var root = exports || this; - - var initializing = false; - var fnTest = (/xyz/.test(function() { return xyz; }) ? /\b_super\b/ : /.*/); - // The base Class implementation (does nothing) - root.Class = function(){}; - - // Create a new Class that inherits from this class - root.Class.extend = function(prop) { - var _super = this.prototype; - - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; - var prototype = new this(); - initializing = false; - - // Copy the properties over onto the new prototype - for (var name in prop) { - // Check if we're overwriting an existing function - prototype[name] = typeof prop[name] == "function" && - typeof _super[name] == "function" && fnTest.test(prop[name]) ? - (function(name, fn){ - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same method - // but on the super-class - this._super = _super[name]; - - // The method only need to be bound temporarily, so we - // remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]) : - prop[name]; - } - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if ( !initializing && this.init ) - this.init.apply(this, arguments); - } - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.constructor = Class; - - // And make this class extendable - Class.extend = arguments.callee; - - return Class; - }; -})(); -}); - -require.define("/lib/http.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2012 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; - - var Class = require('./jquery.class').Class; - var logger = require('./log').Logger; - var utils = require('./utils'); - var CookieHandler = require('cookie'); - - var root = exports || this; - var Http = null; - - var queryBuilderMap = { - "5": function(message) { - var query = message.query || {}; - var post = message.post || {}; - var outputMode = query.output_mode || post.output_mode || "json"; - - // If the output mode doesn't start with "json" (e.g. "csv" or - // "xml"), we change it to "json". - if (!utils.startsWith(outputMode, "json")) { - outputMode = "json"; - } - - query.output_mode = outputMode; - - return query; - }, - "4": function(message) { - return message.query || {}; - }, - "default": function(message) { - return queryBuilderMap["5"](message); - }, - "none": function(message) { - return message.query || {}; - } - }; - - /** - * A base class for HTTP abstraction that provides the basic functionality - * for performing GET, POST, DELETE, and REQUEST operations, and provides - * utilities to construct uniform responses. - * - * Base classes should only override `makeRequest` and `parseJSON`. - * - * @class splunkjs.Http - */ - module.exports = root = Http = Class.extend({ /** - * Constructor for `splunkjs.Http`. + * An abstraction over the Splunk HTTP-wire protocol that provides the basic + * functionality for communicating with a Splunk instance over HTTP, handles + * authentication and authorization, and formats HTTP requests (GET, POST, + * and DELETE) in the format that Splunk expects. * - * @constructor - * @return {splunkjs.Http} A new `splunkjs.Http` instance. - * - * @method splunkjs.Http - */ - init: function() { - - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this.get = utils.bind(this, this.get); - this.del = utils.bind(this, this.del); - this.post = utils.bind(this, this.post); - this.request = utils.bind(this, this.request); - this._buildResponse = utils.bind(this, this._buildResponse); - - // Set our default version to "none" - this._setSplunkVersion("none"); - - // Cookie store for cookie based authentication. - this._cookieStore = {}; - }, - - /*!*/ - _setSplunkVersion: function(version) { - this.version = version; - }, - - /** - * Returns all cookies formatted as a string to be put into the Cookie Header. - */ - _getCookieString: function() { - var cookieString = ""; - - utils.forEach(this._cookieStore, function (cookieValue, cookieKey) { - cookieString += cookieKey; - cookieString += '='; - cookieString += cookieValue; - cookieString += '; '; - }); - - return cookieString; - - }, - - /** - * Takes a cookie header and returns an object of form { key: $cookieKey value: $cookieValue } + * @class splunkjs.Context */ - _parseCookieHeader: function(cookieHeader) { - // Returns an object of form { $cookieKey: $cookieValue, $optionalCookieAttributeName: $""value, ... } - var parsedCookieObject = CookieHandler.parse(cookieHeader); - var cookie = {}; - - // This gets the first key value pair into an object and just repeatedly returns thereafter - utils.forEach(parsedCookieObject, function(cookieValue, cookieKey) { - if(cookie.key) { - return; + module.exports = root = Class.extend({ + + /** + * Constructor for `splunkjs.Context`. + * + * @constructor + * @param {splunkjs.Http} http An instance of a `splunkjs.Http` class. + * @param {Object} params A dictionary of optional parameters: + * - `scheme` (_string_): The scheme ("http" or "https") for accessing Splunk. + * - `host` (_string_): The host name (the default is "localhost"). + * - `port` (_integer_): The port number (the default is 8089). + * - `username` (_string_): The Splunk account username, which is used to authenticate the Splunk instance. + * - `password` (_string_): The password, which is used to authenticate the Splunk instance. + * - `owner` (_string_): The owner (username) component of the namespace. + * - `app` (_string_): The app component of the namespace. + * - `sessionKey` (_string_): The current session token. + * - `autologin` (_boolean_): `true` to automatically try to log in again if the session terminates, `false` if not (`true` by default). + * - 'timeout' (_integer): The connection timeout in milliseconds. ('0' by default). + * - `version` (_string_): The version string for Splunk, for example "4.3.2" (the default is "5.0"). + * @return {splunkjs.Context} A new `splunkjs.Context` instance. + * + * @method splunkjs.Context + */ + init: function(http, params) { + if (!(http instanceof Http) && !params) { + // Move over the params + params = http; + http = null; } - cookie.key = cookieKey; - cookie.value = cookieValue; - }); - - return cookie; - }, - - /** - * Performs a GET request. - * - * @param {String} url The URL of the GET request. - * @param {Object} headers An object of headers for this request. - * @param {Object} params Parameters for this request. - * @param {Number} timeout A timeout period. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Http - */ - get: function(url, headers, params, timeout, callback) { - var message = { - method: "GET", - headers: headers, - timeout: timeout, - query: params - }; - - return this.request(url, message, callback); - }, - - /** - * Performs a POST request. - * - * @param {String} url The URL of the POST request. - * @param {Object} headers An object of headers for this request. - * @param {Object} params Parameters for this request. - * @param {Number} timeout A timeout period. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Http - */ - post: function(url, headers, params, timeout, callback) { - headers["Content-Type"] = "application/x-www-form-urlencoded"; - var message = { - method: "POST", - headers: headers, - timeout: timeout, - post: params - }; - - return this.request(url, message, callback); - }, - - /** - * Performs a DELETE request. - * - * @param {String} url The URL of the DELETE request. - * @param {Object} headers An object of headers for this request. - * @param {Object} params Query parameters for this request. - * @param {Number} timeout A timeout period. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Http - */ - del: function(url, headers, params, timeout, callback) { - var message = { - method: "DELETE", - headers: headers, - timeout: timeout, - query: params - }; - - return this.request(url, message, callback); - }, - - /** - * Performs a request. - * - * This function sets up how to handle a response from a request, but - * delegates calling the request to the `makeRequest` subclass. - * - * @param {String} url The encoded URL of the request. - * @param {Object} message An object with values for method, headers, timeout, and encoded body. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Http - * @see makeRequest - */ - request: function(url, message, callback) { - var that = this; - var wrappedCallback = function(response) { - callback = callback || function() {}; - - // Handle cookies if 'set-cookie' header is in the response - - var cookieHeaders = response.response.headers['set-cookie']; - if (cookieHeaders) { - utils.forEach(cookieHeaders, function (cookieHeader) { - var cookie = that._parseCookieHeader(cookieHeader); - that._cookieStore[cookie.key] = cookie.value; - }); + + params = params || {}; + + this.scheme = params.scheme || "https"; + this.host = params.host || "localhost"; + this.port = params.port || 8089; + this.username = params.username || null; + this.password = params.password || null; + this.owner = params.owner; + this.app = params.app; + this.sessionKey = params.sessionKey || ""; + this.authorization = params.authorization || "Splunk"; + this.paths = params.paths || Paths; + this.version = params.version || "default"; + this.timeout = params.timeout || 0; + this.autologin = true; + + // Initialize autologin + // The reason we explicitly check to see if 'autologin' + // is actually set is because we need to distinguish the + // case of it being set to 'false', and it not being set. + // Unfortunately, in JavaScript, these are both false-y + if (params.hasOwnProperty("autologin")) { + this.autologin = params.autologin; } - - // Handle callback - - if (response.status < 400 && response.status !== "abort") { - callback(null, response); + + if (!http) { + // If there is no HTTP implementation set, we check what platform + // we're running on. If we're running in the browser, then complain, + // else, we instantiate NodeHttp. + if (typeof(window) !== 'undefined') { + throw new Error("Http instance required when creating a Context within a browser."); + } + else { + var NodeHttp = require('./platform/node/node_http').NodeHttp; + http = new NodeHttp(); + } } - else { - callback(response); + + // Store the HTTP implementation + this.http = http; + this.http._setSplunkVersion(this.version); + + // Store our full prefix, which is just combining together + // the scheme with the host + var versionPrefix = utils.getWithVersion(this.version, prefixMap); + this.prefix = this.scheme + "://" + this.host + ":" + this.port + versionPrefix; + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this._headers = utils.bind(this, this._headers); + this.fullpath = utils.bind(this, this.fullpath); + this.urlify = utils.bind(this, this.urlify); + this.get = utils.bind(this, this.get); + this.del = utils.bind(this, this.del); + this.post = utils.bind(this, this.post); + this.login = utils.bind(this, this.login); + this._shouldAutoLogin = utils.bind(this, this._shouldAutoLogin); + this._requestWrapper = utils.bind(this, this._requestWrapper); + }, + + /** + * Appends Splunk-specific headers. + * + * @param {Object} headers A dictionary of headers (optional). + * @return {Object} An augmented dictionary of headers. + * + * @method splunkjs.Context + * @private + */ + _headers: function (headers) { + headers = headers || {}; + if (this.sessionKey) { + headers["Authorization"] = this.authorization + " " + this.sessionKey; + } + return headers; + }, + + /*!*/ + _shouldAutoLogin: function() { + return this.username && this.password && this.autologin; + }, + + /*!*/ + /** + * This internal function aids with the autologin feature. + * It takes two parameters: `task`, which is a function describing an + * HTTP request, and `callback`, to be invoked when all is said + * and done. + * + * @param {Function} task A function taking a single argument: `(callback)`. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + */ + _requestWrapper: function(task, callback) { + callback = callback || function() {}; + + var that = this; + var req = null; + + // This is the callback that will be invoked + // if we are currently logged in but our session key + // expired (i.e. we get a 401 response from the server). + // We will only retry once. + var reloginIfNecessary = function(err) { + // If we aborted, ignore it + if (req.wasAborted) { + return; + } + + if (err && err.status === 401 && that._shouldAutoLogin()) { + // If we had an authorization error, we'll try and login + // again, but only once + that.sessionKey = null; + that.login(function(err, success) { + // If we've already aborted the request, + // just do nothing + if (req.wasAborted) { + return; + } + + if (err) { + // If there was an error logging in, send it through + callback(err); + } + else { + // Relogging in was successful, so we execute + // our task again. + task(callback); + } + }); + } + else { + callback.apply(null, arguments); + } + }; + + if (!this._shouldAutoLogin() || this.sessionKey) { + // Since we are not auto-logging in, just execute our task, + // but intercept any 401s so we can login then + req = task(reloginIfNecessary); + return req; } - }; - - var query = utils.getWithVersion(this.version, queryBuilderMap)(message); - var post = message.post || {}; - - var encodedUrl = url + "?" + Http.encode(query); - var body = message.body ? message.body : Http.encode(post); - - var cookieString = that._getCookieString(); - - if (cookieString.length !== 0) { - message.headers["Cookie"] = cookieString; - - // Remove Authorization header - // Splunk will use Authorization header and ignore Cookies if Authorization header is sent - delete message.headers["Authorization"]; - } - - var options = { - method: message.method, - headers: message.headers, - timeout: message.timeout, - body: body - }; - - // Now we can invoke the user-provided HTTP class, - // passing in our "wrapped" callback - return this.makeRequest(encodedUrl, options, wrappedCallback); - }, - - /** - * Encapsulates the client-specific logic for performing a request. This - * function is meant to be overriden by subclasses. - * - * @param {String} url The encoded URL of the request. - * @param {Object} message An object with values for method, headers, timeout, and encoded body. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Http - */ - makeRequest: function(url, message, callback) { - throw new Error("UNDEFINED FUNCTION - OVERRIDE REQUIRED"); - }, - - /** - * Encapsulates the client-specific logic for parsing the JSON response. - * - * @param {String} json The JSON response to parse. - * @return {Object} The parsed JSON. - * - * @method splunkjs.Http - */ - parseJson: function(json) { - throw new Error("UNDEFINED FUNCTION - OVERRIDE REQUIRED"); - }, - - /** - * Generates a unified response with the given parameters. - * - * @param {Object} error An error object, if one exists for the request. - * @param {Object} response The response object. - * @param {Object} data The response data. - * @return {Object} A unified response object. - * - * @method splunkjs.Http - */ - _buildResponse: function(error, response, data) { - var complete_response, json = {}; - - var contentType = null; - if (response && response.headers) { - contentType = utils.trim(response.headers["content-type"] || response.headers["Content-Type"] || response.headers["Content-type"] || response.headers["contentType"]); - } - - if (utils.startsWith(contentType, "application/json") && data) { - try { - json = this.parseJson(data) || {}; + + // OK, so we know that we should try and autologin, + // so we try and login, and if we succeed, execute + // the original task + req = this.login(function(err, success) { + // If we've already aborted the request, + // just do nothing + if (req.wasAborted) { + return; + } + + if (err) { + // If there was an error logging in, send it through + callback(err); + } + else { + // Logging in was successful, so we execute + // our task. + task(callback); + } + }); + + return req; + }, + + /** + * Converts a partial path to a fully-qualified path to a REST endpoint, + * and if necessary includes the namespace owner and app. + * + * @param {String} path The partial path. + * @param {String} namespace The namespace, in the format "_owner_/_app_". + * @return {String} The fully-qualified path. + * + * @method splunkjs.Context + */ + fullpath: function(path, namespace) { + namespace = namespace || {}; + + if (utils.startsWith(path, "/")) { + return path; } - catch(e) { - logger.error("Error in parsing JSON:", data, e); - json = data; + + // If we don't have an app name (explicitly or implicitly), we default to /services/ + if (!namespace.app && !this.app && namespace.sharing !== root.Sharing.SYSTEM) { + return "/services/" + path; } - } - else { - json = data; - } - - if (json) { - logger.printMessages(json.messages); - } - - complete_response = { - response: response, - status: (response ? response.statusCode : 0), - data: json, - error: error - }; - - return complete_response; - } - }); - - /** - * Encodes a dictionary of values into a URL-encoded format. - * - * @example - * - * // should be a=1&b=2&b=3&b=4 - * encode({a: 1, b: [2,3,4]}) - * - * @param {Object} params The parameters to URL encode. - * @return {String} The URL-encoded string. - * - * @function splunkjs.Http - */ - Http.encode = function(params) { - var encodedStr = ""; - - // We loop over all the keys so we encode them. - for (var key in params) { - if (params.hasOwnProperty(key)) { - // Only append the ampersand if we already have - // something encoded, and the last character isn't - // already an ampersand - if (encodedStr && encodedStr[encodedStr.length - 1] !== "&") { - encodedStr = encodedStr + "&"; + + // Get the app and owner, first from the passed in namespace, then the service, + // finally defaulting to wild cards + var owner = namespace.owner || this.owner || "-"; + var app = namespace.app || this.app || "-"; + + namespace.sharing = (namespace.sharing || "").toLowerCase(); + + // Modify the owner and app appropriately based on the sharing parameter + if (namespace.sharing === root.Sharing.APP || namespace.sharing === root.Sharing.GLOBAL) { + owner = "nobody"; } - - // Get the value - var value = params[key]; - - // If it's an array, we loop over each value - // and encode it in the form &key=value[i] - if (value instanceof Array) { - for (var i = 0; i < value.length; i++) { - encodedStr = encodedStr + key + "=" + encodeURIComponent(value[i]) + "&"; - } - } - else if (typeof value === "object") { - for(var innerKey in value) { - if (value.hasOwnProperty(innerKey)) { - var innerValue = value[innerKey]; - encodedStr = encodedStr + key + "=" + encodeURIComponent(value[innerKey]) + "&"; - } + else if (namespace.sharing === root.Sharing.SYSTEM) { + owner = "nobody"; + app = "system"; + } + + return utils.trim("/servicesNS/" + encodeURIComponent(owner) + "/" + encodeURIComponent(app) + "/" + path); + }, + + /** + * Converts a partial path to a fully-qualified URL. + * + * @param {String} path The partial path. + * @return {String} The fully-qualified URL. + * + * @method splunkjs.Context + * @private + */ + urlify: function(path) { + return this.prefix + this.fullpath(path); + }, + + /** + * Authenticates and logs in to a Splunk instance, then stores the + * resulting session key. + * + * @param {Function} callback The function to call when login has finished: `(err, wasSuccessful)`. + * + * @method splunkjs.Context + * @private + */ + login: function(callback) { + var that = this; + var url = this.paths.login; + var params = { + username: this.username, + password: this.password, + cookie : '1' + }; + + callback = callback || function() {}; + var wrappedCallback = function(err, response) { + // Let's make sure that not only did the request succeed, but + // we actually got a non-empty session key back. + var hasSessionKey = !!(!err && response.data && response.data.sessionKey); + + if (err || !hasSessionKey) { + callback(err || "No session key available", false); + } + else { + that.sessionKey = response.data.sessionKey; + callback(null, true); } + }; + + return this.http.post( + this.urlify(url), + this._headers(), + params, + this.timeout, + wrappedCallback + ); + }, + + + /** + * Logs the session out resulting in the removal of all cookies and the + * session key. + * + * @param {Function} callback The function to call when logout has finished: `()`. + * + * @method splunkjs.Context + * @private + */ + logout: function(callback) { + callback = callback || function() {}; + + this.sessionKey = null; + this.http._cookieStore = {}; + callback(); + }, + + /** + * Performs a GET request. + * + * @param {String} path The REST endpoint path of the GET request. + * @param {Object} params The entity-specific parameters for this request. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Context + */ + get: function(path, params, callback) { + var that = this; + var request = function(callback) { + return that.http.get( + that.urlify(path), + that._headers(), + params, + that.timeout, + callback + ); + }; + + return this._requestWrapper(request, callback); + }, + + /** + * Performs a DELETE request. + * + * @param {String} path The REST endpoint path of the DELETE request. + * @param {Object} params The entity-specific parameters for this request. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Context + */ + del: function(path, params, callback) { + var that = this; + var request = function(callback) { + return that.http.del( + that.urlify(path), + that._headers(), + params, + that.timeout, + callback + ); + }; + + return this._requestWrapper(request, callback); + }, + + /** + * Performs a POST request. + * + * @param {String} path The REST endpoint path of the POST request. + * @param {Object} params The entity-specific parameters for this request. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Context + */ + post: function(path, params, callback) { + var that = this; + var request = function(callback) { + return that.http.post( + that.urlify(path), + that._headers(), + params, + that.timeout, + callback + ); + }; + + return this._requestWrapper(request, callback); + }, + + /** + * Issues an arbitrary HTTP request to the REST endpoint path segment. + * + * @param {String} path The REST endpoint path segment (with any query parameters already appended and encoded). + * @param {String} method The HTTP method (can be `GET`, `POST`, or `DELETE`). + * @param {Object} query The entity-specific parameters for this request. + * @param {Object} post A dictionary of POST argument that will get form encoded. + * @param {Object} body The body of the request, mutually exclusive with `post`. + * @param {Object} headers Headers for this request. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Context + */ + request: function(path, method, query, post, body, headers, callback) { + var that = this; + var request = function(callback) { + return that.http.request( + that.urlify(path), + { + method: method, + headers: that._headers(headers), + query: query, + post: post, + body: body, + timeout: that.timeout + }, + callback + ); + }; + + return this._requestWrapper(request, callback); + }, + + /** + * Compares the Splunk server's version to the specified version string. + * Returns -1 if (this.version < otherVersion), + * 0 if (this.version == otherVersion), + * 1 if (this.version > otherVersion). + * + * @param {String} otherVersion The other version string, for example "5.0". + * + * @method splunkjs.Context + */ + versionCompare: function(otherVersion) { + var thisVersion = this.version; + if (thisVersion === "default") { + thisVersion = "5.0"; } - else { - // If it's not an array, we just encode it - encodedStr = encodedStr + key + "=" + encodeURIComponent(value); + + var components1 = thisVersion.split("."); + var components2 = otherVersion.split("."); + var numComponents = Math.max(components1.length, components2.length); + + for (var i = 0; i < numComponents; i++) { + var c1 = (i < components1.length) ? parseInt(components1[i], 10) : 0; + var c2 = (i < components2.length) ? parseInt(components2[i], 10) : 0; + if (c1 < c2) { + return -1; + } else if (c1 > c2) { + return 1; + } } + return 0; } - } - - if (encodedStr[encodedStr.length - 1] === '&') { - encodedStr = encodedStr.substr(0, encodedStr.length - 1); - } - - return encodedStr; - }; -})(); - -}); - -require.define("/node_modules/cookie/package.json", function (require, module, exports, __dirname, __filename) { -module.exports = {} -}); - -require.define("/node_modules/cookie/index.js", function (require, module, exports, __dirname, __filename) { -/*! - * cookie - * Copyright(c) 2012-2014 Roman Shtylman - * MIT Licensed - */ - -/** - * Module exports. - * @public - */ - -exports.parse = parse; -exports.serialize = serialize; - -/** - * Module variables. - * @private - */ - -var decode = decodeURIComponent; -var encode = encodeURIComponent; -var pairSplitRegExp = /; */; - -/** - * Parse a cookie header. - * - * Parse the given cookie header string into an object - * The object has the various cookies as keys(names) => values - * - * @param {string} str - * @param {object} [options] - * @return {object} - * @public - */ - -function parse(str, options) { - if (typeof str !== 'string') { - throw new TypeError('argument str must be a string'); - } - - var obj = {} - var opt = options || {}; - var pairs = str.split(pairSplitRegExp); - var dec = opt.decode || decode; - - pairs.forEach(function(pair) { - var eq_idx = pair.indexOf('=') - - // skip things that don't look like key=value - if (eq_idx < 0) { - return; - } - - var key = pair.substr(0, eq_idx).trim() - var val = pair.substr(++eq_idx, pair.length).trim(); - - // quoted values - if ('"' == val[0]) { - val = val.slice(1, -1); - } - - // only assign once - if (undefined == obj[key]) { - obj[key] = tryDecode(val, dec); - } - }); - - return obj; -} - -/** - * Serialize data into a cookie header. - * - * Serialize the a name value pair into a cookie string suitable for - * http headers. An optional options object specified cookie parameters. - * - * serialize('foo', 'bar', { httpOnly: true }) - * => "foo=bar; httpOnly" - * - * @param {string} name - * @param {string} val - * @param {object} [options] - * @return {string} - * @public - */ - -function serialize(name, val, options) { - var opt = options || {}; - var enc = opt.encode || encode; - var pairs = [name + '=' + enc(val)]; - - if (null != opt.maxAge) { - var maxAge = opt.maxAge - 0; - if (isNaN(maxAge)) throw new Error('maxAge should be a Number'); - pairs.push('Max-Age=' + maxAge); - } - - if (opt.domain) pairs.push('Domain=' + opt.domain); - if (opt.path) pairs.push('Path=' + opt.path); - if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString()); - if (opt.httpOnly) pairs.push('HttpOnly'); - if (opt.secure) pairs.push('Secure'); - if (opt.firstPartyOnly) pairs.push('First-Party-Only'); - - return pairs.join('; '); -} - -/** - * Try decoding a string using a decoding function. - * - * @param {string} str - * @param {function} decode - * @private - */ - -function tryDecode(str, decode) { - try { - return decode(str); - } catch (e) { - return str; - } -} - -}); - -require.define("/lib/service.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2014 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; + }); - var Context = require('./context'); - var Http = require('./http'); - var Async = require('./async'); - var Paths = require('./paths').Paths; - var Class = require('./jquery.class').Class; - var utils = require('./utils'); + /*!*/ + root.Sharing = { + USER: "user", + APP: "app", + GLOBAL: "global", + SYSTEM: "system" + }; + })(); - var root = exports || this; - var Service = null; + }); + + require.define("/lib/paths.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2012 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; + + var root = exports || this; + + // A list of the Splunk REST API endpoint paths + root.Paths = { + apps: "/services/apps/local", + capabilities: "authorization/capabilities", + configurations: "configs", + dataModels: "datamodel/model", + deploymentClient: "deployment/client", + deploymentServers: "deployment/server", + deploymentServerClasses: "deployment/serverclass", + deploymentTenants: "deployment/tenants", + eventTypes: "saved/eventtypes", + firedAlerts: "alerts/fired_alerts", + indexes: "data/indexes", + info: "/services/server/info", + inputs: null, + jobs: "search/jobs", + licenseGroups: "licenser/groups", + licenseMessages: "licenser/messages", + licensePools: "licenser/pools", + licenseSlaves: "licenser/slaves", + licenseStacks: "licenser/stacks", + licenses: "licenser/licenses", + loggers: "server/logger", + login: "/services/auth/login", + messages: "messages", + passwords: "admin/passwords", + parser: "search/parser", + pivot: "datamodel/pivot", + properties: "properties", + roles: "authorization/roles", + savedSearches: "saved/searches", + settings: "server/settings", + storagePasswords: "storage/passwords", + users: "/services/authentication/users", + typeahead: "search/typeahead", + views: "data/ui/views", + + currentUser: "/services/authentication/current-context", + submitEvent: "/services/receivers/simple" + }; + })(); - /** - * Contains functionality common to Splunk Enterprise and Splunk Storm. - * - * This class is an implementation detail and is therefore SDK-private. - * - * @class splunkjs.private.BaseService - * @extends splunkjs.Context - */ - var BaseService = Context.extend({ - init: function() { - this._super.apply(this, arguments); - } }); - - /** - * Provides a root access point to Splunk functionality with typed access to - * Splunk resources such as searches, indexes, inputs, and more. Provides - * methods to authenticate and create specialized instances of the service. - * - * @class splunkjs.Service - * @extends splunkjs.private.BaseService + + require.define("/lib/jquery.class.js", function (require, module, exports, __dirname, __filename) { + /*! Simple JavaScript Inheritance + * By John Resig http://ejohn.org/ + * MIT Licensed. + * Inspired by base2 and Prototype */ - module.exports = root = Service = BaseService.extend({ - /** - * Constructor for `splunkjs.Service`. - * - * @constructor - * @param {splunkjs.Http} http An instance of a `splunkjs.Http` class. - * @param {Object} params A dictionary of optional parameters: - * - `scheme` (_string_): The scheme ("http" or "https") for accessing Splunk. - * - `host` (_string_): The host name (the default is "localhost"). - * - `port` (_integer_): The port number (the default is 8089). - * - `username` (_string_): The Splunk account username, which is used to authenticate the Splunk instance. - * - `password` (_string_): The password, which is used to authenticate the Splunk instance. - * - `owner` (_string_): The owner (username) component of the namespace. - * - `app` (_string_): The app component of the namespace. - * - `sessionKey` (_string_): The current session token. - * - `autologin` (_boolean_): `true` to automatically try to log in again if the session terminates, `false` if not (`true` by default). - * - `version` (_string_): The version string for Splunk, for example "4.3.2" (the default is "5.0"). - * @return {splunkjs.Service} A new `splunkjs.Service` instance. - * - * @method splunkjs.Service - */ - init: function() { - this._super.apply(this, arguments); - - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this.specialize = utils.bind(this, this.specialize); - this.apps = utils.bind(this, this.apps); - this.configurations = utils.bind(this, this.configurations); - this.indexes = utils.bind(this, this.indexes); - this.savedSearches = utils.bind(this, this.savedSearches); - this.jobs = utils.bind(this, this.jobs); - this.users = utils.bind(this, this.users); - this.currentUser = utils.bind(this, this.currentUser); - this.views = utils.bind(this, this.views); - this.firedAlertGroups = utils.bind(this, this.firedAlertGroups); - this.dataModels = utils.bind(this, this.dataModels); - }, - - /** - * Creates a specialized version of the current `Service` instance for - * a specific namespace context. - * - * @example - * - * var svc = ...; - * var newService = svc.specialize("myuser", "unix"); - * - * @param {String} owner The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * @param {String} app The app context for this resource (such as "search"). The "-" wildcard means all apps. - * @return {splunkjs.Service} The specialized `Service` instance. - * - * @method splunkjs.Service - */ - specialize: function(owner, app) { - return new Service(this.http, { - scheme: this.scheme, - host: this.host, - port: this.port, - username: this.username, - password: this.password, - owner: owner, - app: app, - sessionKey: this.sessionKey, - version: this.version - }); - }, - - /** - * Gets the `Applications` collection, which allows you to - * list installed apps and retrieve information about them. - * - * @example - * - * // List installed apps - * var apps = svc.apps(); - * apps.fetch(function(err) { console.log(apps.list()); }); - * - * @return {splunkjs.Service.Collection} The `Applications` collection. - * - * @endpoint apps/local - * @method splunkjs.Service - * @see splunkjs.Service.Applications - */ - apps: function() { - return new root.Applications(this); - }, + (function(){ + var root = exports || this; + + var initializing = false; + var fnTest = (/xyz/.test(function() { return xyz; }) ? /\b_super\b/ : /.*/); + // The base Class implementation (does nothing) + root.Class = function(){}; + // Create a new Class that inherits from this class + root.Class.extend = function(prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn){ + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if ( !initializing && this.init ) + this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + }; + })(); + }); + + require.define("/lib/http.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2012 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; + + var Class = require('./jquery.class').Class; + var logger = require('./log').Logger; + var utils = require('./utils'); + var CookieHandler = require('cookie'); + + var root = exports || this; + var Http = null; + + var queryBuilderMap = { + "5": function(message) { + var query = message.query || {}; + var post = message.post || {}; + var outputMode = query.output_mode || post.output_mode || "json"; + + // If the output mode doesn't start with "json" (e.g. "csv" or + // "xml"), we change it to "json". + if (!utils.startsWith(outputMode, "json")) { + outputMode = "json"; + } + + query.output_mode = outputMode; + + return query; + }, + "4": function(message) { + return message.query || {}; + }, + "default": function(message) { + return queryBuilderMap["5"](message); + }, + "none": function(message) { + return message.query || {}; + } + }; + /** - * Gets the `Configurations` collection, which lets you - * create, list, and retrieve configuration (.conf) files. + * A base class for HTTP abstraction that provides the basic functionality + * for performing GET, POST, DELETE, and REQUEST operations, and provides + * utilities to construct uniform responses. * - * @example - * - * // List all properties in the 'props.conf' file - * var files = svc.configurations(); - * files.item("props", function(err, propsFile) { - * propsFile.fetch(function(err, props) { - * console.log(props.properties()); - * }); - * }); - * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Configurations} The `Configurations` collection. + * Base classes should only override `makeRequest` and `parseJSON`. * - * @endpoint configs - * @method splunkjs.Service - * @see splunkjs.Service.Configurations + * @class splunkjs.Http */ - configurations: function(namespace) { - return new root.Configurations(this, namespace); - }, - - /** - * Gets the `Indexes` collection, which lets you create, - * list, and update indexes. - * - * @example - * - * // Check if we have an _internal index - * var indexes = svc.indexes(); - * indexes.fetch(function(err, indexes) { - * var index = indexes.item("_internal"); - * console.log("Was index found: " + !!index); - * // `index` is an Index object. - * }); - * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Indexes} The `Indexes` collection. - * - * @endpoint data/indexes - * @method splunkjs.Service - * @see splunkjs.Service.Indexes - */ - indexes: function(namespace) { - return new root.Indexes(this, namespace); - }, - - /** - * Gets the `SavedSearches` collection, which lets you - * create, list, and update saved searches. - * - * @example - * - * // List all # of saved searches - * var savedSearches = svc.savedSearches(); - * savedSearches.fetch(function(err, savedSearches) { - * console.log("# Of Saved Searches: " + savedSearches.list().length); - * }); - * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.SavedSearches} The `SavedSearches` collection. - * - * @endpoint saved/searches - * @method splunkjs.Service - * @see splunkjs.Service.SavedSearches - */ - savedSearches: function(namespace) { - return new root.SavedSearches(this, namespace); - }, - - /** - * Gets the `StoragePasswords` collection, which lets you - * create, list, and update storage passwords. - * - * @example - * - * // List all # of storage passwords - * var storagePasswords = svc.storagePasswords(); - * storagePasswords.fetch(function(err, storagePasswords) { - * console.log("# of Storage Passwords: " + storagePasswords.list().length); - * }); - * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.StoragePasswords} The `StoragePasswords` collection. - * - * @endpoint storage/passwords - * @method splunkjs.Service - * @see splunkjs.Service.StoragePasswords - */ - storagePasswords: function(namespace) { - return new root.StoragePasswords(this, namespace); - }, - - /** - * Gets the `FiredAlertGroupCollection` collection, which lets you - * list alert groups. - * - * @example - * - * // List all # of fired alert groups - * var firedAlertGroups = svc.firedAlertGroups(); - * firedAlertGroups.fetch(function(err, firedAlertGroups) { - * console.log("# of alert groups: " + firedAlertGroups.list().length); - * }); - * - * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.FiredAlertGroupCollection} The `FiredAlertGroupCollection` collection. - * - * @endpoint saved/searches - * @method splunkjs.Service - * @see splunkjs.Service.FiredAlertGroupCollection - */ - firedAlertGroups: function(namespace) { - return new root.FiredAlertGroupCollection(this, namespace); - }, - + module.exports = root = Http = Class.extend({ + /** + * Constructor for `splunkjs.Http`. + * + * @constructor + * @return {splunkjs.Http} A new `splunkjs.Http` instance. + * + * @method splunkjs.Http + */ + init: function() { + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this.get = utils.bind(this, this.get); + this.del = utils.bind(this, this.del); + this.post = utils.bind(this, this.post); + this.request = utils.bind(this, this.request); + this._buildResponse = utils.bind(this, this._buildResponse); + + // Set our default version to "none" + this._setSplunkVersion("none"); + + // Cookie store for cookie based authentication. + this._cookieStore = {}; + }, + + /*!*/ + _setSplunkVersion: function(version) { + this.version = version; + }, + + /** + * Returns all cookies formatted as a string to be put into the Cookie Header. + */ + _getCookieString: function() { + var cookieString = ""; + + utils.forEach(this._cookieStore, function (cookieValue, cookieKey) { + cookieString += cookieKey; + cookieString += '='; + cookieString += cookieValue; + cookieString += '; '; + }); + + return cookieString; + + }, + + /** + * Takes a cookie header and returns an object of form { key: $cookieKey value: $cookieValue } + */ + _parseCookieHeader: function(cookieHeader) { + // Returns an object of form { $cookieKey: $cookieValue, $optionalCookieAttributeName: $""value, ... } + var parsedCookieObject = CookieHandler.parse(cookieHeader); + var cookie = {}; + + // This gets the first key value pair into an object and just repeatedly returns thereafter + utils.forEach(parsedCookieObject, function(cookieValue, cookieKey) { + if(cookie.key) { + return; + } + cookie.key = cookieKey; + cookie.value = cookieValue; + }); + + return cookie; + }, + + /** + * Performs a GET request. + * + * @param {String} url The URL of the GET request. + * @param {Object} headers An object of headers for this request. + * @param {Object} params Parameters for this request. + * @param {Number} timeout A timeout period. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Http + */ + get: function(url, headers, params, timeout, callback) { + var message = { + method: "GET", + headers: headers, + timeout: timeout, + query: params + }; + + return this.request(url, message, callback); + }, + + /** + * Performs a POST request. + * + * @param {String} url The URL of the POST request. + * @param {Object} headers An object of headers for this request. + * @param {Object} params Parameters for this request. + * @param {Number} timeout A timeout period. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Http + */ + post: function(url, headers, params, timeout, callback) { + headers["Content-Type"] = "application/x-www-form-urlencoded"; + var message = { + method: "POST", + headers: headers, + timeout: timeout, + post: params + }; + + return this.request(url, message, callback); + }, + + /** + * Performs a DELETE request. + * + * @param {String} url The URL of the DELETE request. + * @param {Object} headers An object of headers for this request. + * @param {Object} params Query parameters for this request. + * @param {Number} timeout A timeout period. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Http + */ + del: function(url, headers, params, timeout, callback) { + var message = { + method: "DELETE", + headers: headers, + timeout: timeout, + query: params + }; + + return this.request(url, message, callback); + }, + + /** + * Performs a request. + * + * This function sets up how to handle a response from a request, but + * delegates calling the request to the `makeRequest` subclass. + * + * @param {String} url The encoded URL of the request. + * @param {Object} message An object with values for method, headers, timeout, and encoded body. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Http + * @see makeRequest + */ + request: function(url, message, callback) { + var that = this; + var wrappedCallback = function(response) { + callback = callback || function() {}; + + // Handle cookies if 'set-cookie' header is in the response + + var cookieHeaders = response.response.headers['set-cookie']; + if (cookieHeaders) { + utils.forEach(cookieHeaders, function (cookieHeader) { + var cookie = that._parseCookieHeader(cookieHeader); + that._cookieStore[cookie.key] = cookie.value; + }); + } + + // Handle callback + + if (response.status < 400 && response.status !== "abort") { + callback(null, response); + } + else { + callback(response); + } + }; + + var query = utils.getWithVersion(this.version, queryBuilderMap)(message); + var post = message.post || {}; + + var encodedUrl = url + "?" + Http.encode(query); + var body = message.body ? message.body : Http.encode(post); + + var cookieString = that._getCookieString(); + + if (cookieString.length !== 0) { + message.headers["Cookie"] = cookieString; + + // Remove Authorization header + // Splunk will use Authorization header and ignore Cookies if Authorization header is sent + delete message.headers["Authorization"]; + } + + var options = { + method: message.method, + headers: message.headers, + timeout: message.timeout, + body: body + }; + + // Now we can invoke the user-provided HTTP class, + // passing in our "wrapped" callback + return this.makeRequest(encodedUrl, options, wrappedCallback); + }, + + /** + * Encapsulates the client-specific logic for performing a request. This + * function is meant to be overriden by subclasses. + * + * @param {String} url The encoded URL of the request. + * @param {Object} message An object with values for method, headers, timeout, and encoded body. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Http + */ + makeRequest: function(url, message, callback) { + throw new Error("UNDEFINED FUNCTION - OVERRIDE REQUIRED"); + }, + + /** + * Encapsulates the client-specific logic for parsing the JSON response. + * + * @param {String} json The JSON response to parse. + * @return {Object} The parsed JSON. + * + * @method splunkjs.Http + */ + parseJson: function(json) { + throw new Error("UNDEFINED FUNCTION - OVERRIDE REQUIRED"); + }, + + /** + * Generates a unified response with the given parameters. + * + * @param {Object} error An error object, if one exists for the request. + * @param {Object} response The response object. + * @param {Object} data The response data. + * @return {Object} A unified response object. + * + * @method splunkjs.Http + */ + _buildResponse: function(error, response, data) { + var complete_response, json = {}; + + var contentType = null; + if (response && response.headers) { + contentType = utils.trim(response.headers["content-type"] || response.headers["Content-Type"] || response.headers["Content-type"] || response.headers["contentType"]); + } + + if (utils.startsWith(contentType, "application/json") && data) { + try { + json = this.parseJson(data) || {}; + } + catch(e) { + logger.error("Error in parsing JSON:", data, e); + json = data; + } + } + else { + json = data; + } + + if (json) { + logger.printMessages(json.messages); + } + + complete_response = { + response: response, + status: (response ? response.statusCode : 0), + data: json, + error: error + }; + + return complete_response; + } + }); + /** - * Gets the `Jobs` collection, which lets you create, list, - * and retrieve search jobs. + * Encodes a dictionary of values into a URL-encoded format. * * @example * - * // List all job IDs - * var jobs = svc.jobs(); - * jobs.fetch(function(err, jobs) { - * var list = jobs.list(); - * for(var i = 0; i < list.length; i++) { - * console.log("Job " + (i+1) + ": " + list[i].sid); - * } - * }); + * // should be a=1&b=2&b=3&b=4 + * encode({a: 1, b: [2,3,4]}) * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Jobs} The `Jobs` collection. + * @param {Object} params The parameters to URL encode. + * @return {String} The URL-encoded string. * - * @endpoint search/jobs - * @method splunkjs.Service - * @see splunkjs.Service.Jobs + * @function splunkjs.Http */ - jobs: function(namespace) { - return new root.Jobs(this, namespace); - }, + Http.encode = function(params) { + var encodedStr = ""; + + // We loop over all the keys so we encode them. + for (var key in params) { + if (params.hasOwnProperty(key)) { + // Only append the ampersand if we already have + // something encoded, and the last character isn't + // already an ampersand + if (encodedStr && encodedStr[encodedStr.length - 1] !== "&") { + encodedStr = encodedStr + "&"; + } + + // Get the value + var value = params[key]; + + // If it's an array, we loop over each value + // and encode it in the form &key=value[i] + if (value instanceof Array) { + for (var i = 0; i < value.length; i++) { + encodedStr = encodedStr + key + "=" + encodeURIComponent(value[i]) + "&"; + } + } + else if (typeof value === "object") { + for(var innerKey in value) { + if (value.hasOwnProperty(innerKey)) { + var innerValue = value[innerKey]; + encodedStr = encodedStr + key + "=" + encodeURIComponent(value[innerKey]) + "&"; + } + } + } + else { + // If it's not an array, we just encode it + encodedStr = encodedStr + key + "=" + encodeURIComponent(value); + } + } + } + + if (encodedStr[encodedStr.length - 1] === '&') { + encodedStr = encodedStr.substr(0, encodedStr.length - 1); + } + + return encodedStr; + }; + })(); + + }); + + require.define("/node_modules/cookie/package.json", function (require, module, exports, __dirname, __filename) { + module.exports = {} + }); + + require.define("/node_modules/cookie/index.js", function (require, module, exports, __dirname, __filename) { + /*! + * cookie + * Copyright(c) 2012-2014 Roman Shtylman + * MIT Licensed + */ + + /** + * Module exports. + * @public + */ + + exports.parse = parse; + exports.serialize = serialize; + + /** + * Module variables. + * @private + */ + + var decode = decodeURIComponent; + var encode = encodeURIComponent; + var pairSplitRegExp = /; */; + + /** + * Parse a cookie header. + * + * Parse the given cookie header string into an object + * The object has the various cookies as keys(names) => values + * + * @param {string} str + * @param {object} [options] + * @return {object} + * @public + */ + + function parse(str, options) { + if (typeof str !== 'string') { + throw new TypeError('argument str must be a string'); + } + + var obj = {} + var opt = options || {}; + var pairs = str.split(pairSplitRegExp); + var dec = opt.decode || decode; + + pairs.forEach(function(pair) { + var eq_idx = pair.indexOf('=') + + // skip things that don't look like key=value + if (eq_idx < 0) { + return; + } + + var key = pair.substr(0, eq_idx).trim() + var val = pair.substr(++eq_idx, pair.length).trim(); + + // quoted values + if ('"' == val[0]) { + val = val.slice(1, -1); + } + + // only assign once + if (undefined == obj[key]) { + obj[key] = tryDecode(val, dec); + } + }); + + return obj; + } + + /** + * Serialize data into a cookie header. + * + * Serialize the a name value pair into a cookie string suitable for + * http headers. An optional options object specified cookie parameters. + * + * serialize('foo', 'bar', { httpOnly: true }) + * => "foo=bar; httpOnly" + * + * @param {string} name + * @param {string} val + * @param {object} [options] + * @return {string} + * @public + */ + + function serialize(name, val, options) { + var opt = options || {}; + var enc = opt.encode || encode; + var pairs = [name + '=' + enc(val)]; + + if (null != opt.maxAge) { + var maxAge = opt.maxAge - 0; + if (isNaN(maxAge)) throw new Error('maxAge should be a Number'); + pairs.push('Max-Age=' + maxAge); + } + + if (opt.domain) pairs.push('Domain=' + opt.domain); + if (opt.path) pairs.push('Path=' + opt.path); + if (opt.expires) pairs.push('Expires=' + opt.expires.toUTCString()); + if (opt.httpOnly) pairs.push('HttpOnly'); + if (opt.secure) pairs.push('Secure'); + if (opt.firstPartyOnly) pairs.push('First-Party-Only'); + + return pairs.join('; '); + } + + /** + * Try decoding a string using a decoding function. + * + * @param {string} str + * @param {function} decode + * @private + */ + + function tryDecode(str, decode) { + try { + return decode(str); + } catch (e) { + return str; + } + } + + }); + + require.define("/lib/service.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2014 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; - /** - * Gets the `DataModels` collection, which lets you create, list, - * and retrieve data models. - * - * @endpoint datamodel/model - * @method splunkjs.Service - * @see splunkjs.Service.DataModels - */ - dataModels: function(namespace) { - return new root.DataModels(this, namespace); - }, - - /** - * Gets the `Users` collection, which lets you create, - * list, and retrieve users. - * - * @example - * - * // List all usernames - * var users = svc.users(); - * users.fetch(function(err, users) { - * var list = users.list(); - * for(var i = 0; i < list.length; i++) { - * console.log("User " + (i+1) + ": " + list[i].properties().name); - * } - * }); - * - * @return {splunkjs.Service.Users} The `Users` collection. - * - * @endpoint authorization/users - * @method splunkjs.Service - * @see splunkjs.Service.Users - */ - users: function() { - return new root.Users(this); - }, + var Context = require('./context'); + var Http = require('./http'); + var Async = require('./async'); + var Paths = require('./paths').Paths; + var Class = require('./jquery.class').Class; + var utils = require('./utils'); - /** - * Gets the `Views` collection, which lets you create, - * list, and retrieve views (custom UIs built in Splunk's app framework). - * - * @example - * - * // List all views - * var views = svc.views(); - * views.fetch(function(err, views) { - * var list = views.list(); - * for(var i = 0; i < list.length; i++) { - * console.log("View " + (i+1) + ": " + list[i].properties().name); - * } - * }); - * - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Views} The `Views` collection. - * - * @endpoint data/ui/views - * @method splunkjs.Service - * @see splunkjs.Service.Views - */ - views: function(namespace) { - return new root.Views(this, namespace); - }, + var root = exports || this; + var Service = null; /** - * Creates a search job with a given search query and optional parameters, including `exec_mode` to specify the type of search: - * - * - Use `exec_mode=normal` to return a search job ID immediately (default). - * Poll for completion to find out when you can retrieve search results. - * - * - Use `exec_mode=blocking` to return the search job ID when the search has finished. + * Contains functionality common to Splunk Enterprise and Splunk Storm. * - * To run a oneshot search, which does not create a job but rather returns the search results, use `Service.oneshotSearch`. - * - * @example - * - * service.search("search ERROR", {id: "myjob_123"}, function(err, newJob) { - * console.log("CREATED": newJob.sid); - * }); - * - * @param {String} query The search query. - * @param {Object} params A dictionary of properties for the job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @param {Function} callback A function to call with the created job: `(err, createdJob)`. - * - * @endpoint search/jobs - * @method splunkjs.Service - */ - search: function(query, params, namespace, callback) { - if (!callback && utils.isFunction(namespace)) { - callback = namespace; - namespace = null; - } - - var jobs = new root.Jobs(this, namespace); - return jobs.search(query, params, callback); - }, - - /** - * A convenience method to get a `Job` by its sid. - * - * @param {String} sid The search ID for a search job. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @param {Function} callback A function to call with the created job: `(err, job)`. - * - * @endpoint search/jobs - * @method splunkjs.Service + * This class is an implementation detail and is therefore SDK-private. + * + * @class splunkjs.private.BaseService + * @extends splunkjs.Context */ - getJob: function(sid, namespace, callback) { - if (!callback && utils.isFunction(namespace)) { - callback = namespace; - namespace = null; + var BaseService = Context.extend({ + init: function() { + this._super.apply(this, arguments); } - var job = new root.Job(this, sid, namespace); - return job.fetch({}, callback); - }, - + }); + /** - * Creates a oneshot search from a given search query and optional parameters. - * - * @example + * Provides a root access point to Splunk functionality with typed access to + * Splunk resources such as searches, indexes, inputs, and more. Provides + * methods to authenticate and create specialized instances of the service. * - * service.oneshotSearch("search ERROR", {id: "myjob_123"}, function(err, results) { - * console.log("RESULT FIELDS": results.fields); - * }); - * - * @param {String} query The search query. - * @param {Object} params A dictionary of properties for the search: - * - `output_mode` (_string_): Specifies the output format of the results (XML, JSON, or CSV). - * - `earliest_time` (_string_): Specifies the earliest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. - * - `latest_time` (_string_): Specifies the latest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. - * - `rf` (_string_): Specifies one or more fields to add to the search. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @param {Function} callback A function to call with the results of the search: `(err, results)`. - * - * @endpoint search/jobs - * @method splunkjs.Service + * @class splunkjs.Service + * @extends splunkjs.private.BaseService */ - oneshotSearch: function(query, params, namespace, callback) { - if (!callback && utils.isFunction(namespace)) { - callback = namespace; - namespace = null; - } + module.exports = root = Service = BaseService.extend({ + /** + * Constructor for `splunkjs.Service`. + * + * @constructor + * @param {splunkjs.Http} http An instance of a `splunkjs.Http` class. + * @param {Object} params A dictionary of optional parameters: + * - `scheme` (_string_): The scheme ("http" or "https") for accessing Splunk. + * - `host` (_string_): The host name (the default is "localhost"). + * - `port` (_integer_): The port number (the default is 8089). + * - `username` (_string_): The Splunk account username, which is used to authenticate the Splunk instance. + * - `password` (_string_): The password, which is used to authenticate the Splunk instance. + * - `owner` (_string_): The owner (username) component of the namespace. + * - `app` (_string_): The app component of the namespace. + * - `sessionKey` (_string_): The current session token. + * - `autologin` (_boolean_): `true` to automatically try to log in again if the session terminates, `false` if not (`true` by default). + * - `version` (_string_): The version string for Splunk, for example "4.3.2" (the default is "5.0"). + * @return {splunkjs.Service} A new `splunkjs.Service` instance. + * + * @method splunkjs.Service + */ + init: function() { + this._super.apply(this, arguments); + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this.specialize = utils.bind(this, this.specialize); + this.apps = utils.bind(this, this.apps); + this.configurations = utils.bind(this, this.configurations); + this.indexes = utils.bind(this, this.indexes); + this.savedSearches = utils.bind(this, this.savedSearches); + this.jobs = utils.bind(this, this.jobs); + this.users = utils.bind(this, this.users); + this.currentUser = utils.bind(this, this.currentUser); + this.views = utils.bind(this, this.views); + this.firedAlertGroups = utils.bind(this, this.firedAlertGroups); + this.dataModels = utils.bind(this, this.dataModels); + }, - var jobs = new root.Jobs(this, namespace); - return jobs.oneshotSearch(query, params, callback); - }, - - /** - * Gets the user that is currently logged in. - * - * @example - * - * service.currentUser(function(err, user) { - * console.log("Real name: ", user.properties().realname); - * }); - * - * @param {Function} callback A function to call with the user instance: `(err, user)`. - * @return {splunkjs.Service.currentUser} The `User`. - * - * @endpoint authorization/current-context - * @method splunkjs.Service - */ - currentUser: function(callback) { - callback = callback || function() {}; + /** + * Creates a specialized version of the current `Service` instance for + * a specific namespace context. + * + * @example + * + * var svc = ...; + * var newService = svc.specialize("myuser", "unix"); + * + * @param {String} owner The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * @param {String} app The app context for this resource (such as "search"). The "-" wildcard means all apps. + * @return {splunkjs.Service} The specialized `Service` instance. + * + * @method splunkjs.Service + */ + specialize: function(owner, app) { + return new Service(this.http, { + scheme: this.scheme, + host: this.host, + port: this.port, + username: this.username, + password: this.password, + owner: owner, + app: app, + sessionKey: this.sessionKey, + version: this.version + }); + }, - var that = this; - var req = this.get(Paths.currentUser, {}, function(err, response) { - if (err) { - callback(err); - } - else { - var username = response.data.entry[0].content.username; - var user = new root.User(that, username); - user.fetch(function() { - if (req.wasAborted) { - return; // aborted, so ignore - } - else { - callback.apply(null, arguments); - } - }); - } - }); + /** + * Gets the `Applications` collection, which allows you to + * list installed apps and retrieve information about them. + * + * @example + * + * // List installed apps + * var apps = svc.apps(); + * apps.fetch(function(err) { console.log(apps.list()); }); + * + * @return {splunkjs.Service.Collection} The `Applications` collection. + * + * @endpoint apps/local + * @method splunkjs.Service + * @see splunkjs.Service.Applications + */ + apps: function() { + return new root.Applications(this); + }, - return req; - }, - - /** - * Gets configuration information about the server. - * - * @example - * - * service.serverInfo(function(err, info) { - * console.log("Splunk Version: ", info.properties().version); - * }); - * - * @param {Function} callback A function to call with the server info: `(err, info)`. - * - * @endpoint server/info - * @method splunkjs.Service - */ - serverInfo: function(callback) { - callback = callback || function() {}; + /** + * Gets the `Configurations` collection, which lets you + * create, list, and retrieve configuration (.conf) files. + * + * @example + * + * // List all properties in the 'props.conf' file + * var files = svc.configurations(); + * files.item("props", function(err, propsFile) { + * propsFile.fetch(function(err, props) { + * console.log(props.properties()); + * }); + * }); + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Configurations} The `Configurations` collection. + * + * @endpoint configs + * @method splunkjs.Service + * @see splunkjs.Service.Configurations + */ + configurations: function(namespace) { + return new root.Configurations(this, namespace); + }, - var serverInfo = new root.ServerInfo(this); - return serverInfo.fetch(callback); - }, - - /** - * Parses a search query. - * - * @example - * - * service.parse("search index=_internal | head 1", function(err, parse) { - * console.log("Commands: ", parse.commands); - * }); - * - * @param {String} query The search query to parse. - * @param {Object} params An object of options for the parser: - * - `enable_lookups` (_boolean_): If `true`, performs reverse lookups to expand the search expression. - * - `output_mode` (_string_): The output format (XML or JSON). - * - `parse_only` (_boolean_): If `true`, disables the expansion of search due to evaluation of subsearches, time term expansion, lookups, tags, eventtypes, and sourcetype alias. - * - `reload_macros` (_boolean_): If `true`, reloads macro definitions from macros.conf. - * @param {Function} callback A function to call with the parse info: `(err, parse)`. - * - * @endpoint search/parser - * @method splunkjs.Service - */ - parse: function(query, params, callback) { - if (!callback && utils.isFunction(params)) { - callback = params; - params = {}; - } + /** + * Gets the `Indexes` collection, which lets you create, + * list, and update indexes. + * + * @example + * + * // Check if we have an _internal index + * var indexes = svc.indexes(); + * indexes.fetch(function(err, indexes) { + * var index = indexes.item("_internal"); + * console.log("Was index found: " + !!index); + * // `index` is an Index object. + * }); + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Indexes} The `Indexes` collection. + * + * @endpoint data/indexes + * @method splunkjs.Service + * @see splunkjs.Service.Indexes + */ + indexes: function(namespace) { + return new root.Indexes(this, namespace); + }, - callback = callback || function() {}; - params = params || {}; + /** + * Gets the `SavedSearches` collection, which lets you + * create, list, and update saved searches. + * + * @example + * + * // List all # of saved searches + * var savedSearches = svc.savedSearches(); + * savedSearches.fetch(function(err, savedSearches) { + * console.log("# Of Saved Searches: " + savedSearches.list().length); + * }); + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.SavedSearches} The `SavedSearches` collection. + * + * @endpoint saved/searches + * @method splunkjs.Service + * @see splunkjs.Service.SavedSearches + */ + savedSearches: function(namespace) { + return new root.SavedSearches(this, namespace); + }, - params.q = query; + /** + * Gets the `StoragePasswords` collection, which lets you + * create, list, and update storage passwords. + * + * @example + * + * // List all # of storage passwords + * var storagePasswords = svc.storagePasswords(); + * storagePasswords.fetch(function(err, storagePasswords) { + * console.log("# of Storage Passwords: " + storagePasswords.list().length); + * }); + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.StoragePasswords} The `StoragePasswords` collection. + * + * @endpoint storage/passwords + * @method splunkjs.Service + * @see splunkjs.Service.StoragePasswords + */ + storagePasswords: function(namespace) { + return new root.StoragePasswords(this, namespace); + }, + + /** + * Gets the `FiredAlertGroupCollection` collection, which lets you + * list alert groups. + * + * @example + * + * // List all # of fired alert groups + * var firedAlertGroups = svc.firedAlertGroups(); + * firedAlertGroups.fetch(function(err, firedAlertGroups) { + * console.log("# of alert groups: " + firedAlertGroups.list().length); + * }); + * + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.FiredAlertGroupCollection} The `FiredAlertGroupCollection` collection. + * + * @endpoint saved/searches + * @method splunkjs.Service + * @see splunkjs.Service.FiredAlertGroupCollection + */ + firedAlertGroups: function(namespace) { + return new root.FiredAlertGroupCollection(this, namespace); + }, + + /** + * Gets the `Jobs` collection, which lets you create, list, + * and retrieve search jobs. + * + * @example + * + * // List all job IDs + * var jobs = svc.jobs(); + * jobs.fetch(function(err, jobs) { + * var list = jobs.list(); + * for(var i = 0; i < list.length; i++) { + * console.log("Job " + (i+1) + ": " + list[i].sid); + * } + * }); + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Jobs} The `Jobs` collection. + * + * @endpoint search/jobs + * @method splunkjs.Service + * @see splunkjs.Service.Jobs + */ + jobs: function(namespace) { + return new root.Jobs(this, namespace); + }, - return this.get(Paths.parser, params, function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data); - } - }); - }, - - /** - * Provides auto-complete suggestions for search queries. - * - * @example - * - * service.typeahead("index=", 10, function(err, options) { - * console.log("Autocompletion options: ", options); - * }); - * - * @param {String} prefix The query fragment to autocomplete. - * @param {Number} count The number of options to return (optional). - * @param {Function} callback A function to call with the autocompletion info: `(err, options)`. - * - * @endpoint search/typeahead - * @method splunkjs.Service - */ - typeahead: function(prefix, count, callback) { - if (!callback && utils.isFunction(count)) { - callback = count; - count = 10; - } + /** + * Gets the `DataModels` collection, which lets you create, list, + * and retrieve data models. + * + * @endpoint datamodel/model + * @method splunkjs.Service + * @see splunkjs.Service.DataModels + */ + dataModels: function(namespace) { + return new root.DataModels(this, namespace); + }, + + /** + * Gets the `Users` collection, which lets you create, + * list, and retrieve users. + * + * @example + * + * // List all usernames + * var users = svc.users(); + * users.fetch(function(err, users) { + * var list = users.list(); + * for(var i = 0; i < list.length; i++) { + * console.log("User " + (i+1) + ": " + list[i].properties().name); + * } + * }); + * + * @return {splunkjs.Service.Users} The `Users` collection. + * + * @endpoint authorization/users + * @method splunkjs.Service + * @see splunkjs.Service.Users + */ + users: function() { + return new root.Users(this); + }, - callback = callback || function() {}; - var params = { - count: count || 10, - prefix: prefix - }; + /** + * Gets the `Views` collection, which lets you create, + * list, and retrieve views (custom UIs built in Splunk's app framework). + * + * @example + * + * // List all views + * var views = svc.views(); + * views.fetch(function(err, views) { + * var list = views.list(); + * for(var i = 0; i < list.length; i++) { + * console.log("View " + (i+1) + ": " + list[i].properties().name); + * } + * }); + * + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Views} The `Views` collection. + * + * @endpoint data/ui/views + * @method splunkjs.Service + * @see splunkjs.Service.Views + */ + views: function(namespace) { + return new root.Views(this, namespace); + }, - return this.get(Paths.typeahead, params, function(err, response) { - if (err) { - callback(err); + /** + * Creates a search job with a given search query and optional parameters, including `exec_mode` to specify the type of search: + * + * - Use `exec_mode=normal` to return a search job ID immediately (default). + * Poll for completion to find out when you can retrieve search results. + * + * - Use `exec_mode=blocking` to return the search job ID when the search has finished. + * + * To run a oneshot search, which does not create a job but rather returns the search results, use `Service.oneshotSearch`. + * + * @example + * + * service.search("search ERROR", {id: "myjob_123"}, function(err, newJob) { + * console.log("CREATED": newJob.sid); + * }); + * + * @param {String} query The search query. + * @param {Object} params A dictionary of properties for the job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @param {Function} callback A function to call with the created job: `(err, createdJob)`. + * + * @endpoint search/jobs + * @method splunkjs.Service + */ + search: function(query, params, namespace, callback) { + if (!callback && utils.isFunction(namespace)) { + callback = namespace; + namespace = null; } - else { - var results = (response.data || {}).results; - callback(null, results || []); - } - }); - }, - - /** - * Logs an event to Splunk. - * - * @example - * - * service.log("A new event", {index: "_internal", sourcetype: "mysourcetype"}, function(err, result) { - * console.log("Submitted event: ", result); - * }); - * - * @param {String|Object} event The text for this event, or a JSON object. - * @param {Object} params A dictionary of parameters for indexing: - * - `index` (_string_): The index to send events from this input to. - * - `host` (_string_): The value to populate in the Host field for events from this data input. - * - `host_regex` (_string_): A regular expression used to extract the host value from each event. - * - `source` (_string_): The value to populate in the Source field for events from this data input. - * - `sourcetype` (_string_): The value to populate in the Sourcetype field for events from this data input. - * @param {Function} callback A function to call when the event is submitted: `(err, result)`. - * - * @endpoint receivers/simple - * @method splunkjs.Service - */ - log: function(event, params, callback) { - if (!callback && utils.isFunction(params)) { - callback = params; - params = {}; - } + + var jobs = new root.Jobs(this, namespace); + return jobs.search(query, params, callback); + }, + + /** + * A convenience method to get a `Job` by its sid. + * + * @param {String} sid The search ID for a search job. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @param {Function} callback A function to call with the created job: `(err, job)`. + * + * @endpoint search/jobs + * @method splunkjs.Service + */ + getJob: function(sid, namespace, callback) { + if (!callback && utils.isFunction(namespace)) { + callback = namespace; + namespace = null; + } + var job = new root.Job(this, sid, namespace); + return job.fetch({}, callback); + }, - callback = callback || function() {}; - params = params || {}; + /** + * Creates a oneshot search from a given search query and optional parameters. + * + * @example + * + * service.oneshotSearch("search ERROR", {id: "myjob_123"}, function(err, results) { + * console.log("RESULT FIELDS": results.fields); + * }); + * + * @param {String} query The search query. + * @param {Object} params A dictionary of properties for the search: + * - `output_mode` (_string_): Specifies the output format of the results (XML, JSON, or CSV). + * - `earliest_time` (_string_): Specifies the earliest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. + * - `latest_time` (_string_): Specifies the latest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. + * - `rf` (_string_): Specifies one or more fields to add to the search. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @param {Function} callback A function to call with the results of the search: `(err, results)`. + * + * @endpoint search/jobs + * @method splunkjs.Service + */ + oneshotSearch: function(query, params, namespace, callback) { + if (!callback && utils.isFunction(namespace)) { + callback = namespace; + namespace = null; + } + + var jobs = new root.Jobs(this, namespace); + return jobs.oneshotSearch(query, params, callback); + }, - // If the event is a JSON object, convert it to a string. - if (utils.isObject(event)) { - event = JSON.stringify(event); - } + /** + * Gets the user that is currently logged in. + * + * @example + * + * service.currentUser(function(err, user) { + * console.log("Real name: ", user.properties().realname); + * }); + * + * @param {Function} callback A function to call with the user instance: `(err, user)`. + * @return {splunkjs.Service.currentUser} The `User`. + * + * @endpoint authorization/current-context + * @method splunkjs.Service + */ + currentUser: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.get(Paths.currentUser, {}, function(err, response) { + if (err) { + callback(err); + } + else { + var username = response.data.entry[0].content.username; + var user = new root.User(that, username); + user.fetch(function() { + if (req.wasAborted) { + return; // aborted, so ignore + } + else { + callback.apply(null, arguments); + } + }); + } + }); + + return req; + }, - var path = this.paths.submitEvent; - var method = "POST"; - var headers = {"Content-Type": "text/plain"}; - var body = event; - var get = params; - var post = {}; + /** + * Gets configuration information about the server. + * + * @example + * + * service.serverInfo(function(err, info) { + * console.log("Splunk Version: ", info.properties().version); + * }); + * + * @param {Function} callback A function to call with the server info: `(err, info)`. + * + * @endpoint server/info + * @method splunkjs.Service + */ + serverInfo: function(callback) { + callback = callback || function() {}; + + var serverInfo = new root.ServerInfo(this); + return serverInfo.fetch(callback); + }, - var req = this.request( - path, - method, - get, - post, - body, - headers, - function(err, response) { + /** + * Parses a search query. + * + * @example + * + * service.parse("search index=_internal | head 1", function(err, parse) { + * console.log("Commands: ", parse.commands); + * }); + * + * @param {String} query The search query to parse. + * @param {Object} params An object of options for the parser: + * - `enable_lookups` (_boolean_): If `true`, performs reverse lookups to expand the search expression. + * - `output_mode` (_string_): The output format (XML or JSON). + * - `parse_only` (_boolean_): If `true`, disables the expansion of search due to evaluation of subsearches, time term expansion, lookups, tags, eventtypes, and sourcetype alias. + * - `reload_macros` (_boolean_): If `true`, reloads macro definitions from macros.conf. + * @param {Function} callback A function to call with the parse info: `(err, parse)`. + * + * @endpoint search/parser + * @method splunkjs.Service + */ + parse: function(query, params, callback) { + if (!callback && utils.isFunction(params)) { + callback = params; + params = {}; + } + + callback = callback || function() {}; + params = params || {}; + + params.q = query; + + return this.get(Paths.parser, params, function(err, response) { if (err) { callback(err); } - else { + else { callback(null, response.data); } + }); + }, + + /** + * Provides auto-complete suggestions for search queries. + * + * @example + * + * service.typeahead("index=", 10, function(err, options) { + * console.log("Autocompletion options: ", options); + * }); + * + * @param {String} prefix The query fragment to autocomplete. + * @param {Number} count The number of options to return (optional). + * @param {Function} callback A function to call with the autocompletion info: `(err, options)`. + * + * @endpoint search/typeahead + * @method splunkjs.Service + */ + typeahead: function(prefix, count, callback) { + if (!callback && utils.isFunction(count)) { + callback = count; + count = 10; } - ); + + callback = callback || function() {}; + var params = { + count: count || 10, + prefix: prefix + }; + + return this.get(Paths.typeahead, params, function(err, response) { + if (err) { + callback(err); + } + else { + var results = (response.data || {}).results; + callback(null, results || []); + } + }); + }, - return req; - } - }); - - /** - * Provides a base definition for a Splunk endpoint, which is a combination of - * a specific service and path. Provides convenience methods for GET, POST, and - * DELETE operations used in splunkjs, automatically preparing the path correctly - * and allowing for relative calls. - * - * @class splunkjs.Service.Endpoint - */ - root.Endpoint = Class.extend({ - /** - * Constructor for `splunkjs.Service.Endpoint`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} qualifiedPath A fully-qualified relative endpoint path (for example, "/services/search/jobs"). - * @return {splunkjs.Service.Endpoint} A new `splunkjs.Service.Endpoint` instance. - * - * @method splunkjs.Service.Endpoint - */ - init: function(service, qualifiedPath) { - if (!service) { - throw new Error("Passed in a null Service."); - } - - if (!qualifiedPath) { - throw new Error("Passed in an empty path."); + /** + * Logs an event to Splunk. + * + * @example + * + * service.log("A new event", {index: "_internal", sourcetype: "mysourcetype"}, function(err, result) { + * console.log("Submitted event: ", result); + * }); + * + * @param {String|Object} event The text for this event, or a JSON object. + * @param {Object} params A dictionary of parameters for indexing: + * - `index` (_string_): The index to send events from this input to. + * - `host` (_string_): The value to populate in the Host field for events from this data input. + * - `host_regex` (_string_): A regular expression used to extract the host value from each event. + * - `source` (_string_): The value to populate in the Source field for events from this data input. + * - `sourcetype` (_string_): The value to populate in the Sourcetype field for events from this data input. + * @param {Function} callback A function to call when the event is submitted: `(err, result)`. + * + * @endpoint receivers/simple + * @method splunkjs.Service + */ + log: function(event, params, callback) { + if (!callback && utils.isFunction(params)) { + callback = params; + params = {}; + } + + callback = callback || function() {}; + params = params || {}; + + // If the event is a JSON object, convert it to a string. + if (utils.isObject(event)) { + event = JSON.stringify(event); + } + + var path = this.paths.submitEvent; + var method = "POST"; + var headers = {"Content-Type": "text/plain"}; + var body = event; + var get = params; + var post = {}; + + var req = this.request( + path, + method, + get, + post, + body, + headers, + function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data); + } + } + ); + + return req; } - - this.service = service; - this.qualifiedPath = qualifiedPath; - - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this.get = utils.bind(this, this.get); - this.post = utils.bind(this, this.post); - this.del = utils.bind(this, this.del); - }, - + }); + /** - * Performs a relative GET request on an endpoint's path, - * combined with the parameters and a relative path if specified. - * - * @example - * - * // Will make a request to {service.prefix}/search/jobs/123456/results?offset=1 - * var endpoint = new splunkjs.Service.Endpoint(service, "search/jobs/12345"); - * endpoint.get("results", {offset: 1}, function() { console.log("DONE"))}); + * Provides a base definition for a Splunk endpoint, which is a combination of + * a specific service and path. Provides convenience methods for GET, POST, and + * DELETE operations used in splunkjs, automatically preparing the path correctly + * and allowing for relative calls. * - * @param {String} relpath A relative path to append to the endpoint path. - * @param {Object} params A dictionary of entity-specific parameters to add to the query string. - * @param {Function} callback A function to call when the request is complete: `(err, response)`. - * - * @method splunkjs.Service.Endpoint + * @class splunkjs.Service.Endpoint */ - get: function(relpath, params, callback) { - var url = this.qualifiedPath; - - // If we have a relative path, we will append it with a preceding - // slash. - if (relpath) { - url = url + "/" + relpath; + root.Endpoint = Class.extend({ + /** + * Constructor for `splunkjs.Service.Endpoint`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} qualifiedPath A fully-qualified relative endpoint path (for example, "/services/search/jobs"). + * @return {splunkjs.Service.Endpoint} A new `splunkjs.Service.Endpoint` instance. + * + * @method splunkjs.Service.Endpoint + */ + init: function(service, qualifiedPath) { + if (!service) { + throw new Error("Passed in a null Service."); + } + + if (!qualifiedPath) { + throw new Error("Passed in an empty path."); + } + + this.service = service; + this.qualifiedPath = qualifiedPath; + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this.get = utils.bind(this, this.get); + this.post = utils.bind(this, this.post); + this.del = utils.bind(this, this.del); + }, + + /** + * Performs a relative GET request on an endpoint's path, + * combined with the parameters and a relative path if specified. + * + * @example + * + * // Will make a request to {service.prefix}/search/jobs/123456/results?offset=1 + * var endpoint = new splunkjs.Service.Endpoint(service, "search/jobs/12345"); + * endpoint.get("results", {offset: 1}, function() { console.log("DONE"))}); + * + * @param {String} relpath A relative path to append to the endpoint path. + * @param {Object} params A dictionary of entity-specific parameters to add to the query string. + * @param {Function} callback A function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Service.Endpoint + */ + get: function(relpath, params, callback) { + var url = this.qualifiedPath; + + // If we have a relative path, we will append it with a preceding + // slash. + if (relpath) { + url = url + "/" + relpath; + } + + return this.service.get( + url, + params, + callback + ); + }, + + /** + * Performs a relative POST request on an endpoint's path, + * combined with the parameters and a relative path if specified. + * + * @example + * + * // Will make a request to {service.prefix}/search/jobs/123456/control + * var endpoint = new splunkjs.Service.Endpoint(service, "search/jobs/12345"); + * endpoint.post("control", {action: "cancel"}, function() { console.log("CANCELLED"))}); + * + * @param {String} relpath A relative path to append to the endpoint path. + * @param {Object} params A dictionary of entity-specific parameters to add to the body. + * @param {Function} callback A function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Service.Endpoint + */ + post: function(relpath, params, callback) { + var url = this.qualifiedPath; + + // If we have a relative path, we will append it with a preceding + // slash. + if (relpath) { + url = url + "/" + relpath; + } + + return this.service.post( + url, + params, + callback + ); + }, + + /** + * Performs a relative DELETE request on an endpoint's path, + * combined with the parameters and a relative path if specified. + * + * @example + * + * // Will make a request to {service.prefix}/search/jobs/123456 + * var endpoint = new splunkjs.Service.Endpoint(service, "search/jobs/12345"); + * endpoint.delete("", {}, function() { console.log("DELETED"))}); + * + * @param {String} relpath A relative path to append to the endpoint path. + * @param {Object} params A dictionary of entity-specific parameters to add to the query string. + * @param {Function} callback A function to call when the request is complete: `(err, response)`. + * + * @method splunkjs.Service.Endpoint + */ + del: function(relpath, params, callback) { + var url = this.qualifiedPath; + + // If we have a relative path, we will append it with a preceding + // slash. + if (relpath) { + url = url + "/" + relpath; + } + + return this.service.del( + url, + params, + callback + ); } - - return this.service.get( - url, - params, - callback - ); - }, - + }); + /** - * Performs a relative POST request on an endpoint's path, - * combined with the parameters and a relative path if specified. - * - * @example - * - * // Will make a request to {service.prefix}/search/jobs/123456/control - * var endpoint = new splunkjs.Service.Endpoint(service, "search/jobs/12345"); - * endpoint.post("control", {action: "cancel"}, function() { console.log("CANCELLED"))}); + * Provides a base definition for a Splunk resource (for example, an entity + * such as an index or search job, or a collection of entities). Provides + * basic methods for handling Splunk resources, such as validation and + * accessing properties. * - * @param {String} relpath A relative path to append to the endpoint path. - * @param {Object} params A dictionary of entity-specific parameters to add to the body. - * @param {Function} callback A function to call when the request is complete: `(err, response)`. + * This class should not be used directly because most methods are meant to be overridden. * - * @method splunkjs.Service.Endpoint + * @class splunkjs.Service.Resource + * @extends splunkjs.Service.Endpoint */ - post: function(relpath, params, callback) { - var url = this.qualifiedPath; - - // If we have a relative path, we will append it with a preceding - // slash. - if (relpath) { - url = url + "/" + relpath; + root.Resource = root.Endpoint.extend({ + /** + * Constructor for `splunkjs.Service.Resource`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} path A relative endpoint path (for example, "search/jobs"). + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Resource} A new `splunkjs.Service.Resource` instance. + * + * @method splunkjs.Service.Resource + */ + init: function(service, path, namespace) { + var fullpath = service.fullpath(path, namespace); + + this._super(service, fullpath); + this.namespace = namespace; + this._properties = {}; + this._state = {}; + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this._load = utils.bind(this, this._load); + this.fetch = utils.bind(this, this.fetch); + this.properties = utils.bind(this, this.properties); + this.state = utils.bind(this, this.state); + this.path = utils.bind(this, this.path); + }, + + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Resource + */ + path: function() { + throw new Error("MUST BE OVERRIDDEN"); + }, + + /** + * Loads the resource and stores the properties. + * + * @param {Object} properties The properties for this resource. + * + * @method splunkjs.Service.Resource + * @protected + */ + _load: function(properties) { + this._properties = properties || {}; + this._state = properties || {}; + }, + + /** + * Refreshes the resource by fetching the object from the server + * and loading it. + * + * @param {Function} callback A function to call when the object is retrieved: `(err, resource)`. + * + * @method splunkjs.Service.Resource + * @protected + */ + fetch: function(callback) { + throw new Error("MUST BE OVERRIDDEN"); + }, + + /** + * Retrieves the current properties for this resource. + * + * @return {Object} The properties. + * + * @method splunkjs.Service.Resource + */ + properties: function() { + return this._properties; + }, + + /** + * Retrieves the current full state (properties and metadata) of this resource. + * + * @return {Object} The current full state of this resource. + * + * @method splunkjs.Service.Resource + */ + state: function() { + return this._state; } - - return this.service.post( - url, - params, - callback - ); - }, - + }); + /** - * Performs a relative DELETE request on an endpoint's path, - * combined with the parameters and a relative path if specified. - * - * @example - * - * // Will make a request to {service.prefix}/search/jobs/123456 - * var endpoint = new splunkjs.Service.Endpoint(service, "search/jobs/12345"); - * endpoint.delete("", {}, function() { console.log("DELETED"))}); + * Defines a base class for a Splunk entity, which is a well-defined construct + * with certain operations (such as "properties", "update", and "delete"). + * Entities include search jobs, indexes, inputs, apps, and more. * - * @param {String} relpath A relative path to append to the endpoint path. - * @param {Object} params A dictionary of entity-specific parameters to add to the query string. - * @param {Function} callback A function to call when the request is complete: `(err, response)`. + * Provides basic methods for working with Splunk entities, such as fetching and + * updating them. * - * @method splunkjs.Service.Endpoint + * @class splunkjs.Service.Entity + * @extends splunkjs.Service.Resource */ - del: function(relpath, params, callback) { - var url = this.qualifiedPath; - - // If we have a relative path, we will append it with a preceding - // slash. - if (relpath) { - url = url + "/" + relpath; - } - - return this.service.del( - url, - params, - callback - ); - } - }); - - /** - * Provides a base definition for a Splunk resource (for example, an entity - * such as an index or search job, or a collection of entities). Provides - * basic methods for handling Splunk resources, such as validation and - * accessing properties. - * - * This class should not be used directly because most methods are meant to be overridden. - * - * @class splunkjs.Service.Resource - * @extends splunkjs.Service.Endpoint - */ - root.Resource = root.Endpoint.extend({ - /** - * Constructor for `splunkjs.Service.Resource`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} path A relative endpoint path (for example, "search/jobs"). - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Resource} A new `splunkjs.Service.Resource` instance. - * - * @method splunkjs.Service.Resource - */ - init: function(service, path, namespace) { - var fullpath = service.fullpath(path, namespace); + root.Entity = root.Resource.extend({ + /** + * A static property that indicates whether to call `fetch` after an + * update to get the updated entity. By default, the entity is not + * fetched because the endpoint returns (echoes) the updated entity. + * + * @method splunkjs.Service.Entity + */ + fetchOnUpdate: false, - this._super(service, fullpath); - this.namespace = namespace; - this._properties = {}; - this._state = {}; + /** + * Constructor for `splunkjs.Service.Entity`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} path A relative endpoint path (for example, "search/jobs"). + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Entity} A new `splunkjs.Service.Entity` instance. + * + * @method splunkjs.Service.Entity + */ + init: function(service, path, namespace) { + this._super(service, path, namespace); + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this._load = utils.bind(this, this._load); + this.fetch = utils.bind(this, this.fetch); + this.remove = utils.bind(this, this.remove); + this.update = utils.bind(this, this.update); + this.fields = utils.bind(this, this.fields); + this.links = utils.bind(this, this.links); + this.acl = utils.bind(this, this.acl); + this.author = utils.bind(this, this.author); + this.updated = utils.bind(this, this.updated); + this.published = utils.bind(this, this.published); + this.enable = utils.bind(this, this.enable); + this.disable = utils.bind(this, this.disable); + this.reload = utils.bind(this, this.reload); + + // Initial values + this._properties = {}; + this._fields = {}; + this._acl = {}; + this._links = {}; + }, - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this._load = utils.bind(this, this._load); - this.fetch = utils.bind(this, this.fetch); - this.properties = utils.bind(this, this.properties); - this.state = utils.bind(this, this.state); - this.path = utils.bind(this, this.path); - }, - - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Resource - */ - path: function() { - throw new Error("MUST BE OVERRIDDEN"); - }, - - /** - * Loads the resource and stores the properties. - * - * @param {Object} properties The properties for this resource. - * - * @method splunkjs.Service.Resource - * @protected - */ - _load: function(properties) { - this._properties = properties || {}; - this._state = properties || {}; - }, - - /** - * Refreshes the resource by fetching the object from the server - * and loading it. - * - * @param {Function} callback A function to call when the object is retrieved: `(err, resource)`. - * - * @method splunkjs.Service.Resource - * @protected - */ - fetch: function(callback) { - throw new Error("MUST BE OVERRIDDEN"); - }, - - /** - * Retrieves the current properties for this resource. - * - * @return {Object} The properties. - * - * @method splunkjs.Service.Resource - */ - properties: function() { - return this._properties; - }, - - /** - * Retrieves the current full state (properties and metadata) of this resource. - * - * @return {Object} The current full state of this resource. - * - * @method splunkjs.Service.Resource - */ - state: function() { - return this._state; - } - }); - - /** - * Defines a base class for a Splunk entity, which is a well-defined construct - * with certain operations (such as "properties", "update", and "delete"). - * Entities include search jobs, indexes, inputs, apps, and more. - * - * Provides basic methods for working with Splunk entities, such as fetching and - * updating them. - * - * @class splunkjs.Service.Entity - * @extends splunkjs.Service.Resource - */ - root.Entity = root.Resource.extend({ - /** - * A static property that indicates whether to call `fetch` after an - * update to get the updated entity. By default, the entity is not - * fetched because the endpoint returns (echoes) the updated entity. - * - * @method splunkjs.Service.Entity - */ - fetchOnUpdate: false, - - /** - * Constructor for `splunkjs.Service.Entity`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} path A relative endpoint path (for example, "search/jobs"). - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Entity} A new `splunkjs.Service.Entity` instance. - * - * @method splunkjs.Service.Entity - */ - init: function(service, path, namespace) { - this._super(service, path, namespace); + /** + * Loads the entity and stores the properties. + * + * @param {Object} properties The properties for this entity. + * + * @method splunkjs.Service.Entity + * @protected + */ + _load: function(properties) { + properties = utils.isArray(properties) ? properties[0] : properties; + + // Initialize the properties to + // empty values + properties = properties || { + content: {}, + fields: {}, + acl: {}, + links: {} + }; + + this._super(properties); + + // Take out the entity-specific content + this._properties = properties.content || {}; + this._fields = properties.fields || this._fields || {}; + this._acl = properties.acl || {}; + this._links = properties.links || {}; + this._author = properties.author || null; + this._updated = properties.updated || null; + this._published = properties.published || null; + }, - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this._load = utils.bind(this, this._load); - this.fetch = utils.bind(this, this.fetch); - this.remove = utils.bind(this, this.remove); - this.update = utils.bind(this, this.update); - this.fields = utils.bind(this, this.fields); - this.links = utils.bind(this, this.links); - this.acl = utils.bind(this, this.acl); - this.author = utils.bind(this, this.author); - this.updated = utils.bind(this, this.updated); - this.published = utils.bind(this, this.published); - this.enable = utils.bind(this, this.enable); - this.disable = utils.bind(this, this.disable); - this.reload = utils.bind(this, this.reload); + /** + * Retrieves the fields information for this entity, indicating which + * fields are wildcards, required, and optional. + * + * @return {Object} The fields information. + * + * @method splunkjs.Service.Entity + */ + fields: function() { + return this._fields; + }, - // Initial values - this._properties = {}; - this._fields = {}; - this._acl = {}; - this._links = {}; - }, - - /** - * Loads the entity and stores the properties. - * - * @param {Object} properties The properties for this entity. - * - * @method splunkjs.Service.Entity - * @protected - */ - _load: function(properties) { - properties = utils.isArray(properties) ? properties[0] : properties; + /** + * Retrieves the access control list (ACL) information for this entity, + * which contains the permissions for accessing the entity. + * + * @return {Object} The ACL. + * + * @method splunkjs.Service.Entity + */ + acl: function() { + return this._acl; + }, - // Initialize the properties to - // empty values - properties = properties || { - content: {}, - fields: {}, - acl: {}, - links: {} - }; + /** + * Retrieves the links information for this entity, which is the URI of + * the entity relative to the management port of a Splunk instance. + * + * @return {Object} The links information. + * + * @method splunkjs.Service.Entity + */ + links: function() { + return this._links; + }, - this._super(properties); + /** + * Retrieves the author information for this entity. + * + * @return {String} The author. + * + * @method splunkjs.Service.Entity + */ + author: function() { + return this._author; + }, - // Take out the entity-specific content - this._properties = properties.content || {}; - this._fields = properties.fields || this._fields || {}; - this._acl = properties.acl || {}; - this._links = properties.links || {}; - this._author = properties.author || null; - this._updated = properties.updated || null; - this._published = properties.published || null; - }, - - /** - * Retrieves the fields information for this entity, indicating which - * fields are wildcards, required, and optional. - * - * @return {Object} The fields information. - * - * @method splunkjs.Service.Entity - */ - fields: function() { - return this._fields; - }, - - /** - * Retrieves the access control list (ACL) information for this entity, - * which contains the permissions for accessing the entity. - * - * @return {Object} The ACL. - * - * @method splunkjs.Service.Entity - */ - acl: function() { - return this._acl; - }, - - /** - * Retrieves the links information for this entity, which is the URI of - * the entity relative to the management port of a Splunk instance. - * - * @return {Object} The links information. - * - * @method splunkjs.Service.Entity - */ - links: function() { - return this._links; - }, - - /** - * Retrieves the author information for this entity. - * - * @return {String} The author. - * - * @method splunkjs.Service.Entity - */ - author: function() { - return this._author; - }, - - /** - * Retrieves the updated time for this entity. - * - * @return {String} The updated time. - * - * @method splunkjs.Service.Entity - */ - updated: function() { - return this._updated; - }, - - /** - * Retrieves the published time for this entity. - * - * @return {String} The published time. - * - * @method splunkjs.Service.Entity - */ - published: function() { - return this._published; - }, - - /** - * Refreshes the entity by fetching the object from the server and - * loading it. - * - * @param {Object} options An optional dictionary of collection filtering and pagination options: - * - `count` (_integer_): The maximum number of items to return. - * - `offset` (_integer_): The offset of the first item to return. - * - `search` (_string_): The search query to filter responses. - * - `sort_dir` (_string_): The direction to sort returned items: “asc” or “desc”. - * - `sort_key` (_string_): The field to use for sorting (optional). - * - `sort_mode` (_string_): The collating sequence for sorting returned items: “auto”, “alpha”, “alpha_case”, or “num”. - * @param {Function} callback A function to call when the object is retrieved: `(err, resource)`. - * - * @method splunkjs.Service.Entity - */ - fetch: function(options, callback) { - if (!callback && utils.isFunction(options)) { - callback = options; - options = {}; - } - callback = callback || function() {}; + /** + * Retrieves the updated time for this entity. + * + * @return {String} The updated time. + * + * @method splunkjs.Service.Entity + */ + updated: function() { + return this._updated; + }, - options = options || {}; + /** + * Retrieves the published time for this entity. + * + * @return {String} The published time. + * + * @method splunkjs.Service.Entity + */ + published: function() { + return this._published; + }, - var that = this; - return this.get("", options, function(err, response) { - if (err) { + /** + * Refreshes the entity by fetching the object from the server and + * loading it. + * + * @param {Object} options An optional dictionary of collection filtering and pagination options: + * - `count` (_integer_): The maximum number of items to return. + * - `offset` (_integer_): The offset of the first item to return. + * - `search` (_string_): The search query to filter responses. + * - `sort_dir` (_string_): The direction to sort returned items: “asc” or “desc”. + * - `sort_key` (_string_): The field to use for sorting (optional). + * - `sort_mode` (_string_): The collating sequence for sorting returned items: “auto”, “alpha”, “alpha_case”, or “num”. + * @param {Function} callback A function to call when the object is retrieved: `(err, resource)`. + * + * @method splunkjs.Service.Entity + */ + fetch: function(options, callback) { + if (!callback && utils.isFunction(options)) { + callback = options; + options = {}; + } + callback = callback || function() {}; + + options = options || {}; + + var that = this; + return this.get("", options, function(err, response) { + if (err) { + callback(err); + } + else { + that._load(response.data ? response.data.entry : null); + callback(null, that); + } + }); + }, + + /** + * Deletes the entity from the server. + * + * @param {Function} callback A function to call when the object is deleted: `(err)`. + * + * @method splunkjs.Service.Entity + * @protected + */ + remove: function(callback) { + callback = callback || function() {}; + + var that = this; + return this.del("", {}, function(err) { callback(err); - } - else { - that._load(response.data ? response.data.entry : null); - callback(null, that); + }); + }, + + /** + * Updates the entity on the server. + * + * @param {Object} props The properties to update the object with. + * @param {Function} callback A function to call when the object is updated: `(err, entity)`. + * + * @method splunkjs.Service.Entity + * @protected + */ + update: function(props, callback) { + callback = callback || function() {}; + + if (props.hasOwnProperty("name")) { + throw new Error("Cannot set 'name' field in 'update'"); } - }); - }, - - /** - * Deletes the entity from the server. - * - * @param {Function} callback A function to call when the object is deleted: `(err)`. - * - * @method splunkjs.Service.Entity - * @protected - */ - remove: function(callback) { - callback = callback || function() {}; + + var that = this; + var req = this.post("", props, function(err, response) { + if (!err && !that.fetchOnUpdate) { + that._load(response.data.entry); + callback(err, that); + } + else if (!err && that.fetchOnUpdate) { + that.fetch(function() { + if (req.wasAborted) { + return; // aborted, so ignore + } + else { + callback.apply(null, arguments); + } + }); + } + else { + callback(err, that); + } + }); + + return req; + }, - var that = this; - return this.del("", {}, function(err) { - callback(err); - }); - }, - + /** + * Disables the entity on the server. + * + * @param {Function} callback A function to call when the object is disabled: `(err, entity)`. + * + * @method splunkjs.Service.Entity + * @protected + */ + disable: function(callback) { + callback = callback || function() {}; + + var that = this; + this.post("disable", {}, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, that); + } + }); + }, + + /** + * Enables the entity on the server. + * + * @param {Function} callback A function to call when the object is enabled: `(err, entity)`. + * + * @method splunkjs.Service.Entity + * @protected + */ + enable: function(callback) { + callback = callback || function() {}; + + var that = this; + this.post("enable", {}, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, that); + } + }); + }, + + /** + * Reloads the entity on the server. + * + * @param {Function} callback A function to call when the object is reloaded: `(err, entity)`. + * + * @method splunkjs.Service.Entity + * @protected + */ + reload: function(callback) { + callback = callback || function() {}; + + var that = this; + this.post("_reload", {}, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, that); + } + }); + } + }); + /** - * Updates the entity on the server. + * Defines a base class for a Splunk collection, which is a well-defined construct + * that provides basic methods for working with collections of entities, such as + * creating and listing entities. * - * @param {Object} props The properties to update the object with. - * @param {Function} callback A function to call when the object is updated: `(err, entity)`. - * - * @method splunkjs.Service.Entity - * @protected + * @class splunkjs.Service.Collection + * @extends splunkjs.Service.Resource */ - update: function(props, callback) { - callback = callback || function() {}; + root.Collection = root.Resource.extend({ + /** + * A static property that indicates whether to call `fetch` after an + * entity has been created. By default, the entity is not fetched + * because the endpoint returns (echoes) the new entity. + + * @method splunkjs.Service.Collection + */ + fetchOnEntityCreation: false, - if (props.hasOwnProperty("name")) { - throw new Error("Cannot set 'name' field in 'update'"); - } + /** + * Constructor for `splunkjs.Service.Collection`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} path A relative endpoint path (for example, "search/jobs"). + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Collection} A new `splunkjs.Service.Collection` instance. + * + * @method splunkjs.Service.Collection + */ + init: function(service, path, namespace) { + this._super(service, path, namespace); + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this._load = utils.bind(this, this._load); + this.fetch = utils.bind(this, this.fetch); + this.create = utils.bind(this, this.create); + this.list = utils.bind(this, this.list); + this.item = utils.bind(this, this.item); + this.instantiateEntity = utils.bind(this, this.instantiateEntity); + + // Initial values + this._entities = []; + this._entitiesByName = {}; + this._properties = {}; + this._paging = {}; + this._links = {}; + }, - var that = this; - var req = this.post("", props, function(err, response) { - if (!err && !that.fetchOnUpdate) { - that._load(response.data.entry); - callback(err, that); - } - else if (!err && that.fetchOnUpdate) { - that.fetch(function() { - if (req.wasAborted) { - return; // aborted, so ignore - } - else { - callback.apply(null, arguments); - } - }); - } - else { - callback(err, that); + /** + * Creates a local instance of an entity. + * + * @param {Object} props The properties for this entity. + * @return {splunkjs.Service.Entity} A new `splunkjs.Service.Entity` instance. + * + * @method splunkjs.Service.Collection + */ + instantiateEntity: function(props) { + throw new Error("MUST BE OVERRIDDEN"); + }, + + /** + * Loads the collection and properties, and creates a map of entity + * names to entity IDs (for retrieval purposes). + * + * @param {Object} properties The properties for this collection. + * + * @method splunkjs.Service.Collection + * @private + */ + _load: function(properties) { + this._super(properties); + + var entities = []; + var entitiesByName = {}; + var entityPropertyList = properties.entry || []; + for(var i = 0; i < entityPropertyList.length; i++) { + var props = entityPropertyList[i]; + var entity = this.instantiateEntity(props); + entity._load(props); + entities.push(entity); + + if (entitiesByName.hasOwnProperty(entity.name)) { + entitiesByName[entity.name].push(entity); + } + else { + entitiesByName[entity.name] = [entity]; + } } - }); + this._entities = entities; + this._entitiesByName = entitiesByName; + this._paging = properties.paging || {}; + this._links = properties.links || {}; + this._updated = properties.updated || null; + }, - return req; - }, - - /** - * Disables the entity on the server. - * - * @param {Function} callback A function to call when the object is disabled: `(err, entity)`. - * - * @method splunkjs.Service.Entity - * @protected - */ - disable: function(callback) { - callback = callback || function() {}; + /** + * Retrieves the links information for this collection, which is the URI of + * the resource relative to the management port of a Splunk instance. + * + * @return {Object} The links information. + * + * @method splunkjs.Service.Collection + */ + links: function() { + return this._links; + }, - var that = this; - this.post("disable", {}, function(err, response) { - if (err) { - callback(err); + /** + * Retrieves the author information for this collection. + * + * @return {String} The author. + * + * @method splunkjs.Service.Collection + */ + paging: function() { + return this._paging; + }, + + /** + * Retrieves the updated time for this collection. + * + * @return {String} The updated time. + * + * @method splunkjs.Service.Collection + */ + updated: function() { + return this._updated; + }, + + /** + * Refreshes the resource by fetching the object from the server and + * loading it. + * + * @param {Object} options A dictionary of collection filtering and pagination options: + * - `count` (_integer_): The maximum number of items to return. + * - `offset` (_integer_): The offset of the first item to return. + * - `search` (_string_): The search query to filter responses. + * - `sort_dir` (_string_): The direction to sort returned items: “asc” or “desc”. + * - `sort_key` (_string_): The field to use for sorting (optional). + * - `sort_mode` (_string_): The collating sequence for sorting returned items: “auto”, “alpha”, “alpha_case”, or “num”. + * @param {Function} callback A function to call when the object is retrieved: `(err, resource)`. + * + * @method splunkjs.Service.Collection + */ + fetch: function(options, callback) { + if (!callback && utils.isFunction(options)) { + callback = options; + options = {}; } - else { - callback(null, that); + callback = callback || function() {}; + + options = options || {}; + if (!options.count) { + options.count = 0; } - }); - }, - - /** - * Enables the entity on the server. - * - * @param {Function} callback A function to call when the object is enabled: `(err, entity)`. - * - * @method splunkjs.Service.Entity - * @protected - */ - enable: function(callback) { - callback = callback || function() {}; + + var that = this; + var req = that.get("", options, function(err, response) { + if (err) { + callback(err); + } + else { + that._load(response.data); + callback(null, that); + } + }); + + return req; + }, - var that = this; - this.post("enable", {}, function(err, response) { - if (err) { - callback(err); + /** + * Returns a specific entity from the collection. + * + * @example + * + * var apps = service.apps(); + * apps.fetch(function(err, apps) { + * var app = apps.item("search"); + * console.log("Search App Found: " + !!app); + * // `app` is an Application object. + * }); + * + * @param {String} id The name of the entity to retrieve. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The wildcard value "-", is not acceptable when searching for an entity. + * - `app` (_string_): The app context for this resource (such as "search"). The wildcard value "-" is unacceptable when searching for an entity. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @returns {splunkjs.Service.Entity} The entity, or `null` if one is not found. + * + * @method splunkjs.Service.Collection + */ + item: function(id, namespace) { + if (utils.isEmpty(namespace)) { + namespace = null; + } + + if (!id) { + throw new Error("Must suply a non-empty name."); } - else { - callback(null, that); + + if (namespace && (namespace.app === '-' || namespace.owner === '-')) { + throw new Error("When searching for an entity, wildcards are not allowed in the namespace. Please refine your search."); } - }); - }, + + var fullPath = null; + if (this._entitiesByName.hasOwnProperty(id)) { + var entities = this._entitiesByName[id]; + + if (entities.length === 1 && !namespace) { + // If there is only one entity with the + // specified name and the user did not + // specify a namespace, then we just + // return it + return entities[0]; + } + else if (entities.length === 1 && namespace) { + // If we specified a namespace, then we + // only return the entity if it matches + // the full path + fullPath = this.service.fullpath(entities[0].path(), namespace); + if (entities[0].qualifiedPath === fullPath) { + return entities[0]; + } + else { + return null; + } + } + else if (entities.length > 1 && !namespace) { + // If there is more than one entity and we didn't + // specify a namespace, then we return an error + // saying the match is ambiguous + throw new Error("Ambiguous match for name '" + id + "'"); + } + else { + // There is more than one entity, and we do have + // a namespace, so we try and find it + for(var i = 0; i < entities.length; i++) { + var entity = entities[i]; + fullPath = this.service.fullpath(entities[i].path(), namespace); + if (entity.qualifiedPath === fullPath) { + return entity; + } + } + } + } + else { + return null; + } + }, + + /** + * Creates an entity on the server for this collection with the specified + * parameters. + * + * @example + * + * var apps = service.apps(); + * apps.create({name: "NewSearchApp"}, function(err, newApp) { + * console.log("CREATED"); + * }); + * + * @param {Object} params A dictionary of entity-specific properties. + * @param {Function} callback The function to call when the request is complete: `(err, response)`. + * @returns {Array} An array of `splunkjs.Service.Entity` objects. + * + * @method splunkjs.Service.Collection + */ + create: function(params, callback) { + callback = callback || function() {}; + var that = this; + var req = this.post("", params, function(err, response) { + if (err) { + callback(err); + } + else { + var props = response.data.entry; + if (utils.isArray(props)) { + props = props[0]; + } + + var entity = that.instantiateEntity(props); + entity._load(props); + + if (that.fetchOnEntityCreation) { + entity.fetch(function() { + if (req.wasAborted) { + return; // aborted, so ignore + } + else { + callback.apply(null, arguments); + } + }); + } + else { + callback(null, entity); + } + } + }); + + return req; + }, + + /** + * Retrieves a list of all entities in the collection. + * + * @example + * + * var apps = service.apps(); + * apps.fetch(function(err, apps) { + * var appList = apps.list(); + * console.log(appList.length); + * }); + * + * @param {Function} callback A function to call with the list of entities: `(err, list)`. + * + * @method splunkjs.Service.Collection + */ + list: function(callback) { + callback = callback || function() {}; + + return utils.clone(this._entities); + } + }); /** - * Reloads the entity on the server. + * Represents a specific saved search, which you can then view, modify, and + * remove. * - * @param {Function} callback A function to call when the object is reloaded: `(err, entity)`. - * - * @method splunkjs.Service.Entity - * @protected + * @endpoint saved/searches/{name} + * @class splunkjs.Service.SavedSearch + * @extends splunkjs.Service.Entity */ - reload: function(callback) { - callback = callback || function() {}; + root.SavedSearch = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.SavedSearch + */ + path: function() { + return Paths.savedSearches + "/" + encodeURIComponent(this.name); + }, - var that = this; - this.post("_reload", {}, function(err, response) { - if (err) { - callback(err); + /** + * Constructor for `splunkjs.Service.SavedSearch`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name for the new saved search. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.SavedSearch} A new `splunkjs.Service.SavedSearch` instance. + * + * @method splunkjs.Service.SavedSearch + */ + init: function(service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + + this.acknowledge = utils.bind(this, this.acknowledge); + this.dispatch = utils.bind(this, this.dispatch); + this.history = utils.bind(this, this.history); + this.suppressInfo = utils.bind(this, this.suppressInfo); + }, + + /** + * Gets the count of triggered alerts for this savedSearch, + * defaulting to 0 when undefined. + * + * @example + * + * var savedSearch = service.savedSearches().item("MySavedSearch"); + * var alertCount = savedSearch.alertCount(); + * + * @return {Number} The count of triggered alerts. + * + * @method splunkjs.Service.SavedSearch + */ + alertCount: function() { + return parseInt(this.properties().triggered_alert_count, 10) || 0; + }, + + /** + * Acknowledges the suppression of the alerts from a saved search and + * resumes alerting. + * + * @example + * + * var savedSearch = service.savedSearches().item("MySavedSearch"); + * savedSearch.acknowledge(function(err, search) { + * console.log("ACKNOWLEDGED"); + * }); + * + * @param {Function} callback A function to call when the saved search is acknowledged: `(err, savedSearch)`. + * + * @endpoint saved/searches/{name}/acknowledge + * @method splunkjs.Service.SavedSearch + */ + acknowledge: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("acknowledge", {}, function(err) { + callback(err, that); + }); + + return req; + }, + + /** + * Dispatches a saved search, which creates a search job and returns a + * `splunkjs.Service.Job` instance in the callback function. + * + * @example + * + * var savedSearch = service.savedSearches().item("MySavedSearch"); + * savedSearch.dispatch({force_dispatch: false}, function(err, job, savedSearch) { + * console.log("Job SID: ", job.sid); + * }); + * + * @param {Object} options The options for dispatching this saved search: + * - `dispatch.now` (_string_): The time that is used to dispatch the search as though the specified time were the current time. + * - `dispatch.*` (_string_): Overwrites the value of the search field specified in *. + * - `trigger_actions` (_boolean_): Indicates whether to trigger alert actions. + * - `force_dispatch` (_boolean_): Indicates whether to start a new search if another instance of this search is already running. + * @param {Function} callback A function to call when the saved search is dispatched: `(err, job, savedSearch)`. + * + * @endpoint saved/searches/{name}/dispatch + * @method splunkjs.Service.SavedSearch + */ + dispatch: function(options, callback) { + if (!callback && utils.isFunction(options)) { + callback = options; + options = {}; + } + + callback = callback || function() {}; + options = options || {}; + + var that = this; + var req = this.post("dispatch", options, function(err, response) { + if (err) { + callback(err); + return; + } + + var sid = response.data.sid; + var job = new root.Job(that.service, sid, that.namespace); + + callback(null, job, that); + }); + + return req; + }, + + /** + * Gets the `splunkjs.Service.FiredAlertGroup` for firedAlerts associated with this saved search. + * + * @example + * + * var alerts = service.firedAlertGroups().item("MySavedSearch"); + * + * @return {splunkjs.Service.FiredAlertGroup} An AlertGroup object with the + * same name as this SavedSearch object. + * + * @method splunkjs.Service.SavedSearch + */ + firedAlertGroup: function() { + return new root.FiredAlertGroup(this.service, this.name); + }, + + /** + * Retrieves the job history for a saved search, which is a list of + * `splunkjs.Service.Job` instances. + * + * @example + * + * var savedSearch = service.savedSearches().item("MySavedSearch"); + * savedSearch.history(function(err, jobs, search) { + * for(var i = 0; i < jobs.length; i++) { + * console.log("Job", i, ":", jobs[i].sid); + * } + * }); + * + * @param {Function} callback A function to call when the history is retrieved: `(err, job, savedSearch)`. + * + * @endpoint saved/searches/{name}/history + * @method splunkjs.Service.SavedSearch + */ + history: function(callback) { + callback = callback || function() {}; + + var that = this; + return this.get("history", {}, function(err, response) { + if (err) { + callback(err); + return; + } + + var jobs = []; + var data = response.data.entry || []; + for(var i = 0; i < data.length; i++) { + var jobData = response.data.entry[i]; + var namespace = utils.namespaceFromProperties(jobData); + var job = new root.Job(that.service, jobData.name, namespace); + + job._load(jobData); + jobs.push(job); + } + + callback(null, jobs, that); + }); + }, + + /** + * Retrieves the suppression state of a saved search. + * + * @example + * + * var savedSearch = service.savedSearches().item("MySavedSearch"); + * savedSearch.history(function(err, suppressionState, search) { + * console.log("STATE: ", suppressionState); + * }); + * + * @param {Function} callback A function to call when the suppression state is retrieved: `(err, suppressionState, savedSearch)`. + * + * @endpoint saved/searches/{name}/suppress + * @method splunkjs.Service.SavedSearch + */ + suppressInfo: function(callback) { + callback = callback || function() {}; + + var that = this; + return this.get("suppress", {}, function(err, response) { + callback(err, response.data.entry.content, that); + }); + }, + + /** + * Updates the saved search on the server. + * + * **Note:** The search query is required, even when it isn't being modified. + * If you don't provide it, this method will fetch the search string from + * the server or from the local cache. + * + * @param {Object} props The properties to update the saved search with. For a list of available parameters, see Saved search parameters on Splunk Developer Portal. + * @param {Function} callback A function to call when the object is updated: `(err, entity)`. + * + * @method splunkjs.Service.SavedSearch + */ + update: function(params, callback) { + params = params || {}; + + if (!params.search) { + var update = this._super; + var req = this.fetch(function(err, search) { + if (err) { + callback(err); + } + else { + params.search = search.properties().search; + update.call(search, params, function() { + if (req.wasAborted) { + return; // aborted, so ignore + } + else { + callback.apply(null, arguments); + } + }); + } + }); + + return req; } else { - callback(null, that); + return this._super(params, callback); } - }); - } - }); - - /** - * Defines a base class for a Splunk collection, which is a well-defined construct - * that provides basic methods for working with collections of entities, such as - * creating and listing entities. - * - * @class splunkjs.Service.Collection - * @extends splunkjs.Service.Resource - */ - root.Collection = root.Resource.extend({ - /** - * A static property that indicates whether to call `fetch` after an - * entity has been created. By default, the entity is not fetched - * because the endpoint returns (echoes) the new entity. - - * @method splunkjs.Service.Collection - */ - fetchOnEntityCreation: false, + } + }); /** - * Constructor for `splunkjs.Service.Collection`. + * Represents a collection of saved searches. You can create and list saved + * searches using this collection container, or get a specific saved search. * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} path A relative endpoint path (for example, "search/jobs"). - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Collection} A new `splunkjs.Service.Collection` instance. * - * @method splunkjs.Service.Collection - */ - init: function(service, path, namespace) { - this._super(service, path, namespace); + * @endpoint saved/searches + * @class splunkjs.Service.SavedSearches + * @extends splunkjs.Service.Collection + */ + root.SavedSearches = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.SavedSearches + */ + path: function() { + return Paths.savedSearches; + }, - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this._load = utils.bind(this, this._load); - this.fetch = utils.bind(this, this.fetch); - this.create = utils.bind(this, this.create); - this.list = utils.bind(this, this.list); - this.item = utils.bind(this, this.item); - this.instantiateEntity = utils.bind(this, this.instantiateEntity); + /** + * Creates a local instance of a saved search. + * + * @param {Object} props The properties for the new saved search. For a list of available parameters, see Saved search parameters on Splunk Developer Portal. + * @return {splunkjs.Service.SavedSearch} A new `splunkjs.Service.SavedSearch` instance. + * + * @method splunkjs.Service.SavedSearches + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.SavedSearch(this.service, props.name, entityNamespace); + }, - // Initial values - this._entities = []; - this._entitiesByName = {}; - this._properties = {}; - this._paging = {}; - this._links = {}; - }, - + /** + * Constructor for `splunkjs.Service.SavedSearches`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.SavedSearches} A new `splunkjs.Service.SavedSearches` instance. + * + * @method splunkjs.Service.SavedSearches + */ + init: function(service, namespace) { + this._super(service, this.path(), namespace); + } + }); + /** - * Creates a local instance of an entity. - * - * @param {Object} props The properties for this entity. - * @return {splunkjs.Service.Entity} A new `splunkjs.Service.Entity` instance. + * Represents a specific storage password, which you can then view, modify, and + * remove. * - * @method splunkjs.Service.Collection + * @endpoint storage/passwords/{name} + * @class splunkjs.Service.StoragePassword + * @extends splunkjs.Service.Entity */ - instantiateEntity: function(props) { - throw new Error("MUST BE OVERRIDDEN"); - }, - + root.StoragePassword = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.StoragePassword + */ + path: function () { + return Paths.storagePasswords + "/" + encodeURIComponent(this.name); + }, + + /** + * Constructor for `splunkjs.Service.StoragePassword`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name for the new storage password. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.StoragePassword} A new `splunkjs.Service.StoragePassword` instance. + * + * @method splunkjs.Service.StoragePassword + */ + init: function (service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + } + }); + /** - * Loads the collection and properties, and creates a map of entity - * names to entity IDs (for retrieval purposes). + * Represents a collection of storage passwords. You can create and list storage + * passwords using this collection container, or get a specific storage password. * - * @param {Object} properties The properties for this collection. - * - * @method splunkjs.Service.Collection - * @private + * @endpoint storage/passwords + * @class splunkjs.Service.StoragePasswords + * @extends splunkjs.Service.Collection */ - _load: function(properties) { - this._super(properties); + root.StoragePasswords = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.StoragePasswords + */ + path: function() { + return Paths.storagePasswords; + }, - var entities = []; - var entitiesByName = {}; - var entityPropertyList = properties.entry || []; - for(var i = 0; i < entityPropertyList.length; i++) { - var props = entityPropertyList[i]; - var entity = this.instantiateEntity(props); - entity._load(props); - entities.push(entity); - - if (entitiesByName.hasOwnProperty(entity.name)) { - entitiesByName[entity.name].push(entity); - } - else { - entitiesByName[entity.name] = [entity]; - } + /** + * Creates a local instance of a storage password. + * + * @param {Object} props The properties for the new storage password. For a list of available parameters, + * see + * POST storage/passwords on Splunk Developer Portal. + * @return {splunkjs.Service.SavedSearch} A new `splunkjs.Service.StoragePassword` instance. + * + * @method splunkjs.Service.StoragePasswords + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.StoragePassword(this.service, props.name, entityNamespace); + }, + + /** + * Constructor for `splunkjs.Service.StoragePasswords`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.StoragePasswords} A new `splunkjs.Service.StoragePasswords` instance. + * + * @method splunkjs.Service.StoragePasswords + */ + init: function(service, namespace) { + this._super(service, this.path(), namespace); } - this._entities = entities; - this._entitiesByName = entitiesByName; - this._paging = properties.paging || {}; - this._links = properties.links || {}; - this._updated = properties.updated || null; - }, - + }); + /** - * Retrieves the links information for this collection, which is the URI of - * the resource relative to the management port of a Splunk instance. - * - * @return {Object} The links information. + * Represents a fired alert. + * You can retrieve several of the fired alert's properties by + * the corresponding function name. * - * @method splunkjs.Service.Collection + * @endpoint alerts/fired_alerts/{name} + * @class splunkjs.Service.FiredAlert + * @extends splunkjs.Service.Entity */ - links: function() { - return this._links; - }, - + root.FiredAlert = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.FiredAlert + */ + path: function() { + return Paths.firedAlerts + "/" + encodeURIComponent(this.name); + }, + + /** + * Returns this alert's actions (such as notifying by email, running a + * script, adding to RSS, tracking in Alert Manager, and enabling + * summary indexing). + * + * @return {Array} of actions, an empty {Array} if no actions + * @method splunkjs.Service.FiredAlert + */ + actions: function() { + return this.properties().actions || []; + }, + + /** + * Returns this alert's type. + * + * @return {String} the alert's type. + * @method splunkjs.Service.FiredAlert + */ + alertType: function() { + return this.properties().alert_type || null; + }, + + /** + * Indicates whether the result is a set of events (digest) or a single + * event (per result). + * + * This method is available in Splunk 4.3 and later. + * + * @return {Boolean} true if the result is a digest, false if per result + * @method splunkjs.Service.FiredAlert + */ + isDigestMode: function() { + // Convert this property to a Boolean + return !!this.properties().digest_mode; + }, + + /** + * Returns the rendered expiration time for this alert. + * + * This method is available in Splunk 4.3 and later. + * + * @return {String} + * @method splunkjs.Service.FiredAlert + */ + expirationTime: function() { + return this.properties().expiration_time_rendered || null; + }, + + /** + * Returns the saved search for this alert. + * + * @return {String} The saved search name, or {null} if not available. + * @method splunkjs.Service.FiredAlert + */ + savedSearchName: function() { + return this.properties().savedsearch_name || null; + }, + + /** + * Returns this alert's severity on a scale of 1 to 10, with 1 being the + * highest severity. + * + * @return {Number} this alert's severity, -1 if not specified + * @method splunkjs.Service.FiredAlert + */ + severity: function() { + return parseInt(this.properties().severity, 10) || -1; + }, + + /** + * Returns this alert's search ID (SID). + * + * @return {String} This alert's SID, or {null} if not available. + * @method splunkjs.Service.FiredAlert + */ + sid: function() { + return this.properties().sid || null; + }, + + /** + * Returns the time this alert was triggered. + * + * @return {Number} This alert's trigger time, or {null} if not available. + * @method splunkjs.Service.FiredAlert + */ + triggerTime: function() { + return this.properties().trigger_time || null; + }, + + /** + * Returns this alert's rendered trigger time. + * + * This method is available in Splunk 4.3 and later. + * + * @return {String} This alert's rendered trigger time, or {null} if not available. + * @method splunkjs.Service.FiredAlert + */ + triggerTimeRendered: function() { + return this.properties().trigger_time_rendered || null; + }, + + /** + * Returns the count of triggered alerts. + * + * This method is available in Splunk 4.3 and later. + * + * @return {Number} The number of triggered alerts, or -1 if not specified. + * @method splunkjs.Service.FiredAlert + */ + triggeredAlertCount: function() { + return parseInt(this.properties().triggered_alerts, 10) || -1; + }, + + /** + * Constructor for `splunkjs.Service.FiredAlert`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name for the new alert group. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.FiredAlert} A new `splunkjs.Service.FiredAlert` instance. + * + * @method splunkjs.Service.FiredAlert + */ + init: function(service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + } + }); + + /** - * Retrieves the author information for this collection. + * Represents a specific alert group, which you can then view and + * remove. * - * @return {String} The author. - * - * @method splunkjs.Service.Collection + * @endpoint alerts/fired_alerts/{name} + * @class splunkjs.Service.FiredAlertGroup + * @extends splunkjs.Service.Entity */ - paging: function() { - return this._paging; - }, - + root.FiredAlertGroup = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.FiredAlertGroup + */ + path: function() { + return Paths.firedAlerts + "/" + encodeURIComponent(this.name); + }, + + /** + * Returns the `triggered_alert_count` property, the count + * of triggered alerts. + * + * @return {Number} the count of triggered alerts + * + * @method splunkjs.Service.FiredAlertGroup + */ + count: function() { + return parseInt(this.properties().triggered_alert_count, 10) || 0; + }, + + /** + * Returns fired instances of this alert, which is + * a list of `splunkjs.Service.FiredAlert` instances. + * + * @example + * + * var alertGroup = service.firedAlertGroups().item("MyAlert"); + * alertGroup.list(function(err, firedAlerts, alert) { + * for(var i = 0; i < firedAlerts.length; i++) { + * console.log("Fired alert", i, ":", firedAlerts[i].sid); + * } + * }); + * + * @param {Function} callback A function to call when the fired alerts are retrieved: `(err, firedAlerts, alertGroup)`. + * + * @method splunkjs.Service.FiredAlertGroup + */ + list: function(options, callback) { + if (!callback && utils.isFunction(options)) { + callback = options; + options = {}; + } + + callback = callback || function() {}; + options = options || {}; + + var that = this; + return this.get("", options, function(err, response) { + if (err) { + callback(err); + return; + } + + var firedAlerts = []; + var data = response.data.entry || []; + for (var i = 0; i < data.length; i++) { + var firedAlertData = response.data.entry[i]; + var namespace = utils.namespaceFromProperties(firedAlertData); + var firedAlert = new root.FiredAlert(that.service, firedAlertData.name, namespace); + firedAlert._load(firedAlertData); + firedAlerts.push(firedAlert); + } + + callback(null, firedAlerts, that); + }); + }, + + /** + * Constructor for `splunkjs.Service.FiredAlertGroup`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name for the new alert group. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.FiredAlertGroup} A new `splunkjs.Service.FiredAlertGroup` instance. + * + * @method splunkjs.Service.FiredAlertGroup + */ + init: function(service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + + this.list = utils.bind(this, this.list); + } + }); + /** - * Retrieves the updated time for this collection. + * Represents a collection of fired alerts for a saved search. You can + * create and list saved searches using this collection container, or + * get a specific alert group. * - * @return {String} The updated time. * - * @method splunkjs.Service.Collection + * @endpoint alerts/fired_alerts + * @class splunkjs.Service.FiredAlertGroupCollection + * @extends splunkjs.Service.Collection */ - updated: function() { - return this._updated; - }, + root.FiredAlertGroupCollection = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.FiredAlertGroupCollection + */ + path: function() { + return Paths.firedAlerts; + }, + + /** + * Creates a local instance of an alert group. + * + * @param {Object} props The properties for the alert group. + * @return {splunkjs.Service.FiredAlertGroup} A new `splunkjs.Service.FiredAlertGroup` instance. + * + * @method splunkjs.Service.FiredAlertGroupCollection + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.FiredAlertGroup(this.service, props.name, entityNamespace); + }, + + /** + * Suppress removing alerts via the fired alerts endpoint. + * + * @method splunkjs.Service.FiredAlertGroupCollection + */ + remove: function() { + throw new Error("To remove an alert, remove the saved search with the same name."); + }, + + /** + * Constructor for `splunkjs.Service.FiredAlertGroupCollection`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.FiredAlertGroupCollection} A new `splunkjs.Service.FiredAlertGroupCollection` instance. + * + * @method splunkjs.Service.FiredAlertGroupCollection + */ + init: function(service, namespace) { + this._super(service, this.path(), namespace); + + this.instantiateEntity = utils.bind(this, this.instantiateEntity); + this.remove = utils.bind(this, this.remove); + } + }); /** - * Refreshes the resource by fetching the object from the server and - * loading it. + * Represents a specific Splunk app that you can view, modify, and + * remove. * - * @param {Object} options A dictionary of collection filtering and pagination options: - * - `count` (_integer_): The maximum number of items to return. - * - `offset` (_integer_): The offset of the first item to return. - * - `search` (_string_): The search query to filter responses. - * - `sort_dir` (_string_): The direction to sort returned items: “asc” or “desc”. - * - `sort_key` (_string_): The field to use for sorting (optional). - * - `sort_mode` (_string_): The collating sequence for sorting returned items: “auto”, “alpha”, “alpha_case”, or “num”. - * @param {Function} callback A function to call when the object is retrieved: `(err, resource)`. - * - * @method splunkjs.Service.Collection + * @endpoint apps/local/{name} + * @class splunkjs.Service.Application + * @extends splunkjs.Service.Entity */ - fetch: function(options, callback) { - if (!callback && utils.isFunction(options)) { - callback = options; - options = {}; - } - callback = callback || function() {}; + root.Application = root.Entity.extend({ + /** + * Indicates whether to call `fetch` after an update to get the updated + * item. + * + * @method splunkjs.Service.Application + */ + fetchOnUpdate: true, - options = options || {}; - if (!options.count) { - options.count = 0; - } + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Application + */ + path: function() { + return Paths.apps + "/" + encodeURIComponent(this.name); + }, - var that = this; - var req = that.get("", options, function(err, response) { - if (err) { - callback(err); - } - else { - that._load(response.data); - callback(null, that); - } - }); + /** + * Constructor for `splunkjs.Service.Application`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name of the Splunk app. + * @return {splunkjs.Service.Application} A new `splunkjs.Service.Application` instance. + * + * @method splunkjs.Service.Application + */ + init: function(service, name) { + this.name = name; + this._super(service, this.path(), {}); + + this.setupInfo = utils.bind(this, this.setupInfo); + this.updateInfo = utils.bind(this, this.updateInfo); + }, - return req; - }, - - /** - * Returns a specific entity from the collection. - * - * @example - * - * var apps = service.apps(); - * apps.fetch(function(err, apps) { - * var app = apps.item("search"); - * console.log("Search App Found: " + !!app); - * // `app` is an Application object. - * }); - * - * @param {String} id The name of the entity to retrieve. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The wildcard value "-", is not acceptable when searching for an entity. - * - `app` (_string_): The app context for this resource (such as "search"). The wildcard value "-" is unacceptable when searching for an entity. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @returns {splunkjs.Service.Entity} The entity, or `null` if one is not found. - * - * @method splunkjs.Service.Collection - */ - item: function(id, namespace) { - if (utils.isEmpty(namespace)) { - namespace = null; - } - - if (!id) { - throw new Error("Must suply a non-empty name."); - } - - if (namespace && (namespace.app === '-' || namespace.owner === '-')) { - throw new Error("When searching for an entity, wildcards are not allowed in the namespace. Please refine your search."); - } - - var fullPath = null; - if (this._entitiesByName.hasOwnProperty(id)) { - var entities = this._entitiesByName[id]; + /** + * Retrieves the setup information for a Splunk app. + * + * @example + * + * var app = service.apps().item("app"); + * app.setup(function(err, info, search) { + * console.log("SETUP INFO: ", info); + * }); + * + * @param {Function} callback A function to call when setup information is retrieved: `(err, info, app)`. + * + * @endpoint apps/local/{name}/setup + * @method splunkjs.Service.Application + */ + setupInfo: function(callback) { + callback = callback || function() {}; - if (entities.length === 1 && !namespace) { - // If there is only one entity with the - // specified name and the user did not - // specify a namespace, then we just - // return it - return entities[0]; - } - else if (entities.length === 1 && namespace) { - // If we specified a namespace, then we - // only return the entity if it matches - // the full path - fullPath = this.service.fullpath(entities[0].path(), namespace); - if (entities[0].qualifiedPath === fullPath) { - return entities[0]; + var that = this; + return this.get("setup", {}, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data.entry.content, that); } + }); + }, + + /** + * Retrieves any information for an update to a locally-installed Splunk app. + * + * @example + * + * var app = service.apps().item("MyApp"); + * app.updateInfo(function(err, info, app) { + * console.log("UPDATE INFO: ", info); + * }); + * + * @param {Function} callback A function to call when update information is retrieved: `(err, info, app)`. + * + * @endpoint apps/local/{name}/update + * @method splunkjs.Service.Application + */ + updateInfo: function(callback) { + callback = callback || function() {}; + + var that = this; + return this.get("update", {}, function(err, response) { + if (err) { + callback(err); + } else { - return null; + callback(null, response.data.entry.content, that); } - } - else if (entities.length > 1 && !namespace) { - // If there is more than one entity and we didn't - // specify a namespace, then we return an error - // saying the match is ambiguous - throw new Error("Ambiguous match for name '" + id + "'"); - } - else { - // There is more than one entity, and we do have - // a namespace, so we try and find it - for(var i = 0; i < entities.length; i++) { - var entity = entities[i]; - fullPath = this.service.fullpath(entities[i].path(), namespace); - if (entity.qualifiedPath === fullPath) { - return entity; - } - } - } + }); } - else { - return null; - } - }, + }); /** - * Creates an entity on the server for this collection with the specified - * parameters. - * - * @example + * Represents a collection of Splunk apps. You can create and list applications + * using this collection container, or get a specific app. * - * var apps = service.apps(); - * apps.create({name: "NewSearchApp"}, function(err, newApp) { - * console.log("CREATED"); - * }); + * @endpoint apps/local + * @class splunkjs.Service.Applications + * @extends splunkjs.Service.Collection + */ + root.Applications = root.Collection.extend({ + /** + * Indicates whether to call `fetch` after an entity has been created. By + * default, the entity is not fetched because the endpoint returns + * (echoes) the new entity. + * + * @method splunkjs.Service.Applications + */ + fetchOnEntityCreation: true, + + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Applications + */ + path: function() { + return Paths.apps; + }, + + /** + * Creates a local instance of an app. + * + * @param {Object} props The properties for the new app. For details, see the POST apps/local endpoint in the REST API documentation. + * @return {splunkjs.Service.Application} A new `splunkjs.Service.Application` instance. + * + * @method splunkjs.Service.Applications + */ + instantiateEntity: function(props) { + return new root.Application(this.service, props.name, {}); + }, + + /** + * Constructor for `splunkjs.Service.Applications`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @return {splunkjs.Service.Applications} A new `splunkjs.Service.Applications` instance. + * + * @method splunkjs.Service.Applications + */ + init: function(service) { + this._super(service, this.path(), {}); + } + }); + + /** + * Provides access to configuration information about the server. * - * @param {Object} params A dictionary of entity-specific properties. - * @param {Function} callback The function to call when the request is complete: `(err, response)`. - * @returns {Array} An array of `splunkjs.Service.Entity` objects. + * @endpoint server/info + * @class splunkjs.Service.ServerInfo + * @extends splunkjs.Service.Entity + */ + root.ServerInfo = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.ServerInfo + */ + path: function() { + return Paths.info; + }, + + /** + * Constructor for `splunkjs.Service.ServerInfo`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @return {splunkjs.Service.ServerInfo} A new `splunkjs.Service.ServerInfo` instance. + * + * @method splunkjs.Service.ServerInfo + */ + init: function(service) { + this.name = "server-info"; + this._super(service, this.path(), {}); + } + }); + + /** + * Represents a specific Splunk user, which you can view, modify, and + * remove. * - * @method splunkjs.Service.Collection + * @endpoint authentication/users/{name} + * @class splunkjs.Service.User + * @extends splunkjs.Service.Entity */ - create: function(params, callback) { - callback = callback || function() {}; - var that = this; - var req = this.post("", params, function(err, response) { - if (err) { - callback(err); - } - else { - var props = response.data.entry; - if (utils.isArray(props)) { - props = props[0]; + root.User = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.User + */ + path: function() { + return Paths.users + "/" + encodeURIComponent(this.name); + }, + + /** + * Constructor for `splunkjs.Service.User`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The Splunk username. + * @return {splunkjs.Service.User} A new `splunkjs.Service.User` instance. + * + * @method splunkjs.Service.User + */ + init: function(service, name) { + this.name = name; + this._super(service, this.path(), {}); + } + }); + + /** + * Represents a collection of users. You can create and list users using + * this collection container, or get a specific user. + * + * @endpoint authentication/users + * @class splunkjs.Service.Users + * @extends splunkjs.Service.Collection + */ + root.Users = root.Collection.extend({ + /** + * Indicates whether to call `fetch` after an entity has been created. By + * default, the entity is not fetched because the endpoint returns + * (echoes) the new entity. + * + * @method splunkjs.Service.Users + */ + fetchOnEntityCreation: true, + + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Users + */ + path: function() { + return Paths.users; + }, + + /** + * Creates a local instance of a user. + * + * @param {Object} props The properties for this new user. For a list of available parameters, see User authentication parameters on Splunk Developer Portal. + * @return {splunkjs.Service.User} A new `splunkjs.Service.User` instance. + * + * @method splunkjs.Service.Users + */ + instantiateEntity: function(props) { + return new root.User(this.service, props.name, {}); + }, + + /** + * Constructor for `splunkjs.Service.Users`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @return {splunkjs.Service.Users} A new `splunkjs.Service.Users` instance. + * + * @method splunkjs.Service.Users + */ + init: function(service) { + this._super(service, this.path(), {}); + }, + + /** + * Creates a new user. + * + * **Note:** This endpoint requires a special implementation. + * + * @param {Object} params A dictionary of properties. For a list of available parameters, see User authentication parameters on Splunk Developer Portal. + * @param {Function} callback A function to call with the new entity: `(err, createdEntity)`. + * + * @method splunkjs.Service.Users + */ + create: function(params, callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("", params, function(err, response) { + if (err) { + callback(err); } - - var entity = that.instantiateEntity(props); - entity._load(props); - - if (that.fetchOnEntityCreation) { + else { + // This endpoint requires us to use the passed-in name + var props = {name: params.name}; + + var entity = that.instantiateEntity(props); entity.fetch(function() { if (req.wasAborted) { return; // aborted, so ignore @@ -4019,21484 +5067,20593 @@ require.define("/lib/service.js", function (require, module, exports, __dirname, } }); } - else { - callback(null, entity); - } - } - }); - - return req; - }, + }); + + return req; + } + }); /** - * Retrieves a list of all entities in the collection. - * - * @example - * - * var apps = service.apps(); - * apps.fetch(function(err, apps) { - * var appList = apps.list(); - * console.log(appList.length); - * }); - * - * @param {Function} callback A function to call with the list of entities: `(err, list)`. + * Represents a specific Splunk view, which you can view, modify, and + * remove. * - * @method splunkjs.Service.Collection + * @endpoint data/ui/views/{name} + * @class splunkjs.Service.View + * @extends splunkjs.Service.Entity */ - list: function(callback) { - callback = callback || function() {}; + root.View = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.View + */ + path: function() { + return Paths.views + "/" + encodeURIComponent(this.name); + }, - return utils.clone(this._entities); - } - }); - - /** - * Represents a specific saved search, which you can then view, modify, and - * remove. - * - * @endpoint saved/searches/{name} - * @class splunkjs.Service.SavedSearch - * @extends splunkjs.Service.Entity - */ - root.SavedSearch = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.SavedSearch - */ - path: function() { - return Paths.savedSearches + "/" + encodeURIComponent(this.name); - }, + /** + * Constructor for `splunkjs.Service.View`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name of the view. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.View} A new `splunkjs.Service.View` instance. + * + * @method splunkjs.Service.View + */ + init: function(service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + } + }); /** - * Constructor for `splunkjs.Service.SavedSearch`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name for the new saved search. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.SavedSearch} A new `splunkjs.Service.SavedSearch` instance. - * - * @method splunkjs.Service.SavedSearch - */ - init: function(service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); - - this.acknowledge = utils.bind(this, this.acknowledge); - this.dispatch = utils.bind(this, this.dispatch); - this.history = utils.bind(this, this.history); - this.suppressInfo = utils.bind(this, this.suppressInfo); - }, - - /** - * Gets the count of triggered alerts for this savedSearch, - * defaulting to 0 when undefined. + * Represents a collection of views. You can create and list views using + * this collection container, or get a specific view. * - * @example - * - * var savedSearch = service.savedSearches().item("MySavedSearch"); - * var alertCount = savedSearch.alertCount(); - * - * @return {Number} The count of triggered alerts. - * - * @method splunkjs.Service.SavedSearch - */ - alertCount: function() { - return parseInt(this.properties().triggered_alert_count, 10) || 0; - }, - - /** - * Acknowledges the suppression of the alerts from a saved search and - * resumes alerting. - * - * @example - * - * var savedSearch = service.savedSearches().item("MySavedSearch"); - * savedSearch.acknowledge(function(err, search) { - * console.log("ACKNOWLEDGED"); - * }); - * - * @param {Function} callback A function to call when the saved search is acknowledged: `(err, savedSearch)`. - * - * @endpoint saved/searches/{name}/acknowledge - * @method splunkjs.Service.SavedSearch - */ - acknowledge: function(callback) { - callback = callback || function() {}; + * @endpoint data/ui/views + * @class splunkjs.Service.Views + * @extends splunkjs.Service.Collection + */ + root.Views = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Views + */ + path: function() { + return Paths.views; + }, - var that = this; - var req = this.post("acknowledge", {}, function(err) { - callback(err, that); - }); + /** + * Creates a local instance of a view. + * + * @param {Object} props The properties for the new view. For a list of available parameters, see the POST scheduled/views/{name} endpoint in the REST API documentation. + * @return {splunkjs.Service.View} A new `splunkjs.Service.View` instance. + * + * @method splunkjs.Service.Views + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.View(this.service, props.name, entityNamespace); + }, - return req; - }, + /** + * Constructor for `splunkjs.Service.Views`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Views} A new `splunkjs.Service.Views` instance. + * + * @method splunkjs.Service.Views + */ + init: function(service, namespace) { + this._super(service, this.path(), namespace); + } + }); /** - * Dispatches a saved search, which creates a search job and returns a - * `splunkjs.Service.Job` instance in the callback function. - * - * @example - * - * var savedSearch = service.savedSearches().item("MySavedSearch"); - * savedSearch.dispatch({force_dispatch: false}, function(err, job, savedSearch) { - * console.log("Job SID: ", job.sid); - * }); - * - * @param {Object} options The options for dispatching this saved search: - * - `dispatch.now` (_string_): The time that is used to dispatch the search as though the specified time were the current time. - * - `dispatch.*` (_string_): Overwrites the value of the search field specified in *. - * - `trigger_actions` (_boolean_): Indicates whether to trigger alert actions. - * - `force_dispatch` (_boolean_): Indicates whether to start a new search if another instance of this search is already running. - * @param {Function} callback A function to call when the saved search is dispatched: `(err, job, savedSearch)`. + * Represents an index, which you can update and submit events to. * - * @endpoint saved/searches/{name}/dispatch - * @method splunkjs.Service.SavedSearch + * @endpoint data/indexes/name + * @class splunkjs.Service.Index + * @extends splunkjs.Service.Entity */ - dispatch: function(options, callback) { - if (!callback && utils.isFunction(options)) { - callback = options; - options = {}; - } + root.Index = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Index + */ + path: function() { + return Paths.indexes + "/" + encodeURIComponent(this.name); + }, - callback = callback || function() {}; - options = options || {}; + /** + * Constructor for `splunkjs.Service.Index`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name of the index. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Index} A new `splunkjs.Service.Index` instance. + * + * @method splunkjs.Service.Index + */ + init: function(service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + + this.submitEvent = utils.bind(this, this.submitEvent); + }, - var that = this; - var req = this.post("dispatch", options, function(err, response) { - if (err) { - callback(err); - return; + /** + * Submits an event to this index. + * + * @example + * + * var index = service.indexes().item("_internal"); + * index.submitEvent("A new event", {sourcetype: "mysourcetype"}, function(err, result, index) { + * console.log("Submitted event: ", result); + * }); + * + * @param {String} event The text for this event. + * @param {Object} params A dictionary of parameters for indexing: + * - `host` (_string_): The value to populate in the host field for events from this data input. + * - `host_regex` (_string_): A regular expression used to extract the host value from each event. + * - `source` (_string_): The source value to fill in the metadata for this input's events. + * - `sourcetype` (_string_): The sourcetype to apply to events from this input. + * @param {Function} callback A function to call when the event is submitted: `(err, result, index)`. + * + * @endpoint receivers/simple?index={name} + * @method splunkjs.Service.Index + */ + submitEvent: function(event, params, callback) { + if (!callback && utils.isFunction(params)) { + callback = params; + params = {}; } - var sid = response.data.sid; - var job = new root.Job(that.service, sid, that.namespace); + callback = callback || function() {}; + params = params || {}; + + // Add the index name + params["index"] = this.name; - callback(null, job, that); - }); + var that = this; + return this.service.log(event, params, function(err, result) { + callback(err, result, that); + }); + }, + + remove: function(callback) { + if (this.service.versionCompare("5.0") < 0) { + throw new Error("Indexes cannot be removed in Splunk 4.x"); + } + else { + return this._super(callback); + } + } + }); - return req; - }, - - /** - * Gets the `splunkjs.Service.FiredAlertGroup` for firedAlerts associated with this saved search. - * - * @example - * - * var alerts = service.firedAlertGroups().item("MySavedSearch"); - * - * @return {splunkjs.Service.FiredAlertGroup} An AlertGroup object with the - * same name as this SavedSearch object. - * - * @method splunkjs.Service.SavedSearch - */ - firedAlertGroup: function() { - return new root.FiredAlertGroup(this.service, this.name); - }, - /** - * Retrieves the job history for a saved search, which is a list of - * `splunkjs.Service.Job` instances. - * - * @example - * - * var savedSearch = service.savedSearches().item("MySavedSearch"); - * savedSearch.history(function(err, jobs, search) { - * for(var i = 0; i < jobs.length; i++) { - * console.log("Job", i, ":", jobs[i].sid); - * } - * }); + * Represents a collection of indexes. You can create and list indexes using + * this collection container, or get a specific index. * - * @param {Function} callback A function to call when the history is retrieved: `(err, job, savedSearch)`. - * - * @endpoint saved/searches/{name}/history - * @method splunkjs.Service.SavedSearch - */ - history: function(callback) { - callback = callback || function() {}; + * @endpoint data/indexes + * @class splunkjs.Service.Indexes + * @extends splunkjs.Service.Collection + */ + root.Indexes = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Indexes + */ + path: function() { + return Paths.indexes; + }, - var that = this; - return this.get("history", {}, function(err, response) { - if (err) { - callback(err); - return; + /** + * Creates a local instance of an index. + * + * @param {Object} props The properties for the new index. For a list of available parameters, see Index parameters on Splunk Developer Portal. + * @return {splunkjs.Service.Index} A new `splunkjs.Service.Index` instance. + * + * @method splunkjs.Service.Indexes + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.Index(this.service, props.name, entityNamespace); + }, + + /** + * Constructor for `splunkjs.Service.Indexes`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Indexes} A new `splunkjs.Service.Indexes` instance. + * + * @method splunkjs.Service.Indexes + */ + init: function(service, namespace) { + this._super(service, this.path(), namespace); + }, + + /** + * Creates an index with the given name and parameters. + * + * @example + * + * var indexes = service.indexes(); + * indexes.create("NewIndex", {assureUTF8: true}, function(err, newIndex) { + * console.log("CREATED"); + * }); + * + * @param {String} name A name for this index. + * @param {Object} params A dictionary of properties. For a list of available parameters, see Index parameters on Splunk Developer Portal. + * @param {Function} callback A function to call with the new index: `(err, createdIndex)`. + * + * @endpoint data/indexes + * @method splunkjs.Service.Indexes + */ + create: function(name, params, callback) { + // If someone called us with the default style of (params, callback), + // lets make it work + if (utils.isObject(name) && utils.isFunction(params) && !callback) { + callback = params; + params = name; + name = params.name; } - var jobs = []; - var data = response.data.entry || []; - for(var i = 0; i < data.length; i++) { - var jobData = response.data.entry[i]; - var namespace = utils.namespaceFromProperties(jobData); - var job = new root.Job(that.service, jobData.name, namespace); - - job._load(jobData); - jobs.push(job); - } + params = params || {}; + params["name"] = name; - callback(null, jobs, that); - }); - }, + return this._super(params, callback); + } + }); /** - * Retrieves the suppression state of a saved search. - * - * @example - * - * var savedSearch = service.savedSearches().item("MySavedSearch"); - * savedSearch.history(function(err, suppressionState, search) { - * console.log("STATE: ", suppressionState); - * }); - * - * @param {Function} callback A function to call when the suppression state is retrieved: `(err, suppressionState, savedSearch)`. + * Represents a specific stanza, which you can update and remove, from a + * configuration file. * - * @endpoint saved/searches/{name}/suppress - * @method splunkjs.Service.SavedSearch + * @endpoint configs/conf-{file}/{name}` + * @class splunkjs.Service.ConfigurationStanza + * @extends splunkjs.Service.Entity */ - suppressInfo: function(callback) { - callback = callback || function() {}; + root.ConfigurationStanza = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.ConfigurationStanza + */ + path: function() { + var name = this.name === "default" ? "_new" : this.name; + return Paths.configurations + "/conf-" + encodeURIComponent(this.file) + "/" + encodeURIComponent(name); + }, - var that = this; - return this.get("suppress", {}, function(err, response) { - callback(err, response.data.entry.content, that); - }); - }, + /** + * Constructor for `splunkjs.Service.ConfigurationStanza`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} file The name of the configuration file. + * @param {String} name The name of the new stanza. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.ConfigurationStanza} A new `splunkjs.Service.ConfigurationStanza` instance. + * + * @method splunkjs.Service.ConfigurationStanza + */ + init: function(service, file, name, namespace) { + this.name = name; + this.file = file; + this._super(service, this.path(), namespace); + } + }); /** - * Updates the saved search on the server. - * - * **Note:** The search query is required, even when it isn't being modified. - * If you don't provide it, this method will fetch the search string from - * the server or from the local cache. + * Represents a collection of stanzas for a specific property file. You can + * create and list stanzas using this collection container, or get a specific + * stanza. * - * @param {Object} props The properties to update the saved search with. For a list of available parameters, see Saved search parameters on Splunk Developer Portal. - * @param {Function} callback A function to call when the object is updated: `(err, entity)`. - * - * @method splunkjs.Service.SavedSearch - */ - update: function(params, callback) { - params = params || {}; + * @endpoint configs/conf-{file} + * @class splunkjs.Service.ConfigurationFile + * @extends splunkjs.Service.Collection + */ + root.ConfigurationFile = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.ConfigurationFile + */ + path: function() { + return Paths.configurations + "/conf-" + encodeURIComponent(this.name); + }, + + /** + * Creates a local instance of the default stanza in a configuration file. + * You cannot directly update the `ConfigurationStanza` returned by this function. + * + * This is equivalent to viewing `configs/conf-{file}/_new`. + * + * @return {splunkjs.Service.ConfigurationStanza} A new `splunkjs.Service.ConfigurationStanza` instance. + * + * @method splunkjs.Service.ConfigurationFile + */ + getDefaultStanza: function() { + return new root.ConfigurationStanza(this.service, this.name, "default", this.namespace); + }, + + /** + * Creates a local instance of a stanza in a configuration file. + * + * @param {Object} props The key-value properties for the new stanza. + * @return {splunkjs.Service.ConfigurationStanza} A new `splunkjs.Service.ConfigurationStanza` instance. + * + * @method splunkjs.Service.ConfigurationFile + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.ConfigurationStanza(this.service, this.name, props.name, entityNamespace); + }, - if (!params.search) { - var update = this._super; - var req = this.fetch(function(err, search) { - if (err) { - callback(err); - } - else { - params.search = search.properties().search; - update.call(search, params, function() { - if (req.wasAborted) { - return; // aborted, so ignore - } - else { - callback.apply(null, arguments); - } - }); - } - }); + /** + * Constructor for `splunkjs.Service.ConfigurationFile`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name of the configuration file. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.ConfigurationFile} A new `splunkjs.Service.ConfigurationFile` instance. + * + * @method splunkjs.Service.ConfigurationFile + */ + init: function(service, name, namespace) { + this.name = name; + this._super(service, this.path(), namespace); + }, + + /** + * Creates a stanza in this configuration file. + * + * @example + * + * var file = service.configurations().item("props"); + * file.create("my_stanza", function(err, newStanza) { + * console.log("CREATED"); + * }); + * + * @param {String} stanzaName A name for this stanza. + * @param {Object} values A dictionary of key-value pairs to put in this stanza. + * @param {Function} callback A function to call with the created stanza: `(err, createdStanza)`. + * + * @endpoint configs/conf-{file} + * @method splunkjs.Service.ConfigurationFile + */ + create: function(stanzaName, values, callback) { + // If someone called us with the default style of (params, callback), + // lets make it work + if (utils.isObject(stanzaName) && utils.isFunction(values) && !callback) { + callback = values; + values = stanzaName; + stanzaName = values.name; + } - return req; - } - else { - return this._super(params, callback); + if (utils.isFunction(values) && !callback) { + callback = values; + values = {}; + } + + values = values || {}; + values["name"] = stanzaName; + + return this._super(values, callback); } - } - }); - - /** - * Represents a collection of saved searches. You can create and list saved - * searches using this collection container, or get a specific saved search. - * - * - * @endpoint saved/searches - * @class splunkjs.Service.SavedSearches - * @extends splunkjs.Service.Collection - */ - root.SavedSearches = root.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.SavedSearches - */ - path: function() { - return Paths.savedSearches; - }, - - /** - * Creates a local instance of a saved search. - * - * @param {Object} props The properties for the new saved search. For a list of available parameters, see Saved search parameters on Splunk Developer Portal. - * @return {splunkjs.Service.SavedSearch} A new `splunkjs.Service.SavedSearch` instance. - * - * @method splunkjs.Service.SavedSearches - */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.SavedSearch(this.service, props.name, entityNamespace); - }, - - /** - * Constructor for `splunkjs.Service.SavedSearches`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.SavedSearches} A new `splunkjs.Service.SavedSearches` instance. - * - * @method splunkjs.Service.SavedSearches - */ - init: function(service, namespace) { - this._super(service, this.path(), namespace); - } - }); - - /** - * Represents a specific storage password, which you can then view, modify, and - * remove. - * - * @endpoint storage/passwords/{name} - * @class splunkjs.Service.StoragePassword - * @extends splunkjs.Service.Entity - */ - root.StoragePassword = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.StoragePassword - */ - path: function () { - return Paths.storagePasswords + "/" + encodeURIComponent(this.name); - }, - - /** - * Constructor for `splunkjs.Service.StoragePassword`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name for the new storage password. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.StoragePassword} A new `splunkjs.Service.StoragePassword` instance. - * - * @method splunkjs.Service.StoragePassword - */ - init: function (service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); - } - }); - - /** - * Represents a collection of storage passwords. You can create and list storage - * passwords using this collection container, or get a specific storage password. - * - * @endpoint storage/passwords - * @class splunkjs.Service.StoragePasswords - * @extends splunkjs.Service.Collection - */ - root.StoragePasswords = root.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.StoragePasswords - */ - path: function() { - return Paths.storagePasswords; - }, - - /** - * Creates a local instance of a storage password. - * - * @param {Object} props The properties for the new storage password. For a list of available parameters, - * see - * POST storage/passwords on Splunk Developer Portal. - * @return {splunkjs.Service.SavedSearch} A new `splunkjs.Service.StoragePassword` instance. - * - * @method splunkjs.Service.StoragePasswords - */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.StoragePassword(this.service, props.name, entityNamespace); - }, + }); /** - * Constructor for `splunkjs.Service.StoragePasswords`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.StoragePasswords} A new `splunkjs.Service.StoragePasswords` instance. - * - * @method splunkjs.Service.StoragePasswords - */ - init: function(service, namespace) { - this._super(service, this.path(), namespace); - } - }); - - /** - * Represents a fired alert. - * You can retrieve several of the fired alert's properties by - * the corresponding function name. - * - * @endpoint alerts/fired_alerts/{name} - * @class splunkjs.Service.FiredAlert - * @extends splunkjs.Service.Entity - */ - root.FiredAlert = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.FiredAlert - */ - path: function() { - return Paths.firedAlerts + "/" + encodeURIComponent(this.name); - }, - - /** - * Returns this alert's actions (such as notifying by email, running a - * script, adding to RSS, tracking in Alert Manager, and enabling - * summary indexing). - * - * @return {Array} of actions, an empty {Array} if no actions - * @method splunkjs.Service.FiredAlert - */ - actions: function() { - return this.properties().actions || []; - }, - - /** - * Returns this alert's type. - * - * @return {String} the alert's type. - * @method splunkjs.Service.FiredAlert - */ - alertType: function() { - return this.properties().alert_type || null; - }, - - /** - * Indicates whether the result is a set of events (digest) or a single - * event (per result). - * - * This method is available in Splunk 4.3 and later. - * - * @return {Boolean} true if the result is a digest, false if per result - * @method splunkjs.Service.FiredAlert - */ - isDigestMode: function() { - // Convert this property to a Boolean - return !!this.properties().digest_mode; - }, - - /** - * Returns the rendered expiration time for this alert. - * - * This method is available in Splunk 4.3 and later. - * - * @return {String} - * @method splunkjs.Service.FiredAlert - */ - expirationTime: function() { - return this.properties().expiration_time_rendered || null; - }, - - /** - * Returns the saved search for this alert. - * - * @return {String} The saved search name, or {null} if not available. - * @method splunkjs.Service.FiredAlert - */ - savedSearchName: function() { - return this.properties().savedsearch_name || null; - }, - - /** - * Returns this alert's severity on a scale of 1 to 10, with 1 being the - * highest severity. - * - * @return {Number} this alert's severity, -1 if not specified - * @method splunkjs.Service.FiredAlert - */ - severity: function() { - return parseInt(this.properties().severity, 10) || -1; - }, - - /** - * Returns this alert's search ID (SID). - * - * @return {String} This alert's SID, or {null} if not available. - * @method splunkjs.Service.FiredAlert - */ - sid: function() { - return this.properties().sid || null; - }, - - /** - * Returns the time this alert was triggered. - * - * @return {Number} This alert's trigger time, or {null} if not available. - * @method splunkjs.Service.FiredAlert - */ - triggerTime: function() { - return this.properties().trigger_time || null; - }, - - /** - * Returns this alert's rendered trigger time. - * - * This method is available in Splunk 4.3 and later. - * - * @return {String} This alert's rendered trigger time, or {null} if not available. - * @method splunkjs.Service.FiredAlert - */ - triggerTimeRendered: function() { - return this.properties().trigger_time_rendered || null; - }, - - /** - * Returns the count of triggered alerts. - * - * This method is available in Splunk 4.3 and later. - * - * @return {Number} The number of triggered alerts, or -1 if not specified. - * @method splunkjs.Service.FiredAlert - */ - triggeredAlertCount: function() { - return parseInt(this.properties().triggered_alerts, 10) || -1; - }, - - /** - * Constructor for `splunkjs.Service.FiredAlert`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name for the new alert group. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.FiredAlert} A new `splunkjs.Service.FiredAlert` instance. - * - * @method splunkjs.Service.FiredAlert - */ - init: function(service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); - } - }); - - - /** - * Represents a specific alert group, which you can then view and - * remove. - * - * @endpoint alerts/fired_alerts/{name} - * @class splunkjs.Service.FiredAlertGroup - * @extends splunkjs.Service.Entity - */ - root.FiredAlertGroup = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.FiredAlertGroup - */ - path: function() { - return Paths.firedAlerts + "/" + encodeURIComponent(this.name); - }, - - /** - * Returns the `triggered_alert_count` property, the count - * of triggered alerts. - * - * @return {Number} the count of triggered alerts - * - * @method splunkjs.Service.FiredAlertGroup - */ - count: function() { - return parseInt(this.properties().triggered_alert_count, 10) || 0; - }, - - /** - * Returns fired instances of this alert, which is - * a list of `splunkjs.Service.FiredAlert` instances. - * - * @example - * - * var alertGroup = service.firedAlertGroups().item("MyAlert"); - * alertGroup.list(function(err, firedAlerts, alert) { - * for(var i = 0; i < firedAlerts.length; i++) { - * console.log("Fired alert", i, ":", firedAlerts[i].sid); - * } - * }); + * Represents a collection of configuration files. You can create and list + * configuration files using this collection container, or get a specific file. * - * @param {Function} callback A function to call when the fired alerts are retrieved: `(err, firedAlerts, alertGroup)`. - * - * @method splunkjs.Service.FiredAlertGroup - */ - list: function(options, callback) { - if (!callback && utils.isFunction(options)) { - callback = options; - options = {}; - } - - callback = callback || function() {}; - options = options || {}; - - var that = this; - return this.get("", options, function(err, response) { - if (err) { - callback(err); - return; + * @endpoint properties + * @class splunkjs.Service.Configurations + * @extends splunkjs.Service.Collection + */ + root.Configurations = root.Collection.extend({ + /** + * Indicates whether to call `fetch` after an entity has been created. By + * default, the entity is not fetched because the endpoint returns + * (echoes) the new entity. + * + * @method splunkjs.Service.Configurations + */ + fetchOnEntityCreation: true, + + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Configurations + */ + path: function() { + return Paths.properties; + }, + + /** + * Creates a local instance of a configuration file. + * + * @param {Object} props The properties for this configuration file. + * @return {splunkjs.Service.ConfigurationFile} A new `splunkjs.Service.ConfigurationFile` instance. + * + * @method splunkjs.Service.Configurations + */ + instantiateEntity: function(props) { + return new root.ConfigurationFile(this.service, props.name, this.namespace); + }, + + /** + * Constructor for `splunkjs.Service.Configurations`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Configurations} A new `splunkjs.Service.Configurations` instance. + * + * @method splunkjs.Service.Configurations + */ + init: function(service, namespace) { + if (!namespace || namespace.owner === "-" || namespace.app === "-") { + throw new Error("Configurations requires a non-wildcard owner/app"); } - var firedAlerts = []; - var data = response.data.entry || []; - for (var i = 0; i < data.length; i++) { - var firedAlertData = response.data.entry[i]; - var namespace = utils.namespaceFromProperties(firedAlertData); - var firedAlert = new root.FiredAlert(that.service, firedAlertData.name, namespace); - firedAlert._load(firedAlertData); - firedAlerts.push(firedAlert); + this._super(service, this.path(), namespace); + }, + + /** + * Creates a configuration file. + * + * @example + * + * var configurations = service.configurations(); + * configurations.create("myprops", function(err, newFile) { + * console.log("CREATED"); + * }); + * + * @param {String} filename A name for this configuration file. + * @param {Function} callback A function to call with the new configuration file: `(err, createdFile)`. + * + * @endpoint properties + * @method splunkjs.Service.Configurations + */ + create: function(filename, callback) { + // If someone called us with the default style of (params, callback), + // lets make it work + if (utils.isObject(filename)) { + filename = filename["__conf"]; } - callback(null, firedAlerts, that); - }); - }, - - /** - * Constructor for `splunkjs.Service.FiredAlertGroup`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name for the new alert group. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.FiredAlertGroup} A new `splunkjs.Service.FiredAlertGroup` instance. - * - * @method splunkjs.Service.FiredAlertGroup - */ - init: function(service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); - - this.list = utils.bind(this, this.list); - } - }); - - /** - * Represents a collection of fired alerts for a saved search. You can - * create and list saved searches using this collection container, or - * get a specific alert group. - * - * - * @endpoint alerts/fired_alerts - * @class splunkjs.Service.FiredAlertGroupCollection - * @extends splunkjs.Service.Collection - */ - root.FiredAlertGroupCollection = root.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.FiredAlertGroupCollection - */ - path: function() { - return Paths.firedAlerts; - }, - - /** - * Creates a local instance of an alert group. - * - * @param {Object} props The properties for the alert group. - * @return {splunkjs.Service.FiredAlertGroup} A new `splunkjs.Service.FiredAlertGroup` instance. - * - * @method splunkjs.Service.FiredAlertGroupCollection - */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.FiredAlertGroup(this.service, props.name, entityNamespace); - }, - - /** - * Suppress removing alerts via the fired alerts endpoint. - * - * @method splunkjs.Service.FiredAlertGroupCollection - */ - remove: function() { - throw new Error("To remove an alert, remove the saved search with the same name."); - }, - - /** - * Constructor for `splunkjs.Service.FiredAlertGroupCollection`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.FiredAlertGroupCollection} A new `splunkjs.Service.FiredAlertGroupCollection` instance. - * - * @method splunkjs.Service.FiredAlertGroupCollection - */ - init: function(service, namespace) { - this._super(service, this.path(), namespace); - - this.instantiateEntity = utils.bind(this, this.instantiateEntity); - this.remove = utils.bind(this, this.remove); - } - }); + callback = callback || function() {}; + + var that = this; + var req = this.post("", {__conf: filename}, function(err, response) { + if (err) { + callback(err); + } + else { + var entity = new root.ConfigurationFile(that.service, filename); + entity.fetch(function() { + if (req.wasAborted) { + return; // aborted, so ignore + } + else { + callback.apply(null, arguments); + } + }); + } + }); + + return req; + } + }); - /** - * Represents a specific Splunk app that you can view, modify, and - * remove. - * - * @endpoint apps/local/{name} - * @class splunkjs.Service.Application - * @extends splunkjs.Service.Entity - */ - root.Application = root.Entity.extend({ /** - * Indicates whether to call `fetch` after an update to get the updated - * item. + * Represents a specific search job. You can perform different operations + * on this job, such as reading its status, canceling it, and getting results. * - * @method splunkjs.Service.Application + * @endpoint search/jobs/{search_id} + * @class splunkjs.Service.Job + * @extends splunkjs.Service.Entity */ - fetchOnUpdate: true, - - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Application - */ - path: function() { - return Paths.apps + "/" + encodeURIComponent(this.name); - }, - - /** - * Constructor for `splunkjs.Service.Application`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name of the Splunk app. - * @return {splunkjs.Service.Application} A new `splunkjs.Service.Application` instance. - * - * @method splunkjs.Service.Application - */ - init: function(service, name) { - this.name = name; - this._super(service, this.path(), {}); - - this.setupInfo = utils.bind(this, this.setupInfo); - this.updateInfo = utils.bind(this, this.updateInfo); - }, - - /** - * Retrieves the setup information for a Splunk app. - * - * @example - * - * var app = service.apps().item("app"); - * app.setup(function(err, info, search) { - * console.log("SETUP INFO: ", info); - * }); - * - * @param {Function} callback A function to call when setup information is retrieved: `(err, info, app)`. - * - * @endpoint apps/local/{name}/setup - * @method splunkjs.Service.Application - */ - setupInfo: function(callback) { - callback = callback || function() {}; - - var that = this; - return this.get("setup", {}, function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data.entry.content, that); - } - }); - }, - - /** - * Retrieves any information for an update to a locally-installed Splunk app. - * - * @example - * - * var app = service.apps().item("MyApp"); - * app.updateInfo(function(err, info, app) { - * console.log("UPDATE INFO: ", info); - * }); - * - * @param {Function} callback A function to call when update information is retrieved: `(err, info, app)`. - * - * @endpoint apps/local/{name}/update - * @method splunkjs.Service.Application - */ - updateInfo: function(callback) { - callback = callback || function() {}; + root.Job = root.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Job + */ + path: function() { + return Paths.jobs + "/" + encodeURIComponent(this.name); + }, - var that = this; - return this.get("update", {}, function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data.entry.content, that); - } - }); - } - }); + /** + * Constructor for `splunkjs.Service.Job`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} sid The search ID for this search job. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Job} A new `splunkjs.Service.Job` instance. + * + * @method splunkjs.Service.Job + */ + init: function(service, sid, namespace) { + this.name = sid; + this._super(service, this.path(), namespace); + this.sid = sid; - /** - * Represents a collection of Splunk apps. You can create and list applications - * using this collection container, or get a specific app. - * - * @endpoint apps/local - * @class splunkjs.Service.Applications - * @extends splunkjs.Service.Collection - */ - root.Applications = root.Collection.extend({ - /** - * Indicates whether to call `fetch` after an entity has been created. By - * default, the entity is not fetched because the endpoint returns - * (echoes) the new entity. - * - * @method splunkjs.Service.Applications - */ - fetchOnEntityCreation: true, - - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Applications - */ - path: function() { - return Paths.apps; - }, - - /** - * Creates a local instance of an app. - * - * @param {Object} props The properties for the new app. For details, see the POST apps/local endpoint in the REST API documentation. - * @return {splunkjs.Service.Application} A new `splunkjs.Service.Application` instance. - * - * @method splunkjs.Service.Applications - */ - instantiateEntity: function(props) { - return new root.Application(this.service, props.name, {}); - }, + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this.cancel = utils.bind(this, this.cancel); + this.disablePreview = utils.bind(this, this.disablePreview); + this.enablePreview = utils.bind(this, this.enablePreview); + this.events = utils.bind(this, this.events); + this.finalize = utils.bind(this, this.finalize); + this.pause = utils.bind(this, this.pause); + this.preview = utils.bind(this, this.preview); + this.results = utils.bind(this, this.results); + this.searchlog = utils.bind(this, this.searchlog); + this.setPriority = utils.bind(this, this.setPriority); + this.setTTL = utils.bind(this, this.setTTL); + this.summary = utils.bind(this, this.summary); + this.timeline = utils.bind(this, this.timeline); + this.touch = utils.bind(this, this.touch); + this.unpause = utils.bind(this, this.unpause); + }, + + /** + * Cancels a search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.cancel(function(err) { + * console.log("CANCELLED"); + * }); + * + * @param {Function} callback A function to call when the search is done: `(err)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + cancel: function(callback) { + var req = this.post("control", {action: "cancel"}, callback); - /** - * Constructor for `splunkjs.Service.Applications`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @return {splunkjs.Service.Applications} A new `splunkjs.Service.Applications` instance. - * - * @method splunkjs.Service.Applications - */ - init: function(service) { - this._super(service, this.path(), {}); - } - }); + return req; + }, - /** - * Provides access to configuration information about the server. - * - * @endpoint server/info - * @class splunkjs.Service.ServerInfo - * @extends splunkjs.Service.Entity - */ - root.ServerInfo = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.ServerInfo - */ - path: function() { - return Paths.info; - }, - - /** - * Constructor for `splunkjs.Service.ServerInfo`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @return {splunkjs.Service.ServerInfo} A new `splunkjs.Service.ServerInfo` instance. - * - * @method splunkjs.Service.ServerInfo - */ - init: function(service) { - this.name = "server-info"; - this._super(service, this.path(), {}); - } - }); + /** + * Disables preview generation for a search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.disablePreview(function(err, job) { + * console.log("PREVIEW DISABLED"); + * }); + * + * @param {Function} callback A function to call with this search job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + disablePreview: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "disablepreview"}, function(err) { + callback(err, that); + }); + + return req; + }, - /** - * Represents a specific Splunk user, which you can view, modify, and - * remove. - * - * @endpoint authentication/users/{name} - * @class splunkjs.Service.User - * @extends splunkjs.Service.Entity - */ - root.User = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.User - */ - path: function() { - return Paths.users + "/" + encodeURIComponent(this.name); - }, - - /** - * Constructor for `splunkjs.Service.User`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The Splunk username. - * @return {splunkjs.Service.User} A new `splunkjs.Service.User` instance. - * - * @method splunkjs.Service.User - */ - init: function(service, name) { - this.name = name; - this._super(service, this.path(), {}); - } - }); + /** + * Enables preview generation for a search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.disablePreview(function(err, job) { + * console.log("PREVIEW ENABLED"); + * }); + * + * @param {Function} callback A function to call with this search job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + enablePreview: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "enablepreview"}, function(err) { + callback(err, that); + }); + + return req; + }, - /** - * Represents a collection of users. You can create and list users using - * this collection container, or get a specific user. - * - * @endpoint authentication/users - * @class splunkjs.Service.Users - * @extends splunkjs.Service.Collection - */ - root.Users = root.Collection.extend({ - /** - * Indicates whether to call `fetch` after an entity has been created. By - * default, the entity is not fetched because the endpoint returns - * (echoes) the new entity. - * - * @method splunkjs.Service.Users - */ - fetchOnEntityCreation: true, - - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Users - */ - path: function() { - return Paths.users; - }, - - /** - * Creates a local instance of a user. - * - * @param {Object} props The properties for this new user. For a list of available parameters, see User authentication parameters on Splunk Developer Portal. - * @return {splunkjs.Service.User} A new `splunkjs.Service.User` instance. - * - * @method splunkjs.Service.Users - */ - instantiateEntity: function(props) { - return new root.User(this.service, props.name, {}); - }, - - /** - * Constructor for `splunkjs.Service.Users`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @return {splunkjs.Service.Users} A new `splunkjs.Service.Users` instance. - * - * @method splunkjs.Service.Users - */ - init: function(service) { - this._super(service, this.path(), {}); - }, - - /** - * Creates a new user. - * - * **Note:** This endpoint requires a special implementation. - * - * @param {Object} params A dictionary of properties. For a list of available parameters, see User authentication parameters on Splunk Developer Portal. - * @param {Function} callback A function to call with the new entity: `(err, createdEntity)`. - * - * @method splunkjs.Service.Users - */ - create: function(params, callback) { - callback = callback || function() {}; - - var that = this; - var req = this.post("", params, function(err, response) { - if (err) { - callback(err); - } - else { - // This endpoint requires us to use the passed-in name - var props = {name: params.name}; - - var entity = that.instantiateEntity(props); - entity.fetch(function() { - if (req.wasAborted) { - return; // aborted, so ignore - } - else { - callback.apply(null, arguments); - } - }); - } - }); + /** + * Returns the events of a search job with given parameters. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.events({count: 10}, function(err, events, job) { + * console.log("Fields: ", events.fields); + * }); + * + * @param {Object} params The parameters for retrieving events. For a list of available parameters, see the GET search/jobs/{search_id}/events endpoint in the REST API documentation. + * @param {Function} callback A function to call when the events are retrieved: `(err, events, job)`. + * + * @endpoint search/jobs/{search_id}/events + * @method splunkjs.Service.Job + */ + events: function(params, callback) { + callback = callback || function() {}; + params = params || {}; + params.output_mode = params.output_mode || "json_rows"; + + var that = this; + return this.get("events", params, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data, that); + } + }); + }, + + /** + * Finalizes a search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.finalize(function(err, job) { + * console.log("JOB FINALIZED"); + * }); + * + * @param {Function} callback A function to call with the job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + finalize: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "finalize"}, function(err) { + callback(err, that); + }); + + return req; + }, - return req; - } - }); + /** + * Returns an iterator over this search job's events or results. + * + * @param {String} type One of {"events", "preview", "results"}. + * @param {Object} params A dictionary of optional parameters: + * - `pagesize` (_integer_): The number of items to return on each request. Defaults to as many as possible. + * @return {Object} An iterator object with a `next(callback)` method, where `callback` is of the form `(err, results, hasMoreResults)`. + * + * @endpoint search/jobs/{search_id}/results + * @method splunkjs.Service.Job + */ + iterator: function(type, params) { + return new root.PaginatedEndpointIterator(this[type], params); + }, - /** - * Represents a specific Splunk view, which you can view, modify, and - * remove. - * - * @endpoint data/ui/views/{name} - * @class splunkjs.Service.View - * @extends splunkjs.Service.Entity - */ - root.View = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.View - */ - path: function() { - return Paths.views + "/" + encodeURIComponent(this.name); - }, - - /** - * Constructor for `splunkjs.Service.View`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name of the view. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.View} A new `splunkjs.Service.View` instance. - * - * @method splunkjs.Service.View - */ - init: function(service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); - } - }); + /** + * Pauses a search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.pause(function(err, job) { + * console.log("JOB PAUSED"); + * }); + * + * @param {Function} callback A function to call with the job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + pause: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "pause"}, function(err) { + callback(err, that); + }); + + return req; + }, - /** - * Represents a collection of views. You can create and list views using - * this collection container, or get a specific view. - * - * @endpoint data/ui/views - * @class splunkjs.Service.Views - * @extends splunkjs.Service.Collection - */ - root.Views = root.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Views - */ - path: function() { - return Paths.views; - }, - - /** - * Creates a local instance of a view. - * - * @param {Object} props The properties for the new view. For a list of available parameters, see the POST scheduled/views/{name} endpoint in the REST API documentation. - * @return {splunkjs.Service.View} A new `splunkjs.Service.View` instance. - * - * @method splunkjs.Service.Views - */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.View(this.service, props.name, entityNamespace); - }, - - /** - * Constructor for `splunkjs.Service.Views`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Views} A new `splunkjs.Service.Views` instance. - * - * @method splunkjs.Service.Views - */ - init: function(service, namespace) { - this._super(service, this.path(), namespace); - } - }); + /* + * Gets the preview results for a search job with given parameters. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.preview({count: 10}, function(err, results, job) { + * console.log("Fields: ", results.fields); + * }); + * + * @param {Object} params The parameters for retrieving preview results. For a list of available parameters, see the GET search/jobs/{search_id}/results_preview endpoint in the REST API documentation. + * @param {Function} callback A function to call when the preview results are retrieved : `(err, results, job)`. + * + * @endpoint search/jobs/{search_id}/results_preview + * @method splunkjs.Service.Job + */ + preview: function(params, callback) { + callback = callback || function() {}; + params = params || {}; + params.output_mode = params.output_mode || "json_rows"; + + var that = this; + return this.get("results_preview", params, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data, that); + } + }); + }, - /** - * Represents an index, which you can update and submit events to. - * - * @endpoint data/indexes/name - * @class splunkjs.Service.Index - * @extends splunkjs.Service.Entity - */ - root.Index = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Index - */ - path: function() { - return Paths.indexes + "/" + encodeURIComponent(this.name); - }, - - /** - * Constructor for `splunkjs.Service.Index`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name of the index. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Index} A new `splunkjs.Service.Index` instance. - * - * @method splunkjs.Service.Index - */ - init: function(service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); + /** + * Gets the results for a search job with given parameters. + * + * The callback can get `undefined` for its `results` parameter if the + * job is not yet done. To avoid this, use the `Job.track()` method to + * wait until the job is complete prior to fetching the results with + * this method. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.results({count: 10}, function(err, results, job) { + * console.log("Fields: ", results.results); + * }); + * + * @param {Object} params The parameters for retrieving search results. For a list of available parameters, see the GET search/jobs/{search_id}/results endpoint in the REST API documentation. + * @param {Function} callback A function to call when the results are retrieved: `(err, results, job)`. + * + * @endpoint search/jobs/{search_id}/results + * @method splunkjs.Service.Job + */ + results: function(params, callback) { + callback = callback || function() {}; + params = params || {}; + params.output_mode = params.output_mode || "json_rows"; + + var that = this; + return this.get("results", params, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data, that); + } + }); + }, + + /** + * Gets the search log for this search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.searchlog(function(err, searchlog, job) { + * console.log(searchlog); + * }); + * + * @param {Function} callback A function to call with the search log and job: `(err, searchlog, job)`. + * + * @endpoint search/jobs/{search_id}/search.log + * @method splunkjs.Service.Job + */ + searchlog: function(callback) { + callback = callback || function() {}; + + var that = this; + return this.get("search.log", {}, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data, that); + } + }); + }, + + /** + * Sets the priority for this search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.setPriority(6, function(err, job) { + * console.log("JOB PRIORITY SET"); + * }); + * + * @param {Number} value The priority (an integer between 1-10). A higher value means a higher priority. + * @param {Function} callback A function to call with the search job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + setPriority: function(value, callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "setpriority", priority: value}, function(err) { + callback(err, that); + }); + + return req; + }, + + /** + * Sets the time to live (TTL) for the search job, which is the time before + * the search job expires after it has been completed and is still available. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.setTTL(1000, function(err, job) { + * console.log("JOB TTL SET"); + * }); + * + * @param {Number} value The time to live, in seconds. + * @param {Function} callback A function to call with the search job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + setTTL: function(value, callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "setttl", ttl: value}, function(err) { + callback(err, that); + }); + + return req; + }, + + /** + * Gets the summary for this search job with the given parameters. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.summary({top_count: 5}, function(err, summary, job) { + * console.log("Summary: ", summary); + * }); + * + * @param {Object} params The parameters for retrieving the summary. For a list of available parameters, see the GET search/jobs/{search_id}/summary endpoint in the REST API documentation. + * @param {Function} callback A function to call with the summary and search job: `(err, summary, job)`. + * + * @endpoint search/jobs/{search_id}/summmary + * @method splunkjs.Service.Job + */ + summary: function(params, callback) { + callback = callback || function() {}; + + var that = this; + return this.get("summary", params, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data, that); + } + }); + }, + + /** + * Gets the timeline for this search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.timeline({time_format: "%c"}, function(err, job, timeline) { + * console.log("Timeline: ", timeline); + * }); + * + * @param {Object} params The parameters for retrieving the timeline. For a list of available parameters, see the GET search/jobs/{search_id}/timeline endpoint in the REST API documentation. + * @param {Function} callback A function to call with the timeline and search job: `(err, timeline, job)`. + * + * @endpoint search/jobs/{search_id}/timeline + * @method splunkjs.Service.Job + */ + timeline: function(params, callback) { + callback = callback || function() {}; + + var that = this; + return this.get("timeline", params, function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data, that); + } + }); + }, + + /** + * Touches a search job, which means extending the expiration time of + * the search to now plus the time to live (TTL). + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.touch(function(err) { + * console.log("JOB TOUCHED"); + * }); + * + * @param {Function} callback A function to call with the search job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + touch: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "touch"}, function(err) { + callback(err, that); + }); + + return req; + }, - this.submitEvent = utils.bind(this, this.submitEvent); - }, - + /** + * Starts polling the status of this search job, and fires callbacks + * upon each status change. + * + * @param {Object} options A dictionary of optional parameters: + * - `period` (_integer_): The number of milliseconds to wait between each poll. Defaults to 500. + * @param {Object|Function} callbacks A dictionary of optional callbacks: + * - `ready`: A function `(job)` invoked when the job's properties first become available. + * - `progress`: A function `(job)` invoked whenever new job properties are available. + * - `done`: A function `(job)` invoked if the job completes successfully. No further polling is done. + * - `failed`: A function `(job)` invoked if the job fails executing on the server. No further polling is done. + * - `error`: A function `(err)` invoked if an error occurs while polling. No further polling is done. + * Or, if a function `(job)`, equivalent to passing it as a `done` callback. + * + * @method splunkjs.Service.Job + */ + track: function(options, callbacks) { + var period = options.period || 500; // ms + + if (utils.isFunction(callbacks)) { + callbacks = { + done: callbacks + }; + } + + var noCallbacksAfterReady = ( + !callbacks.progress && + !callbacks.done && + !callbacks.failed && + !callbacks.error + ); + + callbacks.ready = callbacks.ready || function() {}; + callbacks.progress = callbacks.progress || function() {}; + callbacks.done = callbacks.done || function() {}; + callbacks.failed = callbacks.failed || function() {}; + callbacks.error = callbacks.error || function() {}; + + // For use by tests only + callbacks._preready = callbacks._preready || function() {}; + callbacks._stoppedAfterReady = callbacks._stoppedAfterReady || function() {}; + + var that = this; + var emittedReady = false; + var doneLooping = false; + Async.whilst( + function() { return !doneLooping; }, + function(nextIteration) { + that.fetch(function(err, job) { + if (err) { + nextIteration(err); + return; + } + + var dispatchState = job.properties().dispatchState; + var notReady = dispatchState === "QUEUED" || dispatchState === "PARSING"; + if (notReady) { + callbacks._preready(job); + } + else { + if (!emittedReady) { + callbacks.ready(job); + emittedReady = true; + + // Optimization: Don't keep polling the job if the + // caller only cares about the `ready` event. + if (noCallbacksAfterReady) { + callbacks._stoppedAfterReady(job); + + doneLooping = true; + nextIteration(); + return; + } + } + + callbacks.progress(job); + + var props = job.properties(); + + if (dispatchState === "DONE" && props.isDone) { + callbacks.done(job); + + doneLooping = true; + nextIteration(); + return; + } + else if (dispatchState === "FAILED" && props.isFailed) { + callbacks.failed(job); + + doneLooping = true; + nextIteration(); + return; + } + } + + Async.sleep(period, nextIteration); + }); + }, + function(err) { + if (err) { + callbacks.error(err); + } + } + ); + }, + + /** + * Resumes a search job. + * + * @example + * + * var job = service.jobs().item("mysid"); + * job.unpause(function(err) { + * console.log("JOB UNPAUSED"); + * }); + * + * @param {Function} callback A function to call with the search job: `(err, job)`. + * + * @endpoint search/jobs/{search_id}/control + * @method splunkjs.Service.Job + */ + unpause: function(callback) { + callback = callback || function() {}; + + var that = this; + var req = this.post("control", {action: "unpause"}, function(err) { + callback(err, that); + }); + + return req; + } + }); + /** - * Submits an event to this index. + * Represents a collection of search jobs. You can create and list search + * jobs using this collection container, or get a specific search job. * - * @example - * - * var index = service.indexes().item("_internal"); - * index.submitEvent("A new event", {sourcetype: "mysourcetype"}, function(err, result, index) { - * console.log("Submitted event: ", result); - * }); - * - * @param {String} event The text for this event. - * @param {Object} params A dictionary of parameters for indexing: - * - `host` (_string_): The value to populate in the host field for events from this data input. - * - `host_regex` (_string_): A regular expression used to extract the host value from each event. - * - `source` (_string_): The source value to fill in the metadata for this input's events. - * - `sourcetype` (_string_): The sourcetype to apply to events from this input. - * @param {Function} callback A function to call when the event is submitted: `(err, result, index)`. - * - * @endpoint receivers/simple?index={name} - * @method splunkjs.Service.Index - */ - submitEvent: function(event, params, callback) { - if (!callback && utils.isFunction(params)) { - callback = params; - params = {}; - } - - callback = callback || function() {}; - params = params || {}; + * @endpoint search/jobs + * @class splunkjs.Service.Jobs + * @extends splunkjs.Service.Collection + */ + root.Jobs = root.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.Jobs + */ + path: function() { + return Paths.jobs; + }, - // Add the index name - params["index"] = this.name; + /** + * Creates a local instance of a job. + * + * @param {Object} props The properties for this new job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * @return {splunkjs.Service.Job} A new `splunkjs.Service.Job` instance. + * + * @method splunkjs.Service.Jobs + */ + instantiateEntity: function(props) { + var sid = props.content.sid; + var entityNamespace = utils.namespaceFromProperties(props); + return new root.Job(this.service, sid, entityNamespace); + }, - var that = this; - return this.service.log(event, params, function(err, result) { - callback(err, result, that); - }); - }, - - remove: function(callback) { - if (this.service.versionCompare("5.0") < 0) { - throw new Error("Indexes cannot be removed in Splunk 4.x"); - } - else { - return this._super(callback); + /** + * Constructor for `splunkjs.Service.Jobs`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace Namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @return {splunkjs.Service.Jobs} A new `splunkjs.Service.Jobs` instance. + * + * @method splunkjs.Service.Jobs + */ + init: function(service, namespace) { + this._super(service, this.path(), namespace); + + // We perform the bindings so that every function works + // properly when it is passed as a callback. + this.create = utils.bind(this, this.create); + }, + + /** + * Creates a search job with a given search query and optional parameters, including `exec_mode` to specify the type of search: + * + * - Use `exec_mode=normal` to return a search job ID immediately (default). + * Poll for completion to find out when you can retrieve search results. + * + * - Use `exec_mode=blocking` to return the search job ID when the search has finished. + * + * To run a oneshot search, which does not create a job but rather returns the search results, use `Service.Jobs.oneshotSearch`. + * + * @param {String} query The search query. + * @param {Object} params A dictionary of properties for the search job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * @param {Function} callback A function to call with the created job: `(err, createdJob)`. + * + * @endpoint search/jobs + * @method splunkjs.Service.Jobs + */ + create: function(query, params, callback) { + // If someone called us with the default style of (params, callback), + // lets make it work + if (utils.isObject(query) && utils.isFunction(params) && !callback) { + callback = params; + params = query; + query = params.search; + } + + callback = callback || function() {}; + params = params || {}; + params.search = query; + + if ((params.exec_mode || "").toLowerCase() === "oneshot") { + throw new Error("Please use splunkjs.Service.Jobs.oneshotSearch for exec_mode=oneshot"); + } + + if (!params.search) { + callback("Must provide a query to create a search job"); + return; + } + var that = this; + return this.post("", params, function(err, response) { + if (err) { + callback(err); + } + else { + var job = new root.Job(that.service, response.data.sid, that.namespace); + callback(null, job); + } + }); + }, + + /** + * Creates a search job with a given search query and optional parameters, including `exec_mode` to specify the type of search: + * + * - Use `exec_mode=normal` to return a search job ID immediately (default). + * Poll for completion to find out when you can retrieve search results. + * + * - Use `exec_mode=blocking` to return the search job ID when the search has finished. + * + * To run a oneshot search, which does not create a job but rather returns the search results, use `Service.Jobs.oneshotSearch`. + * + * @example + * + * var jobs = service.jobs(); + * jobs.search("search ERROR", {id: "myjob_123"}, function(err, newJob) { + * console.log("CREATED": newJob.sid); + * }); + * + * @param {String} query The search query. + * @param {Object} params A dictionary of properties for the search job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. + * @param {Function} callback A function to call with the new search job: `(err, createdJob)`. + * + * @endpoint search/jobs + * @method splunkjs.Service.Jobs + */ + search: function(query, params, callback) { + return this.create(query, params, callback); + }, + + /** + * Creates a oneshot search from a given search query and parameters. + * + * @example + * + * var jobs = service.jobs(); + * jobs.oneshotSearch("search ERROR", {id: "myjob_123"}, function(err, results) { + * console.log("RESULT FIELDS": results.fields); + * }); + * + * @param {String} query The search query. + * @param {Object} params A dictionary of properties for the search: + * - `output_mode` (_string_): Specifies the output format of the results (XML, JSON, or CSV). + * - `earliest_time` (_string_): Specifies the earliest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. + * - `latest_time` (_string_): Specifies the latest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. + * - `rf` (_string_): Specifies one or more fields to add to the search. + * @param {Function} callback A function to call with the results of the search: `(err, results)`. + * + * @endpoint search/jobs + * @method splunkjs.Service.Jobs + */ + oneshotSearch: function(query, params, callback) { + // If someone called us with the default style of (params, callback), + // lets make it work + if (utils.isObject(query) && utils.isFunction(params) && !callback) { + callback = params; + params = query; + query = params.search; + } + + callback = callback || function() {}; + params = params || {}; + params.search = query; + params.exec_mode = "oneshot"; + + if (!params.search) { + callback("Must provide a query to create a search job"); + } + + var outputMode = params.output_mode || "json_rows"; + + var path = this.qualifiedPath; + var method = "POST"; + var headers = {}; + var post = params; + var get = {output_mode: outputMode}; + var body = null; + + var req = this.service.request( + path, + method, + get, + post, + body, + headers, + function(err, response) { + if (err) { + callback(err); + } + else { + callback(null, response.data); + } + } + ); + + return req; } - } - }); - - /** - * Represents a collection of indexes. You can create and list indexes using - * this collection container, or get a specific index. - * - * @endpoint data/indexes - * @class splunkjs.Service.Indexes - * @extends splunkjs.Service.Collection - */ - root.Indexes = root.Collection.extend({ + }); + /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Indexes + * Represents a field of a data model object. + * This is a helper class for `DataModelCalculation` + * and `DataModelObject`. + * + * Has these properties: + * - `fieldName` (_string_): The name of this field. + * - `displayName` (_string_): A human readable name for this field. + * - `type` (_string_): The type of this field. + * - `multivalued` (_boolean_): Whether this field is multivalued. + * - `required` (_boolean_): Whether this field is required. + * - `hidden` (_boolean_): Whether this field should be displayed in a data model UI. + * - `editable` (_boolean_): Whether this field can be edited. + * - `comment` (_string_): A comment for this field, or `null` if there isn't one. + * - `fieldSearch` (_string_): A search query fragment for this field. + * - `lineage` (_array_): An array of strings of the lineage of the data model + * on which this field is defined. + * - `owner` (_string_): The name of the data model object on which this field is defined. + * + * Possible types for a data model field: + * - `string` + * - `boolean` + * - `number` + * - `timestamp` + * - `objectCount` + * - `childCount` + * - `ipv4` + * + * @class splunkjs.Service.DataModelField */ - path: function() { - return Paths.indexes; - }, + root.DataModelField = Class.extend({ + _types: [ "string", "number", "timestamp", "objectCount", "childCount", "ipv4", "boolean"], + + /** + * Constructor for a data model field. + * SDK users are not expected to invoke this constructor directly. + * + * @constructor + * @param {Object} props A dictionary of properties to set: + * - `fieldName` (_string_): The name of this field. + * - `displayName` (_string_): A human readable name for this field. + * - `type` (_string_): The type of this field, see valid types in class docs. + * - `multivalue` (_boolean_): Whether this field is multivalued. + * - `required` (_boolean_): Whether this field is required on events in the object + * - `hidden` (_boolean_): Whether this field should be displayed in a data model UI. + * - `editable` (_boolean_): Whether this field can be edited. + * - `comment` (_string_): A comment for this field, or `null` if there isn't one. + * - `fieldSearch` (_string_): A search query fragment for this field. + * - `lineage` (_string_): The lineage of the data model object on which this field + * is defined, items are delimited by a dot. This is converted into an array of + * strings upon construction. + * + * @method splunkjs.Service.DataModelField + */ + init: function(props) { + props = props || {}; + props.owner = props.owner || ""; + + this.name = props.fieldName; + this.displayName = props.displayName; + this.type = props.type; + this.multivalued = props.multivalue; + this.required = props.required; + this.hidden = props.hidden; + this.editable = props.editable; + this.comment = props.comment || null; + this.fieldSearch = props.fieldSearch; + this.lineage = props.owner.split("."); + this.owner = this.lineage[this.lineage.length - 1]; + }, + + /** + * Is this data model field of type string? + * + * @return {Boolean} True if this data model field is of type string. + * + * @method splunkjs.Service.DataModelField + */ + isString: function() { + return "string" === this.type; + }, + + /** + * Is this data model field of type number? + * + * @return {Boolean} True if this data model field is of type number. + * + * @method splunkjs.Service.DataModelField + */ + isNumber: function() { + return "number" === this.type; + }, + + /** + * Is this data model field of type timestamp? + * + * @return {Boolean} True if this data model field is of type timestamp. + * + * @method splunkjs.Service.DataModelField + */ + isTimestamp: function() { + return "timestamp" === this.type; + }, + + /** + * Is this data model field of type object count? + * + * @return {Boolean} True if this data model field is of type object count. + * + * @method splunkjs.Service.DataModelField + */ + isObjectcount: function() { + return "objectCount" === this.type; + }, + + /** + * Is this data model field of type child count? + * + * @return {Boolean} True if this data model field is of type child count. + * + * @method splunkjs.Service.DataModelField + */ + isChildcount: function() { + return "childCount" === this.type; + }, + + /** + * Is this data model field of type ipv4? + * + * @return {Boolean} True if this data model field is of type ipv4. + * + * @method splunkjs.Service.DataModelField + */ + isIPv4: function() { + return "ipv4" === this.type; + }, + + /** + * Is this data model field of type boolean? + * + * @return {Boolean} True if this data model field is of type boolean. + * + * @method splunkjs.Service.DataModelField + */ + isBoolean: function() { + return "boolean" === this.type; + } + }); /** - * Creates a local instance of an index. + * Represents a constraint on a `DataModelObject` or a `DataModelField`. * - * @param {Object} props The properties for the new index. For a list of available parameters, see Index parameters on Splunk Developer Portal. - * @return {splunkjs.Service.Index} A new `splunkjs.Service.Index` instance. + * Has these properties: + * - `query` (_string_): The search query defining this data model constraint. + * - `lineage` (_array_): The lineage of this data model constraint. + * - `owner` (_string_): The name of the data model object that owns + * this data model constraint. * - * @method splunkjs.Service.Indexes + * @class splunkjs.Service.DataModelConstraint */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.Index(this.service, props.name, entityNamespace); - }, + root.DataModelConstraint = Class.extend({ + /** + * Constructor for a data model constraint. + * SDK users are not expected to invoke this constructor directly. + * + * @constructor + * @param {Object} props A dictionary of properties to set: + * - `search` (_string_): The Splunk search query this constraint specifies. + * - `owner` (_string_): The lineage of the data model object that owns this + * constraint, items are delimited by a dot. This is converted into + * an array of strings upon construction. + * + * @method splunkjs.Service.DataModelConstraint + */ + init: function(props) { + props = props || {}; + props.owner = props.owner || ""; + + this.query = props.search; + this.lineage = props.owner.split("."); + this.owner = this.lineage[this.lineage.length - 1]; + } + }); /** - * Constructor for `splunkjs.Service.Indexes`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Indexes} A new `splunkjs.Service.Indexes` instance. - * - * @method splunkjs.Service.Indexes - */ - init: function(service, namespace) { - this._super(service, this.path(), namespace); - }, + * Used for specifying a calculation on a `DataModelObject`. + * + * Has these properties: + * - `id` (_string_): The ID for this data model calculation. + * - `type` (_string_): The type of this data model calculation. + * - `comment` (_string_|_null_): The comment for this data model calculation, or `null`. + * - `editable` (_boolean_): True if this calculation can be edited, false otherwise. + * - `lineage` (_array_): The lineage of the data model object on which this calculation + * is defined in an array of strings. + * - `owner` (_string_): The data model that this calculation belongs to. + * - `outputFields` (_array_): The fields output by this calculation. + * + * The Rex and Eval types have an additional property: + * - `expression` (_string_): The expression to use for this calculation. + * + * The Rex and GeoIP types have an additional property: + * - `inputField` (_string_): The field to use for calculation. + * + * The Lookup type has additional properties: + * - `lookupName` (_string_): The name of the lookup to perform. + * - `inputFieldMappings` (_object_): The mappings from fields in the events to fields in the lookup. + * + * Valid types of calculations are: + * - `Lookup` + * - `Eval` + * - `GeoIP` + * - `Rex` + * + * @class splunkjs.Service.DataModelCalculation + */ + root.DataModelCalculation = Class.extend({ + _types: ["Lookup", "Eval", "GeoIP", "Rex"], + + /** + * Constructor for a data model calculation. + * SDK users are not expected to invoke this constructor directly. + * + * @constructor + * @param {Object} props A dictionary of properties to set: + * - `calculationID` (_string_): The ID of this calculation. + * - `calculationType` (_string_): The type of this calculation, see class docs for valid types. + * - `editable` (_boolean_): Whether this calculation can be edited. + * - `comment` (_string_): A comment for this calculation, or `null` if there isn't one. + * - `owner` (_string_): The lineage of the data model object on which this calculation + * is defined, items are delimited by a dot. This is converted into an array of + * strings upon construction. + * - `outputFields` (_array_): An array of the fields this calculation generates. + * - `expression` (_string_): The expression to use for this calculation; exclusive to `Eval` and `Rex` calculations (optional) + * - `inputField` (_string_): The field to use for calculation; exclusive to `GeoIP` and `Rex` calculations (optional) + * - `lookupName` (_string_): The name of the lookup to perform; exclusive to `Lookup` calculations (optional) + * - `inputFieldMappings` (_array_): One element array containing an object with the mappings from fields in the events to fields + * in the lookup; exclusive to `Lookup` calculations (optional) + * + * @method splunkjs.Service.DataModelCalculation + */ + init: function(props) { + props = props || {}; + props.owner = props.owner || ""; + + this.id = props.calculationID; + this.type = props.calculationType; + this.comment = props.comment || null; + this.editable = props.editable; + this.lineage = props.owner.split("."); + this.owner = this.lineage[this.lineage.length - 1]; + + this.outputFields = []; + for (var i = 0; i < props.outputFields.length; i++) { + this.outputFields[props.outputFields[i].fieldName] = new root.DataModelField(props.outputFields[i]); + } + + if ("Eval" === this.type || "Rex" === this.type) { + this.expression = props.expression; + } + if ("GeoIP" === this.type || "Rex" === this.type) { + this.inputField = props.inputField; + } + if ("Lookup" === this.type) { + this.lookupName = props.lookupName; + this.inputFieldMappings = props.lookupInputs[0]; + } + }, + + /** + * Returns an array of strings of output field names. + * + * @return {Array} An array of strings of output field names. + * + * @method splunkjs.Service.DataModelCalculation + */ + outputFieldNames: function() { + return Object.keys(this.outputFields); + }, + + /** + * Is this data model calculation editable? + * + * @return {Boolean} True if this data model calculation is editable. + * + * @method splunkjs.Service.DataModelCalculation + */ + isEditable: function() { + return !!this.editable; + }, + + /** + * Is this data model calculation of type lookup? + * + * @return {Boolean} True if this data model calculation is of type lookup. + * + * @method splunkjs.Service.DataModelCalculation + */ + isLookup: function() { + return "Lookup" === this.type; + }, + + /** + * Is this data model calculation of type eval? + * + * @return {Boolean} True if this data model calculation is of type eval. + * + * @method splunkjs.Service.DataModelCalculation + */ + isEval: function() { + return "Eval" === this.type; + }, + + /** + * Is this data model calculation of type Rex? + * + * @return {Boolean} True if this data model calculation is of type Rex. + * + * @method splunkjs.Service.DataModelCalculation + */ + isRex: function() { + return "Rex" === this.type; + }, + + /** + * Is this data model calculation of type GeoIP? + * + * @return {Boolean} True if this data model calculation is of type GeoIP. + * + * @method splunkjs.Service.DataModelCalculation + */ + isGeoIP: function() { + return "GeoIP" === this.type; + } + }); /** - * Creates an index with the given name and parameters. + * Pivot represents data about a pivot report returned by the Splunk Server. * - * @example - * - * var indexes = service.indexes(); - * indexes.create("NewIndex", {assureUTF8: true}, function(err, newIndex) { - * console.log("CREATED"); - * }); - * - * @param {String} name A name for this index. - * @param {Object} params A dictionary of properties. For a list of available parameters, see Index parameters on Splunk Developer Portal. - * @param {Function} callback A function to call with the new index: `(err, createdIndex)`. + * Has these properties: + * - `service` (_splunkjs.Service_): A `Service` instance. + * - `search` (_string_): The search string for running the pivot report. + * - `drilldownSearch` (_string_): The search for running this pivot report using drilldown. + * - `openInSearch` (_string_): Equivalent to search parameter, but listed more simply. + * - `prettyQuery` (_string_): Equivalent to `openInSearch`. + * - `pivotSearch` (_string_): A pivot search command based on the named data model. + * - `tstatsSearch` (_string_): The search for running this pivot report using tstats. * - * @endpoint data/indexes - * @method splunkjs.Service.Indexes + * @class splunkjs.Service.Pivot */ - create: function(name, params, callback) { - // If someone called us with the default style of (params, callback), - // lets make it work - if (utils.isObject(name) && utils.isFunction(params) && !callback) { - callback = params; - params = name; - name = params.name; - } - - params = params || {}; - params["name"] = name; - - return this._super(params, callback); - } - }); + root.Pivot = Class.extend({ + /** + * Constructor for a pivot. + * SDK users are not expected to invoke this constructor directly. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} props A dictionary of properties to set: + * - `search` (_string_): The search string for running the pivot report. + * - `drilldown_search` (_string_): The search for running this pivot report using drilldown. + * - `open_in_search` (_string_): Equivalent to search parameter, but listed more simply. + * - `pivot_search` (_string_): A pivot search command based on the named data model. + * - `tstats_search` (_string_|_null_): The search for running this pivot report using tstats, null if acceleration is disabled. + * + * @method splunkjs.Service.Pivot + */ + init: function(service, props) { + this.service = service; + this.search = props.search; + this.drilldownSearch = props.drilldown_search; + this.prettyQuery = this.openInSearch = props.open_in_search; + this.pivotSearch = props.pivot_search; + this.tstatsSearch = props.tstats_search || null; - /** - * Represents a specific stanza, which you can update and remove, from a - * configuration file. - * - * @endpoint configs/conf-{file}/{name}` - * @class splunkjs.Service.ConfigurationStanza - * @extends splunkjs.Service.Entity - */ - root.ConfigurationStanza = root.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.ConfigurationStanza - */ - path: function() { - var name = this.name === "default" ? "_new" : this.name; - return Paths.configurations + "/conf-" + encodeURIComponent(this.file) + "/" + encodeURIComponent(name); - }, - - /** - * Constructor for `splunkjs.Service.ConfigurationStanza`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} file The name of the configuration file. - * @param {String} name The name of the new stanza. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.ConfigurationStanza} A new `splunkjs.Service.ConfigurationStanza` instance. - * - * @method splunkjs.Service.ConfigurationStanza - */ - init: function(service, file, name, namespace) { - this.name = name; - this.file = file; - this._super(service, this.path(), namespace); - } - }); + this.run = utils.bind(this, this.run); + }, + + /** + * Starts a search job running this pivot, accelerated if possible. + * + * @param {Object} args A dictionary of properties for the search job (optional). For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. + * @param {Function} callback A function to call when done creating the search job: `(err, job)`. + * @method splunkjs.Service.Pivot + */ + run: function(args, callback) { + if (utils.isUndefined(callback)) { + callback = args; + args = {}; + } + if (!args || Object.keys(args).length === 0) { + args = {}; + } + + // If tstats is undefined, use pivotSearch (try to run an accelerated search if possible) + this.service.search(this.tstatsSearch || this.pivotSearch, args, callback); + } + }); - /** - * Represents a collection of stanzas for a specific property file. You can - * create and list stanzas using this collection container, or get a specific - * stanza. - * - * @endpoint configs/conf-{file} - * @class splunkjs.Service.ConfigurationFile - * @extends splunkjs.Service.Collection - */ - root.ConfigurationFile = root.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.ConfigurationFile - */ - path: function() { - return Paths.configurations + "/conf-" + encodeURIComponent(this.name); - }, - - /** - * Creates a local instance of the default stanza in a configuration file. - * You cannot directly update the `ConfigurationStanza` returned by this function. - * - * This is equivalent to viewing `configs/conf-{file}/_new`. - * - * @return {splunkjs.Service.ConfigurationStanza} A new `splunkjs.Service.ConfigurationStanza` instance. - * - * @method splunkjs.Service.ConfigurationFile - */ - getDefaultStanza: function() { - return new root.ConfigurationStanza(this.service, this.name, "default", this.namespace); - }, - - /** - * Creates a local instance of a stanza in a configuration file. - * - * @param {Object} props The key-value properties for the new stanza. - * @return {splunkjs.Service.ConfigurationStanza} A new `splunkjs.Service.ConfigurationStanza` instance. - * - * @method splunkjs.Service.ConfigurationFile - */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.ConfigurationStanza(this.service, this.name, props.name, entityNamespace); - }, - /** - * Constructor for `splunkjs.Service.ConfigurationFile`. + * PivotSpecification represents a pivot to be done on a particular data model object. + * The user creates a PivotSpecification on some data model object, adds filters, row splits, + * column splits, and cell values, then calls the pivot method to query splunkd and + * get a set of SPL queries corresponding to this specification. * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name of the configuration file. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.ConfigurationFile} A new `splunkjs.Service.ConfigurationFile` instance. + * Call the `pivot` method to query Splunk for SPL queries corresponding to this pivot. * - * @method splunkjs.Service.ConfigurationFile - */ - init: function(service, name, namespace) { - this.name = name; - this._super(service, this.path(), namespace); - }, - - /** - * Creates a stanza in this configuration file. + * This class supports a fluent API, each function except `init`, `toJsonObject` & `pivot` + * return the modified `splunkjs.Service.PivotSpecification` instance. * * @example - * - * var file = service.configurations().item("props"); - * file.create("my_stanza", function(err, newStanza) { - * console.log("CREATED"); - * }); - * - * @param {String} stanzaName A name for this stanza. - * @param {Object} values A dictionary of key-value pairs to put in this stanza. - * @param {Function} callback A function to call with the created stanza: `(err, createdStanza)`. - * - * @endpoint configs/conf-{file} - * @method splunkjs.Service.ConfigurationFile + * service.dataModels().fetch(function(err, dataModels) { + * var searches = dataModels.item("internal_audit_logs").objectByName("searches"); + * var pivotSpecification = searches.createPivotSpecification(); + * pivotSpecification + * .addRowSplit("user", "Executing user") + * .addRangeColumnSplit("exec_time", {limit: 4}) + * .addCellValue("search", "Search Query", "values") + * .pivot(function(err, pivot) { + * console.log("Got a Pivot object from the Splunk server!"); + * }); + * }); + * + * Has these properties: + * - `dataModelObject` (_splunkjs.Service.DataModelObject_): The `DataModelObject` from which + * this `PivotSpecification` was created. + * - `columns` (_array_): The column splits on this `PivotSpecification`. + * - `rows` (_array_): The row splits on this `PivotSpecification`. + * - `filters` (_array_): The filters on this `PivotSpecification`. + * - `cells` (_array_): The cell aggregations for this`PivotSpecification`. + * - `accelerationNamespace` (_string_|_null_): The name of the `DataModel` that owns the `DataModelObject` + * on which this `PivotSpecification` was created if the `DataModel` is accelerated. Alternatively, + * you can set this property manually to the sid of an acceleration job in the format `sid=`. + * + * Valid comparison types are: + * - `boolean` + * - `string` + * - `number` + * - `ipv4` + * + * Valid boolean comparisons are: + * - `=` + * - `is` + * - `isNull` + * - `isNotNull` + * + * Valid string comparisons are: + * - `=` + * - `is` + * - `isNull` + * - `isNotNull` + * - `contains` + * - `doesNotContain` + * - `startsWith` + * - `endsWith` + * - `regex` + * + * Valid number comparisons are: + * - `=` + * - `!=` + * - `<` + * - `>` + * - `<=` + * - `>=` + * - `is` + * - `isNull` + * - `isNotNull` + * + * Valid ipv4 comparisons are: + * - `is` + * - `isNull` + * - `isNotNull` + * - `contains` + * - `doesNotContain` + * - `startsWith` + * + * Valid binning values are: + * - `auto` + * - `year` + * - `month` + * - `day` + * - `hour` + * - `minute` + * - `second` + * + * Valid sort directions are: + * - `ASCENDING` + * - `DECENDING` + * - `DEFAULT` + * + * Valid stats functions are: + * - `list` + * - `values` + * - `first` + * - `last` + * - `count` + * - `dc` + * - `sum` + * - `average` + * - `max` + * - `min` + * - `stdev` + * - `duration` + * - `earliest` + * - `latest` + * + * @class splunkjs.Service.PivotSpecification */ - create: function(stanzaName, values, callback) { - // If someone called us with the default style of (params, callback), - // lets make it work - if (utils.isObject(stanzaName) && utils.isFunction(values) && !callback) { - callback = values; - values = stanzaName; - stanzaName = values.name; - } - - if (utils.isFunction(values) && !callback) { - callback = values; - values = {}; - } - - values = values || {}; - values["name"] = stanzaName; + root.PivotSpecification = Class.extend({ + _comparisons: { + boolean: ["=", "is", "isNull", "isNotNull"], + string: ["=", "is", "isNull", "isNotNull", "contains", "doesNotContain", "startsWith", "endsWith", "regex"], + number: ["=", "!=", "<", ">", "<=", ">=", "is", "isNull", "isNotNull"], + ipv4: ["is", "isNull", "isNotNull", "contains", "doesNotContain", "startsWith"] + }, + _binning: ["auto", "year", "month", "day", "hour", "minute", "second"], + _sortDirection: ["ASCENDING", "DESCENDING", "DEFAULT"], + _statsFunctions: ["list", "values", "first", "last", "count", "dc", "sum", "average", "max", "min", "stdev", "duration", "earliest", "latest"], + + /** + * Constructor for a pivot specification. + * + * @constructor + * @param {splunkjs.Service.DataModel} parentDataModel The `DataModel` that owns this data model object. + * + * @method splunkjs.Service.PivotSpecification + */ + init: function(dataModelObject) { + this.dataModelObject = dataModelObject; + this.columns = []; + this.rows = []; + this.filters = []; + this.cells = []; + + this.accelerationNamespace = dataModelObject.dataModel.isAccelerated() ? + dataModelObject.dataModel.name : null; + + this.run = utils.bind(this, this.run); + this.pivot = utils.bind(this, this.pivot); + }, - return this._super(values, callback); - } - }); + /** + * Set the acceleration cache for this pivot specification to a job, + * usually generated by createLocalAccelerationJob on a DataModelObject + * instance, as the acceleration cache for this pivot specification. + * + * @param {String|splunkjs.Service.Job} sid The sid of an acceleration job, + * or, a `splunkjs.Service.Job` instance. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + setAccelerationJob: function(sid) { + // If a search object is passed in, get its sid + if (sid && sid instanceof Service.Job) { + sid = sid.sid; + } + + if (!sid) { + throw new Error("Sid to use for acceleration must not be null."); + } - /** - * Represents a collection of configuration files. You can create and list - * configuration files using this collection container, or get a specific file. - * - * @endpoint properties - * @class splunkjs.Service.Configurations - * @extends splunkjs.Service.Collection - */ - root.Configurations = root.Collection.extend({ - /** - * Indicates whether to call `fetch` after an entity has been created. By - * default, the entity is not fetched because the endpoint returns - * (echoes) the new entity. - * - * @method splunkjs.Service.Configurations - */ - fetchOnEntityCreation: true, - - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Configurations - */ - path: function() { - return Paths.properties; - }, - - /** - * Creates a local instance of a configuration file. - * - * @param {Object} props The properties for this configuration file. - * @return {splunkjs.Service.ConfigurationFile} A new `splunkjs.Service.ConfigurationFile` instance. - * - * @method splunkjs.Service.Configurations - */ - instantiateEntity: function(props) { - return new root.ConfigurationFile(this.service, props.name, this.namespace); - }, + this.accelerationNamespace = "sid=" + sid; + return this; + }, + + /** + * Add a filter on a boolean valued field. The filter will be a constraint of the form + * `field `comparison` compareTo`, for example: `is_remote = false`. + * + * @param {String} fieldName The name of field to filter on + * @param {String} comparisonType The type of comparison, see class docs for valid types. + * @param {String} comparisonOp The comparison, see class docs for valid comparisons, based on type. + * @param {String} compareTo The value to compare the field to. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addFilter: function(fieldName, comparisonType, comparisonOp, compareTo) { + if (!this.dataModelObject.hasField(fieldName)) { + throw new Error("Cannot add filter on a nonexistent field."); + } + if (comparisonType !== this.dataModelObject.fieldByName(fieldName).type) { + throw new Error( + "Cannot add " + comparisonType + + " filter on " + fieldName + + " because it is of type " + + this.dataModelObject.fieldByName(fieldName).type); + } + if (!utils.contains(this._comparisons[comparisonType], comparisonOp)) { + throw new Error( + "Cannot add " + comparisonType + + " filter because " + comparisonOp + + " is not a valid comparison operator"); + } + + var ret = { + fieldName: fieldName, + owner: this.dataModelObject.fieldByName(fieldName).lineage.join("."), + type: comparisonType + }; + // These fields are type dependent + if (utils.contains(["boolean", "string", "ipv4", "number"], ret.type)) { + ret.rule = { + comparator: comparisonOp, + compareTo: compareTo + }; + } + this.filters.push(ret); - /** - * Constructor for `splunkjs.Service.Configurations`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Configurations} A new `splunkjs.Service.Configurations` instance. - * - * @method splunkjs.Service.Configurations - */ - init: function(service, namespace) { - if (!namespace || namespace.owner === "-" || namespace.app === "-") { - throw new Error("Configurations requires a non-wildcard owner/app"); - } + return this; + }, + + /** + * Add a limit on the events shown in a pivot by sorting them according to some field, then taking + * the specified number from the beginning or end of the list. + * + * @param {String} fieldName The name of field to filter on. + * @param {String} sortAttribute The name of the field to use for sorting. + * @param {String} sortDirection The direction to sort events, see class docs for valid types. + * @param {String} limit The number of values from the sorted list to allow through this filter. + * @param {String} statsFunction The stats function to use for aggregation before sorting, see class docs for valid types. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addLimitFilter: function(fieldName, sortAttribute, sortDirection, limit, statsFunction) { + if (!this.dataModelObject.hasField(fieldName)) { + throw new Error("Cannot add limit filter on a nonexistent field."); + } + + var f = this.dataModelObject.fieldByName(fieldName); + + if (!utils.contains(["string", "number", "objectCount"], f.type)) { + throw new Error("Cannot add limit filter on " + fieldName + " because it is of type " + f.type); + } + + if ("string" === f.type && !utils.contains(["count", "dc"], statsFunction)) { + throw new Error("Stats function for fields of type string must be COUNT or DISTINCT_COUNT; found " + + statsFunction); + } + + if ("number" === f.type && !utils.contains(["count", "dc", "average", "sum"], statsFunction)) { + throw new Error("Stats function for fields of type number must be one of COUNT, DISTINCT_COUNT, SUM, or AVERAGE; found " + + statsFunction); + } + + if ("objectCount" === f.type && !utils.contains(["count"], statsFunction)) { + throw new Error("Stats function for fields of type object count must be COUNT; found " + statsFunction); + } + + var filter = { + fieldName: fieldName, + owner: f.lineage.join("."), + type: f.type, + attributeName: sortAttribute, + attributeOwner: this.dataModelObject.fieldByName(sortAttribute).lineage.join("."), + sortDirection: sortDirection, + limitAmount: limit, + statsFn: statsFunction + }; + // Assumed "highest" is preferred for when sortDirection is "DEFAULT" + filter.limitType = "ASCENDING" === sortDirection ? "lowest" : "highest"; + this.filters.push(filter); + + return this; + }, + + /** + * Add a row split on a numeric or string valued field, splitting on each distinct value of the field. + * + * @param {String} fieldName The name of field to split on. + * @param {String} label A human readable name for this set of rows. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addRowSplit: function(fieldName, label) { + if (!this.dataModelObject.hasField(fieldName)) { + throw new Error("Did not find field " + fieldName); + } + var f = this.dataModelObject.fieldByName(fieldName); + if (!utils.contains(["number", "string"], f.type)) { + throw new Error("Field was of type " + f.type + ", expected number or string."); + } + + var row = { + fieldName: fieldName, + owner: f.owner, + type: f.type, + label: label + }; + + if ("number" === f.type) { + row.display = "all"; + } + + this.rows.push(row); + + return this; + }, + + /** + * Add a row split on a numeric field, splitting into numeric ranges. + * + * This split generates bins with edges equivalent to the + * classic loop 'for i in to by ' but with a maximum + * number of bins . This dispatches to the stats and xyseries search commands. + * See their documentation for more details. + * + * @param {String} fieldName The field to split on. + * @param {String} label A human readable name for this set of rows. + * @param {Object} options An optional dictionary of collection filtering and pagination options: + * - `start` (_integer_): The value of the start of the first range, or null to take the lowest value in the events. + * - `end` (_integer_): The value for the end of the last range, or null to take the highest value in the events. + * - `step` (_integer_): The the width of each range, or null to have Splunk calculate it. + * - `limit` (_integer_): The maximum number of ranges to split into, or null for no limit. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addRangeRowSplit: function(field, label, ranges) { + if (!this.dataModelObject.hasField(field)) { + throw new Error("Did not find field " + field); + } + var f = this.dataModelObject.fieldByName(field); + if ("number" !== f.type) { + throw new Error("Field was of type " + f.type + ", expected number."); + } + var updateRanges = {}; + if (!utils.isUndefined(ranges.start) && ranges.start !== null) { + updateRanges.start = ranges.start; + } + if (!utils.isUndefined(ranges.end) && ranges.end !== null) { + updateRanges.end = ranges.end; + } + if (!utils.isUndefined(ranges.step) && ranges.step !== null) { + updateRanges.size = ranges.step; + } + if (!utils.isUndefined(ranges.limit) && ranges.limit !== null) { + updateRanges.maxNumberOf = ranges.limit; + } + + this.rows.push({ + fieldName: field, + owner: f.owner, + type: f.type, + label: label, + display: "ranges", + ranges: updateRanges + }); + + return this; + }, + + /** + * Add a row split on a boolean valued field. + * + * @param {String} fieldName The name of field to split on. + * @param {String} label A human readable name for this set of rows. + * @param {String} trueDisplayValue A string to display in the true valued row label. + * @param {String} falseDisplayValue A string to display in the false valued row label. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addBooleanRowSplit: function(field, label, trueDisplayValue, falseDisplayValue) { + if (!this.dataModelObject.fieldByName(field)) { + throw new Error("Did not find field " + field); + } + var f = this.dataModelObject.fieldByName(field); + if ("boolean" !== f.type) { + throw new Error("Field was of type " + f.type + ", expected boolean."); + } + + this.rows.push({ + fieldName: field, + owner: f.owner, + type: f.type, + label: label, + trueLabel: trueDisplayValue, + falseLabel: falseDisplayValue + }); + + return this; + }, + + /** + * Add a row split on a timestamp valued field, binned by the specified bucket size. + * + * @param {String} fieldName The name of field to split on. + * @param {String} label A human readable name for this set of rows. + * @param {String} binning The size of bins to use, see class docs for valid types. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addTimestampRowSplit: function(field, label, binning) { + if (!this.dataModelObject.hasField(field)) { + throw new Error("Did not find field " + field); + } + var f = this.dataModelObject.fieldByName(field); + if ("timestamp" !== f.type) { + throw new Error("Field was of type " + f.type + ", expected timestamp."); + } + if (!utils.contains(this._binning, binning)) { + throw new Error("Invalid binning " + binning + " found. Valid values are: " + this._binning.join(", ")); + } + + this.rows.push({ + fieldName: field, + owner: f.owner, + type: f.type, + label: label, + period: binning + }); + + return this; + }, - this._super(service, this.path(), namespace); - }, - - /** - * Creates a configuration file. - * - * @example - * - * var configurations = service.configurations(); - * configurations.create("myprops", function(err, newFile) { - * console.log("CREATED"); - * }); - * - * @param {String} filename A name for this configuration file. - * @param {Function} callback A function to call with the new configuration file: `(err, createdFile)`. - * - * @endpoint properties - * @method splunkjs.Service.Configurations - */ - create: function(filename, callback) { - // If someone called us with the default style of (params, callback), - // lets make it work - if (utils.isObject(filename)) { - filename = filename["__conf"]; - } + /** + * Add a column split on a string or number valued field, producing a column for + * each distinct value of the field. + * + * @param {String} fieldName The name of field to split on. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addColumnSplit: function(fieldName) { + if (!this.dataModelObject.hasField(fieldName)) { + throw new Error("Did not find field " + fieldName); + } + var f = this.dataModelObject.fieldByName(fieldName); + if (!utils.contains(["number", "string"], f.type)) { + throw new Error("Field was of type " + f.type + ", expected number or string."); + } + + var col = { + fieldName: fieldName, + owner: f.owner, + type: f.type + }; + + if ("number" === f.type) { + col.display = "all"; + } + + this.columns.push(col); + + return this; + }, + + /** + * Add a column split on a numeric field, splitting the values into ranges. + * + * @param {String} fieldName The field to split on. + * @param {Object} options An optional dictionary of collection filtering and pagination options: + * - `start` (_integer_): The value of the start of the first range, or null to take the lowest value in the events. + * - `end` (_integer_): The value for the end of the last range, or null to take the highest value in the events. + * - `step` (_integer_): The the width of each range, or null to have Splunk calculate it. + * - `limit` (_integer_): The maximum number of ranges to split into, or null for no limit. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addRangeColumnSplit: function(fieldName, ranges) { + if (!this.dataModelObject.hasField(fieldName)) { + throw new Error("Did not find field " + fieldName); + } + var f = this.dataModelObject.fieldByName(fieldName); + if ("number" !== f.type) { + throw new Error("Field was of type " + f.type + ", expected number."); + } + + // In Splunk 6.0.1.1, data models incorrectly expect strings for these fields + // instead of numbers. In 6.1, this is fixed and both are accepted. + var updatedRanges = {}; + if (!utils.isUndefined(ranges.start) && ranges.start !== null) { + updatedRanges.start = ranges.start; + } + if (!utils.isUndefined(ranges.end) && ranges.end !== null) { + updatedRanges.end = ranges.end; + } + if (!utils.isUndefined(ranges.step) && ranges.step !== null) { + updatedRanges.size = ranges.step; + } + if (!utils.isUndefined(ranges.limit) && ranges.limit !== null) { + updatedRanges.maxNumberOf = ranges.limit; + } + + this.columns.push({ + fieldName: fieldName, + owner: f.owner, + type: f.type, + display: "ranges", + ranges: updatedRanges + }); + + return this; + }, - callback = callback || function() {}; + /** + * Add a column split on a boolean valued field. + * + * @param {String} fieldName The name of field to split on. + * @param {String} trueDisplayValue A string to display in the true valued column label. + * @param {String} falseDisplayValue A string to display in the false valued column label. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addBooleanColumnSplit: function(fieldName, trueDisplayValue, falseDisplayValue) { + if (!this.dataModelObject.fieldByName(fieldName)) { + throw new Error("Did not find field " + fieldName); + } + var f = this.dataModelObject.fieldByName(fieldName); + if ("boolean" !== f.type) { + throw new Error("Field was of type " + f.type + ", expected boolean."); + } + + this.columns.push({ + fieldName: fieldName, + owner: f.owner, + type: f.type, + trueLabel: trueDisplayValue, + falseLabel: falseDisplayValue + }); + + return this; + }, - var that = this; - var req = this.post("", {__conf: filename}, function(err, response) { - if (err) { - callback(err); + /** + * Add a column split on a timestamp valued field, binned by the specified bucket size. + * + * @param {String} fieldName The name of field to split on. + * @param {String} binning The size of bins to use, see class docs for valid types. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addTimestampColumnSplit: function(field, binning) { + if (!this.dataModelObject.hasField(field)) { + throw new Error("Did not find field " + field); + } + var f = this.dataModelObject.fieldByName(field); + if ("timestamp" !== f.type) { + throw new Error("Field was of type " + f.type + ", expected timestamp."); + } + if (!utils.contains(this._binning, binning)) { + throw new Error("Invalid binning " + binning + " found. Valid values are: " + this._binning.join(", ")); } - else { - var entity = new root.ConfigurationFile(that.service, filename); - entity.fetch(function() { - if (req.wasAborted) { - return; // aborted, so ignore - } - else { - callback.apply(null, arguments); - } - }); + + this.columns.push({ + fieldName: field, + owner: f.owner, + type: f.type, + period: binning + }); + + return this; + }, + + /** + * Add an aggregate to each cell of the pivot. + * + * @param {String} fieldName The name of field to aggregate. + * @param {String} label a human readable name for this aggregate. + * @param {String} statsFunction The function to use for aggregation, see class docs for valid stats functions. + * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + addCellValue: function(fieldName, label, statsFunction) { + if (!this.dataModelObject.hasField(fieldName)) { + throw new Error("Did not find field " + fieldName); } - }); + + var f = this.dataModelObject.fieldByName(fieldName); + if (utils.contains(["string", "ipv4"], f.type) && + !utils.contains([ + "list", + "values", + "first", + "last", + "count", + "dc"], statsFunction) + ) { + throw new Error("Stats function on string and IPv4 fields must be one of:" + + " list, distinct_values, first, last, count, or distinct_count; found " + + statsFunction); + } + else if ("number" === f.type && + !utils.contains([ + "sum", + "count", + "average", + "min", + "max", + "stdev", + "list", + "values" + ], statsFunction) + ) { + throw new Error("Stats function on number field must be must be one of:" + + " sum, count, average, max, min, stdev, list, or distinct_values; found " + + statsFunction + ); + } + else if ("timestamp" === f.type && + !utils.contains([ + "duration", + "earliest", + "latest", + "list", + "values" + ], statsFunction) + ) { + throw new Error("Stats function on timestamp field must be one of:" + + " duration, earliest, latest, list, or distinct values; found " + + statsFunction + ); + } + else if (utils.contains(["objectCount", "childCount"], f.type) && + "count" !== statsFunction + ) { + throw new Error("Stats function on childcount and objectcount fields must be count; " + + "found " + statsFunction); + } + else if ("boolean" === f.type) { + throw new Error("Cannot use boolean valued fields as cell values."); + } + + this.cells.push({ + fieldName: fieldName, + owner: f.lineage.join("."), + type: f.type, + label: label, + sparkline: false, // Not properly implemented in core yet. + value: statsFunction + }); + + return this; + }, - return req; - } - }); - - /** - * Represents a specific search job. You can perform different operations - * on this job, such as reading its status, canceling it, and getting results. - * - * @endpoint search/jobs/{search_id} - * @class splunkjs.Service.Job - * @extends splunkjs.Service.Entity - */ - root.Job = root.Entity.extend({ + /** + * Returns a JSON ready object representation of this pivot specification. + * + * @return {Object} The JSON ready object representation of this pivot specification. + * + * @method splunkjs.Service.PivotSpecification + */ + toJsonObject: function() { + return { + dataModel: this.dataModelObject.dataModel.name, + baseClass: this.dataModelObject.name, + rows: this.rows, + columns: this.columns, + cells: this.cells, + filters: this.filters + }; + }, + + /** + * Query Splunk for SPL queries corresponding to a pivot report + * for this data model, defined by this `PivotSpecification`. + * + * @example + * + * service.dataModels().fetch(function(err, dataModels) { + * var searches = dataModels.item("internal_audit_logs").objectByName("searches"); + * var pivotSpec = searches.createPivotSpecification(); + * // Use of the fluent API + * pivotSpec.addRowSplit("user", "Executing user") + * .addRangeColumnSplit("exec_time", {start: 0, end: 12, step: 5, limit: 4}) + * .addCellValue("search", "Search Query", "values") + * .pivot(function(pivotErr, pivot) { + * console.log("Pivot search is:", pivot.search); + * }); + * }); + * + * @param {Function} callback A function to call when done getting the pivot: `(err, pivot)`. + * + * @method splunkjs.Service.PivotSpecification + */ + pivot: function(callback) { + var svc = this.dataModelObject.dataModel.service; + + var args = { + pivot_json: JSON.stringify(this.toJsonObject()) + }; + + if (!utils.isUndefined(this.accelerationNamespace)) { + args.namespace = this.accelerationNamespace; + } + + return svc.get(Paths.pivot + "/" + encodeURIComponent(this.dataModelObject.dataModel.name), args, function(err, response) { + if (err) { + callback(new Error(err.data.messages[0].text), response); + return; + } + + if (response.data.entry && response.data.entry[0]) { + callback(null, new root.Pivot(svc, response.data.entry[0].content)); + } + else { + callback(new Error("Didn't get a Pivot report back from Splunk"), response); + } + }); + }, + + /** + * Convenience method to wrap up the `PivotSpecification.pivot()` and + * `Pivot.run()` function calls. + * + * Query Splunk for SPL queries corresponding to a pivot report + * for this data model, defined by this `PivotSpecification`; then, + * starts a search job running this pivot, accelerated if possible. + * + * service.dataModels().fetch(function(fetchErr, dataModels) { + * var searches = dataModels.item("internal_audit_logs").objectByName("searches"); + * var pivotSpec = searches.createPivotSpecification(); + * // Use of the fluent API + * pivotSpec.addRowSplit("user", "Executing user") + * .addRangeColumnSplit("exec_time", {start: 0, end: 12, step: 5, limit: 4}) + * .addCellValue("search", "Search Query", "values") + * .run(function(err, job, pivot) { + * console.log("Job SID is:", job.sid); + * console.log("Pivot search is:", pivot.search); + * }); + * }); + * @param {Object} args A dictionary of properties for the search job (optional). For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. + * @param {Function} callback A function to call when done getting the pivot: `(err, job, pivot)`. + * + * @method splunkjs.Service.PivotSpecification + */ + run: function(args, callback) { + if (!callback) { + callback = args; + args = {}; + } + args = args || {}; + + this.pivot(function(err, pivot) { + if (err) { + callback(err, null, null); + } + else { + pivot.run(args, Async.augment(callback, pivot)); + } + }); + } + }); + /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Job + * Represents one of the structured views in a `DataModel`. + * + * Has these properties: + * - `dataModel` (_splunkjs.Service.DataModel_): The `DataModel` to which this `DataModelObject` belongs. + * - `name` (_string_): The name of this `DataModelObject`. + * - `displayName` (_string_): The human readable name of this `DataModelObject`. + * - `parentName` (_string_): The name of the parent `DataModelObject` to this one. + * - `lineage` (_array_): An array of strings of the lineage of the data model + * on which this field is defined. + * - `fields` (_object_): A dictionary of `DataModelField` objects, accessible by name. + * - `constraints` (_array_): An array of `DataModelConstraint` objects. + * - `calculations` (_object_): A dictionary of `DataModelCalculation` objects, accessible by ID. + * + * BaseSearch has an additional property: + * - `baseSearch` (_string_): The search query wrapped by this data model object. + * + * BaseTransaction has additional properties: + * - `groupByFields` (_string_): The fields that will be used to group events into transactions. + * - `objectsToGroup` (_array_): Names of the data model objects that should be unioned + * and split into transactions. + * - `maxSpan` (_string_): The maximum time span of a transaction. + * - `maxPause` (_string_): The maximum pause time of a transaction. + * + * @class splunkjs.Service.DataModelObject */ - path: function() { - return Paths.jobs + "/" + encodeURIComponent(this.name); - }, - + root.DataModelObject = Class.extend({ + /** + * Constructor for a data model object. + * SDK users are not expected to invoke this constructor directly. + * + * @constructor + * @param {Object} props A dictionary of properties to set: + * - `objectName` (_string_): The name for this data model object. + * - `displayName` (_string_): A human readable name for this data model object. + * - `parentName` (_string_): The name of the data model that owns this data model object. + * - `lineage` (_string_): The lineage of the data model that owns this data model object, + * items are delimited by a dot. This is converted into an array of + * strings upon construction. + * - `fields` (_array_): An array of data model fields. + * - `constraints` (_array_): An array of data model constraints. + * - `calculations` (_array_): An array of data model calculations. + * - `baseSearch` (_string_): The search query wrapped by this data model object; exclusive to BaseSearch (optional) + * - `groupByFields` (_array_): The fields that will be used to group events into transactions; exclusive to BaseTransaction (optional) + * - `objectsToGroup` (_array_): Names of the data model objects that should be unioned + * and split into transactions; exclusive to BaseTransaction (optional) + * - `maxSpan` (_string_): The maximum time span of a transaction; exclusive to BaseTransaction (optional) + * - `maxPause` (_string_): The maximum pause time of a transaction; exclusive to BaseTransaction (optional) + * + * @param {splunkjs.Service.DataModel} parentDataModel The `DataModel` that owns this data model object. + * + * @method splunkjs.Service.DataModelObject + */ + init: function(props, parentDataModel) { + props = props || {}; + props.owner = props.owner || ""; + + this.dataModel = parentDataModel; + this.name = props.objectName; + this.displayName = props.displayName; + this.parentName = props.parentName; + this.lineage = props.lineage.split("."); + + // Properties exclusive to BaseTransaction + if (props.hasOwnProperty("groupByFields")) { + this.groupByFields = props.groupByFields; + } + if (props.hasOwnProperty("objectsToGroup")) { + this.objectsToGroup = props.objectsToGroup; + } + if (props.hasOwnProperty("transactionMaxTimeSpan")) { + this.maxSpan = props.transactionMaxTimeSpan; + } + if (props.hasOwnProperty("transactionMaxPause")) { + this.maxPause = props.transactionMaxPause; + } + + // Property exclusive to BaseSearch + if (props.hasOwnProperty("baseSearch")) { + this.baseSearch = props.baseSearch; + } + + // Parse fields + this.fields = {}; + for (var i = 0; i < props.fields.length; i++) { + this.fields[props.fields[i].fieldName] = new root.DataModelField(props.fields[i]); + } + + // Parse constraints + this.constraints = []; + for (var j = 0; j < props.constraints.length; j++) { + this.constraints.push(new root.DataModelConstraint(props.constraints[j])); + } + + // Parse calculations + this.calculations = []; + for (var k = 0; k < props.calculations.length; k++) { + this.calculations[props.calculations[k].calculationID] = new root.DataModelCalculation(props.calculations[k]); + } + }, + + /** + * Is this data model object a BaseSearch? + * + * @return {Boolean} Whether this data model object is the root type, BaseSearch. + * + * @method splunkjs.Service.DataModelObject + */ + isBaseSearch: function() { + return !utils.isUndefined(this.baseSearch); + }, + + /** + * Is this data model object is a BaseTransaction? + * + * @return {Boolean} Whether this data model object is the root type, BaseTransaction. + * + * @method splunkjs.Service.DataModelObject + */ + isBaseTransaction: function() { + return !utils.isUndefined(this.maxSpan); + }, + + /** + * Returns a string array of the names of this data model object's fields. + * + * @return {Array} An array of strings with the field names of this + * data model object. + * + * @method splunkjs.Service.DataModelObject + */ + fieldNames: function() { + return Object.keys(this.fields); + }, + + /** + * Returns a data model field instance, representing a field on this + * data model object. + * + * @return {splunkjs.Service.DataModelField|null} The data model field + * from this data model object with the specified name, null if it the + * field by that name doesn't exist. + * + * @method splunkjs.Service.DataModelObject + */ + fieldByName: function(name) { + return this.calculatedFields()[name] || this.fields[name] || null; + }, + + /** + * Returns an array of data model fields from this data model object's + * calculations, and this data model object's fields. + * + * @return {Array} An array of `splunk.Service.DataModelField` objects + * which includes this data model object's fields, and the fields from + * this data model object's calculations. + * + * @method splunkjs.Service.DataModelObject + */ + allFields: function() { + // merge fields and calculatedFields() + var combinedFields = []; + + for (var f in this.fields) { + if (this.fields.hasOwnProperty(f)) { + combinedFields[f] = this.fields[f]; + } + } + + var calculatedFields = this.calculatedFields(); + for (var cf in calculatedFields) { + if (calculatedFields.hasOwnProperty(cf)) { + combinedFields[cf] = calculatedFields[cf]; + } + } + + return combinedFields; + }, + + /** + * Returns a string array of the field names of this data model object's + * calculations, and the names of this data model object's fields. + * + * @return {Array} An array of strings with the field names of this + * data model object's calculations, and the names of fields on + * this data model object. + * + * @method splunkjs.Service.DataModelObject + */ + allFieldNames: function() { + return Object.keys(this.allFields()); + }, + + /** + * Returns an array of data model fields from this data model object's + * calculations. + * + * @return {Array} An array of `splunk.Service.DataModelField` objects + * of the fields from this data model object's calculations. + * + * @method splunkjs.Service.DataModelObject + */ + calculatedFields: function(){ + var fields = {}; + // Iterate over the calculations, get their fields + var keys = this.calculationIDs(); + var calculations = this.calculations; + for (var i = 0; i < keys.length; i++) { + var calculation = calculations[keys[i]]; + for (var f = 0; f < calculation.outputFieldNames().length; f++) { + fields[calculation.outputFieldNames()[f]] = calculation.outputFields[calculation.outputFieldNames()[f]]; + } + } + return fields; + }, + + /** + * Returns a string array of the field names of this data model object's + * calculations. + * + * @return {Array} An array of strings with the field names of this + * data model object's calculations. + * + * @method splunkjs.Service.DataModelObject + */ + calculatedFieldNames: function() { + return Object.keys(this.calculatedFields()); + }, + + /** + * Returns whether this data model object contains the field with the + * name passed in the `fieldName` parameter. + * + * @param {String} fieldName The name of the field to look for. + * @return {Boolean} True if this data model contains the field by name. + * + * @method splunkjs.Service.DataModelObject + */ + hasField: function(fieldName) { + return utils.contains(this.allFieldNames(), fieldName); + }, + + /** + * Returns a string array of the IDs of this data model object's + * calculations. + * + * @return {Array} An array of strings with the IDs of this data model + * object's calculations. + * + * @method splunkjs.Service.DataModelObject + */ + calculationIDs: function() { + return Object.keys(this.calculations); + }, + + /** + * Local acceleration is tsidx acceleration of a data model object that is handled + * manually by a user. You create a job which generates an index, and then use that + * index in your pivots on the data model object. + * + * The namespace created by the job is 'sid={sid}' where {sid} is the job's sid. You + * would use it in another job by starting your search query with `| tstats ... from sid={sid} | ...` + * + * The tsidx index created by this job is deleted when the job is garbage collected by Splunk. + * + * It is the user's responsibility to manage this job, including cancelling it. + * + * @example + * + * service.dataModels().fetch(function(err, dataModels) { + * var object = dataModels.item("some_data_model").objectByName("some_object"); + * object.createLocalAccelerationJob("-1d", function(err, accelerationJob) { + * console.log("The job has name:", accelerationJob.name); + * }); + * }); + * + * @param {String} earliestTime A time modifier (e.g., "-2w") setting the earliest time to index. + * @param {Function} callback A function to call with the search job: `(err, accelerationJob)`. + * + * @method splunkjs.Service.DataModelObject + */ + createLocalAccelerationJob: function(earliestTime, callback) { + // If earliestTime parameter is not specified, then set callback to its value + if (!callback && utils.isFunction(earliestTime)) { + callback = earliestTime; + earliestTime = undefined; + } + + var query = "| datamodel \"" + this.dataModel.name + "\" " + this.name + " search | tscollect"; + var args = earliestTime ? {earliest_time: earliestTime} : {}; + + this.dataModel.service.search(query, args, callback); + }, + + /** + * Start a search job that applies querySuffix to all the events in this data model object. + * + * @example + * + * service.dataModels().fetch(function(err, dataModels) { + * var object = dataModels.item("internal_audit_logs").objectByName("searches"); + * object.startSearch({}, "| head 5", function(err, job) { + * console.log("The job has name:", job.name); + * }); + * }); + * + * @param {Object} params A dictionary of properties for the search job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. + * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. + * @param {String} querySuffix A search query, starting with a '|' that will be appended to the command to fetch the contents of this data model object (e.g., "| head 3"). + * @param {Function} callback A function to call with the search job: `(err, job)`. + * + * @method splunkjs.Service.DataModelObject + */ + startSearch: function(params, querySuffix, callback) { + var query = "| datamodel " + this.dataModel.name + " " + this.name + " search"; + // Prepend a space to the querySuffix, or set it to an empty string if null or undefined + querySuffix = (querySuffix) ? (" " + querySuffix) : (""); + this.dataModel.service.search(query + querySuffix, params, callback); + }, + + /** + * Returns the data model object this one inherits from if it is a user defined, + * otherwise return null. + * + * @return {splunkjs.Service.DataModelObject|null} This data model object's parent + * or null if this is not a user defined data model object. + * + * @method splunkjs.Service.DataModelObject + */ + parent: function() { + return this.dataModel.objectByName(this.parentName); + }, + + /** + * Returns a new Pivot Specification, accepts no parameters. + * + * @return {splunkjs.Service.PivotSpecification} A new pivot specification. + * + * @method splunkjs.Service.DataModelObject + */ + createPivotSpecification: function() { + // Pass in this DataModelObject to create a PivotSpecification + return new root.PivotSpecification(this); + } + }); + /** - * Constructor for `splunkjs.Service.Job`. + * Represents a data model on the server. Data models + * contain `DataModelObject` instances, which specify structured + * views on Splunk data. * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} sid The search ID for this search job. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Job} A new `splunkjs.Service.Job` instance. + * @endpoint datamodel/model/{name} + * @class splunkjs.Service.DataModel + * @extends splunkjs.Service.Entity + */ + root.DataModel = Service.Entity.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.DataModel + */ + path: function() { + return Paths.dataModels + "/" + encodeURIComponent(this.name); + }, + + /** + * Constructor for `splunkjs.Service.DataModel`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {String} name The name for the new data model. + * @param {Object} namespace (Optional) namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * @param {Object} props Properties of this data model: + * - `acceleration` (_string_): A JSON object with an `enabled` key, representing if acceleration is enabled or not. + * - `concise` (_string_): Indicates whether to list a concise JSON description of the data model, should always be "0". + * - `description` (_string_): The JSON describing the data model. + * - `displayName` (_string_): The name displayed for the data model in Splunk Web. + * + * @method splunkjs.Service.DataModel + */ + init: function(service, name, namespace, props) { + // If not given a 4th arg, assume the namespace was omitted + if (!props) { + props = namespace; + namespace = {}; + } + + this.name = name; + this._super(service, this.path(), namespace); + + this.acceleration = JSON.parse(props.content.acceleration) || {}; + if (this.acceleration.hasOwnProperty("enabled")) { + // convert the enabled property to a boolean + this.acceleration.enabled = !!this.acceleration.enabled; + } + + // concise=0 (false) forces the server to return all details of the newly created data model. + // we do not want a summary of this data model + if (!props.hasOwnProperty("concise") || utils.isUndefined(props.concise)) { + this.concise = "0"; + } + + var dataModelDefinition = JSON.parse(props.content.description); + + this.objectNames = dataModelDefinition.objectNameList; + this.displayName = dataModelDefinition.displayName; + this.description = dataModelDefinition.description; + + // Parse the objects for this data model + var objs = dataModelDefinition.objects; + this.objects = []; + for (var i = 0; i < objs.length; i++) { + this.objects.push(new root.DataModelObject(objs[i], this)); + } + + this.remove = utils.bind(this, this.remove); + this.update = utils.bind(this, this.update); + }, + + /** + * Returns a boolean indicating whether acceleration is enabled or not. + * + * @return {Boolean} true if acceleration is enabled, false otherwise. + * + * @method splunkjs.Service.DataModel + */ + isAccelerated: function() { + return !!this.acceleration.enabled; + }, + + /** + * Returns a data model object from this data model + * with the specified name if it exists, null otherwise. + * + * @return {Object|null} a data model object. + * + * @method splunkjs.Service.DataModel + */ + objectByName: function(name) { + for (var i = 0; i < this.objects.length; i++) { + if (this.objects[i].name === name) { + return this.objects[i]; + } + } + return null; + }, + + /** + * Returns a boolean of whether this exists in this data model or not. + * + * @return {Boolean} Returns true if this data model has object with specified name, false otherwise. + * + * @method splunkjs.Service.DataModel + */ + hasObject: function(name) { + return utils.contains(this.objectNames, name); + }, + + /** + * Updates the data model on the server, used to update acceleration settings. + * + * @param {Object} props A dictionary of properties to update the object with: + * - `acceleration` (_object_): The acceleration settings for the data model. + * Valid keys are: `enabled`, `earliestTime`, `cronSchedule`. + * Any keys not set will be pulled from the acceleration settings already + * set on this data model. + * @param {Function} callback A function to call when the data model is updated: `(err, dataModel)`. + * + * @method splunkjs.Service.DataModel + */ + update: function(props, callback) { + if (utils.isUndefined(callback)) { + callback = props; + props = {}; + } + callback = callback || function() {}; + + if (!props) { + callback(new Error("Must specify a props argument to update a data model.")); + return; // Exit if props isn't set, to avoid calling the callback twice. + } + if (props.hasOwnProperty("name")) { + callback(new Error("Cannot set 'name' field in 'update'"), this); + return; // Exit if the name is set, to avoid calling the callback twice. + } + + var updatedProps = { + acceleration: JSON.stringify({ + enabled: props.accceleration && props.acceleration.enabled || this.acceleration.enabled, + earliest_time: props.accceleration && props.acceleration.earliestTime || this.acceleration.earliestTime, + cron_schedule: props.accceleration && props.acceleration.cronSchedule || this.acceleration.cronSchedule + }) + }; + + var that = this; + return this.post("", updatedProps, function(err, response) { + if (err) { + callback(err, that); + } + else { + var dataModelNamespace = utils.namespaceFromProperties(response.data.entry[0]); + callback(null, new root.DataModel(that.service, response.data.entry[0].name, dataModelNamespace, response.data.entry[0])); + } + }); + } + }); + + /** + * Represents a collection of data models. You can create and + * list data models using this collection container, or + * get a specific data model. * - * @method splunkjs.Service.Job - */ - init: function(service, sid, namespace) { - this.name = sid; - this._super(service, this.path(), namespace); - this.sid = sid; - - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this.cancel = utils.bind(this, this.cancel); - this.disablePreview = utils.bind(this, this.disablePreview); - this.enablePreview = utils.bind(this, this.enablePreview); - this.events = utils.bind(this, this.events); - this.finalize = utils.bind(this, this.finalize); - this.pause = utils.bind(this, this.pause); - this.preview = utils.bind(this, this.preview); - this.results = utils.bind(this, this.results); - this.searchlog = utils.bind(this, this.searchlog); - this.setPriority = utils.bind(this, this.setPriority); - this.setTTL = utils.bind(this, this.setTTL); - this.summary = utils.bind(this, this.summary); - this.timeline = utils.bind(this, this.timeline); - this.touch = utils.bind(this, this.touch); - this.unpause = utils.bind(this, this.unpause); - }, - + * @endpoint datamodel/model + * @class splunkjs.Service.DataModels + * @extends splunkjs.Service.Collection + */ + root.DataModels = Service.Collection.extend({ + /** + * Retrieves the REST endpoint path for this resource (with no namespace). + * + * @method splunkjs.Service.DataModels + */ + path: function() { + return Paths.dataModels; + }, + + /** + * Constructor for `splunkjs.Service.DataModels`. + * + * @constructor + * @param {splunkjs.Service} service A `Service` instance. + * @param {Object} namespace (Optional) namespace information: + * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. + * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. + * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". + * + * @method splunkjs.Service.DataModels + */ + init: function(service, namespace) { + namespace = namespace || {}; + this._super(service, this.path(), namespace); + this.create = utils.bind(this, this.create); + }, + + /** + * Creates a new `DataModel` object with the given name and parameters. + * It is preferred that you create data models through the Splunk + * Enterprise with a browser. + * + * @param {String} name The name of the data model to create. If it contains spaces they will be replaced + * with underscores. + * @param {Object} params A dictionary of properties. + * @param {Function} callback A function to call with the new `DataModel` object: `(err, createdDataModel)`. + * + * @method splunkjs.Service.DataModels + */ + create: function(name, params, callback) { + // If we get (name, callback) instead of (name, params, callback) + // do the necessary variable swap + if (utils.isFunction(params) && !callback) { + callback = params; + params = {}; + } + + params = params || {}; + callback = callback || function(){}; + name = name.replace(/ /g, "_"); + + var that = this; + return this.post("", {name: name, description: JSON.stringify(params)}, function(err, response) { + if (err) { + callback(err); + } + else { + var dataModel = new root.DataModel(that.service, response.data.entry[0].name, that.namespace, response.data.entry[0]); + callback(null, dataModel); + } + }); + }, + + /** + * Constructor for `splunkjs.Service.DataModel`. + * + * @constructor + * @param {Object} props A dictionary of properties used to create a + * `DataModel` instance. + * @return {splunkjs.Service.DataModel} A new `DataModel` instance. + * + * @method splunkjs.Service.DataModels + */ + instantiateEntity: function(props) { + var entityNamespace = utils.namespaceFromProperties(props); + return new root.DataModel(this.service, props.name, entityNamespace, props); + } + }); + + /*!*/ + // Iterates over an endpoint's results. + root.PaginatedEndpointIterator = Class.extend({ + init: function(endpoint, params) { + params = params || {}; + + this._endpoint = endpoint; + this._pagesize = params.pagesize || 0; + this._offset = 0; + }, + + // Fetches the next page from the endpoint. + next: function(callback) { + callback = callback || function() {}; + + var that = this; + var params = { + count: this._pagesize, + offset: this._offset + }; + return this._endpoint(params, function(err, results) { + if (err) { + callback(err); + } + else { + var numResults = (results.rows ? results.rows.length : 0); + that._offset += numResults; + + callback(null, results, numResults > 0); + } + }); + } + }); + })(); + + }); + + require.define("/lib/async.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2012 Splunk, Inc. + // + // 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. + + (function() { + "use strict"; + + var utils = require('./utils'); + var root = exports || this; + /** - * Cancels a search job. + * Provides utilities for asynchronous control flow and collection handling. * - * @example + * @module splunkjs.Async + */ + + /** + * Runs an asynchronous `while` loop. * - * var job = service.jobs().item("mysid"); - * job.cancel(function(err) { - * console.log("CANCELLED"); - * }); + * @example + * + * var i = 0; + * Async.whilst( + * function() { return i++ < 3; }, + * function(done) { + * Async.sleep(0, function() { done(); }); + * }, + * function(err) { + * console.log(i) // == 3; + * } + * ); * - * @param {Function} callback A function to call when the search is done: `(err)`. + * @param {Function} condition A function that returns a _boolean_ indicating whether the condition has been met. + * @param {Function} body A function that runs the body of the loop: `(done)`. + * @param {Function} callback The function to call when the loop is complete: `(err)`. * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - cancel: function(callback) { - var req = this.post("control", {action: "cancel"}, callback); + root.whilst = function(condition, body, callback) { + condition = condition || function() { return false; }; + body = body || function(done) { done(); }; + callback = callback || function() {}; - return req; - }, - + var iterationDone = function(err) { + if (err) { + callback(err); + } + else { + root.whilst(condition, body, callback); + } + }; + + if (condition()) { + body(iterationDone); + } + else { + callback(null); + } + }; + /** - * Disables preview generation for a search job. + * Runs multiple functions (tasks) in parallel. + * Each task takes the callback function as a parameter. + * When all tasks have been completed or if an error occurs, the callback + * function is called with the combined results of all tasks. * - * @example + * **Note**: Tasks might not be run in the same order as they appear in the array, + * but the results will be returned in that order. * - * var job = service.jobs().item("mysid"); - * job.disablePreview(function(err, job) { - * console.log("PREVIEW DISABLED"); - * }); + * @example + * + * Async.parallel([ + * function(done) { + * done(null, 1); + * }, + * function(done) { + * done(null, 2, 3); + * }], + * function(err, one, two) { + * console.log(err); // == null + * console.log(one); // == 1 + * console.log(two); // == [1,2] + * } + * ); * - * @param {Function} callback A function to call with this search job: `(err, job)`. + * @param {Function} tasks An array of functions: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, ...)`. * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - disablePreview: function(callback) { + root.parallel = function(tasks, callback) { + // Allow for just a list of functions + if (arguments.length > 1 && utils.isFunction(arguments[0])) { + var args = utils.toArray(arguments); + tasks = args.slice(0, args.length - 1); + callback = args[args.length - 1]; + } + + tasks = tasks || []; callback = callback || function() {}; - var that = this; - var req = this.post("control", {action: "disablepreview"}, function(err) { - callback(err, that); - }); + if (tasks.length === 0) { + callback(); + } - return req; - }, - + var tasksLeft = tasks.length; + var results = []; + var doneCallback = function(idx) { + return function(err) { + + if (err) { + if (callback) { + callback(err); + } + callback = null; + } + else { + var args = utils.toArray(arguments); + args.shift(); + + if (args.length === 1) { + args = args[0]; + } + results[idx] = args; + + if ((--tasksLeft) === 0) { + results.unshift(null); + if (callback) { + callback.apply(null, results); + } + } + } + }; + }; + + for(var i = 0; i < tasks.length; i++) { + var task = tasks[i]; + task(doneCallback(i)); + } + }; + /** - * Enables preview generation for a search job. + * Runs multiple functions (tasks) in series. + * Each task takes the callback function as a parameter. + * When all tasks have been completed or if an error occurs, the callback + * function is called with the combined results of all tasks in the order + * they were run. * * @example + * + * var keeper = 0; + * Async.series([ + * function(done) { + * Async.sleep(10, function() { + * console.log(keeper++); // == 0 + * done(null, 1); + * }); + * }, + * function(done) { + * console.log(keeper++); // == 1 + * done(null, 2, 3); + * }], + * function(err, one, two) { + * console.log(err); // == null + * console.log(one); // == 1 + * console.log(two); // == [1,2] + * } + * ); * - * var job = service.jobs().item("mysid"); - * job.disablePreview(function(err, job) { - * console.log("PREVIEW ENABLED"); - * }); - * - * @param {Function} callback A function to call with this search job: `(err, job)`. + * @param {Function} tasks An array of functions: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, ...)`. * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - enablePreview: function(callback) { + root.series = function(tasks, callback) { + // Allow for just a list of functions + if (arguments.length > 1 && utils.isFunction(arguments[0])) { + var args = utils.toArray(arguments); + tasks = args.slice(0, args.length - 1); + callback = args[args.length - 1]; + } + + tasks = tasks || []; callback = callback || function() {}; - var that = this; - var req = this.post("control", {action: "enablepreview"}, function(err) { - callback(err, that); - }); + var innerSeries = function(task, restOfTasks, resultsSoFar, callback) { + if (!task) { + resultsSoFar.unshift(null); + callback.apply(null, resultsSoFar); + return; + } + + task(function(err) { + if (err) { + if (callback) { + callback(err); + } + callback = null; + } + else { + var args = utils.toArray(arguments); + args.shift(); + if (args.length === 1) { + args = args[0]; + } + resultsSoFar.push(args); + + innerSeries(restOfTasks[0], restOfTasks.slice(1), resultsSoFar, callback); + } + }); + }; - return req; - }, - + innerSeries(tasks[0], tasks.slice(1), [], callback); + }; + /** - * Returns the events of a search job with given parameters. + * Runs an asynchronous function (mapping it) over each element in an array, in parallel. + * When all tasks have been completed or if an error occurs, a callback + * function is called with the resulting array. * * @example + * + * Async.parallelMap( + * [1, 2, 3], + * function(val, idx, done) { + * if (val === 2) { + * Async.sleep(100, function() { done(null, val+1); }); + * } + * else { + * done(null, val + 1); + * } + * }, + * function(err, vals) { + * console.log(vals); // == [2,3,4] + * } + * ); * - * var job = service.jobs().item("mysid"); - * job.events({count: 10}, function(err, events, job) { - * console.log("Fields: ", events.fields); - * }); - * - * @param {Object} params The parameters for retrieving events. For a list of available parameters, see the GET search/jobs/{search_id}/events endpoint in the REST API documentation. - * @param {Function} callback A function to call when the events are retrieved: `(err, events, job)`. + * @param {Array} vals An array of values. + * @param {Function} fn A function (possibly asynchronous) to apply to each element: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, mappedVals)`. * - * @endpoint search/jobs/{search_id}/events - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - events: function(params, callback) { + root.parallelMap = function(vals, fn, callback) { + vals = vals || []; callback = callback || function() {}; - params = params || {}; - params.output_mode = params.output_mode || "json_rows"; - var that = this; - return this.get("events", params, function(err, response) { + var tasks = []; + var createTask = function(val, idx) { + return function(done) { fn(val, idx, done); }; + }; + + for(var i = 0; i < vals.length; i++) { + tasks.push(createTask(vals[i], i)); + } + + root.parallel(tasks, function(err) { if (err) { - callback(err); + if (callback) { + callback(err); + } + callback = null; } else { - callback(null, response.data, that); + var args = utils.toArray(arguments); + args.shift(); + callback(null, args); } }); - }, - + }; + /** - * Finalizes a search job. + * Runs an asynchronous function (mapping it) over each element in an array, in series. + * When all tasks have been completed or if an error occurs, a callback + * function is called with the resulting array. * * @example + * + * var keeper = 1; + * Async.seriesMap( + * [1, 2, 3], + * function(val, idx, done) { + * console.log(keeper++); // == 1, then 2, then 3 + * done(null, val + 1); + * }, + * function(err, vals) { + * console.log(vals); // == [2,3,4]; + * } + * ); * - * var job = service.jobs().item("mysid"); - * job.finalize(function(err, job) { - * console.log("JOB FINALIZED"); - * }); - * - * @param {Function} callback A function to call with the job: `(err, job)`. - * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job - */ - finalize: function(callback) { - callback = callback || function() {}; - - var that = this; - var req = this.post("control", {action: "finalize"}, function(err) { - callback(err, that); - }); - - return req; - }, - - /** - * Returns an iterator over this search job's events or results. - * - * @param {String} type One of {"events", "preview", "results"}. - * @param {Object} params A dictionary of optional parameters: - * - `pagesize` (_integer_): The number of items to return on each request. Defaults to as many as possible. - * @return {Object} An iterator object with a `next(callback)` method, where `callback` is of the form `(err, results, hasMoreResults)`. - * - * @endpoint search/jobs/{search_id}/results - * @method splunkjs.Service.Job - */ - iterator: function(type, params) { - return new root.PaginatedEndpointIterator(this[type], params); - }, - - /** - * Pauses a search job. - * - * @example - * - * var job = service.jobs().item("mysid"); - * job.pause(function(err, job) { - * console.log("JOB PAUSED"); - * }); - * - * @param {Function} callback A function to call with the job: `(err, job)`. + * @param {Array} vals An array of values. + * @param {Function} fn A function (possibly asynchronous) to apply to each element: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, mappedVals)`. * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - pause: function(callback) { + root.seriesMap = function(vals, fn, callback) { + vals = vals || []; callback = callback || function() {}; - var that = this; - var req = this.post("control", {action: "pause"}, function(err) { - callback(err, that); - }); + var tasks = []; + var createTask = function(val, idx) { + return function(done) { fn(val, idx, done); }; + }; - return req; - }, - - /* - * Gets the preview results for a search job with given parameters. - * - * @example - * - * var job = service.jobs().item("mysid"); - * job.preview({count: 10}, function(err, results, job) { - * console.log("Fields: ", results.fields); - * }); - * - * @param {Object} params The parameters for retrieving preview results. For a list of available parameters, see the GET search/jobs/{search_id}/results_preview endpoint in the REST API documentation. - * @param {Function} callback A function to call when the preview results are retrieved : `(err, results, job)`. - * - * @endpoint search/jobs/{search_id}/results_preview - * @method splunkjs.Service.Job - */ - preview: function(params, callback) { - callback = callback || function() {}; - params = params || {}; - params.output_mode = params.output_mode || "json_rows"; + for(var i = 0; i < vals.length; i++) { + tasks.push(createTask(vals[i], i)); + } - var that = this; - return this.get("results_preview", params, function(err, response) { + root.series(tasks, function(err) { if (err) { - callback(err); + if (callback) { + callback(err); + } } else { - callback(null, response.data, that); + var args = utils.toArray(arguments); + args.shift(); + callback(null, args); } }); - }, - + }; + /** - * Gets the results for a search job with given parameters. - * - * The callback can get `undefined` for its `results` parameter if the - * job is not yet done. To avoid this, use the `Job.track()` method to - * wait until the job is complete prior to fetching the results with - * this method. - * - * @example + * Applies an asynchronous function over each element in an array, in parallel. + * A callback function is called when all tasks have been completed. If an + * error occurs, the callback function is called with an error parameter. * - * var job = service.jobs().item("mysid"); - * job.results({count: 10}, function(err, results, job) { - * console.log("Fields: ", results.results); - * }); + * @example + * + * var total = 0; + * Async.parallelEach( + * [1, 2, 3], + * function(val, idx, done) { + * var go = function() { + * total += val; + * done(); + * }; + * + * if (idx === 1) { + * Async.sleep(100, go); + * } + * else { + * go(); + * } + * }, + * function(err) { + * console.log(total); // == 6 + * } + * ); * - * @param {Object} params The parameters for retrieving search results. For a list of available parameters, see the GET search/jobs/{search_id}/results endpoint in the REST API documentation. - * @param {Function} callback A function to call when the results are retrieved: `(err, results, job)`. + * @param {Array} vals An array of values. + * @param {Function} fn A function (possibly asynchronous) to apply to each element: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err)`. * - * @endpoint search/jobs/{search_id}/results - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - results: function(params, callback) { + root.parallelEach = function(vals, fn, callback) { + vals = vals || []; callback = callback || function() {}; - params = params || {}; - params.output_mode = params.output_mode || "json_rows"; - var that = this; - return this.get("results", params, function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data, that); - } + root.parallelMap(vals, fn, function(err, result) { + callback(err); }); - }, - + }; + /** - * Gets the search log for this search job. + * Applies an asynchronous function over each element in an array, in series. + * A callback function is called when all tasks have been completed. If an + * error occurs, the callback function is called with an error parameter. * * @example + * + * var results = [1, 3, 6]; + * var total = 0; + * Async.seriesEach( + * [1, 2, 3], + * function(val, idx, done) { + * total += val; + * console.log(total === results[idx]); //== true + * done(); + * }, + * function(err) { + * console.log(total); //== 6 + * } + * ); * - * var job = service.jobs().item("mysid"); - * job.searchlog(function(err, searchlog, job) { - * console.log(searchlog); - * }); - * - * @param {Function} callback A function to call with the search log and job: `(err, searchlog, job)`. + * @param {Array} vals An array of values. + * @param {Function} fn A function (possibly asynchronous)to apply to each element: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err)`. * - * @endpoint search/jobs/{search_id}/search.log - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - searchlog: function(callback) { + root.seriesEach = function(vals, fn, callback) { + vals = vals || []; callback = callback || function() {}; - var that = this; - return this.get("search.log", {}, function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data, that); - } + root.seriesMap(vals, fn, function(err, result) { + callback(err); }); - }, - + }; + /** - * Sets the priority for this search job. + * Chains asynchronous tasks together by running a function (task) and + * passing the results as arguments to the next task. When all tasks have + * been completed or if an error occurs, a callback function is called with + * the results of the final task. * - * @example - * - * var job = service.jobs().item("mysid"); - * job.setPriority(6, function(err, job) { - * console.log("JOB PRIORITY SET"); - * }); + * Each task takes one or more parameters, depending on the previous task in the chain. + * The last parameter is always the function to run when the task is complete. * - * @param {Number} value The priority (an integer between 1-10). A higher value means a higher priority. - * @param {Function} callback A function to call with the search job: `(err, job)`. + * `err` arguments are not passed to individual tasks, but are are propagated + * to the final callback function. * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @example + * + * Async.chain( + * function(callback) { + * callback(null, 1, 2); + * }, + * function(val1, val2, callback) { + * callback(null, val1 + 1); + * }, + * function(val1, callback) { + * callback(null, val1 + 1, 5); + * }, + * function(err, val1, val2) { + * console.log(val1); //== 3 + * console.log(val2); //== 5 + * } + * ); + * + * @param {Function} tasks An array of functions: `(done)`. + * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, ...)`. + * + * @function splunkjs.Async */ - setPriority: function(value, callback) { - callback = callback || function() {}; + root.chain = function(tasks, callback) { + // Allow for just a list of functions + if (arguments.length > 1 && utils.isFunction(arguments[0])) { + var args = utils.toArray(arguments); + tasks = args.slice(0, args.length - 1); + callback = args[args.length - 1]; + } - var that = this; - var req = this.post("control", {action: "setpriority", priority: value}, function(err) { - callback(err, that); - }); + tasks = tasks || []; + callback = callback || function() {}; - return req; - }, - + if (!tasks.length) { + callback(); + } + else { + var innerChain = function(task, restOfTasks, result) { + var chainCallback = function(err) { + if (err) { + callback(err); + callback = function() {}; + } + else { + var args = utils.toArray(arguments); + args.shift(); + innerChain(restOfTasks[0], restOfTasks.slice(1), args); + } + }; + + var args = result; + if (!restOfTasks.length) { + args.push(callback); + } + else { + args.push(chainCallback); + } + + task.apply(null, args); + }; + + innerChain(tasks[0], tasks.slice(1), []); + } + }; + /** - * Sets the time to live (TTL) for the search job, which is the time before - * the search job expires after it has been completed and is still available. + * Runs a function after a delay (a specified timeout period). + * The main purpose of this function is to make `setTimeout` adhere to + * Node.js-style function signatures. * * @example + * + * Async.sleep(1000, function() { console.log("TIMEOUT");}); + * + * @param {Number} timeout The timeout period, in milliseconds. + * @param {Function} callback The function to call when the timeout occurs. * - * var job = service.jobs().item("mysid"); - * job.setTTL(1000, function(err, job) { - * console.log("JOB TTL SET"); - * }); - * - * @param {Number} value The time to live, in seconds. - * @param {Function} callback A function to call with the search job: `(err, job)`. - * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - setTTL: function(value, callback) { - callback = callback || function() {}; - - var that = this; - var req = this.post("control", {action: "setttl", ttl: value}, function(err) { - callback(err, that); - }); - - return req; - }, - + root.sleep = function(timeout, callback) { + setTimeout(function() { + callback(); + }, timeout); + }; + /** - * Gets the summary for this search job with the given parameters. + * Runs a callback function with additional parameters, which are appended to + * the parameter list. * * @example * - * var job = service.jobs().item("mysid"); - * job.summary({top_count: 5}, function(err, summary, job) { - * console.log("Summary: ", summary); - * }); - * - * @param {Object} params The parameters for retrieving the summary. For a list of available parameters, see the GET search/jobs/{search_id}/summary endpoint in the REST API documentation. - * @param {Function} callback A function to call with the summary and search job: `(err, summary, job)`. + * var callback = function(a, b) { + * console.log(a); //== 1 + * console.log(b); //== 2 + * }; + * + * var augmented = Async.augment(callback, 2); + * augmented(1); + * + * @param {Function} callback The callback function to augment. + * @param {Anything...} rest The number of arguments to add. * - * @endpoint search/jobs/{search_id}/summmary - * @method splunkjs.Service.Job + * @function splunkjs.Async */ - summary: function(params, callback) { - callback = callback || function() {}; - - var that = this; - return this.get("summary", params, function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data, that); + root.augment = function(callback) { + var args = Array.prototype.slice.call(arguments, 1); + return function() { + var augmentedArgs = Array.prototype.slice.call(arguments); + for(var i = 0; i < args.length; i++) { + augmentedArgs.push(args[i]); } + + callback.apply(null, augmentedArgs); + }; + }; + })(); + }); + + require.define("/lib/modularinputs/index.js", function (require, module, exports, __dirname, __filename) { + + // Copyright 2014 Splunk, Inc. + // + // 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. + + var Async = require('../async'); + + var ModularInputs = { + utils: require("./utils"), + ValidationDefinition: require('./validationdefinition'), + InputDefinition: require('./inputdefinition'), + Event: require('./event'), + EventWriter: require('./eventwriter'), + Argument: require('./argument'), + Scheme: require('./scheme'), + ModularInput: require('./modularinput'), + Logger: require('./logger') + }; + + /** + * Executes a modular input script. + * + * @param {Object} exports An instance of ModularInput representing a modular input. + * @param {Object} module The module object, used for determining if it's the main module (`require.main`). + */ + ModularInputs.execute = function(exports, module) { + if (require.main === module) { + // Slice process.argv ignoring the first argument as it is the path to the node executable. + var args = process.argv.slice(1); + + // Default empty functions for life cycle events. + exports.setup = exports.setup || ModularInputs.ModularInput.prototype.setup; + exports.start = exports.start || ModularInputs.ModularInput.prototype.start; + exports.end = exports.end || ModularInputs.ModularInput.prototype.end; + exports.teardown = exports.teardown || ModularInputs.ModularInput.prototype.teardown; + + // Setup the default values. + exports._inputDefinition = exports._inputDefinition || null; + exports._service = exports._service || null; + + // We will call close() on this EventWriter after streaming events, which is handled internally + // by ModularInput.runScript(). + var ew = new this.EventWriter(); + + // In order to ensure that everything that is written to stdout/stderr is flushed before we exit, + // set the file handles to blocking. This ensures we exit properly in a timely fashion. + // https://github.com/nodejs/node/issues/6456 + [process.stdout, process.stderr].forEach(function(s) { + s && s.isTTY && s._handle && s._handle.setBlocking && s._handle.setBlocking(true); }); - }, - - /** - * Gets the timeline for this search job. - * - * @example - * - * var job = service.jobs().item("mysid"); - * job.timeline({time_format: "%c"}, function(err, job, timeline) { - * console.log("Timeline: ", timeline); - * }); - * - * @param {Object} params The parameters for retrieving the timeline. For a list of available parameters, see the GET search/jobs/{search_id}/timeline endpoint in the REST API documentation. - * @param {Function} callback A function to call with the timeline and search job: `(err, timeline, job)`. - * - * @endpoint search/jobs/{search_id}/timeline - * @method splunkjs.Service.Job - */ - timeline: function(params, callback) { - callback = callback || function() {}; - - var that = this; - return this.get("timeline", params, function(err, response) { - if (err) { - callback(err); + + var scriptStatus; + Async.chain([ + function(done) { + exports.setup(done); + }, + function(done) { + ModularInputs.ModularInput.runScript(exports, args, ew, process.stdin, done); + }, + function(status, done) { + scriptStatus = status; + exports.teardown(done); + } + ], + function(err) { + if (err) { + ModularInputs.Logger.error('', err, ew._err); + } + + process.exit(scriptStatus || err ? 1 : 0); } - else { - callback(null, response.data, that); + ); + } + }; + + module.exports = ModularInputs; + + }); + + require.define("/lib/modularinputs/utils.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2014 Splunk, Inc. + // + // 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. + + var utils = require('../utils'); // Get all of the existing utils + + /** + * Parse the parameters from an `InputDefinition` or `ValidationDefinition`. + * + * This is a helper function for `parseXMLData`. + * + * The XML typically will look like this: + * + * `` + * `` + * `value1` + * `value2` + * `0` + * `default` + * `` + * `` + * `value11` + * `value22` + * `0` + * `default` + * `` + * `value1` + * `value2` + * `` + * `` + * `value3` + * `value4` + * `` + * `` + * `` + * + * @param {Object} an `Elementree` object representing the `` XML node. + * @return {Object} an `Elementree` object representing the parameters of node passed in. + */ + utils.parseParameters = function(paramNode) { + switch (paramNode.tag) { + case "param": + return paramNode.text; + case "param_list": + var parameters = []; + var paramChildren = paramNode.getchildren(); + for (var i = 0; i < paramChildren.length; i++) { + var mvp = paramChildren[i]; + parameters.push(mvp.text); + } + return parameters; + default: + throw new Error("Invalid configuration scheme, <" + paramNode.tag + "> tag unexpected."); + } + }; + + /** + * Parses the parameters from `Elementtree` representations of XML for + * `InputDefinition` and `ValidationDefinition` objects. + * + * @param {Object} a parent `Elementtree` element object. + * @param {String} the name of the child element to parse parameters from. + * @return {Object} an object of the parameters parsed. + */ + utils.parseXMLData = function(parentNode, childNodeTag) { + var data = {}; + var children = parentNode.getchildren(); + for (var i = 0; i < children.length; i++) { + var child = children[i]; + if (child.tag === childNodeTag) { + if (childNodeTag === "stanza") { + data[child.get("name")] = {}; + var stanzaChildren = child.getchildren(); + for (var p = 0; p < stanzaChildren.length; p++) { + var param = stanzaChildren[p]; + data[child.get("name")][param.get("name")] = utils.parseParameters(param); + } } - }); - }, - + } + else if ("item" === parentNode.tag) { + data[child.get("name")] = utils.parseParameters(child); + } + } + return data; + }; + + module.exports = utils; + + }); + + require.define("/lib/modularinputs/validationdefinition.js", function (require, module, exports, __dirname, __filename) { + /*!*/ + // Copyright 2014 Splunk, Inc. + // + // 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. + + (function() { + var ET = require("elementtree"); + var utils = require("./utils"); + /** - * Touches a search job, which means extending the expiration time of - * the search to now plus the time to live (TTL). + * This class represents the XML sent by Splunk for external validation of a + * new modular input. * * @example * - * var job = service.jobs().item("mysid"); - * job.touch(function(err) { - * console.log("JOB TOUCHED"); - * }); - * - * @param {Function} callback A function to call with the search job: `(err, job)`. + * var v = new ValidationDefinition(); * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job + * @class splunkjs.ModularInputs.ValidationDefinition */ - touch: function(callback) { - callback = callback || function() {}; - - var that = this; - var req = this.post("control", {action: "touch"}, function(err) { - callback(err, that); - }); - - return req; - }, - + function ValidationDefinition() { + this.metadata = {}; + this.parameters = {}; + } + /** - * Starts polling the status of this search job, and fires callbacks - * upon each status change. - * - * @param {Object} options A dictionary of optional parameters: - * - `period` (_integer_): The number of milliseconds to wait between each poll. Defaults to 500. - * @param {Object|Function} callbacks A dictionary of optional callbacks: - * - `ready`: A function `(job)` invoked when the job's properties first become available. - * - `progress`: A function `(job)` invoked whenever new job properties are available. - * - `done`: A function `(job)` invoked if the job completes successfully. No further polling is done. - * - `failed`: A function `(job)` invoked if the job fails executing on the server. No further polling is done. - * - `error`: A function `(err)` invoked if an error occurs while polling. No further polling is done. - * Or, if a function `(job)`, equivalent to passing it as a `done` callback. + * Creates a `ValidationDefinition` from a provided string containing XML. + * + * This function will throw an exception if `str` + * contains unexpected XML. * - * @method splunkjs.Service.Job + * The XML typically will look like this: + * + * `` + * ` myHost` + * ` https://127.0.0.1:8089` + * ` 123102983109283019283` + * ` /opt/splunk/var/lib/splunk/modinputs` + * ` ` + * ` value1` + * ` ` + * ` value2` + * ` value3` + * ` value4` + * ` ` + * ` ` + * `` + * + * @param {String} str A string containing XML to parse. + * + * @function splunkjs.ModularInputs.ValidationDefinition */ - track: function(options, callbacks) { - var period = options.period || 500; // ms - - if (utils.isFunction(callbacks)) { - callbacks = { - done: callbacks - }; - } - - var noCallbacksAfterReady = ( - !callbacks.progress && - !callbacks.done && - !callbacks.failed && - !callbacks.error - ); - - callbacks.ready = callbacks.ready || function() {}; - callbacks.progress = callbacks.progress || function() {}; - callbacks.done = callbacks.done || function() {}; - callbacks.failed = callbacks.failed || function() {}; - callbacks.error = callbacks.error || function() {}; - - // For use by tests only - callbacks._preready = callbacks._preready || function() {}; - callbacks._stoppedAfterReady = callbacks._stoppedAfterReady || function() {}; - - var that = this; - var emittedReady = false; - var doneLooping = false; - Async.whilst( - function() { return !doneLooping; }, - function(nextIteration) { - that.fetch(function(err, job) { - if (err) { - nextIteration(err); - return; - } - - var dispatchState = job.properties().dispatchState; - var notReady = dispatchState === "QUEUED" || dispatchState === "PARSING"; - if (notReady) { - callbacks._preready(job); - } - else { - if (!emittedReady) { - callbacks.ready(job); - emittedReady = true; - - // Optimization: Don't keep polling the job if the - // caller only cares about the `ready` event. - if (noCallbacksAfterReady) { - callbacks._stoppedAfterReady(job); - - doneLooping = true; - nextIteration(); - return; - } - } - - callbacks.progress(job); - - var props = job.properties(); - - if (dispatchState === "DONE" && props.isDone) { - callbacks.done(job); - - doneLooping = true; - nextIteration(); - return; - } - else if (dispatchState === "FAILED" && props.isFailed) { - callbacks.failed(job); - - doneLooping = true; - nextIteration(); - return; - } - } - - Async.sleep(period, nextIteration); - }); - }, - function(err) { - if (err) { - callbacks.error(err); - } - } - ); - }, - - /** - * Resumes a search job. - * - * @example - * - * var job = service.jobs().item("mysid"); - * job.unpause(function(err) { - * console.log("JOB UNPAUSED"); - * }); - * - * @param {Function} callback A function to call with the search job: `(err, job)`. - * - * @endpoint search/jobs/{search_id}/control - * @method splunkjs.Service.Job - */ - unpause: function(callback) { - callback = callback || function() {}; - - var that = this; - var req = this.post("control", {action: "unpause"}, function(err) { - callback(err, that); - }); - - return req; - } - }); - - /** - * Represents a collection of search jobs. You can create and list search - * jobs using this collection container, or get a specific search job. - * - * @endpoint search/jobs - * @class splunkjs.Service.Jobs - * @extends splunkjs.Service.Collection - */ - root.Jobs = root.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.Jobs - */ - path: function() { - return Paths.jobs; - }, - - /** - * Creates a local instance of a job. - * - * @param {Object} props The properties for this new job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * @return {splunkjs.Service.Job} A new `splunkjs.Service.Job` instance. - * - * @method splunkjs.Service.Jobs - */ - instantiateEntity: function(props) { - var sid = props.content.sid; - var entityNamespace = utils.namespaceFromProperties(props); - return new root.Job(this.service, sid, entityNamespace); - }, - - /** - * Constructor for `splunkjs.Service.Jobs`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace Namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @return {splunkjs.Service.Jobs} A new `splunkjs.Service.Jobs` instance. - * - * @method splunkjs.Service.Jobs - */ - init: function(service, namespace) { - this._super(service, this.path(), namespace); - - // We perform the bindings so that every function works - // properly when it is passed as a callback. - this.create = utils.bind(this, this.create); - }, - - /** - * Creates a search job with a given search query and optional parameters, including `exec_mode` to specify the type of search: - * - * - Use `exec_mode=normal` to return a search job ID immediately (default). - * Poll for completion to find out when you can retrieve search results. - * - * - Use `exec_mode=blocking` to return the search job ID when the search has finished. - * - * To run a oneshot search, which does not create a job but rather returns the search results, use `Service.Jobs.oneshotSearch`. - * - * @param {String} query The search query. - * @param {Object} params A dictionary of properties for the search job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * @param {Function} callback A function to call with the created job: `(err, createdJob)`. - * - * @endpoint search/jobs - * @method splunkjs.Service.Jobs - */ - create: function(query, params, callback) { - // If someone called us with the default style of (params, callback), - // lets make it work - if (utils.isObject(query) && utils.isFunction(params) && !callback) { - callback = params; - params = query; - query = params.search; - } - - callback = callback || function() {}; - params = params || {}; - params.search = query; - - if ((params.exec_mode || "").toLowerCase() === "oneshot") { - throw new Error("Please use splunkjs.Service.Jobs.oneshotSearch for exec_mode=oneshot"); - } - - if (!params.search) { - callback("Must provide a query to create a search job"); - return; - } - var that = this; - return this.post("", params, function(err, response) { - if (err) { - callback(err); + ValidationDefinition.parse = function(str) { + var definition = new ValidationDefinition(); + var rootChildren = ET.parse(str).getroot().getchildren(); + + for (var i = 0; i < rootChildren.length; i++) { + var node = rootChildren[i]; + if (node.tag === "item") { + definition.metadata["name"] = node.get("name"); + definition.parameters = utils.parseXMLData(node, ""); } else { - var job = new root.Job(that.service, response.data.sid, that.namespace); - callback(null, job); + definition.metadata[node.tag] = node.text; } - }); - }, - - /** - * Creates a search job with a given search query and optional parameters, including `exec_mode` to specify the type of search: - * - * - Use `exec_mode=normal` to return a search job ID immediately (default). - * Poll for completion to find out when you can retrieve search results. - * - * - Use `exec_mode=blocking` to return the search job ID when the search has finished. - * - * To run a oneshot search, which does not create a job but rather returns the search results, use `Service.Jobs.oneshotSearch`. - * - * @example - * - * var jobs = service.jobs(); - * jobs.search("search ERROR", {id: "myjob_123"}, function(err, newJob) { - * console.log("CREATED": newJob.sid); - * }); - * - * @param {String} query The search query. - * @param {Object} params A dictionary of properties for the search job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. - * @param {Function} callback A function to call with the new search job: `(err, createdJob)`. - * - * @endpoint search/jobs - * @method splunkjs.Service.Jobs - */ - search: function(query, params, callback) { - return this.create(query, params, callback); - }, - - /** - * Creates a oneshot search from a given search query and parameters. - * - * @example - * - * var jobs = service.jobs(); - * jobs.oneshotSearch("search ERROR", {id: "myjob_123"}, function(err, results) { - * console.log("RESULT FIELDS": results.fields); - * }); - * - * @param {String} query The search query. - * @param {Object} params A dictionary of properties for the search: - * - `output_mode` (_string_): Specifies the output format of the results (XML, JSON, or CSV). - * - `earliest_time` (_string_): Specifies the earliest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. - * - `latest_time` (_string_): Specifies the latest time in the time range to search. The time string can be a UTC time (with fractional seconds), a relative time specifier (to now), or a formatted time string. - * - `rf` (_string_): Specifies one or more fields to add to the search. - * @param {Function} callback A function to call with the results of the search: `(err, results)`. - * - * @endpoint search/jobs - * @method splunkjs.Service.Jobs - */ - oneshotSearch: function(query, params, callback) { - // If someone called us with the default style of (params, callback), - // lets make it work - if (utils.isObject(query) && utils.isFunction(params) && !callback) { - callback = params; - params = query; - query = params.search; - } - - callback = callback || function() {}; - params = params || {}; - params.search = query; - params.exec_mode = "oneshot"; - - if (!params.search) { - callback("Must provide a query to create a search job"); } - - var outputMode = params.output_mode || "json_rows"; - - var path = this.qualifiedPath; - var method = "POST"; - var headers = {}; - var post = params; - var get = {output_mode: outputMode}; - var body = null; - - var req = this.service.request( - path, - method, - get, - post, - body, - headers, - function(err, response) { - if (err) { - callback(err); - } - else { - callback(null, response.data); - } - } - ); - - return req; - } - }); - - /** - * Represents a field of a data model object. - * This is a helper class for `DataModelCalculation` - * and `DataModelObject`. - * - * Has these properties: - * - `fieldName` (_string_): The name of this field. - * - `displayName` (_string_): A human readable name for this field. - * - `type` (_string_): The type of this field. - * - `multivalued` (_boolean_): Whether this field is multivalued. - * - `required` (_boolean_): Whether this field is required. - * - `hidden` (_boolean_): Whether this field should be displayed in a data model UI. - * - `editable` (_boolean_): Whether this field can be edited. - * - `comment` (_string_): A comment for this field, or `null` if there isn't one. - * - `fieldSearch` (_string_): A search query fragment for this field. - * - `lineage` (_array_): An array of strings of the lineage of the data model - * on which this field is defined. - * - `owner` (_string_): The name of the data model object on which this field is defined. - * - * Possible types for a data model field: - * - `string` - * - `boolean` - * - `number` - * - `timestamp` - * - `objectCount` - * - `childCount` - * - `ipv4` - * - * @class splunkjs.Service.DataModelField - */ - root.DataModelField = Class.extend({ - _types: [ "string", "number", "timestamp", "objectCount", "childCount", "ipv4", "boolean"], - - /** - * Constructor for a data model field. - * SDK users are not expected to invoke this constructor directly. - * - * @constructor - * @param {Object} props A dictionary of properties to set: - * - `fieldName` (_string_): The name of this field. - * - `displayName` (_string_): A human readable name for this field. - * - `type` (_string_): The type of this field, see valid types in class docs. - * - `multivalue` (_boolean_): Whether this field is multivalued. - * - `required` (_boolean_): Whether this field is required on events in the object - * - `hidden` (_boolean_): Whether this field should be displayed in a data model UI. - * - `editable` (_boolean_): Whether this field can be edited. - * - `comment` (_string_): A comment for this field, or `null` if there isn't one. - * - `fieldSearch` (_string_): A search query fragment for this field. - * - `lineage` (_string_): The lineage of the data model object on which this field - * is defined, items are delimited by a dot. This is converted into an array of - * strings upon construction. - * - * @method splunkjs.Service.DataModelField - */ - init: function(props) { - props = props || {}; - props.owner = props.owner || ""; - - this.name = props.fieldName; - this.displayName = props.displayName; - this.type = props.type; - this.multivalued = props.multivalue; - this.required = props.required; - this.hidden = props.hidden; - this.editable = props.editable; - this.comment = props.comment || null; - this.fieldSearch = props.fieldSearch; - this.lineage = props.owner.split("."); - this.owner = this.lineage[this.lineage.length - 1]; - }, - - /** - * Is this data model field of type string? - * - * @return {Boolean} True if this data model field is of type string. - * - * @method splunkjs.Service.DataModelField - */ - isString: function() { - return "string" === this.type; - }, - - /** - * Is this data model field of type number? - * - * @return {Boolean} True if this data model field is of type number. - * - * @method splunkjs.Service.DataModelField - */ - isNumber: function() { - return "number" === this.type; - }, - - /** - * Is this data model field of type timestamp? - * - * @return {Boolean} True if this data model field is of type timestamp. - * - * @method splunkjs.Service.DataModelField - */ - isTimestamp: function() { - return "timestamp" === this.type; - }, - - /** - * Is this data model field of type object count? - * - * @return {Boolean} True if this data model field is of type object count. - * - * @method splunkjs.Service.DataModelField - */ - isObjectcount: function() { - return "objectCount" === this.type; - }, - - /** - * Is this data model field of type child count? - * - * @return {Boolean} True if this data model field is of type child count. - * - * @method splunkjs.Service.DataModelField - */ - isChildcount: function() { - return "childCount" === this.type; - }, - - /** - * Is this data model field of type ipv4? - * - * @return {Boolean} True if this data model field is of type ipv4. - * - * @method splunkjs.Service.DataModelField - */ - isIPv4: function() { - return "ipv4" === this.type; - }, - - /** - * Is this data model field of type boolean? - * - * @return {Boolean} True if this data model field is of type boolean. - * - * @method splunkjs.Service.DataModelField - */ - isBoolean: function() { - return "boolean" === this.type; - } + return definition; + }; + + module.exports = ValidationDefinition; + })(); }); - /** - * Represents a constraint on a `DataModelObject` or a `DataModelField`. - * - * Has these properties: - * - `query` (_string_): The search query defining this data model constraint. - * - `lineage` (_array_): The lineage of this data model constraint. - * - `owner` (_string_): The name of the data model object that owns - * this data model constraint. - * - * @class splunkjs.Service.DataModelConstraint - */ - root.DataModelConstraint = Class.extend({ - /** - * Constructor for a data model constraint. - * SDK users are not expected to invoke this constructor directly. - * - * @constructor - * @param {Object} props A dictionary of properties to set: - * - `search` (_string_): The Splunk search query this constraint specifies. - * - `owner` (_string_): The lineage of the data model object that owns this - * constraint, items are delimited by a dot. This is converted into - * an array of strings upon construction. - * - * @method splunkjs.Service.DataModelConstraint - */ - init: function(props) { - props = props || {}; - props.owner = props.owner || ""; - - this.query = props.search; - this.lineage = props.owner.split("."); - this.owner = this.lineage[this.lineage.length - 1]; - } + require.define("/node_modules/elementtree/package.json", function (require, module, exports, __dirname, __filename) { + module.exports = {"main":"lib/elementtree.js"} }); + require.define("/node_modules/elementtree/lib/elementtree.js", function (require, module, exports, __dirname, __filename) { /** - * Used for specifying a calculation on a `DataModelObject`. - * - * Has these properties: - * - `id` (_string_): The ID for this data model calculation. - * - `type` (_string_): The type of this data model calculation. - * - `comment` (_string_|_null_): The comment for this data model calculation, or `null`. - * - `editable` (_boolean_): True if this calculation can be edited, false otherwise. - * - `lineage` (_array_): The lineage of the data model object on which this calculation - * is defined in an array of strings. - * - `owner` (_string_): The data model that this calculation belongs to. - * - `outputFields` (_array_): The fields output by this calculation. + * Copyright 2011 Rackspace * - * The Rex and Eval types have an additional property: - * - `expression` (_string_): The expression to use for this calculation. + * 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 * - * The Rex and GeoIP types have an additional property: - * - `inputField` (_string_): The field to use for calculation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * The Lookup type has additional properties: - * - `lookupName` (_string_): The name of the lookup to perform. - * - `inputFieldMappings` (_object_): The mappings from fields in the events to fields in the lookup. + * 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. * - * Valid types of calculations are: - * - `Lookup` - * - `Eval` - * - `GeoIP` - * - `Rex` - * - * @class splunkjs.Service.DataModelCalculation */ - root.DataModelCalculation = Class.extend({ - _types: ["Lookup", "Eval", "GeoIP", "Rex"], - - /** - * Constructor for a data model calculation. - * SDK users are not expected to invoke this constructor directly. - * - * @constructor - * @param {Object} props A dictionary of properties to set: - * - `calculationID` (_string_): The ID of this calculation. - * - `calculationType` (_string_): The type of this calculation, see class docs for valid types. - * - `editable` (_boolean_): Whether this calculation can be edited. - * - `comment` (_string_): A comment for this calculation, or `null` if there isn't one. - * - `owner` (_string_): The lineage of the data model object on which this calculation - * is defined, items are delimited by a dot. This is converted into an array of - * strings upon construction. - * - `outputFields` (_array_): An array of the fields this calculation generates. - * - `expression` (_string_): The expression to use for this calculation; exclusive to `Eval` and `Rex` calculations (optional) - * - `inputField` (_string_): The field to use for calculation; exclusive to `GeoIP` and `Rex` calculations (optional) - * - `lookupName` (_string_): The name of the lookup to perform; exclusive to `Lookup` calculations (optional) - * - `inputFieldMappings` (_array_): One element array containing an object with the mappings from fields in the events to fields - * in the lookup; exclusive to `Lookup` calculations (optional) - * - * @method splunkjs.Service.DataModelCalculation - */ - init: function(props) { - props = props || {}; - props.owner = props.owner || ""; - - this.id = props.calculationID; - this.type = props.calculationType; - this.comment = props.comment || null; - this.editable = props.editable; - this.lineage = props.owner.split("."); - this.owner = this.lineage[this.lineage.length - 1]; - - this.outputFields = []; - for (var i = 0; i < props.outputFields.length; i++) { - this.outputFields[props.outputFields[i].fieldName] = new root.DataModelField(props.outputFields[i]); - } - - if ("Eval" === this.type || "Rex" === this.type) { - this.expression = props.expression; - } - if ("GeoIP" === this.type || "Rex" === this.type) { - this.inputField = props.inputField; - } - if ("Lookup" === this.type) { - this.lookupName = props.lookupName; - this.inputFieldMappings = props.lookupInputs[0]; - } - }, - - /** - * Returns an array of strings of output field names. - * - * @return {Array} An array of strings of output field names. - * - * @method splunkjs.Service.DataModelCalculation - */ - outputFieldNames: function() { - return Object.keys(this.outputFields); - }, - - /** - * Is this data model calculation editable? - * - * @return {Boolean} True if this data model calculation is editable. - * - * @method splunkjs.Service.DataModelCalculation - */ - isEditable: function() { - return !!this.editable; - }, - - /** - * Is this data model calculation of type lookup? - * - * @return {Boolean} True if this data model calculation is of type lookup. - * - * @method splunkjs.Service.DataModelCalculation - */ - isLookup: function() { - return "Lookup" === this.type; - }, - - /** - * Is this data model calculation of type eval? - * - * @return {Boolean} True if this data model calculation is of type eval. - * - * @method splunkjs.Service.DataModelCalculation - */ - isEval: function() { - return "Eval" === this.type; - }, - - /** - * Is this data model calculation of type Rex? - * - * @return {Boolean} True if this data model calculation is of type Rex. - * - * @method splunkjs.Service.DataModelCalculation - */ - isRex: function() { - return "Rex" === this.type; - }, - - /** - * Is this data model calculation of type GeoIP? - * - * @return {Boolean} True if this data model calculation is of type GeoIP. - * - * @method splunkjs.Service.DataModelCalculation - */ - isGeoIP: function() { - return "GeoIP" === this.type; + + var sprintf = require('./sprintf').sprintf; + + var utils = require('./utils'); + var ElementPath = require('./elementpath'); + var TreeBuilder = require('./treebuilder').TreeBuilder; + var get_parser = require('./parser').get_parser; + var constants = require('./constants'); + + var element_ids = 0; + + function Element(tag, attrib) + { + this._id = element_ids++; + this.tag = tag; + this.attrib = {}; + this.text = null; + this.tail = null; + this._children = []; + + if (attrib) { + this.attrib = utils.merge(this.attrib, attrib); + } + } + + Element.prototype.toString = function() + { + return sprintf("", this.tag, this._id); + }; + + Element.prototype.makeelement = function(tag, attrib) + { + return new Element(tag, attrib); + }; + + Element.prototype.len = function() + { + return this._children.length; + }; + + Element.prototype.getItem = function(index) + { + return this._children[index]; + }; + + Element.prototype.setItem = function(index, element) + { + this._children[index] = element; + }; + + Element.prototype.delItem = function(index) + { + this._children.splice(index, 1); + }; + + Element.prototype.getSlice = function(start, stop) + { + return this._children.slice(start, stop); + }; + + Element.prototype.setSlice = function(start, stop, elements) + { + var i; + var k = 0; + for (i = start; i < stop; i++, k++) { + this._children[i] = elements[k]; + } + }; + + Element.prototype.delSlice = function(start, stop) + { + this._children.splice(start, stop - start); + }; + + Element.prototype.append = function(element) + { + this._children.push(element); + }; + + Element.prototype.extend = function(elements) + { + this._children.concat(elements); + }; + + Element.prototype.insert = function(index, element) + { + this._children[index] = element; + }; + + Element.prototype.remove = function(element) + { + this._children = this._children.filter(function(e) { + /* TODO: is this the right way to do this? */ + if (e._id === element._id) { + return false; } - }); + return true; + }); + }; - /** - * Pivot represents data about a pivot report returned by the Splunk Server. - * - * Has these properties: - * - `service` (_splunkjs.Service_): A `Service` instance. - * - `search` (_string_): The search string for running the pivot report. - * - `drilldownSearch` (_string_): The search for running this pivot report using drilldown. - * - `openInSearch` (_string_): Equivalent to search parameter, but listed more simply. - * - `prettyQuery` (_string_): Equivalent to `openInSearch`. - * - `pivotSearch` (_string_): A pivot search command based on the named data model. - * - `tstatsSearch` (_string_): The search for running this pivot report using tstats. - * - * @class splunkjs.Service.Pivot - */ - root.Pivot = Class.extend({ - /** - * Constructor for a pivot. - * SDK users are not expected to invoke this constructor directly. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} props A dictionary of properties to set: - * - `search` (_string_): The search string for running the pivot report. - * - `drilldown_search` (_string_): The search for running this pivot report using drilldown. - * - `open_in_search` (_string_): Equivalent to search parameter, but listed more simply. - * - `pivot_search` (_string_): A pivot search command based on the named data model. - * - `tstats_search` (_string_|_null_): The search for running this pivot report using tstats, null if acceleration is disabled. - * - * @method splunkjs.Service.Pivot - */ - init: function(service, props) { - this.service = service; - this.search = props.search; - this.drilldownSearch = props.drilldown_search; - this.prettyQuery = this.openInSearch = props.open_in_search; - this.pivotSearch = props.pivot_search; - this.tstatsSearch = props.tstats_search || null; - - this.run = utils.bind(this, this.run); - }, - - /** - * Starts a search job running this pivot, accelerated if possible. - * - * @param {Object} args A dictionary of properties for the search job (optional). For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. - * @param {Function} callback A function to call when done creating the search job: `(err, job)`. - * @method splunkjs.Service.Pivot - */ - run: function(args, callback) { - if (utils.isUndefined(callback)) { - callback = args; - args = {}; - } - if (!args || Object.keys(args).length === 0) { - args = {}; - } - - // If tstats is undefined, use pivotSearch (try to run an accelerated search if possible) - this.service.search(this.tstatsSearch || this.pivotSearch, args, callback); + Element.prototype.getchildren = function() { + return this._children; + }; + + Element.prototype.find = function(path) + { + return ElementPath.find(this, path); + }; + + Element.prototype.findtext = function(path, defvalue) + { + return ElementPath.findtext(this, path, defvalue); + }; + + Element.prototype.findall = function(path, defvalue) + { + return ElementPath.findall(this, path, defvalue); + }; + + Element.prototype.clear = function() + { + this.attrib = {}; + this._children = []; + this.text = null; + this.tail = null; + }; + + Element.prototype.get = function(key, defvalue) + { + if (this.attrib[key] !== undefined) { + return this.attrib[key]; + } + else { + return defvalue; + } + }; + + Element.prototype.set = function(key, value) + { + this.attrib[key] = value; + }; + + Element.prototype.keys = function() + { + return Object.keys(this.attrib); + }; + + Element.prototype.items = function() + { + return utils.items(this.attrib); + }; + + /* + * In python this uses a generator, but in v8 we don't have em, + * so we use a callback instead. + **/ + Element.prototype.iter = function(tag, callback) + { + var self = this; + var i, child; + + if (tag === "*") { + tag = null; + } + + if (tag === null || this.tag === tag) { + callback(self); + } + + for (i = 0; i < this._children.length; i++) { + child = this._children[i]; + child.iter(tag, function(e) { + callback(e); + }); + } + }; + + Element.prototype.itertext = function(callback) + { + this.iter(null, function(e) { + if (e.text) { + callback(e.text); } - }); - + + if (e.tail) { + callback(e.tail); + } + }); + }; + + + function SubElement(parent, tag, attrib) { + var element = parent.makeelement(tag, attrib); + parent.append(element); + return element; + } + + function Comment(text) { + var element = new Element(Comment); + if (text) { + element.text = text; + } + return element; + } + + function CData(text) { + var element = new Element(CData); + if (text) { + element.text = text; + } + return element; + } + + function ProcessingInstruction(target, text) + { + var element = new Element(ProcessingInstruction); + element.text = target; + if (text) { + element.text = element.text + " " + text; + } + return element; + } + + function QName(text_or_uri, tag) + { + if (tag) { + text_or_uri = sprintf("{%s}%s", text_or_uri, tag); + } + this.text = text_or_uri; + } + + QName.prototype.toString = function() { + return this.text; + }; + + function ElementTree(element) + { + this._root = element; + } + + ElementTree.prototype.getroot = function() { + return this._root; + }; + + ElementTree.prototype._setroot = function(element) { + this._root = element; + }; + + ElementTree.prototype.parse = function(source, parser) { + if (!parser) { + parser = get_parser(constants.DEFAULT_PARSER); + parser = new parser.XMLParser(new TreeBuilder()); + } + + parser.feed(source); + this._root = parser.close(); + return this._root; + }; + + ElementTree.prototype.iter = function(tag, callback) { + this._root.iter(tag, callback); + }; + + ElementTree.prototype.find = function(path) { + return this._root.find(path); + }; + + ElementTree.prototype.findtext = function(path, defvalue) { + return this._root.findtext(path, defvalue); + }; + + ElementTree.prototype.findall = function(path) { + return this._root.findall(path); + }; + /** - * PivotSpecification represents a pivot to be done on a particular data model object. - * The user creates a PivotSpecification on some data model object, adds filters, row splits, - * column splits, and cell values, then calls the pivot method to query splunkd and - * get a set of SPL queries corresponding to this specification. - * - * Call the `pivot` method to query Splunk for SPL queries corresponding to this pivot. - * - * This class supports a fluent API, each function except `init`, `toJsonObject` & `pivot` - * return the modified `splunkjs.Service.PivotSpecification` instance. - * - * @example - * service.dataModels().fetch(function(err, dataModels) { - * var searches = dataModels.item("internal_audit_logs").objectByName("searches"); - * var pivotSpecification = searches.createPivotSpecification(); - * pivotSpecification - * .addRowSplit("user", "Executing user") - * .addRangeColumnSplit("exec_time", {limit: 4}) - * .addCellValue("search", "Search Query", "values") - * .pivot(function(err, pivot) { - * console.log("Got a Pivot object from the Splunk server!"); - * }); - * }); - * - * Has these properties: - * - `dataModelObject` (_splunkjs.Service.DataModelObject_): The `DataModelObject` from which - * this `PivotSpecification` was created. - * - `columns` (_array_): The column splits on this `PivotSpecification`. - * - `rows` (_array_): The row splits on this `PivotSpecification`. - * - `filters` (_array_): The filters on this `PivotSpecification`. - * - `cells` (_array_): The cell aggregations for this`PivotSpecification`. - * - `accelerationNamespace` (_string_|_null_): The name of the `DataModel` that owns the `DataModelObject` - * on which this `PivotSpecification` was created if the `DataModel` is accelerated. Alternatively, - * you can set this property manually to the sid of an acceleration job in the format `sid=`. - * - * Valid comparison types are: - * - `boolean` - * - `string` - * - `number` - * - `ipv4` - * - * Valid boolean comparisons are: - * - `=` - * - `is` - * - `isNull` - * - `isNotNull` - * - * Valid string comparisons are: - * - `=` - * - `is` - * - `isNull` - * - `isNotNull` - * - `contains` - * - `doesNotContain` - * - `startsWith` - * - `endsWith` - * - `regex` - * - * Valid number comparisons are: - * - `=` - * - `!=` - * - `<` - * - `>` - * - `<=` - * - `>=` - * - `is` - * - `isNull` - * - `isNotNull` - * - * Valid ipv4 comparisons are: - * - `is` - * - `isNull` - * - `isNotNull` - * - `contains` - * - `doesNotContain` - * - `startsWith` - * - * Valid binning values are: - * - `auto` - * - `year` - * - `month` - * - `day` - * - `hour` - * - `minute` - * - `second` - * - * Valid sort directions are: - * - `ASCENDING` - * - `DECENDING` - * - `DEFAULT` - * - * Valid stats functions are: - * - `list` - * - `values` - * - `first` - * - `last` - * - `count` - * - `dc` - * - `sum` - * - `average` - * - `max` - * - `min` - * - `stdev` - * - `duration` - * - `earliest` - * - `latest` - * - * @class splunkjs.Service.PivotSpecification + * Unlike ElementTree, we don't write to a file, we return you a string. */ - root.PivotSpecification = Class.extend({ - _comparisons: { - boolean: ["=", "is", "isNull", "isNotNull"], - string: ["=", "is", "isNull", "isNotNull", "contains", "doesNotContain", "startsWith", "endsWith", "regex"], - number: ["=", "!=", "<", ">", "<=", ">=", "is", "isNull", "isNotNull"], - ipv4: ["is", "isNull", "isNotNull", "contains", "doesNotContain", "startsWith"] - }, - _binning: ["auto", "year", "month", "day", "hour", "minute", "second"], - _sortDirection: ["ASCENDING", "DESCENDING", "DEFAULT"], - _statsFunctions: ["list", "values", "first", "last", "count", "dc", "sum", "average", "max", "min", "stdev", "duration", "earliest", "latest"], - - /** - * Constructor for a pivot specification. - * - * @constructor - * @param {splunkjs.Service.DataModel} parentDataModel The `DataModel` that owns this data model object. - * - * @method splunkjs.Service.PivotSpecification - */ - init: function(dataModelObject) { - this.dataModelObject = dataModelObject; - this.columns = []; - this.rows = []; - this.filters = []; - this.cells = []; - - this.accelerationNamespace = dataModelObject.dataModel.isAccelerated() ? - dataModelObject.dataModel.name : null; - - this.run = utils.bind(this, this.run); - this.pivot = utils.bind(this, this.pivot); - }, - - /** - * Set the acceleration cache for this pivot specification to a job, - * usually generated by createLocalAccelerationJob on a DataModelObject - * instance, as the acceleration cache for this pivot specification. - * - * @param {String|splunkjs.Service.Job} sid The sid of an acceleration job, - * or, a `splunkjs.Service.Job` instance. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - setAccelerationJob: function(sid) { - // If a search object is passed in, get its sid - if (sid && sid instanceof Service.Job) { - sid = sid.sid; - } - - if (!sid) { - throw new Error("Sid to use for acceleration must not be null."); - } - - this.accelerationNamespace = "sid=" + sid; - return this; - }, - - /** - * Add a filter on a boolean valued field. The filter will be a constraint of the form - * `field `comparison` compareTo`, for example: `is_remote = false`. - * - * @param {String} fieldName The name of field to filter on - * @param {String} comparisonType The type of comparison, see class docs for valid types. - * @param {String} comparisonOp The comparison, see class docs for valid comparisons, based on type. - * @param {String} compareTo The value to compare the field to. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addFilter: function(fieldName, comparisonType, comparisonOp, compareTo) { - if (!this.dataModelObject.hasField(fieldName)) { - throw new Error("Cannot add filter on a nonexistent field."); - } - if (comparisonType !== this.dataModelObject.fieldByName(fieldName).type) { - throw new Error( - "Cannot add " + comparisonType + - " filter on " + fieldName + - " because it is of type " + - this.dataModelObject.fieldByName(fieldName).type); - } - if (!utils.contains(this._comparisons[comparisonType], comparisonOp)) { - throw new Error( - "Cannot add " + comparisonType + - " filter because " + comparisonOp + - " is not a valid comparison operator"); + ElementTree.prototype.write = function(options) { + var sb = []; + options = utils.merge({ + encoding: 'utf-8', + xml_declaration: null, + default_namespace: null, + method: 'xml'}, options); + + if (options.xml_declaration !== false) { + sb.push("\n"); + } + + if (options.method === "text") { + _serialize_text(sb, self._root, encoding); + } + else { + var qnames, namespaces, indent, indent_string; + var x = _namespaces(this._root, options.encoding, options.default_namespace); + qnames = x[0]; + namespaces = x[1]; + + if (options.hasOwnProperty('indent')) { + indent = 0; + indent_string = new Array(options.indent + 1).join(' '); + } + else { + indent = false; + } + + if (options.method === "xml") { + _serialize_xml(function(data) { + sb.push(data); + }, this._root, options.encoding, qnames, namespaces, indent, indent_string); + } + else { + /* TODO: html */ + throw new Error("unknown serialization method "+ options.method); + } + } + + return sb.join(""); + }; + + var _namespace_map = { + /* "well-known" namespace prefixes */ + "http://www.w3.org/XML/1998/namespace": "xml", + "http://www.w3.org/1999/xhtml": "html", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", + "http://schemas.xmlsoap.org/wsdl/": "wsdl", + /* xml schema */ + "http://www.w3.org/2001/XMLSchema": "xs", + "http://www.w3.org/2001/XMLSchema-instance": "xsi", + /* dublic core */ + "http://purl.org/dc/elements/1.1/": "dc", + }; + + function register_namespace(prefix, uri) { + if (/ns\d+$/.test(prefix)) { + throw new Error('Prefix format reserved for internal use'); + } + + if (_namespace_map.hasOwnProperty(uri) && _namespace_map[uri] === prefix) { + delete _namespace_map[uri]; + } + + _namespace_map[uri] = prefix; + } + + + function _escape(text, encoding, isAttribute, isText) { + if (text) { + text = text.toString(); + text = text.replace(/&/g, '&'); + text = text.replace(//g, '>'); + if (!isText) { + text = text.replace(/\n/g, ' '); + text = text.replace(/\r/g, ' '); + } + if (isAttribute) { + text = text.replace(/"/g, '"'); + } + } + return text; + } + + /* TODO: benchmark single regex */ + function _escape_attrib(text, encoding) { + return _escape(text, encoding, true); + } + + function _escape_cdata(text, encoding) { + return _escape(text, encoding, false); + } + + function _escape_text(text, encoding) { + return _escape(text, encoding, false, true); + } + + function _namespaces(elem, encoding, default_namespace) { + var qnames = {}; + var namespaces = {}; + + if (default_namespace) { + namespaces[default_namespace] = ""; + } + + function encode(text) { + return text; + } + + function add_qname(qname) { + if (qname[0] === "{") { + var tmp = qname.substring(1).split("}", 2); + var uri = tmp[0]; + var tag = tmp[1]; + var prefix = namespaces[uri]; + + if (prefix === undefined) { + prefix = _namespace_map[uri]; + if (prefix === undefined) { + prefix = "ns" + Object.keys(namespaces).length; } - - var ret = { - fieldName: fieldName, - owner: this.dataModelObject.fieldByName(fieldName).lineage.join("."), - type: comparisonType - }; - // These fields are type dependent - if (utils.contains(["boolean", "string", "ipv4", "number"], ret.type)) { - ret.rule = { - comparator: comparisonOp, - compareTo: compareTo - }; + if (prefix !== "xml") { + namespaces[uri] = prefix; } - this.filters.push(ret); + } - return this; - }, - - /** - * Add a limit on the events shown in a pivot by sorting them according to some field, then taking - * the specified number from the beginning or end of the list. - * - * @param {String} fieldName The name of field to filter on. - * @param {String} sortAttribute The name of the field to use for sorting. - * @param {String} sortDirection The direction to sort events, see class docs for valid types. - * @param {String} limit The number of values from the sorted list to allow through this filter. - * @param {String} statsFunction The stats function to use for aggregation before sorting, see class docs for valid types. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addLimitFilter: function(fieldName, sortAttribute, sortDirection, limit, statsFunction) { - if (!this.dataModelObject.hasField(fieldName)) { - throw new Error("Cannot add limit filter on a nonexistent field."); - } - - var f = this.dataModelObject.fieldByName(fieldName); - - if (!utils.contains(["string", "number", "objectCount"], f.type)) { - throw new Error("Cannot add limit filter on " + fieldName + " because it is of type " + f.type); - } - - if ("string" === f.type && !utils.contains(["count", "dc"], statsFunction)) { - throw new Error("Stats function for fields of type string must be COUNT or DISTINCT_COUNT; found " + - statsFunction); - } - - if ("number" === f.type && !utils.contains(["count", "dc", "average", "sum"], statsFunction)) { - throw new Error("Stats function for fields of type number must be one of COUNT, DISTINCT_COUNT, SUM, or AVERAGE; found " + - statsFunction); - } - - if ("objectCount" === f.type && !utils.contains(["count"], statsFunction)) { - throw new Error("Stats function for fields of type object count must be COUNT; found " + statsFunction); - } - - var filter = { - fieldName: fieldName, - owner: f.lineage.join("."), - type: f.type, - attributeName: sortAttribute, - attributeOwner: this.dataModelObject.fieldByName(sortAttribute).lineage.join("."), - sortDirection: sortDirection, - limitAmount: limit, - statsFn: statsFunction - }; - // Assumed "highest" is preferred for when sortDirection is "DEFAULT" - filter.limitType = "ASCENDING" === sortDirection ? "lowest" : "highest"; - this.filters.push(filter); - - return this; - }, - - /** - * Add a row split on a numeric or string valued field, splitting on each distinct value of the field. - * - * @param {String} fieldName The name of field to split on. - * @param {String} label A human readable name for this set of rows. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addRowSplit: function(fieldName, label) { - if (!this.dataModelObject.hasField(fieldName)) { - throw new Error("Did not find field " + fieldName); - } - var f = this.dataModelObject.fieldByName(fieldName); - if (!utils.contains(["number", "string"], f.type)) { - throw new Error("Field was of type " + f.type + ", expected number or string."); - } - - var row = { - fieldName: fieldName, - owner: f.owner, - type: f.type, - label: label - }; - - if ("number" === f.type) { - row.display = "all"; - } - - this.rows.push(row); - - return this; - }, - - /** - * Add a row split on a numeric field, splitting into numeric ranges. - * - * This split generates bins with edges equivalent to the - * classic loop 'for i in to by ' but with a maximum - * number of bins . This dispatches to the stats and xyseries search commands. - * See their documentation for more details. - * - * @param {String} fieldName The field to split on. - * @param {String} label A human readable name for this set of rows. - * @param {Object} options An optional dictionary of collection filtering and pagination options: - * - `start` (_integer_): The value of the start of the first range, or null to take the lowest value in the events. - * - `end` (_integer_): The value for the end of the last range, or null to take the highest value in the events. - * - `step` (_integer_): The the width of each range, or null to have Splunk calculate it. - * - `limit` (_integer_): The maximum number of ranges to split into, or null for no limit. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addRangeRowSplit: function(field, label, ranges) { - if (!this.dataModelObject.hasField(field)) { - throw new Error("Did not find field " + field); - } - var f = this.dataModelObject.fieldByName(field); - if ("number" !== f.type) { - throw new Error("Field was of type " + f.type + ", expected number."); - } - var updateRanges = {}; - if (!utils.isUndefined(ranges.start) && ranges.start !== null) { - updateRanges.start = ranges.start; - } - if (!utils.isUndefined(ranges.end) && ranges.end !== null) { - updateRanges.end = ranges.end; - } - if (!utils.isUndefined(ranges.step) && ranges.step !== null) { - updateRanges.size = ranges.step; - } - if (!utils.isUndefined(ranges.limit) && ranges.limit !== null) { - updateRanges.maxNumberOf = ranges.limit; - } - - this.rows.push({ - fieldName: field, - owner: f.owner, - type: f.type, - label: label, - display: "ranges", - ranges: updateRanges - }); - - return this; - }, - - /** - * Add a row split on a boolean valued field. - * - * @param {String} fieldName The name of field to split on. - * @param {String} label A human readable name for this set of rows. - * @param {String} trueDisplayValue A string to display in the true valued row label. - * @param {String} falseDisplayValue A string to display in the false valued row label. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addBooleanRowSplit: function(field, label, trueDisplayValue, falseDisplayValue) { - if (!this.dataModelObject.fieldByName(field)) { - throw new Error("Did not find field " + field); - } - var f = this.dataModelObject.fieldByName(field); - if ("boolean" !== f.type) { - throw new Error("Field was of type " + f.type + ", expected boolean."); - } - - this.rows.push({ - fieldName: field, - owner: f.owner, - type: f.type, - label: label, - trueLabel: trueDisplayValue, - falseLabel: falseDisplayValue - }); - - return this; - }, - - /** - * Add a row split on a timestamp valued field, binned by the specified bucket size. - * - * @param {String} fieldName The name of field to split on. - * @param {String} label A human readable name for this set of rows. - * @param {String} binning The size of bins to use, see class docs for valid types. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addTimestampRowSplit: function(field, label, binning) { - if (!this.dataModelObject.hasField(field)) { - throw new Error("Did not find field " + field); - } - var f = this.dataModelObject.fieldByName(field); - if ("timestamp" !== f.type) { - throw new Error("Field was of type " + f.type + ", expected timestamp."); - } - if (!utils.contains(this._binning, binning)) { - throw new Error("Invalid binning " + binning + " found. Valid values are: " + this._binning.join(", ")); - } - - this.rows.push({ - fieldName: field, - owner: f.owner, - type: f.type, - label: label, - period: binning - }); - - return this; - }, - - /** - * Add a column split on a string or number valued field, producing a column for - * each distinct value of the field. - * - * @param {String} fieldName The name of field to split on. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addColumnSplit: function(fieldName) { - if (!this.dataModelObject.hasField(fieldName)) { - throw new Error("Did not find field " + fieldName); - } - var f = this.dataModelObject.fieldByName(fieldName); - if (!utils.contains(["number", "string"], f.type)) { - throw new Error("Field was of type " + f.type + ", expected number or string."); - } - - var col = { - fieldName: fieldName, - owner: f.owner, - type: f.type - }; - - if ("number" === f.type) { - col.display = "all"; - } - - this.columns.push(col); - - return this; - }, - - /** - * Add a column split on a numeric field, splitting the values into ranges. - * - * @param {String} fieldName The field to split on. - * @param {Object} options An optional dictionary of collection filtering and pagination options: - * - `start` (_integer_): The value of the start of the first range, or null to take the lowest value in the events. - * - `end` (_integer_): The value for the end of the last range, or null to take the highest value in the events. - * - `step` (_integer_): The the width of each range, or null to have Splunk calculate it. - * - `limit` (_integer_): The maximum number of ranges to split into, or null for no limit. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addRangeColumnSplit: function(fieldName, ranges) { - if (!this.dataModelObject.hasField(fieldName)) { - throw new Error("Did not find field " + fieldName); - } - var f = this.dataModelObject.fieldByName(fieldName); - if ("number" !== f.type) { - throw new Error("Field was of type " + f.type + ", expected number."); - } - - // In Splunk 6.0.1.1, data models incorrectly expect strings for these fields - // instead of numbers. In 6.1, this is fixed and both are accepted. - var updatedRanges = {}; - if (!utils.isUndefined(ranges.start) && ranges.start !== null) { - updatedRanges.start = ranges.start; - } - if (!utils.isUndefined(ranges.end) && ranges.end !== null) { - updatedRanges.end = ranges.end; - } - if (!utils.isUndefined(ranges.step) && ranges.step !== null) { - updatedRanges.size = ranges.step; - } - if (!utils.isUndefined(ranges.limit) && ranges.limit !== null) { - updatedRanges.maxNumberOf = ranges.limit; - } - - this.columns.push({ - fieldName: fieldName, - owner: f.owner, - type: f.type, - display: "ranges", - ranges: updatedRanges - }); - - return this; - }, - - /** - * Add a column split on a boolean valued field. - * - * @param {String} fieldName The name of field to split on. - * @param {String} trueDisplayValue A string to display in the true valued column label. - * @param {String} falseDisplayValue A string to display in the false valued column label. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addBooleanColumnSplit: function(fieldName, trueDisplayValue, falseDisplayValue) { - if (!this.dataModelObject.fieldByName(fieldName)) { - throw new Error("Did not find field " + fieldName); - } - var f = this.dataModelObject.fieldByName(fieldName); - if ("boolean" !== f.type) { - throw new Error("Field was of type " + f.type + ", expected boolean."); - } - - this.columns.push({ - fieldName: fieldName, - owner: f.owner, - type: f.type, - trueLabel: trueDisplayValue, - falseLabel: falseDisplayValue - }); - - return this; - }, - - /** - * Add a column split on a timestamp valued field, binned by the specified bucket size. - * - * @param {String} fieldName The name of field to split on. - * @param {String} binning The size of bins to use, see class docs for valid types. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addTimestampColumnSplit: function(field, binning) { - if (!this.dataModelObject.hasField(field)) { - throw new Error("Did not find field " + field); - } - var f = this.dataModelObject.fieldByName(field); - if ("timestamp" !== f.type) { - throw new Error("Field was of type " + f.type + ", expected timestamp."); - } - if (!utils.contains(this._binning, binning)) { - throw new Error("Invalid binning " + binning + " found. Valid values are: " + this._binning.join(", ")); - } - - this.columns.push({ - fieldName: field, - owner: f.owner, - type: f.type, - period: binning - }); - - return this; - }, - - /** - * Add an aggregate to each cell of the pivot. - * - * @param {String} fieldName The name of field to aggregate. - * @param {String} label a human readable name for this aggregate. - * @param {String} statsFunction The function to use for aggregation, see class docs for valid stats functions. - * @return {splunkjs.Service.PivotSpecification} The updated pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - addCellValue: function(fieldName, label, statsFunction) { - if (!this.dataModelObject.hasField(fieldName)) { - throw new Error("Did not find field " + fieldName); - } - - var f = this.dataModelObject.fieldByName(fieldName); - if (utils.contains(["string", "ipv4"], f.type) && - !utils.contains([ - "list", - "values", - "first", - "last", - "count", - "dc"], statsFunction) - ) { - throw new Error("Stats function on string and IPv4 fields must be one of:" + - " list, distinct_values, first, last, count, or distinct_count; found " + - statsFunction); - } - else if ("number" === f.type && - !utils.contains([ - "sum", - "count", - "average", - "min", - "max", - "stdev", - "list", - "values" - ], statsFunction) - ) { - throw new Error("Stats function on number field must be must be one of:" + - " sum, count, average, max, min, stdev, list, or distinct_values; found " + - statsFunction - ); - } - else if ("timestamp" === f.type && - !utils.contains([ - "duration", - "earliest", - "latest", - "list", - "values" - ], statsFunction) - ) { - throw new Error("Stats function on timestamp field must be one of:" + - " duration, earliest, latest, list, or distinct values; found " + - statsFunction - ); - } - else if (utils.contains(["objectCount", "childCount"], f.type) && - "count" !== statsFunction - ) { - throw new Error("Stats function on childcount and objectcount fields must be count; " + - "found " + statsFunction); - } - else if ("boolean" === f.type) { - throw new Error("Cannot use boolean valued fields as cell values."); - } - - this.cells.push({ - fieldName: fieldName, - owner: f.lineage.join("."), - type: f.type, - label: label, - sparkline: false, // Not properly implemented in core yet. - value: statsFunction - }); - - return this; - }, - - /** - * Returns a JSON ready object representation of this pivot specification. - * - * @return {Object} The JSON ready object representation of this pivot specification. - * - * @method splunkjs.Service.PivotSpecification - */ - toJsonObject: function() { - return { - dataModel: this.dataModelObject.dataModel.name, - baseClass: this.dataModelObject.name, - rows: this.rows, - columns: this.columns, - cells: this.cells, - filters: this.filters - }; - }, - - /** - * Query Splunk for SPL queries corresponding to a pivot report - * for this data model, defined by this `PivotSpecification`. - * - * @example - * - * service.dataModels().fetch(function(err, dataModels) { - * var searches = dataModels.item("internal_audit_logs").objectByName("searches"); - * var pivotSpec = searches.createPivotSpecification(); - * // Use of the fluent API - * pivotSpec.addRowSplit("user", "Executing user") - * .addRangeColumnSplit("exec_time", {start: 0, end: 12, step: 5, limit: 4}) - * .addCellValue("search", "Search Query", "values") - * .pivot(function(pivotErr, pivot) { - * console.log("Pivot search is:", pivot.search); - * }); - * }); - * - * @param {Function} callback A function to call when done getting the pivot: `(err, pivot)`. - * - * @method splunkjs.Service.PivotSpecification - */ - pivot: function(callback) { - var svc = this.dataModelObject.dataModel.service; - - var args = { - pivot_json: JSON.stringify(this.toJsonObject()) - }; - - if (!utils.isUndefined(this.accelerationNamespace)) { - args.namespace = this.accelerationNamespace; - } - - return svc.get(Paths.pivot + "/" + encodeURIComponent(this.dataModelObject.dataModel.name), args, function(err, response) { - if (err) { - callback(new Error(err.data.messages[0].text), response); - return; + if (prefix) { + qnames[qname] = sprintf("%s:%s", prefix, tag); + } + else { + qnames[qname] = tag; + } + } + else { + if (default_namespace) { + throw new Error('cannot use non-qualified names with default_namespace option'); + } + + qnames[qname] = qname; + } + } + + + elem.iter(null, function(e) { + var i; + var tag = e.tag; + var text = e.text; + var items = e.items(); + + if (tag instanceof QName && qnames[tag.text] === undefined) { + add_qname(tag.text); + } + else if (typeof(tag) === "string") { + add_qname(tag); + } + else if (tag !== null && tag !== Comment && tag !== CData && tag !== ProcessingInstruction) { + throw new Error('Invalid tag type for serialization: '+ tag); + } + + if (text instanceof QName && qnames[text.text] === undefined) { + add_qname(text.text); + } + + items.forEach(function(item) { + var key = item[0], + value = item[1]; + if (key instanceof QName) { + key = key.text; + } + + if (qnames[key] === undefined) { + add_qname(key); + } + + if (value instanceof QName && qnames[value.text] === undefined) { + add_qname(value.text); + } + }); + }); + return [qnames, namespaces]; + } + + function _serialize_xml(write, elem, encoding, qnames, namespaces, indent, indent_string) { + var tag = elem.tag; + var text = elem.text; + var items; + var i; + + var newlines = indent || (indent === 0); + write(Array(indent + 1).join(indent_string)); + + if (tag === Comment) { + write(sprintf("", _escape_cdata(text, encoding))); + } + else if (tag === ProcessingInstruction) { + write(sprintf("", _escape_cdata(text, encoding))); + } + else if (tag === CData) { + text = text || ''; + write(sprintf("", text)); + } + else { + tag = qnames[tag]; + if (tag === undefined) { + if (text) { + write(_escape_text(text, encoding)); + } + elem.iter(function(e) { + _serialize_xml(write, e, encoding, qnames, null, newlines ? indent + 1 : false, indent_string); + }); + } + else { + write("<" + tag); + items = elem.items(); + + if (items || namespaces) { + items.sort(); // lexical order + + items.forEach(function(item) { + var k = item[0], + v = item[1]; + + if (k instanceof QName) { + k = k.text; } - - if (response.data.entry && response.data.entry[0]) { - callback(null, new root.Pivot(svc, response.data.entry[0].content)); + + if (v instanceof QName) { + v = qnames[v.text]; } else { - callback(new Error("Didn't get a Pivot report back from Splunk"), response); + v = _escape_attrib(v, encoding); } + write(sprintf(" %s=\"%s\"", qnames[k], v)); }); - }, - - /** - * Convenience method to wrap up the `PivotSpecification.pivot()` and - * `Pivot.run()` function calls. - * - * Query Splunk for SPL queries corresponding to a pivot report - * for this data model, defined by this `PivotSpecification`; then, - * starts a search job running this pivot, accelerated if possible. - * - * service.dataModels().fetch(function(fetchErr, dataModels) { - * var searches = dataModels.item("internal_audit_logs").objectByName("searches"); - * var pivotSpec = searches.createPivotSpecification(); - * // Use of the fluent API - * pivotSpec.addRowSplit("user", "Executing user") - * .addRangeColumnSplit("exec_time", {start: 0, end: 12, step: 5, limit: 4}) - * .addCellValue("search", "Search Query", "values") - * .run(function(err, job, pivot) { - * console.log("Job SID is:", job.sid); - * console.log("Pivot search is:", pivot.search); - * }); - * }); - * @param {Object} args A dictionary of properties for the search job (optional). For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. - * @param {Function} callback A function to call when done getting the pivot: `(err, job, pivot)`. - * - * @method splunkjs.Service.PivotSpecification - */ - run: function(args, callback) { - if (!callback) { - callback = args; - args = {}; - } - args = args || {}; - - this.pivot(function(err, pivot) { - if (err) { - callback(err, null, null); - } - else { - pivot.run(args, Async.augment(callback, pivot)); + + if (namespaces) { + items = utils.items(namespaces); + items.sort(function(a, b) { return a[1] < b[1]; }); + + items.forEach(function(item) { + var k = item[1], + v = item[0]; + + if (k) { + k = ':' + k; } - }); - } - }); - - /** - * Represents one of the structured views in a `DataModel`. - * - * Has these properties: - * - `dataModel` (_splunkjs.Service.DataModel_): The `DataModel` to which this `DataModelObject` belongs. - * - `name` (_string_): The name of this `DataModelObject`. - * - `displayName` (_string_): The human readable name of this `DataModelObject`. - * - `parentName` (_string_): The name of the parent `DataModelObject` to this one. - * - `lineage` (_array_): An array of strings of the lineage of the data model - * on which this field is defined. - * - `fields` (_object_): A dictionary of `DataModelField` objects, accessible by name. - * - `constraints` (_array_): An array of `DataModelConstraint` objects. - * - `calculations` (_object_): A dictionary of `DataModelCalculation` objects, accessible by ID. - * - * BaseSearch has an additional property: - * - `baseSearch` (_string_): The search query wrapped by this data model object. - * - * BaseTransaction has additional properties: - * - `groupByFields` (_string_): The fields that will be used to group events into transactions. - * - `objectsToGroup` (_array_): Names of the data model objects that should be unioned - * and split into transactions. - * - `maxSpan` (_string_): The maximum time span of a transaction. - * - `maxPause` (_string_): The maximum pause time of a transaction. - * - * @class splunkjs.Service.DataModelObject - */ - root.DataModelObject = Class.extend({ - /** - * Constructor for a data model object. - * SDK users are not expected to invoke this constructor directly. - * - * @constructor - * @param {Object} props A dictionary of properties to set: - * - `objectName` (_string_): The name for this data model object. - * - `displayName` (_string_): A human readable name for this data model object. - * - `parentName` (_string_): The name of the data model that owns this data model object. - * - `lineage` (_string_): The lineage of the data model that owns this data model object, - * items are delimited by a dot. This is converted into an array of - * strings upon construction. - * - `fields` (_array_): An array of data model fields. - * - `constraints` (_array_): An array of data model constraints. - * - `calculations` (_array_): An array of data model calculations. - * - `baseSearch` (_string_): The search query wrapped by this data model object; exclusive to BaseSearch (optional) - * - `groupByFields` (_array_): The fields that will be used to group events into transactions; exclusive to BaseTransaction (optional) - * - `objectsToGroup` (_array_): Names of the data model objects that should be unioned - * and split into transactions; exclusive to BaseTransaction (optional) - * - `maxSpan` (_string_): The maximum time span of a transaction; exclusive to BaseTransaction (optional) - * - `maxPause` (_string_): The maximum pause time of a transaction; exclusive to BaseTransaction (optional) - * - * @param {splunkjs.Service.DataModel} parentDataModel The `DataModel` that owns this data model object. - * - * @method splunkjs.Service.DataModelObject - */ - init: function(props, parentDataModel) { - props = props || {}; - props.owner = props.owner || ""; - - this.dataModel = parentDataModel; - this.name = props.objectName; - this.displayName = props.displayName; - this.parentName = props.parentName; - this.lineage = props.lineage.split("."); - - // Properties exclusive to BaseTransaction - if (props.hasOwnProperty("groupByFields")) { - this.groupByFields = props.groupByFields; - } - if (props.hasOwnProperty("objectsToGroup")) { - this.objectsToGroup = props.objectsToGroup; + + write(sprintf(" xmlns%s=\"%s\"", k, _escape_attrib(v, encoding))); + }); } - if (props.hasOwnProperty("transactionMaxTimeSpan")) { - this.maxSpan = props.transactionMaxTimeSpan; + } + + if (text || elem.len()) { + if (text && text.toString().match(/^\s*$/)) { + text = null; } - if (props.hasOwnProperty("transactionMaxPause")) { - this.maxPause = props.transactionMaxPause; + + write(">"); + if (!text && newlines) { + write("\n"); } - - // Property exclusive to BaseSearch - if (props.hasOwnProperty("baseSearch")) { - this.baseSearch = props.baseSearch; + + if (text) { + write(_escape_text(text, encoding)); } - - // Parse fields - this.fields = {}; - for (var i = 0; i < props.fields.length; i++) { - this.fields[props.fields[i].fieldName] = new root.DataModelField(props.fields[i]); + elem._children.forEach(function(e) { + _serialize_xml(write, e, encoding, qnames, null, newlines ? indent + 1 : false, indent_string); + }); + + if (!text && indent) { + write(Array(indent + 1).join(indent_string)); } - - // Parse constraints - this.constraints = []; - for (var j = 0; j < props.constraints.length; j++) { - this.constraints.push(new root.DataModelConstraint(props.constraints[j])); - } - - // Parse calculations - this.calculations = []; - for (var k = 0; k < props.calculations.length; k++) { - this.calculations[props.calculations[k].calculationID] = new root.DataModelCalculation(props.calculations[k]); - } - }, - - /** - * Is this data model object a BaseSearch? - * - * @return {Boolean} Whether this data model object is the root type, BaseSearch. - * - * @method splunkjs.Service.DataModelObject - */ - isBaseSearch: function() { - return !utils.isUndefined(this.baseSearch); - }, - - /** - * Is this data model object is a BaseTransaction? - * - * @return {Boolean} Whether this data model object is the root type, BaseTransaction. - * - * @method splunkjs.Service.DataModelObject - */ - isBaseTransaction: function() { - return !utils.isUndefined(this.maxSpan); - }, - - /** - * Returns a string array of the names of this data model object's fields. - * - * @return {Array} An array of strings with the field names of this - * data model object. - * - * @method splunkjs.Service.DataModelObject - */ - fieldNames: function() { - return Object.keys(this.fields); - }, - - /** - * Returns a data model field instance, representing a field on this - * data model object. - * - * @return {splunkjs.Service.DataModelField|null} The data model field - * from this data model object with the specified name, null if it the - * field by that name doesn't exist. - * - * @method splunkjs.Service.DataModelObject - */ - fieldByName: function(name) { - return this.calculatedFields()[name] || this.fields[name] || null; - }, - - /** - * Returns an array of data model fields from this data model object's - * calculations, and this data model object's fields. - * - * @return {Array} An array of `splunk.Service.DataModelField` objects - * which includes this data model object's fields, and the fields from - * this data model object's calculations. - * - * @method splunkjs.Service.DataModelObject - */ - allFields: function() { - // merge fields and calculatedFields() - var combinedFields = []; - - for (var f in this.fields) { - if (this.fields.hasOwnProperty(f)) { - combinedFields[f] = this.fields[f]; - } - } - - var calculatedFields = this.calculatedFields(); - for (var cf in calculatedFields) { - if (calculatedFields.hasOwnProperty(cf)) { - combinedFields[cf] = calculatedFields[cf]; - } - } - - return combinedFields; - }, - - /** - * Returns a string array of the field names of this data model object's - * calculations, and the names of this data model object's fields. - * - * @return {Array} An array of strings with the field names of this - * data model object's calculations, and the names of fields on - * this data model object. - * - * @method splunkjs.Service.DataModelObject - */ - allFieldNames: function() { - return Object.keys(this.allFields()); - }, - - /** - * Returns an array of data model fields from this data model object's - * calculations. - * - * @return {Array} An array of `splunk.Service.DataModelField` objects - * of the fields from this data model object's calculations. - * - * @method splunkjs.Service.DataModelObject - */ - calculatedFields: function(){ - var fields = {}; - // Iterate over the calculations, get their fields - var keys = this.calculationIDs(); - var calculations = this.calculations; - for (var i = 0; i < keys.length; i++) { - var calculation = calculations[keys[i]]; - for (var f = 0; f < calculation.outputFieldNames().length; f++) { - fields[calculation.outputFieldNames()[f]] = calculation.outputFields[calculation.outputFieldNames()[f]]; - } - } - return fields; - }, - - /** - * Returns a string array of the field names of this data model object's - * calculations. - * - * @return {Array} An array of strings with the field names of this - * data model object's calculations. - * - * @method splunkjs.Service.DataModelObject - */ - calculatedFieldNames: function() { - return Object.keys(this.calculatedFields()); - }, - - /** - * Returns whether this data model object contains the field with the - * name passed in the `fieldName` parameter. - * - * @param {String} fieldName The name of the field to look for. - * @return {Boolean} True if this data model contains the field by name. - * - * @method splunkjs.Service.DataModelObject - */ - hasField: function(fieldName) { - return utils.contains(this.allFieldNames(), fieldName); - }, - - /** - * Returns a string array of the IDs of this data model object's - * calculations. - * - * @return {Array} An array of strings with the IDs of this data model - * object's calculations. - * - * @method splunkjs.Service.DataModelObject - */ - calculationIDs: function() { - return Object.keys(this.calculations); - }, - - /** - * Local acceleration is tsidx acceleration of a data model object that is handled - * manually by a user. You create a job which generates an index, and then use that - * index in your pivots on the data model object. - * - * The namespace created by the job is 'sid={sid}' where {sid} is the job's sid. You - * would use it in another job by starting your search query with `| tstats ... from sid={sid} | ...` - * - * The tsidx index created by this job is deleted when the job is garbage collected by Splunk. - * - * It is the user's responsibility to manage this job, including cancelling it. - * - * @example - * - * service.dataModels().fetch(function(err, dataModels) { - * var object = dataModels.item("some_data_model").objectByName("some_object"); - * object.createLocalAccelerationJob("-1d", function(err, accelerationJob) { - * console.log("The job has name:", accelerationJob.name); - * }); - * }); - * - * @param {String} earliestTime A time modifier (e.g., "-2w") setting the earliest time to index. - * @param {Function} callback A function to call with the search job: `(err, accelerationJob)`. - * - * @method splunkjs.Service.DataModelObject - */ - createLocalAccelerationJob: function(earliestTime, callback) { - // If earliestTime parameter is not specified, then set callback to its value - if (!callback && utils.isFunction(earliestTime)) { - callback = earliestTime; - earliestTime = undefined; - } - - var query = "| datamodel \"" + this.dataModel.name + "\" " + this.name + " search | tscollect"; - var args = earliestTime ? {earliest_time: earliestTime} : {}; - - this.dataModel.service.search(query, args, callback); - }, - - /** - * Start a search job that applies querySuffix to all the events in this data model object. - * - * @example - * - * service.dataModels().fetch(function(err, dataModels) { - * var object = dataModels.item("internal_audit_logs").objectByName("searches"); - * object.startSearch({}, "| head 5", function(err, job) { - * console.log("The job has name:", job.name); - * }); - * }); - * - * @param {Object} params A dictionary of properties for the search job. For a list of available parameters, see Search job parameters on Splunk Developer Portal. - * **Note:** This method throws an error if the `exec_mode=oneshot` parameter is passed in with the properties dictionary. - * @param {String} querySuffix A search query, starting with a '|' that will be appended to the command to fetch the contents of this data model object (e.g., "| head 3"). - * @param {Function} callback A function to call with the search job: `(err, job)`. - * - * @method splunkjs.Service.DataModelObject - */ - startSearch: function(params, querySuffix, callback) { - var query = "| datamodel " + this.dataModel.name + " " + this.name + " search"; - // Prepend a space to the querySuffix, or set it to an empty string if null or undefined - querySuffix = (querySuffix) ? (" " + querySuffix) : (""); - this.dataModel.service.search(query + querySuffix, params, callback); - }, - - /** - * Returns the data model object this one inherits from if it is a user defined, - * otherwise return null. - * - * @return {splunkjs.Service.DataModelObject|null} This data model object's parent - * or null if this is not a user defined data model object. - * - * @method splunkjs.Service.DataModelObject - */ - parent: function() { - return this.dataModel.objectByName(this.parentName); - }, - - /** - * Returns a new Pivot Specification, accepts no parameters. - * - * @return {splunkjs.Service.PivotSpecification} A new pivot specification. - * - * @method splunkjs.Service.DataModelObject - */ - createPivotSpecification: function() { - // Pass in this DataModelObject to create a PivotSpecification - return new root.PivotSpecification(this); + write(""); + } + else { + write(" />"); + } + } + } + + if (newlines) { + write("\n"); + } + } + + function parse(source, parser) { + var tree = new ElementTree(); + tree.parse(source, parser); + return tree; + } + + function tostring(element, options) { + return new ElementTree(element).write(options); + } + + exports.PI = ProcessingInstruction; + exports.Comment = Comment; + exports.CData = CData; + exports.ProcessingInstruction = ProcessingInstruction; + exports.SubElement = SubElement; + exports.QName = QName; + exports.ElementTree = ElementTree; + exports.ElementPath = ElementPath; + exports.Element = function(tag, attrib) { + return new Element(tag, attrib); + }; + + exports.XML = function(data) { + var et = new ElementTree(); + return et.parse(data); + }; + + exports.parse = parse; + exports.register_namespace = register_namespace; + exports.tostring = tostring; + + }); + + require.define("/node_modules/elementtree/lib/sprintf.js", function (require, module, exports, __dirname, __filename) { + /* + * Copyright 2011 Rackspace + * + * 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. + * + */ + + var cache = {}; + + + // Do any others need escaping? + var TO_ESCAPE = { + '\'': '\\\'', + '\n': '\\n' + }; + + + function populate(formatter) { + var i, type, + key = formatter, + prev = 0, + arg = 1, + builder = 'return \''; + + for (i = 0; i < formatter.length; i++) { + if (formatter[i] === '%') { + type = formatter[i + 1]; + + switch (type) { + case 's': + builder += formatter.slice(prev, i) + '\' + arguments[' + arg + '] + \''; + prev = i + 2; + arg++; + break; + case 'j': + builder += formatter.slice(prev, i) + '\' + JSON.stringify(arguments[' + arg + ']) + \''; + prev = i + 2; + arg++; + break; + case '%': + builder += formatter.slice(prev, i + 1); + prev = i + 2; + i++; + break; + } + + + } else if (TO_ESCAPE[formatter[i]]) { + builder += formatter.slice(prev, i) + TO_ESCAPE[formatter[i]]; + prev = i + 1; } + } + + builder += formatter.slice(prev) + '\';'; + cache[key] = new Function(builder); + } + + + /** + * A fast version of sprintf(), which currently only supports the %s and %j. + * This caches a formatting function for each format string that is used, so + * you should only use this sprintf() will be called many times with a single + * format string and a limited number of format strings will ever be used (in + * general this means that format strings should be string literals). + * + * @param {String} formatter A format string. + * @param {...String} var_args Values that will be formatted by %s and %j. + * @return {String} The formatted output. + */ + exports.sprintf = function(formatter, var_args) { + if (!cache[formatter]) { + populate(formatter); + } + + return cache[formatter].apply(null, arguments); + }; + }); + require.define("/node_modules/elementtree/lib/utils.js", function (require, module, exports, __dirname, __filename) { /** - * Represents a data model on the server. Data models - * contain `DataModelObject` instances, which specify structured - * views on Splunk data. + * Copyright 2011 Rackspace + * + * 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. * - * @endpoint datamodel/model/{name} - * @class splunkjs.Service.DataModel - * @extends splunkjs.Service.Entity */ - root.DataModel = Service.Entity.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.DataModel - */ - path: function() { - return Paths.dataModels + "/" + encodeURIComponent(this.name); - }, - - /** - * Constructor for `splunkjs.Service.DataModel`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {String} name The name for the new data model. - * @param {Object} namespace (Optional) namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * @param {Object} props Properties of this data model: - * - `acceleration` (_string_): A JSON object with an `enabled` key, representing if acceleration is enabled or not. - * - `concise` (_string_): Indicates whether to list a concise JSON description of the data model, should always be "0". - * - `description` (_string_): The JSON describing the data model. - * - `displayName` (_string_): The name displayed for the data model in Splunk Web. - * - * @method splunkjs.Service.DataModel - */ - init: function(service, name, namespace, props) { - // If not given a 4th arg, assume the namespace was omitted - if (!props) { - props = namespace; - namespace = {}; - } - - this.name = name; - this._super(service, this.path(), namespace); - - this.acceleration = JSON.parse(props.content.acceleration) || {}; - if (this.acceleration.hasOwnProperty("enabled")) { - // convert the enabled property to a boolean - this.acceleration.enabled = !!this.acceleration.enabled; - } - - // concise=0 (false) forces the server to return all details of the newly created data model. - // we do not want a summary of this data model - if (!props.hasOwnProperty("concise") || utils.isUndefined(props.concise)) { - this.concise = "0"; - } - - var dataModelDefinition = JSON.parse(props.content.description); - - this.objectNames = dataModelDefinition.objectNameList; - this.displayName = dataModelDefinition.displayName; - this.description = dataModelDefinition.description; - - // Parse the objects for this data model - var objs = dataModelDefinition.objects; - this.objects = []; - for (var i = 0; i < objs.length; i++) { - this.objects.push(new root.DataModelObject(objs[i], this)); - } - - this.remove = utils.bind(this, this.remove); - this.update = utils.bind(this, this.update); - }, - - /** - * Returns a boolean indicating whether acceleration is enabled or not. - * - * @return {Boolean} true if acceleration is enabled, false otherwise. - * - * @method splunkjs.Service.DataModel - */ - isAccelerated: function() { - return !!this.acceleration.enabled; - }, - - /** - * Returns a data model object from this data model - * with the specified name if it exists, null otherwise. - * - * @return {Object|null} a data model object. - * - * @method splunkjs.Service.DataModel - */ - objectByName: function(name) { - for (var i = 0; i < this.objects.length; i++) { - if (this.objects[i].name === name) { - return this.objects[i]; - } - } - return null; - }, - - /** - * Returns a boolean of whether this exists in this data model or not. - * - * @return {Boolean} Returns true if this data model has object with specified name, false otherwise. - * - * @method splunkjs.Service.DataModel - */ - hasObject: function(name) { - return utils.contains(this.objectNames, name); - }, - - /** - * Updates the data model on the server, used to update acceleration settings. - * - * @param {Object} props A dictionary of properties to update the object with: - * - `acceleration` (_object_): The acceleration settings for the data model. - * Valid keys are: `enabled`, `earliestTime`, `cronSchedule`. - * Any keys not set will be pulled from the acceleration settings already - * set on this data model. - * @param {Function} callback A function to call when the data model is updated: `(err, dataModel)`. - * - * @method splunkjs.Service.DataModel - */ - update: function(props, callback) { - if (utils.isUndefined(callback)) { - callback = props; - props = {}; - } - callback = callback || function() {}; - - if (!props) { - callback(new Error("Must specify a props argument to update a data model.")); - return; // Exit if props isn't set, to avoid calling the callback twice. - } - if (props.hasOwnProperty("name")) { - callback(new Error("Cannot set 'name' field in 'update'"), this); - return; // Exit if the name is set, to avoid calling the callback twice. - } - - var updatedProps = { - acceleration: JSON.stringify({ - enabled: props.accceleration && props.acceleration.enabled || this.acceleration.enabled, - earliest_time: props.accceleration && props.acceleration.earliestTime || this.acceleration.earliestTime, - cron_schedule: props.accceleration && props.acceleration.cronSchedule || this.acceleration.cronSchedule - }) - }; - - var that = this; - return this.post("", updatedProps, function(err, response) { - if (err) { - callback(err, that); - } - else { - var dataModelNamespace = utils.namespaceFromProperties(response.data.entry[0]); - callback(null, new root.DataModel(that.service, response.data.entry[0].name, dataModelNamespace, response.data.entry[0])); - } - }); + + /** + * @param {Object} hash. + * @param {Array} ignored. + */ + function items(hash, ignored) { + ignored = ignored || null; + var k, rv = []; + + function is_ignored(key) { + if (!ignored || ignored.length === 0) { + return false; + } + + return ignored.indexOf(key); + } + + for (k in hash) { + if (hash.hasOwnProperty(k) && !(is_ignored(ignored))) { + rv.push([k, hash[k]]); + } + } + + return rv; + } + + + function findall(re, str) { + var match, matches = []; + + while ((match = re.exec(str))) { + matches.push(match); + } + + return matches; + } + + function merge(a, b) { + var c = {}, attrname; + + for (attrname in a) { + if (a.hasOwnProperty(attrname)) { + c[attrname] = a[attrname]; + } + } + for (attrname in b) { + if (b.hasOwnProperty(attrname)) { + c[attrname] = b[attrname]; } + } + return c; + } + + exports.items = items; + exports.findall = findall; + exports.merge = merge; + }); + require.define("/node_modules/elementtree/lib/elementpath.js", function (require, module, exports, __dirname, __filename) { /** - * Represents a collection of data models. You can create and - * list data models using this collection container, or - * get a specific data model. + * Copyright 2011 Rackspace + * + * 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. * - * @endpoint datamodel/model - * @class splunkjs.Service.DataModels - * @extends splunkjs.Service.Collection */ - root.DataModels = Service.Collection.extend({ - /** - * Retrieves the REST endpoint path for this resource (with no namespace). - * - * @method splunkjs.Service.DataModels - */ - path: function() { - return Paths.dataModels; - }, - - /** - * Constructor for `splunkjs.Service.DataModels`. - * - * @constructor - * @param {splunkjs.Service} service A `Service` instance. - * @param {Object} namespace (Optional) namespace information: - * - `owner` (_string_): The Splunk username, such as "admin". A value of "nobody" means no specific user. The "-" wildcard means all users. - * - `app` (_string_): The app context for this resource (such as "search"). The "-" wildcard means all apps. - * - `sharing` (_string_): A mode that indicates how the resource is shared. The sharing mode can be "user", "app", "global", or "system". - * - * @method splunkjs.Service.DataModels - */ - init: function(service, namespace) { - namespace = namespace || {}; - this._super(service, this.path(), namespace); - this.create = utils.bind(this, this.create); - }, - - /** - * Creates a new `DataModel` object with the given name and parameters. - * It is preferred that you create data models through the Splunk - * Enterprise with a browser. - * - * @param {String} name The name of the data model to create. If it contains spaces they will be replaced - * with underscores. - * @param {Object} params A dictionary of properties. - * @param {Function} callback A function to call with the new `DataModel` object: `(err, createdDataModel)`. - * - * @method splunkjs.Service.DataModels - */ - create: function(name, params, callback) { - // If we get (name, callback) instead of (name, params, callback) - // do the necessary variable swap - if (utils.isFunction(params) && !callback) { - callback = params; - params = {}; + + var sprintf = require('./sprintf').sprintf; + + var utils = require('./utils'); + var SyntaxError = require('./errors').SyntaxError; + + var _cache = {}; + + var RE = new RegExp( + "(" + + "'[^']*'|\"[^\"]*\"|" + + "::|" + + "//?|" + + "\\.\\.|" + + "\\(\\)|" + + "[/.*:\\[\\]\\(\\)@=])|" + + "((?:\\{[^}]+\\})?[^/\\[\\]\\(\\)@=\\s]+)|" + + "\\s+", 'g' + ); + + var xpath_tokenizer = utils.findall.bind(null, RE); + + function prepare_tag(next, token) { + var tag = token[0]; + + function select(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + elem._children.forEach(function(e) { + if (e.tag === tag) { + rv.push(e); } - - params = params || {}; - callback = callback || function(){}; - name = name.replace(/ /g, "_"); - - var that = this; - return this.post("", {name: name, description: JSON.stringify(params)}, function(err, response) { - if (err) { - callback(err); - } - else { - var dataModel = new root.DataModel(that.service, response.data.entry[0].name, that.namespace, response.data.entry[0]); - callback(null, dataModel); - } - }); - }, - - /** - * Constructor for `splunkjs.Service.DataModel`. - * - * @constructor - * @param {Object} props A dictionary of properties used to create a - * `DataModel` instance. - * @return {splunkjs.Service.DataModel} A new `DataModel` instance. - * - * @method splunkjs.Service.DataModels - */ - instantiateEntity: function(props) { - var entityNamespace = utils.namespaceFromProperties(props); - return new root.DataModel(this.service, props.name, entityNamespace, props); + }); } - }); - - /*!*/ - // Iterates over an endpoint's results. - root.PaginatedEndpointIterator = Class.extend({ - init: function(endpoint, params) { - params = params || {}; - - this._endpoint = endpoint; - this._pagesize = params.pagesize || 0; - this._offset = 0; - }, - - // Fetches the next page from the endpoint. - next: function(callback) { - callback = callback || function() {}; - - var that = this; - var params = { - count: this._pagesize, - offset: this._offset - }; - return this._endpoint(params, function(err, results) { - if (err) { - callback(err); - } - else { - var numResults = (results.rows ? results.rows.length : 0); - that._offset += numResults; - - callback(null, results, numResults > 0); - } + + return rv; + } + + return select; + } + + function prepare_star(next, token) { + function select(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + elem._children.forEach(function(e) { + rv.push(e); + }); + } + + return rv; + } + + return select; + } + + function prepare_dot(next, token) { + function select(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + rv.push(elem); + } + + return rv; + } + + return select; + } + + function prepare_iter(next, token) { + var tag; + token = next(); + + if (token[1] === '*') { + tag = '*'; + } + else if (!token[1]) { + tag = token[0] || ''; + } + else { + throw new SyntaxError(token); + } + + function select(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + elem.iter(tag, function(e) { + if (e !== elem) { + rv.push(e); + } + }); + } + + return rv; + } + + return select; + } + + function prepare_dot_dot(next, token) { + function select(context, result) { + var i, len, elem, rv = [], parent_map = context.parent_map; + + if (!parent_map) { + context.parent_map = parent_map = {}; + + context.root.iter(null, function(p) { + p._children.forEach(function(e) { + parent_map[e] = p; }); + }); } - }); -})(); - -}); - -require.define("/lib/async.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2012 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; - var utils = require('./utils'); - var root = exports || this; - - /** - * Provides utilities for asynchronous control flow and collection handling. - * - * @module splunkjs.Async - */ - - /** - * Runs an asynchronous `while` loop. - * - * @example - * - * var i = 0; - * Async.whilst( - * function() { return i++ < 3; }, - * function(done) { - * Async.sleep(0, function() { done(); }); - * }, - * function(err) { - * console.log(i) // == 3; - * } - * ); - * - * @param {Function} condition A function that returns a _boolean_ indicating whether the condition has been met. - * @param {Function} body A function that runs the body of the loop: `(done)`. - * @param {Function} callback The function to call when the loop is complete: `(err)`. - * - * @function splunkjs.Async - */ - root.whilst = function(condition, body, callback) { - condition = condition || function() { return false; }; - body = body || function(done) { done(); }; - callback = callback || function() {}; - - var iterationDone = function(err) { - if (err) { - callback(err); + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + + if (parent_map.hasOwnProperty(elem)) { + rv.push(parent_map[elem]); + } + } + + return rv; + } + + return select; + } + + + function prepare_predicate(next, token) { + var tag, key, value, select; + token = next(); + + if (token[1] === '@') { + // attribute + token = next(); + + if (token[1]) { + throw new SyntaxError(token, 'Invalid attribute predicate'); + } + + key = token[0]; + token = next(); + + if (token[1] === ']') { + select = function(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + + if (elem.get(key)) { + rv.push(elem); + } } - else { - root.whilst(condition, body, callback); + + return rv; + }; + } + else if (token[1] === '=') { + value = next()[1]; + + if (value[0] === '"' || value[value.length - 1] === '\'') { + value = value.slice(1, value.length - 1); + } + else { + throw new SyntaxError(token, 'Ivalid comparison target'); + } + + token = next(); + select = function(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + + if (elem.get(key) === value) { + rv.push(elem); + } } - }; - - if (condition()) { - body(iterationDone); + + return rv; + }; } - else { - callback(null); + + if (token[1] !== ']') { + throw new SyntaxError(token, 'Invalid attribute predicate'); + } + } + else if (!token[1]) { + tag = token[0] || ''; + token = next(); + + if (token[1] !== ']') { + throw new SyntaxError(token, 'Invalid node predicate'); } + + select = function(context, result) { + var i, len, elem, rv = []; + + for (i = 0, len = result.length; i < len; i++) { + elem = result[i]; + + if (elem.find(tag)) { + rv.push(elem); + } + } + + return rv; + }; + } + else { + throw new SyntaxError(null, 'Invalid predicate'); + } + + return select; + } + + + + var ops = { + "": prepare_tag, + "*": prepare_star, + ".": prepare_dot, + "..": prepare_dot_dot, + "//": prepare_iter, + "[": prepare_predicate, }; + function _SelectorContext(root) { + this.parent_map = null; + this.root = root; + } + + function findall(elem, path) { + var selector, result, i, len, token, value, select, context; + + if (_cache.hasOwnProperty(path)) { + selector = _cache[path]; + } + else { + // TODO: Use smarter cache purging approach + if (Object.keys(_cache).length > 100) { + _cache = {}; + } + + if (path.charAt(0) === '/') { + throw new SyntaxError(null, 'Cannot use absolute path on element'); + } + + result = xpath_tokenizer(path); + selector = []; + + function getToken() { + return result.shift(); + } + + token = getToken(); + while (true) { + var c = token[1] || ''; + value = ops[c](getToken, token); + + if (!value) { + throw new SyntaxError(null, sprintf('Invalid path: %s', path)); + } + + selector.push(value); + token = getToken(); + + if (!token) { + break; + } + else if (token[1] === '/') { + token = getToken(); + } + + if (!token) { + break; + } + } + + _cache[path] = selector; + } + + // Execute slector pattern + result = [elem]; + context = new _SelectorContext(elem); + + for (i = 0, len = selector.length; i < len; i++) { + select = selector[i]; + result = select(context, result); + } + + return result || []; + } + + function find(element, path) { + var resultElements = findall(element, path); + + if (resultElements && resultElements.length > 0) { + return resultElements[0]; + } + + return null; + } + + function findtext(element, path, defvalue) { + var resultElements = findall(element, path); + + if (resultElements && resultElements.length > 0) { + return resultElements[0].text; + } + + return defvalue; + } + + + exports.find = find; + exports.findall = findall; + exports.findtext = findtext; + + }); + + require.define("/node_modules/elementtree/lib/errors.js", function (require, module, exports, __dirname, __filename) { /** - * Runs multiple functions (tasks) in parallel. - * Each task takes the callback function as a parameter. - * When all tasks have been completed or if an error occurs, the callback - * function is called with the combined results of all tasks. + * Copyright 2011 Rackspace * - * **Note**: Tasks might not be run in the same order as they appear in the array, - * but the results will be returned in that order. + * 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 * - * @example - * - * Async.parallel([ - * function(done) { - * done(null, 1); - * }, - * function(done) { - * done(null, 2, 3); - * }], - * function(err, one, two) { - * console.log(err); // == null - * console.log(one); // == 1 - * console.log(two); // == [1,2] - * } - * ); + * http://www.apache.org/licenses/LICENSE-2.0 * - * @param {Function} tasks An array of functions: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, ...)`. + * 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. * - * @function splunkjs.Async */ - root.parallel = function(tasks, callback) { - // Allow for just a list of functions - if (arguments.length > 1 && utils.isFunction(arguments[0])) { - var args = utils.toArray(arguments); - tasks = args.slice(0, args.length - 1); - callback = args[args.length - 1]; + + var util = require('util'); + + var sprintf = require('./sprintf').sprintf; + + function SyntaxError(token, msg) { + msg = msg || sprintf('Syntax Error at token %s', token.toString()); + this.token = token; + this.message = msg; + Error.call(this, msg); + } + + util.inherits(SyntaxError, Error); + + exports.SyntaxError = SyntaxError; + + }); + + require.define("util", function (require, module, exports, __dirname, __filename) { + var events = require('events'); + + exports.print = function () {}; + exports.puts = function () {}; + exports.debug = function() {}; + + exports.inspect = function(obj, showHidden, depth, colors) { + var seen = []; + + var stylize = function(str, styleType) { + // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + var styles = + { 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'white' : [37, 39], + 'grey' : [90, 39], + 'black' : [30, 39], + 'blue' : [34, 39], + 'cyan' : [36, 39], + 'green' : [32, 39], + 'magenta' : [35, 39], + 'red' : [31, 39], + 'yellow' : [33, 39] }; + + var style = + { 'special': 'cyan', + 'number': 'blue', + 'boolean': 'yellow', + 'undefined': 'grey', + 'null': 'bold', + 'string': 'green', + 'date': 'magenta', + // "name": intentionally not styling + 'regexp': 'red' }[styleType]; + + if (style) { + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; + } else { + return str; } - - tasks = tasks || []; - callback = callback || function() {}; - - if (tasks.length === 0) { - callback(); + }; + if (! colors) { + stylize = function(str, styleType) { return str; }; + } + + function format(value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if (value && typeof value.inspect === 'function' && + // Filter out the util module, it's inspect function is special + value !== exports && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + return value.inspect(recurseTimes); } - - var tasksLeft = tasks.length; - var results = []; - var doneCallback = function(idx) { - return function(err) { - - if (err) { - if (callback) { - callback(err); - } - callback = null; - } - else { - var args = utils.toArray(arguments); - args.shift(); - - if (args.length === 1) { - args = args[0]; - } - results[idx] = args; - - if ((--tasksLeft) === 0) { - results.unshift(null); - if (callback) { - callback.apply(null, results); - } - } - } - }; - }; - - for(var i = 0; i < tasks.length; i++) { - var task = tasks[i]; - task(doneCallback(i)); + + // Primitive types cannot have properties + switch (typeof value) { + case 'undefined': + return stylize('undefined', 'undefined'); + + case 'string': + var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + '\''; + return stylize(simple, 'string'); + + case 'number': + return stylize('' + value, 'number'); + + case 'boolean': + return stylize('' + value, 'boolean'); + } + // For some reason typeof null is "object", so special case here. + if (value === null) { + return stylize('null', 'null'); } - }; - /** - * Runs multiple functions (tasks) in series. - * Each task takes the callback function as a parameter. - * When all tasks have been completed or if an error occurs, the callback - * function is called with the combined results of all tasks in the order - * they were run. - * - * @example - * - * var keeper = 0; - * Async.series([ - * function(done) { - * Async.sleep(10, function() { - * console.log(keeper++); // == 0 - * done(null, 1); - * }); - * }, - * function(done) { - * console.log(keeper++); // == 1 - * done(null, 2, 3); - * }], - * function(err, one, two) { - * console.log(err); // == null - * console.log(one); // == 1 - * console.log(two); // == [1,2] - * } - * ); - * - * @param {Function} tasks An array of functions: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, ...)`. - * - * @function splunkjs.Async - */ - root.series = function(tasks, callback) { - // Allow for just a list of functions - if (arguments.length > 1 && utils.isFunction(arguments[0])) { - var args = utils.toArray(arguments); - tasks = args.slice(0, args.length - 1); - callback = args[args.length - 1]; + // Look up the keys of the object. + var visible_keys = Object_keys(value); + var keys = showHidden ? Object_getOwnPropertyNames(value) : visible_keys; + + // Functions without properties can be shortcutted. + if (typeof value === 'function' && keys.length === 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + var name = value.name ? ': ' + value.name : ''; + return stylize('[Function' + name + ']', 'special'); + } } - - tasks = tasks || []; - callback = callback || function() {}; - - var innerSeries = function(task, restOfTasks, resultsSoFar, callback) { - if (!task) { - resultsSoFar.unshift(null); - callback.apply(null, resultsSoFar); - return; - } - - task(function(err) { - if (err) { - if (callback) { - callback(err); - } - callback = null; - } - else { - var args = utils.toArray(arguments); - args.shift(); - if (args.length === 1) { - args = args[0]; - } - resultsSoFar.push(args); - - innerSeries(restOfTasks[0], restOfTasks.slice(1), resultsSoFar, callback); - } - }); - }; - - innerSeries(tasks[0], tasks.slice(1), [], callback); - }; - /** - * Runs an asynchronous function (mapping it) over each element in an array, in parallel. - * When all tasks have been completed or if an error occurs, a callback - * function is called with the resulting array. - * - * @example - * - * Async.parallelMap( - * [1, 2, 3], - * function(val, idx, done) { - * if (val === 2) { - * Async.sleep(100, function() { done(null, val+1); }); - * } - * else { - * done(null, val + 1); - * } - * }, - * function(err, vals) { - * console.log(vals); // == [2,3,4] - * } - * ); - * - * @param {Array} vals An array of values. - * @param {Function} fn A function (possibly asynchronous) to apply to each element: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, mappedVals)`. - * - * @function splunkjs.Async - */ - root.parallelMap = function(vals, fn, callback) { - vals = vals || []; - callback = callback || function() {}; - - var tasks = []; - var createTask = function(val, idx) { - return function(done) { fn(val, idx, done); }; - }; - - for(var i = 0; i < vals.length; i++) { - tasks.push(createTask(vals[i], i)); + // Dates without properties can be shortcutted + if (isDate(value) && keys.length === 0) { + return stylize(value.toUTCString(), 'date'); } - - root.parallel(tasks, function(err) { - if (err) { - if (callback) { - callback(err); - } - callback = null; - } - else { - var args = utils.toArray(arguments); - args.shift(); - callback(null, args); - } - }); - }; - /** - * Runs an asynchronous function (mapping it) over each element in an array, in series. - * When all tasks have been completed or if an error occurs, a callback - * function is called with the resulting array. - * - * @example - * - * var keeper = 1; - * Async.seriesMap( - * [1, 2, 3], - * function(val, idx, done) { - * console.log(keeper++); // == 1, then 2, then 3 - * done(null, val + 1); - * }, - * function(err, vals) { - * console.log(vals); // == [2,3,4]; - * } - * ); - * - * @param {Array} vals An array of values. - * @param {Function} fn A function (possibly asynchronous) to apply to each element: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, mappedVals)`. - * - * @function splunkjs.Async - */ - root.seriesMap = function(vals, fn, callback) { - vals = vals || []; - callback = callback || function() {}; - - var tasks = []; - var createTask = function(val, idx) { - return function(done) { fn(val, idx, done); }; - }; - - for(var i = 0; i < vals.length; i++) { - tasks.push(createTask(vals[i], i)); + var base, type, braces; + // Determine the object type + if (isArray(value)) { + type = 'Array'; + braces = ['[', ']']; + } else { + type = 'Object'; + braces = ['{', '}']; } - - root.series(tasks, function(err) { - if (err) { - if (callback) { - callback(err); + + // Make functions say that they are functions + if (typeof value === 'function') { + var n = value.name ? ': ' + value.name : ''; + base = (isRegExp(value)) ? ' ' + value : ' [Function' + n + ']'; + } else { + base = ''; + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = ' ' + value.toUTCString(); + } + + if (keys.length === 0) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return stylize('' + value, 'regexp'); + } else { + return stylize('[Object]', 'special'); + } + } + + seen.push(value); + + var output = keys.map(function(key) { + var name, str; + if (value.__lookupGetter__) { + if (value.__lookupGetter__(key)) { + if (value.__lookupSetter__(key)) { + str = stylize('[Getter/Setter]', 'special'); + } else { + str = stylize('[Getter]', 'special'); + } + } else { + if (value.__lookupSetter__(key)) { + str = stylize('[Setter]', 'special'); + } + } + } + if (visible_keys.indexOf(key) < 0) { + name = '[' + key + ']'; + } + if (!str) { + if (seen.indexOf(value[key]) < 0) { + if (recurseTimes === null) { + str = format(value[key]); + } else { + str = format(value[key], recurseTimes - 1); + } + if (str.indexOf('\n') > -1) { + if (isArray(value)) { + str = str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n').substr(2); + } else { + str = '\n' + str.split('\n').map(function(line) { + return ' ' + line; + }).join('\n'); } + } + } else { + str = stylize('[Circular]', 'special'); } - else { - var args = utils.toArray(arguments); - args.shift(); - callback(null, args); + } + if (typeof name === 'undefined') { + if (type === 'Array' && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify('' + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = stylize(name, 'name'); + } else { + name = name.replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = stylize(name, 'string'); } - }); - }; + } - /** - * Applies an asynchronous function over each element in an array, in parallel. - * A callback function is called when all tasks have been completed. If an - * error occurs, the callback function is called with an error parameter. - * - * @example - * - * var total = 0; - * Async.parallelEach( - * [1, 2, 3], - * function(val, idx, done) { - * var go = function() { - * total += val; - * done(); - * }; - * - * if (idx === 1) { - * Async.sleep(100, go); - * } - * else { - * go(); - * } - * }, - * function(err) { - * console.log(total); // == 6 - * } - * ); - * - * @param {Array} vals An array of values. - * @param {Function} fn A function (possibly asynchronous) to apply to each element: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err)`. - * - * @function splunkjs.Async - */ - root.parallelEach = function(vals, fn, callback) { - vals = vals || []; - callback = callback || function() {}; - - root.parallelMap(vals, fn, function(err, result) { - callback(err); + return name + ': ' + str; }); + + seen.pop(); + + var numLinesEst = 0; + var length = output.reduce(function(prev, cur) { + numLinesEst++; + if (cur.indexOf('\n') >= 0) numLinesEst++; + return prev + cur.length + 1; + }, 0); + + if (length > 50) { + output = braces[0] + + (base === '' ? '' : base + '\n ') + + ' ' + + output.join(',\n ') + + ' ' + + braces[1]; + + } else { + output = braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; + } + + return output; + } + return format(obj, (typeof depth === 'undefined' ? 2 : depth)); }; - /** - * Applies an asynchronous function over each element in an array, in series. - * A callback function is called when all tasks have been completed. If an - * error occurs, the callback function is called with an error parameter. - * - * @example - * - * var results = [1, 3, 6]; - * var total = 0; - * Async.seriesEach( - * [1, 2, 3], - * function(val, idx, done) { - * total += val; - * console.log(total === results[idx]); //== true - * done(); - * }, - * function(err) { - * console.log(total); //== 6 - * } - * ); - * - * @param {Array} vals An array of values. - * @param {Function} fn A function (possibly asynchronous)to apply to each element: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err)`. - * - * @function splunkjs.Async - */ - root.seriesEach = function(vals, fn, callback) { - vals = vals || []; - callback = callback || function() {}; - - root.seriesMap(vals, fn, function(err, result) { - callback(err); - }); + + function isArray(ar) { + return ar instanceof Array || + Array.isArray(ar) || + (ar && ar !== Object.prototype && isArray(ar.__proto__)); + } + + + function isRegExp(re) { + return re instanceof RegExp || + (typeof re === 'object' && Object.prototype.toString.call(re) === '[object RegExp]'); + } + + + function isDate(d) { + if (d instanceof Date) return true; + if (typeof d !== 'object') return false; + var properties = Date.prototype && Object_getOwnPropertyNames(Date.prototype); + var proto = d.__proto__ && Object_getOwnPropertyNames(d.__proto__); + return JSON.stringify(proto) === JSON.stringify(properties); + } + + function pad(n) { + return n < 10 ? '0' + n.toString(10) : n.toString(10); + } + + var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', + 'Oct', 'Nov', 'Dec']; + + // 26 Feb 16:19:34 + function timestamp() { + var d = new Date(); + var time = [pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds())].join(':'); + return [d.getDate(), months[d.getMonth()], time].join(' '); + } + + exports.log = function (msg) {}; + + exports.pump = null; + + var Object_keys = Object.keys || function (obj) { + var res = []; + for (var key in obj) res.push(key); + return res; }; - /** - * Chains asynchronous tasks together by running a function (task) and - * passing the results as arguments to the next task. When all tasks have - * been completed or if an error occurs, a callback function is called with - * the results of the final task. - * - * Each task takes one or more parameters, depending on the previous task in the chain. - * The last parameter is always the function to run when the task is complete. - * - * `err` arguments are not passed to individual tasks, but are are propagated - * to the final callback function. - * - * @example - * - * Async.chain( - * function(callback) { - * callback(null, 1, 2); - * }, - * function(val1, val2, callback) { - * callback(null, val1 + 1); - * }, - * function(val1, callback) { - * callback(null, val1 + 1, 5); - * }, - * function(err, val1, val2) { - * console.log(val1); //== 3 - * console.log(val2); //== 5 - * } - * ); - * - * @param {Function} tasks An array of functions: `(done)`. - * @param {Function} callback The function to call when all tasks are done or if an error occurred: `(err, ...)`. - * - * @function splunkjs.Async - */ - root.chain = function(tasks, callback) { - // Allow for just a list of functions - if (arguments.length > 1 && utils.isFunction(arguments[0])) { - var args = utils.toArray(arguments); - tasks = args.slice(0, args.length - 1); - callback = args[args.length - 1]; + var Object_getOwnPropertyNames = Object.getOwnPropertyNames || function (obj) { + var res = []; + for (var key in obj) { + if (Object.hasOwnProperty.call(obj, key)) res.push(key); } - - tasks = tasks || []; - callback = callback || function() {}; - - if (!tasks.length) { - callback(); + return res; + }; + + var Object_create = Object.create || function (prototype, properties) { + // from es5-shim + var object; + if (prototype === null) { + object = { '__proto__' : null }; } else { - var innerChain = function(task, restOfTasks, result) { - var chainCallback = function(err) { - if (err) { - callback(err); - callback = function() {}; - } - else { - var args = utils.toArray(arguments); - args.shift(); - innerChain(restOfTasks[0], restOfTasks.slice(1), args); - } - }; - - var args = result; - if (!restOfTasks.length) { - args.push(callback); - } - else { - args.push(chainCallback); - } - - task.apply(null, args); - }; - - innerChain(tasks[0], tasks.slice(1), []); + if (typeof prototype !== 'object') { + throw new TypeError( + 'typeof prototype[' + (typeof prototype) + '] != \'object\'' + ); + } + var Type = function () {}; + Type.prototype = prototype; + object = new Type(); + object.__proto__ = prototype; } + if (typeof properties !== 'undefined' && Object.defineProperties) { + Object.defineProperties(object, properties); + } + return object; }; - /** - * Runs a function after a delay (a specified timeout period). - * The main purpose of this function is to make `setTimeout` adhere to - * Node.js-style function signatures. - * - * @example - * - * Async.sleep(1000, function() { console.log("TIMEOUT");}); - * - * @param {Number} timeout The timeout period, in milliseconds. - * @param {Function} callback The function to call when the timeout occurs. - * - * @function splunkjs.Async - */ - root.sleep = function(timeout, callback) { - setTimeout(function() { - callback(); - }, timeout); + exports.inherits = function(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object_create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); }; - /** - * Runs a callback function with additional parameters, which are appended to - * the parameter list. - * - * @example - * - * var callback = function(a, b) { - * console.log(a); //== 1 - * console.log(b); //== 2 - * }; - * - * var augmented = Async.augment(callback, 2); - * augmented(1); - * - * @param {Function} callback The callback function to augment. - * @param {Anything...} rest The number of arguments to add. - * - * @function splunkjs.Async - */ - root.augment = function(callback) { - var args = Array.prototype.slice.call(arguments, 1); - return function() { - var augmentedArgs = Array.prototype.slice.call(arguments); - for(var i = 0; i < args.length; i++) { - augmentedArgs.push(args[i]); - } - - callback.apply(null, augmentedArgs); - }; + }); + + require.define("events", function (require, module, exports, __dirname, __filename) { + if (!process.EventEmitter) process.EventEmitter = function () {}; + + var EventEmitter = exports.EventEmitter = process.EventEmitter; + var isArray = typeof Array.isArray === 'function' + ? Array.isArray + : function (xs) { + return Object.toString.call(xs) === '[object Array]' + } + ; + + // By default EventEmitters will print a warning if more than + // 10 listeners are added to it. This is a useful default which + // helps finding memory leaks. + // + // Obviously not all Emitters should be limited to 10. This function allows + // that to be increased. Set to zero for unlimited. + var defaultMaxListeners = 10; + EventEmitter.prototype.setMaxListeners = function(n) { + if (!this._events) this._events = {}; + this._events.maxListeners = n; }; -})(); -}); - -require.define("/lib/modularinputs/index.js", function (require, module, exports, __dirname, __filename) { - -// Copyright 2014 Splunk, Inc. -// -// 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. - -var Async = require('../async'); - -var ModularInputs = { - utils: require("./utils"), - ValidationDefinition: require('./validationdefinition'), - InputDefinition: require('./inputdefinition'), - Event: require('./event'), - EventWriter: require('./eventwriter'), - Argument: require('./argument'), - Scheme: require('./scheme'), - ModularInput: require('./modularinput'), - Logger: require('./logger') -}; - -/** - * Executes a modular input script. - * - * @param {Object} exports An instance of ModularInput representing a modular input. - * @param {Object} module The module object, used for determining if it's the main module (`require.main`). - */ -ModularInputs.execute = function(exports, module) { - if (require.main === module) { - // Slice process.argv ignoring the first argument as it is the path to the node executable. - var args = process.argv.slice(1); - - // Default empty functions for life cycle events. - exports.setup = exports.setup || ModularInputs.ModularInput.prototype.setup; - exports.start = exports.start || ModularInputs.ModularInput.prototype.start; - exports.end = exports.end || ModularInputs.ModularInput.prototype.end; - exports.teardown = exports.teardown || ModularInputs.ModularInput.prototype.teardown; - - // Setup the default values. - exports._inputDefinition = exports._inputDefinition || null; - exports._service = exports._service || null; - - // We will call close() on this EventWriter after streaming events, which is handled internally - // by ModularInput.runScript(). - var ew = new this.EventWriter(); - - // In order to ensure that everything that is written to stdout/stderr is flushed before we exit, - // set the file handles to blocking. This ensures we exit properly in a timely fashion. - // https://github.com/nodejs/node/issues/6456 - [process.stdout, process.stderr].forEach(function(s) { - s && s.isTTY && s._handle && s._handle.setBlocking && s._handle.setBlocking(true); - }); - - var scriptStatus; - Async.chain([ - function(done) { - exports.setup(done); - }, - function(done) { - ModularInputs.ModularInput.runScript(exports, args, ew, process.stdin, done); - }, - function(status, done) { - scriptStatus = status; - exports.teardown(done); - } - ], - function(err) { - if (err) { - ModularInputs.Logger.error('', err, ew._err); - } - - process.exit(scriptStatus || err ? 1 : 0); - } - ); - } -}; - -module.exports = ModularInputs; - -}); - -require.define("/lib/modularinputs/utils.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2014 Splunk, Inc. -// -// 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. - -var utils = require('../utils'); // Get all of the existing utils - -/** - * Parse the parameters from an `InputDefinition` or `ValidationDefinition`. - * - * This is a helper function for `parseXMLData`. - * - * The XML typically will look like this: - * - * `` - * `` - * `value1` - * `value2` - * `0` - * `default` - * `` - * `` - * `value11` - * `value22` - * `0` - * `default` - * `` - * `value1` - * `value2` - * `` - * `` - * `value3` - * `value4` - * `` - * `` - * `` - * - * @param {Object} an `Elementree` object representing the `` XML node. - * @return {Object} an `Elementree` object representing the parameters of node passed in. - */ -utils.parseParameters = function(paramNode) { - switch (paramNode.tag) { - case "param": - return paramNode.text; - case "param_list": - var parameters = []; - var paramChildren = paramNode.getchildren(); - for (var i = 0; i < paramChildren.length; i++) { - var mvp = paramChildren[i]; - parameters.push(mvp.text); - } - return parameters; - default: - throw new Error("Invalid configuration scheme, <" + paramNode.tag + "> tag unexpected."); - } -}; - -/** - * Parses the parameters from `Elementtree` representations of XML for - * `InputDefinition` and `ValidationDefinition` objects. - * - * @param {Object} a parent `Elementtree` element object. - * @param {String} the name of the child element to parse parameters from. - * @return {Object} an object of the parameters parsed. - */ -utils.parseXMLData = function(parentNode, childNodeTag) { - var data = {}; - var children = parentNode.getchildren(); - for (var i = 0; i < children.length; i++) { - var child = children[i]; - if (child.tag === childNodeTag) { - if (childNodeTag === "stanza") { - data[child.get("name")] = {}; - var stanzaChildren = child.getchildren(); - for (var p = 0; p < stanzaChildren.length; p++) { - var param = stanzaChildren[p]; - data[child.get("name")][param.get("name")] = utils.parseParameters(param); - } - } + + + EventEmitter.prototype.emit = function(type) { + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events || !this._events.error || + (isArray(this._events.error) && !this._events.error.length)) + { + if (arguments[1] instanceof Error) { + throw arguments[1]; // Unhandled 'error' event + } else { + throw new Error("Uncaught, unspecified 'error' event."); + } + return false; } - else if ("item" === parentNode.tag) { - data[child.get("name")] = utils.parseParameters(child); + } + + if (!this._events) return false; + var handler = this._events[type]; + if (!handler) return false; + + if (typeof handler == 'function') { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + var args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); } + return true; + + } else if (isArray(handler)) { + var args = Array.prototype.slice.call(arguments, 1); + + var listeners = handler.slice(); + for (var i = 0, l = listeners.length; i < l; i++) { + listeners[i].apply(this, args); + } + return true; + + } else { + return false; + } + }; + + // EventEmitter is defined in src/node_events.cc + // EventEmitter.prototype.emit() is also defined there. + EventEmitter.prototype.addListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new Error('addListener only takes instances of Function'); + } + + if (!this._events) this._events = {}; + + // To avoid recursion in the case that type == "newListeners"! Before + // adding it to the listeners, first emit "newListeners". + this.emit('newListener', type, listener); + + if (!this._events[type]) { + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + } else if (isArray(this._events[type])) { + + // Check for listener leak + if (!this._events[type].warned) { + var m; + if (this._events.maxListeners !== undefined) { + m = this._events.maxListeners; + } else { + m = defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + console.trace(); + } + } + + // If we've already got an array, just append. + this._events[type].push(listener); + } else { + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + } + + return this; + }; + + EventEmitter.prototype.on = EventEmitter.prototype.addListener; + + EventEmitter.prototype.once = function(type, listener) { + var self = this; + self.on(type, function g() { + self.removeListener(type, g); + listener.apply(this, arguments); + }); + + return this; + }; + + EventEmitter.prototype.removeListener = function(type, listener) { + if ('function' !== typeof listener) { + throw new Error('removeListener only takes instances of Function'); + } + + // does not use listeners(), so no side effect of creating _events[type] + if (!this._events || !this._events[type]) return this; + + var list = this._events[type]; + + if (isArray(list)) { + var i = list.indexOf(listener); + if (i < 0) return this; + list.splice(i, 1); + if (list.length == 0) + delete this._events[type]; + } else if (this._events[type] === listener) { + delete this._events[type]; + } + + return this; + }; + + EventEmitter.prototype.removeAllListeners = function(type) { + // does not use listeners(), so no side effect of creating _events[type] + if (type && this._events && this._events[type]) this._events[type] = null; + return this; + }; + + EventEmitter.prototype.listeners = function(type) { + if (!this._events) this._events = {}; + if (!this._events[type]) this._events[type] = []; + if (!isArray(this._events[type])) { + this._events[type] = [this._events[type]]; + } + return this._events[type]; + }; + + }); + + require.define("/node_modules/elementtree/lib/treebuilder.js", function (require, module, exports, __dirname, __filename) { + function TreeBuilder(element_factory) { + this._data = []; + this._elem = []; + this._last = null; + this._tail = null; + if (!element_factory) { + /* evil circular dep */ + element_factory = require('./elementtree').Element; + } + this._factory = element_factory; } - return data; -}; - -module.exports = utils; - -}); - -require.define("/lib/modularinputs/validationdefinition.js", function (require, module, exports, __dirname, __filename) { -/*!*/ -// Copyright 2014 Splunk, Inc. -// -// 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. - -(function() { - var ET = require("elementtree"); - var utils = require("./utils"); - - /** - * This class represents the XML sent by Splunk for external validation of a - * new modular input. - * - * @example - * - * var v = new ValidationDefinition(); - * - * @class splunkjs.ModularInputs.ValidationDefinition - */ - function ValidationDefinition() { - this.metadata = {}; - this.parameters = {}; - } - - /** - * Creates a `ValidationDefinition` from a provided string containing XML. + + TreeBuilder.prototype.close = function() { + return this._last; + }; + + TreeBuilder.prototype._flush = function() { + if (this._data) { + if (this._last !== null) { + var text = this._data.join(""); + if (this._tail) { + this._last.tail = text; + } + else { + this._last.text = text; + } + } + this._data = []; + } + }; + + TreeBuilder.prototype.data = function(data) { + this._data.push(data); + }; + + TreeBuilder.prototype.start = function(tag, attrs) { + this._flush(); + var elem = this._factory(tag, attrs); + this._last = elem; + + if (this._elem.length) { + this._elem[this._elem.length - 1].append(elem); + } + + this._elem.push(elem); + + this._tail = null; + }; + + TreeBuilder.prototype.end = function(tag) { + this._flush(); + this._last = this._elem.pop(); + if (this._last.tag !== tag) { + throw new Error("end tag mismatch"); + } + this._tail = 1; + return this._last; + }; + + exports.TreeBuilder = TreeBuilder; + + }); + + require.define("/node_modules/elementtree/lib/parser.js", function (require, module, exports, __dirname, __filename) { + /* + * Copyright 2011 Rackspace * - * This function will throw an exception if `str` - * contains unexpected XML. + * 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 * - * The XML typically will look like this: - * - * `` - * ` myHost` - * ` https://127.0.0.1:8089` - * ` 123102983109283019283` - * ` /opt/splunk/var/lib/splunk/modinputs` - * ` ` - * ` value1` - * ` ` - * ` value2` - * ` value3` - * ` value4` - * ` ` - * ` ` - * `` + * http://www.apache.org/licenses/LICENSE-2.0 * - * @param {String} str A string containing XML to parse. + * 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. * - * @function splunkjs.ModularInputs.ValidationDefinition */ - ValidationDefinition.parse = function(str) { - var definition = new ValidationDefinition(); - var rootChildren = ET.parse(str).getroot().getchildren(); - - for (var i = 0; i < rootChildren.length; i++) { - var node = rootChildren[i]; - if (node.tag === "item") { - definition.metadata["name"] = node.get("name"); - definition.parameters = utils.parseXMLData(node, ""); - } - else { - definition.metadata[node.tag] = node.text; - } - } - return definition; - }; - module.exports = ValidationDefinition; -})(); -}); - -require.define("/node_modules/elementtree/package.json", function (require, module, exports, __dirname, __filename) { -module.exports = {"main":"lib/elementtree.js"} -}); - -require.define("/node_modules/elementtree/lib/elementtree.js", function (require, module, exports, __dirname, __filename) { -/** - * Copyright 2011 Rackspace - * - * 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. - * - */ - -var sprintf = require('./sprintf').sprintf; - -var utils = require('./utils'); -var ElementPath = require('./elementpath'); -var TreeBuilder = require('./treebuilder').TreeBuilder; -var get_parser = require('./parser').get_parser; -var constants = require('./constants'); - -var element_ids = 0; - -function Element(tag, attrib) -{ - this._id = element_ids++; - this.tag = tag; - this.attrib = {}; - this.text = null; - this.tail = null; - this._children = []; - - if (attrib) { - this.attrib = utils.merge(this.attrib, attrib); - } -} - -Element.prototype.toString = function() -{ - return sprintf("", this.tag, this._id); -}; - -Element.prototype.makeelement = function(tag, attrib) -{ - return new Element(tag, attrib); -}; - -Element.prototype.len = function() -{ - return this._children.length; -}; - -Element.prototype.getItem = function(index) -{ - return this._children[index]; -}; - -Element.prototype.setItem = function(index, element) -{ - this._children[index] = element; -}; - -Element.prototype.delItem = function(index) -{ - this._children.splice(index, 1); -}; - -Element.prototype.getSlice = function(start, stop) -{ - return this._children.slice(start, stop); -}; - -Element.prototype.setSlice = function(start, stop, elements) -{ - var i; - var k = 0; - for (i = start; i < stop; i++, k++) { - this._children[i] = elements[k]; - } -}; - -Element.prototype.delSlice = function(start, stop) -{ - this._children.splice(start, stop - start); -}; - -Element.prototype.append = function(element) -{ - this._children.push(element); -}; - -Element.prototype.extend = function(elements) -{ - this._children.concat(elements); -}; - -Element.prototype.insert = function(index, element) -{ - this._children[index] = element; -}; - -Element.prototype.remove = function(element) -{ - this._children = this._children.filter(function(e) { - /* TODO: is this the right way to do this? */ - if (e._id === element._id) { - return false; + /* TODO: support node-expat C++ module optionally */ + + var util = require('util'); + var parsers = require('./parsers/index'); + + function get_parser(name) { + if (name === 'sax') { + return parsers.sax; + } + else { + throw new Error('Invalid parser: ' + name); + } } - return true; - }); -}; - -Element.prototype.getchildren = function() { - return this._children; -}; - -Element.prototype.find = function(path) -{ - return ElementPath.find(this, path); -}; - -Element.prototype.findtext = function(path, defvalue) -{ - return ElementPath.findtext(this, path, defvalue); -}; - -Element.prototype.findall = function(path, defvalue) -{ - return ElementPath.findall(this, path, defvalue); -}; - -Element.prototype.clear = function() -{ - this.attrib = {}; - this._children = []; - this.text = null; - this.tail = null; -}; - -Element.prototype.get = function(key, defvalue) -{ - if (this.attrib[key] !== undefined) { - return this.attrib[key]; - } - else { - return defvalue; - } -}; - -Element.prototype.set = function(key, value) -{ - this.attrib[key] = value; -}; - -Element.prototype.keys = function() -{ - return Object.keys(this.attrib); -}; - -Element.prototype.items = function() -{ - return utils.items(this.attrib); -}; - -/* - * In python this uses a generator, but in v8 we don't have em, - * so we use a callback instead. - **/ -Element.prototype.iter = function(tag, callback) -{ - var self = this; - var i, child; - - if (tag === "*") { - tag = null; - } - - if (tag === null || this.tag === tag) { - callback(self); - } - - for (i = 0; i < this._children.length; i++) { - child = this._children[i]; - child.iter(tag, function(e) { - callback(e); + + + exports.get_parser = get_parser; + }); - } -}; - -Element.prototype.itertext = function(callback) -{ - this.iter(null, function(e) { - if (e.text) { - callback(e.text); - } - - if (e.tail) { - callback(e.tail); - } - }); -}; - - -function SubElement(parent, tag, attrib) { - var element = parent.makeelement(tag, attrib); - parent.append(element); - return element; -} - -function Comment(text) { - var element = new Element(Comment); - if (text) { - element.text = text; - } - return element; -} - -function CData(text) { - var element = new Element(CData); - if (text) { - element.text = text; - } - return element; -} - -function ProcessingInstruction(target, text) -{ - var element = new Element(ProcessingInstruction); - element.text = target; - if (text) { - element.text = element.text + " " + text; - } - return element; -} - -function QName(text_or_uri, tag) -{ - if (tag) { - text_or_uri = sprintf("{%s}%s", text_or_uri, tag); - } - this.text = text_or_uri; -} - -QName.prototype.toString = function() { - return this.text; -}; - -function ElementTree(element) -{ - this._root = element; -} - -ElementTree.prototype.getroot = function() { - return this._root; -}; - -ElementTree.prototype._setroot = function(element) { - this._root = element; -}; - -ElementTree.prototype.parse = function(source, parser) { - if (!parser) { - parser = get_parser(constants.DEFAULT_PARSER); - parser = new parser.XMLParser(new TreeBuilder()); - } - - parser.feed(source); - this._root = parser.close(); - return this._root; -}; - -ElementTree.prototype.iter = function(tag, callback) { - this._root.iter(tag, callback); -}; - -ElementTree.prototype.find = function(path) { - return this._root.find(path); -}; - -ElementTree.prototype.findtext = function(path, defvalue) { - return this._root.findtext(path, defvalue); -}; - -ElementTree.prototype.findall = function(path) { - return this._root.findall(path); -}; - -/** - * Unlike ElementTree, we don't write to a file, we return you a string. - */ -ElementTree.prototype.write = function(options) { - var sb = []; - options = utils.merge({ - encoding: 'utf-8', - xml_declaration: null, - default_namespace: null, - method: 'xml'}, options); - - if (options.xml_declaration !== false) { - sb.push("\n"); - } - - if (options.method === "text") { - _serialize_text(sb, self._root, encoding); - } - else { - var qnames, namespaces, indent, indent_string; - var x = _namespaces(this._root, options.encoding, options.default_namespace); - qnames = x[0]; - namespaces = x[1]; - - if (options.hasOwnProperty('indent')) { - indent = 0; - indent_string = new Array(options.indent + 1).join(' '); - } - else { - indent = false; + + require.define("/node_modules/elementtree/lib/parsers/index.js", function (require, module, exports, __dirname, __filename) { + exports.sax = require('./sax'); + + }); + + require.define("/node_modules/elementtree/lib/parsers/sax.js", function (require, module, exports, __dirname, __filename) { + var util = require('util'); + + var sax = require('sax'); + + var TreeBuilder = require('./../treebuilder').TreeBuilder; + + function XMLParser(target) { + this.parser = sax.parser(true); + + this.target = (target) ? target : new TreeBuilder(); + + this.parser.onopentag = this._handleOpenTag.bind(this); + this.parser.ontext = this._handleText.bind(this); + this.parser.oncdata = this._handleCdata.bind(this); + this.parser.ondoctype = this._handleDoctype.bind(this); + this.parser.oncomment = this._handleComment.bind(this); + this.parser.onclosetag = this._handleCloseTag.bind(this); + this.parser.onerror = this._handleError.bind(this); } - - if (options.method === "xml") { - _serialize_xml(function(data) { - sb.push(data); - }, this._root, options.encoding, qnames, namespaces, indent, indent_string); + + XMLParser.prototype._handleOpenTag = function(tag) { + this.target.start(tag.name, tag.attributes); + }; + + XMLParser.prototype._handleText = function(text) { + this.target.data(text); + }; + + XMLParser.prototype._handleCdata = function(text) { + this.target.data(text); + }; + + XMLParser.prototype._handleDoctype = function(text) { + }; + + XMLParser.prototype._handleComment = function(comment) { + }; + + XMLParser.prototype._handleCloseTag = function(tag) { + this.target.end(tag); + }; + + XMLParser.prototype._handleError = function(err) { + throw err; + }; + + XMLParser.prototype.feed = function(chunk) { + this.parser.write(chunk); + }; + + XMLParser.prototype.close = function() { + this.parser.close(); + return this.target.close(); + }; + + exports.XMLParser = XMLParser; + + }); + + require.define("/node_modules/sax/package.json", function (require, module, exports, __dirname, __filename) { + module.exports = {"main":"lib/sax.js"} + }); + + require.define("/node_modules/sax/lib/sax.js", function (require, module, exports, __dirname, __filename) { + // wrapper for non-node envs + ;(function (sax) { + + sax.parser = function (strict, opt) { return new SAXParser(strict, opt) } + sax.SAXParser = SAXParser + sax.SAXStream = SAXStream + sax.createStream = createStream + + // When we pass the MAX_BUFFER_LENGTH position, start checking for buffer overruns. + // When we check, schedule the next check for MAX_BUFFER_LENGTH - (max(buffer lengths)), + // since that's the earliest that a buffer overrun could occur. This way, checks are + // as rare as required, but as often as necessary to ensure never crossing this bound. + // Furthermore, buffers are only tested at most once per write(), so passing a very + // large string into write() might have undesirable effects, but this is manageable by + // the caller, so it is assumed to be safe. Thus, a call to write() may, in the extreme + // edge case, result in creating at most one complete copy of the string passed in. + // Set to Infinity to have unlimited buffers. + sax.MAX_BUFFER_LENGTH = 64 * 1024 + + var buffers = [ + "comment", "sgmlDecl", "textNode", "tagName", "doctype", + "procInstName", "procInstBody", "entity", "attribName", + "attribValue", "cdata", "script" + ] + + sax.EVENTS = // for discoverability. + [ "text" + , "processinginstruction" + , "sgmldeclaration" + , "doctype" + , "comment" + , "attribute" + , "opentag" + , "closetag" + , "opencdata" + , "cdata" + , "closecdata" + , "error" + , "end" + , "ready" + , "script" + , "opennamespace" + , "closenamespace" + ] + + function SAXParser (strict, opt) { + if (!(this instanceof SAXParser)) return new SAXParser(strict, opt) + + var parser = this + clearBuffers(parser) + parser.q = parser.c = "" + parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH + parser.opt = opt || {} + parser.tagCase = parser.opt.lowercasetags ? "toLowerCase" : "toUpperCase" + parser.tags = [] + parser.closed = parser.closedRoot = parser.sawRoot = false + parser.tag = parser.error = null + parser.strict = !!strict + parser.noscript = !!(strict || parser.opt.noscript) + parser.state = S.BEGIN + parser.ENTITIES = Object.create(sax.ENTITIES) + parser.attribList = [] + + // namespaces form a prototype chain. + // it always points at the current tag, + // which protos to its parent tag. + if (parser.opt.xmlns) parser.ns = Object.create(rootNS) + + // mostly just for error reporting + parser.position = parser.line = parser.column = 0 + emit(parser, "onready") } - else { - /* TODO: html */ - throw new Error("unknown serialization method "+ options.method); + + if (!Object.create) Object.create = function (o) { + function f () { this.__proto__ = o } + f.prototype = o + return new f } - } - - return sb.join(""); -}; - -var _namespace_map = { - /* "well-known" namespace prefixes */ - "http://www.w3.org/XML/1998/namespace": "xml", - "http://www.w3.org/1999/xhtml": "html", - "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf", - "http://schemas.xmlsoap.org/wsdl/": "wsdl", - /* xml schema */ - "http://www.w3.org/2001/XMLSchema": "xs", - "http://www.w3.org/2001/XMLSchema-instance": "xsi", - /* dublic core */ - "http://purl.org/dc/elements/1.1/": "dc", -}; - -function register_namespace(prefix, uri) { - if (/ns\d+$/.test(prefix)) { - throw new Error('Prefix format reserved for internal use'); - } - - if (_namespace_map.hasOwnProperty(uri) && _namespace_map[uri] === prefix) { - delete _namespace_map[uri]; - } - - _namespace_map[uri] = prefix; -} - - -function _escape(text, encoding, isAttribute, isText) { - if (text) { - text = text.toString(); - text = text.replace(/&/g, '&'); - text = text.replace(//g, '>'); - if (!isText) { - text = text.replace(/\n/g, ' '); - text = text.replace(/\r/g, ' '); + + if (!Object.getPrototypeOf) Object.getPrototypeOf = function (o) { + return o.__proto__ } - if (isAttribute) { - text = text.replace(/"/g, '"'); + + if (!Object.keys) Object.keys = function (o) { + var a = [] + for (var i in o) if (o.hasOwnProperty(i)) a.push(i) + return a } - } - return text; -} - -/* TODO: benchmark single regex */ -function _escape_attrib(text, encoding) { - return _escape(text, encoding, true); -} - -function _escape_cdata(text, encoding) { - return _escape(text, encoding, false); -} - -function _escape_text(text, encoding) { - return _escape(text, encoding, false, true); -} - -function _namespaces(elem, encoding, default_namespace) { - var qnames = {}; - var namespaces = {}; - - if (default_namespace) { - namespaces[default_namespace] = ""; - } - - function encode(text) { - return text; - } - - function add_qname(qname) { - if (qname[0] === "{") { - var tmp = qname.substring(1).split("}", 2); - var uri = tmp[0]; - var tag = tmp[1]; - var prefix = namespaces[uri]; - - if (prefix === undefined) { - prefix = _namespace_map[uri]; - if (prefix === undefined) { - prefix = "ns" + Object.keys(namespaces).length; - } - if (prefix !== "xml") { - namespaces[uri] = prefix; + + function checkBufferLength (parser) { + var maxAllowed = Math.max(sax.MAX_BUFFER_LENGTH, 10) + , maxActual = 0 + for (var i = 0, l = buffers.length; i < l; i ++) { + var len = parser[buffers[i]].length + if (len > maxAllowed) { + // Text/cdata nodes can get big, and since they're buffered, + // we can get here under normal conditions. + // Avoid issues by emitting the text node now, + // so at least it won't get any bigger. + switch (buffers[i]) { + case "textNode": + closeText(parser) + break + + case "cdata": + emitNode(parser, "oncdata", parser.cdata) + parser.cdata = "" + break + + case "script": + emitNode(parser, "onscript", parser.script) + parser.script = "" + break + + default: + error(parser, "Max buffer length exceeded: "+buffers[i]) + } } + maxActual = Math.max(maxActual, len) } - - if (prefix) { - qnames[qname] = sprintf("%s:%s", prefix, tag); - } - else { - qnames[qname] = tag; + // schedule the next check for the earliest possible buffer overrun. + parser.bufferCheckPosition = (sax.MAX_BUFFER_LENGTH - maxActual) + + parser.position + } + + function clearBuffers (parser) { + for (var i = 0, l = buffers.length; i < l; i ++) { + parser[buffers[i]] = "" } } - else { - if (default_namespace) { - throw new Error('cannot use non-qualified names with default_namespace option'); + + SAXParser.prototype = + { end: function () { end(this) } + , write: write + , resume: function () { this.error = null; return this } + , close: function () { return this.write(null) } + , end: function () { return this.write(null) } } - - qnames[qname] = qname; + + try { + var Stream = require("stream").Stream + } catch (ex) { + var Stream = function () {} } - } - - - elem.iter(null, function(e) { - var i; - var tag = e.tag; - var text = e.text; - var items = e.items(); - - if (tag instanceof QName && qnames[tag.text] === undefined) { - add_qname(tag.text); + + + var streamWraps = sax.EVENTS.filter(function (ev) { + return ev !== "error" && ev !== "end" + }) + + function createStream (strict, opt) { + return new SAXStream(strict, opt) } - else if (typeof(tag) === "string") { - add_qname(tag); + + function SAXStream (strict, opt) { + if (!(this instanceof SAXStream)) return new SAXStream(strict, opt) + + Stream.apply(me) + + this._parser = new SAXParser(strict, opt) + this.writable = true + this.readable = true + + + var me = this + + this._parser.onend = function () { + me.emit("end") + } + + this._parser.onerror = function (er) { + me.emit("error", er) + + // if didn't throw, then means error was handled. + // go ahead and clear error, so we can write again. + me._parser.error = null + } + + streamWraps.forEach(function (ev) { + Object.defineProperty(me, "on" + ev, { + get: function () { return me._parser["on" + ev] }, + set: function (h) { + if (!h) { + me.removeAllListeners(ev) + return me._parser["on"+ev] = h + } + me.on(ev, h) + }, + enumerable: true, + configurable: false + }) + }) } - else if (tag !== null && tag !== Comment && tag !== CData && tag !== ProcessingInstruction) { - throw new Error('Invalid tag type for serialization: '+ tag); + + SAXStream.prototype = Object.create(Stream.prototype, + { constructor: { value: SAXStream } }) + + SAXStream.prototype.write = function (data) { + this._parser.write(data.toString()) + this.emit("data", data) + return true } - - if (text instanceof QName && qnames[text.text] === undefined) { - add_qname(text.text); + + SAXStream.prototype.end = function (chunk) { + if (chunk && chunk.length) this._parser.write(chunk.toString()) + this._parser.end() + return true } - - items.forEach(function(item) { - var key = item[0], - value = item[1]; - if (key instanceof QName) { - key = key.text; + + SAXStream.prototype.on = function (ev, handler) { + var me = this + if (!me._parser["on"+ev] && streamWraps.indexOf(ev) !== -1) { + me._parser["on"+ev] = function () { + var args = arguments.length === 1 ? [arguments[0]] + : Array.apply(null, arguments) + args.splice(0, 0, ev) + me.emit.apply(me, args) + } } - - if (qnames[key] === undefined) { - add_qname(key); - } - - if (value instanceof QName && qnames[value.text] === undefined) { - add_qname(value.text); - } - }); - }); - return [qnames, namespaces]; -} - -function _serialize_xml(write, elem, encoding, qnames, namespaces, indent, indent_string) { - var tag = elem.tag; - var text = elem.text; - var items; - var i; - - var newlines = indent || (indent === 0); - write(Array(indent + 1).join(indent_string)); - - if (tag === Comment) { - write(sprintf("", _escape_cdata(text, encoding))); - } - else if (tag === ProcessingInstruction) { - write(sprintf("", _escape_cdata(text, encoding))); - } - else if (tag === CData) { - text = text || ''; - write(sprintf("", text)); - } - else { - tag = qnames[tag]; - if (tag === undefined) { - if (text) { - write(_escape_text(text, encoding)); - } - elem.iter(function(e) { - _serialize_xml(write, e, encoding, qnames, null, newlines ? indent + 1 : false, indent_string); - }); + + return Stream.prototype.on.call(me, ev, handler) } - else { - write("<" + tag); - items = elem.items(); - - if (items || namespaces) { - items.sort(); // lexical order - - items.forEach(function(item) { - var k = item[0], - v = item[1]; - - if (k instanceof QName) { - k = k.text; - } - - if (v instanceof QName) { - v = qnames[v.text]; - } - else { - v = _escape_attrib(v, encoding); - } - write(sprintf(" %s=\"%s\"", qnames[k], v)); - }); - - if (namespaces) { - items = utils.items(namespaces); - items.sort(function(a, b) { return a[1] < b[1]; }); - - items.forEach(function(item) { - var k = item[1], - v = item[0]; - - if (k) { - k = ':' + k; - } - - write(sprintf(" xmlns%s=\"%s\"", k, _escape_attrib(v, encoding))); - }); - } - } - - if (text || elem.len()) { - if (text && text.toString().match(/^\s*$/)) { - text = null; - } - - write(">"); - if (!text && newlines) { - write("\n"); - } - - if (text) { - write(_escape_text(text, encoding)); - } - elem._children.forEach(function(e) { - _serialize_xml(write, e, encoding, qnames, null, newlines ? indent + 1 : false, indent_string); - }); - - if (!text && indent) { - write(Array(indent + 1).join(indent_string)); - } - write(""); - } - else { - write(" />"); - } + + + + // character classes and tokens + var whitespace = "\r\n\t " + // this really needs to be replaced with character classes. + // XML allows all manner of ridiculous numbers and digits. + , number = "0124356789" + , letter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + // (Letter | "_" | ":") + , nameStart = letter+"_:" + , nameBody = nameStart+number+"-." + , quote = "'\"" + , entity = number+letter+"#" + , attribEnd = whitespace + ">" + , CDATA = "[CDATA[" + , DOCTYPE = "DOCTYPE" + , XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" + , XMLNS_NAMESPACE = "http://www.w3.org/2000/xmlns/" + , rootNS = { xml: XML_NAMESPACE, xmlns: XMLNS_NAMESPACE } + + // turn all the string character sets into character class objects. + whitespace = charClass(whitespace) + number = charClass(number) + letter = charClass(letter) + nameStart = charClass(nameStart) + nameBody = charClass(nameBody) + quote = charClass(quote) + entity = charClass(entity) + attribEnd = charClass(attribEnd) + + function charClass (str) { + return str.split("").reduce(function (s, c) { + s[c] = true + return s + }, {}) } - } - - if (newlines) { - write("\n"); - } -} - -function parse(source, parser) { - var tree = new ElementTree(); - tree.parse(source, parser); - return tree; -} - -function tostring(element, options) { - return new ElementTree(element).write(options); -} - -exports.PI = ProcessingInstruction; -exports.Comment = Comment; -exports.CData = CData; -exports.ProcessingInstruction = ProcessingInstruction; -exports.SubElement = SubElement; -exports.QName = QName; -exports.ElementTree = ElementTree; -exports.ElementPath = ElementPath; -exports.Element = function(tag, attrib) { - return new Element(tag, attrib); -}; - -exports.XML = function(data) { - var et = new ElementTree(); - return et.parse(data); -}; - -exports.parse = parse; -exports.register_namespace = register_namespace; -exports.tostring = tostring; - -}); - -require.define("/node_modules/elementtree/lib/sprintf.js", function (require, module, exports, __dirname, __filename) { -/* - * Copyright 2011 Rackspace - * - * 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. - * - */ - -var cache = {}; - - -// Do any others need escaping? -var TO_ESCAPE = { - '\'': '\\\'', - '\n': '\\n' -}; - - -function populate(formatter) { - var i, type, - key = formatter, - prev = 0, - arg = 1, - builder = 'return \''; - - for (i = 0; i < formatter.length; i++) { - if (formatter[i] === '%') { - type = formatter[i + 1]; - - switch (type) { - case 's': - builder += formatter.slice(prev, i) + '\' + arguments[' + arg + '] + \''; - prev = i + 2; - arg++; - break; - case 'j': - builder += formatter.slice(prev, i) + '\' + JSON.stringify(arguments[' + arg + ']) + \''; - prev = i + 2; - arg++; - break; - case '%': - builder += formatter.slice(prev, i + 1); - prev = i + 2; - i++; - break; - } - - - } else if (TO_ESCAPE[formatter[i]]) { - builder += formatter.slice(prev, i) + TO_ESCAPE[formatter[i]]; - prev = i + 1; + + function is (charclass, c) { + return charclass[c] } - } - - builder += formatter.slice(prev) + '\';'; - cache[key] = new Function(builder); -} - - -/** - * A fast version of sprintf(), which currently only supports the %s and %j. - * This caches a formatting function for each format string that is used, so - * you should only use this sprintf() will be called many times with a single - * format string and a limited number of format strings will ever be used (in - * general this means that format strings should be string literals). - * - * @param {String} formatter A format string. - * @param {...String} var_args Values that will be formatted by %s and %j. - * @return {String} The formatted output. - */ -exports.sprintf = function(formatter, var_args) { - if (!cache[formatter]) { - populate(formatter); - } - - return cache[formatter].apply(null, arguments); -}; - -}); - -require.define("/node_modules/elementtree/lib/utils.js", function (require, module, exports, __dirname, __filename) { -/** - * Copyright 2011 Rackspace - * - * 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. - * - */ - -/** - * @param {Object} hash. - * @param {Array} ignored. - */ -function items(hash, ignored) { - ignored = ignored || null; - var k, rv = []; - - function is_ignored(key) { - if (!ignored || ignored.length === 0) { - return false; + + function not (charclass, c) { + return !charclass[c] } - - return ignored.indexOf(key); - } - - for (k in hash) { - if (hash.hasOwnProperty(k) && !(is_ignored(ignored))) { - rv.push([k, hash[k]]); + + var S = 0 + sax.STATE = + { BEGIN : S++ + , TEXT : S++ // general stuff + , TEXT_ENTITY : S++ // & and such. + , OPEN_WAKA : S++ // < + , SGML_DECL : S++ // + , SCRIPT : S++ // @@ -296,7 +296,7 @@
  • - +
  • diff --git a/examples/browser/index.html b/examples/browser/index.html index add46b802..1e956c6ab 100644 --- a/examples/browser/index.html +++ b/examples/browser/index.html @@ -14,7 +14,7 @@ - + - - + + + + + + + \ No newline at end of file diff --git a/tests/tests.js b/tests/tests.js index c4cdfad45..ea3865def 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -1,91 +1,86 @@ -// Copyright 2011 Splunk, Inc. -// -// 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. - -(function() { - var test = require('../contrib/nodeunit/test_reporter'); - var options = require('../examples/node/cmdline'); - var splunkjs = require('../index'); - var utils = require('../lib/utils'); - var NodeHttp = splunkjs.NodeHttp; - - var parser = new options.create(); - - // If we found the --quiet flag, remove it - var quiet = utils.contains(process.argv, "--quiet"); - if (quiet) { - var quietIndex = utils.keyOf("--quiet", process.argv); - process.argv.splice(quietIndex, 1); - } - - // Do the normal parsing - var cmdline = parser.parse(process.argv); - - var nonSplunkHttp = new NodeHttp(false); - var svc = new splunkjs.Service({ - scheme: cmdline.opts.scheme, - host: cmdline.opts.host, - port: cmdline.opts.port, - username: cmdline.opts.username, - password: cmdline.opts.password, - version: cmdline.opts.version - }); - - var loggedOutSvc = new splunkjs.Service({ - scheme: cmdline.opts.scheme, - host: cmdline.opts.host, - port: cmdline.opts.port, - username: cmdline.opts.username, - password: cmdline.opts.password + 'wrong', - version: cmdline.opts.version - }); - - - exports.Tests = {}; - - // Modular input tests - exports.Tests.ModularInputs = require('./modularinputs'); - - // Building block tests - exports.Tests.Utils = require('./test_utils').setup(); - exports.Tests.Async = require('./test_async').setup(); - exports.Tests.Http = require('./test_http').setup(nonSplunkHttp); - exports.Tests.Log = require('./test_log').setup(); - - // Splunk-specific tests - exports.Tests.Context = require('./test_context').setup(svc); - exports.Tests.Service = require('./test_service').setup(svc, loggedOutSvc); - exports.Tests.Examples = require('./test_examples').setup(svc, cmdline.opts); - - // If the --quiet flag is passed, don't show splunkd output - if (quiet) { - splunkjs.Logger.setLevel("NONE"); - } - else { - splunkjs.Logger.setLevel("ALL"); - } - - // If $SPLUNK_HOME isn't set, abort the tests - if (!Object.prototype.hasOwnProperty.call(process.env, "SPLUNK_HOME")) { - console.log("$SPLUNK_HOME is not set, aborting tests."); - return; - } - - svc.login(function(err, success) { - if (err) { - console.log(err); - return; - } - test.run([exports]); - }); -})(); +// Copyright 2011 Splunk, Inc. +// +// 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. + +var describe = require('mocha').describe; +var options = require('../examples/node/cmdline'); +var splunkjs = require('../index'); +var utils = require('../lib/utils'); +var NodeHttp = splunkjs.NodeHttp; + +var parser = new options.create(); + +// If we found the --quiet flag, remove it +var quiet = utils.contains(process.argv, "--quiet"); +if (quiet) { + splunkjs.Logger.setLevel("NONE"); + var quietIndex = utils.keyOf("--quiet", process.argv); + process.argv.splice(quietIndex, 1); +} +else { + splunkjs.Logger.setLevel("ALL"); +} + +// If $SPLUNK_HOME isn't set, abort the tests +if (!Object.prototype.hasOwnProperty.call(process.env, "SPLUNK_HOME")) { + console.error("$SPLUNK_HOME is not set, aborting tests."); + return; +} + +// Do the normal parsing +var cmdline = parser.parse(process.argv); + +var nonSplunkHttp = new NodeHttp(false); + +var svc = new splunkjs.Service({ + scheme: cmdline.opts.scheme, + host: cmdline.opts.host, + port: cmdline.opts.port, + username: cmdline.opts.username, + password: cmdline.opts.password, + version: cmdline.opts.version +}); + +var loggedOutSvc = new splunkjs.Service({ + scheme: cmdline.opts.scheme, + host: cmdline.opts.host, + port: cmdline.opts.port, + username: cmdline.opts.username, + password: cmdline.opts.password + 'wrong', + version: cmdline.opts.version +}); + +describe("Server tests", function () { + + this.beforeAll(function (done) { + svc.login(function (err, success) { + if (err || !success) { + throw new Error("Login failed - not running tests", err || ""); + } + }); + done(); + }) + + require('./modularinputs'); + require('./test_async').setup(); + require('./test_context').setup(svc); + require('./test_examples').setup(svc, cmdline.opts); + require('./test_http').setup(nonSplunkHttp); + require('./test_log').setup(); + require('./test_service').setup(svc, loggedOutSvc); + require('./test_utils').setup(); +}) + + + + diff --git a/tests/utils.js b/tests/utils.js index 917e0e777..3d9018f90 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -1,52 +1,53 @@ -// Copyright 2011 Splunk, Inc. -// -// 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. - -(function() { - "use strict"; - var Async = require('../lib/async'); - - var root = exports || this; - - root.pollUntil = function(obj, condition, iterations, callback) { - callback = callback || function() {}; - - var i = 0; - Async.whilst( - function() { return !condition(obj) && (i++ < iterations); }, - function(done) { - Async.sleep(500, function() { - obj.fetch(done); - }); - }, - function(err) { - callback(err, obj); - } - ); - }; - - // Minimal Http implementation that is designed to pass the tests - // done by Context.init(), but nothing more. - root.DummyHttp = { - // Required by Context.init() - _setSplunkVersion: function(version) { - // nothing - } - }; - - var idCounter = 0; - root.getNextId = function() { - return "id" + (idCounter++) + "_" + ((new Date()).valueOf()); - }; - -})(); \ No newline at end of file +// Copyright 2011 Splunk, Inc. +// +// 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. + +(function () { + "use strict"; + var Async = require('../lib/async'); + var assert = require('chai').assert; + + var root = exports || this; + + root.pollUntil = function (obj, condition, iterations, callback) { + callback = callback || function () { }; + + var i = 0; + Async.whilst( + function () { return !condition(obj) && (i++ < iterations); }, + function (done) { + Async.sleep(500, function () { + obj.fetch(done); + }); + }, + function (err) { + callback(err, obj); + } + ); + }; + + // Minimal Http implementation that is designed to pass the tests + // done by Context.init(), but nothing more. + root.DummyHttp = { + // Required by Context.init() + _setSplunkVersion: function (version) { + // nothing + } + }; + + var idCounter = 0; + root.getNextId = function () { + return "id" + (idCounter++) + "_" + ((new Date()).valueOf()); + }; + +})();