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..804efe0ff --- /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.1.0/sdkappcollection.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/.jshintignore b/.jshintignore index 8f70fdda3..d73f60eb1 100644 --- a/.jshintignore +++ b/.jshintignore @@ -5,8 +5,10 @@ node_modules build client external +examples/browser/create-react-app examples/browser/minisplunk examples/browser/resources +examples\\browser\\create-react-app examples\\browser\\minisplunk examples\\browser\\resources old_english diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a87226721..000000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -sudo: required - -services: - - docker - -notifications: - email: false - -before_install: - # Create .splunkrc file with default credentials - - echo host=localhost >> $HOME/.splunkrc - - echo username=admin >> $HOME/.splunkrc - - echo password=changed! >> $HOME/.splunkrc - # Set SPLUNK_HOME - - export SPLUNK_HOME="/opt/splunk" - # Add DOCKER to iptables, 1/10 times this is needed, force 0 exit status - - sudo iptables -N DOCKER || true - # Start docker-compose in detached mode - - docker-compose up -d - # Health Check (3 minutes) - - for i in `seq 0 180`; do if docker exec -it splunk /sbin/checkstate.sh &> /dev/null; then break; fi; echo $i; sleep 1; done - -language: node_js -node_js: - - "10.0" - - "4.2" - - "0.12" - -env: - - SPLUNK_VERSION=7.3 - - SPLUNK_VERSION=8.0 - -before_script: - node sdkdo hint - -# Test script, should return non 0 exit status if a test fails -script: - node sdkdo tests diff --git a/CHANGELOG.md b/CHANGELOG.md index afba321f0..c07a3d9dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,34 @@ -# Splunk SDK for JavaScript Changelog +# Splunk Enterprise SDK for JavaScript Changelog + +## v1.10.0 + +### Major changes +* Dropped support for deprecated request http client. +* Added support for [needle](https://www.npmjs.com/package/needle) http client library. + +### Minor changes + +* Added support for Splunk Enterprise 8.2 +* Added support for Node v14. +* Dropped support for deprecated nodeunit testing library. +* Added support for [mocha](https://www.npmjs.com/package/mocha) for testing and [chai](https://www.npmjs.com/package/chai) for assertions. +* Updated `cookie` dependency version to `0.4.1` +* Updated `elementtree` dependency version to `0.1.7` +* Updated `browserify` dependency version to `17.0.0` +* Updated `jshint` dependency version to `2.13.0` +* Updated `mustache` dependency version to `4.2.0` +* Updated `readable-stream` dependency version to `3.6.0` +* Updated `uglify-js` dependency version to `3.13.8` +* Added support for `dotenv@10.0.0` to declared env variables. +* Added support for `mochawesome@6.2.2` to generate test report. +* Fixed the Buffer and new Buffer() deprecation warnings appeared when running the tests. +* Added support for running single test file and test case. Run `make test_specific` for more info. + +## v1.9.1 + +### Minor changes + +* Added third party credits file (CREDITS.md) and updated support and contributing documentation. ## v1.9.0 @@ -173,7 +203,7 @@ across all versions of Splunk. ### New features and APIs -* The Splunk SDK for JavaScript now supports Node.js v0.8.x and v0.10.x +* The Splunk Enterprise SDK for JavaScript now supports Node.js v0.8.x and v0.10.x * Add back general JQuery HTTP implementation. @@ -181,7 +211,7 @@ across all versions of Splunk. ### Breaking changes -* The Splunk SDK for JavaScript no longer supports Node.js v0.6.x +* The Splunk Enterprise SDK for JavaScript no longer supports Node.js v0.6.x * Updated the Node.js request module dependency to v2.21.1 @@ -250,11 +280,11 @@ across all versions of Splunk. `splunkjs.JobManager.{events|results|preview}Iterator` methods. * A new "hello-world"-style code example, `log.js`, has been added to show how - to do simple application logging using the Splunk SDK for JavaScript. + to do simple application logging using the Splunk Enterprise SDK for JavaScript. ### Breaking changes -* The easyXDM library is no longer included with the Splunk SDK for JavaScript +* The easyXDM library is no longer included with the Splunk Enterprise SDK for JavaScript because this library was not being used, and could not work with a Splunk instance that had a self-signed SSL certificate. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e6c0ed3f..1821009da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,23 +2,19 @@ ## How to contribute -If you would like to contribute to this project, go here for more information: +If you would like to contribute to this project, see [Contributions to Splunk](https://www.splunk.com/en_us/form/contributions.html) for more information. -* [Splunk and open source][contributions] -* [Individual contributions][indivcontrib] -* [Company contributions][companycontrib] +## Issues and bug reports -## Issues & Bug Reports +If you're seeing some unexpected behavior with this project, please create an [issue](https://github.com/splunk/splunk-sdk-javascript/issues) on GitHub with the following information: -If you're seeing some unexpected behavior with this project, please create an [issue on GitHub][issues] with the following information: +1. Version of this project you're using (ex: 1.7.1) +2. Platform version (ex: Windows Server 2012) +3. Framework version (ex: Node.js v.0.12) or Browser (ex: Chrome 43.0.2357.81) +4. Splunk Enterprise version (ex: 7.0) +5. Other relevant information (ex: local/remote environment, Splunk network configuration, standalone or distributed deployment, are load balancers used) -0. Version of this project you're using (ex: 1.7.1) -0. Platform version (ex: Windows Server 2012) -0. Framework version (ex: Node.js 0.10.37) or Browser (ex: Chrome 43.0.2357.81) -0. Splunk version (ex: 6.2.2) -0. Other relevant information (ex: local/remote environment, Splunk network configuration) - -Alternatively, if you have a Splunk question please ask on [Splunk Answers][answers] +Alternatively, if you have a Splunk question please ask on [Splunk Answers](https://community.splunk.com/t5/Splunk-Development/ct-p/developer-tools). ## Pull requests @@ -26,21 +22,14 @@ We love to see pull requests! To create a pull request: -0. Fill out the [Individual Contributor Agreement][indivcontrib]. -0. Fork [the repository][repo]. -0. Make changes to the **`develop`** branch, preferably with tests. -0. Create a [pull request][pulls] against the **`develop`** branch. +1. Fill out the [Individual Contributor Agreement](https://www.splunk.com/en_us/form/contributions.html). +2. Fork the [repository](https://github.com/splunk/splunk-sdk-javascript). +3. Make changes to the **develop** branch, preferably with tests. +4. Create a [pull request](https://github.com/splunk/splunk-sdk-javascript/pulls) against the **develop** branch. ## Contact us -You can reach Splunk support at _support@splunk.com_ if you have Splunk related questions. +If you have a paid Splunk Enterprise or Splunk Cloud license, you can contact [Support](https://www.splunk.com/en_us/support-and-services.html) with questions. -You can reach the Developer Platform team at _devinfo@splunk.com_. +You can reach the Splunk Developer Platform team at _devinfo@splunk.com_. -[contributions]: http://dev.splunk.com/view/opensource/SP-CAAAEDM -[indivcontrib]: http://dev.splunk.com/goto/individualcontributions -[companycontrib]: http://dev.splunk.com/view/companycontributions/SP-CAAAEDR -[answers]: http://answers.splunk.com/ -[repo]: https://github.com/splunk/splunk-sdk-javascript -[issues]: https://github.com/splunk/splunk-sdk-javascript/issues -[pulls]: https://github.com/splunk/splunk-sdk-javascript/pulls \ No newline at end of file diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 000000000..f847d8706 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,20 @@ +# Third-party software credits + +Some of the components included in the Splunk Enterprise SDK for JavaScript are licensed under free or open source licenses. We wish to thank the contributors to those projects. + +| Contributor | Description | License | +|:----------- |:----------- |:------- | +| [dox](https://github.com/visionmedia/dox) | RESTful degradable JavaScript routing | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-DOX) | +| [davis.js](https://github.com/olivernn/davis.js) | Documentation generator | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-DAVIS) | +| [jquery.class.js](http://ejohn.org/blog/simple-javascript-inheritance/) | Custom class creator | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-JQUERYCLASS) | +| [nodeunit](https://github.com/caolan/nodeunit/) | Unit testing in node.js and the browser | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-NODEUNIT) | +| [showdown.js](https://github.com/coreyti/showdown/) | Markdown to HTML converter | [BSD](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-SHOWDOWN) | +| [staticresource](https://github.com/atsuya/static-resource/) | Static resource handling | [MIT](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-STATICRESOURCE) | +| [webapp2](http://code.google.com/p/webapp-improved/) | A framework for Google App Engine | [Apache](https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-WEBAPP2) | +| [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 104100f15..646d863b1 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,38 @@ [![Build Status](https://travis-ci.org/splunk/splunk-sdk-javascript.svg?branch=master)](https://travis-ci.org/splunk/splunk-sdk-javascript) -# The Splunk Software Development Kit for JavaScript +# The Splunk Enterprise Software Development Kit for JavaScript -#### Version 1.9.0 +#### Version 1.10.0 -The Splunk Software Development Kit (SDK) for JavaScript contains library code and -examples designed to enable developers to build applications using Splunk and -JavaScript. This SDK supports both server- and client-side JavaScript. +The Splunk Enterprise Software Development Kit (SDK) for JavaScript contains library code and examples designed to enable developers to build applications using the Splunk platform and JavaScript. This SDK supports server-side and client-side JavaScript. + +For more information, see [Splunk Enterprise SDK for JavaScript](https://dev.splunk.com/enterprise/docs/devtools/javascript/sdk-javascript/) on the Splunk Developer Portal. ## Requirements -* Node.js v.0.12, or v4 or later. The Splunk SDK for Javascript is tested with Node.js v.0.12, v4.2, and v10.0. -* Splunk Enterprise 6.3.0 or later, or Splunk Cloud. The Splunk SDK for Javascript is tested with Splunk Enterprise 7.0 and 7.2. +* Node.js v 8.17.0, or v14 or later -## Installation + The Splunk Enterprise SDK for JavaScript was tested with Node.js v8.17.0, v14. + +* Splunk Enterprise 8.0 or 8.2, or Splunk Cloud + + The Splunk Enterprise SDK for JavaScript was tested with Splunk Enterprise 8.0 or 8.2, or Splunk Cloud. -This section describes the basic steps for installing the Splunk SDK for JavaScript. -For more detailed instructions and requirements, see the -[Splunk Developer Portal][install]. +* Splunk Enterprise SDK for JavaScript -### Get the Splunk SDK for JavaScript + Download the SDK as a [ZIP file](https://github.com/splunk/splunk-sdk-javascript/zipball/master) or clone the repository: -You can get the SDK by [downloading it][zip] from GitHub, or by cloning it: + git clone https://github.com/splunk/splunk-sdk-javascript.git + + +## Installation - git clone https://github.com/splunk/splunk-sdk-javascript.git +This section describes the basic steps for installing the Splunk Enterprise SDK for JavaScript. -### Use the Splunk SDK for JavaScript components on your web page -To use the components from the Splunk SDK for JavaScript on your web page, copy the -**/splunk-sdk-javascript/client** directory to your web server. -Then, include the **splunk.js** or **splunk.min.js** file from this directory in -your code. +### Use the Splunk Enterprise SDK for JavaScript components on your web page + +To use the components from the Splunk Enterprise SDK for JavaScript on your web page, copy the **/splunk-sdk-javascript/client** directory to your web server. +Then, include the **splunk.js** or **splunk.min.js** file from this directory in your code. For example, include one of the following tags in your code: @@ -39,39 +42,32 @@ Or: -You can also include the UI components, such as the Timeline and Charting -controls. These UI component files (splunk.ui.timeline and -splunk.ui.charting) are also in the /splunk-sdk-javascript/client -directory. +You can also include the UI components, such as the Timeline and Charting controls. These UI component files (**splunk.ui.timeline** and **splunk.ui.charting**) are also in the **/splunk-sdk-javascript/client** directory. -### Install the Splunk SDK for JavaScript for Node.js -> **Note:** The Splunk SDK for JavaScript v1.7.0 requires Node.js version 0.10.x, or 0.12.x or 4+. +### Install Node.js in your project -If you want to use the Splunk SDK for JavaScript with your Node.js programs, install -the SDK by running `npm` in *your* project's directory as follows: +To use the Splunk Enterprise SDK for JavaScript with your Node.js programs, install the SDK by running `npm` in *your* project's directory as follows: npm install splunk-sdk -Then, to include the Splunk SDK for JavaScript, use the `require` function in your -code: +Then, to include the Splunk Enterprise SDK for JavaScript, use the `require` function in your code: var splunkjs = require('splunk-sdk'); ## Usage -The following examples show you how to list search jobs using client-side and -server-side code. +The following examples show you how to list search jobs using client-side and server-side code. ### Client-side code example -This HTML example uses the Splunk SDK for JavaScript to list all jobs: - +This HTML example uses the Splunk Enterprise SDK for JavaScript to list all jobs: +```javascript +``` ### Node.js code example -This example shows how to use the Splunk SDK for JavaScript and Node.js to list all -jobs: +This example shows how to use the Splunk Enterprise SDK for JavaScript and Node.js to list all jobs: + +##### Login with username and password +```javascript var splunkjs = require('splunk-sdk'); - var service = new splunkjs.Service({username: "admin", password: "changeme"}); + var service = new splunkjs.Service({username: "admin", password: "changed!"}); service.login(function(err, success) { if (err) { throw err; @@ -109,64 +108,135 @@ jobs: } }); }); +``` +##### Login with sessionKey + +```shell +# Create a sessionKey +curl -k -u : ://:/services/auth/login -d username= -d password= +``` + +```javascript +var serviceWithSessionKey = new splunkjs.Service( + { + // Replace the host if you are accessing remote host + scheme: 'https', + host: 'localhost', + port: '8089', + sessionKey: SESSION_KEY, // Add your sessionKey here + version: '8', + }); + +serviceWithSessionKey.get("search/jobs", { count: 1 }, function (err, res) { + if (err) { + console.log(err); + } else } + console.log("Login successful with sessionKey"); + } +}); +``` + +##### Login with token + +```shell +#### From shell #### +# Enable token authetication +curl -k -u : -X POST ://:/services/admin/token-auth/tokens_auth -d disabled=false + +# Create a token +curl -k -u : -X POST ://:/services/authorization/tokens?output_mode=json --data name= --data audience=Users --data-urlencode expires_on=+30d +``` + +```shell +#### From web #### +# Enable token authentication +Go to settings > Tokens and click on 'Enable Token Authentication' + +# Create a token +1. Go to settings > Token and click on 'New Token' +2. Enter the relevant information +3. Copy the created token and save it somewhere safe. +``` + +```javascript +var serviceWithBearerToken = new splunkjs.Service( + { + // Replace the host if you are accessing remote host + scheme: 'https', + host: 'localhost', + port: '8089', + sessionKey: TOKEN, // Add your token here + version: '8', + }); + +serviceWithBearerToken.get("search/jobs", { count: 2 }, function (err, res) { + if (err) + console.log(err); + else + console.log("Login successful with bearer token"); +}); +``` ## SDK examples -The Splunk SDK for JavaScript contains several server- and client-based examples. -For detailed instructions about getting them running, see the -[Splunk Developer Portal][examples]. +The Splunk Enterprise SDK for JavaScript contains several server- and client-based examples. +For details, see the [Splunk Enterprise SDK for JavaScript Examples](https://dev.splunk.com/enterprise/docs/devtools/javascript/sdk-javascript/sdkjavascriptexamples) on the Splunk Developer Portal. + +#### Create a .splunkrc convenience file -### Set up the .splunkrc file +To connect to Splunk Enterprise, many of the SDK examples and unit tests take command-line arguments that specify values for the host, port, and login credentials for Splunk Enterprise. For convenience during development, you can store these arguments as key-value pairs in a text file named **.splunkrc**. Then, the SDK examples and unit tests use the values from the **.splunkrc** file when you don't specify them. -To connect to Splunk, many of the SDK examples and unit tests take command-line -arguments that specify values for the host, port, and login credentials for -Splunk. For convenience during development, you can store these arguments as -key-value pairs in a text file named **.splunkrc**. Then, the SDK examples and -unit tests use the values from the **.splunkrc** file when you don't specify -them. +>**Note**: Storing login credentials in the **.splunkrc** file is only for convenience during development. This file isn't part of the Splunk platform and shouldn't be used for storing user credentials for production. And, if you're at all concerned about the security of your credentials, enter them at the command line rather than saving them in this file. -To use this convenience file, create a text file with the following format and -save it as **.splunkrc** in the current user's home directory: +To use this convenience file, create a text file with the following format: - # Splunk host (default: localhost) + # Splunk Enterprise host (default: localhost) host=localhost - # Splunk admin port (default: 8089) + # Splunk Enterprise admin port (default: 8089) port=8089 - # Splunk username + # Splunk Enterprise username username=admin - # Splunk password - password=changeme + # Splunk Enterprise password + password=changed! # Access scheme (default: https) scheme=https - # Your version of Splunk (default: 5.0) - version=5.0 + # Your version of Splunk Enterprise + version=8.2 + +Save the file as **.splunkrc** in the current user's home directory. + +* For example on OS X, save the file as: + + ~/.splunkrc + +* On Windows, save the file as: + + C:\Users\currentusername\.splunkrc + + You might get errors in Windows when you try to name the file because ".splunkrc" appears to be a nameless file with an extension. You can use the command line to create this file by going to the **C:\Users\\<currentusername>** directory and entering the following command: + + Notepad.exe .splunkrc + + Click **Yes**, then continue creating the file. -**Note**: The `version` key is required if using Splunk 4.3. ### Client-side examples -The Splunk SDK for JavaScript includes several browser-based examples, which you can -run from the Examples web page. +The Splunk Enterprise SDK for JavaScript includes several browser-based examples, which you can run from the Examples web page. -To start a simple web server and open the Examples page in a -web browser, enter: +To start a simple web server and open the Examples page in a web browser, enter: node sdkdo examples ### Node.js examples -The Splunk SDK for JavaScript includes several command-line examples, which are -located in the **/splunk-sdk-javascript/examples/node** directory. These -examples run with Node.js and use the command-line arguments from the -**.splunkrc** file, if you set this up with your login credentials. +The Splunk Enterprise SDK for JavaScript includes several command-line examples, which are located in the **/splunk-sdk-javascript/examples/node** directory. These examples run with Node.js and use the command-line arguments from the **.splunkrc** file, if you set this up with your login credentials. -For example, to run the **jobs.js** example, open a command prompt in the -**/splunk-sdk-javascript/examples/node** directory and enter: +For example, to run the **jobs.js** example, open a command prompt in the **/splunk-sdk-javascript/examples/node** directory and enter: node jobs.js list -If you aren't storing your login credentials in **.splunkrc**, enter the -following command, providing your own values: +If you aren't storing your login credentials in **.splunkrc**, enter the following command, providing your own values: node jobs.js --username yourusername --password yourpassword list @@ -183,37 +253,27 @@ Your output should look something like this: ## Development -The Splunk SDK for JavaScript infrastructure relies on Node.js, so if you want to -build files, run examples, run tests, or generate documentation, you must -install Node.js. You can read more about how to set up your environment -on the [Splunk Developer Portal][requirements]. - +The Splunk Enterprise SDK for JavaScript infrastructure relies on Node.js to build files, run examples, run tests, and generate documentation. -All development activities are managed by a helper script called *sdkdo*. For a -list of possible commands and options, open a command prompt in the -**splunk-sdk-javascript** directory and enter: +All development activities are managed by a helper script called `sdkdo`. For a list of possible commands and options, open a command prompt in the **splunk-sdk-javascript** directory and enter: node sdkdo --help -### Compile (combine and minify) the browser files +### Compile the browser files -To rebuild and minify the browser files, open a command prompt in the -**splunk-sdk-javascript** directory and enter: +To rebuild and minify the browser files, open a command prompt in the **splunk-sdk-javascript** directory and enter: node sdkdo compile ### Run unit tests -The Splunk SDK for JavaScript includes several unit tests for each component. You -can run individual test modules or run all tests. Before you run them, some -searches need to be running in your splunkd instance. You can start some -searches by logging into Splunk Web and opening the Search app, which will run a -few searches to populate its dashboard. +The Splunk Enterprise SDK for JavaScript includes several unit tests for each component. You can run individual test modules or run all tests. Some searches need to be running in your Splunk Enterprise instance before you run these tests. You can start some searches by logging into Splunk Web and opening the Search app, which runs a few searches to populate the dashboard. + +>**Note**: The [SDK App Collection](https://github.com/splunk/sdk-app-collection) app is required for running unit tests. -**Note**: The 'sdk-app-collection' app is required for running unit tests. +To run the unit tests, open a command prompt in the **splunk-sdk-javascript** directory, then run the following commands. -To run the unit tests, open a command prompt in the **splunk-sdk-javascript** -directory. To run all tests, enter: +To run all tests, enter: node sdkdo tests @@ -221,233 +281,93 @@ To run the HTTP and the Async tests, enter: node sdkdo tests http,async -To run the browser tests, enter: - - node sdkdo tests-browser - -To run all unit tests without log messages from splunk, enter: - - `node sdkdo tests --quiet` - -To run all the tests and generate JUnit compatible XML in `splunk-sdk-javascript/test_logs/junit_test_results.xml`, enter: +To run tests containing a particular string, enter: - `node sdkdo tests --reporter junit` + node sdkdo tests --grep "While success" -## Repository - - - - - - - - - - - +To run the browser tests, enter: - - - - + node sdkdo tests-browser - - - - +To run all unit tests without log messages, enter: - - - - + node sdkdo tests --quiet - - - - +To run all the tests and generate test report in **splunk-sdk-javascript/mochawesome-report/mochawesome.html**, enter: - - - - + node sdkdo tests --reporter mochawesome - - - - +To get more info to run tests, enter: - - - - + make test_specific +## Repository -
/binExecutable files (such as sdkdo)
/clientPre-built files for the browser
/contribPackaged third-party dependencies (such as test runners)
/docsAPI reference documentation
/examplesExamples
/libThe SDK code files
/licensesLicense information for packaged third-party dependencies
/node_modulesJavaScript modules used by Node.js
/testsUnit tests
+| Directory | Description | +|:------------- |:---------------------------------------------------------- | +| /bin | Executable files (such as sdkdo) | +| /client | Pre-built files for the browser | +| /contrib | Packaged third-party dependencies (such as test runners) | +| /docs | API reference documentation | +| /examples | Examples | +| /lib | The SDK code files | +| /licenses | License information for packaged third-party dependencies | +| /node_modules | JavaScript modules used by Node.js | +| /tests | Unit tests | ### Changelog -The **CHANGELOG.md** file in the root of the repository contains a description -of changes for each version of the SDK. You can also find the -[Splunk SDK for JavaScript Changelog][changelog] online. +The [CHANGELOG](CHANGELOG.md) contains a description of changes for each version of the SDK. For the latest version, see the [CHANGELOG.md](https://github.com/splunk/splunk-sdk-javascript/blob/master/CHANGELOG.md) on GitHub. ### Branches -The **master** branch always represents a stable and released version of the -SDK. You can read more about the -[JavaScript SDK Branching Model][branchingmodel] on our wiki. +The **master** branch represents a stable and released version of the SDK. +To learn about our branching model, see [Branching Model](https://github.com/splunk/splunk-sdk-javascript/wiki/Branching-Model) on GitHub. ## Documentation and resources -If you need to know more: +| Resource | Description | +|:----------------------- |:----------- | +| [Splunk Developer Portal](http://dev.splunk.com) | General developer documentation, tools, and examples | +| [Integrate the Splunk platform using development tools for JavaScript](https://dev.splunk.com/enterprise/docs/devtools/javascript)| Documentation for JavaScript development | +| [Splunk Enterprise SDK for JavaScript Reference](http://docs.splunk.com/Documentation/JavaScriptSDK) | SDK API reference documentation | +| [REST API Reference Manual](https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTprolog) | Splunk REST API reference documentation | +| [Splunk>Docs](https://docs.splunk.com/Documentation) | General documentation for the Splunk platform | +| [GitHub Wiki](https://github.com/splunk/splunk-sdk-javascript/wiki/) | Documentation for this SDK's repository on GitHub | -* For all things developer with Splunk, your main resource is the - [Splunk Developer Portal][devportal]. - -* For conceptual and how-to documentation, see the - [Overview of the Splunk SDK for JavaScript][jsoverview]. - -* For API reference documentation, see the - [Splunk SDK for JavaScript Reference][jsapiref]. - -* For more about the Splunk REST API, see the - [REST API Reference][restapiref]. - -* For more about about Splunk in general, see [Splunk>Docs][splunkdocs]. - - -* For more about this SDK's repository, see our [GitHub Wiki][jsgithubwiki]. ## Community -Stay connected with other developers building on Splunk. +Stay connected with other developers building on the Splunk platform. - +* [Email](mailto:devinfo@splunk.com) +* [Issues and pull requests](https://github.com/splunk/splunk-sdk-javascript/issues/) +* [Community Slack](https://splunk-usergroups.slack.com/app_redirect?channel=appdev) +* [Splunk Answers](https://community.splunk.com/t5/Splunk-Development/ct-p/developer-tools) +* [Splunk Blogs](https://www.splunk.com/blog) +* [Twitter](https://twitter.com/splunkdev) - - - - +### Contributions - - - +If you would like to contribute to the SDK, see [Contributing to Splunk](https://www.splunk.com/en_us/form/contributions.html). For additional guidelines, see [CONTRIBUTING](CONTRIBUTING.md). - - - - - - - - - - - - -
Emaildevinfo@splunk.com
Issues -https://github.com/splunk/splunk-sdk-javascript/issues/
Answers -http://splunk-base.splunk.com/tags/javascript/
Blog -http://blogs.splunk.com/dev/
Twitter -@splunkdev
- -### How to contribute +### Support -If you would like to contribute to the SDK, go here for more information: +* You will be granted support if you or your company are already covered under an existing maintenance/support agreement. Submit a new case in the [Support Portal](https://www.splunk.com/en_us/support-and-services.html) and include "Splunk Enterprise SDK for JavaScript" in the subject line. -* [Splunk and open source][contributions] + If you are not covered under an existing maintenance/support agreement, you can find help through the broader community at [Splunk Answers](https://community.splunk.com/t5/Splunk-Development/ct-p/developer-tools). -* [Individual contributions][indivcontrib] +* Splunk will NOT provide support for SDKs if the core library (the code in the **/splunklib** directory) has been modified. If you modify an SDK and want support, you can find help through the broader community and [Splunk Answers](https://community.splunk.com/t5/Splunk-Development/ct-p/developer-tools). -* [Company contributions][companycontrib] + We would also like to know why you modified the core library, so please send feedback to _devinfo@splunk.com_. +* File any issues on [GitHub](https://github.com/splunk/splunk-sdk-javascript/issues). -### Support -1. You will be granted support if you or your company are already covered under an existing maintenance/support agreement. - Send an email to support@splunk.com and include "Splunk SDK for JavaScript" in the subject line. - 2. If you are not covered under an existing maintenance/support agreement, you - can find help through the broader community at: -
    -
  • Splunk Answers (use - the sdk, java, python, and javascript tags to - identify your questions)
  • -
-3. Splunk will NOT provide support for SDKs if the core library (the - code in the /lib directory) has been modified. If you modify an SDK - and want support, you can find help through the broader community and Splunk - answers (see above). We would also like to know why you modified the core - library—please send feedback to devinfo@splunk.com. -4. File any issues on [GitHub](githubjsissues) - ### Contact us -You can reach the Developer Platform team at _devinfo@splunk.com_. +You can reach the Splunk Developer Platform team at _devinfo@splunk.com_. ## License -The Splunk JavaScript Software Development Kit is licensed under the Apache -License 2.0. Details can be found in the LICENSE file. - -### Third-party libraries - -The embedded third-party libraries may have different licenses. Here is a list -of embedded libraries and their licenses: - -* [dox RESTful degradable JavaScript routing][dox]: [MIT][dox-license] -* [davis.js Documentation Generator][davis.js]: [MIT][davis-license] -* [jquery.class.js Create custom class with jquery][jquery.class.js]: [MIT][jquery.class-license] -* [nodeunit Unit testing in node.js and the browser][nodeunit]: [MIT][nodeunit-license] -* [showdown.js Markdown to HTML converter][showdown.js]: [BSD][showdown-license] -* [staticresource Static resource handling][staticresource]: [MIT][staticresource-license] -* [webapp2 A framework for Google App Engine][webapp2]: [Apache][webapp2-license] -* [commander Node.js command-line interfaces][commander]: [MIT][commander-license] -* [script.js Asyncronous JavaScript loader and dependency manager][script.js]: [Apache][scriptjs-license] -* [base64.js Fast base64 encoding/decoding][base64.js]: [MIT][base64-license] - - -[dox]: https://github.com/visionmedia/dox -[davis.js]: https://github.com/olivernn/davis.js -[jquery.class.js]: http://ejohn.org/blog/simple-javascript-inheritance/ -[nodeunit]: https://github.com/caolan/nodeunit/ -[showdown.js]: https://github.com/coreyti/showdown/ -[staticresource]: https://github.com/atsuya/static-resource/ -[webapp2]: http://code.google.com/p/webapp-improved/ -[commander]: https://github.com/visionmedia/commander.js/ -[script.js]: https://github.com/ded/script.js/ -[base64.js]: http://code.google.com/p/javascriptbase64/ -[dox-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-DOX -[davis-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-DAVIS -[jquery.class-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-JQUERYCLASS -[nodeunit-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-NODEUNIT -[showdown-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-SHOWDOWN -[staticresource-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-STATICRESOURCE -[webapp2-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-WEBAPP2 -[commander-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-COMMANDER -[scriptjs-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-SCRIPTJS -[base64-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-BASE64 -[event-license]: https://github.com/splunk/splunk-sdk-javascript/blob/master/licenses/LICENSE-BASE64 - -[json2]: http://www.json.org/js.html -[splunkrc]: https://github.com/splunk/splunk-sdk-javascript/blob/master/splunkrc.spec -[node_examples_dir]: https://github.com/splunk/splunk-sdk-javascript/blob/master/examples/node -[browser_examples_dir]: https://github.com/splunk/splunk-sdk-javascript/blob/master/examples/browser -[client_dir]: https://github.com/splunk/splunk-sdk-javascript/blob/master/client -[refdocs]: http://docs.splunk.com/Documentation/JavaScriptSDK -[devportal]: http://dev.splunk.com -[cli]: https://github.com/splunk/splunk-sdk-javascript/blob/master/bin/cli.js -[SplunkInstall]: http://docs.splunk.com/Documentation/Splunk/latest/Installation/WhatsintheInstallationManual -[zip]: https://github.com/splunk/splunk-sdk-javascript/zipball/master -[jsoverview]: http://dev.splunk.com/view/SP-CAAAECM -[install]: http://dev.splunk.com/view/javascript-sdk-getting-started/SP-CAAAEFN -[examples]: http://dev.splunk.com/view/javascript-sdk-getting-started/SP-CAAAEDD -[requirements]: http://dev.splunk.com/view/javascript-sdk-getting-started/SP-CAAAED6 -[contributions]: http://dev.splunk.com/view/opensource/SP-CAAAEDM -[changelog]: https://github.com/splunk/splunk-sdk-javascript/blob/master/CHANGELOG.md -[branchingmodel]: https://github.com/splunk/splunk-sdk-javascript/wiki/Branching-Model -[jsapiref]: http://docs.splunk.com/Documentation/JavaScriptSDK -[restapiref]: http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI -[splunkdocs]: http://docs.splunk.com/Documentation/Splunk -[jsgithubwiki]: https://github.com/splunk/splunk-sdk-javascript/wiki -[indivcontrib]: http://dev.splunk.com/goto/individualcontributions -[companycontrib]: http://dev.splunk.com/view/companycontributions/SP-CAAAEDR -[githubjsissues]: https://github.com/splunk/splunk-sdk-javascript/issues +The Splunk Enterprise Software Development Kit for JavaScript is licensed under the Apache License 2.0. See [LICENSE](LICENSE) for details. diff --git a/bin/cli.js b/bin/cli.js index 81cbadea3..bd502b741 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -12,38 +12,39 @@ // 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'); - var program = require('../contrib/commander'); - var spawn = require('child_process').spawn; - var path = require('path'); - var fs = require('fs'); - var browserify = require('browserify'); - var http = require('http'); - var url = require('url'); - var request = require('request'); + var dox = require('../contrib/dox/dox'); + var doc_builder = require('../contrib/dox/doc_builder'); + var program = require('../contrib/commander'); + var spawn = require('child_process').spawn; + var path = require('path'); + var fs = require('fs'); + var browserify = require('browserify'); + var http = require('http'); + var url = require('url'); + 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); }); @@ -809,7 +830,11 @@ .option('--port ', 'Splunk port') .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('--reporter ', '(optional) How to report results') + .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()); + }; + +})();