diff --git a/.bra.toml b/.bra.toml index e832e9beee5..ef1a925158d 100644 --- a/.bra.toml +++ b/.bra.toml @@ -9,7 +9,7 @@ watch_dirs = [ "$WORKDIR/public/views", "$WORKDIR/conf", ] -watch_exts = [".go", ".ini"] +watch_exts = [".go", ".ini", ".toml", ".html"] build_delay = 1500 cmds = [ ["go", "build", "-o", "./bin/grafana-server"], diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..5760be58369 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 99ed4c4adfd..b52ecff18b8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ coverage/ .aws-config.json awsconfig /dist +/emails/dist +/public_gen /tmp docs/AWS_S3_BUCKET @@ -27,4 +29,5 @@ public/css/*.min.css conf/custom.ini fig.yml profile.cov - +grafana +tsconfig.json diff --git a/.jshintrc b/.jshintrc index e8f044e1da9..16904c42413 100644 --- a/.jshintrc +++ b/.jshintrc @@ -23,7 +23,7 @@ "laxcomma": true, "sub": true, "unused": true, - "maxdepth": 5, + "maxdepth": 6, "maxlen": 140, "globals": { @@ -32,4 +32,4 @@ "Chromath": false, "setImmediate": true } -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index c046d3364cc..fca413673ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,86 @@ -# 2.1.0 (unreleased - master branch) +# 2.5 (unreleased) + +** New Feature: Mix data sources ** +A built in data source is now available named `-- Mixed --`, When picked in the metrics tab, +it allows you to add queries of differnet data source types & instances to the same graph/panel! +[Issue #436](https://github.com/grafana/grafana/issues/436) + +** New Feature: Elasticsearch Metrics Query Editor and Viz Support ** +- Feature rich query editor and processing features enables you to issues all kind of metric queries to Elasticsearch +- See [Issue #1034](https://github.com/grafana/grafana/issues/1034) for more info. + +** User Onboarding ** +- Org admin can now send email invites (or invite links) to people who are not yet Grafana users +- Sign up flow now supports email verification (if enabled) +- See [Issue #2353](https://github.com/grafana/grafana/issues/2354) for more info. + +** Other new Features && Enhancements** +- [Pull #2720](https://github.com/grafana/grafana/pull/2720). Admin: Initial basic quota support (per Org) +- [Issue #2577](https://github.com/grafana/grafana/issues/2577). Panel: Resize handles in panel bottom right corners for easy width and height change +- [Issue #2457](https://github.com/grafana/grafana/issues/2457). Admin: admin page for all grafana organizations (list / edit view) +- [Issue #1186](https://github.com/grafana/grafana/issues/1186). Time Picker: New option `today`, will set time range from midnight to now +- [Issue #1186](https://github.com/grafana/grafana/issues/1186). Time Picker: New option `today`, will set time range from midnight to now +- [Issue #2647](https://github.com/grafana/grafana/issues/2647). InfluxDB: You can now set group by time interval on each query +- [Issue #2599](https://github.com/grafana/grafana/issues/2599). InfluxDB: Improved alias support, you can now use the `AS` clause for each select statement +- [Issue #2708](https://github.com/grafana/grafana/issues/2708). InfluxDB: You can now set math expression for select clauses. + +**Fixes** +- [Issue #2413](https://github.com/grafana/grafana/issues/2413). InfluxDB 0.9: Fix for handling empty series object in response from influxdb +- [Issue #2574](https://github.com/grafana/grafana/issues/2574). Snapshot: Fix for snapshot with expire 7 days option, 7 days option not correct, was 7 hours +- [Issue #2568](https://github.com/grafana/grafana/issues/2568). AuthProxy: Fix for server side rendering of panel when using auth proxy +- [Issue #2490](https://github.com/grafana/grafana/issues/2490). Graphite: Dashboard import was broken in 2.1 and 2.1.1, working now +- [Issue #2565](https://github.com/grafana/grafana/issues/2565). TimePicker: Fix for when you applied custom time range it did not refreh dashboard +- [Issue #2563](https://github.com/grafana/grafana/issues/2563). Annotations: Fixed issue when html sanitizer failes for title to annotation body, now fallbacks to html escaping title and text +- [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url) +- [Issue #2620](https://github.com/grafana/grafana/issues/2620). Graph: multi series tooltip did no highlight correct point when stacking was enabled and series were of different resolution +- [Issue #2636](https://github.com/grafana/grafana/issues/2636). InfluxDB: Do no show template vars in dropdown for tag keys and group by keys +- [Issue #2604](https://github.com/grafana/grafana/issues/2604). InfluxDB: More alias options, can now use `$[0-9]` syntax to reference part of a measurement name (seperated by dots) + +**Breaking Changes** +- Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that +require an update to custom data sources for them to work in 2.2. [Read this doc](https://github.com/grafana/grafana/tree/master/docs/sources/datasources/plugin_api.md) for more on the +data source api change. +- The duplicate query function used in data source editors is changed, and moveMetricQuery function was renamed + +**Tech (Note for devs)** +Started using Typescript (transpiled to ES5), uncompiled typescript files and less files are in public folder (in source tree) +This folder is never modified by build steps. Compiled css and javascript files are put in public_gen, all other files +that do not undergo transformation are just copied from public to public_gen, it is public_gen that is used by grafana-server +if it is found. + +Grunt & Watch tasks: +- `grunt` : default task, will remove public_gen, copy over all files from public, do less & typescript compilation +- `grunt watch`: will watch for changes to less, and typescript files and compile them to public_gen, and for other files it will just copy them to public_gen + + + 2.1.3 (2015-08-24) + +**Fixes** +- [Issue #2580](https://github.com/grafana/grafana/issues/2580). Packaging: ldap.toml was not marked as config file and could be overwritten in upgrade +- [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url) + +# 2.1.2 (2015-08-20) + +**Fixes** +- [Issue #2558](https://github.com/grafana/grafana/issues/2558). DragDrop: Fix for broken drag drop behavior +- [Issue #2534](https://github.com/grafana/grafana/issues/2534). Templating: fix for setting template variable value via url and having repeated panels or rows + +# 2.1.1 (2015-08-11) + +**Fixes** +- [Issue #2443](https://github.com/grafana/grafana/issues/2443). Templating: Fix for buggy repeat row behavior when combined with with repeat panel due to recent change before 2.1 release +- [Issue #2442](https://github.com/grafana/grafana/issues/2442). Templating: Fix text panel when using template variables in text in in repeated panel +- [Issue #2446](https://github.com/grafana/grafana/issues/2446). InfluxDB: Fix for using template vars inside alias field (InfluxDB 0.9) +- [Issue #2460](https://github.com/grafana/grafana/issues/2460). SinglestatPanel: Fix to handle series with no data points +- [Issue #2461](https://github.com/grafana/grafana/issues/2461). LDAP: Fix for ldap users with empty email address +- [Issue #2484](https://github.com/grafana/grafana/issues/2484). Graphite: Fix bug when using series ref (#A-Z) and referenced series is hidden in query editor. +- [Issue #1896](https://github.com/grafana/grafana/issues/1896). Postgres: Dashboard search is now case insensitive when using Postgres + +**Enhancements** +- [Issue #2477](https://github.com/grafana/grafana/issues/2477). InfluxDB(0.9): Added more condition operators (`<`, `>`, `<>`, `!~`), thx @thuck +- [Issue #2483](https://github.com/grafana/grafana/issues/2484). InfluxDB(0.9): Use $col as option in alias patterns, thx @thuck + +# 2.1.0 (2015-08-04) **Data sources** - [Issue #1525](https://github.com/grafana/grafana/issues/1525). InfluxDB: Full support for InfluxDB 0.9 with new adapted query editor @@ -103,6 +185,10 @@ # 2.0.0-Beta1 (2015-03-30) +**Important Note** + +Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated backend server. Please read the [Documentation](http://docs.grafana.org) for more detailed about this SIGNIFCANT change to Grafana + **New features** - [Issue #1623](https://github.com/grafana/grafana/issues/1623). Share Dashboard: Dashboard snapshot sharing (dash and data snapshot), save to local or save to public snapshot dashboard snapshots.raintank.io site - [Issue #1622](https://github.com/grafana/grafana/issues/1622). Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 063f0efedd7..e9977b8ac7c 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -5,6 +5,11 @@ "./pkg/..." ], "Deps": [ + { + "ImportPath": "github.com/BurntSushi/toml", + "Comment": "v0.1.0-21-g056c9bc", + "Rev": "056c9bc7be7190eaa7715723883caffa5f8fa3e4" + }, { "ImportPath": "github.com/Unknwon/com", "Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df" @@ -13,6 +18,50 @@ "ImportPath": "github.com/Unknwon/macaron", "Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a" }, + { + "ImportPath": "github.com/aws/aws-sdk-go/aws", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/endpoints", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/query", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/rest", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/internal/signer/v4", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatch", + "Comment": "v0.7.3", + "Rev": "bed164a424e75154a40550c04c313ef51a7bb275" + }, + { + "ImportPath": "github.com/davecgh/go-spew/spew", + "Rev": "2df174808ee097f90d259e432cc04442cf60be21" + }, + { + "ImportPath": "github.com/go-ldap/ldap", + "Comment": "v1-19-g83e6542", + "Rev": "83e65426fd1c06626e88aa8a085e5bfed0208e29" + }, { "ImportPath": "github.com/go-sql-driver/mysql", "Comment": "v1.2-26-g9543750", @@ -65,6 +114,10 @@ "ImportPath": "github.com/streadway/amqp", "Rev": "150b7f24d6ad507e6026c13d85ce1f1391ac7400" }, + { + "ImportPath": "github.com/vaughan0/go-ini", + "Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1" + }, { "ImportPath": "golang.org/x/net/context", "Rev": "972f0c5fbe4ae29e666c3f78c3ed42ae7a448b0a" @@ -73,6 +126,11 @@ "ImportPath": "golang.org/x/oauth2", "Rev": "c58fcf0ffc1c772aa2e1ee4894bc19f2649263b2" }, + { + "ImportPath": "gopkg.in/asn1-ber.v1", + "Comment": "v1", + "Rev": "9eae18c3681ae3d3c677ac2b80a8fe57de45fc09" + }, { "ImportPath": "gopkg.in/bufio.v1", "Comment": "v1", diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/.gitignore b/Godeps/_workspace/src/github.com/BurntSushi/toml/.gitignore new file mode 100644 index 00000000000..0cd3800377d --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/.gitignore @@ -0,0 +1,5 @@ +TAGS +tags +.*.swp +tomlcheck/tomlcheck +toml.test diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/.travis.yml b/Godeps/_workspace/src/github.com/BurntSushi/toml/.travis.yml new file mode 100644 index 00000000000..43caf6d0211 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/.travis.yml @@ -0,0 +1,12 @@ +language: go +go: + - 1.1 + - 1.2 + - tip +install: + - go install ./... + - go get github.com/BurntSushi/toml-test +script: + - export PATH="$PATH:$HOME/gopath/bin" + - make test + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/COMPATIBLE b/Godeps/_workspace/src/github.com/BurntSushi/toml/COMPATIBLE new file mode 100644 index 00000000000..21e0938caef --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/COMPATIBLE @@ -0,0 +1,3 @@ +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/COPYING b/Godeps/_workspace/src/github.com/BurntSushi/toml/COPYING new file mode 100644 index 00000000000..5a8e332545f --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/Makefile b/Godeps/_workspace/src/github.com/BurntSushi/toml/Makefile new file mode 100644 index 00000000000..3600848d331 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/Makefile @@ -0,0 +1,19 @@ +install: + go install ./... + +test: install + go test -v + toml-test toml-test-decoder + toml-test -encoder toml-test-encoder + +fmt: + gofmt -w *.go */*.go + colcheck *.go */*.go + +tags: + find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS + +push: + git push origin master + git push github master + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/README.md b/Godeps/_workspace/src/github.com/BurntSushi/toml/README.md new file mode 100644 index 00000000000..5a5df637094 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/README.md @@ -0,0 +1,220 @@ +## TOML parser and encoder for Go with reflection + +TOML stands for Tom's Obvious, Minimal Language. This Go package provides a +reflection interface similar to Go's standard library `json` and `xml` +packages. This package also supports the `encoding.TextUnmarshaler` and +`encoding.TextMarshaler` interfaces so that you can define custom data +representations. (There is an example of this below.) + +Spec: https://github.com/mojombo/toml + +Compatible with TOML version +[v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md) + +Documentation: http://godoc.org/github.com/BurntSushi/toml + +Installation: + +```bash +go get github.com/BurntSushi/toml +``` + +Try the toml validator: + +```bash +go get github.com/BurntSushi/toml/cmd/tomlv +tomlv some-toml-file.toml +``` + +[![Build status](https://api.travis-ci.org/BurntSushi/toml.png)](https://travis-ci.org/BurntSushi/toml) + + +### Testing + +This package passes all tests in +[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder +and the encoder. + +### Examples + +This package works similarly to how the Go standard library handles `XML` +and `JSON`. Namely, data is loaded into Go values via reflection. + +For the simplest example, consider some TOML file as just a list of keys +and values: + +```toml +Age = 25 +Cats = [ "Cauchy", "Plato" ] +Pi = 3.14 +Perfection = [ 6, 28, 496, 8128 ] +DOB = 1987-07-05T05:45:00Z +``` + +Which could be defined in Go as: + +```go +type Config struct { + Age int + Cats []string + Pi float64 + Perfection []int + DOB time.Time // requires `import time` +} +``` + +And then decoded with: + +```go +var conf Config +if _, err := toml.Decode(tomlData, &conf); err != nil { + // handle error +} +``` + +You can also use struct tags if your struct field name doesn't map to a TOML +key value directly: + +```toml +some_key_NAME = "wat" +``` + +```go +type TOML struct { + ObscureKey string `toml:"some_key_NAME"` +} +``` + +### Using the `encoding.TextUnmarshaler` interface + +Here's an example that automatically parses duration strings into +`time.Duration` values: + +```toml +[[song]] +name = "Thunder Road" +duration = "4m49s" + +[[song]] +name = "Stairway to Heaven" +duration = "8m03s" +``` + +Which can be decoded with: + +```go +type song struct { + Name string + Duration duration +} +type songs struct { + Song []song +} +var favorites songs +if _, err := toml.Decode(blob, &favorites); err != nil { + log.Fatal(err) +} + +for _, s := range favorites.Song { + fmt.Printf("%s (%s)\n", s.Name, s.Duration) +} +``` + +And you'll also need a `duration` type that satisfies the +`encoding.TextUnmarshaler` interface: + +```go +type duration struct { + time.Duration +} + +func (d *duration) UnmarshalText(text []byte) error { + var err error + d.Duration, err = time.ParseDuration(string(text)) + return err +} +``` + +### More complex usage + +Here's an example of how to load the example from the official spec page: + +```toml +# This is a TOML document. Boom. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # First class dates? Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it + +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] +``` + +And the corresponding Go types are: + +```go +type tomlConfig struct { + Title string + Owner ownerInfo + DB database `toml:"database"` + Servers map[string]server + Clients clients +} + +type ownerInfo struct { + Name string + Org string `toml:"organization"` + Bio string + DOB time.Time +} + +type database struct { + Server string + Ports []int + ConnMax int `toml:"connection_max"` + Enabled bool +} + +type server struct { + IP string + DC string +} + +type clients struct { + Data [][]interface{} + Hosts []string +} +``` + +Note that a case insensitive match will be tried if an exact match can't be +found. + +A working example of the above can be found in `_examples/example.{go,toml}`. + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING new file mode 100644 index 00000000000..5a8e332545f --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md new file mode 100644 index 00000000000..24421eb7034 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md @@ -0,0 +1,14 @@ +# Implements the TOML test suite interface + +This is an implementation of the interface expected by +[toml-test](https://github.com/BurntSushi/toml-test) for my +[toml parser written in Go](https://github.com/BurntSushi/toml). +In particular, it maps TOML data on `stdin` to a JSON format on `stdout`. + + +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + +Compatible with `toml-test` version +[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0) + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go new file mode 100644 index 00000000000..14e7557005d --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go @@ -0,0 +1,90 @@ +// Command toml-test-decoder satisfies the toml-test interface for testing +// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout. +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "path" + "time" + + "github.com/BurntSushi/toml" +) + +func init() { + log.SetFlags(0) + + flag.Usage = usage + flag.Parse() +} + +func usage() { + log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0])) + flag.PrintDefaults() + + os.Exit(1) +} + +func main() { + if flag.NArg() != 0 { + flag.Usage() + } + + var tmp interface{} + if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil { + log.Fatalf("Error decoding TOML: %s", err) + } + + typedTmp := translate(tmp) + if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil { + log.Fatalf("Error encoding JSON: %s", err) + } +} + +func translate(tomlData interface{}) interface{} { + switch orig := tomlData.(type) { + case map[string]interface{}: + typed := make(map[string]interface{}, len(orig)) + for k, v := range orig { + typed[k] = translate(v) + } + return typed + case []map[string]interface{}: + typed := make([]map[string]interface{}, len(orig)) + for i, v := range orig { + typed[i] = translate(v).(map[string]interface{}) + } + return typed + case []interface{}: + typed := make([]interface{}, len(orig)) + for i, v := range orig { + typed[i] = translate(v) + } + + // We don't really need to tag arrays, but let's be future proof. + // (If TOML ever supports tuples, we'll need this.) + return tag("array", typed) + case time.Time: + return tag("datetime", orig.Format("2006-01-02T15:04:05Z")) + case bool: + return tag("bool", fmt.Sprintf("%v", orig)) + case int64: + return tag("integer", fmt.Sprintf("%d", orig)) + case float64: + return tag("float", fmt.Sprintf("%v", orig)) + case string: + return tag("string", orig) + } + + panic(fmt.Sprintf("Unknown type: %T", tomlData)) +} + +func tag(typeName string, data interface{}) map[string]interface{} { + return map[string]interface{}{ + "type": typeName, + "value": data, + } +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING new file mode 100644 index 00000000000..5a8e332545f --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md new file mode 100644 index 00000000000..45a603f2980 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md @@ -0,0 +1,14 @@ +# Implements the TOML test suite interface for TOML encoders + +This is an implementation of the interface expected by +[toml-test](https://github.com/BurntSushi/toml-test) for the +[TOML encoder](https://github.com/BurntSushi/toml). +In particular, it maps JSON data on `stdin` to a TOML format on `stdout`. + + +Compatible with TOML version +[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) + +Compatible with `toml-test` version +[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0) + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go new file mode 100644 index 00000000000..092cc684496 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go @@ -0,0 +1,131 @@ +// Command toml-test-encoder satisfies the toml-test interface for testing +// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout. +package main + +import ( + "encoding/json" + "flag" + "log" + "os" + "path" + "strconv" + "time" + + "github.com/BurntSushi/toml" +) + +func init() { + log.SetFlags(0) + + flag.Usage = usage + flag.Parse() +} + +func usage() { + log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0])) + flag.PrintDefaults() + + os.Exit(1) +} + +func main() { + if flag.NArg() != 0 { + flag.Usage() + } + + var tmp interface{} + if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil { + log.Fatalf("Error decoding JSON: %s", err) + } + + tomlData := translate(tmp) + if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil { + log.Fatalf("Error encoding TOML: %s", err) + } +} + +func translate(typedJson interface{}) interface{} { + switch v := typedJson.(type) { + case map[string]interface{}: + if len(v) == 2 && in("type", v) && in("value", v) { + return untag(v) + } + m := make(map[string]interface{}, len(v)) + for k, v2 := range v { + m[k] = translate(v2) + } + return m + case []interface{}: + tabArray := make([]map[string]interface{}, len(v)) + for i := range v { + if m, ok := translate(v[i]).(map[string]interface{}); ok { + tabArray[i] = m + } else { + log.Fatalf("JSON arrays may only contain objects. This " + + "corresponds to only tables being allowed in " + + "TOML table arrays.") + } + } + return tabArray + } + log.Fatalf("Unrecognized JSON format '%T'.", typedJson) + panic("unreachable") +} + +func untag(typed map[string]interface{}) interface{} { + t := typed["type"].(string) + v := typed["value"] + switch t { + case "string": + return v.(string) + case "integer": + v := v.(string) + n, err := strconv.Atoi(v) + if err != nil { + log.Fatalf("Could not parse '%s' as integer: %s", v, err) + } + return n + case "float": + v := v.(string) + f, err := strconv.ParseFloat(v, 64) + if err != nil { + log.Fatalf("Could not parse '%s' as float64: %s", v, err) + } + return f + case "datetime": + v := v.(string) + t, err := time.Parse("2006-01-02T15:04:05Z", v) + if err != nil { + log.Fatalf("Could not parse '%s' as a datetime: %s", v, err) + } + return t + case "bool": + v := v.(string) + switch v { + case "true": + return true + case "false": + return false + } + log.Fatalf("Could not parse '%s' as a boolean.", v) + case "array": + v := v.([]interface{}) + array := make([]interface{}, len(v)) + for i := range v { + if m, ok := v[i].(map[string]interface{}); ok { + array[i] = untag(m) + } else { + log.Fatalf("Arrays may only contain other arrays or "+ + "primitive values, but found a '%T'.", m) + } + } + return array + } + log.Fatalf("Unrecognized tag type '%s'.", t) + panic("unreachable") +} + +func in(key string, m map[string]interface{}) bool { + _, ok := m[key] + return ok +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/COPYING b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/COPYING new file mode 100644 index 00000000000..5a8e332545f --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/README.md b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/README.md new file mode 100644 index 00000000000..5df0dc32bbf --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/README.md @@ -0,0 +1,22 @@ +# TOML Validator + +If Go is installed, it's simple to try it out: + +```bash +go get github.com/BurntSushi/toml/cmd/tomlv +tomlv some-toml-file.toml +``` + +You can see the types of every key in a TOML file with: + +```bash +tomlv -types some-toml-file.toml +``` + +At the moment, only one error message is reported at a time. Error messages +include line numbers. No output means that the files given are valid TOML, or +there is a bug in `tomlv`. + +Compatible with TOML version +[v0.1.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md) + diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/main.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/main.go new file mode 100644 index 00000000000..c7d689a7e91 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/cmd/tomlv/main.go @@ -0,0 +1,61 @@ +// Command tomlv validates TOML documents and prints each key's type. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path" + "strings" + "text/tabwriter" + + "github.com/BurntSushi/toml" +) + +var ( + flagTypes = false +) + +func init() { + log.SetFlags(0) + + flag.BoolVar(&flagTypes, "types", flagTypes, + "When set, the types of every defined key will be shown.") + + flag.Usage = usage + flag.Parse() +} + +func usage() { + log.Printf("Usage: %s toml-file [ toml-file ... ]\n", + path.Base(os.Args[0])) + flag.PrintDefaults() + + os.Exit(1) +} + +func main() { + if flag.NArg() < 1 { + flag.Usage() + } + for _, f := range flag.Args() { + var tmp interface{} + md, err := toml.DecodeFile(f, &tmp) + if err != nil { + log.Fatalf("Error in '%s': %s", f, err) + } + if flagTypes { + printTypes(md) + } + } +} + +func printTypes(md toml.MetaData) { + tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + for _, key := range md.Keys() { + fmt.Fprintf(tabw, "%s%s\t%s\n", + strings.Repeat(" ", len(key)-1), key, md.Type(key...)) + } + tabw.Flush() +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/decode.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/decode.go new file mode 100644 index 00000000000..6c7d398b89a --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/decode.go @@ -0,0 +1,492 @@ +package toml + +import ( + "fmt" + "io" + "io/ioutil" + "math" + "reflect" + "strings" + "time" +) + +var e = fmt.Errorf + +// Unmarshaler is the interface implemented by objects that can unmarshal a +// TOML description of themselves. +type Unmarshaler interface { + UnmarshalTOML(interface{}) error +} + +// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. +func Unmarshal(p []byte, v interface{}) error { + _, err := Decode(string(p), v) + return err +} + +// Primitive is a TOML value that hasn't been decoded into a Go value. +// When using the various `Decode*` functions, the type `Primitive` may +// be given to any value, and its decoding will be delayed. +// +// A `Primitive` value can be decoded using the `PrimitiveDecode` function. +// +// The underlying representation of a `Primitive` value is subject to change. +// Do not rely on it. +// +// N.B. Primitive values are still parsed, so using them will only avoid +// the overhead of reflection. They can be useful when you don't know the +// exact type of TOML data until run time. +type Primitive struct { + undecoded interface{} + context Key +} + +// DEPRECATED! +// +// Use MetaData.PrimitiveDecode instead. +func PrimitiveDecode(primValue Primitive, v interface{}) error { + md := MetaData{decoded: make(map[string]bool)} + return md.unify(primValue.undecoded, rvalue(v)) +} + +// PrimitiveDecode is just like the other `Decode*` functions, except it +// decodes a TOML value that has already been parsed. Valid primitive values +// can *only* be obtained from values filled by the decoder functions, +// including this method. (i.e., `v` may contain more `Primitive` +// values.) +// +// Meta data for primitive values is included in the meta data returned by +// the `Decode*` functions with one exception: keys returned by the Undecoded +// method will only reflect keys that were decoded. Namely, any keys hidden +// behind a Primitive will be considered undecoded. Executing this method will +// update the undecoded keys in the meta data. (See the example.) +func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { + md.context = primValue.context + defer func() { md.context = nil }() + return md.unify(primValue.undecoded, rvalue(v)) +} + +// Decode will decode the contents of `data` in TOML format into a pointer +// `v`. +// +// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be +// used interchangeably.) +// +// TOML arrays of tables correspond to either a slice of structs or a slice +// of maps. +// +// TOML datetimes correspond to Go `time.Time` values. +// +// All other TOML types (float, string, int, bool and array) correspond +// to the obvious Go types. +// +// An exception to the above rules is if a type implements the +// encoding.TextUnmarshaler interface. In this case, any primitive TOML value +// (floats, strings, integers, booleans and datetimes) will be converted to +// a byte string and given to the value's UnmarshalText method. See the +// Unmarshaler example for a demonstration with time duration strings. +// +// Key mapping +// +// TOML keys can map to either keys in a Go map or field names in a Go +// struct. The special `toml` struct tag may be used to map TOML keys to +// struct fields that don't match the key name exactly. (See the example.) +// A case insensitive match to struct names will be tried if an exact match +// can't be found. +// +// The mapping between TOML values and Go values is loose. That is, there +// may exist TOML values that cannot be placed into your representation, and +// there may be parts of your representation that do not correspond to +// TOML values. This loose mapping can be made stricter by using the IsDefined +// and/or Undecoded methods on the MetaData returned. +// +// This decoder will not handle cyclic types. If a cyclic type is passed, +// `Decode` will not terminate. +func Decode(data string, v interface{}) (MetaData, error) { + p, err := parse(data) + if err != nil { + return MetaData{}, err + } + md := MetaData{ + p.mapping, p.types, p.ordered, + make(map[string]bool, len(p.ordered)), nil, + } + return md, md.unify(p.mapping, rvalue(v)) +} + +// DecodeFile is just like Decode, except it will automatically read the +// contents of the file at `fpath` and decode it for you. +func DecodeFile(fpath string, v interface{}) (MetaData, error) { + bs, err := ioutil.ReadFile(fpath) + if err != nil { + return MetaData{}, err + } + return Decode(string(bs), v) +} + +// DecodeReader is just like Decode, except it will consume all bytes +// from the reader and decode it for you. +func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { + bs, err := ioutil.ReadAll(r) + if err != nil { + return MetaData{}, err + } + return Decode(string(bs), v) +} + +// unify performs a sort of type unification based on the structure of `rv`, +// which is the client representation. +// +// Any type mismatch produces an error. Finding a type that we don't know +// how to handle produces an unsupported type error. +func (md *MetaData) unify(data interface{}, rv reflect.Value) error { + + // Special case. Look for a `Primitive` value. + if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { + // Save the undecoded data and the key context into the primitive + // value. + context := make(Key, len(md.context)) + copy(context, md.context) + rv.Set(reflect.ValueOf(Primitive{ + undecoded: data, + context: context, + })) + return nil + } + + // Special case. Unmarshaler Interface support. + if rv.CanAddr() { + if v, ok := rv.Addr().Interface().(Unmarshaler); ok { + return v.UnmarshalTOML(data) + } + } + + // Special case. Handle time.Time values specifically. + // TODO: Remove this code when we decide to drop support for Go 1.1. + // This isn't necessary in Go 1.2 because time.Time satisfies the encoding + // interfaces. + if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { + return md.unifyDatetime(data, rv) + } + + // Special case. Look for a value satisfying the TextUnmarshaler interface. + if v, ok := rv.Interface().(TextUnmarshaler); ok { + return md.unifyText(data, v) + } + // BUG(burntsushi) + // The behavior here is incorrect whenever a Go type satisfies the + // encoding.TextUnmarshaler interface but also corresponds to a TOML + // hash or array. In particular, the unmarshaler should only be applied + // to primitive TOML values. But at this point, it will be applied to + // all kinds of values and produce an incorrect error whenever those values + // are hashes or arrays (including arrays of tables). + + k := rv.Kind() + + // laziness + if k >= reflect.Int && k <= reflect.Uint64 { + return md.unifyInt(data, rv) + } + switch k { + case reflect.Ptr: + elem := reflect.New(rv.Type().Elem()) + err := md.unify(data, reflect.Indirect(elem)) + if err != nil { + return err + } + rv.Set(elem) + return nil + case reflect.Struct: + return md.unifyStruct(data, rv) + case reflect.Map: + return md.unifyMap(data, rv) + case reflect.Array: + return md.unifyArray(data, rv) + case reflect.Slice: + return md.unifySlice(data, rv) + case reflect.String: + return md.unifyString(data, rv) + case reflect.Bool: + return md.unifyBool(data, rv) + case reflect.Interface: + // we only support empty interfaces. + if rv.NumMethod() > 0 { + return e("Unsupported type '%s'.", rv.Kind()) + } + return md.unifyAnything(data, rv) + case reflect.Float32: + fallthrough + case reflect.Float64: + return md.unifyFloat64(data, rv) + } + return e("Unsupported type '%s'.", rv.Kind()) +} + +func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { + tmap, ok := mapping.(map[string]interface{}) + if !ok { + return mismatch(rv, "map", mapping) + } + + for key, datum := range tmap { + var f *field + fields := cachedTypeFields(rv.Type()) + for i := range fields { + ff := &fields[i] + if ff.name == key { + f = ff + break + } + if f == nil && strings.EqualFold(ff.name, key) { + f = ff + } + } + if f != nil { + subv := rv + for _, i := range f.index { + subv = indirect(subv.Field(i)) + } + if isUnifiable(subv) { + md.decoded[md.context.add(key).String()] = true + md.context = append(md.context, key) + if err := md.unify(datum, subv); err != nil { + return e("Type mismatch for '%s.%s': %s", + rv.Type().String(), f.name, err) + } + md.context = md.context[0 : len(md.context)-1] + } else if f.name != "" { + // Bad user! No soup for you! + return e("Field '%s.%s' is unexported, and therefore cannot "+ + "be loaded with reflection.", rv.Type().String(), f.name) + } + } + } + return nil +} + +func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { + tmap, ok := mapping.(map[string]interface{}) + if !ok { + return badtype("map", mapping) + } + if rv.IsNil() { + rv.Set(reflect.MakeMap(rv.Type())) + } + for k, v := range tmap { + md.decoded[md.context.add(k).String()] = true + md.context = append(md.context, k) + + rvkey := indirect(reflect.New(rv.Type().Key())) + rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) + if err := md.unify(v, rvval); err != nil { + return err + } + md.context = md.context[0 : len(md.context)-1] + + rvkey.SetString(k) + rv.SetMapIndex(rvkey, rvval) + } + return nil +} + +func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + return badtype("slice", data) + } + sliceLen := datav.Len() + if sliceLen != rv.Len() { + return e("expected array length %d; got TOML array of length %d", + rv.Len(), sliceLen) + } + return md.unifySliceArray(datav, rv) +} + +func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { + datav := reflect.ValueOf(data) + if datav.Kind() != reflect.Slice { + return badtype("slice", data) + } + sliceLen := datav.Len() + if rv.IsNil() { + rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen)) + } + return md.unifySliceArray(datav, rv) +} + +func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { + sliceLen := data.Len() + for i := 0; i < sliceLen; i++ { + v := data.Index(i).Interface() + sliceval := indirect(rv.Index(i)) + if err := md.unify(v, sliceval); err != nil { + return err + } + } + return nil +} + +func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { + if _, ok := data.(time.Time); ok { + rv.Set(reflect.ValueOf(data)) + return nil + } + return badtype("time.Time", data) +} + +func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { + if s, ok := data.(string); ok { + rv.SetString(s) + return nil + } + return badtype("string", data) +} + +func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { + if num, ok := data.(float64); ok { + switch rv.Kind() { + case reflect.Float32: + fallthrough + case reflect.Float64: + rv.SetFloat(num) + default: + panic("bug") + } + return nil + } + return badtype("float", data) +} + +func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { + if num, ok := data.(int64); ok { + if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { + switch rv.Kind() { + case reflect.Int, reflect.Int64: + // No bounds checking necessary. + case reflect.Int8: + if num < math.MinInt8 || num > math.MaxInt8 { + return e("Value '%d' is out of range for int8.", num) + } + case reflect.Int16: + if num < math.MinInt16 || num > math.MaxInt16 { + return e("Value '%d' is out of range for int16.", num) + } + case reflect.Int32: + if num < math.MinInt32 || num > math.MaxInt32 { + return e("Value '%d' is out of range for int32.", num) + } + } + rv.SetInt(num) + } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { + unum := uint64(num) + switch rv.Kind() { + case reflect.Uint, reflect.Uint64: + // No bounds checking necessary. + case reflect.Uint8: + if num < 0 || unum > math.MaxUint8 { + return e("Value '%d' is out of range for uint8.", num) + } + case reflect.Uint16: + if num < 0 || unum > math.MaxUint16 { + return e("Value '%d' is out of range for uint16.", num) + } + case reflect.Uint32: + if num < 0 || unum > math.MaxUint32 { + return e("Value '%d' is out of range for uint32.", num) + } + } + rv.SetUint(unum) + } else { + panic("unreachable") + } + return nil + } + return badtype("integer", data) +} + +func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { + if b, ok := data.(bool); ok { + rv.SetBool(b) + return nil + } + return badtype("boolean", data) +} + +func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { + rv.Set(reflect.ValueOf(data)) + return nil +} + +func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { + var s string + switch sdata := data.(type) { + case TextMarshaler: + text, err := sdata.MarshalText() + if err != nil { + return err + } + s = string(text) + case fmt.Stringer: + s = sdata.String() + case string: + s = sdata + case bool: + s = fmt.Sprintf("%v", sdata) + case int64: + s = fmt.Sprintf("%d", sdata) + case float64: + s = fmt.Sprintf("%f", sdata) + default: + return badtype("primitive (string-like)", data) + } + if err := v.UnmarshalText([]byte(s)); err != nil { + return err + } + return nil +} + +// rvalue returns a reflect.Value of `v`. All pointers are resolved. +func rvalue(v interface{}) reflect.Value { + return indirect(reflect.ValueOf(v)) +} + +// indirect returns the value pointed to by a pointer. +// Pointers are followed until the value is not a pointer. +// New values are allocated for each nil pointer. +// +// An exception to this rule is if the value satisfies an interface of +// interest to us (like encoding.TextUnmarshaler). +func indirect(v reflect.Value) reflect.Value { + if v.Kind() != reflect.Ptr { + if v.CanAddr() { + pv := v.Addr() + if _, ok := pv.Interface().(TextUnmarshaler); ok { + return pv + } + } + return v + } + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + return indirect(reflect.Indirect(v)) +} + +func isUnifiable(rv reflect.Value) bool { + if rv.CanSet() { + return true + } + if _, ok := rv.Interface().(TextUnmarshaler); ok { + return true + } + return false +} + +func badtype(expected string, data interface{}) error { + return e("Expected %s but found '%T'.", expected, data) +} + +func mismatch(user reflect.Value, expected string, data interface{}) error { + return e("Type mismatch for %s. Expected %s but found '%T'.", + user.Type().String(), expected, data) +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/decode_meta.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/decode_meta.go new file mode 100644 index 00000000000..ef6f545fa1b --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/decode_meta.go @@ -0,0 +1,122 @@ +package toml + +import "strings" + +// MetaData allows access to meta information about TOML data that may not +// be inferrable via reflection. In particular, whether a key has been defined +// and the TOML type of a key. +type MetaData struct { + mapping map[string]interface{} + types map[string]tomlType + keys []Key + decoded map[string]bool + context Key // Used only during decoding. +} + +// IsDefined returns true if the key given exists in the TOML data. The key +// should be specified hierarchially. e.g., +// +// // access the TOML key 'a.b.c' +// IsDefined("a", "b", "c") +// +// IsDefined will return false if an empty key given. Keys are case sensitive. +func (md *MetaData) IsDefined(key ...string) bool { + if len(key) == 0 { + return false + } + + var hash map[string]interface{} + var ok bool + var hashOrVal interface{} = md.mapping + for _, k := range key { + if hash, ok = hashOrVal.(map[string]interface{}); !ok { + return false + } + if hashOrVal, ok = hash[k]; !ok { + return false + } + } + return true +} + +// Type returns a string representation of the type of the key specified. +// +// Type will return the empty string if given an empty key or a key that +// does not exist. Keys are case sensitive. +func (md *MetaData) Type(key ...string) string { + fullkey := strings.Join(key, ".") + if typ, ok := md.types[fullkey]; ok { + return typ.typeString() + } + return "" +} + +// Key is the type of any TOML key, including key groups. Use (MetaData).Keys +// to get values of this type. +type Key []string + +func (k Key) String() string { + return strings.Join(k, ".") +} + +func (k Key) maybeQuotedAll() string { + var ss []string + for i := range k { + ss = append(ss, k.maybeQuoted(i)) + } + return strings.Join(ss, ".") +} + +func (k Key) maybeQuoted(i int) string { + quote := false + for _, c := range k[i] { + if !isBareKeyChar(c) { + quote = true + break + } + } + if quote { + return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" + } else { + return k[i] + } +} + +func (k Key) add(piece string) Key { + newKey := make(Key, len(k)+1) + copy(newKey, k) + newKey[len(k)] = piece + return newKey +} + +// Keys returns a slice of every key in the TOML data, including key groups. +// Each key is itself a slice, where the first element is the top of the +// hierarchy and the last is the most specific. +// +// The list will have the same order as the keys appeared in the TOML data. +// +// All keys returned are non-empty. +func (md *MetaData) Keys() []Key { + return md.keys +} + +// Undecoded returns all keys that have not been decoded in the order in which +// they appear in the original TOML document. +// +// This includes keys that haven't been decoded because of a Primitive value. +// Once the Primitive value is decoded, the keys will be considered decoded. +// +// Also note that decoding into an empty interface will result in no decoding, +// and so no keys will be considered decoded. +// +// In this sense, the Undecoded keys correspond to keys in the TOML document +// that do not have a concrete type in your representation. +func (md *MetaData) Undecoded() []Key { + undecoded := make([]Key, 0, len(md.keys)) + for _, key := range md.keys { + if !md.decoded[key.String()] { + undecoded = append(undecoded, key) + } + } + return undecoded +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/decode_test.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/decode_test.go new file mode 100644 index 00000000000..3805931f2d3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/decode_test.go @@ -0,0 +1,950 @@ +package toml + +import ( + "fmt" + "log" + "reflect" + "testing" + "time" +) + +func init() { + log.SetFlags(0) +} + +func TestDecodeSimple(t *testing.T) { + var testSimple = ` +age = 250 +andrew = "gallant" +kait = "brady" +now = 1987-07-05T05:45:00Z +yesOrNo = true +pi = 3.14 +colors = [ + ["red", "green", "blue"], + ["cyan", "magenta", "yellow", "black"], +] + +[My.Cats] +plato = "cat 1" +cauchy = "cat 2" +` + + type cats struct { + Plato string + Cauchy string + } + type simple struct { + Age int + Colors [][]string + Pi float64 + YesOrNo bool + Now time.Time + Andrew string + Kait string + My map[string]cats + } + + var val simple + _, err := Decode(testSimple, &val) + if err != nil { + t.Fatal(err) + } + + now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00") + if err != nil { + panic(err) + } + var answer = simple{ + Age: 250, + Andrew: "gallant", + Kait: "brady", + Now: now, + YesOrNo: true, + Pi: 3.14, + Colors: [][]string{ + {"red", "green", "blue"}, + {"cyan", "magenta", "yellow", "black"}, + }, + My: map[string]cats{ + "Cats": cats{Plato: "cat 1", Cauchy: "cat 2"}, + }, + } + if !reflect.DeepEqual(val, answer) { + t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", + answer, val) + } +} + +func TestDecodeEmbedded(t *testing.T) { + type Dog struct{ Name string } + type Age int + + tests := map[string]struct { + input string + decodeInto interface{} + wantDecoded interface{} + }{ + "embedded struct": { + input: `Name = "milton"`, + decodeInto: &struct{ Dog }{}, + wantDecoded: &struct{ Dog }{Dog{"milton"}}, + }, + "embedded non-nil pointer to struct": { + input: `Name = "milton"`, + decodeInto: &struct{ *Dog }{}, + wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, + }, + "embedded nil pointer to struct": { + input: ``, + decodeInto: &struct{ *Dog }{}, + wantDecoded: &struct{ *Dog }{nil}, + }, + "embedded int": { + input: `Age = -5`, + decodeInto: &struct{ Age }{}, + wantDecoded: &struct{ Age }{-5}, + }, + } + + for label, test := range tests { + _, err := Decode(test.input, test.decodeInto) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) { + t.Errorf("%s: want decoded == %+v, got %+v", + label, test.wantDecoded, test.decodeInto) + } + } +} + +func TestTableArrays(t *testing.T) { + var tomlTableArrays = ` +[[albums]] +name = "Born to Run" + + [[albums.songs]] + name = "Jungleland" + + [[albums.songs]] + name = "Meeting Across the River" + +[[albums]] +name = "Born in the USA" + + [[albums.songs]] + name = "Glory Days" + + [[albums.songs]] + name = "Dancing in the Dark" +` + + type Song struct { + Name string + } + + type Album struct { + Name string + Songs []Song + } + + type Music struct { + Albums []Album + } + + expected := Music{[]Album{ + {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, + {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, + }} + var got Music + if _, err := Decode(tomlTableArrays, &got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expected, got) { + t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) + } +} + +// Case insensitive matching tests. +// A bit more comprehensive than needed given the current implementation, +// but implementations change. +// Probably still missing demonstrations of some ugly corner cases regarding +// case insensitive matching and multiple fields. +func TestCase(t *testing.T) { + var caseToml = ` +tOpString = "string" +tOpInt = 1 +tOpFloat = 1.1 +tOpBool = true +tOpdate = 2006-01-02T15:04:05Z +tOparray = [ "array" ] +Match = "i should be in Match only" +MatcH = "i should be in MatcH only" +once = "just once" +[nEst.eD] +nEstedString = "another string" +` + + type InsensitiveEd struct { + NestedString string + } + + type InsensitiveNest struct { + Ed InsensitiveEd + } + + type Insensitive struct { + TopString string + TopInt int + TopFloat float64 + TopBool bool + TopDate time.Time + TopArray []string + Match string + MatcH string + Once string + OncE string + Nest InsensitiveNest + } + + tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5]) + if err != nil { + panic(err) + } + expected := Insensitive{ + TopString: "string", + TopInt: 1, + TopFloat: 1.1, + TopBool: true, + TopDate: tme, + TopArray: []string{"array"}, + MatcH: "i should be in MatcH only", + Match: "i should be in Match only", + Once: "just once", + OncE: "", + Nest: InsensitiveNest{ + Ed: InsensitiveEd{NestedString: "another string"}, + }, + } + var got Insensitive + if _, err := Decode(caseToml, &got); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(expected, got) { + t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) + } +} + +func TestPointers(t *testing.T) { + type Object struct { + Type string + Description string + } + + type Dict struct { + NamedObject map[string]*Object + BaseObject *Object + Strptr *string + Strptrs []*string + } + s1, s2, s3 := "blah", "abc", "def" + expected := &Dict{ + Strptr: &s1, + Strptrs: []*string{&s2, &s3}, + NamedObject: map[string]*Object{ + "foo": {"FOO", "fooooo!!!"}, + "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, + }, + BaseObject: &Object{"BASE", "da base"}, + } + + ex1 := ` +Strptr = "blah" +Strptrs = ["abc", "def"] + +[NamedObject.foo] +Type = "FOO" +Description = "fooooo!!!" + +[NamedObject.bar] +Type = "BAR" +Description = "ba-ba-ba-ba-barrrr!!!" + +[BaseObject] +Type = "BASE" +Description = "da base" +` + dict := new(Dict) + _, err := Decode(ex1, dict) + if err != nil { + t.Errorf("Decode error: %v", err) + } + if !reflect.DeepEqual(expected, dict) { + t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) + } +} + +type sphere struct { + Center [3]float64 + Radius float64 +} + +func TestDecodeSimpleArray(t *testing.T) { + var s1 sphere + if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil { + t.Fatal(err) + } +} + +func TestDecodeArrayWrongSize(t *testing.T) { + var s1 sphere + if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil { + t.Fatal("Expected array type mismatch error") + } +} + +func TestDecodeLargeIntoSmallInt(t *testing.T) { + type table struct { + Value int8 + } + var tab table + if _, err := Decode(`value = 500`, &tab); err == nil { + t.Fatal("Expected integer out-of-bounds error.") + } +} + +func TestDecodeSizedInts(t *testing.T) { + type table struct { + U8 uint8 + U16 uint16 + U32 uint32 + U64 uint64 + U uint + I8 int8 + I16 int16 + I32 int32 + I64 int64 + I int + } + answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1} + toml := ` + u8 = 1 + u16 = 1 + u32 = 1 + u64 = 1 + u = 1 + i8 = -1 + i16 = -1 + i32 = -1 + i64 = -1 + i = -1 + ` + var tab table + if _, err := Decode(toml, &tab); err != nil { + t.Fatal(err.Error()) + } + if answer != tab { + t.Fatalf("Expected %#v but got %#v", answer, tab) + } +} + +func TestUnmarshaler(t *testing.T) { + + var tomlBlob = ` +[dishes.hamboogie] +name = "Hamboogie with fries" +price = 10.99 + +[[dishes.hamboogie.ingredients]] +name = "Bread Bun" + +[[dishes.hamboogie.ingredients]] +name = "Lettuce" + +[[dishes.hamboogie.ingredients]] +name = "Real Beef Patty" + +[[dishes.hamboogie.ingredients]] +name = "Tomato" + +[dishes.eggsalad] +name = "Egg Salad with rice" +price = 3.99 + +[[dishes.eggsalad.ingredients]] +name = "Egg" + +[[dishes.eggsalad.ingredients]] +name = "Mayo" + +[[dishes.eggsalad.ingredients]] +name = "Rice" +` + m := &menu{} + if _, err := Decode(tomlBlob, m); err != nil { + log.Fatal(err) + } + + if len(m.Dishes) != 2 { + t.Log("two dishes should be loaded with UnmarshalTOML()") + t.Errorf("expected %d but got %d", 2, len(m.Dishes)) + } + + eggSalad := m.Dishes["eggsalad"] + if _, ok := interface{}(eggSalad).(dish); !ok { + t.Errorf("expected a dish") + } + + if eggSalad.Name != "Egg Salad with rice" { + t.Errorf("expected the dish to be named 'Egg Salad with rice'") + } + + if len(eggSalad.Ingredients) != 3 { + t.Log("dish should be loaded with UnmarshalTOML()") + t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients)) + } + + found := false + for _, i := range eggSalad.Ingredients { + if i.Name == "Rice" { + found = true + break + } + } + if !found { + t.Error("Rice was not loaded in UnmarshalTOML()") + } + + // test on a value - must be passed as * + o := menu{} + if _, err := Decode(tomlBlob, &o); err != nil { + log.Fatal(err) + } + +} + +type menu struct { + Dishes map[string]dish +} + +func (m *menu) UnmarshalTOML(p interface{}) error { + m.Dishes = make(map[string]dish) + data, _ := p.(map[string]interface{}) + dishes := data["dishes"].(map[string]interface{}) + for n, v := range dishes { + if d, ok := v.(map[string]interface{}); ok { + nd := dish{} + nd.UnmarshalTOML(d) + m.Dishes[n] = nd + } else { + return fmt.Errorf("not a dish") + } + } + return nil +} + +type dish struct { + Name string + Price float32 + Ingredients []ingredient +} + +func (d *dish) UnmarshalTOML(p interface{}) error { + data, _ := p.(map[string]interface{}) + d.Name, _ = data["name"].(string) + d.Price, _ = data["price"].(float32) + ingredients, _ := data["ingredients"].([]map[string]interface{}) + for _, e := range ingredients { + n, _ := interface{}(e).(map[string]interface{}) + name, _ := n["name"].(string) + i := ingredient{name} + d.Ingredients = append(d.Ingredients, i) + } + return nil +} + +type ingredient struct { + Name string +} + +func ExampleMetaData_PrimitiveDecode() { + var md MetaData + var err error + + var tomlBlob = ` +ranking = ["Springsteen", "J Geils"] + +[bands.Springsteen] +started = 1973 +albums = ["Greetings", "WIESS", "Born to Run", "Darkness"] + +[bands."J Geils"] +started = 1970 +albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"] +` + + type band struct { + Started int + Albums []string + } + type classics struct { + Ranking []string + Bands map[string]Primitive + } + + // Do the initial decode. Reflection is delayed on Primitive values. + var music classics + if md, err = Decode(tomlBlob, &music); err != nil { + log.Fatal(err) + } + + // MetaData still includes information on Primitive values. + fmt.Printf("Is `bands.Springsteen` defined? %v\n", + md.IsDefined("bands", "Springsteen")) + + // Decode primitive data into Go values. + for _, artist := range music.Ranking { + // A band is a primitive value, so we need to decode it to get a + // real `band` value. + primValue := music.Bands[artist] + + var aBand band + if err = md.PrimitiveDecode(primValue, &aBand); err != nil { + log.Fatal(err) + } + fmt.Printf("%s started in %d.\n", artist, aBand.Started) + } + // Check to see if there were any fields left undecoded. + // Note that this won't be empty before decoding the Primitive value! + fmt.Printf("Undecoded: %q\n", md.Undecoded()) + + // Output: + // Is `bands.Springsteen` defined? true + // Springsteen started in 1973. + // J Geils started in 1970. + // Undecoded: [] +} + +func ExampleDecode() { + var tomlBlob = ` +# Some comments. +[alpha] +ip = "10.0.0.1" + + [alpha.config] + Ports = [ 8001, 8002 ] + Location = "Toronto" + Created = 1987-07-05T05:45:00Z + +[beta] +ip = "10.0.0.2" + + [beta.config] + Ports = [ 9001, 9002 ] + Location = "New Jersey" + Created = 1887-01-05T05:55:00Z +` + + type serverConfig struct { + Ports []int + Location string + Created time.Time + } + + type server struct { + IP string `toml:"ip"` + Config serverConfig `toml:"config"` + } + + type servers map[string]server + + var config servers + if _, err := Decode(tomlBlob, &config); err != nil { + log.Fatal(err) + } + + for _, name := range []string{"alpha", "beta"} { + s := config[name] + fmt.Printf("Server: %s (ip: %s) in %s created on %s\n", + name, s.IP, s.Config.Location, + s.Config.Created.Format("2006-01-02")) + fmt.Printf("Ports: %v\n", s.Config.Ports) + } + + // Output: + // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05 + // Ports: [8001 8002] + // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05 + // Ports: [9001 9002] +} + +type duration struct { + time.Duration +} + +func (d *duration) UnmarshalText(text []byte) error { + var err error + d.Duration, err = time.ParseDuration(string(text)) + return err +} + +// Example Unmarshaler shows how to decode TOML strings into your own +// custom data type. +func Example_unmarshaler() { + blob := ` +[[song]] +name = "Thunder Road" +duration = "4m49s" + +[[song]] +name = "Stairway to Heaven" +duration = "8m03s" +` + type song struct { + Name string + Duration duration + } + type songs struct { + Song []song + } + var favorites songs + if _, err := Decode(blob, &favorites); err != nil { + log.Fatal(err) + } + + // Code to implement the TextUnmarshaler interface for `duration`: + // + // type duration struct { + // time.Duration + // } + // + // func (d *duration) UnmarshalText(text []byte) error { + // var err error + // d.Duration, err = time.ParseDuration(string(text)) + // return err + // } + + for _, s := range favorites.Song { + fmt.Printf("%s (%s)\n", s.Name, s.Duration) + } + // Output: + // Thunder Road (4m49s) + // Stairway to Heaven (8m3s) +} + +// Example StrictDecoding shows how to detect whether there are keys in the +// TOML document that weren't decoded into the value given. This is useful +// for returning an error to the user if they've included extraneous fields +// in their configuration. +func Example_strictDecoding() { + var blob = ` +key1 = "value1" +key2 = "value2" +key3 = "value3" +` + type config struct { + Key1 string + Key3 string + } + + var conf config + md, err := Decode(blob, &conf) + if err != nil { + log.Fatal(err) + } + fmt.Printf("Undecoded keys: %q\n", md.Undecoded()) + // Output: + // Undecoded keys: ["key2"] +} + +// Example UnmarshalTOML shows how to implement a struct type that knows how to +// unmarshal itself. The struct must take full responsibility for mapping the +// values passed into the struct. The method may be used with interfaces in a +// struct in cases where the actual type is not known until the data is +// examined. +func Example_unmarshalTOML() { + + var blob = ` +[[parts]] +type = "valve" +id = "valve-1" +size = 1.2 +rating = 4 + +[[parts]] +type = "valve" +id = "valve-2" +size = 2.1 +rating = 5 + +[[parts]] +type = "pipe" +id = "pipe-1" +length = 2.1 +diameter = 12 + +[[parts]] +type = "cable" +id = "cable-1" +length = 12 +rating = 3.1 +` + o := &order{} + err := Unmarshal([]byte(blob), o) + if err != nil { + log.Fatal(err) + } + + fmt.Println(len(o.parts)) + + for _, part := range o.parts { + fmt.Println(part.Name()) + } + + // Code to implement UmarshalJSON. + + // type order struct { + // // NOTE `order.parts` is a private slice of type `part` which is an + // // interface and may only be loaded from toml using the + // // UnmarshalTOML() method of the Umarshaler interface. + // parts parts + // } + + // func (o *order) UnmarshalTOML(data interface{}) error { + + // // NOTE the example below contains detailed type casting to show how + // // the 'data' is retrieved. In operational use, a type cast wrapper + // // may be prefered e.g. + // // + // // func AsMap(v interface{}) (map[string]interface{}, error) { + // // return v.(map[string]interface{}) + // // } + // // + // // resulting in: + // // d, _ := AsMap(data) + // // + + // d, _ := data.(map[string]interface{}) + // parts, _ := d["parts"].([]map[string]interface{}) + + // for _, p := range parts { + + // typ, _ := p["type"].(string) + // id, _ := p["id"].(string) + + // // detect the type of part and handle each case + // switch p["type"] { + // case "valve": + + // size := float32(p["size"].(float64)) + // rating := int(p["rating"].(int64)) + + // valve := &valve{ + // Type: typ, + // ID: id, + // Size: size, + // Rating: rating, + // } + + // o.parts = append(o.parts, valve) + + // case "pipe": + + // length := float32(p["length"].(float64)) + // diameter := int(p["diameter"].(int64)) + + // pipe := &pipe{ + // Type: typ, + // ID: id, + // Length: length, + // Diameter: diameter, + // } + + // o.parts = append(o.parts, pipe) + + // case "cable": + + // length := int(p["length"].(int64)) + // rating := float32(p["rating"].(float64)) + + // cable := &cable{ + // Type: typ, + // ID: id, + // Length: length, + // Rating: rating, + // } + + // o.parts = append(o.parts, cable) + + // } + // } + + // return nil + // } + + // type parts []part + + // type part interface { + // Name() string + // } + + // type valve struct { + // Type string + // ID string + // Size float32 + // Rating int + // } + + // func (v *valve) Name() string { + // return fmt.Sprintf("VALVE: %s", v.ID) + // } + + // type pipe struct { + // Type string + // ID string + // Length float32 + // Diameter int + // } + + // func (p *pipe) Name() string { + // return fmt.Sprintf("PIPE: %s", p.ID) + // } + + // type cable struct { + // Type string + // ID string + // Length int + // Rating float32 + // } + + // func (c *cable) Name() string { + // return fmt.Sprintf("CABLE: %s", c.ID) + // } + + // Output: + // 4 + // VALVE: valve-1 + // VALVE: valve-2 + // PIPE: pipe-1 + // CABLE: cable-1 + +} + +type order struct { + // NOTE `order.parts` is a private slice of type `part` which is an + // interface and may only be loaded from toml using the UnmarshalTOML() + // method of the Umarshaler interface. + parts parts +} + +func (o *order) UnmarshalTOML(data interface{}) error { + + // NOTE the example below contains detailed type casting to show how + // the 'data' is retrieved. In operational use, a type cast wrapper + // may be prefered e.g. + // + // func AsMap(v interface{}) (map[string]interface{}, error) { + // return v.(map[string]interface{}) + // } + // + // resulting in: + // d, _ := AsMap(data) + // + + d, _ := data.(map[string]interface{}) + parts, _ := d["parts"].([]map[string]interface{}) + + for _, p := range parts { + + typ, _ := p["type"].(string) + id, _ := p["id"].(string) + + // detect the type of part and handle each case + switch p["type"] { + case "valve": + + size := float32(p["size"].(float64)) + rating := int(p["rating"].(int64)) + + valve := &valve{ + Type: typ, + ID: id, + Size: size, + Rating: rating, + } + + o.parts = append(o.parts, valve) + + case "pipe": + + length := float32(p["length"].(float64)) + diameter := int(p["diameter"].(int64)) + + pipe := &pipe{ + Type: typ, + ID: id, + Length: length, + Diameter: diameter, + } + + o.parts = append(o.parts, pipe) + + case "cable": + + length := int(p["length"].(int64)) + rating := float32(p["rating"].(float64)) + + cable := &cable{ + Type: typ, + ID: id, + Length: length, + Rating: rating, + } + + o.parts = append(o.parts, cable) + + } + } + + return nil +} + +type parts []part + +type part interface { + Name() string +} + +type valve struct { + Type string + ID string + Size float32 + Rating int +} + +func (v *valve) Name() string { + return fmt.Sprintf("VALVE: %s", v.ID) +} + +type pipe struct { + Type string + ID string + Length float32 + Diameter int +} + +func (p *pipe) Name() string { + return fmt.Sprintf("PIPE: %s", p.ID) +} + +type cable struct { + Type string + ID string + Length int + Rating float32 +} + +func (c *cable) Name() string { + return fmt.Sprintf("CABLE: %s", c.ID) +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/doc.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/doc.go new file mode 100644 index 00000000000..fe26800041b --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/doc.go @@ -0,0 +1,27 @@ +/* +Package toml provides facilities for decoding and encoding TOML configuration +files via reflection. There is also support for delaying decoding with +the Primitive type, and querying the set of keys in a TOML document with the +MetaData type. + +The specification implemented: https://github.com/mojombo/toml + +The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify +whether a file is a valid TOML document. It can also be used to print the +type of each key in a TOML document. + +Testing + +There are two important types of tests used for this package. The first is +contained inside '*_test.go' files and uses the standard Go unit testing +framework. These tests are primarily devoted to holistically testing the +decoder and encoder. + +The second type of testing is used to verify the implementation's adherence +to the TOML specification. These tests have been factored into their own +project: https://github.com/BurntSushi/toml-test + +The reason the tests are in a separate project is so that they can be used by +any implementation of TOML. Namely, it is language agnostic. +*/ +package toml diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/encode.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/encode.go new file mode 100644 index 00000000000..c7e227c7f4f --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/encode.go @@ -0,0 +1,551 @@ +package toml + +import ( + "bufio" + "errors" + "fmt" + "io" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +type tomlEncodeError struct{ error } + +var ( + errArrayMixedElementTypes = errors.New( + "can't encode array with mixed element types") + errArrayNilElement = errors.New( + "can't encode array with nil element") + errNonString = errors.New( + "can't encode a map with non-string key type") + errAnonNonStruct = errors.New( + "can't encode an anonymous field that is not a struct") + errArrayNoTable = errors.New( + "TOML array element can't contain a table") + errNoKey = errors.New( + "top-level values must be a Go map or struct") + errAnything = errors.New("") // used in testing +) + +var quotedReplacer = strings.NewReplacer( + "\t", "\\t", + "\n", "\\n", + "\r", "\\r", + "\"", "\\\"", + "\\", "\\\\", +) + +// Encoder controls the encoding of Go values to a TOML document to some +// io.Writer. +// +// The indentation level can be controlled with the Indent field. +type Encoder struct { + // A single indentation level. By default it is two spaces. + Indent string + + // hasWritten is whether we have written any output to w yet. + hasWritten bool + w *bufio.Writer +} + +// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer +// given. By default, a single indentation level is 2 spaces. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: bufio.NewWriter(w), + Indent: " ", + } +} + +// Encode writes a TOML representation of the Go value to the underlying +// io.Writer. If the value given cannot be encoded to a valid TOML document, +// then an error is returned. +// +// The mapping between Go values and TOML values should be precisely the same +// as for the Decode* functions. Similarly, the TextMarshaler interface is +// supported by encoding the resulting bytes as strings. (If you want to write +// arbitrary binary data then you will need to use something like base64 since +// TOML does not have any binary types.) +// +// When encoding TOML hashes (i.e., Go maps or structs), keys without any +// sub-hashes are encoded first. +// +// If a Go map is encoded, then its keys are sorted alphabetically for +// deterministic output. More control over this behavior may be provided if +// there is demand for it. +// +// Encoding Go values without a corresponding TOML representation---like map +// types with non-string keys---will cause an error to be returned. Similarly +// for mixed arrays/slices, arrays/slices with nil elements, embedded +// non-struct types and nested slices containing maps or structs. +// (e.g., [][]map[string]string is not allowed but []map[string]string is OK +// and so is []map[string][]string.) +func (enc *Encoder) Encode(v interface{}) error { + rv := eindirect(reflect.ValueOf(v)) + if err := enc.safeEncode(Key([]string{}), rv); err != nil { + return err + } + return enc.w.Flush() +} + +func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { + defer func() { + if r := recover(); r != nil { + if terr, ok := r.(tomlEncodeError); ok { + err = terr.error + return + } + panic(r) + } + }() + enc.encode(key, rv) + return nil +} + +func (enc *Encoder) encode(key Key, rv reflect.Value) { + // Special case. Time needs to be in ISO8601 format. + // Special case. If we can marshal the type to text, then we used that. + // Basically, this prevents the encoder for handling these types as + // generic structs (or whatever the underlying type of a TextMarshaler is). + switch rv.Interface().(type) { + case time.Time, TextMarshaler: + enc.keyEqElement(key, rv) + return + } + + k := rv.Kind() + switch k { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64, + reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: + enc.keyEqElement(key, rv) + case reflect.Array, reflect.Slice: + if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { + enc.eArrayOfTables(key, rv) + } else { + enc.keyEqElement(key, rv) + } + case reflect.Interface: + if rv.IsNil() { + return + } + enc.encode(key, rv.Elem()) + case reflect.Map: + if rv.IsNil() { + return + } + enc.eTable(key, rv) + case reflect.Ptr: + if rv.IsNil() { + return + } + enc.encode(key, rv.Elem()) + case reflect.Struct: + enc.eTable(key, rv) + default: + panic(e("Unsupported type for key '%s': %s", key, k)) + } +} + +// eElement encodes any value that can be an array element (primitives and +// arrays). +func (enc *Encoder) eElement(rv reflect.Value) { + switch v := rv.Interface().(type) { + case time.Time: + // Special case time.Time as a primitive. Has to come before + // TextMarshaler below because time.Time implements + // encoding.TextMarshaler, but we need to always use UTC. + enc.wf(v.In(time.FixedZone("UTC", 0)).Format("2006-01-02T15:04:05Z")) + return + case TextMarshaler: + // Special case. Use text marshaler if it's available for this value. + if s, err := v.MarshalText(); err != nil { + encPanic(err) + } else { + enc.writeQuoted(string(s)) + } + return + } + switch rv.Kind() { + case reflect.Bool: + enc.wf(strconv.FormatBool(rv.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64: + enc.wf(strconv.FormatInt(rv.Int(), 10)) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64: + enc.wf(strconv.FormatUint(rv.Uint(), 10)) + case reflect.Float32: + enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) + case reflect.Float64: + enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) + case reflect.Array, reflect.Slice: + enc.eArrayOrSliceElement(rv) + case reflect.Interface: + enc.eElement(rv.Elem()) + case reflect.String: + enc.writeQuoted(rv.String()) + default: + panic(e("Unexpected primitive type: %s", rv.Kind())) + } +} + +// By the TOML spec, all floats must have a decimal with at least one +// number on either side. +func floatAddDecimal(fstr string) string { + if !strings.Contains(fstr, ".") { + return fstr + ".0" + } + return fstr +} + +func (enc *Encoder) writeQuoted(s string) { + enc.wf("\"%s\"", quotedReplacer.Replace(s)) +} + +func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { + length := rv.Len() + enc.wf("[") + for i := 0; i < length; i++ { + elem := rv.Index(i) + enc.eElement(elem) + if i != length-1 { + enc.wf(", ") + } + } + enc.wf("]") +} + +func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { + if len(key) == 0 { + encPanic(errNoKey) + } + for i := 0; i < rv.Len(); i++ { + trv := rv.Index(i) + if isNil(trv) { + continue + } + panicIfInvalidKey(key) + enc.newline() + enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) + enc.newline() + enc.eMapOrStruct(key, trv) + } +} + +func (enc *Encoder) eTable(key Key, rv reflect.Value) { + panicIfInvalidKey(key) + if len(key) == 1 { + // Output an extra new line between top-level tables. + // (The newline isn't written if nothing else has been written though.) + enc.newline() + } + if len(key) > 0 { + enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) + enc.newline() + } + enc.eMapOrStruct(key, rv) +} + +func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { + switch rv := eindirect(rv); rv.Kind() { + case reflect.Map: + enc.eMap(key, rv) + case reflect.Struct: + enc.eStruct(key, rv) + default: + panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) + } +} + +func (enc *Encoder) eMap(key Key, rv reflect.Value) { + rt := rv.Type() + if rt.Key().Kind() != reflect.String { + encPanic(errNonString) + } + + // Sort keys so that we have deterministic output. And write keys directly + // underneath this key first, before writing sub-structs or sub-maps. + var mapKeysDirect, mapKeysSub []string + for _, mapKey := range rv.MapKeys() { + k := mapKey.String() + if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { + mapKeysSub = append(mapKeysSub, k) + } else { + mapKeysDirect = append(mapKeysDirect, k) + } + } + + var writeMapKeys = func(mapKeys []string) { + sort.Strings(mapKeys) + for _, mapKey := range mapKeys { + mrv := rv.MapIndex(reflect.ValueOf(mapKey)) + if isNil(mrv) { + // Don't write anything for nil fields. + continue + } + enc.encode(key.add(mapKey), mrv) + } + } + writeMapKeys(mapKeysDirect) + writeMapKeys(mapKeysSub) +} + +func (enc *Encoder) eStruct(key Key, rv reflect.Value) { + // Write keys for fields directly under this key first, because if we write + // a field that creates a new table, then all keys under it will be in that + // table (not the one we're writing here). + rt := rv.Type() + var fieldsDirect, fieldsSub [][]int + var addFields func(rt reflect.Type, rv reflect.Value, start []int) + addFields = func(rt reflect.Type, rv reflect.Value, start []int) { + for i := 0; i < rt.NumField(); i++ { + f := rt.Field(i) + // skip unexporded fields + if f.PkgPath != "" { + continue + } + frv := rv.Field(i) + if f.Anonymous { + frv := eindirect(frv) + t := frv.Type() + if t.Kind() != reflect.Struct { + encPanic(errAnonNonStruct) + } + addFields(t, frv, f.Index) + } else if typeIsHash(tomlTypeOfGo(frv)) { + fieldsSub = append(fieldsSub, append(start, f.Index...)) + } else { + fieldsDirect = append(fieldsDirect, append(start, f.Index...)) + } + } + } + addFields(rt, rv, nil) + + var writeFields = func(fields [][]int) { + for _, fieldIndex := range fields { + sft := rt.FieldByIndex(fieldIndex) + sf := rv.FieldByIndex(fieldIndex) + if isNil(sf) { + // Don't write anything for nil fields. + continue + } + + keyName := sft.Tag.Get("toml") + if keyName == "-" { + continue + } + if keyName == "" { + keyName = sft.Name + } + + keyName, opts := getOptions(keyName) + if _, ok := opts["omitempty"]; ok && isEmpty(sf) { + continue + } else if _, ok := opts["omitzero"]; ok && isZero(sf) { + continue + } + + enc.encode(key.add(keyName), sf) + } + } + writeFields(fieldsDirect) + writeFields(fieldsSub) +} + +// tomlTypeName returns the TOML type name of the Go value's type. It is +// used to determine whether the types of array elements are mixed (which is +// forbidden). If the Go value is nil, then it is illegal for it to be an array +// element, and valueIsNil is returned as true. + +// Returns the TOML type of a Go value. The type may be `nil`, which means +// no concrete TOML type could be found. +func tomlTypeOfGo(rv reflect.Value) tomlType { + if isNil(rv) || !rv.IsValid() { + return nil + } + switch rv.Kind() { + case reflect.Bool: + return tomlBool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64: + return tomlInteger + case reflect.Float32, reflect.Float64: + return tomlFloat + case reflect.Array, reflect.Slice: + if typeEqual(tomlHash, tomlArrayType(rv)) { + return tomlArrayHash + } else { + return tomlArray + } + case reflect.Ptr, reflect.Interface: + return tomlTypeOfGo(rv.Elem()) + case reflect.String: + return tomlString + case reflect.Map: + return tomlHash + case reflect.Struct: + switch rv.Interface().(type) { + case time.Time: + return tomlDatetime + case TextMarshaler: + return tomlString + default: + return tomlHash + } + default: + panic("unexpected reflect.Kind: " + rv.Kind().String()) + } +} + +// tomlArrayType returns the element type of a TOML array. The type returned +// may be nil if it cannot be determined (e.g., a nil slice or a zero length +// slize). This function may also panic if it finds a type that cannot be +// expressed in TOML (such as nil elements, heterogeneous arrays or directly +// nested arrays of tables). +func tomlArrayType(rv reflect.Value) tomlType { + if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { + return nil + } + firstType := tomlTypeOfGo(rv.Index(0)) + if firstType == nil { + encPanic(errArrayNilElement) + } + + rvlen := rv.Len() + for i := 1; i < rvlen; i++ { + elem := rv.Index(i) + switch elemType := tomlTypeOfGo(elem); { + case elemType == nil: + encPanic(errArrayNilElement) + case !typeEqual(firstType, elemType): + encPanic(errArrayMixedElementTypes) + } + } + // If we have a nested array, then we must make sure that the nested + // array contains ONLY primitives. + // This checks arbitrarily nested arrays. + if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { + nest := tomlArrayType(eindirect(rv.Index(0))) + if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { + encPanic(errArrayNoTable) + } + } + return firstType +} + +func getOptions(keyName string) (string, map[string]struct{}) { + opts := make(map[string]struct{}) + ss := strings.Split(keyName, ",") + name := ss[0] + if len(ss) > 1 { + for _, opt := range ss { + opts[opt] = struct{}{} + } + } + + return name, opts +} + +func isZero(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if rv.Int() == 0 { + return true + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + if rv.Uint() == 0 { + return true + } + case reflect.Float32, reflect.Float64: + if rv.Float() == 0.0 { + return true + } + } + + return false +} + +func isEmpty(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.String: + if len(strings.TrimSpace(rv.String())) == 0 { + return true + } + case reflect.Array, reflect.Slice, reflect.Map: + if rv.Len() == 0 { + return true + } + } + + return false +} + +func (enc *Encoder) newline() { + if enc.hasWritten { + enc.wf("\n") + } +} + +func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { + if len(key) == 0 { + encPanic(errNoKey) + } + panicIfInvalidKey(key) + enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) + enc.eElement(val) + enc.newline() +} + +func (enc *Encoder) wf(format string, v ...interface{}) { + if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { + encPanic(err) + } + enc.hasWritten = true +} + +func (enc *Encoder) indentStr(key Key) string { + return strings.Repeat(enc.Indent, len(key)-1) +} + +func encPanic(err error) { + panic(tomlEncodeError{err}) +} + +func eindirect(v reflect.Value) reflect.Value { + switch v.Kind() { + case reflect.Ptr, reflect.Interface: + return eindirect(v.Elem()) + default: + return v + } +} + +func isNil(rv reflect.Value) bool { + switch rv.Kind() { + case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return rv.IsNil() + default: + return false + } +} + +func panicIfInvalidKey(key Key) { + for _, k := range key { + if len(k) == 0 { + encPanic(e("Key '%s' is not a valid table name. Key names "+ + "cannot be empty.", key.maybeQuotedAll())) + } + } +} + +func isValidKeyName(s string) bool { + return len(s) != 0 +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/encode_test.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/encode_test.go new file mode 100644 index 00000000000..445ca8e2110 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/encode_test.go @@ -0,0 +1,542 @@ +package toml + +import ( + "bytes" + "fmt" + "log" + "net" + "testing" + "time" +) + +func TestEncodeRoundTrip(t *testing.T) { + type Config struct { + Age int + Cats []string + Pi float64 + Perfection []int + DOB time.Time + Ipaddress net.IP + } + + var inputs = Config{ + 13, + []string{"one", "two", "three"}, + 3.145, + []int{11, 2, 3, 4}, + time.Now(), + net.ParseIP("192.168.59.254"), + } + + var firstBuffer bytes.Buffer + e := NewEncoder(&firstBuffer) + err := e.Encode(inputs) + if err != nil { + t.Fatal(err) + } + var outputs Config + if _, err := Decode(firstBuffer.String(), &outputs); err != nil { + log.Printf("Could not decode:\n-----\n%s\n-----\n", + firstBuffer.String()) + t.Fatal(err) + } + + // could test each value individually, but I'm lazy + var secondBuffer bytes.Buffer + e2 := NewEncoder(&secondBuffer) + err = e2.Encode(outputs) + if err != nil { + t.Fatal(err) + } + if firstBuffer.String() != secondBuffer.String() { + t.Error( + firstBuffer.String(), + "\n\n is not identical to\n\n", + secondBuffer.String()) + } +} + +// XXX(burntsushi) +// I think these tests probably should be removed. They are good, but they +// ought to be obsolete by toml-test. +func TestEncode(t *testing.T) { + type Embedded struct { + Int int `toml:"_int"` + } + type NonStruct int + + date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600)) + dateStr := "2014-05-11T19:30:40Z" + + tests := map[string]struct { + input interface{} + wantOutput string + wantError error + }{ + "bool field": { + input: struct { + BoolTrue bool + BoolFalse bool + }{true, false}, + wantOutput: "BoolTrue = true\nBoolFalse = false\n", + }, + "int fields": { + input: struct { + Int int + Int8 int8 + Int16 int16 + Int32 int32 + Int64 int64 + }{1, 2, 3, 4, 5}, + wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", + }, + "uint fields": { + input: struct { + Uint uint + Uint8 uint8 + Uint16 uint16 + Uint32 uint32 + Uint64 uint64 + }{1, 2, 3, 4, 5}, + wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + + "\nUint64 = 5\n", + }, + "float fields": { + input: struct { + Float32 float32 + Float64 float64 + }{1.5, 2.5}, + wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", + }, + "string field": { + input: struct{ String string }{"foo"}, + wantOutput: "String = \"foo\"\n", + }, + "string field and unexported field": { + input: struct { + String string + unexported int + }{"foo", 0}, + wantOutput: "String = \"foo\"\n", + }, + "datetime field in UTC": { + input: struct{ Date time.Time }{date}, + wantOutput: fmt.Sprintf("Date = %s\n", dateStr), + }, + "datetime field as primitive": { + // Using a map here to fail if isStructOrMap() returns true for + // time.Time. + input: map[string]interface{}{ + "Date": date, + "Int": 1, + }, + wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), + }, + "array fields": { + input: struct { + IntArray0 [0]int + IntArray3 [3]int + }{[0]int{}, [3]int{1, 2, 3}}, + wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", + }, + "slice fields": { + input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ + nil, []int{}, []int{1, 2, 3}, + }, + wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", + }, + "datetime slices": { + input: struct{ DatetimeSlice []time.Time }{ + []time.Time{date, date}, + }, + wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", + dateStr, dateStr), + }, + "nested arrays and slices": { + input: struct { + SliceOfArrays [][2]int + ArrayOfSlices [2][]int + SliceOfArraysOfSlices [][2][]int + ArrayOfSlicesOfArrays [2][][2]int + SliceOfMixedArrays [][2]interface{} + ArrayOfMixedSlices [2][]interface{} + }{ + [][2]int{{1, 2}, {3, 4}}, + [2][]int{{1, 2}, {3, 4}}, + [][2][]int{ + { + {1, 2}, {3, 4}, + }, + { + {5, 6}, {7, 8}, + }, + }, + [2][][2]int{ + { + {1, 2}, {3, 4}, + }, + { + {5, 6}, {7, 8}, + }, + }, + [][2]interface{}{ + {1, 2}, {"a", "b"}, + }, + [2][]interface{}{ + {1, 2}, {"a", "b"}, + }, + }, + wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] +ArrayOfSlices = [[1, 2], [3, 4]] +SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] +SliceOfMixedArrays = [[1, 2], ["a", "b"]] +ArrayOfMixedSlices = [[1, 2], ["a", "b"]] +`, + }, + "empty slice": { + input: struct{ Empty []interface{} }{[]interface{}{}}, + wantOutput: "Empty = []\n", + }, + "(error) slice with element type mismatch (string and integer)": { + input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, + wantError: errArrayMixedElementTypes, + }, + "(error) slice with element type mismatch (integer and float)": { + input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, + wantError: errArrayMixedElementTypes, + }, + "slice with elems of differing Go types, same TOML types": { + input: struct { + MixedInts []interface{} + MixedFloats []interface{} + }{ + []interface{}{ + int(1), int8(2), int16(3), int32(4), int64(5), + uint(1), uint8(2), uint16(3), uint32(4), uint64(5), + }, + []interface{}{float32(1.5), float64(2.5)}, + }, + wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + + "MixedFloats = [1.5, 2.5]\n", + }, + "(error) slice w/ element type mismatch (one is nested array)": { + input: struct{ Mixed []interface{} }{ + []interface{}{1, []interface{}{2}}, + }, + wantError: errArrayMixedElementTypes, + }, + "(error) slice with 1 nil element": { + input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, + wantError: errArrayNilElement, + }, + "(error) slice with 1 nil element (and other non-nil elements)": { + input: struct{ NilElement []interface{} }{ + []interface{}{1, nil}, + }, + wantError: errArrayNilElement, + }, + "simple map": { + input: map[string]int{"a": 1, "b": 2}, + wantOutput: "a = 1\nb = 2\n", + }, + "map with interface{} value type": { + input: map[string]interface{}{"a": 1, "b": "c"}, + wantOutput: "a = 1\nb = \"c\"\n", + }, + "map with interface{} value type, some of which are structs": { + input: map[string]interface{}{ + "a": struct{ Int int }{2}, + "b": 1, + }, + wantOutput: "b = 1\n\n[a]\n Int = 2\n", + }, + "nested map": { + input: map[string]map[string]int{ + "a": {"b": 1}, + "c": {"d": 2}, + }, + wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", + }, + "nested struct": { + input: struct{ Struct struct{ Int int } }{ + struct{ Int int }{1}, + }, + wantOutput: "[Struct]\n Int = 1\n", + }, + "nested struct and non-struct field": { + input: struct { + Struct struct{ Int int } + Bool bool + }{struct{ Int int }{1}, true}, + wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", + }, + "2 nested structs": { + input: struct{ Struct1, Struct2 struct{ Int int } }{ + struct{ Int int }{1}, struct{ Int int }{2}, + }, + wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", + }, + "deeply nested structs": { + input: struct { + Struct1, Struct2 struct{ Struct3 *struct{ Int int } } + }{ + struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, + struct{ Struct3 *struct{ Int int } }{nil}, + }, + wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + + "\n\n[Struct2]\n", + }, + "nested struct with nil struct elem": { + input: struct { + Struct struct{ Inner *struct{ Int int } } + }{ + struct{ Inner *struct{ Int int } }{nil}, + }, + wantOutput: "[Struct]\n", + }, + "nested struct with no fields": { + input: struct { + Struct struct{ Inner struct{} } + }{ + struct{ Inner struct{} }{struct{}{}}, + }, + wantOutput: "[Struct]\n [Struct.Inner]\n", + }, + "struct with tags": { + input: struct { + Struct struct { + Int int `toml:"_int"` + } `toml:"_struct"` + Bool bool `toml:"_bool"` + }{ + struct { + Int int `toml:"_int"` + }{1}, true, + }, + wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", + }, + "embedded struct": { + input: struct{ Embedded }{Embedded{1}}, + wantOutput: "_int = 1\n", + }, + "embedded *struct": { + input: struct{ *Embedded }{&Embedded{1}}, + wantOutput: "_int = 1\n", + }, + "nested embedded struct": { + input: struct { + Struct struct{ Embedded } `toml:"_struct"` + }{struct{ Embedded }{Embedded{1}}}, + wantOutput: "[_struct]\n _int = 1\n", + }, + "nested embedded *struct": { + input: struct { + Struct struct{ *Embedded } `toml:"_struct"` + }{struct{ *Embedded }{&Embedded{1}}}, + wantOutput: "[_struct]\n _int = 1\n", + }, + "array of tables": { + input: struct { + Structs []*struct{ Int int } `toml:"struct"` + }{ + []*struct{ Int int }{{1}, {3}}, + }, + wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", + }, + "array of tables order": { + input: map[string]interface{}{ + "map": map[string]interface{}{ + "zero": 5, + "arr": []map[string]int{ + map[string]int{ + "friend": 5, + }, + }, + }, + }, + wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", + }, + "(error) top-level slice": { + input: []struct{ Int int }{{1}, {2}, {3}}, + wantError: errNoKey, + }, + "(error) slice of slice": { + input: struct { + Slices [][]struct{ Int int } + }{ + [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, + }, + wantError: errArrayNoTable, + }, + "(error) map no string key": { + input: map[int]string{1: ""}, + wantError: errNonString, + }, + "(error) anonymous non-struct": { + input: struct{ NonStruct }{5}, + wantError: errAnonNonStruct, + }, + "(error) empty key name": { + input: map[string]int{"": 1}, + wantError: errAnything, + }, + "(error) empty map name": { + input: map[string]interface{}{ + "": map[string]int{"v": 1}, + }, + wantError: errAnything, + }, + } + for label, test := range tests { + encodeExpected(t, label, test.input, test.wantOutput, test.wantError) + } +} + +func TestEncodeNestedTableArrays(t *testing.T) { + type song struct { + Name string `toml:"name"` + } + type album struct { + Name string `toml:"name"` + Songs []song `toml:"songs"` + } + type springsteen struct { + Albums []album `toml:"albums"` + } + value := springsteen{ + []album{ + {"Born to Run", + []song{{"Jungleland"}, {"Meeting Across the River"}}}, + {"Born in the USA", + []song{{"Glory Days"}, {"Dancing in the Dark"}}}, + }, + } + expected := `[[albums]] + name = "Born to Run" + + [[albums.songs]] + name = "Jungleland" + + [[albums.songs]] + name = "Meeting Across the River" + +[[albums]] + name = "Born in the USA" + + [[albums.songs]] + name = "Glory Days" + + [[albums.songs]] + name = "Dancing in the Dark" +` + encodeExpected(t, "nested table arrays", value, expected, nil) +} + +func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) { + type Alpha struct { + V int + } + type Beta struct { + V int + } + type Conf struct { + V int + A Alpha + B []Beta + } + + val := Conf{ + V: 1, + A: Alpha{2}, + B: []Beta{{3}}, + } + expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n" + encodeExpected(t, "array hash with normal hash order", val, expected, nil) +} + +func TestEncodeWithOmitEmpty(t *testing.T) { + type simple struct { + User string `toml:"user"` + Pass string `toml:"password,omitempty"` + } + + value := simple{"Testing", ""} + expected := fmt.Sprintf("user = %q\n", value.User) + encodeExpected(t, "simple with omitempty, is empty", value, expected, nil) + value.Pass = "some password" + expected = fmt.Sprintf("user = %q\npassword = %q\n", value.User, value.Pass) + encodeExpected(t, "simple with omitempty, not empty", value, expected, nil) +} + +func TestEncodeWithOmitZero(t *testing.T) { + type simple struct { + Number int `toml:"number,omitzero"` + Real float64 `toml:"real,omitzero"` + Unsigned uint `toml:"unsigned,omitzero"` + } + + value := simple{0, 0.0, uint(0)} + expected := "" + + encodeExpected(t, "simple with omitzero, all zero", value, expected, nil) + + value.Number = 10 + value.Real = 20 + value.Unsigned = 5 + expected = `number = 10 +real = 20.0 +unsigned = 5 +` + encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil) +} + +func encodeExpected( + t *testing.T, label string, val interface{}, wantStr string, wantErr error, +) { + var buf bytes.Buffer + enc := NewEncoder(&buf) + err := enc.Encode(val) + if err != wantErr { + if wantErr != nil { + if wantErr == errAnything && err != nil { + return + } + t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err) + } else { + t.Errorf("%s: Encode failed: %s", label, err) + } + } + if err != nil { + return + } + if got := buf.String(); wantStr != got { + t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n", + label, wantStr, got) + } +} + +func ExampleEncoder_Encode() { + date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC") + var config = map[string]interface{}{ + "date": date, + "counts": []int{1, 1, 2, 3, 5, 8}, + "hash": map[string]string{ + "key1": "val1", + "key2": "val2", + }, + } + buf := new(bytes.Buffer) + if err := NewEncoder(buf).Encode(config); err != nil { + log.Fatal(err) + } + fmt.Println(buf.String()) + + // Output: + // counts = [1, 1, 2, 3, 5, 8] + // date = 2010-03-14T18:00:00Z + // + // [hash] + // key1 = "val1" + // key2 = "val2" +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types.go new file mode 100644 index 00000000000..d36e1dd6002 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types.go @@ -0,0 +1,19 @@ +// +build go1.2 + +package toml + +// In order to support Go 1.1, we define our own TextMarshaler and +// TextUnmarshaler types. For Go 1.2+, we just alias them with the +// standard library interfaces. + +import ( + "encoding" +) + +// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here +// so that Go 1.1 can be supported. +type TextMarshaler encoding.TextMarshaler + +// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined +// here so that Go 1.1 can be supported. +type TextUnmarshaler encoding.TextUnmarshaler diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types_1.1.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types_1.1.go new file mode 100644 index 00000000000..e8d503d0469 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/encoding_types_1.1.go @@ -0,0 +1,18 @@ +// +build !go1.2 + +package toml + +// These interfaces were introduced in Go 1.2, so we add them manually when +// compiling for Go 1.1. + +// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here +// so that Go 1.1 can be supported. +type TextMarshaler interface { + MarshalText() (text []byte, err error) +} + +// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined +// here so that Go 1.1 can be supported. +type TextUnmarshaler interface { + UnmarshalText(text []byte) error +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/lex.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/lex.go new file mode 100644 index 00000000000..219122857e3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/lex.go @@ -0,0 +1,874 @@ +package toml + +import ( + "fmt" + "strings" + "unicode/utf8" +) + +type itemType int + +const ( + itemError itemType = iota + itemNIL // used in the parser to indicate no type + itemEOF + itemText + itemString + itemRawString + itemMultilineString + itemRawMultilineString + itemBool + itemInteger + itemFloat + itemDatetime + itemArray // the start of an array + itemArrayEnd + itemTableStart + itemTableEnd + itemArrayTableStart + itemArrayTableEnd + itemKeyStart + itemCommentStart +) + +const ( + eof = 0 + tableStart = '[' + tableEnd = ']' + arrayTableStart = '[' + arrayTableEnd = ']' + tableSep = '.' + keySep = '=' + arrayStart = '[' + arrayEnd = ']' + arrayValTerm = ',' + commentStart = '#' + stringStart = '"' + stringEnd = '"' + rawStringStart = '\'' + rawStringEnd = '\'' +) + +type stateFn func(lx *lexer) stateFn + +type lexer struct { + input string + start int + pos int + width int + line int + state stateFn + items chan item + + // A stack of state functions used to maintain context. + // The idea is to reuse parts of the state machine in various places. + // For example, values can appear at the top level or within arbitrarily + // nested arrays. The last state on the stack is used after a value has + // been lexed. Similarly for comments. + stack []stateFn +} + +type item struct { + typ itemType + val string + line int +} + +func (lx *lexer) nextItem() item { + for { + select { + case item := <-lx.items: + return item + default: + lx.state = lx.state(lx) + } + } +} + +func lex(input string) *lexer { + lx := &lexer{ + input: input + "\n", + state: lexTop, + line: 1, + items: make(chan item, 10), + stack: make([]stateFn, 0, 10), + } + return lx +} + +func (lx *lexer) push(state stateFn) { + lx.stack = append(lx.stack, state) +} + +func (lx *lexer) pop() stateFn { + if len(lx.stack) == 0 { + return lx.errorf("BUG in lexer: no states to pop.") + } + last := lx.stack[len(lx.stack)-1] + lx.stack = lx.stack[0 : len(lx.stack)-1] + return last +} + +func (lx *lexer) current() string { + return lx.input[lx.start:lx.pos] +} + +func (lx *lexer) emit(typ itemType) { + lx.items <- item{typ, lx.current(), lx.line} + lx.start = lx.pos +} + +func (lx *lexer) emitTrim(typ itemType) { + lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line} + lx.start = lx.pos +} + +func (lx *lexer) next() (r rune) { + if lx.pos >= len(lx.input) { + lx.width = 0 + return eof + } + + if lx.input[lx.pos] == '\n' { + lx.line++ + } + r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) + lx.pos += lx.width + return r +} + +// ignore skips over the pending input before this point. +func (lx *lexer) ignore() { + lx.start = lx.pos +} + +// backup steps back one rune. Can be called only once per call of next. +func (lx *lexer) backup() { + lx.pos -= lx.width + if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { + lx.line-- + } +} + +// accept consumes the next rune if it's equal to `valid`. +func (lx *lexer) accept(valid rune) bool { + if lx.next() == valid { + return true + } + lx.backup() + return false +} + +// peek returns but does not consume the next rune in the input. +func (lx *lexer) peek() rune { + r := lx.next() + lx.backup() + return r +} + +// errorf stops all lexing by emitting an error and returning `nil`. +// Note that any value that is a character is escaped if it's a special +// character (new lines, tabs, etc.). +func (lx *lexer) errorf(format string, values ...interface{}) stateFn { + lx.items <- item{ + itemError, + fmt.Sprintf(format, values...), + lx.line, + } + return nil +} + +// lexTop consumes elements at the top level of TOML data. +func lexTop(lx *lexer) stateFn { + r := lx.next() + if isWhitespace(r) || isNL(r) { + return lexSkip(lx, lexTop) + } + + switch r { + case commentStart: + lx.push(lexTop) + return lexCommentStart + case tableStart: + return lexTableStart + case eof: + if lx.pos > lx.start { + return lx.errorf("Unexpected EOF.") + } + lx.emit(itemEOF) + return nil + } + + // At this point, the only valid item can be a key, so we back up + // and let the key lexer do the rest. + lx.backup() + lx.push(lexTopEnd) + return lexKeyStart +} + +// lexTopEnd is entered whenever a top-level item has been consumed. (A value +// or a table.) It must see only whitespace, and will turn back to lexTop +// upon a new line. If it sees EOF, it will quit the lexer successfully. +func lexTopEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case r == commentStart: + // a comment will read to a new line for us. + lx.push(lexTop) + return lexCommentStart + case isWhitespace(r): + return lexTopEnd + case isNL(r): + lx.ignore() + return lexTop + case r == eof: + lx.ignore() + return lexTop + } + return lx.errorf("Expected a top-level item to end with a new line, "+ + "comment or EOF, but got %q instead.", r) +} + +// lexTable lexes the beginning of a table. Namely, it makes sure that +// it starts with a character other than '.' and ']'. +// It assumes that '[' has already been consumed. +// It also handles the case that this is an item in an array of tables. +// e.g., '[[name]]'. +func lexTableStart(lx *lexer) stateFn { + if lx.peek() == arrayTableStart { + lx.next() + lx.emit(itemArrayTableStart) + lx.push(lexArrayTableEnd) + } else { + lx.emit(itemTableStart) + lx.push(lexTableEnd) + } + return lexTableNameStart +} + +func lexTableEnd(lx *lexer) stateFn { + lx.emit(itemTableEnd) + return lexTopEnd +} + +func lexArrayTableEnd(lx *lexer) stateFn { + if r := lx.next(); r != arrayTableEnd { + return lx.errorf("Expected end of table array name delimiter %q, "+ + "but got %q instead.", arrayTableEnd, r) + } + lx.emit(itemArrayTableEnd) + return lexTopEnd +} + +func lexTableNameStart(lx *lexer) stateFn { + switch r := lx.peek(); { + case r == tableEnd || r == eof: + return lx.errorf("Unexpected end of table name. (Table names cannot " + + "be empty.)") + case r == tableSep: + return lx.errorf("Unexpected table separator. (Table names cannot " + + "be empty.)") + case r == stringStart || r == rawStringStart: + lx.ignore() + lx.push(lexTableNameEnd) + return lexValue // reuse string lexing + case isWhitespace(r): + return lexTableNameStart + default: + return lexBareTableName + } +} + +// lexTableName lexes the name of a table. It assumes that at least one +// valid character for the table has already been read. +func lexBareTableName(lx *lexer) stateFn { + switch r := lx.next(); { + case isBareKeyChar(r): + return lexBareTableName + case r == tableSep || r == tableEnd: + lx.backup() + lx.emitTrim(itemText) + return lexTableNameEnd + default: + return lx.errorf("Bare keys cannot contain %q.", r) + } +} + +// lexTableNameEnd reads the end of a piece of a table name, optionally +// consuming whitespace. +func lexTableNameEnd(lx *lexer) stateFn { + switch r := lx.next(); { + case isWhitespace(r): + return lexTableNameEnd + case r == tableSep: + lx.ignore() + return lexTableNameStart + case r == tableEnd: + return lx.pop() + default: + return lx.errorf("Expected '.' or ']' to end table name, but got %q "+ + "instead.", r) + } +} + +// lexKeyStart consumes a key name up until the first non-whitespace character. +// lexKeyStart will ignore whitespace. +func lexKeyStart(lx *lexer) stateFn { + r := lx.peek() + switch { + case r == keySep: + return lx.errorf("Unexpected key separator %q.", keySep) + case isWhitespace(r) || isNL(r): + lx.next() + return lexSkip(lx, lexKeyStart) + case r == stringStart || r == rawStringStart: + lx.ignore() + lx.emit(itemKeyStart) + lx.push(lexKeyEnd) + return lexValue // reuse string lexing + default: + lx.ignore() + lx.emit(itemKeyStart) + return lexBareKey + } +} + +// lexBareKey consumes the text of a bare key. Assumes that the first character +// (which is not whitespace) has not yet been consumed. +func lexBareKey(lx *lexer) stateFn { + switch r := lx.next(); { + case isBareKeyChar(r): + return lexBareKey + case isWhitespace(r): + lx.emitTrim(itemText) + return lexKeyEnd + case r == keySep: + lx.backup() + lx.emitTrim(itemText) + return lexKeyEnd + default: + return lx.errorf("Bare keys cannot contain %q.", r) + } +} + +// lexKeyEnd consumes the end of a key and trims whitespace (up to the key +// separator). +func lexKeyEnd(lx *lexer) stateFn { + switch r := lx.next(); { + case r == keySep: + return lexSkip(lx, lexValue) + case isWhitespace(r): + return lexSkip(lx, lexKeyEnd) + default: + return lx.errorf("Expected key separator %q, but got %q instead.", + keySep, r) + } +} + +// lexValue starts the consumption of a value anywhere a value is expected. +// lexValue will ignore whitespace. +// After a value is lexed, the last state on the next is popped and returned. +func lexValue(lx *lexer) stateFn { + // We allow whitespace to precede a value, but NOT new lines. + // In array syntax, the array states are responsible for ignoring new + // lines. + r := lx.next() + if isWhitespace(r) { + return lexSkip(lx, lexValue) + } + + switch { + case r == arrayStart: + lx.ignore() + lx.emit(itemArray) + return lexArrayValue + case r == stringStart: + if lx.accept(stringStart) { + if lx.accept(stringStart) { + lx.ignore() // Ignore """ + return lexMultilineString + } + lx.backup() + } + lx.ignore() // ignore the '"' + return lexString + case r == rawStringStart: + if lx.accept(rawStringStart) { + if lx.accept(rawStringStart) { + lx.ignore() // Ignore """ + return lexMultilineRawString + } + lx.backup() + } + lx.ignore() // ignore the "'" + return lexRawString + case r == 't': + return lexTrue + case r == 'f': + return lexFalse + case r == '-': + return lexNumberStart + case isDigit(r): + lx.backup() // avoid an extra state and use the same as above + return lexNumberOrDateStart + case r == '.': // special error case, be kind to users + return lx.errorf("Floats must start with a digit, not '.'.") + } + return lx.errorf("Expected value but found %q instead.", r) +} + +// lexArrayValue consumes one value in an array. It assumes that '[' or ',' +// have already been consumed. All whitespace and new lines are ignored. +func lexArrayValue(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValue) + case r == commentStart: + lx.push(lexArrayValue) + return lexCommentStart + case r == arrayValTerm: + return lx.errorf("Unexpected array value terminator %q.", + arrayValTerm) + case r == arrayEnd: + return lexArrayEnd + } + + lx.backup() + lx.push(lexArrayValueEnd) + return lexValue +} + +// lexArrayValueEnd consumes the cruft between values of an array. Namely, +// it ignores whitespace and expects either a ',' or a ']'. +func lexArrayValueEnd(lx *lexer) stateFn { + r := lx.next() + switch { + case isWhitespace(r) || isNL(r): + return lexSkip(lx, lexArrayValueEnd) + case r == commentStart: + lx.push(lexArrayValueEnd) + return lexCommentStart + case r == arrayValTerm: + lx.ignore() + return lexArrayValue // move on to the next value + case r == arrayEnd: + return lexArrayEnd + } + return lx.errorf("Expected an array value terminator %q or an array "+ + "terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) +} + +// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has +// just been consumed. +func lexArrayEnd(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemArrayEnd) + return lx.pop() +} + +// lexString consumes the inner contents of a string. It assumes that the +// beginning '"' has already been consumed and ignored. +func lexString(lx *lexer) stateFn { + r := lx.next() + switch { + case isNL(r): + return lx.errorf("Strings cannot contain new lines.") + case r == '\\': + lx.push(lexString) + return lexStringEscape + case r == stringEnd: + lx.backup() + lx.emit(itemString) + lx.next() + lx.ignore() + return lx.pop() + } + return lexString +} + +// lexMultilineString consumes the inner contents of a string. It assumes that +// the beginning '"""' has already been consumed and ignored. +func lexMultilineString(lx *lexer) stateFn { + r := lx.next() + switch { + case r == '\\': + return lexMultilineStringEscape + case r == stringEnd: + if lx.accept(stringEnd) { + if lx.accept(stringEnd) { + lx.backup() + lx.backup() + lx.backup() + lx.emit(itemMultilineString) + lx.next() + lx.next() + lx.next() + lx.ignore() + return lx.pop() + } + lx.backup() + } + } + return lexMultilineString +} + +// lexRawString consumes a raw string. Nothing can be escaped in such a string. +// It assumes that the beginning "'" has already been consumed and ignored. +func lexRawString(lx *lexer) stateFn { + r := lx.next() + switch { + case isNL(r): + return lx.errorf("Strings cannot contain new lines.") + case r == rawStringEnd: + lx.backup() + lx.emit(itemRawString) + lx.next() + lx.ignore() + return lx.pop() + } + return lexRawString +} + +// lexMultilineRawString consumes a raw string. Nothing can be escaped in such +// a string. It assumes that the beginning "'" has already been consumed and +// ignored. +func lexMultilineRawString(lx *lexer) stateFn { + r := lx.next() + switch { + case r == rawStringEnd: + if lx.accept(rawStringEnd) { + if lx.accept(rawStringEnd) { + lx.backup() + lx.backup() + lx.backup() + lx.emit(itemRawMultilineString) + lx.next() + lx.next() + lx.next() + lx.ignore() + return lx.pop() + } + lx.backup() + } + } + return lexMultilineRawString +} + +// lexMultilineStringEscape consumes an escaped character. It assumes that the +// preceding '\\' has already been consumed. +func lexMultilineStringEscape(lx *lexer) stateFn { + // Handle the special case first: + if isNL(lx.next()) { + lx.next() + return lexMultilineString + } else { + lx.backup() + lx.push(lexMultilineString) + return lexStringEscape(lx) + } +} + +func lexStringEscape(lx *lexer) stateFn { + r := lx.next() + switch r { + case 'b': + fallthrough + case 't': + fallthrough + case 'n': + fallthrough + case 'f': + fallthrough + case 'r': + fallthrough + case '"': + fallthrough + case '\\': + return lx.pop() + case 'u': + return lexShortUnicodeEscape + case 'U': + return lexLongUnicodeEscape + } + return lx.errorf("Invalid escape character %q. Only the following "+ + "escape characters are allowed: "+ + "\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+ + "\\uXXXX and \\UXXXXXXXX.", r) +} + +func lexShortUnicodeEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 4; i++ { + r = lx.next() + if !isHexadecimal(r) { + return lx.errorf("Expected four hexadecimal digits after '\\u', "+ + "but got '%s' instead.", lx.current()) + } + } + return lx.pop() +} + +func lexLongUnicodeEscape(lx *lexer) stateFn { + var r rune + for i := 0; i < 8; i++ { + r = lx.next() + if !isHexadecimal(r) { + return lx.errorf("Expected eight hexadecimal digits after '\\U', "+ + "but got '%s' instead.", lx.current()) + } + } + return lx.pop() +} + +// lexNumberOrDateStart consumes either a (positive) integer, float or +// datetime. It assumes that NO negative sign has been consumed. +func lexNumberOrDateStart(lx *lexer) stateFn { + r := lx.next() + if !isDigit(r) { + if r == '.' { + return lx.errorf("Floats must start with a digit, not '.'.") + } else { + return lx.errorf("Expected a digit but got %q.", r) + } + } + return lexNumberOrDate +} + +// lexNumberOrDate consumes either a (positive) integer, float or datetime. +func lexNumberOrDate(lx *lexer) stateFn { + r := lx.next() + switch { + case r == '-': + if lx.pos-lx.start != 5 { + return lx.errorf("All ISO8601 dates must be in full Zulu form.") + } + return lexDateAfterYear + case isDigit(r): + return lexNumberOrDate + case r == '.': + return lexFloatStart + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format. +// It assumes that "YYYY-" has already been consumed. +func lexDateAfterYear(lx *lexer) stateFn { + formats := []rune{ + // digits are '0'. + // everything else is direct equality. + '0', '0', '-', '0', '0', + 'T', + '0', '0', ':', '0', '0', ':', '0', '0', + 'Z', + } + for _, f := range formats { + r := lx.next() + if f == '0' { + if !isDigit(r) { + return lx.errorf("Expected digit in ISO8601 datetime, "+ + "but found %q instead.", r) + } + } else if f != r { + return lx.errorf("Expected %q in ISO8601 datetime, "+ + "but found %q instead.", f, r) + } + } + lx.emit(itemDatetime) + return lx.pop() +} + +// lexNumberStart consumes either an integer or a float. It assumes that +// a negative sign has already been read, but that *no* digits have been +// consumed. lexNumberStart will move to the appropriate integer or float +// states. +func lexNumberStart(lx *lexer) stateFn { + // we MUST see a digit. Even floats have to start with a digit. + r := lx.next() + if !isDigit(r) { + if r == '.' { + return lx.errorf("Floats must start with a digit, not '.'.") + } else { + return lx.errorf("Expected a digit but got %q.", r) + } + } + return lexNumber +} + +// lexNumber consumes an integer or a float after seeing the first digit. +func lexNumber(lx *lexer) stateFn { + r := lx.next() + switch { + case isDigit(r): + return lexNumber + case r == '.': + return lexFloatStart + } + + lx.backup() + lx.emit(itemInteger) + return lx.pop() +} + +// lexFloatStart starts the consumption of digits of a float after a '.'. +// Namely, at least one digit is required. +func lexFloatStart(lx *lexer) stateFn { + r := lx.next() + if !isDigit(r) { + return lx.errorf("Floats must have a digit after the '.', but got "+ + "%q instead.", r) + } + return lexFloat +} + +// lexFloat consumes the digits of a float after a '.'. +// Assumes that one digit has been consumed after a '.' already. +func lexFloat(lx *lexer) stateFn { + r := lx.next() + if isDigit(r) { + return lexFloat + } + + lx.backup() + lx.emit(itemFloat) + return lx.pop() +} + +// lexConst consumes the s[1:] in s. It assumes that s[0] has already been +// consumed. +func lexConst(lx *lexer, s string) stateFn { + for i := range s[1:] { + if r := lx.next(); r != rune(s[i+1]) { + return lx.errorf("Expected %q, but found %q instead.", s[:i+1], + s[:i]+string(r)) + } + } + return nil +} + +// lexTrue consumes the "rue" in "true". It assumes that 't' has already +// been consumed. +func lexTrue(lx *lexer) stateFn { + if fn := lexConst(lx, "true"); fn != nil { + return fn + } + lx.emit(itemBool) + return lx.pop() +} + +// lexFalse consumes the "alse" in "false". It assumes that 'f' has already +// been consumed. +func lexFalse(lx *lexer) stateFn { + if fn := lexConst(lx, "false"); fn != nil { + return fn + } + lx.emit(itemBool) + return lx.pop() +} + +// lexCommentStart begins the lexing of a comment. It will emit +// itemCommentStart and consume no characters, passing control to lexComment. +func lexCommentStart(lx *lexer) stateFn { + lx.ignore() + lx.emit(itemCommentStart) + return lexComment +} + +// lexComment lexes an entire comment. It assumes that '#' has been consumed. +// It will consume *up to* the first new line character, and pass control +// back to the last state on the stack. +func lexComment(lx *lexer) stateFn { + r := lx.peek() + if isNL(r) || r == eof { + lx.emit(itemText) + return lx.pop() + } + lx.next() + return lexComment +} + +// lexSkip ignores all slurped input and moves on to the next state. +func lexSkip(lx *lexer, nextState stateFn) stateFn { + return func(lx *lexer) stateFn { + lx.ignore() + return nextState + } +} + +// isWhitespace returns true if `r` is a whitespace character according +// to the spec. +func isWhitespace(r rune) bool { + return r == '\t' || r == ' ' +} + +func isNL(r rune) bool { + return r == '\n' || r == '\r' +} + +func isDigit(r rune) bool { + return r >= '0' && r <= '9' +} + +func isHexadecimal(r rune) bool { + return (r >= '0' && r <= '9') || + (r >= 'a' && r <= 'f') || + (r >= 'A' && r <= 'F') +} + +func isBareKeyChar(r rune) bool { + return (r >= 'A' && r <= 'Z') || + (r >= 'a' && r <= 'z') || + (r >= '0' && r <= '9') || + r == '_' || + r == '-' +} + +func (itype itemType) String() string { + switch itype { + case itemError: + return "Error" + case itemNIL: + return "NIL" + case itemEOF: + return "EOF" + case itemText: + return "Text" + case itemString: + return "String" + case itemRawString: + return "String" + case itemMultilineString: + return "String" + case itemRawMultilineString: + return "String" + case itemBool: + return "Bool" + case itemInteger: + return "Integer" + case itemFloat: + return "Float" + case itemDatetime: + return "DateTime" + case itemTableStart: + return "TableStart" + case itemTableEnd: + return "TableEnd" + case itemKeyStart: + return "KeyStart" + case itemArray: + return "Array" + case itemArrayEnd: + return "ArrayEnd" + case itemCommentStart: + return "CommentStart" + } + panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) +} + +func (item item) String() string { + return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val) +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/parse.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/parse.go new file mode 100644 index 00000000000..c6069be1f17 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/parse.go @@ -0,0 +1,498 @@ +package toml + +import ( + "fmt" + "log" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +type parser struct { + mapping map[string]interface{} + types map[string]tomlType + lx *lexer + + // A list of keys in the order that they appear in the TOML data. + ordered []Key + + // the full key for the current hash in scope + context Key + + // the base key name for everything except hashes + currentKey string + + // rough approximation of line number + approxLine int + + // A map of 'key.group.names' to whether they were created implicitly. + implicits map[string]bool +} + +type parseError string + +func (pe parseError) Error() string { + return string(pe) +} + +func parse(data string) (p *parser, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + if err, ok = r.(parseError); ok { + return + } + panic(r) + } + }() + + p = &parser{ + mapping: make(map[string]interface{}), + types: make(map[string]tomlType), + lx: lex(data), + ordered: make([]Key, 0), + implicits: make(map[string]bool), + } + for { + item := p.next() + if item.typ == itemEOF { + break + } + p.topLevel(item) + } + + return p, nil +} + +func (p *parser) panicf(format string, v ...interface{}) { + msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", + p.approxLine, p.current(), fmt.Sprintf(format, v...)) + panic(parseError(msg)) +} + +func (p *parser) next() item { + it := p.lx.nextItem() + if it.typ == itemError { + p.panicf("%s", it.val) + } + return it +} + +func (p *parser) bug(format string, v ...interface{}) { + log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...)) +} + +func (p *parser) expect(typ itemType) item { + it := p.next() + p.assertEqual(typ, it.typ) + return it +} + +func (p *parser) assertEqual(expected, got itemType) { + if expected != got { + p.bug("Expected '%s' but got '%s'.", expected, got) + } +} + +func (p *parser) topLevel(item item) { + switch item.typ { + case itemCommentStart: + p.approxLine = item.line + p.expect(itemText) + case itemTableStart: + kg := p.next() + p.approxLine = kg.line + + var key Key + for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { + key = append(key, p.keyString(kg)) + } + p.assertEqual(itemTableEnd, kg.typ) + + p.establishContext(key, false) + p.setType("", tomlHash) + p.ordered = append(p.ordered, key) + case itemArrayTableStart: + kg := p.next() + p.approxLine = kg.line + + var key Key + for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { + key = append(key, p.keyString(kg)) + } + p.assertEqual(itemArrayTableEnd, kg.typ) + + p.establishContext(key, true) + p.setType("", tomlArrayHash) + p.ordered = append(p.ordered, key) + case itemKeyStart: + kname := p.next() + p.approxLine = kname.line + p.currentKey = p.keyString(kname) + + val, typ := p.value(p.next()) + p.setValue(p.currentKey, val) + p.setType(p.currentKey, typ) + p.ordered = append(p.ordered, p.context.add(p.currentKey)) + p.currentKey = "" + default: + p.bug("Unexpected type at top level: %s", item.typ) + } +} + +// Gets a string for a key (or part of a key in a table name). +func (p *parser) keyString(it item) string { + switch it.typ { + case itemText: + return it.val + case itemString, itemMultilineString, + itemRawString, itemRawMultilineString: + s, _ := p.value(it) + return s.(string) + default: + p.bug("Unexpected key type: %s", it.typ) + panic("unreachable") + } +} + +// value translates an expected value from the lexer into a Go value wrapped +// as an empty interface. +func (p *parser) value(it item) (interface{}, tomlType) { + switch it.typ { + case itemString: + return p.replaceEscapes(it.val), p.typeOfPrimitive(it) + case itemMultilineString: + trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) + return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) + case itemRawString: + return it.val, p.typeOfPrimitive(it) + case itemRawMultilineString: + return stripFirstNewline(it.val), p.typeOfPrimitive(it) + case itemBool: + switch it.val { + case "true": + return true, p.typeOfPrimitive(it) + case "false": + return false, p.typeOfPrimitive(it) + } + p.bug("Expected boolean value, but got '%s'.", it.val) + case itemInteger: + num, err := strconv.ParseInt(it.val, 10, 64) + if err != nil { + // See comment below for floats describing why we make a + // distinction between a bug and a user error. + if e, ok := err.(*strconv.NumError); ok && + e.Err == strconv.ErrRange { + + p.panicf("Integer '%s' is out of the range of 64-bit "+ + "signed integers.", it.val) + } else { + p.bug("Expected integer value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) + case itemFloat: + num, err := strconv.ParseFloat(it.val, 64) + if err != nil { + // Distinguish float values. Normally, it'd be a bug if the lexer + // provides an invalid float, but it's possible that the float is + // out of range of valid values (which the lexer cannot determine). + // So mark the former as a bug but the latter as a legitimate user + // error. + // + // This is also true for integers. + if e, ok := err.(*strconv.NumError); ok && + e.Err == strconv.ErrRange { + + p.panicf("Float '%s' is out of the range of 64-bit "+ + "IEEE-754 floating-point numbers.", it.val) + } else { + p.bug("Expected float value, but got '%s'.", it.val) + } + } + return num, p.typeOfPrimitive(it) + case itemDatetime: + t, err := time.Parse("2006-01-02T15:04:05Z", it.val) + if err != nil { + p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val) + } + return t, p.typeOfPrimitive(it) + case itemArray: + array := make([]interface{}, 0) + types := make([]tomlType, 0) + + for it = p.next(); it.typ != itemArrayEnd; it = p.next() { + if it.typ == itemCommentStart { + p.expect(itemText) + continue + } + + val, typ := p.value(it) + array = append(array, val) + types = append(types, typ) + } + return array, p.typeOfArray(types) + } + p.bug("Unexpected value type: %s", it.typ) + panic("unreachable") +} + +// establishContext sets the current context of the parser, +// where the context is either a hash or an array of hashes. Which one is +// set depends on the value of the `array` parameter. +// +// Establishing the context also makes sure that the key isn't a duplicate, and +// will create implicit hashes automatically. +func (p *parser) establishContext(key Key, array bool) { + var ok bool + + // Always start at the top level and drill down for our context. + hashContext := p.mapping + keyContext := make(Key, 0) + + // We only need implicit hashes for key[0:-1] + for _, k := range key[0 : len(key)-1] { + _, ok = hashContext[k] + keyContext = append(keyContext, k) + + // No key? Make an implicit hash and move on. + if !ok { + p.addImplicit(keyContext) + hashContext[k] = make(map[string]interface{}) + } + + // If the hash context is actually an array of tables, then set + // the hash context to the last element in that array. + // + // Otherwise, it better be a table, since this MUST be a key group (by + // virtue of it not being the last element in a key). + switch t := hashContext[k].(type) { + case []map[string]interface{}: + hashContext = t[len(t)-1] + case map[string]interface{}: + hashContext = t + default: + p.panicf("Key '%s' was already created as a hash.", keyContext) + } + } + + p.context = keyContext + if array { + // If this is the first element for this array, then allocate a new + // list of tables for it. + k := key[len(key)-1] + if _, ok := hashContext[k]; !ok { + hashContext[k] = make([]map[string]interface{}, 0, 5) + } + + // Add a new table. But make sure the key hasn't already been used + // for something else. + if hash, ok := hashContext[k].([]map[string]interface{}); ok { + hashContext[k] = append(hash, make(map[string]interface{})) + } else { + p.panicf("Key '%s' was already created and cannot be used as "+ + "an array.", keyContext) + } + } else { + p.setValue(key[len(key)-1], make(map[string]interface{})) + } + p.context = append(p.context, key[len(key)-1]) +} + +// setValue sets the given key to the given value in the current context. +// It will make sure that the key hasn't already been defined, account for +// implicit key groups. +func (p *parser) setValue(key string, value interface{}) { + var tmpHash interface{} + var ok bool + + hash := p.mapping + keyContext := make(Key, 0) + for _, k := range p.context { + keyContext = append(keyContext, k) + if tmpHash, ok = hash[k]; !ok { + p.bug("Context for key '%s' has not been established.", keyContext) + } + switch t := tmpHash.(type) { + case []map[string]interface{}: + // The context is a table of hashes. Pick the most recent table + // defined as the current hash. + hash = t[len(t)-1] + case map[string]interface{}: + hash = t + default: + p.bug("Expected hash to have type 'map[string]interface{}', but "+ + "it has '%T' instead.", tmpHash) + } + } + keyContext = append(keyContext, key) + + if _, ok := hash[key]; ok { + // Typically, if the given key has already been set, then we have + // to raise an error since duplicate keys are disallowed. However, + // it's possible that a key was previously defined implicitly. In this + // case, it is allowed to be redefined concretely. (See the + // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) + // + // But we have to make sure to stop marking it as an implicit. (So that + // another redefinition provokes an error.) + // + // Note that since it has already been defined (as a hash), we don't + // want to overwrite it. So our business is done. + if p.isImplicit(keyContext) { + p.removeImplicit(keyContext) + return + } + + // Otherwise, we have a concrete key trying to override a previous + // key, which is *always* wrong. + p.panicf("Key '%s' has already been defined.", keyContext) + } + hash[key] = value +} + +// setType sets the type of a particular value at a given key. +// It should be called immediately AFTER setValue. +// +// Note that if `key` is empty, then the type given will be applied to the +// current context (which is either a table or an array of tables). +func (p *parser) setType(key string, typ tomlType) { + keyContext := make(Key, 0, len(p.context)+1) + for _, k := range p.context { + keyContext = append(keyContext, k) + } + if len(key) > 0 { // allow type setting for hashes + keyContext = append(keyContext, key) + } + p.types[keyContext.String()] = typ +} + +// addImplicit sets the given Key as having been created implicitly. +func (p *parser) addImplicit(key Key) { + p.implicits[key.String()] = true +} + +// removeImplicit stops tagging the given key as having been implicitly +// created. +func (p *parser) removeImplicit(key Key) { + p.implicits[key.String()] = false +} + +// isImplicit returns true if the key group pointed to by the key was created +// implicitly. +func (p *parser) isImplicit(key Key) bool { + return p.implicits[key.String()] +} + +// current returns the full key name of the current context. +func (p *parser) current() string { + if len(p.currentKey) == 0 { + return p.context.String() + } + if len(p.context) == 0 { + return p.currentKey + } + return fmt.Sprintf("%s.%s", p.context, p.currentKey) +} + +func stripFirstNewline(s string) string { + if len(s) == 0 || s[0] != '\n' { + return s + } + return s[1:len(s)] +} + +func stripEscapedWhitespace(s string) string { + esc := strings.Split(s, "\\\n") + if len(esc) > 1 { + for i := 1; i < len(esc); i++ { + esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) + } + } + return strings.Join(esc, "") +} + +func (p *parser) replaceEscapes(str string) string { + var replaced []rune + s := []byte(str) + r := 0 + for r < len(s) { + if s[r] != '\\' { + c, size := utf8.DecodeRune(s[r:]) + r += size + replaced = append(replaced, c) + continue + } + r += 1 + if r >= len(s) { + p.bug("Escape sequence at end of string.") + return "" + } + switch s[r] { + default: + p.bug("Expected valid escape code after \\, but got %q.", s[r]) + return "" + case 'b': + replaced = append(replaced, rune(0x0008)) + r += 1 + case 't': + replaced = append(replaced, rune(0x0009)) + r += 1 + case 'n': + replaced = append(replaced, rune(0x000A)) + r += 1 + case 'f': + replaced = append(replaced, rune(0x000C)) + r += 1 + case 'r': + replaced = append(replaced, rune(0x000D)) + r += 1 + case '"': + replaced = append(replaced, rune(0x0022)) + r += 1 + case '\\': + replaced = append(replaced, rune(0x005C)) + r += 1 + case 'u': + // At this point, we know we have a Unicode escape of the form + // `uXXXX` at [r, r+5). (Because the lexer guarantees this + // for us.) + escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) + replaced = append(replaced, escaped) + r += 5 + case 'U': + // At this point, we know we have a Unicode escape of the form + // `uXXXX` at [r, r+9). (Because the lexer guarantees this + // for us.) + escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) + replaced = append(replaced, escaped) + r += 9 + } + } + return string(replaced) +} + +func (p *parser) asciiEscapeToUnicode(bs []byte) rune { + s := string(bs) + hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) + if err != nil { + p.bug("Could not parse '%s' as a hexadecimal number, but the "+ + "lexer claims it's OK: %s", s, err) + } + + // BUG(burntsushi) + // I honestly don't understand how this works. I can't seem + // to find a way to make this fail. I figured this would fail on invalid + // UTF-8 characters like U+DCFF, but it doesn't. + if !utf8.ValidString(string(rune(hex))) { + p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) + } + return rune(hex) +} + +func isStringType(ty itemType) bool { + return ty == itemString || ty == itemMultilineString || + ty == itemRawString || ty == itemRawMultilineString +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/session.vim b/Godeps/_workspace/src/github.com/BurntSushi/toml/session.vim new file mode 100644 index 00000000000..562164be060 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/session.vim @@ -0,0 +1 @@ +au BufWritePost *.go silent!make tags > /dev/null 2>&1 diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/type_check.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/type_check.go new file mode 100644 index 00000000000..c73f8afc1a6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/type_check.go @@ -0,0 +1,91 @@ +package toml + +// tomlType represents any Go type that corresponds to a TOML type. +// While the first draft of the TOML spec has a simplistic type system that +// probably doesn't need this level of sophistication, we seem to be militating +// toward adding real composite types. +type tomlType interface { + typeString() string +} + +// typeEqual accepts any two types and returns true if they are equal. +func typeEqual(t1, t2 tomlType) bool { + if t1 == nil || t2 == nil { + return false + } + return t1.typeString() == t2.typeString() +} + +func typeIsHash(t tomlType) bool { + return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) +} + +type tomlBaseType string + +func (btype tomlBaseType) typeString() string { + return string(btype) +} + +func (btype tomlBaseType) String() string { + return btype.typeString() +} + +var ( + tomlInteger tomlBaseType = "Integer" + tomlFloat tomlBaseType = "Float" + tomlDatetime tomlBaseType = "Datetime" + tomlString tomlBaseType = "String" + tomlBool tomlBaseType = "Bool" + tomlArray tomlBaseType = "Array" + tomlHash tomlBaseType = "Hash" + tomlArrayHash tomlBaseType = "ArrayHash" +) + +// typeOfPrimitive returns a tomlType of any primitive value in TOML. +// Primitive values are: Integer, Float, Datetime, String and Bool. +// +// Passing a lexer item other than the following will cause a BUG message +// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. +func (p *parser) typeOfPrimitive(lexItem item) tomlType { + switch lexItem.typ { + case itemInteger: + return tomlInteger + case itemFloat: + return tomlFloat + case itemDatetime: + return tomlDatetime + case itemString: + return tomlString + case itemMultilineString: + return tomlString + case itemRawString: + return tomlString + case itemRawMultilineString: + return tomlString + case itemBool: + return tomlBool + } + p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) + panic("unreachable") +} + +// typeOfArray returns a tomlType for an array given a list of types of its +// values. +// +// In the current spec, if an array is homogeneous, then its type is always +// "Array". If the array is not homogeneous, an error is generated. +func (p *parser) typeOfArray(types []tomlType) tomlType { + // Empty arrays are cool. + if len(types) == 0 { + return tomlArray + } + + theType := types[0] + for _, t := range types[1:] { + if !typeEqual(theType, t) { + p.panicf("Array contains values of type '%s' and '%s', but "+ + "arrays must be homogeneous.", theType, t) + } + } + return tomlArray +} diff --git a/Godeps/_workspace/src/github.com/BurntSushi/toml/type_fields.go b/Godeps/_workspace/src/github.com/BurntSushi/toml/type_fields.go new file mode 100644 index 00000000000..7592f87a454 --- /dev/null +++ b/Godeps/_workspace/src/github.com/BurntSushi/toml/type_fields.go @@ -0,0 +1,241 @@ +package toml + +// Struct field handling is adapted from code in encoding/json: +// +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the Go distribution. + +import ( + "reflect" + "sort" + "sync" +) + +// A field represents a single field found in a struct. +type field struct { + name string // the name of the field (`toml` tag included) + tag bool // whether field has a `toml` tag + index []int // represents the depth of an anonymous field + typ reflect.Type // the type of the field +} + +// byName sorts field by name, breaking ties with depth, +// then breaking ties with "name came from toml tag", then +// breaking ties with index sequence. +type byName []field + +func (x byName) Len() int { return len(x) } + +func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byName) Less(i, j int) bool { + if x[i].name != x[j].name { + return x[i].name < x[j].name + } + if len(x[i].index) != len(x[j].index) { + return len(x[i].index) < len(x[j].index) + } + if x[i].tag != x[j].tag { + return x[i].tag + } + return byIndex(x).Less(i, j) +} + +// byIndex sorts field by index sequence. +type byIndex []field + +func (x byIndex) Len() int { return len(x) } + +func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (x byIndex) Less(i, j int) bool { + for k, xik := range x[i].index { + if k >= len(x[j].index) { + return false + } + if xik != x[j].index[k] { + return xik < x[j].index[k] + } + } + return len(x[i].index) < len(x[j].index) +} + +// typeFields returns a list of fields that TOML should recognize for the given +// type. The algorithm is breadth-first search over the set of structs to +// include - the top struct and then any reachable anonymous structs. +func typeFields(t reflect.Type) []field { + // Anonymous fields to explore at the current level and the next. + current := []field{} + next := []field{{typ: t}} + + // Count of queued names for current level and the next. + count := map[reflect.Type]int{} + nextCount := map[reflect.Type]int{} + + // Types already visited at an earlier level. + visited := map[reflect.Type]bool{} + + // Fields found. + var fields []field + + for len(next) > 0 { + current, next = next, current[:0] + count, nextCount = nextCount, map[reflect.Type]int{} + + for _, f := range current { + if visited[f.typ] { + continue + } + visited[f.typ] = true + + // Scan f.typ for fields to include. + for i := 0; i < f.typ.NumField(); i++ { + sf := f.typ.Field(i) + if sf.PkgPath != "" { // unexported + continue + } + name := sf.Tag.Get("toml") + if name == "-" { + continue + } + index := make([]int, len(f.index)+1) + copy(index, f.index) + index[len(f.index)] = i + + ft := sf.Type + if ft.Name() == "" && ft.Kind() == reflect.Ptr { + // Follow pointer. + ft = ft.Elem() + } + + // Record found field and index sequence. + if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { + tagged := name != "" + if name == "" { + name = sf.Name + } + fields = append(fields, field{name, tagged, index, ft}) + if count[f.typ] > 1 { + // If there were multiple instances, add a second, + // so that the annihilation code will see a duplicate. + // It only cares about the distinction between 1 or 2, + // so don't bother generating any more copies. + fields = append(fields, fields[len(fields)-1]) + } + continue + } + + // Record new anonymous struct to explore in next round. + nextCount[ft]++ + if nextCount[ft] == 1 { + f := field{name: ft.Name(), index: index, typ: ft} + next = append(next, f) + } + } + } + } + + sort.Sort(byName(fields)) + + // Delete all fields that are hidden by the Go rules for embedded fields, + // except that fields with TOML tags are promoted. + + // The fields are sorted in primary order of name, secondary order + // of field index length. Loop over names; for each name, delete + // hidden fields by choosing the one dominant field that survives. + out := fields[:0] + for advance, i := 0, 0; i < len(fields); i += advance { + // One iteration per name. + // Find the sequence of fields with the name of this first field. + fi := fields[i] + name := fi.name + for advance = 1; i+advance < len(fields); advance++ { + fj := fields[i+advance] + if fj.name != name { + break + } + } + if advance == 1 { // Only one field with this name + out = append(out, fi) + continue + } + dominant, ok := dominantField(fields[i : i+advance]) + if ok { + out = append(out, dominant) + } + } + + fields = out + sort.Sort(byIndex(fields)) + + return fields +} + +// dominantField looks through the fields, all of which are known to +// have the same name, to find the single field that dominates the +// others using Go's embedding rules, modified by the presence of +// TOML tags. If there are multiple top-level fields, the boolean +// will be false: This condition is an error in Go and we skip all +// the fields. +func dominantField(fields []field) (field, bool) { + // The fields are sorted in increasing index-length order. The winner + // must therefore be one with the shortest index length. Drop all + // longer entries, which is easy: just truncate the slice. + length := len(fields[0].index) + tagged := -1 // Index of first tagged field. + for i, f := range fields { + if len(f.index) > length { + fields = fields[:i] + break + } + if f.tag { + if tagged >= 0 { + // Multiple tagged fields at the same level: conflict. + // Return no field. + return field{}, false + } + tagged = i + } + } + if tagged >= 0 { + return fields[tagged], true + } + // All remaining fields have the same length. If there's more than one, + // we have a conflict (two fields named "X" at the same level) and we + // return no field. + if len(fields) > 1 { + return field{}, false + } + return fields[0], true +} + +var fieldCache struct { + sync.RWMutex + m map[reflect.Type][]field +} + +// cachedTypeFields is like typeFields but uses a cache to avoid repeated work. +func cachedTypeFields(t reflect.Type) []field { + fieldCache.RLock() + f := fieldCache.m[t] + fieldCache.RUnlock() + if f != nil { + return f + } + + // Compute fields without lock. + // Might duplicate effort but won't hold other computations back. + f = typeFields(t) + if f == nil { + f = []field{} + } + + fieldCache.Lock() + if fieldCache.m == nil { + fieldCache.m = map[reflect.Type][]field{} + } + fieldCache.m[t] = f + fieldCache.Unlock() + return f +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/error.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/error.go new file mode 100644 index 00000000000..a52743bef1c --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/error.go @@ -0,0 +1,105 @@ +// Package awserr represents API error interface accessors for the SDK. +package awserr + +// An Error wraps lower level errors with code, message and an original error. +// The underlying concrete error type may also satisfy other interfaces which +// can be to used to obtain more specific information about the error. +// +// Calling Error() or String() will always include the full information about +// an error based on its underlying type. +// +// Example: +// +// output, err := s3manage.Upload(svc, input, opts) +// if err != nil { +// if awsErr, ok := err.(awserr.Error); ok { +// // Get error details +// log.Println("Error:", err.Code(), err.Message()) +// +// // Prints out full error message, including original error if there was one. +// log.Println("Error:", err.Error()) +// +// // Get original error +// if origErr := err.Err(); origErr != nil { +// // operate on original error. +// } +// } else { +// fmt.Println(err.Error()) +// } +// } +// +type Error interface { + // Satisfy the generic error interface. + error + + // Returns the short phrase depicting the classification of the error. + Code() string + + // Returns the error details message. + Message() string + + // Returns the original error if one was set. Nil is returned if not set. + OrigErr() error +} + +// New returns an Error object described by the code, message, and origErr. +// +// If origErr satisfies the Error interface it will not be wrapped within a new +// Error object and will instead be returned. +func New(code, message string, origErr error) Error { + if e, ok := origErr.(Error); ok && e != nil { + return e + } + return newBaseError(code, message, origErr) +} + +// A RequestFailure is an interface to extract request failure information from +// an Error such as the request ID of the failed request returned by a service. +// RequestFailures may not always have a requestID value if the request failed +// prior to reaching the service such as a connection error. +// +// Example: +// +// output, err := s3manage.Upload(svc, input, opts) +// if err != nil { +// if reqerr, ok := err.(RequestFailure); ok { +// log.Printf("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID()) +// } else { +// log.Printf("Error:", err.Error() +// } +// } +// +// Combined with awserr.Error: +// +// output, err := s3manage.Upload(svc, input, opts) +// if err != nil { +// if awsErr, ok := err.(awserr.Error); ok { +// // Generic AWS Error with Code, Message, and original error (if any) +// fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) +// +// if reqErr, ok := err.(awserr.RequestFailure); ok { +// // A service error occurred +// fmt.Println(reqErr.StatusCode(), reqErr.RequestID()) +// } +// } else { +// fmt.Println(err.Error()) +// } +// } +// +type RequestFailure interface { + Error + + // The status code of the HTTP response. + StatusCode() int + + // The request ID returned by the service for a request failure. This will + // be empty if no request ID is available such as the request failed due + // to a connection error. + RequestID() string +} + +// NewRequestFailure returns a new request error wrapper for the given Error +// provided. +func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure { + return newRequestError(err, statusCode, reqID) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/types.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/types.go new file mode 100644 index 00000000000..418fc4c14b4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awserr/types.go @@ -0,0 +1,135 @@ +package awserr + +import "fmt" + +// SprintError returns a string of the formatted error code. +// +// Both extra and origErr are optional. If they are included their lines +// will be added, but if they are not included their lines will be ignored. +func SprintError(code, message, extra string, origErr error) string { + msg := fmt.Sprintf("%s: %s", code, message) + if extra != "" { + msg = fmt.Sprintf("%s\n\t%s", msg, extra) + } + if origErr != nil { + msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error()) + } + return msg +} + +// A baseError wraps the code and message which defines an error. It also +// can be used to wrap an original error object. +// +// Should be used as the root for errors satisfying the awserr.Error. Also +// for any error which does not fit into a specific error wrapper type. +type baseError struct { + // Classification of error + code string + + // Detailed information about error + message string + + // Optional original error this error is based off of. Allows building + // chained errors. + origErr error +} + +// newBaseError returns an error object for the code, message, and err. +// +// code is a short no whitespace phrase depicting the classification of +// the error that is being created. +// +// message is the free flow string containing detailed information about the error. +// +// origErr is the error object which will be nested under the new error to be returned. +func newBaseError(code, message string, origErr error) *baseError { + return &baseError{ + code: code, + message: message, + origErr: origErr, + } +} + +// Error returns the string representation of the error. +// +// See ErrorWithExtra for formatting. +// +// Satisfies the error interface. +func (b baseError) Error() string { + return SprintError(b.code, b.message, "", b.origErr) +} + +// String returns the string representation of the error. +// Alias for Error to satisfy the stringer interface. +func (b baseError) String() string { + return b.Error() +} + +// Code returns the short phrase depicting the classification of the error. +func (b baseError) Code() string { + return b.code +} + +// Message returns the error details message. +func (b baseError) Message() string { + return b.message +} + +// OrigErr returns the original error if one was set. Nil is returned if no error +// was set. +func (b baseError) OrigErr() error { + return b.origErr +} + +// So that the Error interface type can be included as an anonymous field +// in the requestError struct and not conflict with the error.Error() method. +type awsError Error + +// A requestError wraps a request or service error. +// +// Composed of baseError for code, message, and original error. +type requestError struct { + awsError + statusCode int + requestID string +} + +// newRequestError returns a wrapped error with additional information for request +// status code, and service requestID. +// +// Should be used to wrap all request which involve service requests. Even if +// the request failed without a service response, but had an HTTP status code +// that may be meaningful. +// +// Also wraps original errors via the baseError. +func newRequestError(err Error, statusCode int, requestID string) *requestError { + return &requestError{ + awsError: err, + statusCode: statusCode, + requestID: requestID, + } +} + +// Error returns the string representation of the error. +// Satisfies the error interface. +func (r requestError) Error() string { + extra := fmt.Sprintf("status code: %d, request id: [%s]", + r.statusCode, r.requestID) + return SprintError(r.Code(), r.Message(), extra, r.OrigErr()) +} + +// String returns the string representation of the error. +// Alias for Error to satisfy the stringer interface. +func (r requestError) String() string { + return r.Error() +} + +// StatusCode returns the wrapped status code for the error +func (r requestError) StatusCode() int { + return r.statusCode +} + +// RequestID returns the wrapped requestID +func (r requestError) RequestID() string { + return r.requestID +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy.go new file mode 100644 index 00000000000..f91743c6e1d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy.go @@ -0,0 +1,103 @@ +package awsutil + +import ( + "io" + "reflect" +) + +// Copy deeply copies a src structure to dst. Useful for copying request and +// response structures. +// +// Can copy between structs of different type, but will only copy fields which +// are assignable, and exist in both structs. Fields which are not assignable, +// or do not exist in both structs are ignored. +func Copy(dst, src interface{}) { + dstval := reflect.ValueOf(dst) + if !dstval.IsValid() { + panic("Copy dst cannot be nil") + } + + rcopy(dstval, reflect.ValueOf(src), true) +} + +// CopyOf returns a copy of src while also allocating the memory for dst. +// src must be a pointer type or this operation will fail. +func CopyOf(src interface{}) (dst interface{}) { + dsti := reflect.New(reflect.TypeOf(src).Elem()) + dst = dsti.Interface() + rcopy(dsti, reflect.ValueOf(src), true) + return +} + +// rcopy performs a recursive copy of values from the source to destination. +// +// root is used to skip certain aspects of the copy which are not valid +// for the root node of a object. +func rcopy(dst, src reflect.Value, root bool) { + if !src.IsValid() { + return + } + + switch src.Kind() { + case reflect.Ptr: + if _, ok := src.Interface().(io.Reader); ok { + if dst.Kind() == reflect.Ptr && dst.Elem().CanSet() { + dst.Elem().Set(src) + } else if dst.CanSet() { + dst.Set(src) + } + } else { + e := src.Type().Elem() + if dst.CanSet() && !src.IsNil() { + dst.Set(reflect.New(e)) + } + if src.Elem().IsValid() { + // Keep the current root state since the depth hasn't changed + rcopy(dst.Elem(), src.Elem(), root) + } + } + case reflect.Struct: + if !root { + dst.Set(reflect.New(src.Type()).Elem()) + } + + t := dst.Type() + for i := 0; i < t.NumField(); i++ { + name := t.Field(i).Name + srcval := src.FieldByName(name) + if srcval.IsValid() { + rcopy(dst.FieldByName(name), srcval, false) + } + } + case reflect.Slice: + if src.IsNil() { + break + } + + s := reflect.MakeSlice(src.Type(), src.Len(), src.Cap()) + dst.Set(s) + for i := 0; i < src.Len(); i++ { + rcopy(dst.Index(i), src.Index(i), false) + } + case reflect.Map: + if src.IsNil() { + break + } + + s := reflect.MakeMap(src.Type()) + dst.Set(s) + for _, k := range src.MapKeys() { + v := src.MapIndex(k) + v2 := reflect.New(v.Type()).Elem() + rcopy(v2, v, false) + dst.SetMapIndex(k, v2) + } + default: + // Assign the value if possible. If its not assignable, the value would + // need to be converted and the impact of that may be unexpected, or is + // not compatible with the dst type. + if src.Type().AssignableTo(dst.Type()) { + dst.Set(src) + } + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy_test.go new file mode 100644 index 00000000000..4f26241a2b8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/copy_test.go @@ -0,0 +1,201 @@ +package awsutil_test + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "testing" + + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/stretchr/testify/assert" +) + +func ExampleCopy() { + type Foo struct { + A int + B []*string + } + + // Create the initial value + str1 := "hello" + str2 := "bye bye" + f1 := &Foo{A: 1, B: []*string{&str1, &str2}} + + // Do the copy + var f2 Foo + awsutil.Copy(&f2, f1) + + // Print the result + fmt.Println(awsutil.Prettify(f2)) + + // Output: + // { + // A: 1, + // B: ["hello","bye bye"] + // } +} + +func TestCopy(t *testing.T) { + type Foo struct { + A int + B []*string + C map[string]*int + } + + // Create the initial value + str1 := "hello" + str2 := "bye bye" + int1 := 1 + int2 := 2 + f1 := &Foo{ + A: 1, + B: []*string{&str1, &str2}, + C: map[string]*int{ + "A": &int1, + "B": &int2, + }, + } + + // Do the copy + var f2 Foo + awsutil.Copy(&f2, f1) + + // Values are equal + assert.Equal(t, f2.A, f1.A) + assert.Equal(t, f2.B, f1.B) + assert.Equal(t, f2.C, f1.C) + + // But pointers are not! + str3 := "nothello" + int3 := 57 + f2.A = 100 + f2.B[0] = &str3 + f2.C["B"] = &int3 + assert.NotEqual(t, f2.A, f1.A) + assert.NotEqual(t, f2.B, f1.B) + assert.NotEqual(t, f2.C, f1.C) +} + +func TestCopyIgnoreNilMembers(t *testing.T) { + type Foo struct { + A *string + B []string + C map[string]string + } + + f := &Foo{} + assert.Nil(t, f.A) + assert.Nil(t, f.B) + assert.Nil(t, f.C) + + var f2 Foo + awsutil.Copy(&f2, f) + assert.Nil(t, f2.A) + assert.Nil(t, f2.B) + assert.Nil(t, f2.C) + + fcopy := awsutil.CopyOf(f) + f3 := fcopy.(*Foo) + assert.Nil(t, f3.A) + assert.Nil(t, f3.B) + assert.Nil(t, f3.C) +} + +func TestCopyPrimitive(t *testing.T) { + str := "hello" + var s string + awsutil.Copy(&s, &str) + assert.Equal(t, "hello", s) +} + +func TestCopyNil(t *testing.T) { + var s string + awsutil.Copy(&s, nil) + assert.Equal(t, "", s) +} + +func TestCopyReader(t *testing.T) { + var buf io.Reader = bytes.NewReader([]byte("hello world")) + var r io.Reader + awsutil.Copy(&r, buf) + b, err := ioutil.ReadAll(r) + assert.NoError(t, err) + assert.Equal(t, []byte("hello world"), b) + + // empty bytes because this is not a deep copy + b, err = ioutil.ReadAll(buf) + assert.NoError(t, err) + assert.Equal(t, []byte(""), b) +} + +func TestCopyDifferentStructs(t *testing.T) { + type SrcFoo struct { + A int + B []*string + C map[string]*int + SrcUnique string + SameNameDiffType int + } + type DstFoo struct { + A int + B []*string + C map[string]*int + DstUnique int + SameNameDiffType string + } + + // Create the initial value + str1 := "hello" + str2 := "bye bye" + int1 := 1 + int2 := 2 + f1 := &SrcFoo{ + A: 1, + B: []*string{&str1, &str2}, + C: map[string]*int{ + "A": &int1, + "B": &int2, + }, + SrcUnique: "unique", + SameNameDiffType: 1, + } + + // Do the copy + var f2 DstFoo + awsutil.Copy(&f2, f1) + + // Values are equal + assert.Equal(t, f2.A, f1.A) + assert.Equal(t, f2.B, f1.B) + assert.Equal(t, f2.C, f1.C) + assert.Equal(t, "unique", f1.SrcUnique) + assert.Equal(t, 1, f1.SameNameDiffType) + assert.Equal(t, 0, f2.DstUnique) + assert.Equal(t, "", f2.SameNameDiffType) +} + +func ExampleCopyOf() { + type Foo struct { + A int + B []*string + } + + // Create the initial value + str1 := "hello" + str2 := "bye bye" + f1 := &Foo{A: 1, B: []*string{&str1, &str2}} + + // Do the copy + v := awsutil.CopyOf(f1) + var f2 *Foo = v.(*Foo) + + // Print the result + fmt.Println(awsutil.Prettify(f2)) + + // Output: + // { + // A: 1, + // B: ["hello","bye bye"] + // } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go new file mode 100644 index 00000000000..905d82385ec --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go @@ -0,0 +1,187 @@ +package awsutil + +import ( + "reflect" + "regexp" + "strconv" + "strings" +) + +var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`) + +// rValuesAtPath returns a slice of values found in value v. The values +// in v are explored recursively so all nested values are collected. +func rValuesAtPath(v interface{}, path string, create bool, caseSensitive bool) []reflect.Value { + pathparts := strings.Split(path, "||") + if len(pathparts) > 1 { + for _, pathpart := range pathparts { + vals := rValuesAtPath(v, pathpart, create, caseSensitive) + if vals != nil && len(vals) > 0 { + return vals + } + } + return nil + } + + values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))} + components := strings.Split(path, ".") + for len(values) > 0 && len(components) > 0 { + var index *int64 + var indexStar bool + c := strings.TrimSpace(components[0]) + if c == "" { // no actual component, illegal syntax + return nil + } else if caseSensitive && c != "*" && strings.ToLower(c[0:1]) == c[0:1] { + // TODO normalize case for user + return nil // don't support unexported fields + } + + // parse this component + if m := indexRe.FindStringSubmatch(c); m != nil { + c = m[1] + if m[2] == "" { + index = nil + indexStar = true + } else { + i, _ := strconv.ParseInt(m[2], 10, 32) + index = &i + indexStar = false + } + } + + nextvals := []reflect.Value{} + for _, value := range values { + // pull component name out of struct member + if value.Kind() != reflect.Struct { + continue + } + + if c == "*" { // pull all members + for i := 0; i < value.NumField(); i++ { + if f := reflect.Indirect(value.Field(i)); f.IsValid() { + nextvals = append(nextvals, f) + } + } + continue + } + + value = value.FieldByNameFunc(func(name string) bool { + if c == name { + return true + } else if !caseSensitive && strings.ToLower(name) == strings.ToLower(c) { + return true + } + return false + }) + + if create && value.Kind() == reflect.Ptr && value.IsNil() { + value.Set(reflect.New(value.Type().Elem())) + value = value.Elem() + } else { + value = reflect.Indirect(value) + } + + if value.Kind() == reflect.Slice || value.Kind() == reflect.Map { + if !create && value.IsNil() { + value = reflect.ValueOf(nil) + } + } + + if value.IsValid() { + nextvals = append(nextvals, value) + } + } + values = nextvals + + if indexStar || index != nil { + nextvals = []reflect.Value{} + for _, value := range values { + value := reflect.Indirect(value) + if value.Kind() != reflect.Slice { + continue + } + + if indexStar { // grab all indices + for i := 0; i < value.Len(); i++ { + idx := reflect.Indirect(value.Index(i)) + if idx.IsValid() { + nextvals = append(nextvals, idx) + } + } + continue + } + + // pull out index + i := int(*index) + if i >= value.Len() { // check out of bounds + if create { + // TODO resize slice + } else { + continue + } + } else if i < 0 { // support negative indexing + i = value.Len() + i + } + value = reflect.Indirect(value.Index(i)) + + if value.Kind() == reflect.Slice || value.Kind() == reflect.Map { + if !create && value.IsNil() { + value = reflect.ValueOf(nil) + } + } + + if value.IsValid() { + nextvals = append(nextvals, value) + } + } + values = nextvals + } + + components = components[1:] + } + return values +} + +// ValuesAtPath returns a list of objects at the lexical path inside of a structure +func ValuesAtPath(i interface{}, path string) []interface{} { + if rvals := rValuesAtPath(i, path, false, true); rvals != nil { + vals := make([]interface{}, len(rvals)) + for i, rval := range rvals { + vals[i] = rval.Interface() + } + return vals + } + return nil +} + +// ValuesAtAnyPath returns a list of objects at the case-insensitive lexical +// path inside of a structure +func ValuesAtAnyPath(i interface{}, path string) []interface{} { + if rvals := rValuesAtPath(i, path, false, false); rvals != nil { + vals := make([]interface{}, len(rvals)) + for i, rval := range rvals { + vals[i] = rval.Interface() + } + return vals + } + return nil +} + +// SetValueAtPath sets an object at the lexical path inside of a structure +func SetValueAtPath(i interface{}, path string, v interface{}) { + if rvals := rValuesAtPath(i, path, true, true); rvals != nil { + for _, rval := range rvals { + rval.Set(reflect.ValueOf(v)) + } + } +} + +// SetValueAtAnyPath sets an object at the case insensitive lexical path inside +// of a structure +func SetValueAtAnyPath(i interface{}, path string, v interface{}) { + if rvals := rValuesAtPath(i, path, true, false); rvals != nil { + for _, rval := range rvals { + rval.Set(reflect.ValueOf(v)) + } + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go new file mode 100644 index 00000000000..0da6b06fddc --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/path_value_test.go @@ -0,0 +1,68 @@ +package awsutil_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/stretchr/testify/assert" +) + +type Struct struct { + A []Struct + z []Struct + B *Struct + D *Struct + C string +} + +var data = Struct{ + A: []Struct{{C: "value1"}, {C: "value2"}, {C: "value3"}}, + z: []Struct{{C: "value1"}, {C: "value2"}, {C: "value3"}}, + B: &Struct{B: &Struct{C: "terminal"}, D: &Struct{C: "terminal2"}}, + C: "initial", +} + +func TestValueAtPathSuccess(t *testing.T) { + assert.Equal(t, []interface{}{"initial"}, awsutil.ValuesAtPath(data, "C")) + assert.Equal(t, []interface{}{"value1"}, awsutil.ValuesAtPath(data, "A[0].C")) + assert.Equal(t, []interface{}{"value2"}, awsutil.ValuesAtPath(data, "A[1].C")) + assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtPath(data, "A[2].C")) + assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtAnyPath(data, "a[2].c")) + assert.Equal(t, []interface{}{"value3"}, awsutil.ValuesAtPath(data, "A[-1].C")) + assert.Equal(t, []interface{}{"value1", "value2", "value3"}, awsutil.ValuesAtPath(data, "A[].C")) + assert.Equal(t, []interface{}{"terminal"}, awsutil.ValuesAtPath(data, "B . B . C")) + assert.Equal(t, []interface{}{"terminal", "terminal2"}, awsutil.ValuesAtPath(data, "B.*.C")) + assert.Equal(t, []interface{}{"initial"}, awsutil.ValuesAtPath(data, "A.D.X || C")) +} + +func TestValueAtPathFailure(t *testing.T) { + assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, "C.x")) + assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, ".x")) + assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "X.Y.Z")) + assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "A[100].C")) + assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "A[3].C")) + assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(data, "B.B.C.Z")) + assert.Equal(t, []interface{}(nil), awsutil.ValuesAtPath(data, "z[-1].C")) + assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(nil, "A.B.C")) + assert.Equal(t, []interface{}{}, awsutil.ValuesAtPath(Struct{}, "A")) +} + +func TestSetValueAtPathSuccess(t *testing.T) { + var s Struct + awsutil.SetValueAtPath(&s, "C", "test1") + awsutil.SetValueAtPath(&s, "B.B.C", "test2") + awsutil.SetValueAtPath(&s, "B.D.C", "test3") + assert.Equal(t, "test1", s.C) + assert.Equal(t, "test2", s.B.B.C) + assert.Equal(t, "test3", s.B.D.C) + + awsutil.SetValueAtPath(&s, "B.*.C", "test0") + assert.Equal(t, "test0", s.B.B.C) + assert.Equal(t, "test0", s.B.D.C) + + var s2 Struct + awsutil.SetValueAtAnyPath(&s2, "b.b.c", "test0") + assert.Equal(t, "test0", s2.B.B.C) + awsutil.SetValueAtAnyPath(&s2, "A", []Struct{{}}) + assert.Equal(t, []Struct{{}}, s2.A) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go new file mode 100644 index 00000000000..0de3eaa0f63 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/awsutil/prettify.go @@ -0,0 +1,103 @@ +package awsutil + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strings" +) + +// Prettify returns the string representation of a value. +func Prettify(i interface{}) string { + var buf bytes.Buffer + prettify(reflect.ValueOf(i), 0, &buf) + return buf.String() +} + +// prettify will recursively walk value v to build a textual +// representation of the value. +func prettify(v reflect.Value, indent int, buf *bytes.Buffer) { + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + switch v.Kind() { + case reflect.Struct: + strtype := v.Type().String() + if strtype == "time.Time" { + fmt.Fprintf(buf, "%s", v.Interface()) + break + } else if strings.HasPrefix(strtype, "io.") { + buf.WriteString("") + break + } + + buf.WriteString("{\n") + + names := []string{} + for i := 0; i < v.Type().NumField(); i++ { + name := v.Type().Field(i).Name + f := v.Field(i) + if name[0:1] == strings.ToLower(name[0:1]) { + continue // ignore unexported fields + } + if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice || f.Kind() == reflect.Map) && f.IsNil() { + continue // ignore unset fields + } + names = append(names, name) + } + + for i, n := range names { + val := v.FieldByName(n) + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString(n + ": ") + prettify(val, indent+2, buf) + + if i < len(names)-1 { + buf.WriteString(",\n") + } + } + + buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") + case reflect.Slice: + nl, id, id2 := "", "", "" + if v.Len() > 3 { + nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2) + } + buf.WriteString("[" + nl) + for i := 0; i < v.Len(); i++ { + buf.WriteString(id2) + prettify(v.Index(i), indent+2, buf) + + if i < v.Len()-1 { + buf.WriteString("," + nl) + } + } + + buf.WriteString(nl + id + "]") + case reflect.Map: + buf.WriteString("{\n") + + for i, k := range v.MapKeys() { + buf.WriteString(strings.Repeat(" ", indent+2)) + buf.WriteString(k.String() + ": ") + prettify(v.MapIndex(k), indent+2, buf) + + if i < v.Len()-1 { + buf.WriteString(",\n") + } + } + + buf.WriteString("\n" + strings.Repeat(" ", indent) + "}") + default: + format := "%v" + switch v.Interface().(type) { + case string: + format = "%q" + case io.ReadSeeker, io.Reader: + format = "buffer(%p)" + } + fmt.Fprintf(buf, format, v.Interface()) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go new file mode 100644 index 00000000000..c27bdd3c106 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config.go @@ -0,0 +1,254 @@ +package aws + +import ( + "net/http" + "os" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" +) + +// DefaultChainCredentials is a Credentials which will find the first available +// credentials Value from the list of Providers. +// +// This should be used in the default case. Once the type of credentials are +// known switching to the specific Credentials will be more efficient. +var DefaultChainCredentials = credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{Filename: "", Profile: ""}, + &credentials.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, + }) + +// The default number of retries for a service. The value of -1 indicates that +// the service specific retry default will be used. +const DefaultRetries = -1 + +// DefaultConfig is the default all service configuration will be based off of. +// By default, all clients use this structure for initialization options unless +// a custom configuration object is passed in. +// +// You may modify this global structure to change all default configuration +// in the SDK. Note that configuration options are copied by value, so any +// modifications must happen before constructing a client. +var DefaultConfig = NewConfig(). + WithCredentials(DefaultChainCredentials). + WithRegion(os.Getenv("AWS_REGION")). + WithHTTPClient(http.DefaultClient). + WithMaxRetries(DefaultRetries). + WithLogger(NewDefaultLogger()). + WithLogLevel(LogOff) + +// A Config provides service configuration for service clients. By default, +// all clients will use the {DefaultConfig} structure. +type Config struct { + // The credentials object to use when signing requests. Defaults to + // {DefaultChainCredentials}. + Credentials *credentials.Credentials + + // An optional endpoint URL (hostname only or fully qualified URI) + // that overrides the default generated endpoint for a client. Set this + // to `""` to use the default generated endpoint. + // + // @note You must still provide a `Region` value when specifying an + // endpoint for a client. + Endpoint *string + + // The region to send requests to. This parameter is required and must + // be configured globally or on a per-client basis unless otherwise + // noted. A full list of regions is found in the "Regions and Endpoints" + // document. + // + // @see http://docs.aws.amazon.com/general/latest/gr/rande.html + // AWS Regions and Endpoints + Region *string + + // Set this to `true` to disable SSL when sending requests. Defaults + // to `false`. + DisableSSL *bool + + // The HTTP client to use when sending requests. Defaults to + // `http.DefaultClient`. + HTTPClient *http.Client + + // An integer value representing the logging level. The default log level + // is zero (LogOff), which represents no logging. To enable logging set + // to a LogLevel Value. + LogLevel *LogLevelType + + // The logger writer interface to write logging messages to. Defaults to + // standard out. + Logger Logger + + // The maximum number of times that a request will be retried for failures. + // Defaults to -1, which defers the max retry setting to the service specific + // configuration. + MaxRetries *int + + // Disables semantic parameter validation, which validates input for missing + // required fields and/or other semantic request input errors. + DisableParamValidation *bool + + // Disables the computation of request and response checksums, e.g., + // CRC32 checksums in Amazon DynamoDB. + DisableComputeChecksums *bool + + // Set this to `true` to force the request to use path-style addressing, + // i.e., `http://s3.amazonaws.com/BUCKET/KEY`. By default, the S3 client will + // use virtual hosted bucket addressing when possible + // (`http://BUCKET.s3.amazonaws.com/KEY`). + // + // @note This configuration option is specific to the Amazon S3 service. + // @see http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html + // Amazon S3: Virtual Hosting of Buckets + S3ForcePathStyle *bool +} + +// NewConfig returns a new Config pointer that can be chained with builder methods to +// set multiple configuration values inline without using pointers. +// +// svc := s3.New(aws.NewConfig().WithRegion("us-west-2").WithMaxRetries(10)) +// +func NewConfig() *Config { + return &Config{} +} + +// WithCredentials sets a config Credentials value returning a Config pointer +// for chaining. +func (c *Config) WithCredentials(creds *credentials.Credentials) *Config { + c.Credentials = creds + return c +} + +// WithEndpoint sets a config Endpoint value returning a Config pointer for +// chaining. +func (c *Config) WithEndpoint(endpoint string) *Config { + c.Endpoint = &endpoint + return c +} + +// WithRegion sets a config Region value returning a Config pointer for +// chaining. +func (c *Config) WithRegion(region string) *Config { + c.Region = ®ion + return c +} + +// WithDisableSSL sets a config DisableSSL value returning a Config pointer +// for chaining. +func (c *Config) WithDisableSSL(disable bool) *Config { + c.DisableSSL = &disable + return c +} + +// WithHTTPClient sets a config HTTPClient value returning a Config pointer +// for chaining. +func (c *Config) WithHTTPClient(client *http.Client) *Config { + c.HTTPClient = client + return c +} + +// WithMaxRetries sets a config MaxRetries value returning a Config pointer +// for chaining. +func (c *Config) WithMaxRetries(max int) *Config { + c.MaxRetries = &max + return c +} + +// WithDisableParamValidation sets a config DisableParamValidation value +// returning a Config pointer for chaining. +func (c *Config) WithDisableParamValidation(disable bool) *Config { + c.DisableParamValidation = &disable + return c +} + +// WithDisableComputeChecksums sets a config DisableComputeChecksums value +// returning a Config pointer for chaining. +func (c *Config) WithDisableComputeChecksums(disable bool) *Config { + c.DisableComputeChecksums = &disable + return c +} + +// WithLogLevel sets a config LogLevel value returning a Config pointer for +// chaining. +func (c *Config) WithLogLevel(level LogLevelType) *Config { + c.LogLevel = &level + return c +} + +// WithLogger sets a config Logger value returning a Config pointer for +// chaining. +func (c *Config) WithLogger(logger Logger) *Config { + c.Logger = logger + return c +} + +// WithS3ForcePathStyle sets a config S3ForcePathStyle value returning a Config +// pointer for chaining. +func (c *Config) WithS3ForcePathStyle(force bool) *Config { + c.S3ForcePathStyle = &force + return c +} + +// Merge returns a new Config with the other Config's attribute values merged into +// this Config. If the other Config's attribute is nil it will not be merged into +// the new Config to be returned. +func (c Config) Merge(other *Config) *Config { + if other == nil { + return &c + } + + dst := c + + if other.Credentials != nil { + dst.Credentials = other.Credentials + } + + if other.Endpoint != nil { + dst.Endpoint = other.Endpoint + } + + if other.Region != nil { + dst.Region = other.Region + } + + if other.DisableSSL != nil { + dst.DisableSSL = other.DisableSSL + } + + if other.HTTPClient != nil { + dst.HTTPClient = other.HTTPClient + } + + if other.LogLevel != nil { + dst.LogLevel = other.LogLevel + } + + if other.Logger != nil { + dst.Logger = other.Logger + } + + if other.MaxRetries != nil { + dst.MaxRetries = other.MaxRetries + } + + if other.DisableParamValidation != nil { + dst.DisableParamValidation = other.DisableParamValidation + } + + if other.DisableComputeChecksums != nil { + dst.DisableComputeChecksums = other.DisableComputeChecksums + } + + if other.S3ForcePathStyle != nil { + dst.S3ForcePathStyle = other.S3ForcePathStyle + } + + return &dst +} + +// Copy will return a shallow copy of the Config object. +func (c Config) Copy() *Config { + dst := c + return &dst +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config_test.go new file mode 100644 index 00000000000..ddf3f39764d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/config_test.go @@ -0,0 +1,87 @@ +package aws + +import ( + "net/http" + "reflect" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" +) + +var testCredentials = credentials.NewChainCredentials([]credentials.Provider{ + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{ + Filename: "TestFilename", + Profile: "TestProfile"}, + &credentials.EC2RoleProvider{ExpiryWindow: 5 * time.Minute}, +}) + +var copyTestConfig = Config{ + Credentials: testCredentials, + Endpoint: String("CopyTestEndpoint"), + Region: String("COPY_TEST_AWS_REGION"), + DisableSSL: Bool(true), + HTTPClient: http.DefaultClient, + LogLevel: LogLevel(LogDebug), + Logger: NewDefaultLogger(), + MaxRetries: Int(DefaultRetries), + DisableParamValidation: Bool(true), + DisableComputeChecksums: Bool(true), + S3ForcePathStyle: Bool(true), +} + +func TestCopy(t *testing.T) { + want := copyTestConfig + got := copyTestConfig.Copy() + if !reflect.DeepEqual(*got, want) { + t.Errorf("Copy() = %+v", got) + t.Errorf(" want %+v", want) + } +} + +func TestCopyReturnsNewInstance(t *testing.T) { + want := copyTestConfig + got := copyTestConfig.Copy() + if got == &want { + t.Errorf("Copy() = %p; want different instance as source %p", got, &want) + } +} + +var mergeTestZeroValueConfig = Config{} + +var mergeTestConfig = Config{ + Credentials: testCredentials, + Endpoint: String("MergeTestEndpoint"), + Region: String("MERGE_TEST_AWS_REGION"), + DisableSSL: Bool(true), + HTTPClient: http.DefaultClient, + LogLevel: LogLevel(LogDebug), + Logger: NewDefaultLogger(), + MaxRetries: Int(10), + DisableParamValidation: Bool(true), + DisableComputeChecksums: Bool(true), + S3ForcePathStyle: Bool(true), +} + +var mergeTests = []struct { + cfg *Config + in *Config + want *Config +}{ + {&Config{}, nil, &Config{}}, + {&Config{}, &mergeTestZeroValueConfig, &Config{}}, + {&Config{}, &mergeTestConfig, &mergeTestConfig}, +} + +func TestMerge(t *testing.T) { + for i, tt := range mergeTests { + got := tt.cfg.Merge(tt.in) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Config %d %+v", i, tt.cfg) + t.Errorf(" Merge(%+v)", tt.in) + t.Errorf(" got %+v", got) + t.Errorf(" want %+v", tt.want) + } + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/convutil.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/convutil.go new file mode 100644 index 00000000000..d6a7b08dffe --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/convutil.go @@ -0,0 +1,357 @@ +package aws + +import "time" + +// String returns a pointer to of the string value passed in. +func String(v string) *string { + return &v +} + +// StringValue returns the value of the string pointer passed in or +// "" if the pointer is nil. +func StringValue(v *string) string { + if v != nil { + return *v + } + return "" +} + +// StringSlice converts a slice of string values into a slice of +// string pointers +func StringSlice(src []string) []*string { + dst := make([]*string, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// StringValueSlice converts a slice of string pointers into a slice of +// string values +func StringValueSlice(src []*string) []string { + dst := make([]string, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// StringMap converts a string map of string values into a string +// map of string pointers +func StringMap(src map[string]string) map[string]*string { + dst := make(map[string]*string) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// StringValueMap converts a string map of string pointers into a string +// map of string values +func StringValueMap(src map[string]*string) map[string]string { + dst := make(map[string]string) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Bool returns a pointer to of the bool value passed in. +func Bool(v bool) *bool { + return &v +} + +// BoolValue returns the value of the bool pointer passed in or +// false if the pointer is nil. +func BoolValue(v *bool) bool { + if v != nil { + return *v + } + return false +} + +// BoolSlice converts a slice of bool values into a slice of +// bool pointers +func BoolSlice(src []bool) []*bool { + dst := make([]*bool, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// BoolValueSlice converts a slice of bool pointers into a slice of +// bool values +func BoolValueSlice(src []*bool) []bool { + dst := make([]bool, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// BoolMap converts a string map of bool values into a string +// map of bool pointers +func BoolMap(src map[string]bool) map[string]*bool { + dst := make(map[string]*bool) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// BoolValueMap converts a string map of bool pointers into a string +// map of bool values +func BoolValueMap(src map[string]*bool) map[string]bool { + dst := make(map[string]bool) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int returns a pointer to of the int value passed in. +func Int(v int) *int { + return &v +} + +// IntValue returns the value of the int pointer passed in or +// 0 if the pointer is nil. +func IntValue(v *int) int { + if v != nil { + return *v + } + return 0 +} + +// IntSlice converts a slice of int values into a slice of +// int pointers +func IntSlice(src []int) []*int { + dst := make([]*int, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// IntValueSlice converts a slice of int pointers into a slice of +// int values +func IntValueSlice(src []*int) []int { + dst := make([]int, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// IntMap converts a string map of int values into a string +// map of int pointers +func IntMap(src map[string]int) map[string]*int { + dst := make(map[string]*int) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// IntValueMap converts a string map of int pointers into a string +// map of int values +func IntValueMap(src map[string]*int) map[string]int { + dst := make(map[string]int) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Int64 returns a pointer to of the int64 value passed in. +func Int64(v int64) *int64 { + return &v +} + +// Int64Value returns the value of the int64 pointer passed in or +// 0 if the pointer is nil. +func Int64Value(v *int64) int64 { + if v != nil { + return *v + } + return 0 +} + +// Int64Slice converts a slice of int64 values into a slice of +// int64 pointers +func Int64Slice(src []int64) []*int64 { + dst := make([]*int64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Int64ValueSlice converts a slice of int64 pointers into a slice of +// int64 values +func Int64ValueSlice(src []*int64) []int64 { + dst := make([]int64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Int64Map converts a string map of int64 values into a string +// map of int64 pointers +func Int64Map(src map[string]int64) map[string]*int64 { + dst := make(map[string]*int64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Int64ValueMap converts a string map of int64 pointers into a string +// map of int64 values +func Int64ValueMap(src map[string]*int64) map[string]int64 { + dst := make(map[string]int64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Float64 returns a pointer to of the float64 value passed in. +func Float64(v float64) *float64 { + return &v +} + +// Float64Value returns the value of the float64 pointer passed in or +// 0 if the pointer is nil. +func Float64Value(v *float64) float64 { + if v != nil { + return *v + } + return 0 +} + +// Float64Slice converts a slice of float64 values into a slice of +// float64 pointers +func Float64Slice(src []float64) []*float64 { + dst := make([]*float64, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// Float64ValueSlice converts a slice of float64 pointers into a slice of +// float64 values +func Float64ValueSlice(src []*float64) []float64 { + dst := make([]float64, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// Float64Map converts a string map of float64 values into a string +// map of float64 pointers +func Float64Map(src map[string]float64) map[string]*float64 { + dst := make(map[string]*float64) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// Float64ValueMap converts a string map of float64 pointers into a string +// map of float64 values +func Float64ValueMap(src map[string]*float64) map[string]float64 { + dst := make(map[string]float64) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} + +// Time returns a pointer to of the time.Time value passed in. +func Time(v time.Time) *time.Time { + return &v +} + +// TimeValue returns the value of the time.Time pointer passed in or +// time.Time{} if the pointer is nil. +func TimeValue(v *time.Time) time.Time { + if v != nil { + return *v + } + return time.Time{} +} + +// TimeSlice converts a slice of time.Time values into a slice of +// time.Time pointers +func TimeSlice(src []time.Time) []*time.Time { + dst := make([]*time.Time, len(src)) + for i := 0; i < len(src); i++ { + dst[i] = &(src[i]) + } + return dst +} + +// TimeValueSlice converts a slice of time.Time pointers into a slice of +// time.Time values +func TimeValueSlice(src []*time.Time) []time.Time { + dst := make([]time.Time, len(src)) + for i := 0; i < len(src); i++ { + if src[i] != nil { + dst[i] = *(src[i]) + } + } + return dst +} + +// TimeMap converts a string map of time.Time values into a string +// map of time.Time pointers +func TimeMap(src map[string]time.Time) map[string]*time.Time { + dst := make(map[string]*time.Time) + for k, val := range src { + v := val + dst[k] = &v + } + return dst +} + +// TimeValueMap converts a string map of time.Time pointers into a string +// map of time.Time values +func TimeValueMap(src map[string]*time.Time) map[string]time.Time { + dst := make(map[string]time.Time) + for k, val := range src { + if val != nil { + dst[k] = *val + } + } + return dst +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/convutil_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/convutil_test.go new file mode 100644 index 00000000000..6d06cd30f0a --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/convutil_test.go @@ -0,0 +1,438 @@ +package aws_test + +import ( + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/stretchr/testify/assert" +) + +var testCasesStringSlice = [][]string{ + {"a", "b", "c", "d", "e"}, + {"a", "b", "", "", "e"}, +} + +func TestStringSlice(t *testing.T) { + for idx, in := range testCasesStringSlice { + if in == nil { + continue + } + out := aws.StringSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.StringValueSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesStringValueSlice = [][]*string{ + {aws.String("a"), aws.String("b"), nil, aws.String("c")}, +} + +func TestStringValueSlice(t *testing.T) { + for idx, in := range testCasesStringValueSlice { + if in == nil { + continue + } + out := aws.StringValueSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + if in[i] == nil { + assert.Empty(t, out[i], "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx) + } + } + + out2 := aws.StringSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + for i := range out2 { + if in[i] == nil { + assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx) + } + } + } +} + +var testCasesStringMap = []map[string]string{ + {"a": "1", "b": "2", "c": "3"}, +} + +func TestStringMap(t *testing.T) { + for idx, in := range testCasesStringMap { + if in == nil { + continue + } + out := aws.StringMap(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.StringValueMap(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesBoolSlice = [][]bool{ + {true, true, false, false}, +} + +func TestBoolSlice(t *testing.T) { + for idx, in := range testCasesBoolSlice { + if in == nil { + continue + } + out := aws.BoolSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.BoolValueSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesBoolValueSlice = [][]*bool{} + +func TestBoolValueSlice(t *testing.T) { + for idx, in := range testCasesBoolValueSlice { + if in == nil { + continue + } + out := aws.BoolValueSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + if in[i] == nil { + assert.Empty(t, out[i], "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx) + } + } + + out2 := aws.BoolSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + for i := range out2 { + if in[i] == nil { + assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx) + } + } + } +} + +var testCasesBoolMap = []map[string]bool{ + {"a": true, "b": false, "c": true}, +} + +func TestBoolMap(t *testing.T) { + for idx, in := range testCasesBoolMap { + if in == nil { + continue + } + out := aws.BoolMap(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.BoolValueMap(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesIntSlice = [][]int{ + {1, 2, 3, 4}, +} + +func TestIntSlice(t *testing.T) { + for idx, in := range testCasesIntSlice { + if in == nil { + continue + } + out := aws.IntSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.IntValueSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesIntValueSlice = [][]*int{} + +func TestIntValueSlice(t *testing.T) { + for idx, in := range testCasesIntValueSlice { + if in == nil { + continue + } + out := aws.IntValueSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + if in[i] == nil { + assert.Empty(t, out[i], "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx) + } + } + + out2 := aws.IntSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + for i := range out2 { + if in[i] == nil { + assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx) + } + } + } +} + +var testCasesIntMap = []map[string]int{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestIntMap(t *testing.T) { + for idx, in := range testCasesIntMap { + if in == nil { + continue + } + out := aws.IntMap(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.IntValueMap(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesInt64Slice = [][]int64{ + {1, 2, 3, 4}, +} + +func TestInt64Slice(t *testing.T) { + for idx, in := range testCasesInt64Slice { + if in == nil { + continue + } + out := aws.Int64Slice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.Int64ValueSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesInt64ValueSlice = [][]*int64{} + +func TestInt64ValueSlice(t *testing.T) { + for idx, in := range testCasesInt64ValueSlice { + if in == nil { + continue + } + out := aws.Int64ValueSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + if in[i] == nil { + assert.Empty(t, out[i], "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx) + } + } + + out2 := aws.Int64Slice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + for i := range out2 { + if in[i] == nil { + assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx) + } + } + } +} + +var testCasesInt64Map = []map[string]int64{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestInt64Map(t *testing.T) { + for idx, in := range testCasesInt64Map { + if in == nil { + continue + } + out := aws.Int64Map(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.Int64ValueMap(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesFloat64Slice = [][]float64{ + {1, 2, 3, 4}, +} + +func TestFloat64Slice(t *testing.T) { + for idx, in := range testCasesFloat64Slice { + if in == nil { + continue + } + out := aws.Float64Slice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.Float64ValueSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesFloat64ValueSlice = [][]*float64{} + +func TestFloat64ValueSlice(t *testing.T) { + for idx, in := range testCasesFloat64ValueSlice { + if in == nil { + continue + } + out := aws.Float64ValueSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + if in[i] == nil { + assert.Empty(t, out[i], "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx) + } + } + + out2 := aws.Float64Slice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + for i := range out2 { + if in[i] == nil { + assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx) + } + } + } +} + +var testCasesFloat64Map = []map[string]float64{ + {"a": 3, "b": 2, "c": 1}, +} + +func TestFloat64Map(t *testing.T) { + for idx, in := range testCasesFloat64Map { + if in == nil { + continue + } + out := aws.Float64Map(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.Float64ValueMap(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesTimeSlice = [][]time.Time{ + {time.Now(), time.Now().AddDate(100, 0, 0)}, +} + +func TestTimeSlice(t *testing.T) { + for idx, in := range testCasesTimeSlice { + if in == nil { + continue + } + out := aws.TimeSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.TimeValueSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} + +var testCasesTimeValueSlice = [][]*time.Time{} + +func TestTimeValueSlice(t *testing.T) { + for idx, in := range testCasesTimeValueSlice { + if in == nil { + continue + } + out := aws.TimeValueSlice(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + if in[i] == nil { + assert.Empty(t, out[i], "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx) + } + } + + out2 := aws.TimeSlice(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + for i := range out2 { + if in[i] == nil { + assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx) + } else { + assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx) + } + } + } +} + +var testCasesTimeMap = []map[string]time.Time{ + {"a": time.Now().AddDate(-100, 0, 0), "b": time.Now()}, +} + +func TestTimeMap(t *testing.T) { + for idx, in := range testCasesTimeMap { + if in == nil { + continue + } + out := aws.TimeMap(in) + assert.Len(t, out, len(in), "Unexpected len at idx %d", idx) + for i := range out { + assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx) + } + + out2 := aws.TimeValueMap(out) + assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx) + assert.Equal(t, in, out2, "Unexpected value at idx %d", idx) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go new file mode 100644 index 00000000000..7f509ca83ca --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider.go @@ -0,0 +1,85 @@ +package credentials + +import ( + "github.com/aws/aws-sdk-go/aws/awserr" +) + +var ( + // ErrNoValidProvidersFoundInChain Is returned when there are no valid + // providers in the ChainProvider. + // + // @readonly + ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", "no valid providers in chain", nil) +) + +// A ChainProvider will search for a provider which returns credentials +// and cache that provider until Retrieve is called again. +// +// The ChainProvider provides a way of chaining multiple providers together +// which will pick the first available using priority order of the Providers +// in the list. +// +// If none of the Providers retrieve valid credentials Value, ChainProvider's +// Retrieve() will return the error ErrNoValidProvidersFoundInChain. +// +// If a Provider is found which returns valid credentials Value ChainProvider +// will cache that Provider for all calls to IsExpired(), until Retrieve is +// called again. +// +// Example of ChainProvider to be used with an EnvProvider and EC2RoleProvider. +// In this example EnvProvider will first check if any credentials are available +// vai the environment variables. If there are none ChainProvider will check +// the next Provider in the list, EC2RoleProvider in this case. If EC2RoleProvider +// does not return any credentials ChainProvider will return the error +// ErrNoValidProvidersFoundInChain +// +// creds := NewChainCredentials( +// []Provider{ +// &EnvProvider{}, +// &EC2RoleProvider{}, +// }) +// +// // Usage of ChainCredentials with aws.Config +// svc := ec2.New(&aws.Config{Credentials: creds}) +// +type ChainProvider struct { + Providers []Provider + curr Provider +} + +// NewChainCredentials returns a pointer to a new Credentials object +// wrapping a chain of providers. +func NewChainCredentials(providers []Provider) *Credentials { + return NewCredentials(&ChainProvider{ + Providers: append([]Provider{}, providers...), + }) +} + +// Retrieve returns the credentials value or error if no provider returned +// without error. +// +// If a provider is found it will be cached and any calls to IsExpired() +// will return the expired state of the cached provider. +func (c *ChainProvider) Retrieve() (Value, error) { + for _, p := range c.Providers { + if creds, err := p.Retrieve(); err == nil { + c.curr = p + return creds, nil + } + } + c.curr = nil + + // TODO better error reporting. maybe report error for each failed retrieve? + + return Value{}, ErrNoValidProvidersFoundInChain +} + +// IsExpired will returned the expired state of the currently cached provider +// if there is one. If there is no current provider, true will be returned. +func (c *ChainProvider) IsExpired() bool { + if c.curr != nil { + return c.curr.IsExpired() + } + + return true +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider_test.go new file mode 100644 index 00000000000..4fba22f29f4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/chain_provider_test.go @@ -0,0 +1,73 @@ +package credentials + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/stretchr/testify/assert" +) + +func TestChainProviderGet(t *testing.T) { + p := &ChainProvider{ + Providers: []Provider{ + &stubProvider{err: awserr.New("FirstError", "first provider error", nil)}, + &stubProvider{err: awserr.New("SecondError", "second provider error", nil)}, + &stubProvider{ + creds: Value{ + AccessKeyID: "AKID", + SecretAccessKey: "SECRET", + SessionToken: "", + }, + }, + }, + } + + creds, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match") + assert.Empty(t, creds.SessionToken, "Expect session token to be empty") +} + +func TestChainProviderIsExpired(t *testing.T) { + stubProvider := &stubProvider{expired: true} + p := &ChainProvider{ + Providers: []Provider{ + stubProvider, + }, + } + + assert.True(t, p.IsExpired(), "Expect expired to be true before any Retrieve") + _, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + assert.False(t, p.IsExpired(), "Expect not expired after retrieve") + + stubProvider.expired = true + assert.True(t, p.IsExpired(), "Expect return of expired provider") + + _, err = p.Retrieve() + assert.False(t, p.IsExpired(), "Expect not expired after retrieve") +} + +func TestChainProviderWithNoProvider(t *testing.T) { + p := &ChainProvider{ + Providers: []Provider{}, + } + + assert.True(t, p.IsExpired(), "Expect expired with no providers") + _, err := p.Retrieve() + assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned") +} + +func TestChainProviderWithNoValidProvider(t *testing.T) { + p := &ChainProvider{ + Providers: []Provider{ + &stubProvider{err: awserr.New("FirstError", "first provider error", nil)}, + &stubProvider{err: awserr.New("SecondError", "second provider error", nil)}, + }, + } + + assert.True(t, p.IsExpired(), "Expect expired with no providers") + _, err := p.Retrieve() + assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned") +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials.go new file mode 100644 index 00000000000..2834a088aab --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials.go @@ -0,0 +1,220 @@ +// Package credentials provides credential retrieval and management +// +// The Credentials is the primary method of getting access to and managing +// credentials Values. Using dependency injection retrieval of the credential +// values is handled by a object which satisfies the Provider interface. +// +// By default the Credentials.Get() will cache the successful result of a +// Provider's Retrieve() until Provider.IsExpired() returns true. At which +// point Credentials will call Provider's Retrieve() to get new credential Value. +// +// The Provider is responsible for determining when credentials Value have expired. +// It is also important to note that Credentials will always call Retrieve the +// first time Credentials.Get() is called. +// +// Example of using the environment variable credentials. +// +// creds := NewEnvCredentials() +// +// // Retrieve the credentials value +// credValue, err := creds.Get() +// if err != nil { +// // handle error +// } +// +// Example of forcing credentials to expire and be refreshed on the next Get(). +// This may be helpful to proactively expire credentials and refresh them sooner +// than they would naturally expire on their own. +// +// creds := NewCredentials(&EC2RoleProvider{}) +// creds.Expire() +// credsValue, err := creds.Get() +// // New credentials will be retrieved instead of from cache. +// +// +// Custom Provider +// +// Each Provider built into this package also provides a helper method to generate +// a Credentials pointer setup with the provider. To use a custom Provider just +// create a type which satisfies the Provider interface and pass it to the +// NewCredentials method. +// +// type MyProvider struct{} +// func (m *MyProvider) Retrieve() (Value, error) {...} +// func (m *MyProvider) IsExpired() bool {...} +// +// creds := NewCredentials(&MyProvider{}) +// credValue, err := creds.Get() +// +package credentials + +import ( + "sync" + "time" +) + +// Create an empty Credential object that can be used as dummy placeholder +// credentials for requests that do not need signed. +// +// This Credentials can be used to configure a service to not sign requests +// when making service API calls. For example, when accessing public +// s3 buckets. +// +// svc := s3.New(&aws.Config{Credentials: AnonymousCredentials}) +// // Access public S3 buckets. +// +// @readonly +var AnonymousCredentials = NewStaticCredentials("", "", "") + +// A Value is the AWS credentials value for individual credential fields. +type Value struct { + // AWS Access key ID + AccessKeyID string + + // AWS Secret Access Key + SecretAccessKey string + + // AWS Session Token + SessionToken string +} + +// A Provider is the interface for any component which will provide credentials +// Value. A provider is required to manage its own Expired state, and what to +// be expired means. +// +// The Provider should not need to implement its own mutexes, because +// that will be managed by Credentials. +type Provider interface { + // Refresh returns nil if it successfully retrieved the value. + // Error is returned if the value were not obtainable, or empty. + Retrieve() (Value, error) + + // IsExpired returns if the credentials are no longer valid, and need + // to be retrieved. + IsExpired() bool +} + +// A Expiry provides shared expiration logic to be used by credentials +// providers to implement expiry functionality. +// +// The best method to use this struct is as an anonymous field within the +// provider's struct. +// +// Example: +// type EC2RoleProvider struct { +// Expiry +// ... +// } +type Expiry struct { + // The date/time when to expire on + expiration time.Time + + // If set will be used by IsExpired to determine the current time. + // Defaults to time.Now if CurrentTime is not set. Available for testing + // to be able to mock out the current time. + CurrentTime func() time.Time +} + +// SetExpiration sets the expiration IsExpired will check when called. +// +// If window is greater than 0 the expiration time will be reduced by the +// window value. +// +// Using a window is helpful to trigger credentials to expire sooner than +// the expiration time given to ensure no requests are made with expired +// tokens. +func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) { + e.expiration = expiration + if window > 0 { + e.expiration = e.expiration.Add(-window) + } +} + +// IsExpired returns if the credentials are expired. +func (e *Expiry) IsExpired() bool { + if e.CurrentTime == nil { + e.CurrentTime = time.Now + } + return e.expiration.Before(e.CurrentTime()) +} + +// A Credentials provides synchronous safe retrieval of AWS credentials Value. +// Credentials will cache the credentials value until they expire. Once the value +// expires the next Get will attempt to retrieve valid credentials. +// +// Credentials is safe to use across multiple goroutines and will manage the +// synchronous state so the Providers do not need to implement their own +// synchronization. +// +// The first Credentials.Get() will always call Provider.Retrieve() to get the +// first instance of the credentials Value. All calls to Get() after that +// will return the cached credentials Value until IsExpired() returns true. +type Credentials struct { + creds Value + forceRefresh bool + m sync.Mutex + + provider Provider +} + +// NewCredentials returns a pointer to a new Credentials with the provider set. +func NewCredentials(provider Provider) *Credentials { + return &Credentials{ + provider: provider, + forceRefresh: true, + } +} + +// Get returns the credentials value, or error if the credentials Value failed +// to be retrieved. +// +// Will return the cached credentials Value if it has not expired. If the +// credentials Value has expired the Provider's Retrieve() will be called +// to refresh the credentials. +// +// If Credentials.Expire() was called the credentials Value will be force +// expired, and the next call to Get() will cause them to be refreshed. +func (c *Credentials) Get() (Value, error) { + c.m.Lock() + defer c.m.Unlock() + + if c.isExpired() { + creds, err := c.provider.Retrieve() + if err != nil { + return Value{}, err + } + c.creds = creds + c.forceRefresh = false + } + + return c.creds, nil +} + +// Expire expires the credentials and forces them to be retrieved on the +// next call to Get(). +// +// This will override the Provider's expired state, and force Credentials +// to call the Provider's Retrieve(). +func (c *Credentials) Expire() { + c.m.Lock() + defer c.m.Unlock() + + c.forceRefresh = true +} + +// IsExpired returns if the credentials are no longer valid, and need +// to be retrieved. +// +// If the Credentials were forced to be expired with Expire() this will +// reflect that override. +func (c *Credentials) IsExpired() bool { + c.m.Lock() + defer c.m.Unlock() + + return c.isExpired() +} + +// isExpired helper method wrapping the definition of expired credentials. +func (c *Credentials) isExpired() bool { + return c.forceRefresh || c.provider.IsExpired() +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials_test.go new file mode 100644 index 00000000000..99c2b47742e --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/credentials_test.go @@ -0,0 +1,62 @@ +package credentials + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/stretchr/testify/assert" +) + +type stubProvider struct { + creds Value + expired bool + err error +} + +func (s *stubProvider) Retrieve() (Value, error) { + s.expired = false + return s.creds, s.err +} +func (s *stubProvider) IsExpired() bool { + return s.expired +} + +func TestCredentialsGet(t *testing.T) { + c := NewCredentials(&stubProvider{ + creds: Value{ + AccessKeyID: "AKID", + SecretAccessKey: "SECRET", + SessionToken: "", + }, + expired: true, + }) + + creds, err := c.Get() + assert.Nil(t, err, "Expected no error") + assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match") + assert.Empty(t, creds.SessionToken, "Expect session token to be empty") +} + +func TestCredentialsGetWithError(t *testing.T) { + c := NewCredentials(&stubProvider{err: awserr.New("provider error", "", nil), expired: true}) + + _, err := c.Get() + assert.Equal(t, "provider error", err.(awserr.Error).Code(), "Expected provider error") +} + +func TestCredentialsExpire(t *testing.T) { + stub := &stubProvider{} + c := NewCredentials(stub) + + stub.expired = false + assert.True(t, c.IsExpired(), "Expected to start out expired") + c.Expire() + assert.True(t, c.IsExpired(), "Expected to be expired") + + c.forceRefresh = false + assert.False(t, c.IsExpired(), "Expected not to be expired") + + stub.expired = true + assert.True(t, c.IsExpired(), "Expected to be expired") +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider.go new file mode 100644 index 00000000000..0eecbe3b125 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider.go @@ -0,0 +1,162 @@ +package credentials + +import ( + "bufio" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +const metadataCredentialsEndpoint = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" + +// A EC2RoleProvider retrieves credentials from the EC2 service, and keeps track if +// those credentials are expired. +// +// Example how to configure the EC2RoleProvider with custom http Client, Endpoint +// or ExpiryWindow +// +// p := &credentials.EC2RoleProvider{ +// // Pass in a custom timeout to be used when requesting +// // IAM EC2 Role credentials. +// Client: &http.Client{ +// Timeout: 10 * time.Second, +// }, +// // Use default EC2 Role metadata endpoint, Alternate endpoints can be +// // specified setting Endpoint to something else. +// Endpoint: "", +// // Do not use early expiry of credentials. If a non zero value is +// // specified the credentials will be expired early +// ExpiryWindow: 0, +// } +type EC2RoleProvider struct { + Expiry + + // Endpoint must be fully quantified URL + Endpoint string + + // HTTP client to use when connecting to EC2 service + Client *http.Client + + // ExpiryWindow will allow the credentials to trigger refreshing prior to + // the credentials actually expiring. This is beneficial so race conditions + // with expiring credentials do not cause request to fail unexpectedly + // due to ExpiredTokenException exceptions. + // + // So a ExpiryWindow of 10s would cause calls to IsExpired() to return true + // 10 seconds before the credentials are actually expired. + // + // If ExpiryWindow is 0 or less it will be ignored. + ExpiryWindow time.Duration +} + +// NewEC2RoleCredentials returns a pointer to a new Credentials object +// wrapping the EC2RoleProvider. +// +// Takes a custom http.Client which can be configured for custom handling of +// things such as timeout. +// +// Endpoint is the URL that the EC2RoleProvider will connect to when retrieving +// role and credentials. +// +// Window is the expiry window that will be subtracted from the expiry returned +// by the role credential request. This is done so that the credentials will +// expire sooner than their actual lifespan. +func NewEC2RoleCredentials(client *http.Client, endpoint string, window time.Duration) *Credentials { + return NewCredentials(&EC2RoleProvider{ + Endpoint: endpoint, + Client: client, + ExpiryWindow: window, + }) +} + +// Retrieve retrieves credentials from the EC2 service. +// Error will be returned if the request fails, or unable to extract +// the desired credentials. +func (m *EC2RoleProvider) Retrieve() (Value, error) { + if m.Client == nil { + m.Client = http.DefaultClient + } + if m.Endpoint == "" { + m.Endpoint = metadataCredentialsEndpoint + } + + credsList, err := requestCredList(m.Client, m.Endpoint) + if err != nil { + return Value{}, err + } + + if len(credsList) == 0 { + return Value{}, awserr.New("EmptyEC2RoleList", "empty EC2 Role list", nil) + } + credsName := credsList[0] + + roleCreds, err := requestCred(m.Client, m.Endpoint, credsName) + if err != nil { + return Value{}, err + } + + m.SetExpiration(roleCreds.Expiration, m.ExpiryWindow) + + return Value{ + AccessKeyID: roleCreds.AccessKeyID, + SecretAccessKey: roleCreds.SecretAccessKey, + SessionToken: roleCreds.Token, + }, nil +} + +// A ec2RoleCredRespBody provides the shape for deserializing credential +// request responses. +type ec2RoleCredRespBody struct { + Expiration time.Time + AccessKeyID string + SecretAccessKey string + Token string +} + +// requestCredList requests a list of credentials from the EC2 service. +// If there are no credentials, or there is an error making or receiving the request +func requestCredList(client *http.Client, endpoint string) ([]string, error) { + resp, err := client.Get(endpoint) + if err != nil { + return nil, awserr.New("ListEC2Role", "failed to list EC2 Roles", err) + } + defer resp.Body.Close() + + credsList := []string{} + s := bufio.NewScanner(resp.Body) + for s.Scan() { + credsList = append(credsList, s.Text()) + } + + if err := s.Err(); err != nil { + return nil, awserr.New("ReadEC2Role", "failed to read list of EC2 Roles", err) + } + + return credsList, nil +} + +// requestCred requests the credentials for a specific credentials from the EC2 service. +// +// If the credentials cannot be found, or there is an error reading the response +// and error will be returned. +func requestCred(client *http.Client, endpoint, credsName string) (*ec2RoleCredRespBody, error) { + resp, err := client.Get(endpoint + credsName) + if err != nil { + return nil, awserr.New("GetEC2RoleCredentials", + fmt.Sprintf("failed to get %s EC2 Role credentials", credsName), + err) + } + defer resp.Body.Close() + + respCreds := &ec2RoleCredRespBody{} + if err := json.NewDecoder(resp.Body).Decode(respCreds); err != nil { + return nil, awserr.New("DecodeEC2RoleCredentials", + fmt.Sprintf("failed to decode %s EC2 Role credentials", credsName), + err) + } + + return respCreds, nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider_test.go new file mode 100644 index 00000000000..da1549a4f2e --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/ec2_role_provider_test.go @@ -0,0 +1,108 @@ +package credentials + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func initTestServer(expireOn string) *httptest.Server { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/" { + fmt.Fprintln(w, "/creds") + } else { + fmt.Fprintf(w, `{ + "AccessKeyId" : "accessKey", + "SecretAccessKey" : "secret", + "Token" : "token", + "Expiration" : "%s" +}`, expireOn) + } + })) + + return server +} + +func TestEC2RoleProvider(t *testing.T) { + server := initTestServer("2014-12-16T01:51:37Z") + defer server.Close() + + p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL} + + creds, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match") + assert.Equal(t, "token", creds.SessionToken, "Expect session token to match") +} + +func TestEC2RoleProviderIsExpired(t *testing.T) { + server := initTestServer("2014-12-16T01:51:37Z") + defer server.Close() + + p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL} + p.CurrentTime = func() time.Time { + return time.Date(2014, 12, 15, 21, 26, 0, 0, time.UTC) + } + + assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve.") + + _, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve.") + + p.CurrentTime = func() time.Time { + return time.Date(3014, 12, 15, 21, 26, 0, 0, time.UTC) + } + + assert.True(t, p.IsExpired(), "Expect creds to be expired.") +} + +func TestEC2RoleProviderExpiryWindowIsExpired(t *testing.T) { + server := initTestServer("2014-12-16T01:51:37Z") + defer server.Close() + + p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL, ExpiryWindow: time.Hour * 1} + p.CurrentTime = func() time.Time { + return time.Date(2014, 12, 15, 0, 51, 37, 0, time.UTC) + } + + assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve.") + + _, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve.") + + p.CurrentTime = func() time.Time { + return time.Date(2014, 12, 16, 0, 55, 37, 0, time.UTC) + } + + assert.True(t, p.IsExpired(), "Expect creds to be expired.") +} + +func BenchmarkEC2RoleProvider(b *testing.B) { + server := initTestServer("2014-12-16T01:51:37Z") + defer server.Close() + + p := &EC2RoleProvider{Client: http.DefaultClient, Endpoint: server.URL} + _, err := p.Retrieve() + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := p.Retrieve() + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go new file mode 100644 index 00000000000..043e861d6f2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider.go @@ -0,0 +1,73 @@ +package credentials + +import ( + "os" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +var ( + // ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be + // found in the process's environment. + // + // @readonly + ErrAccessKeyIDNotFound = awserr.New("EnvAccessKeyNotFound", "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment", nil) + + // ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key + // can't be found in the process's environment. + // + // @readonly + ErrSecretAccessKeyNotFound = awserr.New("EnvSecretNotFound", "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment", nil) +) + +// A EnvProvider retrieves credentials from the environment variables of the +// running process. Environment credentials never expire. +// +// Environment variables used: +// +// * Access Key ID: AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY +// * Secret Access Key: AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY +type EnvProvider struct { + retrieved bool +} + +// NewEnvCredentials returns a pointer to a new Credentials object +// wrapping the environment variable provider. +func NewEnvCredentials() *Credentials { + return NewCredentials(&EnvProvider{}) +} + +// Retrieve retrieves the keys from the environment. +func (e *EnvProvider) Retrieve() (Value, error) { + e.retrieved = false + + id := os.Getenv("AWS_ACCESS_KEY_ID") + if id == "" { + id = os.Getenv("AWS_ACCESS_KEY") + } + + secret := os.Getenv("AWS_SECRET_ACCESS_KEY") + if secret == "" { + secret = os.Getenv("AWS_SECRET_KEY") + } + + if id == "" { + return Value{}, ErrAccessKeyIDNotFound + } + + if secret == "" { + return Value{}, ErrSecretAccessKeyNotFound + } + + e.retrieved = true + return Value{ + AccessKeyID: id, + SecretAccessKey: secret, + SessionToken: os.Getenv("AWS_SESSION_TOKEN"), + }, nil +} + +// IsExpired returns if the credentials have been retrieved. +func (e *EnvProvider) IsExpired() bool { + return !e.retrieved +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider_test.go new file mode 100644 index 00000000000..53f6ce256eb --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/env_provider_test.go @@ -0,0 +1,70 @@ +package credentials + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestEnvProviderRetrieve(t *testing.T) { + os.Clearenv() + os.Setenv("AWS_ACCESS_KEY_ID", "access") + os.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + os.Setenv("AWS_SESSION_TOKEN", "token") + + e := EnvProvider{} + creds, err := e.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "access", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match") + assert.Equal(t, "token", creds.SessionToken, "Expect session token to match") +} + +func TestEnvProviderIsExpired(t *testing.T) { + os.Clearenv() + os.Setenv("AWS_ACCESS_KEY_ID", "access") + os.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + os.Setenv("AWS_SESSION_TOKEN", "token") + + e := EnvProvider{} + + assert.True(t, e.IsExpired(), "Expect creds to be expired before retrieve.") + + _, err := e.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.False(t, e.IsExpired(), "Expect creds to not be expired after retrieve.") +} + +func TestEnvProviderNoAccessKeyID(t *testing.T) { + os.Clearenv() + os.Setenv("AWS_SECRET_ACCESS_KEY", "secret") + + e := EnvProvider{} + creds, err := e.Retrieve() + assert.Equal(t, ErrAccessKeyIDNotFound, err, "ErrAccessKeyIDNotFound expected, but was %#v error: %#v", creds, err) +} + +func TestEnvProviderNoSecretAccessKey(t *testing.T) { + os.Clearenv() + os.Setenv("AWS_ACCESS_KEY_ID", "access") + + e := EnvProvider{} + creds, err := e.Retrieve() + assert.Equal(t, ErrSecretAccessKeyNotFound, err, "ErrSecretAccessKeyNotFound expected, but was %#v error: %#v", creds, err) +} + +func TestEnvProviderAlternateNames(t *testing.T) { + os.Clearenv() + os.Setenv("AWS_ACCESS_KEY", "access") + os.Setenv("AWS_SECRET_KEY", "secret") + + e := EnvProvider{} + creds, err := e.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "access", creds.AccessKeyID, "Expected access key ID") + assert.Equal(t, "secret", creds.SecretAccessKey, "Expected secret access key") + assert.Empty(t, creds.SessionToken, "Expected no token") +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/example.ini b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/example.ini new file mode 100644 index 00000000000..aa2dc506ad0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/example.ini @@ -0,0 +1,8 @@ +[default] +aws_access_key_id = accessKey +aws_secret_access_key = secret +aws_session_token = token + +[no_token] +aws_access_key_id = accessKey +aws_secret_access_key = secret diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go new file mode 100644 index 00000000000..b457e63a338 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider.go @@ -0,0 +1,135 @@ +package credentials + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/vaughan0/go-ini" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +var ( + // ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found. + // + // @readonly + ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil) +) + +// A SharedCredentialsProvider retrieves credentials from the current user's home +// directory, and keeps track if those credentials are expired. +// +// Profile ini file example: $HOME/.aws/credentials +type SharedCredentialsProvider struct { + // Path to the shared credentials file. If empty will default to current user's + // home directory. + Filename string + + // AWS Profile to extract credentials from the shared credentials file. If empty + // will default to environment variable "AWS_PROFILE" or "default" if + // environment variable is also not set. + Profile string + + // retrieved states if the credentials have been successfully retrieved. + retrieved bool +} + +// NewSharedCredentials returns a pointer to a new Credentials object +// wrapping the Profile file provider. +func NewSharedCredentials(filename, profile string) *Credentials { + return NewCredentials(&SharedCredentialsProvider{ + Filename: filename, + Profile: profile, + }) +} + +// Retrieve reads and extracts the shared credentials from the current +// users home directory. +func (p *SharedCredentialsProvider) Retrieve() (Value, error) { + p.retrieved = false + + filename, err := p.filename() + if err != nil { + return Value{}, err + } + + creds, err := loadProfile(filename, p.profile()) + if err != nil { + return Value{}, err + } + + p.retrieved = true + return creds, nil +} + +// IsExpired returns if the shared credentials have expired. +func (p *SharedCredentialsProvider) IsExpired() bool { + return !p.retrieved +} + +// loadProfiles loads from the file pointed to by shared credentials filename for profile. +// The credentials retrieved from the profile will be returned or error. Error will be +// returned if it fails to read from the file, or the data is invalid. +func loadProfile(filename, profile string) (Value, error) { + config, err := ini.LoadFile(filename) + if err != nil { + return Value{}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err) + } + iniProfile := config.Section(profile) + + id, ok := iniProfile["aws_access_key_id"] + if !ok { + return Value{}, awserr.New("SharedCredsAccessKey", + fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename), + nil) + } + + secret, ok := iniProfile["aws_secret_access_key"] + if !ok { + return Value{}, awserr.New("SharedCredsSecret", + fmt.Sprintf("shared credentials %s in %s did not contain aws_secret_access_key", profile, filename), + nil) + } + + token := iniProfile["aws_session_token"] + + return Value{ + AccessKeyID: id, + SecretAccessKey: secret, + SessionToken: token, + }, nil +} + +// filename returns the filename to use to read AWS shared credentials. +// +// Will return an error if the user's home directory path cannot be found. +func (p *SharedCredentialsProvider) filename() (string, error) { + if p.Filename == "" { + homeDir := os.Getenv("HOME") // *nix + if homeDir == "" { // Windows + homeDir = os.Getenv("USERPROFILE") + } + if homeDir == "" { + return "", ErrSharedCredentialsHomeNotFound + } + + p.Filename = filepath.Join(homeDir, ".aws", "credentials") + } + + return p.Filename, nil +} + +// profile returns the AWS shared credentials profile. If empty will read +// environment variable "AWS_PROFILE". If that is not set profile will +// return "default". +func (p *SharedCredentialsProvider) profile() string { + if p.Profile == "" { + p.Profile = os.Getenv("AWS_PROFILE") + } + if p.Profile == "" { + p.Profile = "default" + } + + return p.Profile +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider_test.go new file mode 100644 index 00000000000..3621d56eddc --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/shared_credentials_provider_test.go @@ -0,0 +1,77 @@ +package credentials + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestSharedCredentialsProvider(t *testing.T) { + os.Clearenv() + + p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""} + creds, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match") + assert.Equal(t, "token", creds.SessionToken, "Expect session token to match") +} + +func TestSharedCredentialsProviderIsExpired(t *testing.T) { + os.Clearenv() + + p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""} + + assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve") + + _, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve") +} + +func TestSharedCredentialsProviderWithAWS_PROFILE(t *testing.T) { + os.Clearenv() + os.Setenv("AWS_PROFILE", "no_token") + + p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""} + creds, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match") + assert.Empty(t, creds.SessionToken, "Expect no token") +} + +func TestSharedCredentialsProviderWithoutTokenFromProfile(t *testing.T) { + os.Clearenv() + + p := SharedCredentialsProvider{Filename: "example.ini", Profile: "no_token"} + creds, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match") + assert.Empty(t, creds.SessionToken, "Expect no token") +} + +func BenchmarkSharedCredentialsProvider(b *testing.B) { + os.Clearenv() + + p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""} + _, err := p.Retrieve() + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := p.Retrieve() + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go new file mode 100644 index 00000000000..530a9ac2f36 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider.go @@ -0,0 +1,44 @@ +package credentials + +import ( + "github.com/aws/aws-sdk-go/aws/awserr" +) + +var ( + // ErrStaticCredentialsEmpty is emitted when static credentials are empty. + // + // @readonly + ErrStaticCredentialsEmpty = awserr.New("EmptyStaticCreds", "static credentials are empty", nil) +) + +// A StaticProvider is a set of credentials which are set pragmatically, +// and will never expire. +type StaticProvider struct { + Value +} + +// NewStaticCredentials returns a pointer to a new Credentials object +// wrapping a static credentials value provider. +func NewStaticCredentials(id, secret, token string) *Credentials { + return NewCredentials(&StaticProvider{Value: Value{ + AccessKeyID: id, + SecretAccessKey: secret, + SessionToken: token, + }}) +} + +// Retrieve returns the credentials or error if the credentials are invalid. +func (s *StaticProvider) Retrieve() (Value, error) { + if s.AccessKeyID == "" || s.SecretAccessKey == "" { + return Value{}, ErrStaticCredentialsEmpty + } + + return s.Value, nil +} + +// IsExpired returns if the credentials are expired. +// +// For StaticProvider, the credentials never expired. +func (s *StaticProvider) IsExpired() bool { + return false +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider_test.go new file mode 100644 index 00000000000..ea012369624 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/static_provider_test.go @@ -0,0 +1,34 @@ +package credentials + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestStaticProviderGet(t *testing.T) { + s := StaticProvider{ + Value: Value{ + AccessKeyID: "AKID", + SecretAccessKey: "SECRET", + SessionToken: "", + }, + } + + creds, err := s.Retrieve() + assert.Nil(t, err, "Expect no error") + assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match") + assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match") + assert.Empty(t, creds.SessionToken, "Expect no session token") +} + +func TestStaticProviderIsExpired(t *testing.T) { + s := StaticProvider{ + Value: Value{ + AccessKeyID: "AKID", + SecretAccessKey: "SECRET", + SessionToken: "", + }, + } + + assert.False(t, s.IsExpired(), "Expect static credentials to never expire") +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go new file mode 100644 index 00000000000..80890b9d0dd --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider.go @@ -0,0 +1,120 @@ +// Package stscreds are credential Providers to retrieve STS AWS credentials. +// +// STS provides multiple ways to retrieve credentials which can be used when making +// future AWS service API operation calls. +package stscreds + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/service/sts" + "time" +) + +// AssumeRoler represents the minimal subset of the STS client API used by this provider. +type AssumeRoler interface { + AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) +} + +// AssumeRoleProvider retrieves temporary credentials from the STS service, and +// keeps track of their expiration time. This provider must be used explicitly, +// as it is not included in the credentials chain. +// +// Example how to configure a service to use this provider: +// +// config := &aws.Config{ +// Credentials: stscreds.NewCredentials(nil, "arn-of-the-role-to-assume", 10*time.Second), +// }) +// // Use config for creating your AWS service. +// +// Example how to obtain customised credentials: +// +// provider := &stscreds.Provider{ +// // Extend the duration to 1 hour. +// Duration: time.Hour, +// // Custom role name. +// RoleSessionName: "custom-session-name", +// } +// creds := credentials.NewCredentials(provider) +// +type AssumeRoleProvider struct { + credentials.Expiry + + // Custom STS client. If not set the default STS client will be used. + Client AssumeRoler + + // Role to be assumed. + RoleARN string + + // Session name, if you wish to reuse the credentials elsewhere. + RoleSessionName string + + // Expiry duration of the STS credentials. Defaults to 15 minutes if not set. + Duration time.Duration + + // ExpiryWindow will allow the credentials to trigger refreshing prior to + // the credentials actually expiring. This is beneficial so race conditions + // with expiring credentials do not cause request to fail unexpectedly + // due to ExpiredTokenException exceptions. + // + // So a ExpiryWindow of 10s would cause calls to IsExpired() to return true + // 10 seconds before the credentials are actually expired. + // + // If ExpiryWindow is 0 or less it will be ignored. + ExpiryWindow time.Duration +} + +// NewCredentials returns a pointer to a new Credentials object wrapping the +// AssumeRoleProvider. The credentials will expire every 15 minutes and the +// role will be named after a nanosecond timestamp of this operation. +// +// The sts and roleARN parameters are used for building the "AssumeRole" call. +// Pass nil as sts to use the default client. +// +// Window is the expiry window that will be subtracted from the expiry returned +// by the role credential request. This is done so that the credentials will +// expire sooner than their actual lifespan. +func NewCredentials(client AssumeRoler, roleARN string, window time.Duration) *credentials.Credentials { + return credentials.NewCredentials(&AssumeRoleProvider{ + Client: client, + RoleARN: roleARN, + ExpiryWindow: window, + }) +} + +// Retrieve generates a new set of temporary credentials using STS. +func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) { + + // Apply defaults where parameters are not set. + if p.Client == nil { + p.Client = sts.New(nil) + } + if p.RoleSessionName == "" { + // Try to work out a role name that will hopefully end up unique. + p.RoleSessionName = fmt.Sprintf("%d", time.Now().UTC().UnixNano()) + } + if p.Duration == 0 { + // Expire as often as AWS permits. + p.Duration = 15 * time.Minute + } + + roleOutput, err := p.Client.AssumeRole(&sts.AssumeRoleInput{ + DurationSeconds: aws.Int64(int64(p.Duration / time.Second)), + RoleARN: aws.String(p.RoleARN), + RoleSessionName: aws.String(p.RoleSessionName), + }) + + if err != nil { + return credentials.Value{}, err + } + + // We will proactively generate new credentials before they expire. + p.SetExpiration(*roleOutput.Credentials.Expiration, p.ExpiryWindow) + + return credentials.Value{ + AccessKeyID: *roleOutput.Credentials.AccessKeyID, + SecretAccessKey: *roleOutput.Credentials.SecretAccessKey, + SessionToken: *roleOutput.Credentials.SessionToken, + }, nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider_test.go new file mode 100644 index 00000000000..98b7690026b --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/credentials/stscreds/assume_role_provider_test.go @@ -0,0 +1,58 @@ +package stscreds + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +type stubSTS struct { +} + +func (s *stubSTS) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { + expiry := time.Now().Add(60 * time.Minute) + return &sts.AssumeRoleOutput{ + Credentials: &sts.Credentials{ + // Just reflect the role arn to the provider. + AccessKeyID: input.RoleARN, + SecretAccessKey: aws.String("assumedSecretAccessKey"), + SessionToken: aws.String("assumedSessionToken"), + Expiration: &expiry, + }, + }, nil +} + +func TestAssumeRoleProvider(t *testing.T) { + stub := &stubSTS{} + p := &AssumeRoleProvider{ + Client: stub, + RoleARN: "roleARN", + } + + creds, err := p.Retrieve() + assert.Nil(t, err, "Expect no error") + + assert.Equal(t, "roleARN", creds.AccessKeyID, "Expect access key ID to be reflected role ARN") + assert.Equal(t, "assumedSecretAccessKey", creds.SecretAccessKey, "Expect secret access key to match") + assert.Equal(t, "assumedSessionToken", creds.SessionToken, "Expect session token to match") +} + +func BenchmarkAssumeRoleProvider(b *testing.B) { + stub := &stubSTS{} + p := &AssumeRoleProvider{ + Client: stub, + RoleARN: "roleARN", + } + + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + _, err := p.Retrieve() + if err != nil { + b.Fatal(err) + } + } + }) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions.go new file mode 100644 index 00000000000..45ae8807359 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions.go @@ -0,0 +1,157 @@ +package aws + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "regexp" + "strconv" + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +var sleepDelay = func(delay time.Duration) { + time.Sleep(delay) +} + +// Interface for matching types which also have a Len method. +type lener interface { + Len() int +} + +// BuildContentLength builds the content length of a request based on the body, +// or will use the HTTPRequest.Header's "Content-Length" if defined. If unable +// to determine request body length and no "Content-Length" was specified it will panic. +func BuildContentLength(r *Request) { + if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" { + length, _ := strconv.ParseInt(slength, 10, 64) + r.HTTPRequest.ContentLength = length + return + } + + var length int64 + switch body := r.Body.(type) { + case nil: + length = 0 + case lener: + length = int64(body.Len()) + case io.Seeker: + r.bodyStart, _ = body.Seek(0, 1) + end, _ := body.Seek(0, 2) + body.Seek(r.bodyStart, 0) // make sure to seek back to original location + length = end - r.bodyStart + default: + panic("Cannot get length of body, must provide `ContentLength`") + } + + r.HTTPRequest.ContentLength = length + r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length)) +} + +// UserAgentHandler is a request handler for injecting User agent into requests. +func UserAgentHandler(r *Request) { + r.HTTPRequest.Header.Set("User-Agent", SDKName+"/"+SDKVersion) +} + +var reStatusCode = regexp.MustCompile(`^(\d+)`) + +// SendHandler is a request handler to send service request using HTTP client. +func SendHandler(r *Request) { + var err error + r.HTTPResponse, err = r.Service.Config.HTTPClient.Do(r.HTTPRequest) + if err != nil { + // Capture the case where url.Error is returned for error processing + // response. e.g. 301 without location header comes back as string + // error and r.HTTPResponse is nil. Other url redirect errors will + // comeback in a similar method. + if e, ok := err.(*url.Error); ok { + if s := reStatusCode.FindStringSubmatch(e.Error()); s != nil { + code, _ := strconv.ParseInt(s[1], 10, 64) + r.HTTPResponse = &http.Response{ + StatusCode: int(code), + Status: http.StatusText(int(code)), + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + } + return + } + } + if r.HTTPRequest == nil { + // Add a dummy request response object to ensure the HTTPResponse + // value is consistent. + r.HTTPResponse = &http.Response{ + StatusCode: int(0), + Status: http.StatusText(int(0)), + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + } + } + // Catch all other request errors. + r.Error = awserr.New("RequestError", "send request failed", err) + r.Retryable = Bool(true) // network errors are retryable + } +} + +// ValidateResponseHandler is a request handler to validate service response. +func ValidateResponseHandler(r *Request) { + if r.HTTPResponse.StatusCode == 0 || r.HTTPResponse.StatusCode >= 300 { + // this may be replaced by an UnmarshalError handler + r.Error = awserr.New("UnknownError", "unknown error", nil) + } +} + +// AfterRetryHandler performs final checks to determine if the request should +// be retried and how long to delay. +func AfterRetryHandler(r *Request) { + // If one of the other handlers already set the retry state + // we don't want to override it based on the service's state + if r.Retryable == nil { + r.Retryable = Bool(r.Service.ShouldRetry(r)) + } + + if r.WillRetry() { + r.RetryDelay = r.Service.RetryRules(r) + sleepDelay(r.RetryDelay) + + // when the expired token exception occurs the credentials + // need to be expired locally so that the next request to + // get credentials will trigger a credentials refresh. + if r.Error != nil { + if err, ok := r.Error.(awserr.Error); ok { + if isCodeExpiredCreds(err.Code()) { + r.Config.Credentials.Expire() + } + } + } + + r.RetryCount++ + r.Error = nil + } +} + +var ( + // ErrMissingRegion is an error that is returned if region configuration is + // not found. + // + // @readonly + ErrMissingRegion error = awserr.New("MissingRegion", "could not find region configuration", nil) + + // ErrMissingEndpoint is an error that is returned if an endpoint cannot be + // resolved for a service. + // + // @readonly + ErrMissingEndpoint error = awserr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil) +) + +// ValidateEndpointHandler is a request handler to validate a request had the +// appropriate Region and Endpoint set. Will set r.Error if the endpoint or +// region is not valid. +func ValidateEndpointHandler(r *Request) { + if r.Service.SigningRegion == "" && StringValue(r.Service.Config.Region) == "" { + r.Error = ErrMissingRegion + } else if r.Service.Endpoint == "" { + r.Error = ErrMissingEndpoint + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions_test.go new file mode 100644 index 00000000000..bc6a8f29c1e --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handler_functions_test.go @@ -0,0 +1,81 @@ +package aws + +import ( + "net/http" + "os" + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/stretchr/testify/assert" +) + +func TestValidateEndpointHandler(t *testing.T) { + os.Clearenv() + svc := NewService(NewConfig().WithRegion("us-west-2")) + svc.Handlers.Clear() + svc.Handlers.Validate.PushBack(ValidateEndpointHandler) + + req := NewRequest(svc, &Operation{Name: "Operation"}, nil, nil) + err := req.Build() + + assert.NoError(t, err) +} + +func TestValidateEndpointHandlerErrorRegion(t *testing.T) { + os.Clearenv() + svc := NewService(nil) + svc.Handlers.Clear() + svc.Handlers.Validate.PushBack(ValidateEndpointHandler) + + req := NewRequest(svc, &Operation{Name: "Operation"}, nil, nil) + err := req.Build() + + assert.Error(t, err) + assert.Equal(t, ErrMissingRegion, err) +} + +type mockCredsProvider struct { + expired bool + retrieveCalled bool +} + +func (m *mockCredsProvider) Retrieve() (credentials.Value, error) { + m.retrieveCalled = true + return credentials.Value{}, nil +} + +func (m *mockCredsProvider) IsExpired() bool { + return m.expired +} + +func TestAfterRetryRefreshCreds(t *testing.T) { + os.Clearenv() + credProvider := &mockCredsProvider{} + svc := NewService(&Config{Credentials: credentials.NewCredentials(credProvider), MaxRetries: Int(1)}) + + svc.Handlers.Clear() + svc.Handlers.ValidateResponse.PushBack(func(r *Request) { + r.Error = awserr.New("UnknownError", "", nil) + r.HTTPResponse = &http.Response{StatusCode: 400} + }) + svc.Handlers.UnmarshalError.PushBack(func(r *Request) { + r.Error = awserr.New("ExpiredTokenException", "", nil) + }) + svc.Handlers.AfterRetry.PushBack(func(r *Request) { + AfterRetryHandler(r) + }) + + assert.True(t, svc.Config.Credentials.IsExpired(), "Expect to start out expired") + assert.False(t, credProvider.retrieveCalled) + + req := NewRequest(svc, &Operation{Name: "Operation"}, nil, nil) + req.Send() + + assert.True(t, svc.Config.Credentials.IsExpired()) + assert.False(t, credProvider.retrieveCalled) + + _, err := svc.Config.Credentials.Get() + assert.NoError(t, err) + assert.True(t, credProvider.retrieveCalled) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers.go new file mode 100644 index 00000000000..1968cb9f8b1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers.go @@ -0,0 +1,85 @@ +package aws + +// A Handlers provides a collection of request handlers for various +// stages of handling requests. +type Handlers struct { + Validate HandlerList + Build HandlerList + Sign HandlerList + Send HandlerList + ValidateResponse HandlerList + Unmarshal HandlerList + UnmarshalMeta HandlerList + UnmarshalError HandlerList + Retry HandlerList + AfterRetry HandlerList +} + +// copy returns of this handler's lists. +func (h *Handlers) copy() Handlers { + return Handlers{ + Validate: h.Validate.copy(), + Build: h.Build.copy(), + Sign: h.Sign.copy(), + Send: h.Send.copy(), + ValidateResponse: h.ValidateResponse.copy(), + Unmarshal: h.Unmarshal.copy(), + UnmarshalError: h.UnmarshalError.copy(), + UnmarshalMeta: h.UnmarshalMeta.copy(), + Retry: h.Retry.copy(), + AfterRetry: h.AfterRetry.copy(), + } +} + +// Clear removes callback functions for all handlers +func (h *Handlers) Clear() { + h.Validate.Clear() + h.Build.Clear() + h.Send.Clear() + h.Sign.Clear() + h.Unmarshal.Clear() + h.UnmarshalMeta.Clear() + h.UnmarshalError.Clear() + h.ValidateResponse.Clear() + h.Retry.Clear() + h.AfterRetry.Clear() +} + +// A HandlerList manages zero or more handlers in a list. +type HandlerList struct { + list []func(*Request) +} + +// copy creates a copy of the handler list. +func (l *HandlerList) copy() HandlerList { + var n HandlerList + n.list = append([]func(*Request){}, l.list...) + return n +} + +// Clear clears the handler list. +func (l *HandlerList) Clear() { + l.list = []func(*Request){} +} + +// Len returns the number of handlers in the list. +func (l *HandlerList) Len() int { + return len(l.list) +} + +// PushBack pushes handlers f to the back of the handler list. +func (l *HandlerList) PushBack(f ...func(*Request)) { + l.list = append(l.list, f...) +} + +// PushFront pushes handlers f to the front of the handler list. +func (l *HandlerList) PushFront(f ...func(*Request)) { + l.list = append(f, l.list...) +} + +// Run executes all handlers in the list with a given request object. +func (l *HandlerList) Run(r *Request) { + for _, f := range l.list { + f(r) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers_test.go new file mode 100644 index 00000000000..944e1d3edcf --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/handlers_test.go @@ -0,0 +1,31 @@ +package aws + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHandlerList(t *testing.T) { + s := "" + r := &Request{} + l := HandlerList{} + l.PushBack(func(r *Request) { + s += "a" + r.Data = s + }) + l.Run(r) + assert.Equal(t, "a", s) + assert.Equal(t, "a", r.Data) +} + +func TestMultipleHandlers(t *testing.T) { + r := &Request{} + l := HandlerList{} + l.PushBack(func(r *Request) { r.Data = nil }) + l.PushFront(func(r *Request) { r.Data = Bool(true) }) + l.Run(r) + if r.Data != nil { + t.Error("Expected handler to execute") + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/logger.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/logger.go new file mode 100644 index 00000000000..935661c0b70 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/logger.go @@ -0,0 +1,89 @@ +package aws + +import ( + "log" + "os" +) + +// A LogLevelType defines the level logging should be performed at. Used to instruct +// the SDK which statements should be logged. +type LogLevelType uint + +// LogLevel returns the pointer to a LogLevel. Should be used to workaround +// not being able to take the address of a non-composite literal. +func LogLevel(l LogLevelType) *LogLevelType { + return &l +} + +// Value returns the LogLevel value or the default value LogOff if the LogLevel +// is nil. Safe to use on nil value LogLevelTypes. +func (l *LogLevelType) Value() LogLevelType { + if l != nil { + return *l + } + return LogOff +} + +// Matches returns true if the v LogLevel is enabled by this LogLevel. Should be +// used with logging sub levels. Is safe to use on nil value LogLevelTypes. If +// LogLevel is nill, will default to LogOff comparison. +func (l *LogLevelType) Matches(v LogLevelType) bool { + c := l.Value() + return c&v == v +} + +// AtLeast returns true if this LogLevel is at least high enough to satisfies v. +// Is safe to use on nil value LogLevelTypes. If LogLevel is nill, will default +// to LogOff comparison. +func (l *LogLevelType) AtLeast(v LogLevelType) bool { + c := l.Value() + return c >= v +} + +const ( + // LogOff states that no logging should be performed by the SDK. This is the + // default state of the SDK, and should be use to disable all logging. + LogOff LogLevelType = iota * 0x1000 + + // LogDebug state that debug output should be logged by the SDK. This should + // be used to inspect request made and responses received. + LogDebug +) + +// Debug Logging Sub Levels +const ( + // LogDebugWithSigning states that the SDK should log request signing and + // presigning events. This should be used to log the signing details of + // requests for debugging. Will also enable LogDebug. + LogDebugWithSigning LogLevelType = LogDebug | (1 << iota) + + // LogDebugWithHTTPBody states the SDK should log HTTP request and response + // HTTP bodys in addition to the headers and path. This should be used to + // see the body content of requests and responses made while using the SDK + // Will also enable LogDebug. + LogDebugWithHTTPBody +) + +// A Logger is a minimalistic interface for the SDK to log messages to. Should +// be used to provide custom logging writers for the SDK to use. +type Logger interface { + Log(...interface{}) +} + +// NewDefaultLogger returns a Logger which will write log messages to stdout, and +// use same formatting runes as the stdlib log.Logger +func NewDefaultLogger() Logger { + return &defaultLogger{ + logger: log.New(os.Stdout, "", log.LstdFlags), + } +} + +// A defaultLogger provides a minimalistic logger satisfying the Logger interface. +type defaultLogger struct { + logger *log.Logger +} + +// Log logs the parameters to the stdlib logger. See log.Println. +func (l defaultLogger) Log(args ...interface{}) { + l.logger.Println(args...) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator.go new file mode 100644 index 00000000000..b4e95cebd57 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator.go @@ -0,0 +1,89 @@ +package aws + +import ( + "fmt" + "reflect" + "strings" + + "github.com/aws/aws-sdk-go/aws/awserr" +) + +// ValidateParameters is a request handler to validate the input parameters. +// Validating parameters only has meaning if done prior to the request being sent. +func ValidateParameters(r *Request) { + if r.ParamsFilled() { + v := validator{errors: []string{}} + v.validateAny(reflect.ValueOf(r.Params), "") + + if count := len(v.errors); count > 0 { + format := "%d validation errors:\n- %s" + msg := fmt.Sprintf(format, count, strings.Join(v.errors, "\n- ")) + r.Error = awserr.New("InvalidParameter", msg, nil) + } + } +} + +// A validator validates values. Collects validations errors which occurs. +type validator struct { + errors []string +} + +// validateAny will validate any struct, slice or map type. All validations +// are also performed recursively for nested types. +func (v *validator) validateAny(value reflect.Value, path string) { + value = reflect.Indirect(value) + if !value.IsValid() { + return + } + + switch value.Kind() { + case reflect.Struct: + v.validateStruct(value, path) + case reflect.Slice: + for i := 0; i < value.Len(); i++ { + v.validateAny(value.Index(i), path+fmt.Sprintf("[%d]", i)) + } + case reflect.Map: + for _, n := range value.MapKeys() { + v.validateAny(value.MapIndex(n), path+fmt.Sprintf("[%q]", n.String())) + } + } +} + +// validateStruct will validate the struct value's fields. If the structure has +// nested types those types will be validated also. +func (v *validator) validateStruct(value reflect.Value, path string) { + prefix := "." + if path == "" { + prefix = "" + } + + for i := 0; i < value.Type().NumField(); i++ { + f := value.Type().Field(i) + if strings.ToLower(f.Name[0:1]) == f.Name[0:1] { + continue + } + fvalue := value.FieldByName(f.Name) + + notset := false + if f.Tag.Get("required") != "" { + switch fvalue.Kind() { + case reflect.Ptr, reflect.Slice, reflect.Map: + if fvalue.IsNil() { + notset = true + } + default: + if !fvalue.IsValid() { + notset = true + } + } + } + + if notset { + msg := "missing required parameter: " + path + prefix + f.Name + v.errors = append(v.errors, msg) + } else { + v.validateAny(fvalue, path+prefix+f.Name) + } + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator_test.go new file mode 100644 index 00000000000..e09acf8af34 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/param_validator_test.go @@ -0,0 +1,84 @@ +package aws_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/stretchr/testify/assert" +) + +var service = func() *aws.Service { + s := &aws.Service{ + Config: &aws.Config{}, + ServiceName: "mock-service", + APIVersion: "2015-01-01", + } + return s +}() + +type StructShape struct { + RequiredList []*ConditionalStructShape `required:"true"` + RequiredMap map[string]*ConditionalStructShape `required:"true"` + RequiredBool *bool `required:"true"` + OptionalStruct *ConditionalStructShape + + hiddenParameter *string + + metadataStructureShape +} + +type metadataStructureShape struct { + SDKShapeTraits bool +} + +type ConditionalStructShape struct { + Name *string `required:"true"` + SDKShapeTraits bool +} + +func TestNoErrors(t *testing.T) { + input := &StructShape{ + RequiredList: []*ConditionalStructShape{}, + RequiredMap: map[string]*ConditionalStructShape{ + "key1": {Name: aws.String("Name")}, + "key2": {Name: aws.String("Name")}, + }, + RequiredBool: aws.Bool(true), + OptionalStruct: &ConditionalStructShape{Name: aws.String("Name")}, + } + + req := aws.NewRequest(service, &aws.Operation{}, input, nil) + aws.ValidateParameters(req) + assert.NoError(t, req.Error) +} + +func TestMissingRequiredParameters(t *testing.T) { + input := &StructShape{} + req := aws.NewRequest(service, &aws.Operation{}, input, nil) + aws.ValidateParameters(req) + + assert.Error(t, req.Error) + assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code()) + assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList\n- missing required parameter: RequiredMap\n- missing required parameter: RequiredBool", req.Error.(awserr.Error).Message()) +} + +func TestNestedMissingRequiredParameters(t *testing.T) { + input := &StructShape{ + RequiredList: []*ConditionalStructShape{{}}, + RequiredMap: map[string]*ConditionalStructShape{ + "key1": {Name: aws.String("Name")}, + "key2": {}, + }, + RequiredBool: aws.Bool(true), + OptionalStruct: &ConditionalStructShape{}, + } + + req := aws.NewRequest(service, &aws.Operation{}, input, nil) + aws.ValidateParameters(req) + + assert.Error(t, req.Error) + assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code()) + assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList[0].Name\n- missing required parameter: RequiredMap[\"key2\"].Name\n- missing required parameter: OptionalStruct.Name", req.Error.(awserr.Error).Message()) + +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request.go new file mode 100644 index 00000000000..f3248fc467a --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request.go @@ -0,0 +1,312 @@ +package aws + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/url" + "reflect" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws/awsutil" +) + +// A Request is the service request to be made. +type Request struct { + *Service + Handlers Handlers + Time time.Time + ExpireTime time.Duration + Operation *Operation + HTTPRequest *http.Request + HTTPResponse *http.Response + Body io.ReadSeeker + bodyStart int64 // offset from beginning of Body that the request body starts + Params interface{} + Error error + Data interface{} + RequestID string + RetryCount uint + Retryable *bool + RetryDelay time.Duration + + built bool +} + +// An Operation is the service API operation to be made. +type Operation struct { + Name string + HTTPMethod string + HTTPPath string + *Paginator +} + +// Paginator keeps track of pagination configuration for an API operation. +type Paginator struct { + InputTokens []string + OutputTokens []string + LimitToken string + TruncationToken string +} + +// NewRequest returns a new Request pointer for the service API +// operation and parameters. +// +// Params is any value of input parameters to be the request payload. +// Data is pointer value to an object which the request's response +// payload will be deserialized to. +func NewRequest(service *Service, operation *Operation, params interface{}, data interface{}) *Request { + method := operation.HTTPMethod + if method == "" { + method = "POST" + } + p := operation.HTTPPath + if p == "" { + p = "/" + } + + httpReq, _ := http.NewRequest(method, "", nil) + httpReq.URL, _ = url.Parse(service.Endpoint + p) + + r := &Request{ + Service: service, + Handlers: service.Handlers.copy(), + Time: time.Now(), + ExpireTime: 0, + Operation: operation, + HTTPRequest: httpReq, + Body: nil, + Params: params, + Error: nil, + Data: data, + } + r.SetBufferBody([]byte{}) + + return r +} + +// WillRetry returns if the request's can be retried. +func (r *Request) WillRetry() bool { + return r.Error != nil && BoolValue(r.Retryable) && r.RetryCount < r.Service.MaxRetries() +} + +// ParamsFilled returns if the request's parameters have been populated +// and the parameters are valid. False is returned if no parameters are +// provided or invalid. +func (r *Request) ParamsFilled() bool { + return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid() +} + +// DataFilled returns true if the request's data for response deserialization +// target has been set and is a valid. False is returned if data is not +// set, or is invalid. +func (r *Request) DataFilled() bool { + return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid() +} + +// SetBufferBody will set the request's body bytes that will be sent to +// the service API. +func (r *Request) SetBufferBody(buf []byte) { + r.SetReaderBody(bytes.NewReader(buf)) +} + +// SetStringBody sets the body of the request to be backed by a string. +func (r *Request) SetStringBody(s string) { + r.SetReaderBody(strings.NewReader(s)) +} + +// SetReaderBody will set the request's body reader. +func (r *Request) SetReaderBody(reader io.ReadSeeker) { + r.HTTPRequest.Body = ioutil.NopCloser(reader) + r.Body = reader +} + +// Presign returns the request's signed URL. Error will be returned +// if the signing fails. +func (r *Request) Presign(expireTime time.Duration) (string, error) { + r.ExpireTime = expireTime + r.Sign() + if r.Error != nil { + return "", r.Error + } + return r.HTTPRequest.URL.String(), nil +} + +// Build will build the request's object so it can be signed and sent +// to the service. Build will also validate all the request's parameters. +// Anny additional build Handlers set on this request will be run +// in the order they were set. +// +// The request will only be built once. Multiple calls to build will have +// no effect. +// +// If any Validate or Build errors occur the build will stop and the error +// which occurred will be returned. +func (r *Request) Build() error { + if !r.built { + r.Error = nil + r.Handlers.Validate.Run(r) + if r.Error != nil { + return r.Error + } + r.Handlers.Build.Run(r) + r.built = true + } + + return r.Error +} + +// Sign will sign the request retuning error if errors are encountered. +// +// Send will build the request prior to signing. All Sign Handlers will +// be executed in the order they were set. +func (r *Request) Sign() error { + r.Build() + if r.Error != nil { + return r.Error + } + + r.Handlers.Sign.Run(r) + return r.Error +} + +// Send will send the request returning error if errors are encountered. +// +// Send will sign the request prior to sending. All Send Handlers will +// be executed in the order they were set. +func (r *Request) Send() error { + for { + r.Sign() + if r.Error != nil { + return r.Error + } + + if BoolValue(r.Retryable) { + // Re-seek the body back to the original point in for a retry so that + // send will send the body's contents again in the upcoming request. + r.Body.Seek(r.bodyStart, 0) + } + r.Retryable = nil + + r.Handlers.Send.Run(r) + if r.Error != nil { + r.Handlers.Retry.Run(r) + r.Handlers.AfterRetry.Run(r) + if r.Error != nil { + return r.Error + } + continue + } + + r.Handlers.UnmarshalMeta.Run(r) + r.Handlers.ValidateResponse.Run(r) + if r.Error != nil { + r.Handlers.UnmarshalError.Run(r) + r.Handlers.Retry.Run(r) + r.Handlers.AfterRetry.Run(r) + if r.Error != nil { + return r.Error + } + continue + } + + r.Handlers.Unmarshal.Run(r) + if r.Error != nil { + r.Handlers.Retry.Run(r) + r.Handlers.AfterRetry.Run(r) + if r.Error != nil { + return r.Error + } + continue + } + + break + } + + return nil +} + +// HasNextPage returns true if this request has more pages of data available. +func (r *Request) HasNextPage() bool { + return r.nextPageTokens() != nil +} + +// nextPageTokens returns the tokens to use when asking for the next page of +// data. +func (r *Request) nextPageTokens() []interface{} { + if r.Operation.Paginator == nil { + return nil + } + + if r.Operation.TruncationToken != "" { + tr := awsutil.ValuesAtAnyPath(r.Data, r.Operation.TruncationToken) + if tr == nil || len(tr) == 0 { + return nil + } + switch v := tr[0].(type) { + case bool: + if v == false { + return nil + } + } + } + + found := false + tokens := make([]interface{}, len(r.Operation.OutputTokens)) + + for i, outtok := range r.Operation.OutputTokens { + v := awsutil.ValuesAtAnyPath(r.Data, outtok) + if v != nil && len(v) > 0 { + found = true + tokens[i] = v[0] + } + } + + if found { + return tokens + } + return nil +} + +// NextPage returns a new Request that can be executed to return the next +// page of result data. Call .Send() on this request to execute it. +func (r *Request) NextPage() *Request { + tokens := r.nextPageTokens() + if tokens == nil { + return nil + } + + data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface() + nr := NewRequest(r.Service, r.Operation, awsutil.CopyOf(r.Params), data) + for i, intok := range nr.Operation.InputTokens { + awsutil.SetValueAtAnyPath(nr.Params, intok, tokens[i]) + } + return nr +} + +// EachPage iterates over each page of a paginated request object. The fn +// parameter should be a function with the following sample signature: +// +// func(page *T, lastPage bool) bool { +// return true // return false to stop iterating +// } +// +// Where "T" is the structure type matching the output structure of the given +// operation. For example, a request object generated by +// DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput +// as the structure "T". The lastPage value represents whether the page is +// the last page of data or not. The return value of this function should +// return true to keep iterating or false to stop. +func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error { + for page := r; page != nil; page = page.NextPage() { + page.Send() + shouldContinue := fn(page.Data, !page.HasNextPage()) + if page.Error != nil || !shouldContinue { + return page.Error + } + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_pagination_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_pagination_test.go new file mode 100644 index 00000000000..6b20c073193 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_pagination_test.go @@ -0,0 +1,305 @@ +package aws_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/internal/test/unit" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/stretchr/testify/assert" +) + +var _ = unit.Imported + +// Use DynamoDB methods for simplicity +func TestPagination(t *testing.T) { + db := dynamodb.New(nil) + tokens, pages, numPages, gotToEnd := []string{}, []string{}, 0, false + + reqNum := 0 + resps := []*dynamodb.ListTablesOutput{ + {TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")}, + {TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")}, + {TableNames: []*string{aws.String("Table5")}}, + } + + db.Handlers.Send.Clear() // mock sending + db.Handlers.Unmarshal.Clear() + db.Handlers.UnmarshalMeta.Clear() + db.Handlers.ValidateResponse.Clear() + db.Handlers.Build.PushBack(func(r *aws.Request) { + in := r.Params.(*dynamodb.ListTablesInput) + if in == nil { + tokens = append(tokens, "") + } else if in.ExclusiveStartTableName != nil { + tokens = append(tokens, *in.ExclusiveStartTableName) + } + }) + db.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = resps[reqNum] + reqNum++ + }) + + params := &dynamodb.ListTablesInput{Limit: aws.Int64(2)} + err := db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, last bool) bool { + numPages++ + for _, t := range p.TableNames { + pages = append(pages, *t) + } + if last { + if gotToEnd { + assert.Fail(t, "last=true happened twice") + } + gotToEnd = true + } + return true + }) + + assert.Equal(t, []string{"Table2", "Table4"}, tokens) + assert.Equal(t, []string{"Table1", "Table2", "Table3", "Table4", "Table5"}, pages) + assert.Equal(t, 3, numPages) + assert.True(t, gotToEnd) + assert.Nil(t, err) + assert.Nil(t, params.ExclusiveStartTableName) +} + +// Use DynamoDB methods for simplicity +func TestPaginationEachPage(t *testing.T) { + db := dynamodb.New(nil) + tokens, pages, numPages, gotToEnd := []string{}, []string{}, 0, false + + reqNum := 0 + resps := []*dynamodb.ListTablesOutput{ + {TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")}, + {TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")}, + {TableNames: []*string{aws.String("Table5")}}, + } + + db.Handlers.Send.Clear() // mock sending + db.Handlers.Unmarshal.Clear() + db.Handlers.UnmarshalMeta.Clear() + db.Handlers.ValidateResponse.Clear() + db.Handlers.Build.PushBack(func(r *aws.Request) { + in := r.Params.(*dynamodb.ListTablesInput) + if in == nil { + tokens = append(tokens, "") + } else if in.ExclusiveStartTableName != nil { + tokens = append(tokens, *in.ExclusiveStartTableName) + } + }) + db.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = resps[reqNum] + reqNum++ + }) + + params := &dynamodb.ListTablesInput{Limit: aws.Int64(2)} + req, _ := db.ListTablesRequest(params) + err := req.EachPage(func(p interface{}, last bool) bool { + numPages++ + for _, t := range p.(*dynamodb.ListTablesOutput).TableNames { + pages = append(pages, *t) + } + if last { + if gotToEnd { + assert.Fail(t, "last=true happened twice") + } + gotToEnd = true + } + + return true + }) + + assert.Equal(t, []string{"Table2", "Table4"}, tokens) + assert.Equal(t, []string{"Table1", "Table2", "Table3", "Table4", "Table5"}, pages) + assert.Equal(t, 3, numPages) + assert.True(t, gotToEnd) + assert.Nil(t, err) +} + +// Use DynamoDB methods for simplicity +func TestPaginationEarlyExit(t *testing.T) { + db := dynamodb.New(nil) + numPages, gotToEnd := 0, false + + reqNum := 0 + resps := []*dynamodb.ListTablesOutput{ + {TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")}, + {TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")}, + {TableNames: []*string{aws.String("Table5")}}, + } + + db.Handlers.Send.Clear() // mock sending + db.Handlers.Unmarshal.Clear() + db.Handlers.UnmarshalMeta.Clear() + db.Handlers.ValidateResponse.Clear() + db.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = resps[reqNum] + reqNum++ + }) + + params := &dynamodb.ListTablesInput{Limit: aws.Int64(2)} + err := db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, last bool) bool { + numPages++ + if numPages == 2 { + return false + } + if last { + if gotToEnd { + assert.Fail(t, "last=true happened twice") + } + gotToEnd = true + } + return true + }) + + assert.Equal(t, 2, numPages) + assert.False(t, gotToEnd) + assert.Nil(t, err) +} + +func TestSkipPagination(t *testing.T) { + client := s3.New(nil) + client.Handlers.Send.Clear() // mock sending + client.Handlers.Unmarshal.Clear() + client.Handlers.UnmarshalMeta.Clear() + client.Handlers.ValidateResponse.Clear() + client.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = &s3.HeadBucketOutput{} + }) + + req, _ := client.HeadBucketRequest(&s3.HeadBucketInput{Bucket: aws.String("bucket")}) + + numPages, gotToEnd := 0, false + req.EachPage(func(p interface{}, last bool) bool { + numPages++ + if last { + gotToEnd = true + } + return true + }) + assert.Equal(t, 1, numPages) + assert.True(t, gotToEnd) +} + +// Use S3 for simplicity +func TestPaginationTruncation(t *testing.T) { + count := 0 + client := s3.New(nil) + + reqNum := &count + resps := []*s3.ListObjectsOutput{ + {IsTruncated: aws.Bool(true), Contents: []*s3.Object{{Key: aws.String("Key1")}}}, + {IsTruncated: aws.Bool(true), Contents: []*s3.Object{{Key: aws.String("Key2")}}}, + {IsTruncated: aws.Bool(false), Contents: []*s3.Object{{Key: aws.String("Key3")}}}, + {IsTruncated: aws.Bool(true), Contents: []*s3.Object{{Key: aws.String("Key4")}}}, + } + + client.Handlers.Send.Clear() // mock sending + client.Handlers.Unmarshal.Clear() + client.Handlers.UnmarshalMeta.Clear() + client.Handlers.ValidateResponse.Clear() + client.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = resps[*reqNum] + *reqNum++ + }) + + params := &s3.ListObjectsInput{Bucket: aws.String("bucket")} + + results := []string{} + err := client.ListObjectsPages(params, func(p *s3.ListObjectsOutput, last bool) bool { + results = append(results, *p.Contents[0].Key) + return true + }) + + assert.Equal(t, []string{"Key1", "Key2", "Key3"}, results) + assert.Nil(t, err) + + // Try again without truncation token at all + count = 0 + resps[1].IsTruncated = nil + resps[2].IsTruncated = aws.Bool(true) + results = []string{} + err = client.ListObjectsPages(params, func(p *s3.ListObjectsOutput, last bool) bool { + results = append(results, *p.Contents[0].Key) + return true + }) + + assert.Equal(t, []string{"Key1", "Key2"}, results) + assert.Nil(t, err) + +} + +// Benchmarks +var benchResps = []*dynamodb.ListTablesOutput{ + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")}, + {TableNames: []*string{aws.String("TABLE")}}, +} + +var benchDb = func() *dynamodb.DynamoDB { + db := dynamodb.New(nil) + db.Handlers.Send.Clear() // mock sending + db.Handlers.Unmarshal.Clear() + db.Handlers.UnmarshalMeta.Clear() + db.Handlers.ValidateResponse.Clear() + return db +} + +func BenchmarkCodegenIterator(b *testing.B) { + reqNum := 0 + db := benchDb() + db.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = benchResps[reqNum] + reqNum++ + }) + + input := &dynamodb.ListTablesInput{Limit: aws.Int64(2)} + iter := func(fn func(*dynamodb.ListTablesOutput, bool) bool) error { + page, _ := db.ListTablesRequest(input) + for ; page != nil; page = page.NextPage() { + page.Send() + out := page.Data.(*dynamodb.ListTablesOutput) + if result := fn(out, !page.HasNextPage()); page.Error != nil || !result { + return page.Error + } + } + return nil + } + + for i := 0; i < b.N; i++ { + reqNum = 0 + iter(func(p *dynamodb.ListTablesOutput, last bool) bool { + return true + }) + } +} + +func BenchmarkEachPageIterator(b *testing.B) { + reqNum := 0 + db := benchDb() + db.Handlers.Unmarshal.PushBack(func(r *aws.Request) { + r.Data = benchResps[reqNum] + reqNum++ + }) + + input := &dynamodb.ListTablesInput{Limit: aws.Int64(2)} + for i := 0; i < b.N; i++ { + reqNum = 0 + req, _ := db.ListTablesRequest(input) + req.EachPage(func(p interface{}, last bool) bool { + return true + }) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_test.go new file mode 100644 index 00000000000..d5d945d983f --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/request_test.go @@ -0,0 +1,225 @@ +package aws + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/stretchr/testify/assert" +) + +type testData struct { + Data string +} + +func body(str string) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader([]byte(str))) +} + +func unmarshal(req *Request) { + defer req.HTTPResponse.Body.Close() + if req.Data != nil { + json.NewDecoder(req.HTTPResponse.Body).Decode(req.Data) + } + return +} + +func unmarshalError(req *Request) { + bodyBytes, err := ioutil.ReadAll(req.HTTPResponse.Body) + if err != nil { + req.Error = awserr.New("UnmarshaleError", req.HTTPResponse.Status, err) + return + } + if len(bodyBytes) == 0 { + req.Error = awserr.NewRequestFailure( + awserr.New("UnmarshaleError", req.HTTPResponse.Status, fmt.Errorf("empty body")), + req.HTTPResponse.StatusCode, + "", + ) + return + } + var jsonErr jsonErrorResponse + if err := json.Unmarshal(bodyBytes, &jsonErr); err != nil { + req.Error = awserr.New("UnmarshaleError", "JSON unmarshal", err) + return + } + req.Error = awserr.NewRequestFailure( + awserr.New(jsonErr.Code, jsonErr.Message, nil), + req.HTTPResponse.StatusCode, + "", + ) +} + +type jsonErrorResponse struct { + Code string `json:"__type"` + Message string `json:"message"` +} + +// test that retries occur for 5xx status codes +func TestRequestRecoverRetry5xx(t *testing.T) { + reqNum := 0 + reqs := []http.Response{ + {StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)}, + {StatusCode: 501, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)}, + {StatusCode: 200, Body: body(`{"data":"valid"}`)}, + } + + s := NewService(NewConfig().WithMaxRetries(10)) + s.Handlers.Validate.Clear() + s.Handlers.Unmarshal.PushBack(unmarshal) + s.Handlers.UnmarshalError.PushBack(unmarshalError) + s.Handlers.Send.Clear() // mock sending + s.Handlers.Send.PushBack(func(r *Request) { + r.HTTPResponse = &reqs[reqNum] + reqNum++ + }) + out := &testData{} + r := NewRequest(s, &Operation{Name: "Operation"}, nil, out) + err := r.Send() + assert.Nil(t, err) + assert.Equal(t, 2, int(r.RetryCount)) + assert.Equal(t, "valid", out.Data) +} + +// test that retries occur for 4xx status codes with a response type that can be retried - see `shouldRetry` +func TestRequestRecoverRetry4xxRetryable(t *testing.T) { + reqNum := 0 + reqs := []http.Response{ + {StatusCode: 400, Body: body(`{"__type":"Throttling","message":"Rate exceeded."}`)}, + {StatusCode: 429, Body: body(`{"__type":"ProvisionedThroughputExceededException","message":"Rate exceeded."}`)}, + {StatusCode: 200, Body: body(`{"data":"valid"}`)}, + } + + s := NewService(NewConfig().WithMaxRetries(10)) + s.Handlers.Validate.Clear() + s.Handlers.Unmarshal.PushBack(unmarshal) + s.Handlers.UnmarshalError.PushBack(unmarshalError) + s.Handlers.Send.Clear() // mock sending + s.Handlers.Send.PushBack(func(r *Request) { + r.HTTPResponse = &reqs[reqNum] + reqNum++ + }) + out := &testData{} + r := NewRequest(s, &Operation{Name: "Operation"}, nil, out) + err := r.Send() + assert.Nil(t, err) + assert.Equal(t, 2, int(r.RetryCount)) + assert.Equal(t, "valid", out.Data) +} + +// test that retries don't occur for 4xx status codes with a response type that can't be retried +func TestRequest4xxUnretryable(t *testing.T) { + s := NewService(NewConfig().WithMaxRetries(10)) + s.Handlers.Validate.Clear() + s.Handlers.Unmarshal.PushBack(unmarshal) + s.Handlers.UnmarshalError.PushBack(unmarshalError) + s.Handlers.Send.Clear() // mock sending + s.Handlers.Send.PushBack(func(r *Request) { + r.HTTPResponse = &http.Response{StatusCode: 401, Body: body(`{"__type":"SignatureDoesNotMatch","message":"Signature does not match."}`)} + }) + out := &testData{} + r := NewRequest(s, &Operation{Name: "Operation"}, nil, out) + err := r.Send() + assert.NotNil(t, err) + if e, ok := err.(awserr.RequestFailure); ok { + assert.Equal(t, 401, e.StatusCode()) + } else { + assert.Fail(t, "Expected error to be a service failure") + } + assert.Equal(t, "SignatureDoesNotMatch", err.(awserr.Error).Code()) + assert.Equal(t, "Signature does not match.", err.(awserr.Error).Message()) + assert.Equal(t, 0, int(r.RetryCount)) +} + +func TestRequestExhaustRetries(t *testing.T) { + delays := []time.Duration{} + sleepDelay = func(delay time.Duration) { + delays = append(delays, delay) + } + + reqNum := 0 + reqs := []http.Response{ + {StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)}, + {StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)}, + {StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)}, + {StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)}, + } + + s := NewService(NewConfig().WithMaxRetries(DefaultRetries)) + s.Handlers.Validate.Clear() + s.Handlers.Unmarshal.PushBack(unmarshal) + s.Handlers.UnmarshalError.PushBack(unmarshalError) + s.Handlers.Send.Clear() // mock sending + s.Handlers.Send.PushBack(func(r *Request) { + r.HTTPResponse = &reqs[reqNum] + reqNum++ + }) + r := NewRequest(s, &Operation{Name: "Operation"}, nil, nil) + err := r.Send() + assert.NotNil(t, err) + if e, ok := err.(awserr.RequestFailure); ok { + assert.Equal(t, 500, e.StatusCode()) + } else { + assert.Fail(t, "Expected error to be a service failure") + } + assert.Equal(t, "UnknownError", err.(awserr.Error).Code()) + assert.Equal(t, "An error occurred.", err.(awserr.Error).Message()) + assert.Equal(t, 3, int(r.RetryCount)) + + expectDelays := []struct{ min, max time.Duration }{{30, 59}, {60, 118}, {120, 236}} + for i, v := range delays { + min := expectDelays[i].min * time.Millisecond + max := expectDelays[i].max * time.Millisecond + assert.True(t, min <= v && v <= max, + "Expect delay to be within range, i:%d, v:%s, min:%s, max:%s", i, v, min, max) + } +} + +// test that the request is retried after the credentials are expired. +func TestRequestRecoverExpiredCreds(t *testing.T) { + reqNum := 0 + reqs := []http.Response{ + {StatusCode: 400, Body: body(`{"__type":"ExpiredTokenException","message":"expired token"}`)}, + {StatusCode: 200, Body: body(`{"data":"valid"}`)}, + } + + s := NewService(&Config{MaxRetries: Int(10), Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "")}) + s.Handlers.Validate.Clear() + s.Handlers.Unmarshal.PushBack(unmarshal) + s.Handlers.UnmarshalError.PushBack(unmarshalError) + + credExpiredBeforeRetry := false + credExpiredAfterRetry := false + + s.Handlers.AfterRetry.PushBack(func(r *Request) { + credExpiredAfterRetry = r.Config.Credentials.IsExpired() + }) + + s.Handlers.Sign.Clear() + s.Handlers.Sign.PushBack(func(r *Request) { + r.Config.Credentials.Get() + }) + s.Handlers.Send.Clear() // mock sending + s.Handlers.Send.PushBack(func(r *Request) { + r.HTTPResponse = &reqs[reqNum] + reqNum++ + }) + out := &testData{} + r := NewRequest(s, &Operation{Name: "Operation"}, nil, out) + err := r.Send() + assert.Nil(t, err) + + assert.False(t, credExpiredBeforeRetry, "Expect valid creds before retry check") + assert.True(t, credExpiredAfterRetry, "Expect expired creds after retry check") + assert.False(t, s.Config.Credentials.IsExpired(), "Expect valid creds after cred expired recovery") + + assert.Equal(t, 1, int(r.RetryCount)) + assert.Equal(t, "valid", out.Data) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/service.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/service.go new file mode 100644 index 00000000000..672f7de1d63 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/service.go @@ -0,0 +1,194 @@ +package aws + +import ( + "fmt" + "math" + "math/rand" + "net/http" + "net/http/httputil" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/internal/endpoints" +) + +// A Service implements the base service request and response handling +// used by all services. +type Service struct { + Config *Config + Handlers Handlers + ServiceName string + APIVersion string + Endpoint string + SigningName string + SigningRegion string + JSONVersion string + TargetPrefix string + RetryRules func(*Request) time.Duration + ShouldRetry func(*Request) bool + DefaultMaxRetries uint +} + +var schemeRE = regexp.MustCompile("^([^:]+)://") + +// NewService will return a pointer to a new Server object initialized. +func NewService(config *Config) *Service { + svc := &Service{Config: config} + svc.Initialize() + return svc +} + +// Initialize initializes the service. +func (s *Service) Initialize() { + if s.Config == nil { + s.Config = &Config{} + } + if s.Config.HTTPClient == nil { + s.Config.HTTPClient = http.DefaultClient + } + + if s.RetryRules == nil { + s.RetryRules = retryRules + } + + if s.ShouldRetry == nil { + s.ShouldRetry = shouldRetry + } + + s.DefaultMaxRetries = 3 + s.Handlers.Validate.PushBack(ValidateEndpointHandler) + s.Handlers.Build.PushBack(UserAgentHandler) + s.Handlers.Sign.PushBack(BuildContentLength) + s.Handlers.Send.PushBack(SendHandler) + s.Handlers.AfterRetry.PushBack(AfterRetryHandler) + s.Handlers.ValidateResponse.PushBack(ValidateResponseHandler) + s.AddDebugHandlers() + s.buildEndpoint() + + if !BoolValue(s.Config.DisableParamValidation) { + s.Handlers.Validate.PushBack(ValidateParameters) + } +} + +// buildEndpoint builds the endpoint values the service will use to make requests with. +func (s *Service) buildEndpoint() { + if StringValue(s.Config.Endpoint) != "" { + s.Endpoint = *s.Config.Endpoint + } else { + s.Endpoint, s.SigningRegion = + endpoints.EndpointForRegion(s.ServiceName, StringValue(s.Config.Region)) + } + + if s.Endpoint != "" && !schemeRE.MatchString(s.Endpoint) { + scheme := "https" + if BoolValue(s.Config.DisableSSL) { + scheme = "http" + } + s.Endpoint = scheme + "://" + s.Endpoint + } +} + +// AddDebugHandlers injects debug logging handlers into the service to log request +// debug information. +func (s *Service) AddDebugHandlers() { + if !s.Config.LogLevel.AtLeast(LogDebug) { + return + } + + s.Handlers.Send.PushFront(logRequest) + s.Handlers.Send.PushBack(logResponse) +} + +const logReqMsg = `DEBUG: Request %s/%s Details: +---[ REQUEST POST-SIGN ]----------------------------- +%s +-----------------------------------------------------` + +func logRequest(r *Request) { + logBody := r.Config.LogLevel.Matches(LogDebugWithHTTPBody) + dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, logBody) + + r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ServiceName, r.Operation.Name, string(dumpedBody))) +} + +const logRespMsg = `DEBUG: Response %s/%s Details: +---[ RESPONSE ]-------------------------------------- +%s +-----------------------------------------------------` + +func logResponse(r *Request) { + var msg = "no reponse data" + if r.HTTPResponse != nil { + logBody := r.Config.LogLevel.Matches(LogDebugWithHTTPBody) + dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, logBody) + msg = string(dumpedBody) + } else if r.Error != nil { + msg = r.Error.Error() + } + r.Config.Logger.Log(fmt.Sprintf(logRespMsg, r.ServiceName, r.Operation.Name, msg)) +} + +// MaxRetries returns the number of maximum returns the service will use to make +// an individual API request. +func (s *Service) MaxRetries() uint { + if IntValue(s.Config.MaxRetries) < 0 { + return s.DefaultMaxRetries + } + return uint(IntValue(s.Config.MaxRetries)) +} + +var seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) + +// retryRules returns the delay duration before retrying this request again +func retryRules(r *Request) time.Duration { + + delay := int(math.Pow(2, float64(r.RetryCount))) * (seededRand.Intn(30) + 30) + return time.Duration(delay) * time.Millisecond +} + +// retryableCodes is a collection of service response codes which are retry-able +// without any further action. +var retryableCodes = map[string]struct{}{ + "RequestError": {}, + "ProvisionedThroughputExceededException": {}, + "Throttling": {}, + "ThrottlingException": {}, + "RequestLimitExceeded": {}, + "RequestThrottled": {}, +} + +// credsExpiredCodes is a collection of error codes which signify the credentials +// need to be refreshed. Expired tokens require refreshing of credentials, and +// resigning before the request can be retried. +var credsExpiredCodes = map[string]struct{}{ + "ExpiredToken": {}, + "ExpiredTokenException": {}, + "RequestExpired": {}, // EC2 Only +} + +func isCodeRetryable(code string) bool { + if _, ok := retryableCodes[code]; ok { + return true + } + + return isCodeExpiredCreds(code) +} + +func isCodeExpiredCreds(code string) bool { + _, ok := credsExpiredCodes[code] + return ok +} + +// shouldRetry returns if the request should be retried. +func shouldRetry(r *Request) bool { + if r.HTTPResponse.StatusCode >= 500 { + return true + } + if r.Error != nil { + if err, ok := r.Error.(awserr.Error); ok { + return isCodeRetryable(err.Code()) + } + } + return false +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/types.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/types.go new file mode 100644 index 00000000000..87905d7e06e --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/types.go @@ -0,0 +1,55 @@ +package aws + +import ( + "io" +) + +// ReadSeekCloser wraps a io.Reader returning a ReaderSeakerCloser +func ReadSeekCloser(r io.Reader) ReaderSeekerCloser { + return ReaderSeekerCloser{r} +} + +// ReaderSeekerCloser represents a reader that can also delegate io.Seeker and +// io.Closer interfaces to the underlying object if they are available. +type ReaderSeekerCloser struct { + r io.Reader +} + +// Read reads from the reader up to size of p. The number of bytes read, and +// error if it occurred will be returned. +// +// If the reader is not an io.Reader zero bytes read, and nil error will be returned. +// +// Performs the same functionality as io.Reader Read +func (r ReaderSeekerCloser) Read(p []byte) (int, error) { + switch t := r.r.(type) { + case io.Reader: + return t.Read(p) + } + return 0, nil +} + +// Seek sets the offset for the next Read to offset, interpreted according to +// whence: 0 means relative to the origin of the file, 1 means relative to the +// current offset, and 2 means relative to the end. Seek returns the new offset +// and an error, if any. +// +// If the ReaderSeekerCloser is not an io.Seeker nothing will be done. +func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) { + switch t := r.r.(type) { + case io.Seeker: + return t.Seek(offset, whence) + } + return int64(0), nil +} + +// Close closes the ReaderSeekerCloser. +// +// If the ReaderSeekerCloser is not an io.Closer nothing will be done. +func (r ReaderSeekerCloser) Close() error { + switch t := r.r.(type) { + case io.Closer: + return t.Close() + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go new file mode 100644 index 00000000000..681a6e265c1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/aws/version.go @@ -0,0 +1,8 @@ +// Package aws provides core functionality for making requests to AWS services. +package aws + +// SDKName is the name of this AWS SDK +const SDKName = "aws-sdk-go" + +// SDKVersion is the version of this SDK +const SDKVersion = "0.7.3" diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.go new file mode 100644 index 00000000000..d040cccd57d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.go @@ -0,0 +1,31 @@ +// Package endpoints validates regional endpoints for services. +package endpoints + +//go:generate go run ../model/cli/gen-endpoints/main.go endpoints.json endpoints_map.go +//go:generate gofmt -s -w endpoints_map.go + +import "strings" + +// EndpointForRegion returns an endpoint and its signing region for a service and region. +// if the service and region pair are not found endpoint and signingRegion will be empty. +func EndpointForRegion(svcName, region string) (endpoint, signingRegion string) { + derivedKeys := []string{ + region + "/" + svcName, + region + "/*", + "*/" + svcName, + "*/*", + } + + for _, key := range derivedKeys { + if val, ok := endpointsMap.Endpoints[key]; ok { + ep := val.Endpoint + ep = strings.Replace(ep, "{region}", region, -1) + ep = strings.Replace(ep, "{service}", svcName, -1) + + endpoint = ep + signingRegion = val.SigningRegion + return + } + } + return +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.json b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.json new file mode 100644 index 00000000000..4c588090a9c --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints.json @@ -0,0 +1,77 @@ +{ + "version": 2, + "endpoints": { + "*/*": { + "endpoint": "{service}.{region}.amazonaws.com" + }, + "cn-north-1/*": { + "endpoint": "{service}.{region}.amazonaws.com.cn", + "signatureVersion": "v4" + }, + "us-gov-west-1/iam": { + "endpoint": "iam.us-gov.amazonaws.com" + }, + "us-gov-west-1/sts": { + "endpoint": "sts.us-gov-west-1.amazonaws.com" + }, + "us-gov-west-1/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "*/cloudfront": { + "endpoint": "cloudfront.amazonaws.com", + "signingRegion": "us-east-1" + }, + "*/cloudsearchdomain": { + "endpoint": "", + "signingRegion": "us-east-1" + }, + "*/iam": { + "endpoint": "iam.amazonaws.com", + "signingRegion": "us-east-1" + }, + "*/importexport": { + "endpoint": "importexport.amazonaws.com", + "signingRegion": "us-east-1" + }, + "*/route53": { + "endpoint": "route53.amazonaws.com", + "signingRegion": "us-east-1" + }, + "*/sts": { + "endpoint": "sts.amazonaws.com", + "signingRegion": "us-east-1" + }, + "us-east-1/sdb": { + "endpoint": "sdb.amazonaws.com", + "signingRegion": "us-east-1" + }, + "us-east-1/s3": { + "endpoint": "s3.amazonaws.com" + }, + "us-west-1/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "us-west-2/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "eu-west-1/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "ap-southeast-1/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "ap-southeast-2/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "ap-northeast-1/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "sa-east-1/s3": { + "endpoint": "s3-{region}.amazonaws.com" + }, + "eu-central-1/s3": { + "endpoint": "{service}.{region}.amazonaws.com", + "signatureVersion": "v4" + } + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_map.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_map.go new file mode 100644 index 00000000000..894c1a64341 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_map.go @@ -0,0 +1,89 @@ +package endpoints + +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +type endpointStruct struct { + Version int + Endpoints map[string]endpointEntry +} + +type endpointEntry struct { + Endpoint string + SigningRegion string +} + +var endpointsMap = endpointStruct{ + Version: 2, + Endpoints: map[string]endpointEntry{ + "*/*": { + Endpoint: "{service}.{region}.amazonaws.com", + }, + "*/cloudfront": { + Endpoint: "cloudfront.amazonaws.com", + SigningRegion: "us-east-1", + }, + "*/cloudsearchdomain": { + Endpoint: "", + SigningRegion: "us-east-1", + }, + "*/iam": { + Endpoint: "iam.amazonaws.com", + SigningRegion: "us-east-1", + }, + "*/importexport": { + Endpoint: "importexport.amazonaws.com", + SigningRegion: "us-east-1", + }, + "*/route53": { + Endpoint: "route53.amazonaws.com", + SigningRegion: "us-east-1", + }, + "*/sts": { + Endpoint: "sts.amazonaws.com", + SigningRegion: "us-east-1", + }, + "ap-northeast-1/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "ap-southeast-1/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "ap-southeast-2/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "cn-north-1/*": { + Endpoint: "{service}.{region}.amazonaws.com.cn", + }, + "eu-central-1/s3": { + Endpoint: "{service}.{region}.amazonaws.com", + }, + "eu-west-1/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "sa-east-1/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "us-east-1/s3": { + Endpoint: "s3.amazonaws.com", + }, + "us-east-1/sdb": { + Endpoint: "sdb.amazonaws.com", + SigningRegion: "us-east-1", + }, + "us-gov-west-1/iam": { + Endpoint: "iam.us-gov.amazonaws.com", + }, + "us-gov-west-1/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "us-gov-west-1/sts": { + Endpoint: "sts.us-gov-west-1.amazonaws.com", + }, + "us-west-1/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + "us-west-2/s3": { + Endpoint: "s3-{region}.amazonaws.com", + }, + }, +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_test.go new file mode 100644 index 00000000000..8af65879d40 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/endpoints/endpoints_test.go @@ -0,0 +1,28 @@ +package endpoints + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGlobalEndpoints(t *testing.T) { + region := "mock-region-1" + svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts"} + + for _, name := range svcs { + ep, sr := EndpointForRegion(name, region) + assert.Equal(t, name+".amazonaws.com", ep) + assert.Equal(t, "us-east-1", sr) + } +} + +func TestServicesInCN(t *testing.T) { + region := "cn-north-1" + svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts", "s3"} + + for _, name := range svcs { + ep, _ := EndpointForRegion(name, region) + assert.Equal(t, name+"."+region+".amazonaws.com.cn", ep) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build.go new file mode 100644 index 00000000000..c4d8dd2635a --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build.go @@ -0,0 +1,33 @@ +// Package query provides serialisation of AWS query requests, and responses. +package query + +//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/query.json build_test.go + +import ( + "net/url" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/internal/protocol/query/queryutil" +) + +// Build builds a request for an AWS Query service. +func Build(r *aws.Request) { + body := url.Values{ + "Action": {r.Operation.Name}, + "Version": {r.Service.APIVersion}, + } + if err := queryutil.Parse(body, r.Params, false); err != nil { + r.Error = awserr.New("SerializationError", "failed encoding Query request", err) + return + } + + if r.ExpireTime == 0 { + r.HTTPRequest.Method = "POST" + r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + r.SetBufferBody([]byte(body.Encode())) + } else { // This is a pre-signed request + r.HTTPRequest.Method = "GET" + r.HTTPRequest.URL.RawQuery = body.Encode() + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build_test.go new file mode 100644 index 00000000000..52bbf7e1c87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/build_test.go @@ -0,0 +1,1482 @@ +package query_test + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "io" + "io/ioutil" + "net/http" + "net/url" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/internal/protocol/query" + "github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil" + "github.com/aws/aws-sdk-go/internal/signer/v4" + "github.com/aws/aws-sdk-go/internal/util" + "github.com/stretchr/testify/assert" +) + +var _ bytes.Buffer // always import bytes +var _ http.Request +var _ json.Marshaler +var _ time.Time +var _ xmlutil.XMLNode +var _ xml.Attr +var _ = ioutil.Discard +var _ = util.Trim("") +var _ = url.Values{} +var _ = io.EOF + +type InputService1ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService1ProtocolTest client. +func NewInputService1ProtocolTest(config *aws.Config) *InputService1ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice1protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService1ProtocolTest{service} +} + +// newRequest creates a new request for a InputService1ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService1ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService1TestCaseOperation1 = "OperationName" + +// InputService1TestCaseOperation1Request generates a request for the InputService1TestCaseOperation1 operation. +func (c *InputService1ProtocolTest) InputService1TestCaseOperation1Request(input *InputService1TestShapeInputShape) (req *aws.Request, output *InputService1TestShapeInputService1TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService1TestCaseOperation1, + } + + if input == nil { + input = &InputService1TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService1TestShapeInputService1TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService1ProtocolTest) InputService1TestCaseOperation1(input *InputService1TestShapeInputShape) (*InputService1TestShapeInputService1TestCaseOperation1Output, error) { + req, out := c.InputService1TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type InputService1TestShapeInputService1TestCaseOperation1Output struct { + metadataInputService1TestShapeInputService1TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService1TestShapeInputService1TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService1TestShapeInputShape struct { + Bar *string `type:"string"` + + Foo *string `type:"string"` + + metadataInputService1TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService1TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService2ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService2ProtocolTest client. +func NewInputService2ProtocolTest(config *aws.Config) *InputService2ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice2protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService2ProtocolTest{service} +} + +// newRequest creates a new request for a InputService2ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService2ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService2TestCaseOperation1 = "OperationName" + +// InputService2TestCaseOperation1Request generates a request for the InputService2TestCaseOperation1 operation. +func (c *InputService2ProtocolTest) InputService2TestCaseOperation1Request(input *InputService2TestShapeInputShape) (req *aws.Request, output *InputService2TestShapeInputService2TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService2TestCaseOperation1, + } + + if input == nil { + input = &InputService2TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService2TestShapeInputService2TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService2ProtocolTest) InputService2TestCaseOperation1(input *InputService2TestShapeInputShape) (*InputService2TestShapeInputService2TestCaseOperation1Output, error) { + req, out := c.InputService2TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type InputService2TestShapeInputService2TestCaseOperation1Output struct { + metadataInputService2TestShapeInputService2TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService2TestShapeInputService2TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService2TestShapeInputShape struct { + StructArg *InputService2TestShapeStructType `type:"structure"` + + metadataInputService2TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService2TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService2TestShapeStructType struct { + ScalarArg *string `type:"string"` + + metadataInputService2TestShapeStructType `json:"-" xml:"-"` +} + +type metadataInputService2TestShapeStructType struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService3ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService3ProtocolTest client. +func NewInputService3ProtocolTest(config *aws.Config) *InputService3ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice3protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService3ProtocolTest{service} +} + +// newRequest creates a new request for a InputService3ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService3ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService3TestCaseOperation1 = "OperationName" + +// InputService3TestCaseOperation1Request generates a request for the InputService3TestCaseOperation1 operation. +func (c *InputService3ProtocolTest) InputService3TestCaseOperation1Request(input *InputService3TestShapeInputShape) (req *aws.Request, output *InputService3TestShapeInputService3TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService3TestCaseOperation1, + } + + if input == nil { + input = &InputService3TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService3TestShapeInputService3TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService3ProtocolTest) InputService3TestCaseOperation1(input *InputService3TestShapeInputShape) (*InputService3TestShapeInputService3TestCaseOperation1Output, error) { + req, out := c.InputService3TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +const opInputService3TestCaseOperation2 = "OperationName" + +// InputService3TestCaseOperation2Request generates a request for the InputService3TestCaseOperation2 operation. +func (c *InputService3ProtocolTest) InputService3TestCaseOperation2Request(input *InputService3TestShapeInputShape) (req *aws.Request, output *InputService3TestShapeInputService3TestCaseOperation2Output) { + op := &aws.Operation{ + Name: opInputService3TestCaseOperation2, + } + + if input == nil { + input = &InputService3TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService3TestShapeInputService3TestCaseOperation2Output{} + req.Data = output + return +} + +func (c *InputService3ProtocolTest) InputService3TestCaseOperation2(input *InputService3TestShapeInputShape) (*InputService3TestShapeInputService3TestCaseOperation2Output, error) { + req, out := c.InputService3TestCaseOperation2Request(input) + err := req.Send() + return out, err +} + +type InputService3TestShapeInputService3TestCaseOperation1Output struct { + metadataInputService3TestShapeInputService3TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService3TestShapeInputService3TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService3TestShapeInputService3TestCaseOperation2Output struct { + metadataInputService3TestShapeInputService3TestCaseOperation2Output `json:"-" xml:"-"` +} + +type metadataInputService3TestShapeInputService3TestCaseOperation2Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService3TestShapeInputShape struct { + ListArg []*string `type:"list"` + + metadataInputService3TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService3TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService4ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService4ProtocolTest client. +func NewInputService4ProtocolTest(config *aws.Config) *InputService4ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice4protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService4ProtocolTest{service} +} + +// newRequest creates a new request for a InputService4ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService4ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService4TestCaseOperation1 = "OperationName" + +// InputService4TestCaseOperation1Request generates a request for the InputService4TestCaseOperation1 operation. +func (c *InputService4ProtocolTest) InputService4TestCaseOperation1Request(input *InputService4TestShapeInputShape) (req *aws.Request, output *InputService4TestShapeInputService4TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService4TestCaseOperation1, + } + + if input == nil { + input = &InputService4TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService4TestShapeInputService4TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService4ProtocolTest) InputService4TestCaseOperation1(input *InputService4TestShapeInputShape) (*InputService4TestShapeInputService4TestCaseOperation1Output, error) { + req, out := c.InputService4TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +const opInputService4TestCaseOperation2 = "OperationName" + +// InputService4TestCaseOperation2Request generates a request for the InputService4TestCaseOperation2 operation. +func (c *InputService4ProtocolTest) InputService4TestCaseOperation2Request(input *InputService4TestShapeInputShape) (req *aws.Request, output *InputService4TestShapeInputService4TestCaseOperation2Output) { + op := &aws.Operation{ + Name: opInputService4TestCaseOperation2, + } + + if input == nil { + input = &InputService4TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService4TestShapeInputService4TestCaseOperation2Output{} + req.Data = output + return +} + +func (c *InputService4ProtocolTest) InputService4TestCaseOperation2(input *InputService4TestShapeInputShape) (*InputService4TestShapeInputService4TestCaseOperation2Output, error) { + req, out := c.InputService4TestCaseOperation2Request(input) + err := req.Send() + return out, err +} + +type InputService4TestShapeInputService4TestCaseOperation1Output struct { + metadataInputService4TestShapeInputService4TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService4TestShapeInputService4TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService4TestShapeInputService4TestCaseOperation2Output struct { + metadataInputService4TestShapeInputService4TestCaseOperation2Output `json:"-" xml:"-"` +} + +type metadataInputService4TestShapeInputService4TestCaseOperation2Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService4TestShapeInputShape struct { + ListArg []*string `type:"list" flattened:"true"` + + ScalarArg *string `type:"string"` + + metadataInputService4TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService4TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService5ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService5ProtocolTest client. +func NewInputService5ProtocolTest(config *aws.Config) *InputService5ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice5protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService5ProtocolTest{service} +} + +// newRequest creates a new request for a InputService5ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService5ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService5TestCaseOperation1 = "OperationName" + +// InputService5TestCaseOperation1Request generates a request for the InputService5TestCaseOperation1 operation. +func (c *InputService5ProtocolTest) InputService5TestCaseOperation1Request(input *InputService5TestShapeInputShape) (req *aws.Request, output *InputService5TestShapeInputService5TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService5TestCaseOperation1, + } + + if input == nil { + input = &InputService5TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService5TestShapeInputService5TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService5ProtocolTest) InputService5TestCaseOperation1(input *InputService5TestShapeInputShape) (*InputService5TestShapeInputService5TestCaseOperation1Output, error) { + req, out := c.InputService5TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +const opInputService5TestCaseOperation2 = "OperationName" + +// InputService5TestCaseOperation2Request generates a request for the InputService5TestCaseOperation2 operation. +func (c *InputService5ProtocolTest) InputService5TestCaseOperation2Request(input *InputService5TestShapeInputShape) (req *aws.Request, output *InputService5TestShapeInputService5TestCaseOperation2Output) { + op := &aws.Operation{ + Name: opInputService5TestCaseOperation2, + } + + if input == nil { + input = &InputService5TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService5TestShapeInputService5TestCaseOperation2Output{} + req.Data = output + return +} + +func (c *InputService5ProtocolTest) InputService5TestCaseOperation2(input *InputService5TestShapeInputShape) (*InputService5TestShapeInputService5TestCaseOperation2Output, error) { + req, out := c.InputService5TestCaseOperation2Request(input) + err := req.Send() + return out, err +} + +type InputService5TestShapeInputService5TestCaseOperation1Output struct { + metadataInputService5TestShapeInputService5TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService5TestShapeInputService5TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService5TestShapeInputService5TestCaseOperation2Output struct { + metadataInputService5TestShapeInputService5TestCaseOperation2Output `json:"-" xml:"-"` +} + +type metadataInputService5TestShapeInputService5TestCaseOperation2Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService5TestShapeInputShape struct { + MapArg map[string]*string `type:"map"` + + metadataInputService5TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService5TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService6ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService6ProtocolTest client. +func NewInputService6ProtocolTest(config *aws.Config) *InputService6ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice6protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService6ProtocolTest{service} +} + +// newRequest creates a new request for a InputService6ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService6ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService6TestCaseOperation1 = "OperationName" + +// InputService6TestCaseOperation1Request generates a request for the InputService6TestCaseOperation1 operation. +func (c *InputService6ProtocolTest) InputService6TestCaseOperation1Request(input *InputService6TestShapeInputShape) (req *aws.Request, output *InputService6TestShapeInputService6TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService6TestCaseOperation1, + } + + if input == nil { + input = &InputService6TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService6TestShapeInputService6TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService6ProtocolTest) InputService6TestCaseOperation1(input *InputService6TestShapeInputShape) (*InputService6TestShapeInputService6TestCaseOperation1Output, error) { + req, out := c.InputService6TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type InputService6TestShapeInputService6TestCaseOperation1Output struct { + metadataInputService6TestShapeInputService6TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService6TestShapeInputService6TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService6TestShapeInputShape struct { + MapArg map[string]*string `locationNameKey:"TheKey" locationNameValue:"TheValue" type:"map"` + + metadataInputService6TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService6TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService7ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService7ProtocolTest client. +func NewInputService7ProtocolTest(config *aws.Config) *InputService7ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice7protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService7ProtocolTest{service} +} + +// newRequest creates a new request for a InputService7ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService7ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService7TestCaseOperation1 = "OperationName" + +// InputService7TestCaseOperation1Request generates a request for the InputService7TestCaseOperation1 operation. +func (c *InputService7ProtocolTest) InputService7TestCaseOperation1Request(input *InputService7TestShapeInputShape) (req *aws.Request, output *InputService7TestShapeInputService7TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService7TestCaseOperation1, + } + + if input == nil { + input = &InputService7TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService7TestShapeInputService7TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService7ProtocolTest) InputService7TestCaseOperation1(input *InputService7TestShapeInputShape) (*InputService7TestShapeInputService7TestCaseOperation1Output, error) { + req, out := c.InputService7TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type InputService7TestShapeInputService7TestCaseOperation1Output struct { + metadataInputService7TestShapeInputService7TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService7TestShapeInputService7TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService7TestShapeInputShape struct { + BlobArg []byte `type:"blob"` + + metadataInputService7TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService7TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService8ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService8ProtocolTest client. +func NewInputService8ProtocolTest(config *aws.Config) *InputService8ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice8protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService8ProtocolTest{service} +} + +// newRequest creates a new request for a InputService8ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService8ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService8TestCaseOperation1 = "OperationName" + +// InputService8TestCaseOperation1Request generates a request for the InputService8TestCaseOperation1 operation. +func (c *InputService8ProtocolTest) InputService8TestCaseOperation1Request(input *InputService8TestShapeInputShape) (req *aws.Request, output *InputService8TestShapeInputService8TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService8TestCaseOperation1, + } + + if input == nil { + input = &InputService8TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService8TestShapeInputService8TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService8ProtocolTest) InputService8TestCaseOperation1(input *InputService8TestShapeInputShape) (*InputService8TestShapeInputService8TestCaseOperation1Output, error) { + req, out := c.InputService8TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type InputService8TestShapeInputService8TestCaseOperation1Output struct { + metadataInputService8TestShapeInputService8TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService8TestShapeInputService8TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService8TestShapeInputShape struct { + TimeArg *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + metadataInputService8TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService8TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9ProtocolTest struct { + *aws.Service +} + +// New returns a new InputService9ProtocolTest client. +func NewInputService9ProtocolTest(config *aws.Config) *InputService9ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "inputservice9protocoltest", + APIVersion: "2014-01-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &InputService9ProtocolTest{service} +} + +// newRequest creates a new request for a InputService9ProtocolTest operation and runs any +// custom request initialization. +func (c *InputService9ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opInputService9TestCaseOperation1 = "OperationName" + +// InputService9TestCaseOperation1Request generates a request for the InputService9TestCaseOperation1 operation. +func (c *InputService9ProtocolTest) InputService9TestCaseOperation1Request(input *InputService9TestShapeInputShape) (req *aws.Request, output *InputService9TestShapeInputService9TestCaseOperation1Output) { + op := &aws.Operation{ + Name: opInputService9TestCaseOperation1, + } + + if input == nil { + input = &InputService9TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService9TestShapeInputService9TestCaseOperation1Output{} + req.Data = output + return +} + +func (c *InputService9ProtocolTest) InputService9TestCaseOperation1(input *InputService9TestShapeInputShape) (*InputService9TestShapeInputService9TestCaseOperation1Output, error) { + req, out := c.InputService9TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +const opInputService9TestCaseOperation2 = "OperationName" + +// InputService9TestCaseOperation2Request generates a request for the InputService9TestCaseOperation2 operation. +func (c *InputService9ProtocolTest) InputService9TestCaseOperation2Request(input *InputService9TestShapeInputShape) (req *aws.Request, output *InputService9TestShapeInputService9TestCaseOperation2Output) { + op := &aws.Operation{ + Name: opInputService9TestCaseOperation2, + } + + if input == nil { + input = &InputService9TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService9TestShapeInputService9TestCaseOperation2Output{} + req.Data = output + return +} + +func (c *InputService9ProtocolTest) InputService9TestCaseOperation2(input *InputService9TestShapeInputShape) (*InputService9TestShapeInputService9TestCaseOperation2Output, error) { + req, out := c.InputService9TestCaseOperation2Request(input) + err := req.Send() + return out, err +} + +const opInputService9TestCaseOperation3 = "OperationName" + +// InputService9TestCaseOperation3Request generates a request for the InputService9TestCaseOperation3 operation. +func (c *InputService9ProtocolTest) InputService9TestCaseOperation3Request(input *InputService9TestShapeInputShape) (req *aws.Request, output *InputService9TestShapeInputService9TestCaseOperation3Output) { + op := &aws.Operation{ + Name: opInputService9TestCaseOperation3, + } + + if input == nil { + input = &InputService9TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService9TestShapeInputService9TestCaseOperation3Output{} + req.Data = output + return +} + +func (c *InputService9ProtocolTest) InputService9TestCaseOperation3(input *InputService9TestShapeInputShape) (*InputService9TestShapeInputService9TestCaseOperation3Output, error) { + req, out := c.InputService9TestCaseOperation3Request(input) + err := req.Send() + return out, err +} + +const opInputService9TestCaseOperation4 = "OperationName" + +// InputService9TestCaseOperation4Request generates a request for the InputService9TestCaseOperation4 operation. +func (c *InputService9ProtocolTest) InputService9TestCaseOperation4Request(input *InputService9TestShapeInputShape) (req *aws.Request, output *InputService9TestShapeInputService9TestCaseOperation4Output) { + op := &aws.Operation{ + Name: opInputService9TestCaseOperation4, + } + + if input == nil { + input = &InputService9TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService9TestShapeInputService9TestCaseOperation4Output{} + req.Data = output + return +} + +func (c *InputService9ProtocolTest) InputService9TestCaseOperation4(input *InputService9TestShapeInputShape) (*InputService9TestShapeInputService9TestCaseOperation4Output, error) { + req, out := c.InputService9TestCaseOperation4Request(input) + err := req.Send() + return out, err +} + +const opInputService9TestCaseOperation5 = "OperationName" + +// InputService9TestCaseOperation5Request generates a request for the InputService9TestCaseOperation5 operation. +func (c *InputService9ProtocolTest) InputService9TestCaseOperation5Request(input *InputService9TestShapeInputShape) (req *aws.Request, output *InputService9TestShapeInputService9TestCaseOperation5Output) { + op := &aws.Operation{ + Name: opInputService9TestCaseOperation5, + } + + if input == nil { + input = &InputService9TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService9TestShapeInputService9TestCaseOperation5Output{} + req.Data = output + return +} + +func (c *InputService9ProtocolTest) InputService9TestCaseOperation5(input *InputService9TestShapeInputShape) (*InputService9TestShapeInputService9TestCaseOperation5Output, error) { + req, out := c.InputService9TestCaseOperation5Request(input) + err := req.Send() + return out, err +} + +const opInputService9TestCaseOperation6 = "OperationName" + +// InputService9TestCaseOperation6Request generates a request for the InputService9TestCaseOperation6 operation. +func (c *InputService9ProtocolTest) InputService9TestCaseOperation6Request(input *InputService9TestShapeInputShape) (req *aws.Request, output *InputService9TestShapeInputService9TestCaseOperation6Output) { + op := &aws.Operation{ + Name: opInputService9TestCaseOperation6, + } + + if input == nil { + input = &InputService9TestShapeInputShape{} + } + + req = c.newRequest(op, input, output) + output = &InputService9TestShapeInputService9TestCaseOperation6Output{} + req.Data = output + return +} + +func (c *InputService9ProtocolTest) InputService9TestCaseOperation6(input *InputService9TestShapeInputShape) (*InputService9TestShapeInputService9TestCaseOperation6Output, error) { + req, out := c.InputService9TestCaseOperation6Request(input) + err := req.Send() + return out, err +} + +type InputService9TestShapeInputService9TestCaseOperation1Output struct { + metadataInputService9TestShapeInputService9TestCaseOperation1Output `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputService9TestCaseOperation1Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeInputService9TestCaseOperation2Output struct { + metadataInputService9TestShapeInputService9TestCaseOperation2Output `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputService9TestCaseOperation2Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeInputService9TestCaseOperation3Output struct { + metadataInputService9TestShapeInputService9TestCaseOperation3Output `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputService9TestCaseOperation3Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeInputService9TestCaseOperation4Output struct { + metadataInputService9TestShapeInputService9TestCaseOperation4Output `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputService9TestCaseOperation4Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeInputService9TestCaseOperation5Output struct { + metadataInputService9TestShapeInputService9TestCaseOperation5Output `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputService9TestCaseOperation5Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeInputService9TestCaseOperation6Output struct { + metadataInputService9TestShapeInputService9TestCaseOperation6Output `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputService9TestCaseOperation6Output struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeInputShape struct { + RecursiveStruct *InputService9TestShapeRecursiveStructType `type:"structure"` + + metadataInputService9TestShapeInputShape `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeInputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type InputService9TestShapeRecursiveStructType struct { + NoRecurse *string `type:"string"` + + RecursiveList []*InputService9TestShapeRecursiveStructType `type:"list"` + + RecursiveMap map[string]*InputService9TestShapeRecursiveStructType `type:"map"` + + RecursiveStruct *InputService9TestShapeRecursiveStructType `type:"structure"` + + metadataInputService9TestShapeRecursiveStructType `json:"-" xml:"-"` +} + +type metadataInputService9TestShapeRecursiveStructType struct { + SDKShapeTraits bool `type:"structure"` +} + +// +// Tests begin here +// + +func TestInputService1ProtocolTestScalarMembersCase1(t *testing.T) { + svc := NewInputService1ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService1TestShapeInputShape{ + Bar: aws.String("val2"), + Foo: aws.String("val1"), + } + req, _ := svc.InputService1TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&Bar=val2&Foo=val1&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService2ProtocolTestNestedStructureMembersCase1(t *testing.T) { + svc := NewInputService2ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService2TestShapeInputShape{ + StructArg: &InputService2TestShapeStructType{ + ScalarArg: aws.String("foo"), + }, + } + req, _ := svc.InputService2TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&StructArg.ScalarArg=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService3ProtocolTestListTypesCase1(t *testing.T) { + svc := NewInputService3ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService3TestShapeInputShape{ + ListArg: []*string{ + aws.String("foo"), + aws.String("bar"), + aws.String("baz"), + }, + } + req, _ := svc.InputService3TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&ListArg.member.1=foo&ListArg.member.2=bar&ListArg.member.3=baz&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService3ProtocolTestListTypesCase2(t *testing.T) { + svc := NewInputService3ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService3TestShapeInputShape{ + ListArg: []*string{}, + } + req, _ := svc.InputService3TestCaseOperation2Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&ListArg=&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService4ProtocolTestFlattenedListCase1(t *testing.T) { + svc := NewInputService4ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService4TestShapeInputShape{ + ListArg: []*string{ + aws.String("a"), + aws.String("b"), + aws.String("c"), + }, + ScalarArg: aws.String("foo"), + } + req, _ := svc.InputService4TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&ListArg.1=a&ListArg.2=b&ListArg.3=c&ScalarArg=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService4ProtocolTestFlattenedListCase2(t *testing.T) { + svc := NewInputService4ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService4TestShapeInputShape{ + ListArg: []*string{}, + ScalarArg: aws.String("foo"), + } + req, _ := svc.InputService4TestCaseOperation2Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&ListArg=&ScalarArg=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService5ProtocolTestSerializeMapTypeCase1(t *testing.T) { + svc := NewInputService5ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService5TestShapeInputShape{ + MapArg: map[string]*string{ + "key1": aws.String("val1"), + "key2": aws.String("val2"), + }, + } + req, _ := svc.InputService5TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&MapArg.entry.1.key=key1&MapArg.entry.1.value=val1&MapArg.entry.2.key=key2&MapArg.entry.2.value=val2&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService5ProtocolTestSerializeMapTypeCase2(t *testing.T) { + svc := NewInputService5ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService5TestShapeInputShape{ + MapArg: map[string]*string{}, + } + req, _ := svc.InputService5TestCaseOperation2Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&MapArg=&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService6ProtocolTestSerializeMapTypeWithLocationNameCase1(t *testing.T) { + svc := NewInputService6ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService6TestShapeInputShape{ + MapArg: map[string]*string{ + "key1": aws.String("val1"), + "key2": aws.String("val2"), + }, + } + req, _ := svc.InputService6TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&MapArg.entry.1.TheKey=key1&MapArg.entry.1.TheValue=val1&MapArg.entry.2.TheKey=key2&MapArg.entry.2.TheValue=val2&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService7ProtocolTestBase64EncodedBlobsCase1(t *testing.T) { + svc := NewInputService7ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService7TestShapeInputShape{ + BlobArg: []byte("foo"), + } + req, _ := svc.InputService7TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&BlobArg=Zm9v&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService8ProtocolTestTimestampValuesCase1(t *testing.T) { + svc := NewInputService8ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService8TestShapeInputShape{ + TimeArg: aws.Time(time.Unix(1422172800, 0)), + } + req, _ := svc.InputService8TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&TimeArg=2015-01-25T08%3A00%3A00Z&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService9ProtocolTestRecursiveShapesCase1(t *testing.T) { + svc := NewInputService9ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService9TestShapeInputShape{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + NoRecurse: aws.String("foo"), + }, + } + req, _ := svc.InputService9TestCaseOperation1Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&RecursiveStruct.NoRecurse=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService9ProtocolTestRecursiveShapesCase2(t *testing.T) { + svc := NewInputService9ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService9TestShapeInputShape{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + NoRecurse: aws.String("foo"), + }, + }, + } + req, _ := svc.InputService9TestCaseOperation2Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&RecursiveStruct.RecursiveStruct.NoRecurse=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService9ProtocolTestRecursiveShapesCase3(t *testing.T) { + svc := NewInputService9ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService9TestShapeInputShape{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + NoRecurse: aws.String("foo"), + }, + }, + }, + }, + } + req, _ := svc.InputService9TestCaseOperation3Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&RecursiveStruct.RecursiveStruct.RecursiveStruct.RecursiveStruct.NoRecurse=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService9ProtocolTestRecursiveShapesCase4(t *testing.T) { + svc := NewInputService9ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService9TestShapeInputShape{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveList: []*InputService9TestShapeRecursiveStructType{ + { + NoRecurse: aws.String("foo"), + }, + { + NoRecurse: aws.String("bar"), + }, + }, + }, + } + req, _ := svc.InputService9TestCaseOperation4Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&RecursiveStruct.RecursiveList.member.1.NoRecurse=foo&RecursiveStruct.RecursiveList.member.2.NoRecurse=bar&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService9ProtocolTestRecursiveShapesCase5(t *testing.T) { + svc := NewInputService9ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService9TestShapeInputShape{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveList: []*InputService9TestShapeRecursiveStructType{ + { + NoRecurse: aws.String("foo"), + }, + { + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + NoRecurse: aws.String("bar"), + }, + }, + }, + }, + } + req, _ := svc.InputService9TestCaseOperation5Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&RecursiveStruct.RecursiveList.member.1.NoRecurse=foo&RecursiveStruct.RecursiveList.member.2.RecursiveStruct.NoRecurse=bar&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} + +func TestInputService9ProtocolTestRecursiveShapesCase6(t *testing.T) { + svc := NewInputService9ProtocolTest(nil) + svc.Endpoint = "https://test" + + input := &InputService9TestShapeInputShape{ + RecursiveStruct: &InputService9TestShapeRecursiveStructType{ + RecursiveMap: map[string]*InputService9TestShapeRecursiveStructType{ + "bar": { + NoRecurse: aws.String("bar"), + }, + "foo": { + NoRecurse: aws.String("foo"), + }, + }, + }, + } + req, _ := svc.InputService9TestCaseOperation6Request(input) + r := req.HTTPRequest + + // build request + query.Build(req) + assert.NoError(t, req.Error) + + // assert body + assert.NotNil(t, r.Body) + body, _ := ioutil.ReadAll(r.Body) + assert.Equal(t, util.Trim(`Action=OperationName&RecursiveStruct.RecursiveMap.entry.1.key=bar&RecursiveStruct.RecursiveMap.entry.1.value.NoRecurse=bar&RecursiveStruct.RecursiveMap.entry.2.key=foo&RecursiveStruct.RecursiveMap.entry.2.value.NoRecurse=foo&Version=2014-01-01`), util.Trim(string(body))) + + // assert URL + assert.Equal(t, "https://test/", r.URL.String()) + + // assert headers + +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go new file mode 100644 index 00000000000..3b417a89f71 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/queryutil/queryutil.go @@ -0,0 +1,223 @@ +package queryutil + +import ( + "encoding/base64" + "fmt" + "net/url" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +// Parse parses an object i and fills a url.Values object. The isEC2 flag +// indicates if this is the EC2 Query sub-protocol. +func Parse(body url.Values, i interface{}, isEC2 bool) error { + q := queryParser{isEC2: isEC2} + return q.parseValue(body, reflect.ValueOf(i), "", "") +} + +func elemOf(value reflect.Value) reflect.Value { + for value.Kind() == reflect.Ptr { + value = value.Elem() + } + return value +} + +type queryParser struct { + isEC2 bool +} + +func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { + value = elemOf(value) + + // no need to handle zero values + if !value.IsValid() { + return nil + } + + t := tag.Get("type") + if t == "" { + switch value.Kind() { + case reflect.Struct: + t = "structure" + case reflect.Slice: + t = "list" + case reflect.Map: + t = "map" + } + } + + switch t { + case "structure": + return q.parseStruct(v, value, prefix) + case "list": + return q.parseList(v, value, prefix, tag) + case "map": + return q.parseMap(v, value, prefix, tag) + default: + return q.parseScalar(v, value, prefix, tag) + } +} + +func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error { + if !value.IsValid() { + return nil + } + + t := value.Type() + for i := 0; i < value.NumField(); i++ { + if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c { + continue // ignore unexported fields + } + + value := elemOf(value.Field(i)) + field := t.Field(i) + var name string + + if q.isEC2 { + name = field.Tag.Get("queryName") + } + if name == "" { + if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" { + name = field.Tag.Get("locationNameList") + } else if locName := field.Tag.Get("locationName"); locName != "" { + name = locName + } + if name != "" && q.isEC2 { + name = strings.ToUpper(name[0:1]) + name[1:] + } + } + if name == "" { + name = field.Name + } + + if prefix != "" { + name = prefix + "." + name + } + + if err := q.parseValue(v, value, name, field.Tag); err != nil { + return err + } + } + return nil +} + +func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { + // If it's empty, generate an empty value + if !value.IsNil() && value.Len() == 0 { + v.Set(prefix, "") + return nil + } + + // check for unflattened list member + if !q.isEC2 && tag.Get("flattened") == "" { + prefix += ".member" + } + + for i := 0; i < value.Len(); i++ { + slicePrefix := prefix + if slicePrefix == "" { + slicePrefix = strconv.Itoa(i + 1) + } else { + slicePrefix = slicePrefix + "." + strconv.Itoa(i+1) + } + if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil { + return err + } + } + return nil +} + +func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error { + // If it's empty, generate an empty value + if !value.IsNil() && value.Len() == 0 { + v.Set(prefix, "") + return nil + } + + // check for unflattened list member + if !q.isEC2 && tag.Get("flattened") == "" { + prefix += ".entry" + } + + // sort keys for improved serialization consistency. + // this is not strictly necessary for protocol support. + mapKeyValues := value.MapKeys() + mapKeys := map[string]reflect.Value{} + mapKeyNames := make([]string, len(mapKeyValues)) + for i, mapKey := range mapKeyValues { + name := mapKey.String() + mapKeys[name] = mapKey + mapKeyNames[i] = name + } + sort.Strings(mapKeyNames) + + for i, mapKeyName := range mapKeyNames { + mapKey := mapKeys[mapKeyName] + mapValue := value.MapIndex(mapKey) + + kname := tag.Get("locationNameKey") + if kname == "" { + kname = "key" + } + vname := tag.Get("locationNameValue") + if vname == "" { + vname = "value" + } + + // serialize key + var keyName string + if prefix == "" { + keyName = strconv.Itoa(i+1) + "." + kname + } else { + keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname + } + + if err := q.parseValue(v, mapKey, keyName, ""); err != nil { + return err + } + + // serialize value + var valueName string + if prefix == "" { + valueName = strconv.Itoa(i+1) + "." + vname + } else { + valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname + } + + if err := q.parseValue(v, mapValue, valueName, ""); err != nil { + return err + } + } + + return nil +} + +func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error { + switch value := r.Interface().(type) { + case string: + v.Set(name, value) + case []byte: + if !r.IsNil() { + v.Set(name, base64.StdEncoding.EncodeToString(value)) + } + case bool: + v.Set(name, strconv.FormatBool(value)) + case int64: + v.Set(name, strconv.FormatInt(value, 10)) + case int: + v.Set(name, strconv.Itoa(value)) + case float64: + v.Set(name, strconv.FormatFloat(value, 'f', -1, 64)) + case float32: + v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32)) + case time.Time: + const ISO8601UTC = "2006-01-02T15:04:05Z" + v.Set(name, value.UTC().Format(ISO8601UTC)) + default: + return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name()) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal.go new file mode 100644 index 00000000000..e8cfa926b65 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal.go @@ -0,0 +1,29 @@ +package query + +//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/query.json unmarshal_test.go + +import ( + "encoding/xml" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil" +) + +// Unmarshal unmarshals a response for an AWS Query service. +func Unmarshal(r *aws.Request) { + defer r.HTTPResponse.Body.Close() + if r.DataFilled() { + decoder := xml.NewDecoder(r.HTTPResponse.Body) + err := xmlutil.UnmarshalXML(r.Data, decoder, r.Operation.Name+"Result") + if err != nil { + r.Error = awserr.New("SerializationError", "failed decoding Query response", err) + return + } + } +} + +// UnmarshalMeta unmarshals header response values for an AWS Query service. +func UnmarshalMeta(r *aws.Request) { + // TODO implement unmarshaling of request IDs +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_error.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_error.go new file mode 100644 index 00000000000..d88ee335805 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_error.go @@ -0,0 +1,33 @@ +package query + +import ( + "encoding/xml" + "io" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" +) + +type xmlErrorResponse struct { + XMLName xml.Name `xml:"ErrorResponse"` + Code string `xml:"Error>Code"` + Message string `xml:"Error>Message"` + RequestID string `xml:"RequestId"` +} + +// UnmarshalError unmarshals an error response for an AWS Query service. +func UnmarshalError(r *aws.Request) { + defer r.HTTPResponse.Body.Close() + + resp := &xmlErrorResponse{} + err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp) + if err != nil && err != io.EOF { + r.Error = awserr.New("SerializationError", "failed to decode query XML error response", err) + } else { + r.Error = awserr.NewRequestFailure( + awserr.New(resp.Code, resp.Message, nil), + r.HTTPResponse.StatusCode, + resp.RequestID, + ) + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_test.go new file mode 100644 index 00000000000..a44060ce8fe --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/query/unmarshal_test.go @@ -0,0 +1,1418 @@ +package query_test + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "io" + "io/ioutil" + "net/http" + "net/url" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/internal/protocol/query" + "github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil" + "github.com/aws/aws-sdk-go/internal/signer/v4" + "github.com/aws/aws-sdk-go/internal/util" + "github.com/stretchr/testify/assert" +) + +var _ bytes.Buffer // always import bytes +var _ http.Request +var _ json.Marshaler +var _ time.Time +var _ xmlutil.XMLNode +var _ xml.Attr +var _ = ioutil.Discard +var _ = util.Trim("") +var _ = url.Values{} +var _ = io.EOF + +type OutputService1ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService1ProtocolTest client. +func NewOutputService1ProtocolTest(config *aws.Config) *OutputService1ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice1protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService1ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService1ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService1ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService1TestCaseOperation1 = "OperationName" + +// OutputService1TestCaseOperation1Request generates a request for the OutputService1TestCaseOperation1 operation. +func (c *OutputService1ProtocolTest) OutputService1TestCaseOperation1Request(input *OutputService1TestShapeOutputService1TestCaseOperation1Input) (req *aws.Request, output *OutputService1TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService1TestCaseOperation1, + } + + if input == nil { + input = &OutputService1TestShapeOutputService1TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService1TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService1ProtocolTest) OutputService1TestCaseOperation1(input *OutputService1TestShapeOutputService1TestCaseOperation1Input) (*OutputService1TestShapeOutputShape, error) { + req, out := c.OutputService1TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService1TestShapeOutputService1TestCaseOperation1Input struct { + metadataOutputService1TestShapeOutputService1TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService1TestShapeOutputService1TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService1TestShapeOutputShape struct { + Char *string `type:"character"` + + Double *float64 `type:"double"` + + FalseBool *bool `type:"boolean"` + + Float *float64 `type:"float"` + + Long *int64 `type:"long"` + + Num *int64 `locationName:"FooNum" type:"integer"` + + Str *string `type:"string"` + + Timestamp *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + TrueBool *bool `type:"boolean"` + + metadataOutputService1TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService1TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService2ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService2ProtocolTest client. +func NewOutputService2ProtocolTest(config *aws.Config) *OutputService2ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice2protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService2ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService2ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService2ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService2TestCaseOperation1 = "OperationName" + +// OutputService2TestCaseOperation1Request generates a request for the OutputService2TestCaseOperation1 operation. +func (c *OutputService2ProtocolTest) OutputService2TestCaseOperation1Request(input *OutputService2TestShapeOutputService2TestCaseOperation1Input) (req *aws.Request, output *OutputService2TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService2TestCaseOperation1, + } + + if input == nil { + input = &OutputService2TestShapeOutputService2TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService2TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService2ProtocolTest) OutputService2TestCaseOperation1(input *OutputService2TestShapeOutputService2TestCaseOperation1Input) (*OutputService2TestShapeOutputShape, error) { + req, out := c.OutputService2TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService2TestShapeOutputService2TestCaseOperation1Input struct { + metadataOutputService2TestShapeOutputService2TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService2TestShapeOutputService2TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService2TestShapeOutputShape struct { + Num *int64 `type:"integer"` + + Str *string `type:"string"` + + metadataOutputService2TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService2TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService3ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService3ProtocolTest client. +func NewOutputService3ProtocolTest(config *aws.Config) *OutputService3ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice3protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService3ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService3ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService3ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService3TestCaseOperation1 = "OperationName" + +// OutputService3TestCaseOperation1Request generates a request for the OutputService3TestCaseOperation1 operation. +func (c *OutputService3ProtocolTest) OutputService3TestCaseOperation1Request(input *OutputService3TestShapeOutputService3TestCaseOperation1Input) (req *aws.Request, output *OutputService3TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService3TestCaseOperation1, + } + + if input == nil { + input = &OutputService3TestShapeOutputService3TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService3TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService3ProtocolTest) OutputService3TestCaseOperation1(input *OutputService3TestShapeOutputService3TestCaseOperation1Input) (*OutputService3TestShapeOutputShape, error) { + req, out := c.OutputService3TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService3TestShapeOutputService3TestCaseOperation1Input struct { + metadataOutputService3TestShapeOutputService3TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService3TestShapeOutputService3TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService3TestShapeOutputShape struct { + Blob []byte `type:"blob"` + + metadataOutputService3TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService3TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService4ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService4ProtocolTest client. +func NewOutputService4ProtocolTest(config *aws.Config) *OutputService4ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice4protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService4ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService4ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService4ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService4TestCaseOperation1 = "OperationName" + +// OutputService4TestCaseOperation1Request generates a request for the OutputService4TestCaseOperation1 operation. +func (c *OutputService4ProtocolTest) OutputService4TestCaseOperation1Request(input *OutputService4TestShapeOutputService4TestCaseOperation1Input) (req *aws.Request, output *OutputService4TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService4TestCaseOperation1, + } + + if input == nil { + input = &OutputService4TestShapeOutputService4TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService4TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService4ProtocolTest) OutputService4TestCaseOperation1(input *OutputService4TestShapeOutputService4TestCaseOperation1Input) (*OutputService4TestShapeOutputShape, error) { + req, out := c.OutputService4TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService4TestShapeOutputService4TestCaseOperation1Input struct { + metadataOutputService4TestShapeOutputService4TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService4TestShapeOutputService4TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService4TestShapeOutputShape struct { + ListMember []*string `type:"list"` + + metadataOutputService4TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService4TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService5ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService5ProtocolTest client. +func NewOutputService5ProtocolTest(config *aws.Config) *OutputService5ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice5protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService5ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService5ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService5ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService5TestCaseOperation1 = "OperationName" + +// OutputService5TestCaseOperation1Request generates a request for the OutputService5TestCaseOperation1 operation. +func (c *OutputService5ProtocolTest) OutputService5TestCaseOperation1Request(input *OutputService5TestShapeOutputService5TestCaseOperation1Input) (req *aws.Request, output *OutputService5TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService5TestCaseOperation1, + } + + if input == nil { + input = &OutputService5TestShapeOutputService5TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService5TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService5ProtocolTest) OutputService5TestCaseOperation1(input *OutputService5TestShapeOutputService5TestCaseOperation1Input) (*OutputService5TestShapeOutputShape, error) { + req, out := c.OutputService5TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService5TestShapeOutputService5TestCaseOperation1Input struct { + metadataOutputService5TestShapeOutputService5TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService5TestShapeOutputService5TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService5TestShapeOutputShape struct { + ListMember []*string `locationNameList:"item" type:"list"` + + metadataOutputService5TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService5TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService6ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService6ProtocolTest client. +func NewOutputService6ProtocolTest(config *aws.Config) *OutputService6ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice6protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService6ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService6ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService6ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService6TestCaseOperation1 = "OperationName" + +// OutputService6TestCaseOperation1Request generates a request for the OutputService6TestCaseOperation1 operation. +func (c *OutputService6ProtocolTest) OutputService6TestCaseOperation1Request(input *OutputService6TestShapeOutputService6TestCaseOperation1Input) (req *aws.Request, output *OutputService6TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService6TestCaseOperation1, + } + + if input == nil { + input = &OutputService6TestShapeOutputService6TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService6TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService6ProtocolTest) OutputService6TestCaseOperation1(input *OutputService6TestShapeOutputService6TestCaseOperation1Input) (*OutputService6TestShapeOutputShape, error) { + req, out := c.OutputService6TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService6TestShapeOutputService6TestCaseOperation1Input struct { + metadataOutputService6TestShapeOutputService6TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService6TestShapeOutputService6TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService6TestShapeOutputShape struct { + ListMember []*string `type:"list" flattened:"true"` + + metadataOutputService6TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService6TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService7ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService7ProtocolTest client. +func NewOutputService7ProtocolTest(config *aws.Config) *OutputService7ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice7protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService7ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService7ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService7ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService7TestCaseOperation1 = "OperationName" + +// OutputService7TestCaseOperation1Request generates a request for the OutputService7TestCaseOperation1 operation. +func (c *OutputService7ProtocolTest) OutputService7TestCaseOperation1Request(input *OutputService7TestShapeOutputService7TestCaseOperation1Input) (req *aws.Request, output *OutputService7TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService7TestCaseOperation1, + } + + if input == nil { + input = &OutputService7TestShapeOutputService7TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService7TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService7ProtocolTest) OutputService7TestCaseOperation1(input *OutputService7TestShapeOutputService7TestCaseOperation1Input) (*OutputService7TestShapeOutputShape, error) { + req, out := c.OutputService7TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService7TestShapeOutputService7TestCaseOperation1Input struct { + metadataOutputService7TestShapeOutputService7TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService7TestShapeOutputService7TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService7TestShapeOutputShape struct { + ListMember []*string `type:"list" flattened:"true"` + + metadataOutputService7TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService7TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService8ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService8ProtocolTest client. +func NewOutputService8ProtocolTest(config *aws.Config) *OutputService8ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice8protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService8ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService8ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService8ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService8TestCaseOperation1 = "OperationName" + +// OutputService8TestCaseOperation1Request generates a request for the OutputService8TestCaseOperation1 operation. +func (c *OutputService8ProtocolTest) OutputService8TestCaseOperation1Request(input *OutputService8TestShapeOutputService8TestCaseOperation1Input) (req *aws.Request, output *OutputService8TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService8TestCaseOperation1, + } + + if input == nil { + input = &OutputService8TestShapeOutputService8TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService8TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService8ProtocolTest) OutputService8TestCaseOperation1(input *OutputService8TestShapeOutputService8TestCaseOperation1Input) (*OutputService8TestShapeOutputShape, error) { + req, out := c.OutputService8TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService8TestShapeOutputService8TestCaseOperation1Input struct { + metadataOutputService8TestShapeOutputService8TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService8TestShapeOutputService8TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService8TestShapeOutputShape struct { + List []*OutputService8TestShapeStructureShape `type:"list"` + + metadataOutputService8TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService8TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService8TestShapeStructureShape struct { + Bar *string `type:"string"` + + Baz *string `type:"string"` + + Foo *string `type:"string"` + + metadataOutputService8TestShapeStructureShape `json:"-" xml:"-"` +} + +type metadataOutputService8TestShapeStructureShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService9ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService9ProtocolTest client. +func NewOutputService9ProtocolTest(config *aws.Config) *OutputService9ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice9protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService9ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService9ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService9ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService9TestCaseOperation1 = "OperationName" + +// OutputService9TestCaseOperation1Request generates a request for the OutputService9TestCaseOperation1 operation. +func (c *OutputService9ProtocolTest) OutputService9TestCaseOperation1Request(input *OutputService9TestShapeOutputService9TestCaseOperation1Input) (req *aws.Request, output *OutputService9TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService9TestCaseOperation1, + } + + if input == nil { + input = &OutputService9TestShapeOutputService9TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService9TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService9ProtocolTest) OutputService9TestCaseOperation1(input *OutputService9TestShapeOutputService9TestCaseOperation1Input) (*OutputService9TestShapeOutputShape, error) { + req, out := c.OutputService9TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService9TestShapeOutputService9TestCaseOperation1Input struct { + metadataOutputService9TestShapeOutputService9TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService9TestShapeOutputService9TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService9TestShapeOutputShape struct { + List []*OutputService9TestShapeStructureShape `type:"list" flattened:"true"` + + metadataOutputService9TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService9TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService9TestShapeStructureShape struct { + Bar *string `type:"string"` + + Baz *string `type:"string"` + + Foo *string `type:"string"` + + metadataOutputService9TestShapeStructureShape `json:"-" xml:"-"` +} + +type metadataOutputService9TestShapeStructureShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService10ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService10ProtocolTest client. +func NewOutputService10ProtocolTest(config *aws.Config) *OutputService10ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice10protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService10ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService10ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService10ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService10TestCaseOperation1 = "OperationName" + +// OutputService10TestCaseOperation1Request generates a request for the OutputService10TestCaseOperation1 operation. +func (c *OutputService10ProtocolTest) OutputService10TestCaseOperation1Request(input *OutputService10TestShapeOutputService10TestCaseOperation1Input) (req *aws.Request, output *OutputService10TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService10TestCaseOperation1, + } + + if input == nil { + input = &OutputService10TestShapeOutputService10TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService10TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService10ProtocolTest) OutputService10TestCaseOperation1(input *OutputService10TestShapeOutputService10TestCaseOperation1Input) (*OutputService10TestShapeOutputShape, error) { + req, out := c.OutputService10TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService10TestShapeOutputService10TestCaseOperation1Input struct { + metadataOutputService10TestShapeOutputService10TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService10TestShapeOutputService10TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService10TestShapeOutputShape struct { + List []*string `locationNameList:"NamedList" type:"list" flattened:"true"` + + metadataOutputService10TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService10TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService11ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService11ProtocolTest client. +func NewOutputService11ProtocolTest(config *aws.Config) *OutputService11ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice11protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService11ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService11ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService11ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService11TestCaseOperation1 = "OperationName" + +// OutputService11TestCaseOperation1Request generates a request for the OutputService11TestCaseOperation1 operation. +func (c *OutputService11ProtocolTest) OutputService11TestCaseOperation1Request(input *OutputService11TestShapeOutputService11TestCaseOperation1Input) (req *aws.Request, output *OutputService11TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService11TestCaseOperation1, + } + + if input == nil { + input = &OutputService11TestShapeOutputService11TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService11TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService11ProtocolTest) OutputService11TestCaseOperation1(input *OutputService11TestShapeOutputService11TestCaseOperation1Input) (*OutputService11TestShapeOutputShape, error) { + req, out := c.OutputService11TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService11TestShapeOutputService11TestCaseOperation1Input struct { + metadataOutputService11TestShapeOutputService11TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService11TestShapeOutputService11TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService11TestShapeOutputShape struct { + Map map[string]*OutputService11TestShapeStructType `type:"map"` + + metadataOutputService11TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService11TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService11TestShapeStructType struct { + Foo *string `locationName:"foo" type:"string"` + + metadataOutputService11TestShapeStructType `json:"-" xml:"-"` +} + +type metadataOutputService11TestShapeStructType struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService12ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService12ProtocolTest client. +func NewOutputService12ProtocolTest(config *aws.Config) *OutputService12ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice12protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService12ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService12ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService12ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService12TestCaseOperation1 = "OperationName" + +// OutputService12TestCaseOperation1Request generates a request for the OutputService12TestCaseOperation1 operation. +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1Request(input *OutputService12TestShapeOutputService12TestCaseOperation1Input) (req *aws.Request, output *OutputService12TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService12TestCaseOperation1, + } + + if input == nil { + input = &OutputService12TestShapeOutputService12TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService12TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService12ProtocolTest) OutputService12TestCaseOperation1(input *OutputService12TestShapeOutputService12TestCaseOperation1Input) (*OutputService12TestShapeOutputShape, error) { + req, out := c.OutputService12TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService12TestShapeOutputService12TestCaseOperation1Input struct { + metadataOutputService12TestShapeOutputService12TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService12TestShapeOutputService12TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService12TestShapeOutputShape struct { + Map map[string]*string `type:"map" flattened:"true"` + + metadataOutputService12TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService12TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService13ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService13ProtocolTest client. +func NewOutputService13ProtocolTest(config *aws.Config) *OutputService13ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice13protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService13ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService13ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService13ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService13TestCaseOperation1 = "OperationName" + +// OutputService13TestCaseOperation1Request generates a request for the OutputService13TestCaseOperation1 operation. +func (c *OutputService13ProtocolTest) OutputService13TestCaseOperation1Request(input *OutputService13TestShapeOutputService13TestCaseOperation1Input) (req *aws.Request, output *OutputService13TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService13TestCaseOperation1, + } + + if input == nil { + input = &OutputService13TestShapeOutputService13TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService13TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService13ProtocolTest) OutputService13TestCaseOperation1(input *OutputService13TestShapeOutputService13TestCaseOperation1Input) (*OutputService13TestShapeOutputShape, error) { + req, out := c.OutputService13TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService13TestShapeOutputService13TestCaseOperation1Input struct { + metadataOutputService13TestShapeOutputService13TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService13TestShapeOutputService13TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService13TestShapeOutputShape struct { + Map map[string]*string `locationName:"Attribute" locationNameKey:"Name" locationNameValue:"Value" type:"map" flattened:"true"` + + metadataOutputService13TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService13TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService14ProtocolTest struct { + *aws.Service +} + +// New returns a new OutputService14ProtocolTest client. +func NewOutputService14ProtocolTest(config *aws.Config) *OutputService14ProtocolTest { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "outputservice14protocoltest", + APIVersion: "", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + return &OutputService14ProtocolTest{service} +} + +// newRequest creates a new request for a OutputService14ProtocolTest operation and runs any +// custom request initialization. +func (c *OutputService14ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + return req +} + +const opOutputService14TestCaseOperation1 = "OperationName" + +// OutputService14TestCaseOperation1Request generates a request for the OutputService14TestCaseOperation1 operation. +func (c *OutputService14ProtocolTest) OutputService14TestCaseOperation1Request(input *OutputService14TestShapeOutputService14TestCaseOperation1Input) (req *aws.Request, output *OutputService14TestShapeOutputShape) { + op := &aws.Operation{ + Name: opOutputService14TestCaseOperation1, + } + + if input == nil { + input = &OutputService14TestShapeOutputService14TestCaseOperation1Input{} + } + + req = c.newRequest(op, input, output) + output = &OutputService14TestShapeOutputShape{} + req.Data = output + return +} + +func (c *OutputService14ProtocolTest) OutputService14TestCaseOperation1(input *OutputService14TestShapeOutputService14TestCaseOperation1Input) (*OutputService14TestShapeOutputShape, error) { + req, out := c.OutputService14TestCaseOperation1Request(input) + err := req.Send() + return out, err +} + +type OutputService14TestShapeOutputService14TestCaseOperation1Input struct { + metadataOutputService14TestShapeOutputService14TestCaseOperation1Input `json:"-" xml:"-"` +} + +type metadataOutputService14TestShapeOutputService14TestCaseOperation1Input struct { + SDKShapeTraits bool `type:"structure"` +} + +type OutputService14TestShapeOutputShape struct { + Map map[string]*string `locationNameKey:"foo" locationNameValue:"bar" type:"map" flattened:"true"` + + metadataOutputService14TestShapeOutputShape `json:"-" xml:"-"` +} + +type metadataOutputService14TestShapeOutputShape struct { + SDKShapeTraits bool `type:"structure"` +} + +// +// Tests begin here +// + +func TestOutputService1ProtocolTestScalarMembersCase1(t *testing.T) { + svc := NewOutputService1ProtocolTest(nil) + + buf := bytes.NewReader([]byte("myname123falsetrue1.21.3200a2015-01-25T08:00:00Zrequest-id")) + req, out := svc.OutputService1TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "a", *out.Char) + assert.Equal(t, 1.3, *out.Double) + assert.Equal(t, false, *out.FalseBool) + assert.Equal(t, 1.2, *out.Float) + assert.Equal(t, int64(200), *out.Long) + assert.Equal(t, int64(123), *out.Num) + assert.Equal(t, "myname", *out.Str) + assert.Equal(t, time.Unix(1.4221728e+09, 0).UTC().String(), out.Timestamp.String()) + assert.Equal(t, true, *out.TrueBool) + +} + +func TestOutputService2ProtocolTestNotAllMembersInResponseCase1(t *testing.T) { + svc := NewOutputService2ProtocolTest(nil) + + buf := bytes.NewReader([]byte("mynamerequest-id")) + req, out := svc.OutputService2TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "myname", *out.Str) + +} + +func TestOutputService3ProtocolTestBlobCase1(t *testing.T) { + svc := NewOutputService3ProtocolTest(nil) + + buf := bytes.NewReader([]byte("dmFsdWU=requestid")) + req, out := svc.OutputService3TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "value", string(out.Blob)) + +} + +func TestOutputService4ProtocolTestListsCase1(t *testing.T) { + svc := NewOutputService4ProtocolTest(nil) + + buf := bytes.NewReader([]byte("abc123requestid")) + req, out := svc.OutputService4TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "abc", *out.ListMember[0]) + assert.Equal(t, "123", *out.ListMember[1]) + +} + +func TestOutputService5ProtocolTestListWithCustomMemberNameCase1(t *testing.T) { + svc := NewOutputService5ProtocolTest(nil) + + buf := bytes.NewReader([]byte("abc123requestid")) + req, out := svc.OutputService5TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "abc", *out.ListMember[0]) + assert.Equal(t, "123", *out.ListMember[1]) + +} + +func TestOutputService6ProtocolTestFlattenedListCase1(t *testing.T) { + svc := NewOutputService6ProtocolTest(nil) + + buf := bytes.NewReader([]byte("abc123requestid")) + req, out := svc.OutputService6TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "abc", *out.ListMember[0]) + assert.Equal(t, "123", *out.ListMember[1]) + +} + +func TestOutputService7ProtocolTestFlattenedSingleElementListCase1(t *testing.T) { + svc := NewOutputService7ProtocolTest(nil) + + buf := bytes.NewReader([]byte("abcrequestid")) + req, out := svc.OutputService7TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "abc", *out.ListMember[0]) + +} + +func TestOutputService8ProtocolTestListOfStructuresCase1(t *testing.T) { + svc := NewOutputService8ProtocolTest(nil) + + buf := bytes.NewReader([]byte("firstfoofirstbarfirstbazsecondfoosecondbarsecondbazrequestid")) + req, out := svc.OutputService8TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "firstbar", *out.List[0].Bar) + assert.Equal(t, "firstbaz", *out.List[0].Baz) + assert.Equal(t, "firstfoo", *out.List[0].Foo) + assert.Equal(t, "secondbar", *out.List[1].Bar) + assert.Equal(t, "secondbaz", *out.List[1].Baz) + assert.Equal(t, "secondfoo", *out.List[1].Foo) + +} + +func TestOutputService9ProtocolTestFlattenedListOfStructuresCase1(t *testing.T) { + svc := NewOutputService9ProtocolTest(nil) + + buf := bytes.NewReader([]byte("firstfoofirstbarfirstbazsecondfoosecondbarsecondbazrequestid")) + req, out := svc.OutputService9TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "firstbar", *out.List[0].Bar) + assert.Equal(t, "firstbaz", *out.List[0].Baz) + assert.Equal(t, "firstfoo", *out.List[0].Foo) + assert.Equal(t, "secondbar", *out.List[1].Bar) + assert.Equal(t, "secondbaz", *out.List[1].Baz) + assert.Equal(t, "secondfoo", *out.List[1].Foo) + +} + +func TestOutputService10ProtocolTestFlattenedListWithLocationNameCase1(t *testing.T) { + svc := NewOutputService10ProtocolTest(nil) + + buf := bytes.NewReader([]byte("abrequestid")) + req, out := svc.OutputService10TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "a", *out.List[0]) + assert.Equal(t, "b", *out.List[1]) + +} + +func TestOutputService11ProtocolTestNormalMapCase1(t *testing.T) { + svc := NewOutputService11ProtocolTest(nil) + + buf := bytes.NewReader([]byte("quxbarbazbamrequestid")) + req, out := svc.OutputService11TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "bam", *out.Map["baz"].Foo) + assert.Equal(t, "bar", *out.Map["qux"].Foo) + +} + +func TestOutputService12ProtocolTestFlattenedMapCase1(t *testing.T) { + svc := NewOutputService12ProtocolTest(nil) + + buf := bytes.NewReader([]byte("quxbarbazbamrequestid")) + req, out := svc.OutputService12TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "bam", *out.Map["baz"]) + assert.Equal(t, "bar", *out.Map["qux"]) + +} + +func TestOutputService13ProtocolTestFlattenedMapInShapeDefinitionCase1(t *testing.T) { + svc := NewOutputService13ProtocolTest(nil) + + buf := bytes.NewReader([]byte("quxbarrequestid")) + req, out := svc.OutputService13TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "bar", *out.Map["qux"]) + +} + +func TestOutputService14ProtocolTestNamedMapCase1(t *testing.T) { + svc := NewOutputService14ProtocolTest(nil) + + buf := bytes.NewReader([]byte("quxbarbazbamrequestid")) + req, out := svc.OutputService14TestCaseOperation1Request(nil) + req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}} + + // set headers + + // unmarshal response + query.UnmarshalMeta(req) + query.Unmarshal(req) + assert.NoError(t, req.Error) + + // assert response + assert.NotNil(t, out) // ensure out variable is used + assert.Equal(t, "bam", *out.Map["baz"]) + assert.Equal(t, "bar", *out.Map["qux"]) + +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/build.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/build.go new file mode 100644 index 00000000000..8917ad7a9bc --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/build.go @@ -0,0 +1,217 @@ +// Package rest provides RESTful serialisation of AWS requests and responses. +package rest + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "net/url" + "path" + "reflect" + "strconv" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" +) + +// RFC822 returns an RFC822 formatted timestamp for AWS protocols +const RFC822 = "Mon, 2 Jan 2006 15:04:05 GMT" + +// Whether the byte value can be sent without escaping in AWS URLs +var noEscape [256]bool + +func init() { + for i := 0; i < len(noEscape); i++ { + // AWS expects every character except these to be escaped + noEscape[i] = (i >= 'A' && i <= 'Z') || + (i >= 'a' && i <= 'z') || + (i >= '0' && i <= '9') || + i == '-' || + i == '.' || + i == '_' || + i == '~' + } +} + +// Build builds the REST component of a service request. +func Build(r *aws.Request) { + if r.ParamsFilled() { + v := reflect.ValueOf(r.Params).Elem() + buildLocationElements(r, v) + buildBody(r, v) + } +} + +func buildLocationElements(r *aws.Request, v reflect.Value) { + query := r.HTTPRequest.URL.Query() + + for i := 0; i < v.NumField(); i++ { + m := v.Field(i) + if n := v.Type().Field(i).Name; n[0:1] == strings.ToLower(n[0:1]) { + continue + } + + if m.IsValid() { + field := v.Type().Field(i) + name := field.Tag.Get("locationName") + if name == "" { + name = field.Name + } + if m.Kind() == reflect.Ptr { + m = m.Elem() + } + if !m.IsValid() { + continue + } + + switch field.Tag.Get("location") { + case "headers": // header maps + buildHeaderMap(r, m, field.Tag.Get("locationName")) + case "header": + buildHeader(r, m, name) + case "uri": + buildURI(r, m, name) + case "querystring": + buildQueryString(r, m, name, query) + } + } + if r.Error != nil { + return + } + } + + r.HTTPRequest.URL.RawQuery = query.Encode() + updatePath(r.HTTPRequest.URL, r.HTTPRequest.URL.Path) +} + +func buildBody(r *aws.Request, v reflect.Value) { + if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok { + if payloadName := field.Tag.Get("payload"); payloadName != "" { + pfield, _ := v.Type().FieldByName(payloadName) + if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" { + payload := reflect.Indirect(v.FieldByName(payloadName)) + if payload.IsValid() && payload.Interface() != nil { + switch reader := payload.Interface().(type) { + case io.ReadSeeker: + r.SetReaderBody(reader) + case []byte: + r.SetBufferBody(reader) + case string: + r.SetStringBody(reader) + default: + r.Error = awserr.New("SerializationError", + "failed to encode REST request", + fmt.Errorf("unknown payload type %s", payload.Type())) + } + } + } + } + } +} + +func buildHeader(r *aws.Request, v reflect.Value, name string) { + str, err := convertType(v) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to encode REST request", err) + } else if str != nil { + r.HTTPRequest.Header.Add(name, *str) + } +} + +func buildHeaderMap(r *aws.Request, v reflect.Value, prefix string) { + for _, key := range v.MapKeys() { + str, err := convertType(v.MapIndex(key)) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to encode REST request", err) + } else if str != nil { + r.HTTPRequest.Header.Add(prefix+key.String(), *str) + } + } +} + +func buildURI(r *aws.Request, v reflect.Value, name string) { + value, err := convertType(v) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to encode REST request", err) + } else if value != nil { + uri := r.HTTPRequest.URL.Path + uri = strings.Replace(uri, "{"+name+"}", EscapePath(*value, true), -1) + uri = strings.Replace(uri, "{"+name+"+}", EscapePath(*value, false), -1) + r.HTTPRequest.URL.Path = uri + } +} + +func buildQueryString(r *aws.Request, v reflect.Value, name string, query url.Values) { + str, err := convertType(v) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to encode REST request", err) + } else if str != nil { + query.Set(name, *str) + } +} + +func updatePath(url *url.URL, urlPath string) { + scheme, query := url.Scheme, url.RawQuery + + hasSlash := strings.HasSuffix(urlPath, "/") + + // clean up path + urlPath = path.Clean(urlPath) + if hasSlash && !strings.HasSuffix(urlPath, "/") { + urlPath += "/" + } + + // get formatted URL minus scheme so we can build this into Opaque + url.Scheme, url.Path, url.RawQuery = "", "", "" + s := url.String() + url.Scheme = scheme + url.RawQuery = query + + // build opaque URI + url.Opaque = s + urlPath +} + +// EscapePath escapes part of a URL path in Amazon style +func EscapePath(path string, encodeSep bool) string { + var buf bytes.Buffer + for i := 0; i < len(path); i++ { + c := path[i] + if noEscape[c] || (c == '/' && !encodeSep) { + buf.WriteByte(c) + } else { + buf.WriteByte('%') + buf.WriteString(strings.ToUpper(strconv.FormatUint(uint64(c), 16))) + } + } + return buf.String() +} + +func convertType(v reflect.Value) (*string, error) { + v = reflect.Indirect(v) + if !v.IsValid() { + return nil, nil + } + + var str string + switch value := v.Interface().(type) { + case string: + str = value + case []byte: + str = base64.StdEncoding.EncodeToString(value) + case bool: + str = strconv.FormatBool(value) + case int64: + str = strconv.FormatInt(value, 10) + case float64: + str = strconv.FormatFloat(value, 'f', -1, 64) + case time.Time: + str = value.UTC().Format(RFC822) + default: + err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type()) + return nil, err + } + return &str, nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/payload.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/payload.go new file mode 100644 index 00000000000..1f603bb719f --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/payload.go @@ -0,0 +1,45 @@ +package rest + +import "reflect" + +// PayloadMember returns the payload field member of i if there is one, or nil. +func PayloadMember(i interface{}) interface{} { + if i == nil { + return nil + } + + v := reflect.ValueOf(i).Elem() + if !v.IsValid() { + return nil + } + if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok { + if payloadName := field.Tag.Get("payload"); payloadName != "" { + field, _ := v.Type().FieldByName(payloadName) + if field.Tag.Get("type") != "structure" { + return nil + } + + payload := v.FieldByName(payloadName) + if payload.IsValid() || (payload.Kind() == reflect.Ptr && !payload.IsNil()) { + return payload.Interface() + } + } + } + return nil +} + +// PayloadType returns the type of a payload field member of i if there is one, or "". +func PayloadType(i interface{}) string { + v := reflect.Indirect(reflect.ValueOf(i)) + if !v.IsValid() { + return "" + } + if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok { + if payloadName := field.Tag.Get("payload"); payloadName != "" { + if member, ok := v.Type().FieldByName(payloadName); ok { + return member.Tag.Get("type") + } + } + } + return "" +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/unmarshal.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/unmarshal.go new file mode 100644 index 00000000000..a4155f16699 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/rest/unmarshal.go @@ -0,0 +1,174 @@ +package rest + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "strconv" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" +) + +// Unmarshal unmarshals the REST component of a response in a REST service. +func Unmarshal(r *aws.Request) { + if r.DataFilled() { + v := reflect.Indirect(reflect.ValueOf(r.Data)) + unmarshalBody(r, v) + unmarshalLocationElements(r, v) + } +} + +func unmarshalBody(r *aws.Request, v reflect.Value) { + if field, ok := v.Type().FieldByName("SDKShapeTraits"); ok { + if payloadName := field.Tag.Get("payload"); payloadName != "" { + pfield, _ := v.Type().FieldByName(payloadName) + if ptag := pfield.Tag.Get("type"); ptag != "" && ptag != "structure" { + payload := v.FieldByName(payloadName) + if payload.IsValid() { + switch payload.Interface().(type) { + case []byte: + b, err := ioutil.ReadAll(r.HTTPResponse.Body) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to decode REST response", err) + } else { + payload.Set(reflect.ValueOf(b)) + } + case *string: + b, err := ioutil.ReadAll(r.HTTPResponse.Body) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to decode REST response", err) + } else { + str := string(b) + payload.Set(reflect.ValueOf(&str)) + } + default: + switch payload.Type().String() { + case "io.ReadSeeker": + payload.Set(reflect.ValueOf(aws.ReadSeekCloser(r.HTTPResponse.Body))) + case "aws.ReadSeekCloser", "io.ReadCloser": + payload.Set(reflect.ValueOf(r.HTTPResponse.Body)) + default: + r.Error = awserr.New("SerializationError", + "failed to decode REST response", + fmt.Errorf("unknown payload type %s", payload.Type())) + } + } + } + } + } + } +} + +func unmarshalLocationElements(r *aws.Request, v reflect.Value) { + for i := 0; i < v.NumField(); i++ { + m, field := v.Field(i), v.Type().Field(i) + if n := field.Name; n[0:1] == strings.ToLower(n[0:1]) { + continue + } + + if m.IsValid() { + name := field.Tag.Get("locationName") + if name == "" { + name = field.Name + } + + switch field.Tag.Get("location") { + case "statusCode": + unmarshalStatusCode(m, r.HTTPResponse.StatusCode) + case "header": + err := unmarshalHeader(m, r.HTTPResponse.Header.Get(name)) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to decode REST response", err) + break + } + case "headers": + prefix := field.Tag.Get("locationName") + err := unmarshalHeaderMap(m, r.HTTPResponse.Header, prefix) + if err != nil { + r.Error = awserr.New("SerializationError", "failed to decode REST response", err) + break + } + } + } + if r.Error != nil { + return + } + } +} + +func unmarshalStatusCode(v reflect.Value, statusCode int) { + if !v.IsValid() { + return + } + + switch v.Interface().(type) { + case *int64: + s := int64(statusCode) + v.Set(reflect.ValueOf(&s)) + } +} + +func unmarshalHeaderMap(r reflect.Value, headers http.Header, prefix string) error { + switch r.Interface().(type) { + case map[string]*string: // we only support string map value types + out := map[string]*string{} + for k, v := range headers { + k = http.CanonicalHeaderKey(k) + if strings.HasPrefix(strings.ToLower(k), strings.ToLower(prefix)) { + out[k[len(prefix):]] = &v[0] + } + } + r.Set(reflect.ValueOf(out)) + } + return nil +} + +func unmarshalHeader(v reflect.Value, header string) error { + if !v.IsValid() || (header == "" && v.Elem().Kind() != reflect.String) { + return nil + } + + switch v.Interface().(type) { + case *string: + v.Set(reflect.ValueOf(&header)) + case []byte: + b, err := base64.StdEncoding.DecodeString(header) + if err != nil { + return err + } + v.Set(reflect.ValueOf(&b)) + case *bool: + b, err := strconv.ParseBool(header) + if err != nil { + return err + } + v.Set(reflect.ValueOf(&b)) + case *int64: + i, err := strconv.ParseInt(header, 10, 64) + if err != nil { + return err + } + v.Set(reflect.ValueOf(&i)) + case *float64: + f, err := strconv.ParseFloat(header, 64) + if err != nil { + return err + } + v.Set(reflect.ValueOf(&f)) + case *time.Time: + t, err := time.Parse(RFC822, header) + if err != nil { + return err + } + v.Set(reflect.ValueOf(&t)) + default: + err := fmt.Errorf("Unsupported value for param %v (%s)", v.Interface(), v.Type()) + return err + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/build.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/build.go new file mode 100644 index 00000000000..d3db250231b --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/build.go @@ -0,0 +1,287 @@ +// Package xmlutil provides XML serialisation of AWS requests and responses. +package xmlutil + +import ( + "encoding/base64" + "encoding/xml" + "fmt" + "reflect" + "sort" + "strconv" + "strings" + "time" +) + +// BuildXML will serialize params into an xml.Encoder. +// Error will be returned if the serialization of any of the params or nested values fails. +func BuildXML(params interface{}, e *xml.Encoder) error { + b := xmlBuilder{encoder: e, namespaces: map[string]string{}} + root := NewXMLElement(xml.Name{}) + if err := b.buildValue(reflect.ValueOf(params), root, ""); err != nil { + return err + } + for _, c := range root.Children { + for _, v := range c { + return StructToXML(e, v, false) + } + } + return nil +} + +// Returns the reflection element of a value, if it is a pointer. +func elemOf(value reflect.Value) reflect.Value { + for value.Kind() == reflect.Ptr { + value = value.Elem() + } + return value +} + +// A xmlBuilder serializes values from Go code to XML +type xmlBuilder struct { + encoder *xml.Encoder + namespaces map[string]string +} + +// buildValue generic XMLNode builder for any type. Will build value for their specific type +// struct, list, map, scalar. +// +// Also takes a "type" tag value to set what type a value should be converted to XMLNode as. If +// type is not provided reflect will be used to determine the value's type. +func (b *xmlBuilder) buildValue(value reflect.Value, current *XMLNode, tag reflect.StructTag) error { + value = elemOf(value) + if !value.IsValid() { // no need to handle zero values + return nil + } else if tag.Get("location") != "" { // don't handle non-body location values + return nil + } + + t := tag.Get("type") + if t == "" { + switch value.Kind() { + case reflect.Struct: + t = "structure" + case reflect.Slice: + t = "list" + case reflect.Map: + t = "map" + } + } + + switch t { + case "structure": + if field, ok := value.Type().FieldByName("SDKShapeTraits"); ok { + tag = tag + reflect.StructTag(" ") + field.Tag + } + return b.buildStruct(value, current, tag) + case "list": + return b.buildList(value, current, tag) + case "map": + return b.buildMap(value, current, tag) + default: + return b.buildScalar(value, current, tag) + } +} + +// buildStruct adds a struct and its fields to the current XMLNode. All fields any any nested +// types are converted to XMLNodes also. +func (b *xmlBuilder) buildStruct(value reflect.Value, current *XMLNode, tag reflect.StructTag) error { + if !value.IsValid() { + return nil + } + + fieldAdded := false + + // unwrap payloads + if payload := tag.Get("payload"); payload != "" { + field, _ := value.Type().FieldByName(payload) + tag = field.Tag + value = elemOf(value.FieldByName(payload)) + + if !value.IsValid() { + return nil + } + } + + child := NewXMLElement(xml.Name{Local: tag.Get("locationName")}) + + // there is an xmlNamespace associated with this struct + if prefix, uri := tag.Get("xmlPrefix"), tag.Get("xmlURI"); uri != "" { + ns := xml.Attr{ + Name: xml.Name{Local: "xmlns"}, + Value: uri, + } + if prefix != "" { + b.namespaces[prefix] = uri // register the namespace + ns.Name.Local = "xmlns:" + prefix + } + + child.Attr = append(child.Attr, ns) + } + + t := value.Type() + for i := 0; i < value.NumField(); i++ { + if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c { + continue // ignore unexported fields + } + + member := elemOf(value.Field(i)) + field := t.Field(i) + mTag := field.Tag + + if mTag.Get("location") != "" { // skip non-body members + continue + } + + memberName := mTag.Get("locationName") + if memberName == "" { + memberName = field.Name + mTag = reflect.StructTag(string(mTag) + ` locationName:"` + memberName + `"`) + } + if err := b.buildValue(member, child, mTag); err != nil { + return err + } + + fieldAdded = true + } + + if fieldAdded { // only append this child if we have one ore more valid members + current.AddChild(child) + } + + return nil +} + +// buildList adds the value's list items to the current XMLNode as children nodes. All +// nested values in the list are converted to XMLNodes also. +func (b *xmlBuilder) buildList(value reflect.Value, current *XMLNode, tag reflect.StructTag) error { + if value.IsNil() { // don't build omitted lists + return nil + } + + // check for unflattened list member + flattened := tag.Get("flattened") != "" + + xname := xml.Name{Local: tag.Get("locationName")} + if flattened { + for i := 0; i < value.Len(); i++ { + child := NewXMLElement(xname) + current.AddChild(child) + if err := b.buildValue(value.Index(i), child, ""); err != nil { + return err + } + } + } else { + list := NewXMLElement(xname) + current.AddChild(list) + + for i := 0; i < value.Len(); i++ { + iname := tag.Get("locationNameList") + if iname == "" { + iname = "member" + } + + child := NewXMLElement(xml.Name{Local: iname}) + list.AddChild(child) + if err := b.buildValue(value.Index(i), child, ""); err != nil { + return err + } + } + } + + return nil +} + +// buildMap adds the value's key/value pairs to the current XMLNode as children nodes. All +// nested values in the map are converted to XMLNodes also. +// +// Error will be returned if it is unable to build the map's values into XMLNodes +func (b *xmlBuilder) buildMap(value reflect.Value, current *XMLNode, tag reflect.StructTag) error { + if value.IsNil() { // don't build omitted maps + return nil + } + + maproot := NewXMLElement(xml.Name{Local: tag.Get("locationName")}) + current.AddChild(maproot) + current = maproot + + kname, vname := "key", "value" + if n := tag.Get("locationNameKey"); n != "" { + kname = n + } + if n := tag.Get("locationNameValue"); n != "" { + vname = n + } + + // sorting is not required for compliance, but it makes testing easier + keys := make([]string, value.Len()) + for i, k := range value.MapKeys() { + keys[i] = k.String() + } + sort.Strings(keys) + + for _, k := range keys { + v := value.MapIndex(reflect.ValueOf(k)) + + mapcur := current + if tag.Get("flattened") == "" { // add "entry" tag to non-flat maps + child := NewXMLElement(xml.Name{Local: "entry"}) + mapcur.AddChild(child) + mapcur = child + } + + kchild := NewXMLElement(xml.Name{Local: kname}) + kchild.Text = k + vchild := NewXMLElement(xml.Name{Local: vname}) + mapcur.AddChild(kchild) + mapcur.AddChild(vchild) + + if err := b.buildValue(v, vchild, ""); err != nil { + return err + } + } + + return nil +} + +// buildScalar will convert the value into a string and append it as a attribute or child +// of the current XMLNode. +// +// The value will be added as an attribute if tag contains a "xmlAttribute" attribute value. +// +// Error will be returned if the value type is unsupported. +func (b *xmlBuilder) buildScalar(value reflect.Value, current *XMLNode, tag reflect.StructTag) error { + var str string + switch converted := value.Interface().(type) { + case string: + str = converted + case []byte: + if !value.IsNil() { + str = base64.StdEncoding.EncodeToString(converted) + } + case bool: + str = strconv.FormatBool(converted) + case int64: + str = strconv.FormatInt(converted, 10) + case int: + str = strconv.Itoa(converted) + case float64: + str = strconv.FormatFloat(converted, 'f', -1, 64) + case float32: + str = strconv.FormatFloat(float64(converted), 'f', -1, 32) + case time.Time: + const ISO8601UTC = "2006-01-02T15:04:05Z" + str = converted.UTC().Format(ISO8601UTC) + default: + return fmt.Errorf("unsupported value for param %s: %v (%s)", + tag.Get("locationName"), value.Interface(), value.Type().Name()) + } + + xname := xml.Name{Local: tag.Get("locationName")} + if tag.Get("xmlAttribute") != "" { // put into current node's attribute list + attr := xml.Attr{Name: xname, Value: str} + current.Attr = append(current.Attr, attr) + } else { // regular text node + current.AddChild(&XMLNode{Name: xname, Text: str}) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go new file mode 100644 index 00000000000..5e4fe210b36 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/unmarshal.go @@ -0,0 +1,260 @@ +package xmlutil + +import ( + "encoding/base64" + "encoding/xml" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" +) + +// UnmarshalXML deserializes an xml.Decoder into the container v. V +// needs to match the shape of the XML expected to be decoded. +// If the shape doesn't match unmarshaling will fail. +func UnmarshalXML(v interface{}, d *xml.Decoder, wrapper string) error { + n, _ := XMLToStruct(d, nil) + if n.Children != nil { + for _, root := range n.Children { + for _, c := range root { + if wrappedChild, ok := c.Children[wrapper]; ok { + c = wrappedChild[0] // pull out wrapped element + } + + err := parse(reflect.ValueOf(v), c, "") + if err != nil { + if err == io.EOF { + return nil + } + return err + } + } + } + return nil + } + return nil +} + +// parse deserializes any value from the XMLNode. The type tag is used to infer the type, or reflect +// will be used to determine the type from r. +func parse(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { + rtype := r.Type() + if rtype.Kind() == reflect.Ptr { + rtype = rtype.Elem() // check kind of actual element type + } + + t := tag.Get("type") + if t == "" { + switch rtype.Kind() { + case reflect.Struct: + t = "structure" + case reflect.Slice: + t = "list" + case reflect.Map: + t = "map" + } + } + + switch t { + case "structure": + if field, ok := rtype.FieldByName("SDKShapeTraits"); ok { + tag = field.Tag + } + return parseStruct(r, node, tag) + case "list": + return parseList(r, node, tag) + case "map": + return parseMap(r, node, tag) + default: + return parseScalar(r, node, tag) + } +} + +// parseStruct deserializes a structure and its fields from an XMLNode. Any nested +// types in the structure will also be deserialized. +func parseStruct(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { + t := r.Type() + if r.Kind() == reflect.Ptr { + if r.IsNil() { // create the structure if it's nil + s := reflect.New(r.Type().Elem()) + r.Set(s) + r = s + } + + r = r.Elem() + t = t.Elem() + } + + // unwrap any payloads + if payload := tag.Get("payload"); payload != "" { + field, _ := t.FieldByName(payload) + return parseStruct(r.FieldByName(payload), node, field.Tag) + } + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if c := field.Name[0:1]; strings.ToLower(c) == c { + continue // ignore unexported fields + } + + // figure out what this field is called + name := field.Name + if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" { + name = field.Tag.Get("locationNameList") + } else if locName := field.Tag.Get("locationName"); locName != "" { + name = locName + } + + // try to find the field by name in elements + elems := node.Children[name] + + if elems == nil { // try to find the field in attributes + for _, a := range node.Attr { + if name == a.Name.Local { + // turn this into a text node for de-serializing + elems = []*XMLNode{{Text: a.Value}} + } + } + } + + member := r.FieldByName(field.Name) + for _, elem := range elems { + err := parse(member, elem, field.Tag) + if err != nil { + return err + } + } + } + return nil +} + +// parseList deserializes a list of values from an XML node. Each list entry +// will also be deserialized. +func parseList(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { + t := r.Type() + + if tag.Get("flattened") == "" { // look at all item entries + mname := "member" + if name := tag.Get("locationNameList"); name != "" { + mname = name + } + + if Children, ok := node.Children[mname]; ok { + if r.IsNil() { + r.Set(reflect.MakeSlice(t, len(Children), len(Children))) + } + + for i, c := range Children { + err := parse(r.Index(i), c, "") + if err != nil { + return err + } + } + } + } else { // flattened list means this is a single element + if r.IsNil() { + r.Set(reflect.MakeSlice(t, 0, 0)) + } + + childR := reflect.Zero(t.Elem()) + r.Set(reflect.Append(r, childR)) + err := parse(r.Index(r.Len()-1), node, "") + if err != nil { + return err + } + } + + return nil +} + +// parseMap deserializes a map from an XMLNode. The direct children of the XMLNode +// will also be deserialized as map entries. +func parseMap(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { + if r.IsNil() { + r.Set(reflect.MakeMap(r.Type())) + } + + if tag.Get("flattened") == "" { // look at all child entries + for _, entry := range node.Children["entry"] { + parseMapEntry(r, entry, tag) + } + } else { // this element is itself an entry + parseMapEntry(r, node, tag) + } + + return nil +} + +// parseMapEntry deserializes a map entry from a XML node. +func parseMapEntry(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { + kname, vname := "key", "value" + if n := tag.Get("locationNameKey"); n != "" { + kname = n + } + if n := tag.Get("locationNameValue"); n != "" { + vname = n + } + + keys, ok := node.Children[kname] + values := node.Children[vname] + if ok { + for i, key := range keys { + keyR := reflect.ValueOf(key.Text) + value := values[i] + valueR := reflect.New(r.Type().Elem()).Elem() + + parse(valueR, value, "") + r.SetMapIndex(keyR, valueR) + } + } + return nil +} + +// parseScaller deserializes an XMLNode value into a concrete type based on the +// interface type of r. +// +// Error is returned if the deserialization fails due to invalid type conversion, +// or unsupported interface type. +func parseScalar(r reflect.Value, node *XMLNode, tag reflect.StructTag) error { + switch r.Interface().(type) { + case *string: + r.Set(reflect.ValueOf(&node.Text)) + return nil + case []byte: + b, err := base64.StdEncoding.DecodeString(node.Text) + if err != nil { + return err + } + r.Set(reflect.ValueOf(b)) + case *bool: + v, err := strconv.ParseBool(node.Text) + if err != nil { + return err + } + r.Set(reflect.ValueOf(&v)) + case *int64: + v, err := strconv.ParseInt(node.Text, 10, 64) + if err != nil { + return err + } + r.Set(reflect.ValueOf(&v)) + case *float64: + v, err := strconv.ParseFloat(node.Text, 64) + if err != nil { + return err + } + r.Set(reflect.ValueOf(&v)) + case *time.Time: + const ISO8601UTC = "2006-01-02T15:04:05Z" + t, err := time.Parse(ISO8601UTC, node.Text) + if err != nil { + return err + } + r.Set(reflect.ValueOf(&t)) + default: + return fmt.Errorf("unsupported value: %v (%s)", r.Interface(), r.Type()) + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go new file mode 100644 index 00000000000..72c198a9d8d --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil/xml_to_struct.go @@ -0,0 +1,105 @@ +package xmlutil + +import ( + "encoding/xml" + "io" + "sort" +) + +// A XMLNode contains the values to be encoded or decoded. +type XMLNode struct { + Name xml.Name `json:",omitempty"` + Children map[string][]*XMLNode `json:",omitempty"` + Text string `json:",omitempty"` + Attr []xml.Attr `json:",omitempty"` +} + +// NewXMLElement returns a pointer to a new XMLNode initialized to default values. +func NewXMLElement(name xml.Name) *XMLNode { + return &XMLNode{ + Name: name, + Children: map[string][]*XMLNode{}, + Attr: []xml.Attr{}, + } +} + +// AddChild adds child to the XMLNode. +func (n *XMLNode) AddChild(child *XMLNode) { + if _, ok := n.Children[child.Name.Local]; !ok { + n.Children[child.Name.Local] = []*XMLNode{} + } + n.Children[child.Name.Local] = append(n.Children[child.Name.Local], child) +} + +// XMLToStruct converts a xml.Decoder stream to XMLNode with nested values. +func XMLToStruct(d *xml.Decoder, s *xml.StartElement) (*XMLNode, error) { + out := &XMLNode{} + for { + tok, err := d.Token() + if tok == nil || err == io.EOF { + break + } + if err != nil { + return out, err + } + + switch typed := tok.(type) { + case xml.CharData: + out.Text = string(typed.Copy()) + case xml.StartElement: + el := typed.Copy() + out.Attr = el.Attr + if out.Children == nil { + out.Children = map[string][]*XMLNode{} + } + + name := typed.Name.Local + slice := out.Children[name] + if slice == nil { + slice = []*XMLNode{} + } + node, e := XMLToStruct(d, &el) + if e != nil { + return out, e + } + node.Name = typed.Name + slice = append(slice, node) + out.Children[name] = slice + case xml.EndElement: + if s != nil && s.Name.Local == typed.Name.Local { // matching end token + return out, nil + } + } + } + return out, nil +} + +// StructToXML writes an XMLNode to a xml.Encoder as tokens. +func StructToXML(e *xml.Encoder, node *XMLNode, sorted bool) error { + e.EncodeToken(xml.StartElement{Name: node.Name, Attr: node.Attr}) + + if node.Text != "" { + e.EncodeToken(xml.CharData([]byte(node.Text))) + } else if sorted { + sortedNames := []string{} + for k := range node.Children { + sortedNames = append(sortedNames, k) + } + sort.Strings(sortedNames) + + for _, k := range sortedNames { + for _, v := range node.Children[k] { + StructToXML(e, v, sorted) + } + } + } else { + for _, c := range node.Children { + for _, v := range c { + StructToXML(e, v, sorted) + } + } + } + + e.EncodeToken(xml.EndElement{Name: node.Name}) + return e.Flush() +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/functional_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/functional_test.go new file mode 100644 index 00000000000..fbb0e41cdc1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/functional_test.go @@ -0,0 +1,43 @@ +package v4_test + +import ( + "net/url" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/internal/test/unit" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/stretchr/testify/assert" +) + +var _ = unit.Imported + +func TestPresignHandler(t *testing.T) { + svc := s3.New(nil) + req, _ := svc.PutObjectRequest(&s3.PutObjectInput{ + Bucket: aws.String("bucket"), + Key: aws.String("key"), + ContentDisposition: aws.String("a+b c$d"), + ACL: aws.String("public-read"), + }) + req.Time = time.Unix(0, 0) + urlstr, err := req.Presign(5 * time.Minute) + + assert.NoError(t, err) + + expectedDate := "19700101T000000Z" + expectedHeaders := "host;x-amz-acl" + expectedSig := "7edcb4e3a1bf12f4989018d75acbe3a7f03df24bd6f3112602d59fc551f0e4e2" + expectedCred := "AKID/19700101/mock-region/s3/aws4_request" + + u, _ := url.Parse(urlstr) + urlQ := u.Query() + assert.Equal(t, expectedSig, urlQ.Get("X-Amz-Signature")) + assert.Equal(t, expectedCred, urlQ.Get("X-Amz-Credential")) + assert.Equal(t, expectedHeaders, urlQ.Get("X-Amz-SignedHeaders")) + assert.Equal(t, expectedDate, urlQ.Get("X-Amz-Date")) + assert.Equal(t, "300", urlQ.Get("X-Amz-Expires")) + + assert.NotContains(t, urlstr, "+") // + encoded as %20 +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4.go new file mode 100644 index 00000000000..748c37f2e65 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4.go @@ -0,0 +1,364 @@ +// Package v4 implements signing for AWS V4 signer +package v4 + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/internal/protocol/rest" +) + +const ( + authHeaderPrefix = "AWS4-HMAC-SHA256" + timeFormat = "20060102T150405Z" + shortTimeFormat = "20060102" +) + +var ignoredHeaders = map[string]bool{ + "Authorization": true, + "Content-Type": true, + "Content-Length": true, + "User-Agent": true, +} + +type signer struct { + Request *http.Request + Time time.Time + ExpireTime time.Duration + ServiceName string + Region string + CredValues credentials.Value + Credentials *credentials.Credentials + Query url.Values + Body io.ReadSeeker + Debug aws.LogLevelType + Logger aws.Logger + + isPresign bool + formattedTime string + formattedShortTime string + + signedHeaders string + canonicalHeaders string + canonicalString string + credentialString string + stringToSign string + signature string + authorization string +} + +// Sign requests with signature version 4. +// +// Will sign the requests with the service config's Credentials object +// Signing is skipped if the credentials is the credentials.AnonymousCredentials +// object. +func Sign(req *aws.Request) { + // If the request does not need to be signed ignore the signing of the + // request if the AnonymousCredentials object is used. + if req.Service.Config.Credentials == credentials.AnonymousCredentials { + return + } + + region := req.Service.SigningRegion + if region == "" { + region = aws.StringValue(req.Service.Config.Region) + } + + name := req.Service.SigningName + if name == "" { + name = req.Service.ServiceName + } + + s := signer{ + Request: req.HTTPRequest, + Time: req.Time, + ExpireTime: req.ExpireTime, + Query: req.HTTPRequest.URL.Query(), + Body: req.Body, + ServiceName: name, + Region: region, + Credentials: req.Service.Config.Credentials, + Debug: req.Service.Config.LogLevel.Value(), + Logger: req.Service.Config.Logger, + } + + req.Error = s.sign() +} + +func (v4 *signer) sign() error { + if v4.ExpireTime != 0 { + v4.isPresign = true + } + + if v4.isRequestSigned() { + if !v4.Credentials.IsExpired() { + // If the request is already signed, and the credentials have not + // expired yet ignore the signing request. + return nil + } + + // The credentials have expired for this request. The current signing + // is invalid, and needs to be request because the request will fail. + if v4.isPresign { + v4.removePresign() + // Update the request's query string to ensure the values stays in + // sync in the case retrieving the new credentials fails. + v4.Request.URL.RawQuery = v4.Query.Encode() + } + } + + var err error + v4.CredValues, err = v4.Credentials.Get() + if err != nil { + return err + } + + if v4.isPresign { + v4.Query.Set("X-Amz-Algorithm", authHeaderPrefix) + if v4.CredValues.SessionToken != "" { + v4.Query.Set("X-Amz-Security-Token", v4.CredValues.SessionToken) + } else { + v4.Query.Del("X-Amz-Security-Token") + } + } else if v4.CredValues.SessionToken != "" { + v4.Request.Header.Set("X-Amz-Security-Token", v4.CredValues.SessionToken) + } + + v4.build() + + if v4.Debug.Matches(aws.LogDebugWithSigning) { + v4.logSigningInfo() + } + + return nil +} + +const logSignInfoMsg = `DEBUG: Request Signiture: +---[ CANONICAL STRING ]----------------------------- +%s +---[ STRING TO SIGN ]-------------------------------- +%s%s +-----------------------------------------------------` +const logSignedURLMsg = ` +---[ SIGNED URL ]------------------------------------ +%s` + +func (v4 *signer) logSigningInfo() { + signedURLMsg := "" + if v4.isPresign { + signedURLMsg = fmt.Sprintf(logSignedURLMsg, v4.Request.URL.String()) + } + msg := fmt.Sprintf(logSignInfoMsg, v4.canonicalString, v4.stringToSign, signedURLMsg) + v4.Logger.Log(msg) +} + +func (v4 *signer) build() { + v4.buildTime() // no depends + v4.buildCredentialString() // no depends + if v4.isPresign { + v4.buildQuery() // no depends + } + v4.buildCanonicalHeaders() // depends on cred string + v4.buildCanonicalString() // depends on canon headers / signed headers + v4.buildStringToSign() // depends on canon string + v4.buildSignature() // depends on string to sign + + if v4.isPresign { + v4.Request.URL.RawQuery += "&X-Amz-Signature=" + v4.signature + } else { + parts := []string{ + authHeaderPrefix + " Credential=" + v4.CredValues.AccessKeyID + "/" + v4.credentialString, + "SignedHeaders=" + v4.signedHeaders, + "Signature=" + v4.signature, + } + v4.Request.Header.Set("Authorization", strings.Join(parts, ", ")) + } +} + +func (v4 *signer) buildTime() { + v4.formattedTime = v4.Time.UTC().Format(timeFormat) + v4.formattedShortTime = v4.Time.UTC().Format(shortTimeFormat) + + if v4.isPresign { + duration := int64(v4.ExpireTime / time.Second) + v4.Query.Set("X-Amz-Date", v4.formattedTime) + v4.Query.Set("X-Amz-Expires", strconv.FormatInt(duration, 10)) + } else { + v4.Request.Header.Set("X-Amz-Date", v4.formattedTime) + } +} + +func (v4 *signer) buildCredentialString() { + v4.credentialString = strings.Join([]string{ + v4.formattedShortTime, + v4.Region, + v4.ServiceName, + "aws4_request", + }, "/") + + if v4.isPresign { + v4.Query.Set("X-Amz-Credential", v4.CredValues.AccessKeyID+"/"+v4.credentialString) + } +} + +func (v4 *signer) buildQuery() { + for k, h := range v4.Request.Header { + if strings.HasPrefix(http.CanonicalHeaderKey(k), "X-Amz-") { + continue // never hoist x-amz-* headers, they must be signed + } + if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok { + continue // never hoist ignored headers + } + + v4.Request.Header.Del(k) + v4.Query.Del(k) + for _, v := range h { + v4.Query.Add(k, v) + } + } +} + +func (v4 *signer) buildCanonicalHeaders() { + var headers []string + headers = append(headers, "host") + for k := range v4.Request.Header { + if _, ok := ignoredHeaders[http.CanonicalHeaderKey(k)]; ok { + continue // ignored header + } + headers = append(headers, strings.ToLower(k)) + } + sort.Strings(headers) + + v4.signedHeaders = strings.Join(headers, ";") + + if v4.isPresign { + v4.Query.Set("X-Amz-SignedHeaders", v4.signedHeaders) + } + + headerValues := make([]string, len(headers)) + for i, k := range headers { + if k == "host" { + headerValues[i] = "host:" + v4.Request.URL.Host + } else { + headerValues[i] = k + ":" + + strings.Join(v4.Request.Header[http.CanonicalHeaderKey(k)], ",") + } + } + + v4.canonicalHeaders = strings.Join(headerValues, "\n") +} + +func (v4 *signer) buildCanonicalString() { + v4.Request.URL.RawQuery = strings.Replace(v4.Query.Encode(), "+", "%20", -1) + uri := v4.Request.URL.Opaque + if uri != "" { + uri = "/" + strings.Join(strings.Split(uri, "/")[3:], "/") + } else { + uri = v4.Request.URL.Path + } + if uri == "" { + uri = "/" + } + + if v4.ServiceName != "s3" { + uri = rest.EscapePath(uri, false) + } + + v4.canonicalString = strings.Join([]string{ + v4.Request.Method, + uri, + v4.Request.URL.RawQuery, + v4.canonicalHeaders + "\n", + v4.signedHeaders, + v4.bodyDigest(), + }, "\n") +} + +func (v4 *signer) buildStringToSign() { + v4.stringToSign = strings.Join([]string{ + authHeaderPrefix, + v4.formattedTime, + v4.credentialString, + hex.EncodeToString(makeSha256([]byte(v4.canonicalString))), + }, "\n") +} + +func (v4 *signer) buildSignature() { + secret := v4.CredValues.SecretAccessKey + date := makeHmac([]byte("AWS4"+secret), []byte(v4.formattedShortTime)) + region := makeHmac(date, []byte(v4.Region)) + service := makeHmac(region, []byte(v4.ServiceName)) + credentials := makeHmac(service, []byte("aws4_request")) + signature := makeHmac(credentials, []byte(v4.stringToSign)) + v4.signature = hex.EncodeToString(signature) +} + +func (v4 *signer) bodyDigest() string { + hash := v4.Request.Header.Get("X-Amz-Content-Sha256") + if hash == "" { + if v4.isPresign && v4.ServiceName == "s3" { + hash = "UNSIGNED-PAYLOAD" + } else if v4.Body == nil { + hash = hex.EncodeToString(makeSha256([]byte{})) + } else { + hash = hex.EncodeToString(makeSha256Reader(v4.Body)) + } + v4.Request.Header.Add("X-Amz-Content-Sha256", hash) + } + return hash +} + +// isRequestSigned returns if the request is currently signed or presigned +func (v4 *signer) isRequestSigned() bool { + if v4.isPresign && v4.Query.Get("X-Amz-Signature") != "" { + return true + } + if v4.Request.Header.Get("Authorization") != "" { + return true + } + + return false +} + +// unsign removes signing flags for both signed and presigned requests. +func (v4 *signer) removePresign() { + v4.Query.Del("X-Amz-Algorithm") + v4.Query.Del("X-Amz-Signature") + v4.Query.Del("X-Amz-Security-Token") + v4.Query.Del("X-Amz-Date") + v4.Query.Del("X-Amz-Expires") + v4.Query.Del("X-Amz-Credential") + v4.Query.Del("X-Amz-SignedHeaders") +} + +func makeHmac(key []byte, data []byte) []byte { + hash := hmac.New(sha256.New, key) + hash.Write(data) + return hash.Sum(nil) +} + +func makeSha256(data []byte) []byte { + hash := sha256.New() + hash.Write(data) + return hash.Sum(nil) +} + +func makeSha256Reader(reader io.ReadSeeker) []byte { + hash := sha256.New() + start, _ := reader.Seek(0, 1) + defer reader.Seek(start, 0) + + io.Copy(hash, reader) + return hash.Sum(nil) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4_test.go new file mode 100644 index 00000000000..adf8e7bd89e --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/internal/signer/v4/v4_test.go @@ -0,0 +1,245 @@ +package v4 + +import ( + "net/http" + "strings" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/stretchr/testify/assert" +) + +func buildSigner(serviceName string, region string, signTime time.Time, expireTime time.Duration, body string) signer { + endpoint := "https://" + serviceName + "." + region + ".amazonaws.com" + reader := strings.NewReader(body) + req, _ := http.NewRequest("POST", endpoint, reader) + req.URL.Opaque = "//example.org/bucket/key-._~,!@#$%^&*()" + req.Header.Add("X-Amz-Target", "prefix.Operation") + req.Header.Add("Content-Type", "application/x-amz-json-1.0") + req.Header.Add("Content-Length", string(len(body))) + req.Header.Add("X-Amz-Meta-Other-Header", "some-value=!@#$%^&* (+)") + + return signer{ + Request: req, + Time: signTime, + ExpireTime: expireTime, + Query: req.URL.Query(), + Body: reader, + ServiceName: serviceName, + Region: region, + Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"), + } +} + +func removeWS(text string) string { + text = strings.Replace(text, " ", "", -1) + text = strings.Replace(text, "\n", "", -1) + text = strings.Replace(text, "\t", "", -1) + return text +} + +func assertEqual(t *testing.T, expected, given string) { + if removeWS(expected) != removeWS(given) { + t.Errorf("\nExpected: %s\nGiven: %s", expected, given) + } +} + +func TestPresignRequest(t *testing.T) { + signer := buildSigner("dynamodb", "us-east-1", time.Unix(0, 0), 300*time.Second, "{}") + signer.sign() + + expectedDate := "19700101T000000Z" + expectedHeaders := "host;x-amz-meta-other-header;x-amz-target" + expectedSig := "5eeedebf6f995145ce56daa02902d10485246d3defb34f97b973c1f40ab82d36" + expectedCred := "AKID/19700101/us-east-1/dynamodb/aws4_request" + + q := signer.Request.URL.Query() + assert.Equal(t, expectedSig, q.Get("X-Amz-Signature")) + assert.Equal(t, expectedCred, q.Get("X-Amz-Credential")) + assert.Equal(t, expectedHeaders, q.Get("X-Amz-SignedHeaders")) + assert.Equal(t, expectedDate, q.Get("X-Amz-Date")) +} + +func TestSignRequest(t *testing.T) { + signer := buildSigner("dynamodb", "us-east-1", time.Unix(0, 0), 0, "{}") + signer.sign() + + expectedDate := "19700101T000000Z" + expectedSig := "AWS4-HMAC-SHA256 Credential=AKID/19700101/us-east-1/dynamodb/aws4_request, SignedHeaders=host;x-amz-date;x-amz-meta-other-header;x-amz-security-token;x-amz-target, Signature=69ada33fec48180dab153576e4dd80c4e04124f80dda3eccfed8a67c2b91ed5e" + + q := signer.Request.Header + assert.Equal(t, expectedSig, q.Get("Authorization")) + assert.Equal(t, expectedDate, q.Get("X-Amz-Date")) +} + +func TestSignEmptyBody(t *testing.T) { + signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "") + signer.Body = nil + signer.sign() + hash := signer.Request.Header.Get("X-Amz-Content-Sha256") + assert.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", hash) +} + +func TestSignBody(t *testing.T) { + signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "hello") + signer.sign() + hash := signer.Request.Header.Get("X-Amz-Content-Sha256") + assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash) +} + +func TestSignSeekedBody(t *testing.T) { + signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, " hello") + signer.Body.Read(make([]byte, 3)) // consume first 3 bytes so body is now "hello" + signer.sign() + hash := signer.Request.Header.Get("X-Amz-Content-Sha256") + assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", hash) + + start, _ := signer.Body.Seek(0, 1) + assert.Equal(t, int64(3), start) +} + +func TestPresignEmptyBodyS3(t *testing.T) { + signer := buildSigner("s3", "us-east-1", time.Now(), 5*time.Minute, "hello") + signer.sign() + hash := signer.Request.Header.Get("X-Amz-Content-Sha256") + assert.Equal(t, "UNSIGNED-PAYLOAD", hash) +} + +func TestSignPrecomputedBodyChecksum(t *testing.T) { + signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "hello") + signer.Request.Header.Set("X-Amz-Content-Sha256", "PRECOMPUTED") + signer.sign() + hash := signer.Request.Header.Get("X-Amz-Content-Sha256") + assert.Equal(t, "PRECOMPUTED", hash) +} + +func TestAnonymousCredentials(t *testing.T) { + r := aws.NewRequest( + aws.NewService(&aws.Config{Credentials: credentials.AnonymousCredentials}), + &aws.Operation{ + Name: "BatchGetItem", + HTTPMethod: "POST", + HTTPPath: "/", + }, + nil, + nil, + ) + Sign(r) + + urlQ := r.HTTPRequest.URL.Query() + assert.Empty(t, urlQ.Get("X-Amz-Signature")) + assert.Empty(t, urlQ.Get("X-Amz-Credential")) + assert.Empty(t, urlQ.Get("X-Amz-SignedHeaders")) + assert.Empty(t, urlQ.Get("X-Amz-Date")) + + hQ := r.HTTPRequest.Header + assert.Empty(t, hQ.Get("Authorization")) + assert.Empty(t, hQ.Get("X-Amz-Date")) +} + +func TestIgnoreResignRequestWithValidCreds(t *testing.T) { + r := aws.NewRequest( + aws.NewService(&aws.Config{ + Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"), + Region: aws.String("us-west-2"), + }), + &aws.Operation{ + Name: "BatchGetItem", + HTTPMethod: "POST", + HTTPPath: "/", + }, + nil, + nil, + ) + + Sign(r) + sig := r.HTTPRequest.Header.Get("Authorization") + + Sign(r) + assert.Equal(t, sig, r.HTTPRequest.Header.Get("Authorization")) +} + +func TestIgnorePreResignRequestWithValidCreds(t *testing.T) { + r := aws.NewRequest( + aws.NewService(&aws.Config{ + Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "SESSION"), + Region: aws.String("us-west-2"), + }), + &aws.Operation{ + Name: "BatchGetItem", + HTTPMethod: "POST", + HTTPPath: "/", + }, + nil, + nil, + ) + r.ExpireTime = time.Minute * 10 + + Sign(r) + sig := r.HTTPRequest.Header.Get("X-Amz-Signature") + + Sign(r) + assert.Equal(t, sig, r.HTTPRequest.Header.Get("X-Amz-Signature")) +} + +func TestResignRequestExpiredCreds(t *testing.T) { + creds := credentials.NewStaticCredentials("AKID", "SECRET", "SESSION") + r := aws.NewRequest( + aws.NewService(&aws.Config{Credentials: creds}), + &aws.Operation{ + Name: "BatchGetItem", + HTTPMethod: "POST", + HTTPPath: "/", + }, + nil, + nil, + ) + Sign(r) + querySig := r.HTTPRequest.Header.Get("Authorization") + + creds.Expire() + + Sign(r) + assert.NotEqual(t, querySig, r.HTTPRequest.Header.Get("Authorization")) +} + +func TestPreResignRequestExpiredCreds(t *testing.T) { + provider := &credentials.StaticProvider{credentials.Value{"AKID", "SECRET", "SESSION"}} + creds := credentials.NewCredentials(provider) + r := aws.NewRequest( + aws.NewService(&aws.Config{Credentials: creds}), + &aws.Operation{ + Name: "BatchGetItem", + HTTPMethod: "POST", + HTTPPath: "/", + }, + nil, + nil, + ) + r.ExpireTime = time.Minute * 10 + + Sign(r) + querySig := r.HTTPRequest.URL.Query().Get("X-Amz-Signature") + + creds.Expire() + r.Time = time.Now().Add(time.Hour * 48) + + Sign(r) + assert.NotEqual(t, querySig, r.HTTPRequest.URL.Query().Get("X-Amz-Signature")) +} + +func BenchmarkPresignRequest(b *testing.B) { + signer := buildSigner("dynamodb", "us-east-1", time.Now(), 300*time.Second, "{}") + for i := 0; i < b.N; i++ { + signer.sign() + } +} + +func BenchmarkSignRequest(b *testing.B) { + signer := buildSigner("dynamodb", "us-east-1", time.Now(), 0, "{}") + for i := 0; i < b.N; i++ { + signer.sign() + } +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/api.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/api.go new file mode 100644 index 00000000000..d5a92f6d62b --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/api.go @@ -0,0 +1,1478 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +// Package cloudwatch provides a client for Amazon CloudWatch. +package cloudwatch + +import ( + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awsutil" +) + +const opDeleteAlarms = "DeleteAlarms" + +// DeleteAlarmsRequest generates a request for the DeleteAlarms operation. +func (c *CloudWatch) DeleteAlarmsRequest(input *DeleteAlarmsInput) (req *aws.Request, output *DeleteAlarmsOutput) { + op := &aws.Operation{ + Name: opDeleteAlarms, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &DeleteAlarmsInput{} + } + + req = c.newRequest(op, input, output) + output = &DeleteAlarmsOutput{} + req.Data = output + return +} + +// Deletes all specified alarms. In the event of an error, no alarms are deleted. +func (c *CloudWatch) DeleteAlarms(input *DeleteAlarmsInput) (*DeleteAlarmsOutput, error) { + req, out := c.DeleteAlarmsRequest(input) + err := req.Send() + return out, err +} + +const opDescribeAlarmHistory = "DescribeAlarmHistory" + +// DescribeAlarmHistoryRequest generates a request for the DescribeAlarmHistory operation. +func (c *CloudWatch) DescribeAlarmHistoryRequest(input *DescribeAlarmHistoryInput) (req *aws.Request, output *DescribeAlarmHistoryOutput) { + op := &aws.Operation{ + Name: opDescribeAlarmHistory, + HTTPMethod: "POST", + HTTPPath: "/", + Paginator: &aws.Paginator{ + InputTokens: []string{"NextToken"}, + OutputTokens: []string{"NextToken"}, + LimitToken: "MaxRecords", + TruncationToken: "", + }, + } + + if input == nil { + input = &DescribeAlarmHistoryInput{} + } + + req = c.newRequest(op, input, output) + output = &DescribeAlarmHistoryOutput{} + req.Data = output + return +} + +// Retrieves history for the specified alarm. Filter alarms by date range or +// item type. If an alarm name is not specified, Amazon CloudWatch returns histories +// for all of the owner's alarms. +func (c *CloudWatch) DescribeAlarmHistory(input *DescribeAlarmHistoryInput) (*DescribeAlarmHistoryOutput, error) { + req, out := c.DescribeAlarmHistoryRequest(input) + err := req.Send() + return out, err +} + +func (c *CloudWatch) DescribeAlarmHistoryPages(input *DescribeAlarmHistoryInput, fn func(p *DescribeAlarmHistoryOutput, lastPage bool) (shouldContinue bool)) error { + page, _ := c.DescribeAlarmHistoryRequest(input) + return page.EachPage(func(p interface{}, lastPage bool) bool { + return fn(p.(*DescribeAlarmHistoryOutput), lastPage) + }) +} + +const opDescribeAlarms = "DescribeAlarms" + +// DescribeAlarmsRequest generates a request for the DescribeAlarms operation. +func (c *CloudWatch) DescribeAlarmsRequest(input *DescribeAlarmsInput) (req *aws.Request, output *DescribeAlarmsOutput) { + op := &aws.Operation{ + Name: opDescribeAlarms, + HTTPMethod: "POST", + HTTPPath: "/", + Paginator: &aws.Paginator{ + InputTokens: []string{"NextToken"}, + OutputTokens: []string{"NextToken"}, + LimitToken: "MaxRecords", + TruncationToken: "", + }, + } + + if input == nil { + input = &DescribeAlarmsInput{} + } + + req = c.newRequest(op, input, output) + output = &DescribeAlarmsOutput{} + req.Data = output + return +} + +// Retrieves alarms with the specified names. If no name is specified, all alarms +// for the user are returned. Alarms can be retrieved by using only a prefix +// for the alarm name, the alarm state, or a prefix for any action. +func (c *CloudWatch) DescribeAlarms(input *DescribeAlarmsInput) (*DescribeAlarmsOutput, error) { + req, out := c.DescribeAlarmsRequest(input) + err := req.Send() + return out, err +} + +func (c *CloudWatch) DescribeAlarmsPages(input *DescribeAlarmsInput, fn func(p *DescribeAlarmsOutput, lastPage bool) (shouldContinue bool)) error { + page, _ := c.DescribeAlarmsRequest(input) + return page.EachPage(func(p interface{}, lastPage bool) bool { + return fn(p.(*DescribeAlarmsOutput), lastPage) + }) +} + +const opDescribeAlarmsForMetric = "DescribeAlarmsForMetric" + +// DescribeAlarmsForMetricRequest generates a request for the DescribeAlarmsForMetric operation. +func (c *CloudWatch) DescribeAlarmsForMetricRequest(input *DescribeAlarmsForMetricInput) (req *aws.Request, output *DescribeAlarmsForMetricOutput) { + op := &aws.Operation{ + Name: opDescribeAlarmsForMetric, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &DescribeAlarmsForMetricInput{} + } + + req = c.newRequest(op, input, output) + output = &DescribeAlarmsForMetricOutput{} + req.Data = output + return +} + +// Retrieves all alarms for a single metric. Specify a statistic, period, or +// unit to filter the set of alarms further. +func (c *CloudWatch) DescribeAlarmsForMetric(input *DescribeAlarmsForMetricInput) (*DescribeAlarmsForMetricOutput, error) { + req, out := c.DescribeAlarmsForMetricRequest(input) + err := req.Send() + return out, err +} + +const opDisableAlarmActions = "DisableAlarmActions" + +// DisableAlarmActionsRequest generates a request for the DisableAlarmActions operation. +func (c *CloudWatch) DisableAlarmActionsRequest(input *DisableAlarmActionsInput) (req *aws.Request, output *DisableAlarmActionsOutput) { + op := &aws.Operation{ + Name: opDisableAlarmActions, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &DisableAlarmActionsInput{} + } + + req = c.newRequest(op, input, output) + output = &DisableAlarmActionsOutput{} + req.Data = output + return +} + +// Disables actions for the specified alarms. When an alarm's actions are disabled +// the alarm's state may change, but none of the alarm's actions will execute. +func (c *CloudWatch) DisableAlarmActions(input *DisableAlarmActionsInput) (*DisableAlarmActionsOutput, error) { + req, out := c.DisableAlarmActionsRequest(input) + err := req.Send() + return out, err +} + +const opEnableAlarmActions = "EnableAlarmActions" + +// EnableAlarmActionsRequest generates a request for the EnableAlarmActions operation. +func (c *CloudWatch) EnableAlarmActionsRequest(input *EnableAlarmActionsInput) (req *aws.Request, output *EnableAlarmActionsOutput) { + op := &aws.Operation{ + Name: opEnableAlarmActions, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &EnableAlarmActionsInput{} + } + + req = c.newRequest(op, input, output) + output = &EnableAlarmActionsOutput{} + req.Data = output + return +} + +// Enables actions for the specified alarms. +func (c *CloudWatch) EnableAlarmActions(input *EnableAlarmActionsInput) (*EnableAlarmActionsOutput, error) { + req, out := c.EnableAlarmActionsRequest(input) + err := req.Send() + return out, err +} + +const opGetMetricStatistics = "GetMetricStatistics" + +// GetMetricStatisticsRequest generates a request for the GetMetricStatistics operation. +func (c *CloudWatch) GetMetricStatisticsRequest(input *GetMetricStatisticsInput) (req *aws.Request, output *GetMetricStatisticsOutput) { + op := &aws.Operation{ + Name: opGetMetricStatistics, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &GetMetricStatisticsInput{} + } + + req = c.newRequest(op, input, output) + output = &GetMetricStatisticsOutput{} + req.Data = output + return +} + +// Gets statistics for the specified metric. +// +// The maximum number of data points returned from a single GetMetricStatistics +// request is 1,440, wereas the maximum number of data points that can be queried +// is 50,850. If you make a request that generates more than 1,440 data points, +// Amazon CloudWatch returns an error. In such a case, you can alter the request +// by narrowing the specified time range or increasing the specified period. +// Alternatively, you can make multiple requests across adjacent time ranges. +// +// Amazon CloudWatch aggregates data points based on the length of the period +// that you specify. For example, if you request statistics with a one-minute +// granularity, Amazon CloudWatch aggregates data points with time stamps that +// fall within the same one-minute period. In such a case, the data points queried +// can greatly outnumber the data points returned. +// +// The following examples show various statistics allowed by the data point +// query maximum of 50,850 when you call GetMetricStatistics on Amazon EC2 instances +// with detailed (one-minute) monitoring enabled: +// +// Statistics for up to 400 instances for a span of one hour Statistics for +// up to 35 instances over a span of 24 hours Statistics for up to 2 instances +// over a span of 2 weeks For information about the namespace, metric names, +// and dimensions that other Amazon Web Services products use to send metrics +// to Cloudwatch, go to Amazon CloudWatch Metrics, Namespaces, and Dimensions +// Reference (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html) +// in the Amazon CloudWatch Developer Guide. +func (c *CloudWatch) GetMetricStatistics(input *GetMetricStatisticsInput) (*GetMetricStatisticsOutput, error) { + req, out := c.GetMetricStatisticsRequest(input) + err := req.Send() + return out, err +} + +const opListMetrics = "ListMetrics" + +// ListMetricsRequest generates a request for the ListMetrics operation. +func (c *CloudWatch) ListMetricsRequest(input *ListMetricsInput) (req *aws.Request, output *ListMetricsOutput) { + op := &aws.Operation{ + Name: opListMetrics, + HTTPMethod: "POST", + HTTPPath: "/", + Paginator: &aws.Paginator{ + InputTokens: []string{"NextToken"}, + OutputTokens: []string{"NextToken"}, + LimitToken: "", + TruncationToken: "", + }, + } + + if input == nil { + input = &ListMetricsInput{} + } + + req = c.newRequest(op, input, output) + output = &ListMetricsOutput{} + req.Data = output + return +} + +// Returns a list of valid metrics stored for the AWS account owner. Returned +// metrics can be used with GetMetricStatistics to obtain statistical data for +// a given metric. +func (c *CloudWatch) ListMetrics(input *ListMetricsInput) (*ListMetricsOutput, error) { + req, out := c.ListMetricsRequest(input) + err := req.Send() + return out, err +} + +func (c *CloudWatch) ListMetricsPages(input *ListMetricsInput, fn func(p *ListMetricsOutput, lastPage bool) (shouldContinue bool)) error { + page, _ := c.ListMetricsRequest(input) + return page.EachPage(func(p interface{}, lastPage bool) bool { + return fn(p.(*ListMetricsOutput), lastPage) + }) +} + +const opPutMetricAlarm = "PutMetricAlarm" + +// PutMetricAlarmRequest generates a request for the PutMetricAlarm operation. +func (c *CloudWatch) PutMetricAlarmRequest(input *PutMetricAlarmInput) (req *aws.Request, output *PutMetricAlarmOutput) { + op := &aws.Operation{ + Name: opPutMetricAlarm, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &PutMetricAlarmInput{} + } + + req = c.newRequest(op, input, output) + output = &PutMetricAlarmOutput{} + req.Data = output + return +} + +// Creates or updates an alarm and associates it with the specified Amazon CloudWatch +// metric. Optionally, this operation can associate one or more Amazon Simple +// Notification Service resources with the alarm. +// +// When this operation creates an alarm, the alarm state is immediately set +// to INSUFFICIENT_DATA. The alarm is evaluated and its StateValue is set appropriately. +// Any actions associated with the StateValue is then executed. +func (c *CloudWatch) PutMetricAlarm(input *PutMetricAlarmInput) (*PutMetricAlarmOutput, error) { + req, out := c.PutMetricAlarmRequest(input) + err := req.Send() + return out, err +} + +const opPutMetricData = "PutMetricData" + +// PutMetricDataRequest generates a request for the PutMetricData operation. +func (c *CloudWatch) PutMetricDataRequest(input *PutMetricDataInput) (req *aws.Request, output *PutMetricDataOutput) { + op := &aws.Operation{ + Name: opPutMetricData, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &PutMetricDataInput{} + } + + req = c.newRequest(op, input, output) + output = &PutMetricDataOutput{} + req.Data = output + return +} + +// Publishes metric data points to Amazon CloudWatch. Amazon Cloudwatch associates +// the data points with the specified metric. If the specified metric does not +// exist, Amazon CloudWatch creates the metric. It can take up to fifteen minutes +// for a new metric to appear in calls to the ListMetrics action. +// +// The size of a PutMetricData request is limited to 8 KB for HTTP GET requests +// and 40 KB for HTTP POST requests. +// +// Although the Value parameter accepts numbers of type Double, Amazon CloudWatch +// truncates values with very large exponents. Values with base-10 exponents +// greater than 126 (1 x 10^126) are truncated. Likewise, values with base-10 +// exponents less than -130 (1 x 10^-130) are also truncated. Data that is +// timestamped 24 hours or more in the past may take in excess of 48 hours to +// become available from submission time using GetMetricStatistics. +func (c *CloudWatch) PutMetricData(input *PutMetricDataInput) (*PutMetricDataOutput, error) { + req, out := c.PutMetricDataRequest(input) + err := req.Send() + return out, err +} + +const opSetAlarmState = "SetAlarmState" + +// SetAlarmStateRequest generates a request for the SetAlarmState operation. +func (c *CloudWatch) SetAlarmStateRequest(input *SetAlarmStateInput) (req *aws.Request, output *SetAlarmStateOutput) { + op := &aws.Operation{ + Name: opSetAlarmState, + HTTPMethod: "POST", + HTTPPath: "/", + } + + if input == nil { + input = &SetAlarmStateInput{} + } + + req = c.newRequest(op, input, output) + output = &SetAlarmStateOutput{} + req.Data = output + return +} + +// Temporarily sets the state of an alarm. When the updated StateValue differs +// from the previous value, the action configured for the appropriate state +// is invoked. This is not a permanent change. The next periodic alarm check +// (in about a minute) will set the alarm to its actual state. +func (c *CloudWatch) SetAlarmState(input *SetAlarmStateInput) (*SetAlarmStateOutput, error) { + req, out := c.SetAlarmStateRequest(input) + err := req.Send() + return out, err +} + +// The AlarmHistoryItem data type contains descriptive information about the +// history of a specific alarm. If you call DescribeAlarmHistory, Amazon CloudWatch +// returns this data type as part of the DescribeAlarmHistoryResult data type. +type AlarmHistoryItem struct { + // The descriptive name for the alarm. + AlarmName *string `type:"string"` + + // Machine-readable data about the alarm in JSON format. + HistoryData *string `type:"string"` + + // The type of alarm history item. + HistoryItemType *string `type:"string" enum:"HistoryItemType"` + + // A human-readable summary of the alarm history. + HistorySummary *string `type:"string"` + + // The time stamp for the alarm history item. Amazon CloudWatch uses Coordinated + // Universal Time (UTC) when returning time stamps, which do not accommodate + // seasonal adjustments such as daylight savings time. For more information, + // see Time stamps (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#about_timestamp) + // in the Amazon CloudWatch Developer Guide. + Timestamp *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + metadataAlarmHistoryItem `json:"-" xml:"-"` +} + +type metadataAlarmHistoryItem struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s AlarmHistoryItem) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s AlarmHistoryItem) GoString() string { + return s.String() +} + +// The Datapoint data type encapsulates the statistical data that Amazon CloudWatch +// computes from metric data. +type Datapoint struct { + // The average of metric values that correspond to the datapoint. + Average *float64 `type:"double"` + + // The maximum of the metric value used for the datapoint. + Maximum *float64 `type:"double"` + + // The minimum metric value used for the datapoint. + Minimum *float64 `type:"double"` + + // The number of metric values that contributed to the aggregate value of this + // datapoint. + SampleCount *float64 `type:"double"` + + // The sum of metric values used for the datapoint. + Sum *float64 `type:"double"` + + // The time stamp used for the datapoint. Amazon CloudWatch uses Coordinated + // Universal Time (UTC) when returning time stamps, which do not accommodate + // seasonal adjustments such as daylight savings time. For more information, + // see Time stamps (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#about_timestamp) + // in the Amazon CloudWatch Developer Guide. + Timestamp *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + // The standard unit used for the datapoint. + Unit *string `type:"string" enum:"StandardUnit"` + + metadataDatapoint `json:"-" xml:"-"` +} + +type metadataDatapoint struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s Datapoint) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s Datapoint) GoString() string { + return s.String() +} + +type DeleteAlarmsInput struct { + // A list of alarms to be deleted. + AlarmNames []*string `type:"list" required:"true"` + + metadataDeleteAlarmsInput `json:"-" xml:"-"` +} + +type metadataDeleteAlarmsInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DeleteAlarmsInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DeleteAlarmsInput) GoString() string { + return s.String() +} + +type DeleteAlarmsOutput struct { + metadataDeleteAlarmsOutput `json:"-" xml:"-"` +} + +type metadataDeleteAlarmsOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DeleteAlarmsOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DeleteAlarmsOutput) GoString() string { + return s.String() +} + +type DescribeAlarmHistoryInput struct { + // The name of the alarm. + AlarmName *string `type:"string"` + + // The ending date to retrieve alarm history. + EndDate *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + // The type of alarm histories to retrieve. + HistoryItemType *string `type:"string" enum:"HistoryItemType"` + + // The maximum number of alarm history records to retrieve. + MaxRecords *int64 `type:"integer"` + + // The token returned by a previous call to indicate that there is more data + // available. + NextToken *string `type:"string"` + + // The starting date to retrieve alarm history. + StartDate *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + metadataDescribeAlarmHistoryInput `json:"-" xml:"-"` +} + +type metadataDescribeAlarmHistoryInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DescribeAlarmHistoryInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeAlarmHistoryInput) GoString() string { + return s.String() +} + +// The output for the DescribeAlarmHistory action. +type DescribeAlarmHistoryOutput struct { + // A list of alarm histories in JSON format. + AlarmHistoryItems []*AlarmHistoryItem `type:"list"` + + // A string that marks the start of the next batch of returned results. + NextToken *string `type:"string"` + + metadataDescribeAlarmHistoryOutput `json:"-" xml:"-"` +} + +type metadataDescribeAlarmHistoryOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DescribeAlarmHistoryOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeAlarmHistoryOutput) GoString() string { + return s.String() +} + +type DescribeAlarmsForMetricInput struct { + // The list of dimensions associated with the metric. + Dimensions []*Dimension `type:"list"` + + // The name of the metric. + MetricName *string `type:"string" required:"true"` + + // The namespace of the metric. + Namespace *string `type:"string" required:"true"` + + // The period in seconds over which the statistic is applied. + Period *int64 `type:"integer"` + + // The statistic for the metric. + Statistic *string `type:"string" enum:"Statistic"` + + // The unit for the metric. + Unit *string `type:"string" enum:"StandardUnit"` + + metadataDescribeAlarmsForMetricInput `json:"-" xml:"-"` +} + +type metadataDescribeAlarmsForMetricInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DescribeAlarmsForMetricInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeAlarmsForMetricInput) GoString() string { + return s.String() +} + +// The output for the DescribeAlarmsForMetric action. +type DescribeAlarmsForMetricOutput struct { + // A list of information for each alarm with the specified metric. + MetricAlarms []*MetricAlarm `type:"list"` + + metadataDescribeAlarmsForMetricOutput `json:"-" xml:"-"` +} + +type metadataDescribeAlarmsForMetricOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DescribeAlarmsForMetricOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeAlarmsForMetricOutput) GoString() string { + return s.String() +} + +type DescribeAlarmsInput struct { + // The action name prefix. + ActionPrefix *string `type:"string"` + + // The alarm name prefix. AlarmNames cannot be specified if this parameter is + // specified. + AlarmNamePrefix *string `type:"string"` + + // A list of alarm names to retrieve information for. + AlarmNames []*string `type:"list"` + + // The maximum number of alarm descriptions to retrieve. + MaxRecords *int64 `type:"integer"` + + // The token returned by a previous call to indicate that there is more data + // available. + NextToken *string `type:"string"` + + // The state value to be used in matching alarms. + StateValue *string `type:"string" enum:"StateValue"` + + metadataDescribeAlarmsInput `json:"-" xml:"-"` +} + +type metadataDescribeAlarmsInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DescribeAlarmsInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeAlarmsInput) GoString() string { + return s.String() +} + +// The output for the DescribeAlarms action. +type DescribeAlarmsOutput struct { + // A list of information for the specified alarms. + MetricAlarms []*MetricAlarm `type:"list"` + + // A string that marks the start of the next batch of returned results. + NextToken *string `type:"string"` + + metadataDescribeAlarmsOutput `json:"-" xml:"-"` +} + +type metadataDescribeAlarmsOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DescribeAlarmsOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DescribeAlarmsOutput) GoString() string { + return s.String() +} + +// The Dimension data type further expands on the identity of a metric using +// a Name, Value pair. +// +// For examples that use one or more dimensions, see PutMetricData. +type Dimension struct { + // The name of the dimension. + Name *string `type:"string" required:"true"` + + // The value representing the dimension measurement + Value *string `type:"string" required:"true"` + + metadataDimension `json:"-" xml:"-"` +} + +type metadataDimension struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s Dimension) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s Dimension) GoString() string { + return s.String() +} + +// The DimensionFilter data type is used to filter ListMetrics results. +type DimensionFilter struct { + // The dimension name to be matched. + Name *string `type:"string" required:"true"` + + // The value of the dimension to be matched. + Value *string `type:"string"` + + metadataDimensionFilter `json:"-" xml:"-"` +} + +type metadataDimensionFilter struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DimensionFilter) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DimensionFilter) GoString() string { + return s.String() +} + +type DisableAlarmActionsInput struct { + // The names of the alarms to disable actions for. + AlarmNames []*string `type:"list" required:"true"` + + metadataDisableAlarmActionsInput `json:"-" xml:"-"` +} + +type metadataDisableAlarmActionsInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DisableAlarmActionsInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DisableAlarmActionsInput) GoString() string { + return s.String() +} + +type DisableAlarmActionsOutput struct { + metadataDisableAlarmActionsOutput `json:"-" xml:"-"` +} + +type metadataDisableAlarmActionsOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s DisableAlarmActionsOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s DisableAlarmActionsOutput) GoString() string { + return s.String() +} + +type EnableAlarmActionsInput struct { + // The names of the alarms to enable actions for. + AlarmNames []*string `type:"list" required:"true"` + + metadataEnableAlarmActionsInput `json:"-" xml:"-"` +} + +type metadataEnableAlarmActionsInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s EnableAlarmActionsInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s EnableAlarmActionsInput) GoString() string { + return s.String() +} + +type EnableAlarmActionsOutput struct { + metadataEnableAlarmActionsOutput `json:"-" xml:"-"` +} + +type metadataEnableAlarmActionsOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s EnableAlarmActionsOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s EnableAlarmActionsOutput) GoString() string { + return s.String() +} + +type GetMetricStatisticsInput struct { + // A list of dimensions describing qualities of the metric. + Dimensions []*Dimension `type:"list"` + + // The time stamp to use for determining the last datapoint to return. The value + // specified is exclusive; results will include datapoints up to the time stamp + // specified. + EndTime *time.Time `type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The name of the metric, with or without spaces. + MetricName *string `type:"string" required:"true"` + + // The namespace of the metric, with or without spaces. + Namespace *string `type:"string" required:"true"` + + // The granularity, in seconds, of the returned datapoints. Period must be at + // least 60 seconds and must be a multiple of 60. The default value is 60. + Period *int64 `type:"integer" required:"true"` + + // The time stamp to use for determining the first datapoint to return. The + // value specified is inclusive; results include datapoints with the time stamp + // specified. + StartTime *time.Time `type:"timestamp" timestampFormat:"iso8601" required:"true"` + + // The metric statistics to return. For information about specific statistics + // returned by GetMetricStatistics, go to Statistics (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/index.html?CHAP_TerminologyandKeyConcepts.html#Statistic) + // in the Amazon CloudWatch Developer Guide. + // + // Valid Values: Average | Sum | SampleCount | Maximum | Minimum + Statistics []*string `type:"list" required:"true"` + + // The unit for the metric. + Unit *string `type:"string" enum:"StandardUnit"` + + metadataGetMetricStatisticsInput `json:"-" xml:"-"` +} + +type metadataGetMetricStatisticsInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s GetMetricStatisticsInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s GetMetricStatisticsInput) GoString() string { + return s.String() +} + +// The output for the GetMetricStatistics action. +type GetMetricStatisticsOutput struct { + // The datapoints for the specified metric. + Datapoints []*Datapoint `type:"list"` + + // A label describing the specified metric. + Label *string `type:"string"` + + metadataGetMetricStatisticsOutput `json:"-" xml:"-"` +} + +type metadataGetMetricStatisticsOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s GetMetricStatisticsOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s GetMetricStatisticsOutput) GoString() string { + return s.String() +} + +type ListMetricsInput struct { + // A list of dimensions to filter against. + Dimensions []*DimensionFilter `type:"list"` + + // The name of the metric to filter against. + MetricName *string `type:"string"` + + // The namespace to filter against. + Namespace *string `type:"string"` + + // The token returned by a previous call to indicate that there is more data + // available. + NextToken *string `type:"string"` + + metadataListMetricsInput `json:"-" xml:"-"` +} + +type metadataListMetricsInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s ListMetricsInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s ListMetricsInput) GoString() string { + return s.String() +} + +// The output for the ListMetrics action. +type ListMetricsOutput struct { + // A list of metrics used to generate statistics for an AWS account. + Metrics []*Metric `type:"list"` + + // A string that marks the start of the next batch of returned results. + NextToken *string `type:"string"` + + metadataListMetricsOutput `json:"-" xml:"-"` +} + +type metadataListMetricsOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s ListMetricsOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s ListMetricsOutput) GoString() string { + return s.String() +} + +// The Metric data type contains information about a specific metric. If you +// call ListMetrics, Amazon CloudWatch returns information contained by this +// data type. +// +// The example in the Examples section publishes two metrics named buffers +// and latency. Both metrics are in the examples namespace. Both metrics have +// two dimensions, InstanceID and InstanceType. +type Metric struct { + // A list of dimensions associated with the metric. + Dimensions []*Dimension `type:"list"` + + // The name of the metric. + MetricName *string `type:"string"` + + // The namespace of the metric. + Namespace *string `type:"string"` + + metadataMetric `json:"-" xml:"-"` +} + +type metadataMetric struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s Metric) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s Metric) GoString() string { + return s.String() +} + +// The MetricAlarm data type represents an alarm. You can use PutMetricAlarm +// to create or update an alarm. +type MetricAlarm struct { + // Indicates whether actions should be executed during any changes to the alarm's + // state. + ActionsEnabled *bool `type:"boolean"` + + // The Amazon Resource Name (ARN) of the alarm. + AlarmARN *string `locationName:"AlarmArn" type:"string"` + + // The list of actions to execute when this alarm transitions into an ALARM + // state from any other state. Each action is specified as an Amazon Resource + // Number (ARN). Currently the only actions supported are publishing to an Amazon + // SNS topic and triggering an Auto Scaling policy. + AlarmActions []*string `type:"list"` + + // The time stamp of the last update to the alarm configuration. Amazon CloudWatch + // uses Coordinated Universal Time (UTC) when returning time stamps, which do + // not accommodate seasonal adjustments such as daylight savings time. For more + // information, see Time stamps (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#about_timestamp) + // in the Amazon CloudWatch Developer Guide. + AlarmConfigurationUpdatedTimestamp *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + // The description for the alarm. + AlarmDescription *string `type:"string"` + + // The name of the alarm. + AlarmName *string `type:"string"` + + // The arithmetic operation to use when comparing the specified Statistic and + // Threshold. The specified Statistic value is used as the first operand. + ComparisonOperator *string `type:"string" enum:"ComparisonOperator"` + + // The list of dimensions associated with the alarm's associated metric. + Dimensions []*Dimension `type:"list"` + + // The number of periods over which data is compared to the specified threshold. + EvaluationPeriods *int64 `type:"integer"` + + // The list of actions to execute when this alarm transitions into an INSUFFICIENT_DATA + // state from any other state. Each action is specified as an Amazon Resource + // Number (ARN). Currently the only actions supported are publishing to an Amazon + // SNS topic or triggering an Auto Scaling policy. + // + // The current WSDL lists this attribute as UnknownActions. + InsufficientDataActions []*string `type:"list"` + + // The name of the alarm's metric. + MetricName *string `type:"string"` + + // The namespace of alarm's associated metric. + Namespace *string `type:"string"` + + // The list of actions to execute when this alarm transitions into an OK state + // from any other state. Each action is specified as an Amazon Resource Number + // (ARN). Currently the only actions supported are publishing to an Amazon SNS + // topic and triggering an Auto Scaling policy. + OKActions []*string `type:"list"` + + // The period in seconds over which the statistic is applied. + Period *int64 `type:"integer"` + + // A human-readable explanation for the alarm's state. + StateReason *string `type:"string"` + + // An explanation for the alarm's state in machine-readable JSON format + StateReasonData *string `type:"string"` + + // The time stamp of the last update to the alarm's state. Amazon CloudWatch + // uses Coordinated Universal Time (UTC) when returning time stamps, which do + // not accommodate seasonal adjustments such as daylight savings time. For more + // information, see Time stamps (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#about_timestamp) + // in the Amazon CloudWatch Developer Guide. + StateUpdatedTimestamp *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + // The state value for the alarm. + StateValue *string `type:"string" enum:"StateValue"` + + // The statistic to apply to the alarm's associated metric. + Statistic *string `type:"string" enum:"Statistic"` + + // The value against which the specified statistic is compared. + Threshold *float64 `type:"double"` + + // The unit of the alarm's associated metric. + Unit *string `type:"string" enum:"StandardUnit"` + + metadataMetricAlarm `json:"-" xml:"-"` +} + +type metadataMetricAlarm struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s MetricAlarm) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s MetricAlarm) GoString() string { + return s.String() +} + +// The MetricDatum data type encapsulates the information sent with PutMetricData +// to either create a new metric or add new values to be aggregated into an +// existing metric. +type MetricDatum struct { + // A list of dimensions associated with the metric. Note, when using the Dimensions + // value in a query, you need to append .member.N to it (e.g., Dimensions.member.N). + Dimensions []*Dimension `type:"list"` + + // The name of the metric. + MetricName *string `type:"string" required:"true"` + + // A set of statistical values describing the metric. + StatisticValues *StatisticSet `type:"structure"` + + // The time stamp used for the metric. If not specified, the default value is + // set to the time the metric data was received. Amazon CloudWatch uses Coordinated + // Universal Time (UTC) when returning time stamps, which do not accommodate + // seasonal adjustments such as daylight savings time. For more information, + // see Time stamps (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/cloudwatch_concepts.html#about_timestamp) + // in the Amazon CloudWatch Developer Guide. + Timestamp *time.Time `type:"timestamp" timestampFormat:"iso8601"` + + // The unit of the metric. + Unit *string `type:"string" enum:"StandardUnit"` + + // The value for the metric. + // + // Although the Value parameter accepts numbers of type Double, Amazon CloudWatch + // truncates values with very large exponents. Values with base-10 exponents + // greater than 126 (1 x 10^126) are truncated. Likewise, values with base-10 + // exponents less than -130 (1 x 10^-130) are also truncated. + Value *float64 `type:"double"` + + metadataMetricDatum `json:"-" xml:"-"` +} + +type metadataMetricDatum struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s MetricDatum) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s MetricDatum) GoString() string { + return s.String() +} + +type PutMetricAlarmInput struct { + // Indicates whether or not actions should be executed during any changes to + // the alarm's state. + ActionsEnabled *bool `type:"boolean"` + + // The list of actions to execute when this alarm transitions into an ALARM + // state from any other state. Each action is specified as an Amazon Resource + // Number (ARN). Currently the only action supported is publishing to an Amazon + // SNS topic or an Amazon Auto Scaling policy. + AlarmActions []*string `type:"list"` + + // The description for the alarm. + AlarmDescription *string `type:"string"` + + // The descriptive name for the alarm. This name must be unique within the user's + // AWS account + AlarmName *string `type:"string" required:"true"` + + // The arithmetic operation to use when comparing the specified Statistic and + // Threshold. The specified Statistic value is used as the first operand. + ComparisonOperator *string `type:"string" required:"true" enum:"ComparisonOperator"` + + // The dimensions for the alarm's associated metric. + Dimensions []*Dimension `type:"list"` + + // The number of periods over which data is compared to the specified threshold. + EvaluationPeriods *int64 `type:"integer" required:"true"` + + // The list of actions to execute when this alarm transitions into an INSUFFICIENT_DATA + // state from any other state. Each action is specified as an Amazon Resource + // Number (ARN). Currently the only action supported is publishing to an Amazon + // SNS topic or an Amazon Auto Scaling policy. + InsufficientDataActions []*string `type:"list"` + + // The name for the alarm's associated metric. + MetricName *string `type:"string" required:"true"` + + // The namespace for the alarm's associated metric. + Namespace *string `type:"string" required:"true"` + + // The list of actions to execute when this alarm transitions into an OK state + // from any other state. Each action is specified as an Amazon Resource Number + // (ARN). Currently the only action supported is publishing to an Amazon SNS + // topic or an Amazon Auto Scaling policy. + OKActions []*string `type:"list"` + + // The period in seconds over which the specified statistic is applied. + Period *int64 `type:"integer" required:"true"` + + // The statistic to apply to the alarm's associated metric. + Statistic *string `type:"string" required:"true" enum:"Statistic"` + + // The value against which the specified statistic is compared. + Threshold *float64 `type:"double" required:"true"` + + // The unit for the alarm's associated metric. + Unit *string `type:"string" enum:"StandardUnit"` + + metadataPutMetricAlarmInput `json:"-" xml:"-"` +} + +type metadataPutMetricAlarmInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s PutMetricAlarmInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s PutMetricAlarmInput) GoString() string { + return s.String() +} + +type PutMetricAlarmOutput struct { + metadataPutMetricAlarmOutput `json:"-" xml:"-"` +} + +type metadataPutMetricAlarmOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s PutMetricAlarmOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s PutMetricAlarmOutput) GoString() string { + return s.String() +} + +type PutMetricDataInput struct { + // A list of data describing the metric. + MetricData []*MetricDatum `type:"list" required:"true"` + + // The namespace for the metric data. + Namespace *string `type:"string" required:"true"` + + metadataPutMetricDataInput `json:"-" xml:"-"` +} + +type metadataPutMetricDataInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s PutMetricDataInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s PutMetricDataInput) GoString() string { + return s.String() +} + +type PutMetricDataOutput struct { + metadataPutMetricDataOutput `json:"-" xml:"-"` +} + +type metadataPutMetricDataOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s PutMetricDataOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s PutMetricDataOutput) GoString() string { + return s.String() +} + +type SetAlarmStateInput struct { + // The descriptive name for the alarm. This name must be unique within the user's + // AWS account. The maximum length is 255 characters. + AlarmName *string `type:"string" required:"true"` + + // The reason that this alarm is set to this specific state (in human-readable + // text format) + StateReason *string `type:"string" required:"true"` + + // The reason that this alarm is set to this specific state (in machine-readable + // JSON format) + StateReasonData *string `type:"string"` + + // The value of the state. + StateValue *string `type:"string" required:"true" enum:"StateValue"` + + metadataSetAlarmStateInput `json:"-" xml:"-"` +} + +type metadataSetAlarmStateInput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s SetAlarmStateInput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s SetAlarmStateInput) GoString() string { + return s.String() +} + +type SetAlarmStateOutput struct { + metadataSetAlarmStateOutput `json:"-" xml:"-"` +} + +type metadataSetAlarmStateOutput struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s SetAlarmStateOutput) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s SetAlarmStateOutput) GoString() string { + return s.String() +} + +// The StatisticSet data type describes the StatisticValues component of MetricDatum, +// and represents a set of statistics that describes a specific metric. +type StatisticSet struct { + // The maximum value of the sample set. + Maximum *float64 `type:"double" required:"true"` + + // The minimum value of the sample set. + Minimum *float64 `type:"double" required:"true"` + + // The number of samples used for the statistic set. + SampleCount *float64 `type:"double" required:"true"` + + // The sum of values for the sample set. + Sum *float64 `type:"double" required:"true"` + + metadataStatisticSet `json:"-" xml:"-"` +} + +type metadataStatisticSet struct { + SDKShapeTraits bool `type:"structure"` +} + +// String returns the string representation +func (s StatisticSet) String() string { + return awsutil.Prettify(s) +} + +// GoString returns the string representation +func (s StatisticSet) GoString() string { + return s.String() +} + +const ( + // @enum ComparisonOperator + ComparisonOperatorGreaterThanOrEqualToThreshold = "GreaterThanOrEqualToThreshold" + // @enum ComparisonOperator + ComparisonOperatorGreaterThanThreshold = "GreaterThanThreshold" + // @enum ComparisonOperator + ComparisonOperatorLessThanThreshold = "LessThanThreshold" + // @enum ComparisonOperator + ComparisonOperatorLessThanOrEqualToThreshold = "LessThanOrEqualToThreshold" +) + +const ( + // @enum HistoryItemType + HistoryItemTypeConfigurationUpdate = "ConfigurationUpdate" + // @enum HistoryItemType + HistoryItemTypeStateUpdate = "StateUpdate" + // @enum HistoryItemType + HistoryItemTypeAction = "Action" +) + +const ( + // @enum StandardUnit + StandardUnitSeconds = "Seconds" + // @enum StandardUnit + StandardUnitMicroseconds = "Microseconds" + // @enum StandardUnit + StandardUnitMilliseconds = "Milliseconds" + // @enum StandardUnit + StandardUnitBytes = "Bytes" + // @enum StandardUnit + StandardUnitKilobytes = "Kilobytes" + // @enum StandardUnit + StandardUnitMegabytes = "Megabytes" + // @enum StandardUnit + StandardUnitGigabytes = "Gigabytes" + // @enum StandardUnit + StandardUnitTerabytes = "Terabytes" + // @enum StandardUnit + StandardUnitBits = "Bits" + // @enum StandardUnit + StandardUnitKilobits = "Kilobits" + // @enum StandardUnit + StandardUnitMegabits = "Megabits" + // @enum StandardUnit + StandardUnitGigabits = "Gigabits" + // @enum StandardUnit + StandardUnitTerabits = "Terabits" + // @enum StandardUnit + StandardUnitPercent = "Percent" + // @enum StandardUnit + StandardUnitCount = "Count" + // @enum StandardUnit + StandardUnitBytesSecond = "Bytes/Second" + // @enum StandardUnit + StandardUnitKilobytesSecond = "Kilobytes/Second" + // @enum StandardUnit + StandardUnitMegabytesSecond = "Megabytes/Second" + // @enum StandardUnit + StandardUnitGigabytesSecond = "Gigabytes/Second" + // @enum StandardUnit + StandardUnitTerabytesSecond = "Terabytes/Second" + // @enum StandardUnit + StandardUnitBitsSecond = "Bits/Second" + // @enum StandardUnit + StandardUnitKilobitsSecond = "Kilobits/Second" + // @enum StandardUnit + StandardUnitMegabitsSecond = "Megabits/Second" + // @enum StandardUnit + StandardUnitGigabitsSecond = "Gigabits/Second" + // @enum StandardUnit + StandardUnitTerabitsSecond = "Terabits/Second" + // @enum StandardUnit + StandardUnitCountSecond = "Count/Second" + // @enum StandardUnit + StandardUnitNone = "None" +) + +const ( + // @enum StateValue + StateValueOk = "OK" + // @enum StateValue + StateValueAlarm = "ALARM" + // @enum StateValue + StateValueInsufficientData = "INSUFFICIENT_DATA" +) + +const ( + // @enum Statistic + StatisticSampleCount = "SampleCount" + // @enum Statistic + StatisticAverage = "Average" + // @enum Statistic + StatisticSum = "Sum" + // @enum Statistic + StatisticMinimum = "Minimum" + // @enum Statistic + StatisticMaximum = "Maximum" +) diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface/interface.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface/interface.go new file mode 100644 index 00000000000..77947d73980 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface/interface.go @@ -0,0 +1,62 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +// Package cloudwatchiface provides an interface for the Amazon CloudWatch. +package cloudwatchiface + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" +) + +// CloudWatchAPI is the interface type for cloudwatch.CloudWatch. +type CloudWatchAPI interface { + DeleteAlarmsRequest(*cloudwatch.DeleteAlarmsInput) (*aws.Request, *cloudwatch.DeleteAlarmsOutput) + + DeleteAlarms(*cloudwatch.DeleteAlarmsInput) (*cloudwatch.DeleteAlarmsOutput, error) + + DescribeAlarmHistoryRequest(*cloudwatch.DescribeAlarmHistoryInput) (*aws.Request, *cloudwatch.DescribeAlarmHistoryOutput) + + DescribeAlarmHistory(*cloudwatch.DescribeAlarmHistoryInput) (*cloudwatch.DescribeAlarmHistoryOutput, error) + + DescribeAlarmHistoryPages(*cloudwatch.DescribeAlarmHistoryInput, func(*cloudwatch.DescribeAlarmHistoryOutput, bool) bool) error + + DescribeAlarmsRequest(*cloudwatch.DescribeAlarmsInput) (*aws.Request, *cloudwatch.DescribeAlarmsOutput) + + DescribeAlarms(*cloudwatch.DescribeAlarmsInput) (*cloudwatch.DescribeAlarmsOutput, error) + + DescribeAlarmsPages(*cloudwatch.DescribeAlarmsInput, func(*cloudwatch.DescribeAlarmsOutput, bool) bool) error + + DescribeAlarmsForMetricRequest(*cloudwatch.DescribeAlarmsForMetricInput) (*aws.Request, *cloudwatch.DescribeAlarmsForMetricOutput) + + DescribeAlarmsForMetric(*cloudwatch.DescribeAlarmsForMetricInput) (*cloudwatch.DescribeAlarmsForMetricOutput, error) + + DisableAlarmActionsRequest(*cloudwatch.DisableAlarmActionsInput) (*aws.Request, *cloudwatch.DisableAlarmActionsOutput) + + DisableAlarmActions(*cloudwatch.DisableAlarmActionsInput) (*cloudwatch.DisableAlarmActionsOutput, error) + + EnableAlarmActionsRequest(*cloudwatch.EnableAlarmActionsInput) (*aws.Request, *cloudwatch.EnableAlarmActionsOutput) + + EnableAlarmActions(*cloudwatch.EnableAlarmActionsInput) (*cloudwatch.EnableAlarmActionsOutput, error) + + GetMetricStatisticsRequest(*cloudwatch.GetMetricStatisticsInput) (*aws.Request, *cloudwatch.GetMetricStatisticsOutput) + + GetMetricStatistics(*cloudwatch.GetMetricStatisticsInput) (*cloudwatch.GetMetricStatisticsOutput, error) + + ListMetricsRequest(*cloudwatch.ListMetricsInput) (*aws.Request, *cloudwatch.ListMetricsOutput) + + ListMetrics(*cloudwatch.ListMetricsInput) (*cloudwatch.ListMetricsOutput, error) + + ListMetricsPages(*cloudwatch.ListMetricsInput, func(*cloudwatch.ListMetricsOutput, bool) bool) error + + PutMetricAlarmRequest(*cloudwatch.PutMetricAlarmInput) (*aws.Request, *cloudwatch.PutMetricAlarmOutput) + + PutMetricAlarm(*cloudwatch.PutMetricAlarmInput) (*cloudwatch.PutMetricAlarmOutput, error) + + PutMetricDataRequest(*cloudwatch.PutMetricDataInput) (*aws.Request, *cloudwatch.PutMetricDataOutput) + + PutMetricData(*cloudwatch.PutMetricDataInput) (*cloudwatch.PutMetricDataOutput, error) + + SetAlarmStateRequest(*cloudwatch.SetAlarmStateInput) (*aws.Request, *cloudwatch.SetAlarmStateOutput) + + SetAlarmState(*cloudwatch.SetAlarmStateInput) (*cloudwatch.SetAlarmStateOutput, error) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface/interface_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface/interface_test.go new file mode 100644 index 00000000000..cdc377249db --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface/interface_test.go @@ -0,0 +1,15 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +package cloudwatchiface_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" + "github.com/stretchr/testify/assert" +) + +func TestInterface(t *testing.T) { + assert.Implements(t, (*cloudwatchiface.CloudWatchAPI)(nil), cloudwatch.New(nil)) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/examples_test.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/examples_test.go new file mode 100644 index 00000000000..cd1eeb2350a --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/examples_test.go @@ -0,0 +1,426 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +package cloudwatch_test + +import ( + "bytes" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/awsutil" + "github.com/aws/aws-sdk-go/service/cloudwatch" +) + +var _ time.Duration +var _ bytes.Buffer + +func ExampleCloudWatch_DeleteAlarms() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.DeleteAlarmsInput{ + AlarmNames: []*string{ // Required + aws.String("AlarmName"), // Required + // More values... + }, + } + resp, err := svc.DeleteAlarms(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_DescribeAlarmHistory() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.DescribeAlarmHistoryInput{ + AlarmName: aws.String("AlarmName"), + EndDate: aws.Time(time.Now()), + HistoryItemType: aws.String("HistoryItemType"), + MaxRecords: aws.Int64(1), + NextToken: aws.String("NextToken"), + StartDate: aws.Time(time.Now()), + } + resp, err := svc.DescribeAlarmHistory(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_DescribeAlarms() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.DescribeAlarmsInput{ + ActionPrefix: aws.String("ActionPrefix"), + AlarmNamePrefix: aws.String("AlarmNamePrefix"), + AlarmNames: []*string{ + aws.String("AlarmName"), // Required + // More values... + }, + MaxRecords: aws.Int64(1), + NextToken: aws.String("NextToken"), + StateValue: aws.String("StateValue"), + } + resp, err := svc.DescribeAlarms(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_DescribeAlarmsForMetric() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.DescribeAlarmsForMetricInput{ + MetricName: aws.String("MetricName"), // Required + Namespace: aws.String("Namespace"), // Required + Dimensions: []*cloudwatch.Dimension{ + { // Required + Name: aws.String("DimensionName"), // Required + Value: aws.String("DimensionValue"), // Required + }, + // More values... + }, + Period: aws.Int64(1), + Statistic: aws.String("Statistic"), + Unit: aws.String("StandardUnit"), + } + resp, err := svc.DescribeAlarmsForMetric(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_DisableAlarmActions() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.DisableAlarmActionsInput{ + AlarmNames: []*string{ // Required + aws.String("AlarmName"), // Required + // More values... + }, + } + resp, err := svc.DisableAlarmActions(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_EnableAlarmActions() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.EnableAlarmActionsInput{ + AlarmNames: []*string{ // Required + aws.String("AlarmName"), // Required + // More values... + }, + } + resp, err := svc.EnableAlarmActions(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_GetMetricStatistics() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.GetMetricStatisticsInput{ + EndTime: aws.Time(time.Now()), // Required + MetricName: aws.String("MetricName"), // Required + Namespace: aws.String("Namespace"), // Required + Period: aws.Int64(1), // Required + StartTime: aws.Time(time.Now()), // Required + Statistics: []*string{ // Required + aws.String("Statistic"), // Required + // More values... + }, + Dimensions: []*cloudwatch.Dimension{ + { // Required + Name: aws.String("DimensionName"), // Required + Value: aws.String("DimensionValue"), // Required + }, + // More values... + }, + Unit: aws.String("StandardUnit"), + } + resp, err := svc.GetMetricStatistics(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_ListMetrics() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.ListMetricsInput{ + Dimensions: []*cloudwatch.DimensionFilter{ + { // Required + Name: aws.String("DimensionName"), // Required + Value: aws.String("DimensionValue"), + }, + // More values... + }, + MetricName: aws.String("MetricName"), + Namespace: aws.String("Namespace"), + NextToken: aws.String("NextToken"), + } + resp, err := svc.ListMetrics(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_PutMetricAlarm() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.PutMetricAlarmInput{ + AlarmName: aws.String("AlarmName"), // Required + ComparisonOperator: aws.String("ComparisonOperator"), // Required + EvaluationPeriods: aws.Int64(1), // Required + MetricName: aws.String("MetricName"), // Required + Namespace: aws.String("Namespace"), // Required + Period: aws.Int64(1), // Required + Statistic: aws.String("Statistic"), // Required + Threshold: aws.Float64(1.0), // Required + ActionsEnabled: aws.Bool(true), + AlarmActions: []*string{ + aws.String("ResourceName"), // Required + // More values... + }, + AlarmDescription: aws.String("AlarmDescription"), + Dimensions: []*cloudwatch.Dimension{ + { // Required + Name: aws.String("DimensionName"), // Required + Value: aws.String("DimensionValue"), // Required + }, + // More values... + }, + InsufficientDataActions: []*string{ + aws.String("ResourceName"), // Required + // More values... + }, + OKActions: []*string{ + aws.String("ResourceName"), // Required + // More values... + }, + Unit: aws.String("StandardUnit"), + } + resp, err := svc.PutMetricAlarm(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_PutMetricData() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.PutMetricDataInput{ + MetricData: []*cloudwatch.MetricDatum{ // Required + { // Required + MetricName: aws.String("MetricName"), // Required + Dimensions: []*cloudwatch.Dimension{ + { // Required + Name: aws.String("DimensionName"), // Required + Value: aws.String("DimensionValue"), // Required + }, + // More values... + }, + StatisticValues: &cloudwatch.StatisticSet{ + Maximum: aws.Float64(1.0), // Required + Minimum: aws.Float64(1.0), // Required + SampleCount: aws.Float64(1.0), // Required + Sum: aws.Float64(1.0), // Required + }, + Timestamp: aws.Time(time.Now()), + Unit: aws.String("StandardUnit"), + Value: aws.Float64(1.0), + }, + // More values... + }, + Namespace: aws.String("Namespace"), // Required + } + resp, err := svc.PutMetricData(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} + +func ExampleCloudWatch_SetAlarmState() { + svc := cloudwatch.New(nil) + + params := &cloudwatch.SetAlarmStateInput{ + AlarmName: aws.String("AlarmName"), // Required + StateReason: aws.String("StateReason"), // Required + StateValue: aws.String("StateValue"), // Required + StateReasonData: aws.String("StateReasonData"), + } + resp, err := svc.SetAlarmState(params) + + if err != nil { + if awsErr, ok := err.(awserr.Error); ok { + // Generic AWS error with Code, Message, and original error (if any) + fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr()) + if reqErr, ok := err.(awserr.RequestFailure); ok { + // A service error occurred + fmt.Println(reqErr.Code(), reqErr.Message(), reqErr.StatusCode(), reqErr.RequestID()) + } + } else { + // This case should never be hit, the SDK should always return an + // error which satisfies the awserr.Error interface. + fmt.Println(err.Error()) + } + } + + // Pretty-print the response data. + fmt.Println(awsutil.Prettify(resp)) +} diff --git a/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/service.go b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/service.go new file mode 100644 index 00000000000..eadc454e2a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/aws/aws-sdk-go/service/cloudwatch/service.go @@ -0,0 +1,96 @@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. + +package cloudwatch + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/internal/protocol/query" + "github.com/aws/aws-sdk-go/internal/signer/v4" +) + +// This is the Amazon CloudWatch API Reference. This guide provides detailed +// information about Amazon CloudWatch actions, data types, parameters, and +// errors. For detailed information about Amazon CloudWatch features and their +// associated API calls, go to the Amazon CloudWatch Developer Guide (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide). +// +// Amazon CloudWatch is a web service that enables you to publish, monitor, +// and manage various metrics, as well as configure alarm actions based on data +// from metrics. For more information about this product go to http://aws.amazon.com/cloudwatch +// (http://aws.amazon.com/cloudwatch). +// +// For information about the namespace, metric names, and dimensions that +// other Amazon Web Services products use to send metrics to Cloudwatch, go +// to Amazon CloudWatch Metrics, Namespaces, and Dimensions Reference (http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html) +// in the Amazon CloudWatch Developer Guide. +// +// Use the following links to get started using the Amazon CloudWatch API Reference: +// +// Actions (http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Operations.html): +// An alphabetical list of all Amazon CloudWatch actions. Data Types (http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Types.html): +// An alphabetical list of all Amazon CloudWatch data types. Common Parameters +// (http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/CommonParameters.html): +// Parameters that all Query actions can use. Common Errors (http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/CommonErrors.html): +// Client and server errors that all actions can return. Regions and Endpoints +// (http://docs.aws.amazon.com/general/latest/gr/index.html?rande.html): Itemized +// regions and endpoints for all AWS products. WSDL Location (http://monitoring.amazonaws.com/doc/2010-08-01/CloudWatch.wsdl): +// http://monitoring.amazonaws.com/doc/2010-08-01/CloudWatch.wsdl In addition +// to using the Amazon CloudWatch API, you can also use the following SDKs and +// third-party libraries to access Amazon CloudWatch programmatically. +// +// AWS SDK for Java Documentation (http://aws.amazon.com/documentation/sdkforjava/) +// AWS SDK for .NET Documentation (http://aws.amazon.com/documentation/sdkfornet/) +// AWS SDK for PHP Documentation (http://aws.amazon.com/documentation/sdkforphp/) +// AWS SDK for Ruby Documentation (http://aws.amazon.com/documentation/sdkforruby/) +// Developers in the AWS developer community also provide their own libraries, +// which you can find at the following AWS developer centers: +// +// AWS Java Developer Center (http://aws.amazon.com/java/) AWS PHP Developer +// Center (http://aws.amazon.com/php/) AWS Python Developer Center (http://aws.amazon.com/python/) +// AWS Ruby Developer Center (http://aws.amazon.com/ruby/) AWS Windows and .NET +// Developer Center (http://aws.amazon.com/net/) +type CloudWatch struct { + *aws.Service +} + +// Used for custom service initialization logic +var initService func(*aws.Service) + +// Used for custom request initialization logic +var initRequest func(*aws.Request) + +// New returns a new CloudWatch client. +func New(config *aws.Config) *CloudWatch { + service := &aws.Service{ + Config: aws.DefaultConfig.Merge(config), + ServiceName: "monitoring", + APIVersion: "2010-08-01", + } + service.Initialize() + + // Handlers + service.Handlers.Sign.PushBack(v4.Sign) + service.Handlers.Build.PushBack(query.Build) + service.Handlers.Unmarshal.PushBack(query.Unmarshal) + service.Handlers.UnmarshalMeta.PushBack(query.UnmarshalMeta) + service.Handlers.UnmarshalError.PushBack(query.UnmarshalError) + + // Run custom service initialization if present + if initService != nil { + initService(service) + } + + return &CloudWatch{service} +} + +// newRequest creates a new request for a CloudWatch operation and runs any +// custom request initialization. +func (c *CloudWatch) newRequest(op *aws.Operation, params, data interface{}) *aws.Request { + req := aws.NewRequest(c.Service, op, params, data) + + // Run custom request initialization if present + if initRequest != nil { + initRequest(req) + } + + return req +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypass.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypass.go new file mode 100644 index 00000000000..a8d27a3f677 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypass.go @@ -0,0 +1,136 @@ +// Copyright (c) 2015 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is not running on Google App Engine and "-tags disableunsafe" +// is not added to the go build command line. +// +build !appengine,!disableunsafe + +package spew + +import ( + "reflect" + "unsafe" +) + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = false + + // ptrSize is the size of a pointer on the current arch. + ptrSize = unsafe.Sizeof((*byte)(nil)) +) + +var ( + // offsetPtr, offsetScalar, and offsetFlag are the offsets for the + // internal reflect.Value fields. These values are valid before golang + // commit ecccf07e7f9d which changed the format. The are also valid + // after commit 82f48826c6c7 which changed the format again to mirror + // the original format. Code in the init function updates these offsets + // as necessary. + offsetPtr = uintptr(ptrSize) + offsetScalar = uintptr(0) + offsetFlag = uintptr(ptrSize * 2) + + // flagKindWidth and flagKindShift indicate various bits that the + // reflect package uses internally to track kind information. + // + // flagRO indicates whether or not the value field of a reflect.Value is + // read-only. + // + // flagIndir indicates whether the value field of a reflect.Value is + // the actual data or a pointer to the data. + // + // These values are valid before golang commit 90a7c3c86944 which + // changed their positions. Code in the init function updates these + // flags as necessary. + flagKindWidth = uintptr(5) + flagKindShift = uintptr(flagKindWidth - 1) + flagRO = uintptr(1 << 0) + flagIndir = uintptr(1 << 1) +) + +func init() { + // Older versions of reflect.Value stored small integers directly in the + // ptr field (which is named val in the older versions). Versions + // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named + // scalar for this purpose which unfortunately came before the flag + // field, so the offset of the flag field is different for those + // versions. + // + // This code constructs a new reflect.Value from a known small integer + // and checks if the size of the reflect.Value struct indicates it has + // the scalar field. When it does, the offsets are updated accordingly. + vv := reflect.ValueOf(0xf00) + if unsafe.Sizeof(vv) == (ptrSize * 4) { + offsetScalar = ptrSize * 2 + offsetFlag = ptrSize * 3 + } + + // Commit 90a7c3c86944 changed the flag positions such that the low + // order bits are the kind. This code extracts the kind from the flags + // field and ensures it's the correct type. When it's not, the flag + // order has been changed to the newer format, so the flags are updated + // accordingly. + upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) + upfv := *(*uintptr)(upf) + flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { + flagKindShift = 0 + flagRO = 1 << 5 + flagIndir = 1 << 6 + } +} + +// unsafeReflectValue converts the passed reflect.Value into a one that bypasses +// the typical safety restrictions preventing access to unaddressable and +// unexported data. It works by digging the raw pointer to the underlying +// value out of the protected value and generating a new unprotected (unsafe) +// reflect.Value to it. +// +// This allows us to check for implementations of the Stringer and error +// interfaces to be used for pretty printing ordinarily unaddressable and +// inaccessible values such as unexported struct fields. +func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { + indirects := 1 + vt := v.Type() + upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) + rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) + if rvf&flagIndir != 0 { + vt = reflect.PtrTo(v.Type()) + indirects++ + } else if offsetScalar != 0 { + // The value is in the scalar field when it's not one of the + // reference types. + switch vt.Kind() { + case reflect.Uintptr: + case reflect.Chan: + case reflect.Func: + case reflect.Map: + case reflect.Ptr: + case reflect.UnsafePointer: + default: + upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + + offsetScalar) + } + } + + pv := reflect.NewAt(vt, upv) + rv = pv + for i := 0; i < indirects; i++ { + rv = rv.Elem() + } + return rv +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypasssafe.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypasssafe.go new file mode 100644 index 00000000000..457e41235ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -0,0 +1,37 @@ +// Copyright (c) 2015 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when either the code is running on Google App Engine or "-tags disableunsafe" +// is added to the go build command line. +// +build appengine disableunsafe + +package spew + +import "reflect" + +const ( + // UnsafeDisabled is a build-time constant which specifies whether or + // not access to the unsafe package is available. + UnsafeDisabled = true +) + +// unsafeReflectValue typically converts the passed reflect.Value into a one +// that bypasses the typical safety restrictions preventing access to +// unaddressable and unexported data. However, doing this relies on access to +// the unsafe package. This is a stub version which simply returns the passed +// reflect.Value when the unsafe package is not available. +func unsafeReflectValue(v reflect.Value) reflect.Value { + return v +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/common.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/common.go new file mode 100644 index 00000000000..14f02dc15b7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/common.go @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "reflect" + "sort" + "strconv" +) + +// Some constants in the form of bytes to avoid string overhead. This mirrors +// the technique used in the fmt package. +var ( + panicBytes = []byte("(PANIC=") + plusBytes = []byte("+") + iBytes = []byte("i") + trueBytes = []byte("true") + falseBytes = []byte("false") + interfaceBytes = []byte("(interface {})") + commaNewlineBytes = []byte(",\n") + newlineBytes = []byte("\n") + openBraceBytes = []byte("{") + openBraceNewlineBytes = []byte("{\n") + closeBraceBytes = []byte("}") + asteriskBytes = []byte("*") + colonBytes = []byte(":") + colonSpaceBytes = []byte(": ") + openParenBytes = []byte("(") + closeParenBytes = []byte(")") + spaceBytes = []byte(" ") + pointerChainBytes = []byte("->") + nilAngleBytes = []byte("") + maxNewlineBytes = []byte("\n") + maxShortBytes = []byte("") + circularBytes = []byte("") + circularShortBytes = []byte("") + invalidAngleBytes = []byte("") + openBracketBytes = []byte("[") + closeBracketBytes = []byte("]") + percentBytes = []byte("%") + precisionBytes = []byte(".") + openAngleBytes = []byte("<") + closeAngleBytes = []byte(">") + openMapBytes = []byte("map[") + closeMapBytes = []byte("]") + lenEqualsBytes = []byte("len=") + capEqualsBytes = []byte("cap=") +) + +// hexDigits is used to map a decimal value to a hex digit. +var hexDigits = "0123456789abcdef" + +// catchPanic handles any panics that might occur during the handleMethods +// calls. +func catchPanic(w io.Writer, v reflect.Value) { + if err := recover(); err != nil { + w.Write(panicBytes) + fmt.Fprintf(w, "%v", err) + w.Write(closeParenBytes) + } +} + +// handleMethods attempts to call the Error and String methods on the underlying +// type the passed reflect.Value represents and outputes the result to Writer w. +// +// It handles panics in any called methods by catching and displaying the error +// as the formatted value. +func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) { + // We need an interface to check if the type implements the error or + // Stringer interface. However, the reflect package won't give us an + // interface on certain things like unexported struct fields in order + // to enforce visibility rules. We use unsafe, when it's available, + // to bypass these restrictions since this package does not mutate the + // values. + if !v.CanInterface() { + if UnsafeDisabled { + return false + } + + v = unsafeReflectValue(v) + } + + // Choose whether or not to do error and Stringer interface lookups against + // the base type or a pointer to the base type depending on settings. + // Technically calling one of these methods with a pointer receiver can + // mutate the value, however, types which choose to satisify an error or + // Stringer interface with a pointer receiver should not be mutating their + // state inside these interface methods. + if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() { + v = unsafeReflectValue(v) + } + if v.CanAddr() { + v = v.Addr() + } + + // Is it an error or Stringer? + switch iface := v.Interface().(type) { + case error: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.Error())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + + w.Write([]byte(iface.Error())) + return true + + case fmt.Stringer: + defer catchPanic(w, v) + if cs.ContinueOnMethod { + w.Write(openParenBytes) + w.Write([]byte(iface.String())) + w.Write(closeParenBytes) + w.Write(spaceBytes) + return false + } + w.Write([]byte(iface.String())) + return true + } + return false +} + +// printBool outputs a boolean value as true or false to Writer w. +func printBool(w io.Writer, val bool) { + if val { + w.Write(trueBytes) + } else { + w.Write(falseBytes) + } +} + +// printInt outputs a signed integer value to Writer w. +func printInt(w io.Writer, val int64, base int) { + w.Write([]byte(strconv.FormatInt(val, base))) +} + +// printUint outputs an unsigned integer value to Writer w. +func printUint(w io.Writer, val uint64, base int) { + w.Write([]byte(strconv.FormatUint(val, base))) +} + +// printFloat outputs a floating point value using the specified precision, +// which is expected to be 32 or 64bit, to Writer w. +func printFloat(w io.Writer, val float64, precision int) { + w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision))) +} + +// printComplex outputs a complex value using the specified float precision +// for the real and imaginary parts to Writer w. +func printComplex(w io.Writer, c complex128, floatPrecision int) { + r := real(c) + w.Write(openParenBytes) + w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision))) + i := imag(c) + if i >= 0 { + w.Write(plusBytes) + } + w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision))) + w.Write(iBytes) + w.Write(closeParenBytes) +} + +// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' +// prefix to Writer w. +func printHexPtr(w io.Writer, p uintptr) { + // Null pointer. + num := uint64(p) + if num == 0 { + w.Write(nilAngleBytes) + return + } + + // Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix + buf := make([]byte, 18) + + // It's simpler to construct the hex string right to left. + base := uint64(16) + i := len(buf) - 1 + for num >= base { + buf[i] = hexDigits[num%base] + num /= base + i-- + } + buf[i] = hexDigits[num] + + // Add '0x' prefix. + i-- + buf[i] = 'x' + i-- + buf[i] = '0' + + // Strip unused leading bytes. + buf = buf[i:] + w.Write(buf) +} + +// valuesSorter implements sort.Interface to allow a slice of reflect.Value +// elements to be sorted. +type valuesSorter struct { + values []reflect.Value + strings []string // either nil or same len and values + cs *ConfigState +} + +// newValuesSorter initializes a valuesSorter instance, which holds a set of +// surrogate keys on which the data should be sorted. It uses flags in +// ConfigState to decide if and how to populate those surrogate keys. +func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface { + vs := &valuesSorter{values: values, cs: cs} + if canSortSimply(vs.values[0].Kind()) { + return vs + } + if !cs.DisableMethods { + vs.strings = make([]string, len(values)) + for i := range vs.values { + b := bytes.Buffer{} + if !handleMethods(cs, &b, vs.values[i]) { + vs.strings = nil + break + } + vs.strings[i] = b.String() + } + } + if vs.strings == nil && cs.SpewKeys { + vs.strings = make([]string, len(values)) + for i := range vs.values { + vs.strings[i] = Sprintf("%#v", vs.values[i].Interface()) + } + } + return vs +} + +// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted +// directly, or whether it should be considered for sorting by surrogate keys +// (if the ConfigState allows it). +func canSortSimply(kind reflect.Kind) bool { + // This switch parallels valueSortLess, except for the default case. + switch kind { + case reflect.Bool: + return true + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return true + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return true + case reflect.Float32, reflect.Float64: + return true + case reflect.String: + return true + case reflect.Uintptr: + return true + case reflect.Array: + return true + } + return false +} + +// Len returns the number of values in the slice. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Len() int { + return len(s.values) +} + +// Swap swaps the values at the passed indices. It is part of the +// sort.Interface implementation. +func (s *valuesSorter) Swap(i, j int) { + s.values[i], s.values[j] = s.values[j], s.values[i] + if s.strings != nil { + s.strings[i], s.strings[j] = s.strings[j], s.strings[i] + } +} + +// valueSortLess returns whether the first value should sort before the second +// value. It is used by valueSorter.Less as part of the sort.Interface +// implementation. +func valueSortLess(a, b reflect.Value) bool { + switch a.Kind() { + case reflect.Bool: + return !a.Bool() && b.Bool() + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return a.Int() < b.Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + return a.Uint() < b.Uint() + case reflect.Float32, reflect.Float64: + return a.Float() < b.Float() + case reflect.String: + return a.String() < b.String() + case reflect.Uintptr: + return a.Uint() < b.Uint() + case reflect.Array: + // Compare the contents of both arrays. + l := a.Len() + for i := 0; i < l; i++ { + av := a.Index(i) + bv := b.Index(i) + if av.Interface() == bv.Interface() { + continue + } + return valueSortLess(av, bv) + } + } + return a.String() < b.String() +} + +// Less returns whether the value at index i should sort before the +// value at index j. It is part of the sort.Interface implementation. +func (s *valuesSorter) Less(i, j int) bool { + if s.strings == nil { + return valueSortLess(s.values[i], s.values[j]) + } + return s.strings[i] < s.strings[j] +} + +// sortValues is a sort function that handles both native types and any type that +// can be converted to error or Stringer. Other inputs are sorted according to +// their Value.String() value to ensure display stability. +func sortValues(values []reflect.Value, cs *ConfigState) { + if len(values) == 0 { + return + } + sort.Sort(newValuesSorter(values, cs)) +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/common_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/common_test.go new file mode 100644 index 00000000000..39b7525b3d1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/common_test.go @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew_test + +import ( + "fmt" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +// custom type to test Stinger interface on non-pointer receiver. +type stringer string + +// String implements the Stringer interface for testing invocation of custom +// stringers on types with non-pointer receivers. +func (s stringer) String() string { + return "stringer " + string(s) +} + +// custom type to test Stinger interface on pointer receiver. +type pstringer string + +// String implements the Stringer interface for testing invocation of custom +// stringers on types with only pointer receivers. +func (s *pstringer) String() string { + return "stringer " + string(*s) +} + +// xref1 and xref2 are cross referencing structs for testing circular reference +// detection. +type xref1 struct { + ps2 *xref2 +} +type xref2 struct { + ps1 *xref1 +} + +// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular +// reference for testing detection. +type indirCir1 struct { + ps2 *indirCir2 +} +type indirCir2 struct { + ps3 *indirCir3 +} +type indirCir3 struct { + ps1 *indirCir1 +} + +// embed is used to test embedded structures. +type embed struct { + a string +} + +// embedwrap is used to test embedded structures. +type embedwrap struct { + *embed + e *embed +} + +// panicer is used to intentionally cause a panic for testing spew properly +// handles them +type panicer int + +func (p panicer) String() string { + panic("test panic") +} + +// customError is used to test custom error interface invocation. +type customError int + +func (e customError) Error() string { + return fmt.Sprintf("error: %d", int(e)) +} + +// stringizeWants converts a slice of wanted test output into a format suitable +// for a test error message. +func stringizeWants(wants []string) string { + s := "" + for i, want := range wants { + if i > 0 { + s += fmt.Sprintf("want%d: %s", i+1, want) + } else { + s += "want: " + want + } + } + return s +} + +// testFailed returns whether or not a test failed by checking if the result +// of the test is in the slice of wanted strings. +func testFailed(result string, wants []string) bool { + for _, want := range wants { + if result == want { + return false + } + } + return true +} + +type sortableStruct struct { + x int +} + +func (ss sortableStruct) String() string { + return fmt.Sprintf("ss.%d", ss.x) +} + +type unsortableStruct struct { + x int +} + +type sortTestCase struct { + input []reflect.Value + expected []reflect.Value +} + +func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) { + getInterfaces := func(values []reflect.Value) []interface{} { + interfaces := []interface{}{} + for _, v := range values { + interfaces = append(interfaces, v.Interface()) + } + return interfaces + } + + for _, test := range tests { + spew.SortValues(test.input, cs) + // reflect.DeepEqual cannot really make sense of reflect.Value, + // probably because of all the pointer tricks. For instance, + // v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{} + // instead. + input := getInterfaces(test.input) + expected := getInterfaces(test.expected) + if !reflect.DeepEqual(input, expected) { + t.Errorf("Sort mismatch:\n %v != %v", input, expected) + } + } +} + +// TestSortValues ensures the sort functionality for relect.Value based sorting +// works as intended. +func TestSortValues(t *testing.T) { + v := reflect.ValueOf + + a := v("a") + b := v("b") + c := v("c") + embedA := v(embed{"a"}) + embedB := v(embed{"b"}) + embedC := v(embed{"c"}) + tests := []sortTestCase{ + // No values. + { + []reflect.Value{}, + []reflect.Value{}, + }, + // Bools. + { + []reflect.Value{v(false), v(true), v(false)}, + []reflect.Value{v(false), v(false), v(true)}, + }, + // Ints. + { + []reflect.Value{v(2), v(1), v(3)}, + []reflect.Value{v(1), v(2), v(3)}, + }, + // Uints. + { + []reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))}, + []reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))}, + }, + // Floats. + { + []reflect.Value{v(2.0), v(1.0), v(3.0)}, + []reflect.Value{v(1.0), v(2.0), v(3.0)}, + }, + // Strings. + { + []reflect.Value{b, a, c}, + []reflect.Value{a, b, c}, + }, + // Array + { + []reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})}, + []reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})}, + }, + // Uintptrs. + { + []reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))}, + []reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))}, + }, + // SortableStructs. + { + // Note: not sorted - DisableMethods is set. + []reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, + []reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, + }, + // UnsortableStructs. + { + // Note: not sorted - SpewKeys is false. + []reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, + []reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, + }, + // Invalid. + { + []reflect.Value{embedB, embedA, embedC}, + []reflect.Value{embedB, embedA, embedC}, + }, + } + cs := spew.ConfigState{DisableMethods: true, SpewKeys: false} + helpTestSortValues(tests, &cs, t) +} + +// TestSortValuesWithMethods ensures the sort functionality for relect.Value +// based sorting works as intended when using string methods. +func TestSortValuesWithMethods(t *testing.T) { + v := reflect.ValueOf + + a := v("a") + b := v("b") + c := v("c") + tests := []sortTestCase{ + // Ints. + { + []reflect.Value{v(2), v(1), v(3)}, + []reflect.Value{v(1), v(2), v(3)}, + }, + // Strings. + { + []reflect.Value{b, a, c}, + []reflect.Value{a, b, c}, + }, + // SortableStructs. + { + []reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, + []reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})}, + }, + // UnsortableStructs. + { + // Note: not sorted - SpewKeys is false. + []reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, + []reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, + }, + } + cs := spew.ConfigState{DisableMethods: false, SpewKeys: false} + helpTestSortValues(tests, &cs, t) +} + +// TestSortValuesWithSpew ensures the sort functionality for relect.Value +// based sorting works as intended when using spew to stringify keys. +func TestSortValuesWithSpew(t *testing.T) { + v := reflect.ValueOf + + a := v("a") + b := v("b") + c := v("c") + tests := []sortTestCase{ + // Ints. + { + []reflect.Value{v(2), v(1), v(3)}, + []reflect.Value{v(1), v(2), v(3)}, + }, + // Strings. + { + []reflect.Value{b, a, c}, + []reflect.Value{a, b, c}, + }, + // SortableStructs. + { + []reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})}, + []reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})}, + }, + // UnsortableStructs. + { + []reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})}, + []reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})}, + }, + } + cs := spew.ConfigState{DisableMethods: true, SpewKeys: true} + helpTestSortValues(tests, &cs, t) +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/config.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/config.go new file mode 100644 index 00000000000..ee1ab07b3fd --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/config.go @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "io" + "os" +) + +// ConfigState houses the configuration options used by spew to format and +// display values. There is a global instance, Config, that is used to control +// all top-level Formatter and Dump functionality. Each ConfigState instance +// provides methods equivalent to the top-level functions. +// +// The zero value for ConfigState provides no indentation. You would typically +// want to set it to a space or a tab. +// +// Alternatively, you can use NewDefaultConfig to get a ConfigState instance +// with default settings. See the documentation of NewDefaultConfig for default +// values. +type ConfigState struct { + // Indent specifies the string to use for each indentation level. The + // global config instance that all top-level functions use set this to a + // single space by default. If you would like more indentation, you might + // set this to a tab with "\t" or perhaps two spaces with " ". + Indent string + + // MaxDepth controls the maximum number of levels to descend into nested + // data structures. The default, 0, means there is no limit. + // + // NOTE: Circular data structures are properly detected, so it is not + // necessary to set this value unless you specifically want to limit deeply + // nested data structures. + MaxDepth int + + // DisableMethods specifies whether or not error and Stringer interfaces are + // invoked for types that implement them. + DisableMethods bool + + // DisablePointerMethods specifies whether or not to check for and invoke + // error and Stringer interfaces on types which only accept a pointer + // receiver when the current type is not a pointer. + // + // NOTE: This might be an unsafe action since calling one of these methods + // with a pointer receiver could technically mutate the value, however, + // in practice, types which choose to satisify an error or Stringer + // interface with a pointer receiver should not be mutating their state + // inside these interface methods. As a result, this option relies on + // access to the unsafe package, so it will not have any effect when + // running in environments without access to the unsafe package such as + // Google App Engine or with the "disableunsafe" build tag specified. + DisablePointerMethods bool + + // ContinueOnMethod specifies whether or not recursion should continue once + // a custom error or Stringer interface is invoked. The default, false, + // means it will print the results of invoking the custom error or Stringer + // interface and return immediately instead of continuing to recurse into + // the internals of the data type. + // + // NOTE: This flag does not have any effect if method invocation is disabled + // via the DisableMethods or DisablePointerMethods options. + ContinueOnMethod bool + + // SortKeys specifies map keys should be sorted before being printed. Use + // this to have a more deterministic, diffable output. Note that only + // native types (bool, int, uint, floats, uintptr and string) and types + // that support the error or Stringer interfaces (if methods are + // enabled) are supported, with other types sorted according to the + // reflect.Value.String() output which guarantees display stability. + SortKeys bool + + // SpewKeys specifies that, as a last resort attempt, map keys should + // be spewed to strings and sorted by those strings. This is only + // considered if SortKeys is true. + SpewKeys bool +} + +// Config is the active configuration of the top-level functions. +// The configuration can be changed by modifying the contents of spew.Config. +var Config = ConfigState{Indent: " "} + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the formatted string as a value that satisfies error. See NewFormatter +// for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, c.convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, c.convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, c.convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a Formatter interface returned by c.NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, c.convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Print(a ...interface{}) (n int, err error) { + return fmt.Print(c.convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, c.convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Println(a ...interface{}) (n int, err error) { + return fmt.Println(c.convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprint(a ...interface{}) string { + return fmt.Sprint(c.convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a Formatter interface returned by c.NewFormatter. It returns +// the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, c.convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a Formatter interface returned by c.NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b)) +func (c *ConfigState) Sprintln(a ...interface{}) string { + return fmt.Sprintln(c.convertArgs(a)...) +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +c.Printf, c.Println, or c.Printf. +*/ +func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(c, v) +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) { + fdump(c, w, a...) +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by modifying the public members +of c. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func (c *ConfigState) Dump(a ...interface{}) { + fdump(c, os.Stdout, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func (c *ConfigState) Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(c, &buf, a...) + return buf.String() +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a spew Formatter interface using +// the ConfigState associated with s. +func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = newFormatter(c, arg) + } + return formatters +} + +// NewDefaultConfig returns a ConfigState with the following default settings. +// +// Indent: " " +// MaxDepth: 0 +// DisableMethods: false +// DisablePointerMethods: false +// ContinueOnMethod: false +// SortKeys: false +func NewDefaultConfig() *ConfigState { + return &ConfigState{Indent: " "} +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/doc.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/doc.go new file mode 100644 index 00000000000..5be0c406090 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/doc.go @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package spew implements a deep pretty printer for Go data structures to aid in +debugging. + +A quick overview of the additional features spew provides over the built-in +printing facilities for Go data types are as follows: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output (only when using + Dump style) + +There are two different approaches spew allows for dumping Go data structures: + + * Dump style which prints with newlines, customizable indentation, + and additional debug information such as types and all pointer addresses + used to indirect to the final value + * A custom Formatter interface that integrates cleanly with the standard fmt + package and replaces %v, %+v, %#v, and %#+v to provide inline printing + similar to the default %v while providing the additional functionality + outlined above and passing unsupported format verbs such as %x and %q + along to fmt + +Quick Start + +This section demonstrates how to quickly get started with spew. See the +sections below for further details on formatting and configuration options. + +To dump a variable with full newlines, indentation, type, and pointer +information use Dump, Fdump, or Sdump: + spew.Dump(myVar1, myVar2, ...) + spew.Fdump(someWriter, myVar1, myVar2, ...) + str := spew.Sdump(myVar1, myVar2, ...) + +Alternatively, if you would prefer to use format strings with a compacted inline +printing style, use the convenience wrappers Printf, Fprintf, etc with +%v (most compact), %+v (adds pointer addresses), %#v (adds types), or +%#+v (adds types and pointer addresses): + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +Configuration Options + +Configuration of spew is handled by fields in the ConfigState type. For +convenience, all of the top-level functions use a global state available +via the spew.Config global. + +It is also possible to create a ConfigState instance that provides methods +equivalent to the top-level functions. This allows concurrent configuration +options. See the ConfigState documentation for more details. + +The following configuration options are available: + * Indent + String to use for each indentation level for Dump functions. + It is a single space by default. A popular alternative is "\t". + + * MaxDepth + Maximum number of levels to descend into nested data structures. + There is no limit by default. + + * DisableMethods + Disables invocation of error and Stringer interface methods. + Method invocation is enabled by default. + + * DisablePointerMethods + Disables invocation of error and Stringer interface methods on types + which only accept pointer receivers from non-pointer variables. + Pointer method invocation is enabled by default. + + * ContinueOnMethod + Enables recursion into types after invoking error and Stringer interface + methods. Recursion after method invocation is disabled by default. + + * SortKeys + Specifies map keys should be sorted before being printed. Use + this to have a more deterministic, diffable output. Note that + only native types (bool, int, uint, floats, uintptr and string) + and types which implement error or Stringer interfaces are + supported with other types sorted according to the + reflect.Value.String() output which guarantees display + stability. Natural map order is used by default. + + * SpewKeys + Specifies that, as a last resort attempt, map keys should be + spewed to strings and sorted by those strings. This is only + considered if SortKeys is true. + +Dump Usage + +Simply call spew.Dump with a list of variables you want to dump: + + spew.Dump(myVar1, myVar2, ...) + +You may also call spew.Fdump if you would prefer to output to an arbitrary +io.Writer. For example, to dump to standard error: + + spew.Fdump(os.Stderr, myVar1, myVar2, ...) + +A third option is to call spew.Sdump to get the formatted output as a string: + + str := spew.Sdump(myVar1, myVar2, ...) + +Sample Dump Output + +See the Dump example for details on the setup of the types and variables being +shown here. + + (main.Foo) { + unexportedField: (*main.Bar)(0xf84002e210)({ + flag: (main.Flag) flagTwo, + data: (uintptr) + }), + ExportedField: (map[interface {}]interface {}) (len=1) { + (string) (len=3) "one": (bool) true + } + } + +Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C +command as shown. + ([]uint8) (len=32 cap=32) { + 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | + 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| + 00000020 31 32 |12| + } + +Custom Formatter + +Spew provides a custom formatter that implements the fmt.Formatter interface +so that it integrates cleanly with standard fmt package printing functions. The +formatter is useful for inline printing of smaller data types similar to the +standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Custom Formatter Usage + +The simplest way to make use of the spew custom formatter is to call one of the +convenience functions such as spew.Printf, spew.Println, or spew.Printf. The +functions have syntax you are most likely already familiar with: + + spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + spew.Println(myVar, myVar2) + spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) + spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) + +See the Index for the full list convenience functions. + +Sample Formatter Output + +Double pointer to a uint8: + %v: <**>5 + %+v: <**>(0xf8400420d0->0xf8400420c8)5 + %#v: (**uint8)5 + %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 + +Pointer to circular struct with a uint8 field and a pointer to itself: + %v: <*>{1 <*>} + %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)} + %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)} + %#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)} + +See the Printf example for details on the setup of variables being shown +here. + +Errors + +Since it is possible for custom Stringer/error interfaces to panic, spew +detects them and handles them internally by printing the panic information +inline with the output. Since spew is intended to provide deep pretty printing +capabilities on structures, it intentionally does not return any errors. +*/ +package spew diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dump.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dump.go new file mode 100644 index 00000000000..36a2b6cc9b2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dump.go @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "os" + "reflect" + "regexp" + "strconv" + "strings" +) + +var ( + // uint8Type is a reflect.Type representing a uint8. It is used to + // convert cgo types to uint8 slices for hexdumping. + uint8Type = reflect.TypeOf(uint8(0)) + + // cCharRE is a regular expression that matches a cgo char. + // It is used to detect character arrays to hexdump them. + cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") + + // cUnsignedCharRE is a regular expression that matches a cgo unsigned + // char. It is used to detect unsigned character arrays to hexdump + // them. + cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") + + // cUint8tCharRE is a regular expression that matches a cgo uint8_t. + // It is used to detect uint8_t arrays to hexdump them. + cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") +) + +// dumpState contains information about the state of a dump operation. +type dumpState struct { + w io.Writer + depth int + pointers map[uintptr]int + ignoreNextType bool + ignoreNextIndent bool + cs *ConfigState +} + +// indent performs indentation according to the depth level and cs.Indent +// option. +func (d *dumpState) indent() { + if d.ignoreNextIndent { + d.ignoreNextIndent = false + return + } + d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth)) +} + +// unpackValue returns values inside of non-nil interfaces when possible. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (d *dumpState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface && !v.IsNil() { + v = v.Elem() + } + return v +} + +// dumpPtr handles formatting of pointers by indirecting them as necessary. +func (d *dumpState) dumpPtr(v reflect.Value) { + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range d.pointers { + if depth >= d.depth { + delete(d.pointers, k) + } + } + + // Keep list of all dereferenced pointers to show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by dereferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := d.pointers[addr]; ok && pd < d.depth { + cycleFound = true + indirects-- + break + } + d.pointers[addr] = d.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type information. + d.w.Write(openParenBytes) + d.w.Write(bytes.Repeat(asteriskBytes, indirects)) + d.w.Write([]byte(ve.Type().String())) + d.w.Write(closeParenBytes) + + // Display pointer information. + if len(pointerChain) > 0 { + d.w.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + d.w.Write(pointerChainBytes) + } + printHexPtr(d.w, addr) + } + d.w.Write(closeParenBytes) + } + + // Display dereferenced value. + d.w.Write(openParenBytes) + switch { + case nilFound == true: + d.w.Write(nilAngleBytes) + + case cycleFound == true: + d.w.Write(circularBytes) + + default: + d.ignoreNextType = true + d.dump(ve) + } + d.w.Write(closeParenBytes) +} + +// dumpSlice handles formatting of arrays and slices. Byte (uint8 under +// reflection) arrays and slices are dumped in hexdump -C fashion. +func (d *dumpState) dumpSlice(v reflect.Value) { + // Determine whether this type should be hex dumped or not. Also, + // for types which should be hexdumped, try to use the underlying data + // first, then fall back to trying to convert them to a uint8 slice. + var buf []uint8 + doConvert := false + doHexDump := false + numEntries := v.Len() + if numEntries > 0 { + vt := v.Index(0).Type() + vts := vt.String() + switch { + // C types that need to be converted. + case cCharRE.MatchString(vts): + fallthrough + case cUnsignedCharRE.MatchString(vts): + fallthrough + case cUint8tCharRE.MatchString(vts): + doConvert = true + + // Try to use existing uint8 slices and fall back to converting + // and copying if that fails. + case vt.Kind() == reflect.Uint8: + // TODO(davec): Fix up the disableUnsafe bits... + + // We need an addressable interface to convert the type + // to a byte slice. However, the reflect package won't + // give us an interface on certain things like + // unexported struct fields in order to enforce + // visibility rules. We use unsafe, when available, to + // bypass these restrictions since this package does not + // mutate the values. + vs := v + if !vs.CanInterface() || !vs.CanAddr() { + vs = unsafeReflectValue(vs) + } + if !UnsafeDisabled { + vs = vs.Slice(0, numEntries) + + // Use the existing uint8 slice if it can be + // type asserted. + iface := vs.Interface() + if slice, ok := iface.([]uint8); ok { + buf = slice + doHexDump = true + break + } + } + + // The underlying data needs to be converted if it can't + // be type asserted to a uint8 slice. + doConvert = true + } + + // Copy and convert the underlying type if needed. + if doConvert && vt.ConvertibleTo(uint8Type) { + // Convert and copy each element into a uint8 byte + // slice. + buf = make([]uint8, numEntries) + for i := 0; i < numEntries; i++ { + vv := v.Index(i) + buf[i] = uint8(vv.Convert(uint8Type).Uint()) + } + doHexDump = true + } + } + + // Hexdump the entire slice as needed. + if doHexDump { + indent := strings.Repeat(d.cs.Indent, d.depth) + str := indent + hex.Dump(buf) + str = strings.Replace(str, "\n", "\n"+indent, -1) + str = strings.TrimRight(str, d.cs.Indent) + d.w.Write([]byte(str)) + return + } + + // Recursively call dump for each item. + for i := 0; i < numEntries; i++ { + d.dump(d.unpackValue(v.Index(i))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } +} + +// dump is the main workhorse for dumping a value. It uses the passed reflect +// value to figure out what kind of object we are dealing with and formats it +// appropriately. It is a recursive function, however circular data structures +// are detected and handled properly. +func (d *dumpState) dump(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + d.w.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + d.indent() + d.dumpPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !d.ignoreNextType { + d.indent() + d.w.Write(openParenBytes) + d.w.Write([]byte(v.Type().String())) + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + d.ignoreNextType = false + + // Display length and capacity if the built-in len and cap functions + // work with the value's kind and the len/cap itself is non-zero. + valueLen, valueCap := 0, 0 + switch v.Kind() { + case reflect.Array, reflect.Slice, reflect.Chan: + valueLen, valueCap = v.Len(), v.Cap() + case reflect.Map, reflect.String: + valueLen = v.Len() + } + if valueLen != 0 || valueCap != 0 { + d.w.Write(openParenBytes) + if valueLen != 0 { + d.w.Write(lenEqualsBytes) + printInt(d.w, int64(valueLen), 10) + } + if valueCap != 0 { + if valueLen != 0 { + d.w.Write(spaceBytes) + } + d.w.Write(capEqualsBytes) + printInt(d.w, int64(valueCap), 10) + } + d.w.Write(closeParenBytes) + d.w.Write(spaceBytes) + } + + // Call Stringer/error interfaces if they exist and the handle methods flag + // is enabled + if !d.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(d.cs, d.w, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(d.w, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(d.w, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(d.w, v.Uint(), 10) + + case reflect.Float32: + printFloat(d.w, v.Float(), 32) + + case reflect.Float64: + printFloat(d.w, v.Float(), 64) + + case reflect.Complex64: + printComplex(d.w, v.Complex(), 32) + + case reflect.Complex128: + printComplex(d.w, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + d.dumpSlice(v) + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.String: + d.w.Write([]byte(strconv.Quote(v.String()))) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + d.w.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + d.w.Write(nilAngleBytes) + break + } + + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + numEntries := v.Len() + keys := v.MapKeys() + if d.cs.SortKeys { + sortValues(keys, d.cs) + } + for i, key := range keys { + d.dump(d.unpackValue(key)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.MapIndex(key))) + if i < (numEntries - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Struct: + d.w.Write(openBraceNewlineBytes) + d.depth++ + if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) { + d.indent() + d.w.Write(maxNewlineBytes) + } else { + vt := v.Type() + numFields := v.NumField() + for i := 0; i < numFields; i++ { + d.indent() + vtf := vt.Field(i) + d.w.Write([]byte(vtf.Name)) + d.w.Write(colonSpaceBytes) + d.ignoreNextIndent = true + d.dump(d.unpackValue(v.Field(i))) + if i < (numFields - 1) { + d.w.Write(commaNewlineBytes) + } else { + d.w.Write(newlineBytes) + } + } + } + d.depth-- + d.indent() + d.w.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(d.w, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(d.w, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it in case any new + // types are added. + default: + if v.CanInterface() { + fmt.Fprintf(d.w, "%v", v.Interface()) + } else { + fmt.Fprintf(d.w, "%v", v.String()) + } + } +} + +// fdump is a helper function to consolidate the logic from the various public +// methods which take varying writers and config states. +func fdump(cs *ConfigState, w io.Writer, a ...interface{}) { + for _, arg := range a { + if arg == nil { + w.Write(interfaceBytes) + w.Write(spaceBytes) + w.Write(nilAngleBytes) + w.Write(newlineBytes) + continue + } + + d := dumpState{w: w, cs: cs} + d.pointers = make(map[uintptr]int) + d.dump(reflect.ValueOf(arg)) + d.w.Write(newlineBytes) + } +} + +// Fdump formats and displays the passed arguments to io.Writer w. It formats +// exactly the same as Dump. +func Fdump(w io.Writer, a ...interface{}) { + fdump(&Config, w, a...) +} + +// Sdump returns a string with the passed arguments formatted exactly the same +// as Dump. +func Sdump(a ...interface{}) string { + var buf bytes.Buffer + fdump(&Config, &buf, a...) + return buf.String() +} + +/* +Dump displays the passed parameters to standard out with newlines, customizable +indentation, and additional debug information such as complete types and all +pointer addresses used to indirect to the final value. It provides the +following features over the built-in printing facilities provided by the fmt +package: + + * Pointers are dereferenced and followed + * Circular data structures are detected and handled properly + * Custom Stringer/error interfaces are optionally invoked, including + on unexported types + * Custom types which only implement the Stringer/error interfaces via + a pointer receiver are optionally invoked when passing non-pointer + variables + * Byte arrays and slices are dumped like the hexdump -C command which + includes offsets, byte values in hex, and ASCII output + +The configuration options are controlled by an exported package global, +spew.Config. See ConfigState for options documentation. + +See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to +get the formatted result as a string. +*/ +func Dump(a ...interface{}) { + fdump(&Config, os.Stdout, a...) +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dump_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dump_test.go new file mode 100644 index 00000000000..2b320401dfa --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dump_test.go @@ -0,0 +1,1042 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Test Summary: +NOTE: For each test, a nil pointer, a single pointer and double pointer to the +base test element are also tested to ensure proper indirection across all types. + +- Max int8, int16, int32, int64, int +- Max uint8, uint16, uint32, uint64, uint +- Boolean true and false +- Standard complex64 and complex128 +- Array containing standard ints +- Array containing type with custom formatter on pointer receiver only +- Array containing interfaces +- Array containing bytes +- Slice containing standard float32 values +- Slice containing type with custom formatter on pointer receiver only +- Slice containing interfaces +- Slice containing bytes +- Nil slice +- Standard string +- Nil interface +- Sub-interface +- Map with string keys and int vals +- Map with custom formatter type on pointer receiver only keys and vals +- Map with interface keys and values +- Map with nil interface value +- Struct with primitives +- Struct that contains another struct +- Struct that contains custom type with Stringer pointer interface via both + exported and unexported fields +- Struct that contains embedded struct and field to same struct +- Uintptr to 0 (null pointer) +- Uintptr address of real variable +- Unsafe.Pointer to 0 (null pointer) +- Unsafe.Pointer to address of real variable +- Nil channel +- Standard int channel +- Function with no params and no returns +- Function with param and no returns +- Function with multiple params and multiple returns +- Struct that is circular through self referencing +- Structs that are circular through cross referencing +- Structs that are indirectly circular +- Type that panics in its Stringer interface +*/ + +package spew_test + +import ( + "bytes" + "fmt" + "testing" + "unsafe" + + "github.com/davecgh/go-spew/spew" +) + +// dumpTest is used to describe a test to be perfomed against the Dump method. +type dumpTest struct { + in interface{} + wants []string +} + +// dumpTests houses all of the tests to be performed against the Dump method. +var dumpTests = make([]dumpTest, 0) + +// addDumpTest is a helper method to append the passed input and desired result +// to dumpTests +func addDumpTest(in interface{}, wants ...string) { + test := dumpTest{in, wants} + dumpTests = append(dumpTests, test) +} + +func addIntDumpTests() { + // Max int8. + v := int8(127) + nv := (*int8)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "int8" + vs := "127" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Max int16. + v2 := int16(32767) + nv2 := (*int16)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "int16" + v2s := "32767" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") + + // Max int32. + v3 := int32(2147483647) + nv3 := (*int32)(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "int32" + v3s := "2147483647" + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n") + addDumpTest(nv3, "(*"+v3t+")()\n") + + // Max int64. + v4 := int64(9223372036854775807) + nv4 := (*int64)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "int64" + v4s := "9223372036854775807" + addDumpTest(v4, "("+v4t+") "+v4s+"\n") + addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n") + addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n") + addDumpTest(nv4, "(*"+v4t+")()\n") + + // Max int. + v5 := int(2147483647) + nv5 := (*int)(nil) + pv5 := &v5 + v5Addr := fmt.Sprintf("%p", pv5) + pv5Addr := fmt.Sprintf("%p", &pv5) + v5t := "int" + v5s := "2147483647" + addDumpTest(v5, "("+v5t+") "+v5s+"\n") + addDumpTest(pv5, "(*"+v5t+")("+v5Addr+")("+v5s+")\n") + addDumpTest(&pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")("+v5s+")\n") + addDumpTest(nv5, "(*"+v5t+")()\n") +} + +func addUintDumpTests() { + // Max uint8. + v := uint8(255) + nv := (*uint8)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "uint8" + vs := "255" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Max uint16. + v2 := uint16(65535) + nv2 := (*uint16)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "uint16" + v2s := "65535" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") + + // Max uint32. + v3 := uint32(4294967295) + nv3 := (*uint32)(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "uint32" + v3s := "4294967295" + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n") + addDumpTest(nv3, "(*"+v3t+")()\n") + + // Max uint64. + v4 := uint64(18446744073709551615) + nv4 := (*uint64)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "uint64" + v4s := "18446744073709551615" + addDumpTest(v4, "("+v4t+") "+v4s+"\n") + addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n") + addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n") + addDumpTest(nv4, "(*"+v4t+")()\n") + + // Max uint. + v5 := uint(4294967295) + nv5 := (*uint)(nil) + pv5 := &v5 + v5Addr := fmt.Sprintf("%p", pv5) + pv5Addr := fmt.Sprintf("%p", &pv5) + v5t := "uint" + v5s := "4294967295" + addDumpTest(v5, "("+v5t+") "+v5s+"\n") + addDumpTest(pv5, "(*"+v5t+")("+v5Addr+")("+v5s+")\n") + addDumpTest(&pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")("+v5s+")\n") + addDumpTest(nv5, "(*"+v5t+")()\n") +} + +func addBoolDumpTests() { + // Boolean true. + v := bool(true) + nv := (*bool)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "bool" + vs := "true" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Boolean false. + v2 := bool(false) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "bool" + v2s := "false" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") +} + +func addFloatDumpTests() { + // Standard float32. + v := float32(3.1415) + nv := (*float32)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "float32" + vs := "3.1415" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Standard float64. + v2 := float64(3.1415926) + nv2 := (*float64)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "float64" + v2s := "3.1415926" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") +} + +func addComplexDumpTests() { + // Standard complex64. + v := complex(float32(6), -2) + nv := (*complex64)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "complex64" + vs := "(6-2i)" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Standard complex128. + v2 := complex(float64(-6), 2) + nv2 := (*complex128)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "complex128" + v2s := "(-6+2i)" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") +} + +func addArrayDumpTests() { + // Array containing standard ints. + v := [3]int{1, 2, 3} + vLen := fmt.Sprintf("%d", len(v)) + vCap := fmt.Sprintf("%d", cap(v)) + nv := (*[3]int)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "int" + vs := "(len=" + vLen + " cap=" + vCap + ") {\n (" + vt + ") 1,\n (" + + vt + ") 2,\n (" + vt + ") 3\n}" + addDumpTest(v, "([3]"+vt+") "+vs+"\n") + addDumpTest(pv, "(*[3]"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**[3]"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*[3]"+vt+")()\n") + + // Array containing type with custom formatter on pointer receiver only. + v2i0 := pstringer("1") + v2i1 := pstringer("2") + v2i2 := pstringer("3") + v2 := [3]pstringer{v2i0, v2i1, v2i2} + v2i0Len := fmt.Sprintf("%d", len(v2i0)) + v2i1Len := fmt.Sprintf("%d", len(v2i1)) + v2i2Len := fmt.Sprintf("%d", len(v2i2)) + v2Len := fmt.Sprintf("%d", len(v2)) + v2Cap := fmt.Sprintf("%d", cap(v2)) + nv2 := (*[3]pstringer)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "spew_test.pstringer" + v2sp := "(len=" + v2Len + " cap=" + v2Cap + ") {\n (" + v2t + + ") (len=" + v2i0Len + ") stringer 1,\n (" + v2t + + ") (len=" + v2i1Len + ") stringer 2,\n (" + v2t + + ") (len=" + v2i2Len + ") " + "stringer 3\n}" + v2s := v2sp + if spew.UnsafeDisabled { + v2s = "(len=" + v2Len + " cap=" + v2Cap + ") {\n (" + v2t + + ") (len=" + v2i0Len + ") \"1\",\n (" + v2t + ") (len=" + + v2i1Len + ") \"2\",\n (" + v2t + ") (len=" + v2i2Len + + ") " + "\"3\"\n}" + } + addDumpTest(v2, "([3]"+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*[3]"+v2t+")("+v2Addr+")("+v2sp+")\n") + addDumpTest(&pv2, "(**[3]"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2sp+")\n") + addDumpTest(nv2, "(*[3]"+v2t+")()\n") + + // Array containing interfaces. + v3i0 := "one" + v3 := [3]interface{}{v3i0, int(2), uint(3)} + v3i0Len := fmt.Sprintf("%d", len(v3i0)) + v3Len := fmt.Sprintf("%d", len(v3)) + v3Cap := fmt.Sprintf("%d", cap(v3)) + nv3 := (*[3]interface{})(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "[3]interface {}" + v3t2 := "string" + v3t3 := "int" + v3t4 := "uint" + v3s := "(len=" + v3Len + " cap=" + v3Cap + ") {\n (" + v3t2 + ") " + + "(len=" + v3i0Len + ") \"one\",\n (" + v3t3 + ") 2,\n (" + + v3t4 + ") 3\n}" + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n") + addDumpTest(nv3, "(*"+v3t+")()\n") + + // Array containing bytes. + v4 := [34]byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, + } + v4Len := fmt.Sprintf("%d", len(v4)) + v4Cap := fmt.Sprintf("%d", cap(v4)) + nv4 := (*[34]byte)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "[34]uint8" + v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " + + "{\n 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20" + + " |............... |\n" + + " 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30" + + " |!\"#$%&'()*+,-./0|\n" + + " 00000020 31 32 " + + " |12|\n}" + addDumpTest(v4, "("+v4t+") "+v4s+"\n") + addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n") + addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n") + addDumpTest(nv4, "(*"+v4t+")()\n") +} + +func addSliceDumpTests() { + // Slice containing standard float32 values. + v := []float32{3.14, 6.28, 12.56} + vLen := fmt.Sprintf("%d", len(v)) + vCap := fmt.Sprintf("%d", cap(v)) + nv := (*[]float32)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "float32" + vs := "(len=" + vLen + " cap=" + vCap + ") {\n (" + vt + ") 3.14,\n (" + + vt + ") 6.28,\n (" + vt + ") 12.56\n}" + addDumpTest(v, "([]"+vt+") "+vs+"\n") + addDumpTest(pv, "(*[]"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**[]"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*[]"+vt+")()\n") + + // Slice containing type with custom formatter on pointer receiver only. + v2i0 := pstringer("1") + v2i1 := pstringer("2") + v2i2 := pstringer("3") + v2 := []pstringer{v2i0, v2i1, v2i2} + v2i0Len := fmt.Sprintf("%d", len(v2i0)) + v2i1Len := fmt.Sprintf("%d", len(v2i1)) + v2i2Len := fmt.Sprintf("%d", len(v2i2)) + v2Len := fmt.Sprintf("%d", len(v2)) + v2Cap := fmt.Sprintf("%d", cap(v2)) + nv2 := (*[]pstringer)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "spew_test.pstringer" + v2s := "(len=" + v2Len + " cap=" + v2Cap + ") {\n (" + v2t + ") (len=" + + v2i0Len + ") stringer 1,\n (" + v2t + ") (len=" + v2i1Len + + ") stringer 2,\n (" + v2t + ") (len=" + v2i2Len + ") " + + "stringer 3\n}" + addDumpTest(v2, "([]"+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*[]"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**[]"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*[]"+v2t+")()\n") + + // Slice containing interfaces. + v3i0 := "one" + v3 := []interface{}{v3i0, int(2), uint(3), nil} + v3i0Len := fmt.Sprintf("%d", len(v3i0)) + v3Len := fmt.Sprintf("%d", len(v3)) + v3Cap := fmt.Sprintf("%d", cap(v3)) + nv3 := (*[]interface{})(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "[]interface {}" + v3t2 := "string" + v3t3 := "int" + v3t4 := "uint" + v3t5 := "interface {}" + v3s := "(len=" + v3Len + " cap=" + v3Cap + ") {\n (" + v3t2 + ") " + + "(len=" + v3i0Len + ") \"one\",\n (" + v3t3 + ") 2,\n (" + + v3t4 + ") 3,\n (" + v3t5 + ") \n}" + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n") + addDumpTest(nv3, "(*"+v3t+")()\n") + + // Slice containing bytes. + v4 := []byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, + } + v4Len := fmt.Sprintf("%d", len(v4)) + v4Cap := fmt.Sprintf("%d", cap(v4)) + nv4 := (*[]byte)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "[]uint8" + v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " + + "{\n 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20" + + " |............... |\n" + + " 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30" + + " |!\"#$%&'()*+,-./0|\n" + + " 00000020 31 32 " + + " |12|\n}" + addDumpTest(v4, "("+v4t+") "+v4s+"\n") + addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n") + addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n") + addDumpTest(nv4, "(*"+v4t+")()\n") + + // Nil slice. + v5 := []int(nil) + nv5 := (*[]int)(nil) + pv5 := &v5 + v5Addr := fmt.Sprintf("%p", pv5) + pv5Addr := fmt.Sprintf("%p", &pv5) + v5t := "[]int" + v5s := "" + addDumpTest(v5, "("+v5t+") "+v5s+"\n") + addDumpTest(pv5, "(*"+v5t+")("+v5Addr+")("+v5s+")\n") + addDumpTest(&pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")("+v5s+")\n") + addDumpTest(nv5, "(*"+v5t+")()\n") +} + +func addStringDumpTests() { + // Standard string. + v := "test" + vLen := fmt.Sprintf("%d", len(v)) + nv := (*string)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "string" + vs := "(len=" + vLen + ") \"test\"" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") +} + +func addInterfaceDumpTests() { + // Nil interface. + var v interface{} + nv := (*interface{})(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "interface {}" + vs := "" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Sub-interface. + v2 := interface{}(uint16(65535)) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "uint16" + v2s := "65535" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") +} + +func addMapDumpTests() { + // Map with string keys and int vals. + k := "one" + kk := "two" + m := map[string]int{k: 1, kk: 2} + klen := fmt.Sprintf("%d", len(k)) // not kLen to shut golint up + kkLen := fmt.Sprintf("%d", len(kk)) + mLen := fmt.Sprintf("%d", len(m)) + nilMap := map[string]int(nil) + nm := (*map[string]int)(nil) + pm := &m + mAddr := fmt.Sprintf("%p", pm) + pmAddr := fmt.Sprintf("%p", &pm) + mt := "map[string]int" + mt1 := "string" + mt2 := "int" + ms := "(len=" + mLen + ") {\n (" + mt1 + ") (len=" + klen + ") " + + "\"one\": (" + mt2 + ") 1,\n (" + mt1 + ") (len=" + kkLen + + ") \"two\": (" + mt2 + ") 2\n}" + ms2 := "(len=" + mLen + ") {\n (" + mt1 + ") (len=" + kkLen + ") " + + "\"two\": (" + mt2 + ") 2,\n (" + mt1 + ") (len=" + klen + + ") \"one\": (" + mt2 + ") 1\n}" + addDumpTest(m, "("+mt+") "+ms+"\n", "("+mt+") "+ms2+"\n") + addDumpTest(pm, "(*"+mt+")("+mAddr+")("+ms+")\n", + "(*"+mt+")("+mAddr+")("+ms2+")\n") + addDumpTest(&pm, "(**"+mt+")("+pmAddr+"->"+mAddr+")("+ms+")\n", + "(**"+mt+")("+pmAddr+"->"+mAddr+")("+ms2+")\n") + addDumpTest(nm, "(*"+mt+")()\n") + addDumpTest(nilMap, "("+mt+") \n") + + // Map with custom formatter type on pointer receiver only keys and vals. + k2 := pstringer("one") + v2 := pstringer("1") + m2 := map[pstringer]pstringer{k2: v2} + k2Len := fmt.Sprintf("%d", len(k2)) + v2Len := fmt.Sprintf("%d", len(v2)) + m2Len := fmt.Sprintf("%d", len(m2)) + nilMap2 := map[pstringer]pstringer(nil) + nm2 := (*map[pstringer]pstringer)(nil) + pm2 := &m2 + m2Addr := fmt.Sprintf("%p", pm2) + pm2Addr := fmt.Sprintf("%p", &pm2) + m2t := "map[spew_test.pstringer]spew_test.pstringer" + m2t1 := "spew_test.pstringer" + m2t2 := "spew_test.pstringer" + m2s := "(len=" + m2Len + ") {\n (" + m2t1 + ") (len=" + k2Len + ") " + + "stringer one: (" + m2t2 + ") (len=" + v2Len + ") stringer 1\n}" + if spew.UnsafeDisabled { + m2s = "(len=" + m2Len + ") {\n (" + m2t1 + ") (len=" + k2Len + + ") " + "\"one\": (" + m2t2 + ") (len=" + v2Len + + ") \"1\"\n}" + } + addDumpTest(m2, "("+m2t+") "+m2s+"\n") + addDumpTest(pm2, "(*"+m2t+")("+m2Addr+")("+m2s+")\n") + addDumpTest(&pm2, "(**"+m2t+")("+pm2Addr+"->"+m2Addr+")("+m2s+")\n") + addDumpTest(nm2, "(*"+m2t+")()\n") + addDumpTest(nilMap2, "("+m2t+") \n") + + // Map with interface keys and values. + k3 := "one" + k3Len := fmt.Sprintf("%d", len(k3)) + m3 := map[interface{}]interface{}{k3: 1} + m3Len := fmt.Sprintf("%d", len(m3)) + nilMap3 := map[interface{}]interface{}(nil) + nm3 := (*map[interface{}]interface{})(nil) + pm3 := &m3 + m3Addr := fmt.Sprintf("%p", pm3) + pm3Addr := fmt.Sprintf("%p", &pm3) + m3t := "map[interface {}]interface {}" + m3t1 := "string" + m3t2 := "int" + m3s := "(len=" + m3Len + ") {\n (" + m3t1 + ") (len=" + k3Len + ") " + + "\"one\": (" + m3t2 + ") 1\n}" + addDumpTest(m3, "("+m3t+") "+m3s+"\n") + addDumpTest(pm3, "(*"+m3t+")("+m3Addr+")("+m3s+")\n") + addDumpTest(&pm3, "(**"+m3t+")("+pm3Addr+"->"+m3Addr+")("+m3s+")\n") + addDumpTest(nm3, "(*"+m3t+")()\n") + addDumpTest(nilMap3, "("+m3t+") \n") + + // Map with nil interface value. + k4 := "nil" + k4Len := fmt.Sprintf("%d", len(k4)) + m4 := map[string]interface{}{k4: nil} + m4Len := fmt.Sprintf("%d", len(m4)) + nilMap4 := map[string]interface{}(nil) + nm4 := (*map[string]interface{})(nil) + pm4 := &m4 + m4Addr := fmt.Sprintf("%p", pm4) + pm4Addr := fmt.Sprintf("%p", &pm4) + m4t := "map[string]interface {}" + m4t1 := "string" + m4t2 := "interface {}" + m4s := "(len=" + m4Len + ") {\n (" + m4t1 + ") (len=" + k4Len + ")" + + " \"nil\": (" + m4t2 + ") \n}" + addDumpTest(m4, "("+m4t+") "+m4s+"\n") + addDumpTest(pm4, "(*"+m4t+")("+m4Addr+")("+m4s+")\n") + addDumpTest(&pm4, "(**"+m4t+")("+pm4Addr+"->"+m4Addr+")("+m4s+")\n") + addDumpTest(nm4, "(*"+m4t+")()\n") + addDumpTest(nilMap4, "("+m4t+") \n") +} + +func addStructDumpTests() { + // Struct with primitives. + type s1 struct { + a int8 + b uint8 + } + v := s1{127, 255} + nv := (*s1)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.s1" + vt2 := "int8" + vt3 := "uint8" + vs := "{\n a: (" + vt2 + ") 127,\n b: (" + vt3 + ") 255\n}" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Struct that contains another struct. + type s2 struct { + s1 s1 + b bool + } + v2 := s2{s1{127, 255}, true} + nv2 := (*s2)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "spew_test.s2" + v2t2 := "spew_test.s1" + v2t3 := "int8" + v2t4 := "uint8" + v2t5 := "bool" + v2s := "{\n s1: (" + v2t2 + ") {\n a: (" + v2t3 + ") 127,\n b: (" + + v2t4 + ") 255\n },\n b: (" + v2t5 + ") true\n}" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") + + // Struct that contains custom type with Stringer pointer interface via both + // exported and unexported fields. + type s3 struct { + s pstringer + S pstringer + } + v3 := s3{"test", "test2"} + nv3 := (*s3)(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "spew_test.s3" + v3t2 := "spew_test.pstringer" + v3s := "{\n s: (" + v3t2 + ") (len=4) stringer test,\n S: (" + v3t2 + + ") (len=5) stringer test2\n}" + v3sp := v3s + if spew.UnsafeDisabled { + v3s = "{\n s: (" + v3t2 + ") (len=4) \"test\",\n S: (" + + v3t2 + ") (len=5) \"test2\"\n}" + v3sp = "{\n s: (" + v3t2 + ") (len=4) \"test\",\n S: (" + + v3t2 + ") (len=5) stringer test2\n}" + } + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3sp+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3sp+")\n") + addDumpTest(nv3, "(*"+v3t+")()\n") + + // Struct that contains embedded struct and field to same struct. + e := embed{"embedstr"} + eLen := fmt.Sprintf("%d", len("embedstr")) + v4 := embedwrap{embed: &e, e: &e} + nv4 := (*embedwrap)(nil) + pv4 := &v4 + eAddr := fmt.Sprintf("%p", &e) + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "spew_test.embedwrap" + v4t2 := "spew_test.embed" + v4t3 := "string" + v4s := "{\n embed: (*" + v4t2 + ")(" + eAddr + ")({\n a: (" + v4t3 + + ") (len=" + eLen + ") \"embedstr\"\n }),\n e: (*" + v4t2 + + ")(" + eAddr + ")({\n a: (" + v4t3 + ") (len=" + eLen + ")" + + " \"embedstr\"\n })\n}" + addDumpTest(v4, "("+v4t+") "+v4s+"\n") + addDumpTest(pv4, "(*"+v4t+")("+v4Addr+")("+v4s+")\n") + addDumpTest(&pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")("+v4s+")\n") + addDumpTest(nv4, "(*"+v4t+")()\n") +} + +func addUintptrDumpTests() { + // Null pointer. + v := uintptr(0) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "uintptr" + vs := "" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + + // Address of real variable. + i := 1 + v2 := uintptr(unsafe.Pointer(&i)) + nv2 := (*uintptr)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "uintptr" + v2s := fmt.Sprintf("%p", &i) + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") +} + +func addUnsafePointerDumpTests() { + // Null pointer. + v := unsafe.Pointer(uintptr(0)) + nv := (*unsafe.Pointer)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "unsafe.Pointer" + vs := "" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Address of real variable. + i := 1 + v2 := unsafe.Pointer(&i) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "unsafe.Pointer" + v2s := fmt.Sprintf("%p", &i) + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv, "(*"+vt+")()\n") +} + +func addChanDumpTests() { + // Nil channel. + var v chan int + pv := &v + nv := (*chan int)(nil) + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "chan int" + vs := "" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Real channel. + v2 := make(chan int) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "chan int" + v2s := fmt.Sprintf("%p", v2) + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") +} + +func addFuncDumpTests() { + // Function with no params and no returns. + v := addIntDumpTests + nv := (*func())(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "func()" + vs := fmt.Sprintf("%p", v) + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") + + // Function with param and no returns. + v2 := TestDump + nv2 := (*func(*testing.T))(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "func(*testing.T)" + v2s := fmt.Sprintf("%p", v2) + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s+")\n") + addDumpTest(nv2, "(*"+v2t+")()\n") + + // Function with multiple params and multiple returns. + var v3 = func(i int, s string) (b bool, err error) { + return true, nil + } + nv3 := (*func(int, string) (bool, error))(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "func(int, string) (bool, error)" + v3s := fmt.Sprintf("%p", v3) + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s+")\n") + addDumpTest(nv3, "(*"+v3t+")()\n") +} + +func addCircularDumpTests() { + // Struct that is circular through self referencing. + type circular struct { + c *circular + } + v := circular{nil} + v.c = &v + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.circular" + vs := "{\n c: (*" + vt + ")(" + vAddr + ")({\n c: (*" + vt + ")(" + + vAddr + ")()\n })\n}" + vs2 := "{\n c: (*" + vt + ")(" + vAddr + ")()\n}" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs2+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs2+")\n") + + // Structs that are circular through cross referencing. + v2 := xref1{nil} + ts2 := xref2{&v2} + v2.ps2 = &ts2 + pv2 := &v2 + ts2Addr := fmt.Sprintf("%p", &ts2) + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "spew_test.xref1" + v2t2 := "spew_test.xref2" + v2s := "{\n ps2: (*" + v2t2 + ")(" + ts2Addr + ")({\n ps1: (*" + v2t + + ")(" + v2Addr + ")({\n ps2: (*" + v2t2 + ")(" + ts2Addr + + ")()\n })\n })\n}" + v2s2 := "{\n ps2: (*" + v2t2 + ")(" + ts2Addr + ")({\n ps1: (*" + v2t + + ")(" + v2Addr + ")()\n })\n}" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + addDumpTest(pv2, "(*"+v2t+")("+v2Addr+")("+v2s2+")\n") + addDumpTest(&pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")("+v2s2+")\n") + + // Structs that are indirectly circular. + v3 := indirCir1{nil} + tic2 := indirCir2{nil} + tic3 := indirCir3{&v3} + tic2.ps3 = &tic3 + v3.ps2 = &tic2 + pv3 := &v3 + tic2Addr := fmt.Sprintf("%p", &tic2) + tic3Addr := fmt.Sprintf("%p", &tic3) + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "spew_test.indirCir1" + v3t2 := "spew_test.indirCir2" + v3t3 := "spew_test.indirCir3" + v3s := "{\n ps2: (*" + v3t2 + ")(" + tic2Addr + ")({\n ps3: (*" + v3t3 + + ")(" + tic3Addr + ")({\n ps1: (*" + v3t + ")(" + v3Addr + + ")({\n ps2: (*" + v3t2 + ")(" + tic2Addr + + ")()\n })\n })\n })\n}" + v3s2 := "{\n ps2: (*" + v3t2 + ")(" + tic2Addr + ")({\n ps3: (*" + v3t3 + + ")(" + tic3Addr + ")({\n ps1: (*" + v3t + ")(" + v3Addr + + ")()\n })\n })\n}" + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + addDumpTest(pv3, "(*"+v3t+")("+v3Addr+")("+v3s2+")\n") + addDumpTest(&pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")("+v3s2+")\n") +} + +func addPanicDumpTests() { + // Type that panics in its Stringer interface. + v := panicer(127) + nv := (*panicer)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.panicer" + vs := "(PANIC=test panic)127" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") +} + +func addErrorDumpTests() { + // Type that has a custom Error interface. + v := customError(127) + nv := (*customError)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.customError" + vs := "error: 127" + addDumpTest(v, "("+vt+") "+vs+"\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")("+vs+")\n") + addDumpTest(nv, "(*"+vt+")()\n") +} + +// TestDump executes all of the tests described by dumpTests. +func TestDump(t *testing.T) { + // Setup tests. + addIntDumpTests() + addUintDumpTests() + addBoolDumpTests() + addFloatDumpTests() + addComplexDumpTests() + addArrayDumpTests() + addSliceDumpTests() + addStringDumpTests() + addInterfaceDumpTests() + addMapDumpTests() + addStructDumpTests() + addUintptrDumpTests() + addUnsafePointerDumpTests() + addChanDumpTests() + addFuncDumpTests() + addCircularDumpTests() + addPanicDumpTests() + addErrorDumpTests() + addCgoDumpTests() + + t.Logf("Running %d tests", len(dumpTests)) + for i, test := range dumpTests { + buf := new(bytes.Buffer) + spew.Fdump(buf, test.in) + s := buf.String() + if testFailed(s, test.wants) { + t.Errorf("Dump #%d\n got: %s %s", i, s, stringizeWants(test.wants)) + continue + } + } +} + +func TestDumpSortedKeys(t *testing.T) { + cfg := spew.ConfigState{SortKeys: true} + s := cfg.Sdump(map[int]string{1: "1", 3: "3", 2: "2"}) + expected := "(map[int]string) (len=3) {\n(int) 1: (string) (len=1) " + + "\"1\",\n(int) 2: (string) (len=1) \"2\",\n(int) 3: (string) " + + "(len=1) \"3\"\n" + + "}\n" + if s != expected { + t.Errorf("Sorted keys mismatch:\n %v %v", s, expected) + } + + s = cfg.Sdump(map[stringer]int{"1": 1, "3": 3, "2": 2}) + expected = "(map[spew_test.stringer]int) (len=3) {\n" + + "(spew_test.stringer) (len=1) stringer 1: (int) 1,\n" + + "(spew_test.stringer) (len=1) stringer 2: (int) 2,\n" + + "(spew_test.stringer) (len=1) stringer 3: (int) 3\n" + + "}\n" + if s != expected { + t.Errorf("Sorted keys mismatch:\n %v %v", s, expected) + } + + s = cfg.Sdump(map[pstringer]int{pstringer("1"): 1, pstringer("3"): 3, pstringer("2"): 2}) + expected = "(map[spew_test.pstringer]int) (len=3) {\n" + + "(spew_test.pstringer) (len=1) stringer 1: (int) 1,\n" + + "(spew_test.pstringer) (len=1) stringer 2: (int) 2,\n" + + "(spew_test.pstringer) (len=1) stringer 3: (int) 3\n" + + "}\n" + if spew.UnsafeDisabled { + expected = "(map[spew_test.pstringer]int) (len=3) {\n" + + "(spew_test.pstringer) (len=1) \"1\": (int) 1,\n" + + "(spew_test.pstringer) (len=1) \"2\": (int) 2,\n" + + "(spew_test.pstringer) (len=1) \"3\": (int) 3\n" + + "}\n" + } + if s != expected { + t.Errorf("Sorted keys mismatch:\n %v %v", s, expected) + } + + s = cfg.Sdump(map[customError]int{customError(1): 1, customError(3): 3, customError(2): 2}) + expected = "(map[spew_test.customError]int) (len=3) {\n" + + "(spew_test.customError) error: 1: (int) 1,\n" + + "(spew_test.customError) error: 2: (int) 2,\n" + + "(spew_test.customError) error: 3: (int) 3\n" + + "}\n" + if s != expected { + t.Errorf("Sorted keys mismatch:\n %v %v", s, expected) + } + +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dumpcgo_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dumpcgo_test.go new file mode 100644 index 00000000000..18a38358e05 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dumpcgo_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2013 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when both cgo is supported and "-tags testcgo" is added to the go test +// command line. This means the cgo tests are only added (and hence run) when +// specifially requested. This configuration is used because spew itself +// does not require cgo to run even though it does handle certain cgo types +// specially. Rather than forcing all clients to require cgo and an external +// C compiler just to run the tests, this scheme makes them optional. +// +build cgo,testcgo + +package spew_test + +import ( + "fmt" + + "github.com/davecgh/go-spew/spew/testdata" +) + +func addCgoDumpTests() { + // C char pointer. + v := testdata.GetCgoCharPointer() + nv := testdata.GetCgoNullCharPointer() + pv := &v + vcAddr := fmt.Sprintf("%p", v) + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "*testdata._Ctype_char" + vs := "116" + addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n") + addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n") + addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n") + addDumpTest(nv, "("+vt+")()\n") + + // C char array. + v2, v2l, v2c := testdata.GetCgoCharArray() + v2Len := fmt.Sprintf("%d", v2l) + v2Cap := fmt.Sprintf("%d", v2c) + v2t := "[6]testdata._Ctype_char" + v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " + + "{\n 00000000 74 65 73 74 32 00 " + + " |test2.|\n}" + addDumpTest(v2, "("+v2t+") "+v2s+"\n") + + // C unsigned char array. + v3, v3l, v3c := testdata.GetCgoUnsignedCharArray() + v3Len := fmt.Sprintf("%d", v3l) + v3Cap := fmt.Sprintf("%d", v3c) + v3t := "[6]testdata._Ctype_unsignedchar" + v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " + + "{\n 00000000 74 65 73 74 33 00 " + + " |test3.|\n}" + addDumpTest(v3, "("+v3t+") "+v3s+"\n") + + // C signed char array. + v4, v4l, v4c := testdata.GetCgoSignedCharArray() + v4Len := fmt.Sprintf("%d", v4l) + v4Cap := fmt.Sprintf("%d", v4c) + v4t := "[6]testdata._Ctype_schar" + v4t2 := "testdata._Ctype_schar" + v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " + + "{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 + + ") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 + + ") 0\n}" + addDumpTest(v4, "("+v4t+") "+v4s+"\n") + + // C uint8_t array. + v5, v5l, v5c := testdata.GetCgoUint8tArray() + v5Len := fmt.Sprintf("%d", v5l) + v5Cap := fmt.Sprintf("%d", v5c) + v5t := "[6]testdata._Ctype_uint8_t" + v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " + + "{\n 00000000 74 65 73 74 35 00 " + + " |test5.|\n}" + addDumpTest(v5, "("+v5t+") "+v5s+"\n") + + // C typedefed unsigned char array. + v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray() + v6Len := fmt.Sprintf("%d", v6l) + v6Cap := fmt.Sprintf("%d", v6c) + v6t := "[6]testdata._Ctype_custom_uchar_t" + v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " + + "{\n 00000000 74 65 73 74 36 00 " + + " |test6.|\n}" + addDumpTest(v6, "("+v6t+") "+v6s+"\n") +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dumpnocgo_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dumpnocgo_test.go new file mode 100644 index 00000000000..52a0971fb3f --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/dumpnocgo_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2013 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when either cgo is not supported or "-tags testcgo" is not added to the go +// test command line. This file intentionally does not setup any cgo tests in +// this scenario. +// +build !cgo !testcgo + +package spew_test + +func addCgoDumpTests() { + // Don't add any tests for cgo since this file is only compiled when + // there should not be any cgo tests. +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/example_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/example_test.go new file mode 100644 index 00000000000..de6c4e3097d --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/example_test.go @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew_test + +import ( + "fmt" + + "github.com/davecgh/go-spew/spew" +) + +type Flag int + +const ( + flagOne Flag = iota + flagTwo +) + +var flagStrings = map[Flag]string{ + flagOne: "flagOne", + flagTwo: "flagTwo", +} + +func (f Flag) String() string { + if s, ok := flagStrings[f]; ok { + return s + } + return fmt.Sprintf("Unknown flag (%d)", int(f)) +} + +type Bar struct { + data uintptr +} + +type Foo struct { + unexportedField Bar + ExportedField map[interface{}]interface{} +} + +// This example demonstrates how to use Dump to dump variables to stdout. +func ExampleDump() { + // The following package level declarations are assumed for this example: + /* + type Flag int + + const ( + flagOne Flag = iota + flagTwo + ) + + var flagStrings = map[Flag]string{ + flagOne: "flagOne", + flagTwo: "flagTwo", + } + + func (f Flag) String() string { + if s, ok := flagStrings[f]; ok { + return s + } + return fmt.Sprintf("Unknown flag (%d)", int(f)) + } + + type Bar struct { + data uintptr + } + + type Foo struct { + unexportedField Bar + ExportedField map[interface{}]interface{} + } + */ + + // Setup some sample data structures for the example. + bar := Bar{uintptr(0)} + s1 := Foo{bar, map[interface{}]interface{}{"one": true}} + f := Flag(5) + b := []byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, + } + + // Dump! + spew.Dump(s1, f, b) + + // Output: + // (spew_test.Foo) { + // unexportedField: (spew_test.Bar) { + // data: (uintptr) + // }, + // ExportedField: (map[interface {}]interface {}) (len=1) { + // (string) (len=3) "one": (bool) true + // } + // } + // (spew_test.Flag) Unknown flag (5) + // ([]uint8) (len=34 cap=34) { + // 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | + // 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| + // 00000020 31 32 |12| + // } + // +} + +// This example demonstrates how to use Printf to display a variable with a +// format string and inline formatting. +func ExamplePrintf() { + // Create a double pointer to a uint 8. + ui8 := uint8(5) + pui8 := &ui8 + ppui8 := &pui8 + + // Create a circular data type. + type circular struct { + ui8 uint8 + c *circular + } + c := circular{ui8: 1} + c.c = &c + + // Print! + spew.Printf("ppui8: %v\n", ppui8) + spew.Printf("circular: %v\n", c) + + // Output: + // ppui8: <**>5 + // circular: {1 <*>{1 <*>}} +} + +// This example demonstrates how to use a ConfigState. +func ExampleConfigState() { + // Modify the indent level of the ConfigState only. The global + // configuration is not modified. + scs := spew.ConfigState{Indent: "\t"} + + // Output using the ConfigState instance. + v := map[string]int{"one": 1} + scs.Printf("v: %v\n", v) + scs.Dump(v) + + // Output: + // v: map[one:1] + // (map[string]int) (len=1) { + // (string) (len=3) "one": (int) 1 + // } +} + +// This example demonstrates how to use ConfigState.Dump to dump variables to +// stdout +func ExampleConfigState_Dump() { + // See the top-level Dump example for details on the types used in this + // example. + + // Create two ConfigState instances with different indentation. + scs := spew.ConfigState{Indent: "\t"} + scs2 := spew.ConfigState{Indent: " "} + + // Setup some sample data structures for the example. + bar := Bar{uintptr(0)} + s1 := Foo{bar, map[interface{}]interface{}{"one": true}} + + // Dump using the ConfigState instances. + scs.Dump(s1) + scs2.Dump(s1) + + // Output: + // (spew_test.Foo) { + // unexportedField: (spew_test.Bar) { + // data: (uintptr) + // }, + // ExportedField: (map[interface {}]interface {}) (len=1) { + // (string) (len=3) "one": (bool) true + // } + // } + // (spew_test.Foo) { + // unexportedField: (spew_test.Bar) { + // data: (uintptr) + // }, + // ExportedField: (map[interface {}]interface {}) (len=1) { + // (string) (len=3) "one": (bool) true + // } + // } + // +} + +// This example demonstrates how to use ConfigState.Printf to display a variable +// with a format string and inline formatting. +func ExampleConfigState_Printf() { + // See the top-level Dump example for details on the types used in this + // example. + + // Create two ConfigState instances and modify the method handling of the + // first ConfigState only. + scs := spew.NewDefaultConfig() + scs2 := spew.NewDefaultConfig() + scs.DisableMethods = true + + // Alternatively + // scs := spew.ConfigState{Indent: " ", DisableMethods: true} + // scs2 := spew.ConfigState{Indent: " "} + + // This is of type Flag which implements a Stringer and has raw value 1. + f := flagTwo + + // Dump using the ConfigState instances. + scs.Printf("f: %v\n", f) + scs2.Printf("f: %v\n", f) + + // Output: + // f: 1 + // f: flagTwo +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/format.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/format.go new file mode 100644 index 00000000000..ecf3b80e24b --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/format.go @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" +) + +// supportedFlags is a list of all the character flags supported by fmt package. +const supportedFlags = "0-+# " + +// formatState implements the fmt.Formatter interface and contains information +// about the state of a formatting operation. The NewFormatter function can +// be used to get a new Formatter which can be used directly as arguments +// in standard fmt package printing calls. +type formatState struct { + value interface{} + fs fmt.State + depth int + pointers map[uintptr]int + ignoreNextType bool + cs *ConfigState +} + +// buildDefaultFormat recreates the original format string without precision +// and width information to pass in to fmt.Sprintf in the case of an +// unrecognized type. Unless new types are added to the language, this +// function won't ever be called. +func (f *formatState) buildDefaultFormat() (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + buf.WriteRune('v') + + format = buf.String() + return format +} + +// constructOrigFormat recreates the original format string including precision +// and width information to pass along to the standard fmt package. This allows +// automatic deferral of all format strings this package doesn't support. +func (f *formatState) constructOrigFormat(verb rune) (format string) { + buf := bytes.NewBuffer(percentBytes) + + for _, flag := range supportedFlags { + if f.fs.Flag(int(flag)) { + buf.WriteRune(flag) + } + } + + if width, ok := f.fs.Width(); ok { + buf.WriteString(strconv.Itoa(width)) + } + + if precision, ok := f.fs.Precision(); ok { + buf.Write(precisionBytes) + buf.WriteString(strconv.Itoa(precision)) + } + + buf.WriteRune(verb) + + format = buf.String() + return format +} + +// unpackValue returns values inside of non-nil interfaces when possible and +// ensures that types for values which have been unpacked from an interface +// are displayed when the show types flag is also set. +// This is useful for data types like structs, arrays, slices, and maps which +// can contain varying types packed inside an interface. +func (f *formatState) unpackValue(v reflect.Value) reflect.Value { + if v.Kind() == reflect.Interface { + f.ignoreNextType = false + if !v.IsNil() { + v = v.Elem() + } + } + return v +} + +// formatPtr handles formatting of pointers by indirecting them as necessary. +func (f *formatState) formatPtr(v reflect.Value) { + // Display nil if top level pointer is nil. + showTypes := f.fs.Flag('#') + if v.IsNil() && (!showTypes || f.ignoreNextType) { + f.fs.Write(nilAngleBytes) + return + } + + // Remove pointers at or below the current depth from map used to detect + // circular refs. + for k, depth := range f.pointers { + if depth >= f.depth { + delete(f.pointers, k) + } + } + + // Keep list of all dereferenced pointers to possibly show later. + pointerChain := make([]uintptr, 0) + + // Figure out how many levels of indirection there are by derferencing + // pointers and unpacking interfaces down the chain while detecting circular + // references. + nilFound := false + cycleFound := false + indirects := 0 + ve := v + for ve.Kind() == reflect.Ptr { + if ve.IsNil() { + nilFound = true + break + } + indirects++ + addr := ve.Pointer() + pointerChain = append(pointerChain, addr) + if pd, ok := f.pointers[addr]; ok && pd < f.depth { + cycleFound = true + indirects-- + break + } + f.pointers[addr] = f.depth + + ve = ve.Elem() + if ve.Kind() == reflect.Interface { + if ve.IsNil() { + nilFound = true + break + } + ve = ve.Elem() + } + } + + // Display type or indirection level depending on flags. + if showTypes && !f.ignoreNextType { + f.fs.Write(openParenBytes) + f.fs.Write(bytes.Repeat(asteriskBytes, indirects)) + f.fs.Write([]byte(ve.Type().String())) + f.fs.Write(closeParenBytes) + } else { + if nilFound || cycleFound { + indirects += strings.Count(ve.Type().String(), "*") + } + f.fs.Write(openAngleBytes) + f.fs.Write([]byte(strings.Repeat("*", indirects))) + f.fs.Write(closeAngleBytes) + } + + // Display pointer information depending on flags. + if f.fs.Flag('+') && (len(pointerChain) > 0) { + f.fs.Write(openParenBytes) + for i, addr := range pointerChain { + if i > 0 { + f.fs.Write(pointerChainBytes) + } + printHexPtr(f.fs, addr) + } + f.fs.Write(closeParenBytes) + } + + // Display dereferenced value. + switch { + case nilFound == true: + f.fs.Write(nilAngleBytes) + + case cycleFound == true: + f.fs.Write(circularShortBytes) + + default: + f.ignoreNextType = true + f.format(ve) + } +} + +// format is the main workhorse for providing the Formatter interface. It +// uses the passed reflect value to figure out what kind of object we are +// dealing with and formats it appropriately. It is a recursive function, +// however circular data structures are detected and handled properly. +func (f *formatState) format(v reflect.Value) { + // Handle invalid reflect values immediately. + kind := v.Kind() + if kind == reflect.Invalid { + f.fs.Write(invalidAngleBytes) + return + } + + // Handle pointers specially. + if kind == reflect.Ptr { + f.formatPtr(v) + return + } + + // Print type information unless already handled elsewhere. + if !f.ignoreNextType && f.fs.Flag('#') { + f.fs.Write(openParenBytes) + f.fs.Write([]byte(v.Type().String())) + f.fs.Write(closeParenBytes) + } + f.ignoreNextType = false + + // Call Stringer/error interfaces if they exist and the handle methods + // flag is enabled. + if !f.cs.DisableMethods { + if (kind != reflect.Invalid) && (kind != reflect.Interface) { + if handled := handleMethods(f.cs, f.fs, v); handled { + return + } + } + } + + switch kind { + case reflect.Invalid: + // Do nothing. We should never get here since invalid has already + // been handled above. + + case reflect.Bool: + printBool(f.fs, v.Bool()) + + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + printInt(f.fs, v.Int(), 10) + + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint: + printUint(f.fs, v.Uint(), 10) + + case reflect.Float32: + printFloat(f.fs, v.Float(), 32) + + case reflect.Float64: + printFloat(f.fs, v.Float(), 64) + + case reflect.Complex64: + printComplex(f.fs, v.Complex(), 32) + + case reflect.Complex128: + printComplex(f.fs, v.Complex(), 64) + + case reflect.Slice: + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + fallthrough + + case reflect.Array: + f.fs.Write(openBracketBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + numEntries := v.Len() + for i := 0; i < numEntries; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(v.Index(i))) + } + } + f.depth-- + f.fs.Write(closeBracketBytes) + + case reflect.String: + f.fs.Write([]byte(v.String())) + + case reflect.Interface: + // The only time we should get here is for nil interfaces due to + // unpackValue calls. + if v.IsNil() { + f.fs.Write(nilAngleBytes) + } + + case reflect.Ptr: + // Do nothing. We should never get here since pointers have already + // been handled above. + + case reflect.Map: + // nil maps should be indicated as different than empty maps + if v.IsNil() { + f.fs.Write(nilAngleBytes) + break + } + + f.fs.Write(openMapBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + keys := v.MapKeys() + if f.cs.SortKeys { + sortValues(keys, f.cs) + } + for i, key := range keys { + if i > 0 { + f.fs.Write(spaceBytes) + } + f.ignoreNextType = true + f.format(f.unpackValue(key)) + f.fs.Write(colonBytes) + f.ignoreNextType = true + f.format(f.unpackValue(v.MapIndex(key))) + } + } + f.depth-- + f.fs.Write(closeMapBytes) + + case reflect.Struct: + numFields := v.NumField() + f.fs.Write(openBraceBytes) + f.depth++ + if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) { + f.fs.Write(maxShortBytes) + } else { + vt := v.Type() + for i := 0; i < numFields; i++ { + if i > 0 { + f.fs.Write(spaceBytes) + } + vtf := vt.Field(i) + if f.fs.Flag('+') || f.fs.Flag('#') { + f.fs.Write([]byte(vtf.Name)) + f.fs.Write(colonBytes) + } + f.format(f.unpackValue(v.Field(i))) + } + } + f.depth-- + f.fs.Write(closeBraceBytes) + + case reflect.Uintptr: + printHexPtr(f.fs, uintptr(v.Uint())) + + case reflect.UnsafePointer, reflect.Chan, reflect.Func: + printHexPtr(f.fs, v.Pointer()) + + // There were not any other types at the time this code was written, but + // fall back to letting the default fmt package handle it if any get added. + default: + format := f.buildDefaultFormat() + if v.CanInterface() { + fmt.Fprintf(f.fs, format, v.Interface()) + } else { + fmt.Fprintf(f.fs, format, v.String()) + } + } +} + +// Format satisfies the fmt.Formatter interface. See NewFormatter for usage +// details. +func (f *formatState) Format(fs fmt.State, verb rune) { + f.fs = fs + + // Use standard formatting for verbs that are not v. + if verb != 'v' { + format := f.constructOrigFormat(verb) + fmt.Fprintf(fs, format, f.value) + return + } + + if f.value == nil { + if fs.Flag('#') { + fs.Write(interfaceBytes) + } + fs.Write(nilAngleBytes) + return + } + + f.format(reflect.ValueOf(f.value)) +} + +// newFormatter is a helper function to consolidate the logic from the various +// public methods which take varying config states. +func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter { + fs := &formatState{value: v, cs: cs} + fs.pointers = make(map[uintptr]int) + return fs +} + +/* +NewFormatter returns a custom formatter that satisfies the fmt.Formatter +interface. As a result, it integrates cleanly with standard fmt package +printing functions. The formatter is useful for inline printing of smaller data +types similar to the standard %v format specifier. + +The custom formatter only responds to the %v (most compact), %+v (adds pointer +addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb +combinations. Any other verbs such as %x and %q will be sent to the the +standard fmt package for formatting. In addition, the custom formatter ignores +the width and precision arguments (however they will still work on the format +specifiers not handled by the custom formatter). + +Typically this function shouldn't be called directly. It is much easier to make +use of the custom formatter by calling one of the convenience functions such as +Printf, Println, or Fprintf. +*/ +func NewFormatter(v interface{}) fmt.Formatter { + return newFormatter(&Config, v) +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/format_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/format_test.go new file mode 100644 index 00000000000..b664b3f135f --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/format_test.go @@ -0,0 +1,1558 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Test Summary: +NOTE: For each test, a nil pointer, a single pointer and double pointer to the +base test element are also tested to ensure proper indirection across all types. + +- Max int8, int16, int32, int64, int +- Max uint8, uint16, uint32, uint64, uint +- Boolean true and false +- Standard complex64 and complex128 +- Array containing standard ints +- Array containing type with custom formatter on pointer receiver only +- Array containing interfaces +- Slice containing standard float32 values +- Slice containing type with custom formatter on pointer receiver only +- Slice containing interfaces +- Nil slice +- Standard string +- Nil interface +- Sub-interface +- Map with string keys and int vals +- Map with custom formatter type on pointer receiver only keys and vals +- Map with interface keys and values +- Map with nil interface value +- Struct with primitives +- Struct that contains another struct +- Struct that contains custom type with Stringer pointer interface via both + exported and unexported fields +- Struct that contains embedded struct and field to same struct +- Uintptr to 0 (null pointer) +- Uintptr address of real variable +- Unsafe.Pointer to 0 (null pointer) +- Unsafe.Pointer to address of real variable +- Nil channel +- Standard int channel +- Function with no params and no returns +- Function with param and no returns +- Function with multiple params and multiple returns +- Struct that is circular through self referencing +- Structs that are circular through cross referencing +- Structs that are indirectly circular +- Type that panics in its Stringer interface +- Type that has a custom Error interface +- %x passthrough with uint +- %#x passthrough with uint +- %f passthrough with precision +- %f passthrough with width and precision +- %d passthrough with width +- %q passthrough with string +*/ + +package spew_test + +import ( + "bytes" + "fmt" + "testing" + "unsafe" + + "github.com/davecgh/go-spew/spew" +) + +// formatterTest is used to describe a test to be perfomed against NewFormatter. +type formatterTest struct { + format string + in interface{} + wants []string +} + +// formatterTests houses all of the tests to be performed against NewFormatter. +var formatterTests = make([]formatterTest, 0) + +// addFormatterTest is a helper method to append the passed input and desired +// result to formatterTests. +func addFormatterTest(format string, in interface{}, wants ...string) { + test := formatterTest{format, in, wants} + formatterTests = append(formatterTests, test) +} + +func addIntFormatterTests() { + // Max int8. + v := int8(127) + nv := (*int8)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "int8" + vs := "127" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Max int16. + v2 := int16(32767) + nv2 := (*int16)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "int16" + v2s := "32767" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Max int32. + v3 := int32(2147483647) + nv3 := (*int32)(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "int32" + v3s := "2147483647" + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s) + addFormatterTest("%v", &pv3, "<**>"+v3s) + addFormatterTest("%v", nv3, "") + addFormatterTest("%+v", v3, v3s) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + + // Max int64. + v4 := int64(9223372036854775807) + nv4 := (*int64)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "int64" + v4s := "9223372036854775807" + addFormatterTest("%v", v4, v4s) + addFormatterTest("%v", pv4, "<*>"+v4s) + addFormatterTest("%v", &pv4, "<**>"+v4s) + addFormatterTest("%v", nv4, "") + addFormatterTest("%+v", v4, v4s) + addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s) + addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%#v", v4, "("+v4t+")"+v4s) + addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s) + addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s) + addFormatterTest("%#v", nv4, "(*"+v4t+")"+"") + addFormatterTest("%#+v", v4, "("+v4t+")"+v4s) + addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s) + addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"") + + // Max int. + v5 := int(2147483647) + nv5 := (*int)(nil) + pv5 := &v5 + v5Addr := fmt.Sprintf("%p", pv5) + pv5Addr := fmt.Sprintf("%p", &pv5) + v5t := "int" + v5s := "2147483647" + addFormatterTest("%v", v5, v5s) + addFormatterTest("%v", pv5, "<*>"+v5s) + addFormatterTest("%v", &pv5, "<**>"+v5s) + addFormatterTest("%v", nv5, "") + addFormatterTest("%+v", v5, v5s) + addFormatterTest("%+v", pv5, "<*>("+v5Addr+")"+v5s) + addFormatterTest("%+v", &pv5, "<**>("+pv5Addr+"->"+v5Addr+")"+v5s) + addFormatterTest("%+v", nv5, "") + addFormatterTest("%#v", v5, "("+v5t+")"+v5s) + addFormatterTest("%#v", pv5, "(*"+v5t+")"+v5s) + addFormatterTest("%#v", &pv5, "(**"+v5t+")"+v5s) + addFormatterTest("%#v", nv5, "(*"+v5t+")"+"") + addFormatterTest("%#+v", v5, "("+v5t+")"+v5s) + addFormatterTest("%#+v", pv5, "(*"+v5t+")("+v5Addr+")"+v5s) + addFormatterTest("%#+v", &pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")"+v5s) + addFormatterTest("%#+v", nv5, "(*"+v5t+")"+"") +} + +func addUintFormatterTests() { + // Max uint8. + v := uint8(255) + nv := (*uint8)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "uint8" + vs := "255" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Max uint16. + v2 := uint16(65535) + nv2 := (*uint16)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "uint16" + v2s := "65535" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Max uint32. + v3 := uint32(4294967295) + nv3 := (*uint32)(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "uint32" + v3s := "4294967295" + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s) + addFormatterTest("%v", &pv3, "<**>"+v3s) + addFormatterTest("%v", nv3, "") + addFormatterTest("%+v", v3, v3s) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + + // Max uint64. + v4 := uint64(18446744073709551615) + nv4 := (*uint64)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "uint64" + v4s := "18446744073709551615" + addFormatterTest("%v", v4, v4s) + addFormatterTest("%v", pv4, "<*>"+v4s) + addFormatterTest("%v", &pv4, "<**>"+v4s) + addFormatterTest("%v", nv4, "") + addFormatterTest("%+v", v4, v4s) + addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s) + addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%#v", v4, "("+v4t+")"+v4s) + addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s) + addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s) + addFormatterTest("%#v", nv4, "(*"+v4t+")"+"") + addFormatterTest("%#+v", v4, "("+v4t+")"+v4s) + addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s) + addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"") + + // Max uint. + v5 := uint(4294967295) + nv5 := (*uint)(nil) + pv5 := &v5 + v5Addr := fmt.Sprintf("%p", pv5) + pv5Addr := fmt.Sprintf("%p", &pv5) + v5t := "uint" + v5s := "4294967295" + addFormatterTest("%v", v5, v5s) + addFormatterTest("%v", pv5, "<*>"+v5s) + addFormatterTest("%v", &pv5, "<**>"+v5s) + addFormatterTest("%v", nv5, "") + addFormatterTest("%+v", v5, v5s) + addFormatterTest("%+v", pv5, "<*>("+v5Addr+")"+v5s) + addFormatterTest("%+v", &pv5, "<**>("+pv5Addr+"->"+v5Addr+")"+v5s) + addFormatterTest("%+v", nv5, "") + addFormatterTest("%#v", v5, "("+v5t+")"+v5s) + addFormatterTest("%#v", pv5, "(*"+v5t+")"+v5s) + addFormatterTest("%#v", &pv5, "(**"+v5t+")"+v5s) + addFormatterTest("%#v", nv5, "(*"+v5t+")"+"") + addFormatterTest("%#+v", v5, "("+v5t+")"+v5s) + addFormatterTest("%#+v", pv5, "(*"+v5t+")("+v5Addr+")"+v5s) + addFormatterTest("%#+v", &pv5, "(**"+v5t+")("+pv5Addr+"->"+v5Addr+")"+v5s) + addFormatterTest("%#v", nv5, "(*"+v5t+")"+"") +} + +func addBoolFormatterTests() { + // Boolean true. + v := bool(true) + nv := (*bool)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "bool" + vs := "true" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Boolean false. + v2 := bool(false) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "bool" + v2s := "false" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) +} + +func addFloatFormatterTests() { + // Standard float32. + v := float32(3.1415) + nv := (*float32)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "float32" + vs := "3.1415" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Standard float64. + v2 := float64(3.1415926) + nv2 := (*float64)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "float64" + v2s := "3.1415926" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") +} + +func addComplexFormatterTests() { + // Standard complex64. + v := complex(float32(6), -2) + nv := (*complex64)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "complex64" + vs := "(6-2i)" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Standard complex128. + v2 := complex(float64(-6), 2) + nv2 := (*complex128)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "complex128" + v2s := "(-6+2i)" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") +} + +func addArrayFormatterTests() { + // Array containing standard ints. + v := [3]int{1, 2, 3} + nv := (*[3]int)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "[3]int" + vs := "[1 2 3]" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Array containing type with custom formatter on pointer receiver only. + v2 := [3]pstringer{"1", "2", "3"} + nv2 := (*[3]pstringer)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "[3]spew_test.pstringer" + v2sp := "[stringer 1 stringer 2 stringer 3]" + v2s := v2sp + if spew.UnsafeDisabled { + v2s = "[1 2 3]" + } + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2sp) + addFormatterTest("%v", &pv2, "<**>"+v2sp) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2sp) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2sp) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2sp) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2sp) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2sp) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2sp) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Array containing interfaces. + v3 := [3]interface{}{"one", int(2), uint(3)} + nv3 := (*[3]interface{})(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "[3]interface {}" + v3t2 := "string" + v3t3 := "int" + v3t4 := "uint" + v3s := "[one 2 3]" + v3s2 := "[(" + v3t2 + ")one (" + v3t3 + ")2 (" + v3t4 + ")3]" + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s) + addFormatterTest("%v", &pv3, "<**>"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%+v", v3, v3s) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s2) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s2) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s2) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s2) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s2) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s2) + addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"") +} + +func addSliceFormatterTests() { + // Slice containing standard float32 values. + v := []float32{3.14, 6.28, 12.56} + nv := (*[]float32)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "[]float32" + vs := "[3.14 6.28 12.56]" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Slice containing type with custom formatter on pointer receiver only. + v2 := []pstringer{"1", "2", "3"} + nv2 := (*[]pstringer)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "[]spew_test.pstringer" + v2s := "[stringer 1 stringer 2 stringer 3]" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Slice containing interfaces. + v3 := []interface{}{"one", int(2), uint(3), nil} + nv3 := (*[]interface{})(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "[]interface {}" + v3t2 := "string" + v3t3 := "int" + v3t4 := "uint" + v3t5 := "interface {}" + v3s := "[one 2 3 ]" + v3s2 := "[(" + v3t2 + ")one (" + v3t3 + ")2 (" + v3t4 + ")3 (" + v3t5 + + ")]" + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s) + addFormatterTest("%v", &pv3, "<**>"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%+v", v3, v3s) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s2) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s2) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s2) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s2) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s2) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s2) + addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"") + + // Nil slice. + var v4 []int + nv4 := (*[]int)(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "[]int" + v4s := "" + addFormatterTest("%v", v4, v4s) + addFormatterTest("%v", pv4, "<*>"+v4s) + addFormatterTest("%v", &pv4, "<**>"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%+v", v4, v4s) + addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s) + addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%#v", v4, "("+v4t+")"+v4s) + addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s) + addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s) + addFormatterTest("%#v", nv4, "(*"+v4t+")"+"") + addFormatterTest("%#+v", v4, "("+v4t+")"+v4s) + addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s) + addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"") +} + +func addStringFormatterTests() { + // Standard string. + v := "test" + nv := (*string)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "string" + vs := "test" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") +} + +func addInterfaceFormatterTests() { + // Nil interface. + var v interface{} + nv := (*interface{})(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "interface {}" + vs := "" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Sub-interface. + v2 := interface{}(uint16(65535)) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "uint16" + v2s := "65535" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) +} + +func addMapFormatterTests() { + // Map with string keys and int vals. + v := map[string]int{"one": 1, "two": 2} + nilMap := map[string]int(nil) + nv := (*map[string]int)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "map[string]int" + vs := "map[one:1 two:2]" + vs2 := "map[two:2 one:1]" + addFormatterTest("%v", v, vs, vs2) + addFormatterTest("%v", pv, "<*>"+vs, "<*>"+vs2) + addFormatterTest("%v", &pv, "<**>"+vs, "<**>"+vs2) + addFormatterTest("%+v", nilMap, "") + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs, vs2) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs, "<*>("+vAddr+")"+vs2) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs, + "<**>("+pvAddr+"->"+vAddr+")"+vs2) + addFormatterTest("%+v", nilMap, "") + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs, "("+vt+")"+vs2) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs, "(*"+vt+")"+vs2) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs, "(**"+vt+")"+vs2) + addFormatterTest("%#v", nilMap, "("+vt+")"+"") + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs, "("+vt+")"+vs2) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs, + "(*"+vt+")("+vAddr+")"+vs2) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs, + "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs2) + addFormatterTest("%#+v", nilMap, "("+vt+")"+"") + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Map with custom formatter type on pointer receiver only keys and vals. + v2 := map[pstringer]pstringer{"one": "1"} + nv2 := (*map[pstringer]pstringer)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "map[spew_test.pstringer]spew_test.pstringer" + v2s := "map[stringer one:stringer 1]" + if spew.UnsafeDisabled { + v2s = "map[one:1]" + } + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Map with interface keys and values. + v3 := map[interface{}]interface{}{"one": 1} + nv3 := (*map[interface{}]interface{})(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "map[interface {}]interface {}" + v3t1 := "string" + v3t2 := "int" + v3s := "map[one:1]" + v3s2 := "map[(" + v3t1 + ")one:(" + v3t2 + ")1]" + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s) + addFormatterTest("%v", &pv3, "<**>"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%+v", v3, v3s) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s2) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s2) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s2) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s2) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s2) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s2) + addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"") + + // Map with nil interface value + v4 := map[string]interface{}{"nil": nil} + nv4 := (*map[string]interface{})(nil) + pv4 := &v4 + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "map[string]interface {}" + v4t1 := "interface {}" + v4s := "map[nil:]" + v4s2 := "map[nil:(" + v4t1 + ")]" + addFormatterTest("%v", v4, v4s) + addFormatterTest("%v", pv4, "<*>"+v4s) + addFormatterTest("%v", &pv4, "<**>"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%+v", v4, v4s) + addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s) + addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%#v", v4, "("+v4t+")"+v4s2) + addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s2) + addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s2) + addFormatterTest("%#v", nv4, "(*"+v4t+")"+"") + addFormatterTest("%#+v", v4, "("+v4t+")"+v4s2) + addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s2) + addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s2) + addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"") +} + +func addStructFormatterTests() { + // Struct with primitives. + type s1 struct { + a int8 + b uint8 + } + v := s1{127, 255} + nv := (*s1)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.s1" + vt2 := "int8" + vt3 := "uint8" + vs := "{127 255}" + vs2 := "{a:127 b:255}" + vs3 := "{a:(" + vt2 + ")127 b:(" + vt3 + ")255}" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs2) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs2) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs2) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs3) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs3) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs3) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs3) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs3) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs3) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Struct that contains another struct. + type s2 struct { + s1 s1 + b bool + } + v2 := s2{s1{127, 255}, true} + nv2 := (*s2)(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "spew_test.s2" + v2t2 := "spew_test.s1" + v2t3 := "int8" + v2t4 := "uint8" + v2t5 := "bool" + v2s := "{{127 255} true}" + v2s2 := "{s1:{a:127 b:255} b:true}" + v2s3 := "{s1:(" + v2t2 + "){a:(" + v2t3 + ")127 b:(" + v2t4 + ")255} b:(" + + v2t5 + ")true}" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s2) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s2) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s2) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s3) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s3) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s3) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s3) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s3) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s3) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Struct that contains custom type with Stringer pointer interface via both + // exported and unexported fields. + type s3 struct { + s pstringer + S pstringer + } + v3 := s3{"test", "test2"} + nv3 := (*s3)(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "spew_test.s3" + v3t2 := "spew_test.pstringer" + v3s := "{stringer test stringer test2}" + v3sp := v3s + v3s2 := "{s:stringer test S:stringer test2}" + v3s2p := v3s2 + v3s3 := "{s:(" + v3t2 + ")stringer test S:(" + v3t2 + ")stringer test2}" + v3s3p := v3s3 + if spew.UnsafeDisabled { + v3s = "{test test2}" + v3sp = "{test stringer test2}" + v3s2 = "{s:test S:test2}" + v3s2p = "{s:test S:stringer test2}" + v3s3 = "{s:(" + v3t2 + ")test S:(" + v3t2 + ")test2}" + v3s3p = "{s:(" + v3t2 + ")test S:(" + v3t2 + ")stringer test2}" + } + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3sp) + addFormatterTest("%v", &pv3, "<**>"+v3sp) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%+v", v3, v3s2) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s2p) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s2p) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s3) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s3p) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s3p) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s3) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s3p) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s3p) + addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"") + + // Struct that contains embedded struct and field to same struct. + e := embed{"embedstr"} + v4 := embedwrap{embed: &e, e: &e} + nv4 := (*embedwrap)(nil) + pv4 := &v4 + eAddr := fmt.Sprintf("%p", &e) + v4Addr := fmt.Sprintf("%p", pv4) + pv4Addr := fmt.Sprintf("%p", &pv4) + v4t := "spew_test.embedwrap" + v4t2 := "spew_test.embed" + v4t3 := "string" + v4s := "{<*>{embedstr} <*>{embedstr}}" + v4s2 := "{embed:<*>(" + eAddr + "){a:embedstr} e:<*>(" + eAddr + + "){a:embedstr}}" + v4s3 := "{embed:(*" + v4t2 + "){a:(" + v4t3 + ")embedstr} e:(*" + v4t2 + + "){a:(" + v4t3 + ")embedstr}}" + v4s4 := "{embed:(*" + v4t2 + ")(" + eAddr + "){a:(" + v4t3 + + ")embedstr} e:(*" + v4t2 + ")(" + eAddr + "){a:(" + v4t3 + ")embedstr}}" + addFormatterTest("%v", v4, v4s) + addFormatterTest("%v", pv4, "<*>"+v4s) + addFormatterTest("%v", &pv4, "<**>"+v4s) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%+v", v4, v4s2) + addFormatterTest("%+v", pv4, "<*>("+v4Addr+")"+v4s2) + addFormatterTest("%+v", &pv4, "<**>("+pv4Addr+"->"+v4Addr+")"+v4s2) + addFormatterTest("%+v", nv4, "") + addFormatterTest("%#v", v4, "("+v4t+")"+v4s3) + addFormatterTest("%#v", pv4, "(*"+v4t+")"+v4s3) + addFormatterTest("%#v", &pv4, "(**"+v4t+")"+v4s3) + addFormatterTest("%#v", nv4, "(*"+v4t+")"+"") + addFormatterTest("%#+v", v4, "("+v4t+")"+v4s4) + addFormatterTest("%#+v", pv4, "(*"+v4t+")("+v4Addr+")"+v4s4) + addFormatterTest("%#+v", &pv4, "(**"+v4t+")("+pv4Addr+"->"+v4Addr+")"+v4s4) + addFormatterTest("%#+v", nv4, "(*"+v4t+")"+"") +} + +func addUintptrFormatterTests() { + // Null pointer. + v := uintptr(0) + nv := (*uintptr)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "uintptr" + vs := "" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Address of real variable. + i := 1 + v2 := uintptr(unsafe.Pointer(&i)) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "uintptr" + v2s := fmt.Sprintf("%p", &i) + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) +} + +func addUnsafePointerFormatterTests() { + // Null pointer. + v := unsafe.Pointer(uintptr(0)) + nv := (*unsafe.Pointer)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "unsafe.Pointer" + vs := "" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Address of real variable. + i := 1 + v2 := unsafe.Pointer(&i) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "unsafe.Pointer" + v2s := fmt.Sprintf("%p", &i) + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) +} + +func addChanFormatterTests() { + // Nil channel. + var v chan int + pv := &v + nv := (*chan int)(nil) + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "chan int" + vs := "" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Real channel. + v2 := make(chan int) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "chan int" + v2s := fmt.Sprintf("%p", v2) + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) +} + +func addFuncFormatterTests() { + // Function with no params and no returns. + v := addIntFormatterTests + nv := (*func())(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "func()" + vs := fmt.Sprintf("%p", v) + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") + + // Function with param and no returns. + v2 := TestFormatter + nv2 := (*func(*testing.T))(nil) + pv2 := &v2 + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "func(*testing.T)" + v2s := fmt.Sprintf("%p", v2) + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s) + addFormatterTest("%v", &pv2, "<**>"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%+v", v2, v2s) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%+v", nv2, "") + addFormatterTest("%#v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s) + addFormatterTest("%#v", nv2, "(*"+v2t+")"+"") + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s) + addFormatterTest("%#+v", nv2, "(*"+v2t+")"+"") + + // Function with multiple params and multiple returns. + var v3 = func(i int, s string) (b bool, err error) { + return true, nil + } + nv3 := (*func(int, string) (bool, error))(nil) + pv3 := &v3 + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "func(int, string) (bool, error)" + v3s := fmt.Sprintf("%p", v3) + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s) + addFormatterTest("%v", &pv3, "<**>"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%+v", v3, v3s) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%+v", nv3, "") + addFormatterTest("%#v", v3, "("+v3t+")"+v3s) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s) + addFormatterTest("%#v", nv3, "(*"+v3t+")"+"") + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s) + addFormatterTest("%#+v", nv3, "(*"+v3t+")"+"") +} + +func addCircularFormatterTests() { + // Struct that is circular through self referencing. + type circular struct { + c *circular + } + v := circular{nil} + v.c = &v + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.circular" + vs := "{<*>{<*>}}" + vs2 := "{<*>}" + vs3 := "{c:<*>(" + vAddr + "){c:<*>(" + vAddr + ")}}" + vs4 := "{c:<*>(" + vAddr + ")}" + vs5 := "{c:(*" + vt + "){c:(*" + vt + ")}}" + vs6 := "{c:(*" + vt + ")}" + vs7 := "{c:(*" + vt + ")(" + vAddr + "){c:(*" + vt + ")(" + vAddr + + ")}}" + vs8 := "{c:(*" + vt + ")(" + vAddr + ")}" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs2) + addFormatterTest("%v", &pv, "<**>"+vs2) + addFormatterTest("%+v", v, vs3) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs4) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs4) + addFormatterTest("%#v", v, "("+vt+")"+vs5) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs6) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs6) + addFormatterTest("%#+v", v, "("+vt+")"+vs7) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs8) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs8) + + // Structs that are circular through cross referencing. + v2 := xref1{nil} + ts2 := xref2{&v2} + v2.ps2 = &ts2 + pv2 := &v2 + ts2Addr := fmt.Sprintf("%p", &ts2) + v2Addr := fmt.Sprintf("%p", pv2) + pv2Addr := fmt.Sprintf("%p", &pv2) + v2t := "spew_test.xref1" + v2t2 := "spew_test.xref2" + v2s := "{<*>{<*>{<*>}}}" + v2s2 := "{<*>{<*>}}" + v2s3 := "{ps2:<*>(" + ts2Addr + "){ps1:<*>(" + v2Addr + "){ps2:<*>(" + + ts2Addr + ")}}}" + v2s4 := "{ps2:<*>(" + ts2Addr + "){ps1:<*>(" + v2Addr + ")}}" + v2s5 := "{ps2:(*" + v2t2 + "){ps1:(*" + v2t + "){ps2:(*" + v2t2 + + ")}}}" + v2s6 := "{ps2:(*" + v2t2 + "){ps1:(*" + v2t + ")}}" + v2s7 := "{ps2:(*" + v2t2 + ")(" + ts2Addr + "){ps1:(*" + v2t + + ")(" + v2Addr + "){ps2:(*" + v2t2 + ")(" + ts2Addr + + ")}}}" + v2s8 := "{ps2:(*" + v2t2 + ")(" + ts2Addr + "){ps1:(*" + v2t + + ")(" + v2Addr + ")}}" + addFormatterTest("%v", v2, v2s) + addFormatterTest("%v", pv2, "<*>"+v2s2) + addFormatterTest("%v", &pv2, "<**>"+v2s2) + addFormatterTest("%+v", v2, v2s3) + addFormatterTest("%+v", pv2, "<*>("+v2Addr+")"+v2s4) + addFormatterTest("%+v", &pv2, "<**>("+pv2Addr+"->"+v2Addr+")"+v2s4) + addFormatterTest("%#v", v2, "("+v2t+")"+v2s5) + addFormatterTest("%#v", pv2, "(*"+v2t+")"+v2s6) + addFormatterTest("%#v", &pv2, "(**"+v2t+")"+v2s6) + addFormatterTest("%#+v", v2, "("+v2t+")"+v2s7) + addFormatterTest("%#+v", pv2, "(*"+v2t+")("+v2Addr+")"+v2s8) + addFormatterTest("%#+v", &pv2, "(**"+v2t+")("+pv2Addr+"->"+v2Addr+")"+v2s8) + + // Structs that are indirectly circular. + v3 := indirCir1{nil} + tic2 := indirCir2{nil} + tic3 := indirCir3{&v3} + tic2.ps3 = &tic3 + v3.ps2 = &tic2 + pv3 := &v3 + tic2Addr := fmt.Sprintf("%p", &tic2) + tic3Addr := fmt.Sprintf("%p", &tic3) + v3Addr := fmt.Sprintf("%p", pv3) + pv3Addr := fmt.Sprintf("%p", &pv3) + v3t := "spew_test.indirCir1" + v3t2 := "spew_test.indirCir2" + v3t3 := "spew_test.indirCir3" + v3s := "{<*>{<*>{<*>{<*>}}}}" + v3s2 := "{<*>{<*>{<*>}}}" + v3s3 := "{ps2:<*>(" + tic2Addr + "){ps3:<*>(" + tic3Addr + "){ps1:<*>(" + + v3Addr + "){ps2:<*>(" + tic2Addr + ")}}}}" + v3s4 := "{ps2:<*>(" + tic2Addr + "){ps3:<*>(" + tic3Addr + "){ps1:<*>(" + + v3Addr + ")}}}" + v3s5 := "{ps2:(*" + v3t2 + "){ps3:(*" + v3t3 + "){ps1:(*" + v3t + + "){ps2:(*" + v3t2 + ")}}}}" + v3s6 := "{ps2:(*" + v3t2 + "){ps3:(*" + v3t3 + "){ps1:(*" + v3t + + ")}}}" + v3s7 := "{ps2:(*" + v3t2 + ")(" + tic2Addr + "){ps3:(*" + v3t3 + ")(" + + tic3Addr + "){ps1:(*" + v3t + ")(" + v3Addr + "){ps2:(*" + v3t2 + + ")(" + tic2Addr + ")}}}}" + v3s8 := "{ps2:(*" + v3t2 + ")(" + tic2Addr + "){ps3:(*" + v3t3 + ")(" + + tic3Addr + "){ps1:(*" + v3t + ")(" + v3Addr + ")}}}" + addFormatterTest("%v", v3, v3s) + addFormatterTest("%v", pv3, "<*>"+v3s2) + addFormatterTest("%v", &pv3, "<**>"+v3s2) + addFormatterTest("%+v", v3, v3s3) + addFormatterTest("%+v", pv3, "<*>("+v3Addr+")"+v3s4) + addFormatterTest("%+v", &pv3, "<**>("+pv3Addr+"->"+v3Addr+")"+v3s4) + addFormatterTest("%#v", v3, "("+v3t+")"+v3s5) + addFormatterTest("%#v", pv3, "(*"+v3t+")"+v3s6) + addFormatterTest("%#v", &pv3, "(**"+v3t+")"+v3s6) + addFormatterTest("%#+v", v3, "("+v3t+")"+v3s7) + addFormatterTest("%#+v", pv3, "(*"+v3t+")("+v3Addr+")"+v3s8) + addFormatterTest("%#+v", &pv3, "(**"+v3t+")("+pv3Addr+"->"+v3Addr+")"+v3s8) +} + +func addPanicFormatterTests() { + // Type that panics in its Stringer interface. + v := panicer(127) + nv := (*panicer)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.panicer" + vs := "(PANIC=test panic)127" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") +} + +func addErrorFormatterTests() { + // Type that has a custom Error interface. + v := customError(127) + nv := (*customError)(nil) + pv := &v + vAddr := fmt.Sprintf("%p", pv) + pvAddr := fmt.Sprintf("%p", &pv) + vt := "spew_test.customError" + vs := "error: 127" + addFormatterTest("%v", v, vs) + addFormatterTest("%v", pv, "<*>"+vs) + addFormatterTest("%v", &pv, "<**>"+vs) + addFormatterTest("%v", nv, "") + addFormatterTest("%+v", v, vs) + addFormatterTest("%+v", pv, "<*>("+vAddr+")"+vs) + addFormatterTest("%+v", &pv, "<**>("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%+v", nv, "") + addFormatterTest("%#v", v, "("+vt+")"+vs) + addFormatterTest("%#v", pv, "(*"+vt+")"+vs) + addFormatterTest("%#v", &pv, "(**"+vt+")"+vs) + addFormatterTest("%#v", nv, "(*"+vt+")"+"") + addFormatterTest("%#+v", v, "("+vt+")"+vs) + addFormatterTest("%#+v", pv, "(*"+vt+")("+vAddr+")"+vs) + addFormatterTest("%#+v", &pv, "(**"+vt+")("+pvAddr+"->"+vAddr+")"+vs) + addFormatterTest("%#+v", nv, "(*"+vt+")"+"") +} + +func addPassthroughFormatterTests() { + // %x passthrough with uint. + v := uint(4294967295) + pv := &v + vAddr := fmt.Sprintf("%x", pv) + pvAddr := fmt.Sprintf("%x", &pv) + vs := "ffffffff" + addFormatterTest("%x", v, vs) + addFormatterTest("%x", pv, vAddr) + addFormatterTest("%x", &pv, pvAddr) + + // %#x passthrough with uint. + v2 := int(2147483647) + pv2 := &v2 + v2Addr := fmt.Sprintf("%#x", pv2) + pv2Addr := fmt.Sprintf("%#x", &pv2) + v2s := "0x7fffffff" + addFormatterTest("%#x", v2, v2s) + addFormatterTest("%#x", pv2, v2Addr) + addFormatterTest("%#x", &pv2, pv2Addr) + + // %f passthrough with precision. + addFormatterTest("%.2f", 3.1415, "3.14") + addFormatterTest("%.3f", 3.1415, "3.142") + addFormatterTest("%.4f", 3.1415, "3.1415") + + // %f passthrough with width and precision. + addFormatterTest("%5.2f", 3.1415, " 3.14") + addFormatterTest("%6.3f", 3.1415, " 3.142") + addFormatterTest("%7.4f", 3.1415, " 3.1415") + + // %d passthrough with width. + addFormatterTest("%3d", 127, "127") + addFormatterTest("%4d", 127, " 127") + addFormatterTest("%5d", 127, " 127") + + // %q passthrough with string. + addFormatterTest("%q", "test", "\"test\"") +} + +// TestFormatter executes all of the tests described by formatterTests. +func TestFormatter(t *testing.T) { + // Setup tests. + addIntFormatterTests() + addUintFormatterTests() + addBoolFormatterTests() + addFloatFormatterTests() + addComplexFormatterTests() + addArrayFormatterTests() + addSliceFormatterTests() + addStringFormatterTests() + addInterfaceFormatterTests() + addMapFormatterTests() + addStructFormatterTests() + addUintptrFormatterTests() + addUnsafePointerFormatterTests() + addChanFormatterTests() + addFuncFormatterTests() + addCircularFormatterTests() + addPanicFormatterTests() + addErrorFormatterTests() + addPassthroughFormatterTests() + + t.Logf("Running %d tests", len(formatterTests)) + for i, test := range formatterTests { + buf := new(bytes.Buffer) + spew.Fprintf(buf, test.format, test.in) + s := buf.String() + if testFailed(s, test.wants) { + t.Errorf("Formatter #%d format: %s got: %s %s", i, test.format, s, + stringizeWants(test.wants)) + continue + } + } +} + +type testStruct struct { + x int +} + +func (ts testStruct) String() string { + return fmt.Sprintf("ts.%d", ts.x) +} + +type testStructP struct { + x int +} + +func (ts *testStructP) String() string { + return fmt.Sprintf("ts.%d", ts.x) +} + +func TestPrintSortedKeys(t *testing.T) { + cfg := spew.ConfigState{SortKeys: true} + s := cfg.Sprint(map[int]string{1: "1", 3: "3", 2: "2"}) + expected := "map[1:1 2:2 3:3]" + if s != expected { + t.Errorf("Sorted keys mismatch 1:\n %v %v", s, expected) + } + + s = cfg.Sprint(map[stringer]int{"1": 1, "3": 3, "2": 2}) + expected = "map[stringer 1:1 stringer 2:2 stringer 3:3]" + if s != expected { + t.Errorf("Sorted keys mismatch 2:\n %v %v", s, expected) + } + + s = cfg.Sprint(map[pstringer]int{pstringer("1"): 1, pstringer("3"): 3, pstringer("2"): 2}) + expected = "map[stringer 1:1 stringer 2:2 stringer 3:3]" + if spew.UnsafeDisabled { + expected = "map[1:1 2:2 3:3]" + } + if s != expected { + t.Errorf("Sorted keys mismatch 3:\n %v %v", s, expected) + } + + s = cfg.Sprint(map[testStruct]int{testStruct{1}: 1, testStruct{3}: 3, testStruct{2}: 2}) + expected = "map[ts.1:1 ts.2:2 ts.3:3]" + if s != expected { + t.Errorf("Sorted keys mismatch 4:\n %v %v", s, expected) + } + + if !spew.UnsafeDisabled { + s = cfg.Sprint(map[testStructP]int{testStructP{1}: 1, testStructP{3}: 3, testStructP{2}: 2}) + expected = "map[ts.1:1 ts.2:2 ts.3:3]" + if s != expected { + t.Errorf("Sorted keys mismatch 5:\n %v %v", s, expected) + } + } + + s = cfg.Sprint(map[customError]int{customError(1): 1, customError(3): 3, customError(2): 2}) + expected = "map[error: 1:1 error: 2:2 error: 3:3]" + if s != expected { + t.Errorf("Sorted keys mismatch 6:\n %v %v", s, expected) + } +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/internal_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/internal_test.go new file mode 100644 index 00000000000..1069ee21c1f --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/internal_test.go @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +This test file is part of the spew package rather than than the spew_test +package because it needs access to internals to properly test certain cases +which are not possible via the public interface since they should never happen. +*/ + +package spew + +import ( + "bytes" + "reflect" + "testing" +) + +// dummyFmtState implements a fake fmt.State to use for testing invalid +// reflect.Value handling. This is necessary because the fmt package catches +// invalid values before invoking the formatter on them. +type dummyFmtState struct { + bytes.Buffer +} + +func (dfs *dummyFmtState) Flag(f int) bool { + if f == int('+') { + return true + } + return false +} + +func (dfs *dummyFmtState) Precision() (int, bool) { + return 0, false +} + +func (dfs *dummyFmtState) Width() (int, bool) { + return 0, false +} + +// TestInvalidReflectValue ensures the dump and formatter code handles an +// invalid reflect value properly. This needs access to internal state since it +// should never happen in real code and therefore can't be tested via the public +// API. +func TestInvalidReflectValue(t *testing.T) { + i := 1 + + // Dump invalid reflect value. + v := new(reflect.Value) + buf := new(bytes.Buffer) + d := dumpState{w: buf, cs: &Config} + d.dump(*v) + s := buf.String() + want := "" + if s != want { + t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want) + } + i++ + + // Formatter invalid reflect value. + buf2 := new(dummyFmtState) + f := formatState{value: *v, cs: &Config, fs: buf2} + f.format(*v) + s = buf2.String() + want = "" + if s != want { + t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want) + } +} + +// SortValues makes the internal sortValues function available to the test +// package. +func SortValues(values []reflect.Value, cs *ConfigState) { + sortValues(values, cs) +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/internalunsafe_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/internalunsafe_test.go new file mode 100644 index 00000000000..83e070e9a86 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/internalunsafe_test.go @@ -0,0 +1,101 @@ +// Copyright (c) 2013-2015 Dave Collins + +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. + +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when the code is not running on Google App Engine and "-tags disableunsafe" +// is not added to the go build command line. +// +build !appengine,!disableunsafe + +/* +This test file is part of the spew package rather than than the spew_test +package because it needs access to internals to properly test certain cases +which are not possible via the public interface since they should never happen. +*/ + +package spew + +import ( + "bytes" + "reflect" + "testing" + "unsafe" +) + +// changeKind uses unsafe to intentionally change the kind of a reflect.Value to +// the maximum kind value which does not exist. This is needed to test the +// fallback code which punts to the standard fmt library for new types that +// might get added to the language. +func changeKind(v *reflect.Value, readOnly bool) { + rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag)) + *rvf = *rvf | ((1< + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew + +import ( + "fmt" + "io" +) + +// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the formatted string as a value that satisfies error. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Errorf(format string, a ...interface{}) (err error) { + return fmt.Errorf(format, convertArgs(a)...) +} + +// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprint(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprint(w, convertArgs(a)...) +} + +// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) { + return fmt.Fprintf(w, format, convertArgs(a)...) +} + +// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it +// passed with a default Formatter interface returned by NewFormatter. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b)) +func Fprintln(w io.Writer, a ...interface{}) (n int, err error) { + return fmt.Fprintln(w, convertArgs(a)...) +} + +// Print is a wrapper for fmt.Print that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b)) +func Print(a ...interface{}) (n int, err error) { + return fmt.Print(convertArgs(a)...) +} + +// Printf is a wrapper for fmt.Printf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Printf(format string, a ...interface{}) (n int, err error) { + return fmt.Printf(format, convertArgs(a)...) +} + +// Println is a wrapper for fmt.Println that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the number of bytes written and any write error encountered. See +// NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b)) +func Println(a ...interface{}) (n int, err error) { + return fmt.Println(convertArgs(a)...) +} + +// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprint(a ...interface{}) string { + return fmt.Sprint(convertArgs(a)...) +} + +// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were +// passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, convertArgs(a)...) +} + +// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it +// were passed with a default Formatter interface returned by NewFormatter. It +// returns the resulting string. See NewFormatter for formatting details. +// +// This function is shorthand for the following syntax: +// +// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b)) +func Sprintln(a ...interface{}) string { + return fmt.Sprintln(convertArgs(a)...) +} + +// convertArgs accepts a slice of arguments and returns a slice of the same +// length with each argument converted to a default spew Formatter interface. +func convertArgs(args []interface{}) (formatters []interface{}) { + formatters = make([]interface{}, len(args)) + for index, arg := range args { + formatters[index] = NewFormatter(arg) + } + return formatters +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/spew_test.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/spew_test.go new file mode 100644 index 00000000000..dbbc0856792 --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/spew_test.go @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2013 Dave Collins + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package spew_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/davecgh/go-spew/spew" +) + +// spewFunc is used to identify which public function of the spew package or +// ConfigState a test applies to. +type spewFunc int + +const ( + fCSFdump spewFunc = iota + fCSFprint + fCSFprintf + fCSFprintln + fCSPrint + fCSPrintln + fCSSdump + fCSSprint + fCSSprintf + fCSSprintln + fCSErrorf + fCSNewFormatter + fErrorf + fFprint + fFprintln + fPrint + fPrintln + fSdump + fSprint + fSprintf + fSprintln +) + +// Map of spewFunc values to names for pretty printing. +var spewFuncStrings = map[spewFunc]string{ + fCSFdump: "ConfigState.Fdump", + fCSFprint: "ConfigState.Fprint", + fCSFprintf: "ConfigState.Fprintf", + fCSFprintln: "ConfigState.Fprintln", + fCSSdump: "ConfigState.Sdump", + fCSPrint: "ConfigState.Print", + fCSPrintln: "ConfigState.Println", + fCSSprint: "ConfigState.Sprint", + fCSSprintf: "ConfigState.Sprintf", + fCSSprintln: "ConfigState.Sprintln", + fCSErrorf: "ConfigState.Errorf", + fCSNewFormatter: "ConfigState.NewFormatter", + fErrorf: "spew.Errorf", + fFprint: "spew.Fprint", + fFprintln: "spew.Fprintln", + fPrint: "spew.Print", + fPrintln: "spew.Println", + fSdump: "spew.Sdump", + fSprint: "spew.Sprint", + fSprintf: "spew.Sprintf", + fSprintln: "spew.Sprintln", +} + +func (f spewFunc) String() string { + if s, ok := spewFuncStrings[f]; ok { + return s + } + return fmt.Sprintf("Unknown spewFunc (%d)", int(f)) +} + +// spewTest is used to describe a test to be performed against the public +// functions of the spew package or ConfigState. +type spewTest struct { + cs *spew.ConfigState + f spewFunc + format string + in interface{} + want string +} + +// spewTests houses the tests to be performed against the public functions of +// the spew package and ConfigState. +// +// These tests are only intended to ensure the public functions are exercised +// and are intentionally not exhaustive of types. The exhaustive type +// tests are handled in the dump and format tests. +var spewTests []spewTest + +// redirStdout is a helper function to return the standard output from f as a +// byte slice. +func redirStdout(f func()) ([]byte, error) { + tempFile, err := ioutil.TempFile("", "ss-test") + if err != nil { + return nil, err + } + fileName := tempFile.Name() + defer os.Remove(fileName) // Ignore error + + origStdout := os.Stdout + os.Stdout = tempFile + f() + os.Stdout = origStdout + tempFile.Close() + + return ioutil.ReadFile(fileName) +} + +func initSpewTests() { + // Config states with various settings. + scsDefault := spew.NewDefaultConfig() + scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true} + scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true} + scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1} + scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true} + + // Variables for tests on types which implement Stringer interface with and + // without a pointer receiver. + ts := stringer("test") + tps := pstringer("test") + + // depthTester is used to test max depth handling for structs, array, slices + // and maps. + type depthTester struct { + ic indirCir1 + arr [1]string + slice []string + m map[string]int + } + dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"}, + map[string]int{"one": 1}} + + // Variable for tests on types which implement error interface. + te := customError(10) + + spewTests = []spewTest{ + {scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"}, + {scsDefault, fCSFprint, "", int16(32767), "32767"}, + {scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"}, + {scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"}, + {scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"}, + {scsDefault, fCSPrintln, "", uint8(255), "255\n"}, + {scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"}, + {scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"}, + {scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"}, + {scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"}, + {scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"}, + {scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"}, + {scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"}, + {scsDefault, fFprint, "", float32(3.14), "3.14"}, + {scsDefault, fFprintln, "", float64(6.28), "6.28\n"}, + {scsDefault, fPrint, "", true, "true"}, + {scsDefault, fPrintln, "", false, "false\n"}, + {scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"}, + {scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"}, + {scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"}, + {scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"}, + {scsNoMethods, fCSFprint, "", ts, "test"}, + {scsNoMethods, fCSFprint, "", &ts, "<*>test"}, + {scsNoMethods, fCSFprint, "", tps, "test"}, + {scsNoMethods, fCSFprint, "", &tps, "<*>test"}, + {scsNoPmethods, fCSFprint, "", ts, "stringer test"}, + {scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"}, + {scsNoPmethods, fCSFprint, "", tps, "test"}, + {scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"}, + {scsMaxDepth, fCSFprint, "", dt, "{{} [] [] map[]}"}, + {scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" + + " ic: (spew_test.indirCir1) {\n \n },\n" + + " arr: ([1]string) (len=1 cap=1) {\n \n },\n" + + " slice: ([]string) (len=1 cap=1) {\n \n },\n" + + " m: (map[string]int) (len=1) {\n \n }\n}\n"}, + {scsContinue, fCSFprint, "", ts, "(stringer test) test"}, + {scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " + + "(len=4) (stringer test) \"test\"\n"}, + {scsContinue, fCSFprint, "", te, "(error: 10) 10"}, + {scsContinue, fCSFdump, "", te, "(spew_test.customError) " + + "(error: 10) 10\n"}, + } +} + +// TestSpew executes all of the tests described by spewTests. +func TestSpew(t *testing.T) { + initSpewTests() + + t.Logf("Running %d tests", len(spewTests)) + for i, test := range spewTests { + buf := new(bytes.Buffer) + switch test.f { + case fCSFdump: + test.cs.Fdump(buf, test.in) + + case fCSFprint: + test.cs.Fprint(buf, test.in) + + case fCSFprintf: + test.cs.Fprintf(buf, test.format, test.in) + + case fCSFprintln: + test.cs.Fprintln(buf, test.in) + + case fCSPrint: + b, err := redirStdout(func() { test.cs.Print(test.in) }) + if err != nil { + t.Errorf("%v #%d %v", test.f, i, err) + continue + } + buf.Write(b) + + case fCSPrintln: + b, err := redirStdout(func() { test.cs.Println(test.in) }) + if err != nil { + t.Errorf("%v #%d %v", test.f, i, err) + continue + } + buf.Write(b) + + case fCSSdump: + str := test.cs.Sdump(test.in) + buf.WriteString(str) + + case fCSSprint: + str := test.cs.Sprint(test.in) + buf.WriteString(str) + + case fCSSprintf: + str := test.cs.Sprintf(test.format, test.in) + buf.WriteString(str) + + case fCSSprintln: + str := test.cs.Sprintln(test.in) + buf.WriteString(str) + + case fCSErrorf: + err := test.cs.Errorf(test.format, test.in) + buf.WriteString(err.Error()) + + case fCSNewFormatter: + fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in)) + + case fErrorf: + err := spew.Errorf(test.format, test.in) + buf.WriteString(err.Error()) + + case fFprint: + spew.Fprint(buf, test.in) + + case fFprintln: + spew.Fprintln(buf, test.in) + + case fPrint: + b, err := redirStdout(func() { spew.Print(test.in) }) + if err != nil { + t.Errorf("%v #%d %v", test.f, i, err) + continue + } + buf.Write(b) + + case fPrintln: + b, err := redirStdout(func() { spew.Println(test.in) }) + if err != nil { + t.Errorf("%v #%d %v", test.f, i, err) + continue + } + buf.Write(b) + + case fSdump: + str := spew.Sdump(test.in) + buf.WriteString(str) + + case fSprint: + str := spew.Sprint(test.in) + buf.WriteString(str) + + case fSprintf: + str := spew.Sprintf(test.format, test.in) + buf.WriteString(str) + + case fSprintln: + str := spew.Sprintln(test.in) + buf.WriteString(str) + + default: + t.Errorf("%v #%d unrecognized function", test.f, i) + continue + } + s := buf.String() + if test.want != s { + t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want) + continue + } + } +} diff --git a/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go new file mode 100644 index 00000000000..5c87dd456ed --- /dev/null +++ b/Godeps/_workspace/src/github.com/davecgh/go-spew/spew/testdata/dumpcgo.go @@ -0,0 +1,82 @@ +// Copyright (c) 2013 Dave Collins +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +// NOTE: Due to the following build constraints, this file will only be compiled +// when both cgo is supported and "-tags testcgo" is added to the go test +// command line. This code should really only be in the dumpcgo_test.go file, +// but unfortunately Go will not allow cgo in test files, so this is a +// workaround to allow cgo types to be tested. This configuration is used +// because spew itself does not require cgo to run even though it does handle +// certain cgo types specially. Rather than forcing all clients to require cgo +// and an external C compiler just to run the tests, this scheme makes them +// optional. +// +build cgo,testcgo + +package testdata + +/* +#include +typedef unsigned char custom_uchar_t; + +char *ncp = 0; +char *cp = "test"; +char ca[6] = {'t', 'e', 's', 't', '2', '\0'}; +unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'}; +signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'}; +uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'}; +custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'}; +*/ +import "C" + +// GetCgoNullCharPointer returns a null char pointer via cgo. This is only +// used for tests. +func GetCgoNullCharPointer() interface{} { + return C.ncp +} + +// GetCgoCharPointer returns a char pointer via cgo. This is only used for +// tests. +func GetCgoCharPointer() interface{} { + return C.cp +} + +// GetCgoCharArray returns a char array via cgo and the array's len and cap. +// This is only used for tests. +func GetCgoCharArray() (interface{}, int, int) { + return C.ca, len(C.ca), cap(C.ca) +} + +// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the +// array's len and cap. This is only used for tests. +func GetCgoUnsignedCharArray() (interface{}, int, int) { + return C.uca, len(C.uca), cap(C.uca) +} + +// GetCgoSignedCharArray returns a signed char array via cgo and the array's len +// and cap. This is only used for tests. +func GetCgoSignedCharArray() (interface{}, int, int) { + return C.sca, len(C.sca), cap(C.sca) +} + +// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and +// cap. This is only used for tests. +func GetCgoUint8tArray() (interface{}, int, int) { + return C.ui8ta, len(C.ui8ta), cap(C.ui8ta) +} + +// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via +// cgo and the array's len and cap. This is only used for tests. +func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) { + return C.tuca, len(C.tuca), cap(C.tuca) +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore b/Godeps/_workspace/src/github.com/go-ldap/ldap/.gitignore new file mode 100644 index 00000000000..e69de29bb2d diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml b/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml new file mode 100644 index 00000000000..f90ee667c09 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/.travis.yml @@ -0,0 +1,12 @@ +language: go +go: + - 1.2 + - 1.3 + - tip +install: + - go get gopkg.in/asn1-ber.v1 + - go get gopkg.in/ldap.v1 + - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover + - go build -v ./... +script: + - go test -v -cover ./... diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE b/Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE new file mode 100644 index 00000000000..74487567632 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md b/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md new file mode 100644 index 00000000000..c940520461e --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/README.md @@ -0,0 +1,48 @@ +[![GoDoc](https://godoc.org/gopkg.in/ldap.v1?status.svg)](https://godoc.org/gopkg.in/ldap.v1) [![Build Status](https://travis-ci.org/go-ldap/ldap.svg)](https://travis-ci.org/go-ldap/ldap) + +# Basic LDAP v3 functionality for the GO programming language. + +## Required Librarys: + + - gopkg.in/asn1-ber.v1 + +## Working: + + - Connecting to LDAP server + - Binding to LDAP server + - Searching for entries + - Compiling string filters to LDAP filters + - Paging Search Results + - Modify Requests / Responses + +## Examples: + + - search + - modify + +## Tests Implemented: + + - Filter Compile / Decompile + +## TODO: + + - Add Requests / Responses + - Delete Requests / Responses + - Modify DN Requests / Responses + - Compare Requests / Responses + - Implement Tests / Benchmarks + +--- +This feature is disabled at the moment, because in some cases the "Search Request Done" packet will be handled before the last "Search Request Entry": + + - Mulitple internal goroutines to handle network traffic + Makes library goroutine safe + Can perform multiple search requests at the same time and return + the results to the proper goroutine. All requests are blocking requests, + so the goroutine does not need special handling + +--- + +The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/) +The design is licensed under the Creative Commons 3.0 Attributions license. +Read this article for more details: http://blog.golang.org/gopher diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go new file mode 100644 index 00000000000..4ad4b896c26 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/bind.go @@ -0,0 +1,135 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ldap + +import ( + "errors" + + "gopkg.in/asn1-ber.v1" +) + +type SimpleBindRequest struct { + Username string + Password string + Controls []Control +} + +type SimpleBindResult struct { + Controls []Control +} + +func NewSimpleBindRequest(username string, password string, controls []Control) *SimpleBindRequest { + return &SimpleBindRequest{ + Username: username, + Password: password, + Controls: controls, + } +} + +func (bindRequest *SimpleBindRequest) encode() *ber.Packet { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, bindRequest.Username, "User Name")) + request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, bindRequest.Password, "Password")) + + request.AppendChild(encodeControls(bindRequest.Controls)) + + return request +} + +func (l *Conn) SimpleBind(simpleBindRequest *SimpleBindRequest) (*SimpleBindResult, error) { + messageID := l.nextMessageID() + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + encodedBindRequest := simpleBindRequest.encode() + packet.AppendChild(encodedBindRequest) + + if l.Debug { + ber.PrintPacket(packet) + } + + channel, err := l.sendMessage(packet) + if err != nil { + return nil, err + } + if channel == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + packet = <-channel + if packet == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return nil, err + } + ber.PrintPacket(packet) + } + + result := &SimpleBindResult{ + Controls: make([]Control, 0), + } + + if len(packet.Children) == 3 { + for _, child := range packet.Children[2].Children { + result.Controls = append(result.Controls, DecodeControl(child)) + } + } + + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return result, NewError(resultCode, errors.New(resultDescription)) + } + + return result, nil +} + +func (l *Conn) Bind(username, password string) error { + messageID := l.nextMessageID() + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + bindRequest := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationBindRequest, nil, "Bind Request") + bindRequest.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, 3, "Version")) + bindRequest.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, username, "User Name")) + bindRequest.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, password, "Password")) + packet.AppendChild(bindRequest) + + if l.Debug { + ber.PrintPacket(packet) + } + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + packet = <-channel + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve response")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return err + } + ber.PrintPacket(packet) + } + + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return NewError(resultCode, errors.New(resultDescription)) + } + + return nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go new file mode 100644 index 00000000000..802e9cc932a --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/compare.go @@ -0,0 +1,85 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains Compare functionality +// +// https://tools.ietf.org/html/rfc4511 +// +// CompareRequest ::= [APPLICATION 14] SEQUENCE { +// entry LDAPDN, +// ava AttributeValueAssertion } +// +// AttributeValueAssertion ::= SEQUENCE { +// attributeDesc AttributeDescription, +// assertionValue AssertionValue } +// +// AttributeDescription ::= LDAPString +// -- Constrained to +// -- [RFC4512] +// +// AttributeValue ::= OCTET STRING +// + +package ldap + +import ( + "errors" + "fmt" + + "gopkg.in/asn1-ber.v1" +) + +// Compare checks to see if the attribute of the dn matches value. Returns true if it does otherwise +// false with any error that occurs if any. +func (l *Conn) Compare(dn, attribute, value string) (bool, error) { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationCompareRequest, nil, "Compare Request") + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, dn, "DN")) + + ava := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "AttributeValueAssertion") + ava.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "AttributeDesc")) + ava.AppendChild(ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagOctetString, value, "AssertionValue")) + request.AppendChild(ava) + packet.AppendChild(request) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return false, err + } + if channel == nil { + return false, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return false, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return false, err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Tag == ApplicationCompareResponse { + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode == LDAPResultCompareTrue { + return true, nil + } else if resultCode == LDAPResultCompareFalse { + return false, nil + } else { + return false, NewError(resultCode, errors.New(resultDescription)) + } + } + return false, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag) +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go new file mode 100644 index 00000000000..c51e1afe87d --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/conn.go @@ -0,0 +1,364 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ldap + +import ( + "crypto/tls" + "errors" + "fmt" + "gopkg.in/asn1-ber.v1" + "log" + "net" + "sync" + "time" +) + +const ( + MessageQuit = 0 + MessageRequest = 1 + MessageResponse = 2 + MessageFinish = 3 +) + +type messagePacket struct { + Op int + MessageID int64 + Packet *ber.Packet + Channel chan *ber.Packet +} + +type sendMessageFlags uint + +const ( + startTLS sendMessageFlags = 1 << iota +) + +// Conn represents an LDAP Connection +type Conn struct { + conn net.Conn + isTLS bool + isClosing bool + isStartingTLS bool + Debug debugging + chanConfirm chan bool + chanResults map[int64]chan *ber.Packet + chanMessage chan *messagePacket + chanMessageID chan int64 + wgSender sync.WaitGroup + wgClose sync.WaitGroup + once sync.Once + outstandingRequests uint + messageMutex sync.Mutex +} + +// DefaultTimeout is a package-level variable that sets the timeout value +// used for the Dial and DialTLS methods. +// +// WARNING: since this is a package-level variable, setting this value from +// multiple places will probably result in undesired behaviour. +var DefaultTimeout = 60 * time.Second + +// Dial connects to the given address on the given network using net.Dial +// and then returns a new Conn for the connection. +func Dial(network, addr string) (*Conn, error) { + c, err := net.DialTimeout(network, addr, DefaultTimeout) + if err != nil { + return nil, NewError(ErrorNetwork, err) + } + conn := NewConn(c, false) + conn.Start() + return conn, nil +} + +// DialTLS connects to the given address on the given network using tls.Dial +// and then returns a new Conn for the connection. +func DialTLS(network, addr string, config *tls.Config) (*Conn, error) { + dc, err := net.DialTimeout(network, addr, DefaultTimeout) + if err != nil { + return nil, NewError(ErrorNetwork, err) + } + c := tls.Client(dc, config) + err = c.Handshake() + if err != nil { + // Handshake error, close the established connection before we return an error + dc.Close() + return nil, NewError(ErrorNetwork, err) + } + conn := NewConn(c, true) + conn.Start() + return conn, nil +} + +// NewConn returns a new Conn using conn for network I/O. +func NewConn(conn net.Conn, isTLS bool) *Conn { + return &Conn{ + conn: conn, + chanConfirm: make(chan bool), + chanMessageID: make(chan int64), + chanMessage: make(chan *messagePacket, 10), + chanResults: map[int64]chan *ber.Packet{}, + isTLS: isTLS, + } +} + +func (l *Conn) Start() { + go l.reader() + go l.processMessages() + l.wgClose.Add(1) +} + +// Close closes the connection. +func (l *Conn) Close() { + l.once.Do(func() { + l.isClosing = true + l.wgSender.Wait() + + l.Debug.Printf("Sending quit message and waiting for confirmation") + l.chanMessage <- &messagePacket{Op: MessageQuit} + <-l.chanConfirm + close(l.chanMessage) + + l.Debug.Printf("Closing network connection") + if err := l.conn.Close(); err != nil { + log.Print(err) + } + + l.wgClose.Done() + }) + l.wgClose.Wait() +} + +// Returns the next available messageID +func (l *Conn) nextMessageID() int64 { + if l.chanMessageID != nil { + if messageID, ok := <-l.chanMessageID; ok { + return messageID + } + } + return 0 +} + +// StartTLS sends the command to start a TLS session and then creates a new TLS Client +func (l *Conn) StartTLS(config *tls.Config) error { + messageID := l.nextMessageID() + + if l.isTLS { + return NewError(ErrorNetwork, errors.New("ldap: already encrypted")) + } + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Start TLS") + request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, "1.3.6.1.4.1.1466.20037", "TLS Extended Command")) + packet.AppendChild(request) + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessageWithFlags(packet, startTLS) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + l.finishMessage(messageID) + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + l.Close() + return err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Children[0].Value.(int64) == 0 { + conn := tls.Client(l.conn, config) + + if err := conn.Handshake(); err != nil { + l.Close() + return NewError(ErrorNetwork, fmt.Errorf("TLS handshake failed (%v)", err)) + } + + l.isTLS = true + l.conn = conn + } + go l.reader() + + return nil +} + +func (l *Conn) sendMessage(packet *ber.Packet) (chan *ber.Packet, error) { + return l.sendMessageWithFlags(packet, 0) +} + +func (l *Conn) sendMessageWithFlags(packet *ber.Packet, flags sendMessageFlags) (chan *ber.Packet, error) { + if l.isClosing { + return nil, NewError(ErrorNetwork, errors.New("ldap: connection closed")) + } + l.messageMutex.Lock() + l.Debug.Printf("flags&startTLS = %d", flags&startTLS) + if l.isStartingTLS { + l.messageMutex.Unlock() + return nil, NewError(ErrorNetwork, errors.New("ldap: connection is in startls phase.")) + } + if flags&startTLS != 0 { + if l.outstandingRequests != 0 { + l.messageMutex.Unlock() + return nil, NewError(ErrorNetwork, errors.New("ldap: cannot StartTLS with outstanding requests")) + } else { + l.isStartingTLS = true + } + } + l.outstandingRequests++ + + l.messageMutex.Unlock() + + out := make(chan *ber.Packet) + message := &messagePacket{ + Op: MessageRequest, + MessageID: packet.Children[0].Value.(int64), + Packet: packet, + Channel: out, + } + l.sendProcessMessage(message) + return out, nil +} + +func (l *Conn) finishMessage(messageID int64) { + if l.isClosing { + return + } + + l.messageMutex.Lock() + l.outstandingRequests-- + if l.isStartingTLS { + l.isStartingTLS = false + } + l.messageMutex.Unlock() + + message := &messagePacket{ + Op: MessageFinish, + MessageID: messageID, + } + l.sendProcessMessage(message) +} + +func (l *Conn) sendProcessMessage(message *messagePacket) bool { + if l.isClosing { + return false + } + l.wgSender.Add(1) + l.chanMessage <- message + l.wgSender.Done() + return true +} + +func (l *Conn) processMessages() { + defer func() { + if err := recover(); err != nil { + log.Printf("ldap: recovered panic in processMessages: %v", err) + } + for messageID, channel := range l.chanResults { + l.Debug.Printf("Closing channel for MessageID %d", messageID) + close(channel) + delete(l.chanResults, messageID) + } + close(l.chanMessageID) + l.chanConfirm <- true + close(l.chanConfirm) + }() + + var messageID int64 = 1 + for { + select { + case l.chanMessageID <- messageID: + messageID++ + case messagePacket, ok := <-l.chanMessage: + if !ok { + l.Debug.Printf("Shutting down - message channel is closed") + return + } + switch messagePacket.Op { + case MessageQuit: + l.Debug.Printf("Shutting down - quit message received") + return + case MessageRequest: + // Add to message list and write to network + l.Debug.Printf("Sending message %d", messagePacket.MessageID) + l.chanResults[messagePacket.MessageID] = messagePacket.Channel + // go routine + buf := messagePacket.Packet.Bytes() + + _, err := l.conn.Write(buf) + if err != nil { + l.Debug.Printf("Error Sending Message: %s", err.Error()) + break + } + case MessageResponse: + l.Debug.Printf("Receiving message %d", messagePacket.MessageID) + if chanResult, ok := l.chanResults[messagePacket.MessageID]; ok { + chanResult <- messagePacket.Packet + } else { + log.Printf("Received unexpected message %d", messagePacket.MessageID) + ber.PrintPacket(messagePacket.Packet) + } + case MessageFinish: + // Remove from message list + l.Debug.Printf("Finished message %d", messagePacket.MessageID) + close(l.chanResults[messagePacket.MessageID]) + delete(l.chanResults, messagePacket.MessageID) + } + } + } +} + +func (l *Conn) reader() { + cleanstop := false + defer func() { + if err := recover(); err != nil { + log.Printf("ldap: recovered panic in reader: %v", err) + } + if !cleanstop { + l.Close() + } + }() + + for { + if cleanstop { + l.Debug.Printf("reader clean stopping (without closing the connection)") + return + } + packet, err := ber.ReadPacket(l.conn) + if err != nil { + // A read error is expected here if we are closing the connection... + if !l.isClosing { + l.Debug.Printf("reader error: %s", err.Error()) + } + return + } + addLDAPDescriptions(packet) + if len(packet.Children) == 0 { + l.Debug.Printf("Received bad ldap packet") + continue + } + l.messageMutex.Lock() + if l.isStartingTLS { + cleanstop = true + } + l.messageMutex.Unlock() + message := &messagePacket{ + Op: MessageResponse, + MessageID: packet.Children[0].Value.(int64), + Packet: packet, + } + if !l.sendProcessMessage(message) { + return + } + + } +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go new file mode 100644 index 00000000000..562fbe43090 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/control.go @@ -0,0 +1,300 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ldap + +import ( + "fmt" + "strconv" + + "gopkg.in/asn1-ber.v1" +) + +const ( + ControlTypePaging = "1.2.840.113556.1.4.319" + ControlTypeBeheraPasswordPolicy = "1.3.6.1.4.1.42.2.27.8.5.1" + ControlTypeVChuPasswordMustChange = "2.16.840.1.113730.3.4.4" + ControlTypeVChuPasswordWarning = "2.16.840.1.113730.3.4.5" +) + +var ControlTypeMap = map[string]string{ + ControlTypePaging: "Paging", + ControlTypeBeheraPasswordPolicy: "Password Policy - Behera Draft", +} + +type Control interface { + GetControlType() string + Encode() *ber.Packet + String() string +} + +type ControlString struct { + ControlType string + Criticality bool + ControlValue string +} + +func (c *ControlString) GetControlType() string { + return c.ControlType +} + +func (c *ControlString) Encode() *ber.Packet { + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, c.ControlType, "Control Type ("+ControlTypeMap[c.ControlType]+")")) + if c.Criticality { + packet.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, c.Criticality, "Criticality")) + } + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, string(c.ControlValue), "Control Value")) + return packet +} + +func (c *ControlString) String() string { + return fmt.Sprintf("Control Type: %s (%q) Criticality: %t Control Value: %s", ControlTypeMap[c.ControlType], c.ControlType, c.Criticality, c.ControlValue) +} + +type ControlPaging struct { + PagingSize uint32 + Cookie []byte +} + +func (c *ControlPaging) GetControlType() string { + return ControlTypePaging +} + +func (c *ControlPaging) Encode() *ber.Packet { + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypePaging, "Control Type ("+ControlTypeMap[ControlTypePaging]+")")) + + p2 := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Control Value (Paging)") + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Search Control Value") + seq.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(c.PagingSize), "Paging Size")) + cookie := ber.Encode(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, nil, "Cookie") + cookie.Value = c.Cookie + cookie.Data.Write(c.Cookie) + seq.AppendChild(cookie) + p2.AppendChild(seq) + + packet.AppendChild(p2) + return packet +} + +func (c *ControlPaging) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t PagingSize: %d Cookie: %q", + ControlTypeMap[ControlTypePaging], + ControlTypePaging, + false, + c.PagingSize, + c.Cookie) +} + +func (c *ControlPaging) SetCookie(cookie []byte) { + c.Cookie = cookie +} + +type ControlBeheraPasswordPolicy struct { + Expire int64 + Grace int64 + Error int8 + ErrorString string +} + +func (c *ControlBeheraPasswordPolicy) GetControlType() string { + return ControlTypeBeheraPasswordPolicy +} + +func (c *ControlBeheraPasswordPolicy) Encode() *ber.Packet { + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Control") + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, ControlTypeBeheraPasswordPolicy, "Control Type ("+ControlTypeMap[ControlTypeBeheraPasswordPolicy]+")")) + + return packet +} + +func (c *ControlBeheraPasswordPolicy) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t Expire: %d Grace: %d Error: %d, ErrorString: %s", + ControlTypeMap[ControlTypeBeheraPasswordPolicy], + ControlTypeBeheraPasswordPolicy, + false, + c.Expire, + c.Grace, + c.Error, + c.ErrorString) +} + +type ControlVChuPasswordMustChange struct { + MustChange bool +} + +func (c *ControlVChuPasswordMustChange) GetControlType() string { + return ControlTypeVChuPasswordMustChange +} + +func (c *ControlVChuPasswordMustChange) Encode() *ber.Packet { + return nil +} + +func (c *ControlVChuPasswordMustChange) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t MustChange: %b", + ControlTypeMap[ControlTypeVChuPasswordMustChange], + ControlTypeVChuPasswordMustChange, + false, + c.MustChange) +} + +type ControlVChuPasswordWarning struct { + Expire int64 +} + +func (c *ControlVChuPasswordWarning) GetControlType() string { + return ControlTypeVChuPasswordWarning +} + +func (c *ControlVChuPasswordWarning) Encode() *ber.Packet { + return nil +} + +func (c *ControlVChuPasswordWarning) String() string { + return fmt.Sprintf( + "Control Type: %s (%q) Criticality: %t Expire: %b", + ControlTypeMap[ControlTypeVChuPasswordWarning], + ControlTypeVChuPasswordWarning, + false, + c.Expire) +} + +func FindControl(controls []Control, controlType string) Control { + for _, c := range controls { + if c.GetControlType() == controlType { + return c + } + } + return nil +} + +func DecodeControl(packet *ber.Packet) Control { + ControlType := packet.Children[0].Value.(string) + Criticality := false + + packet.Children[0].Description = "Control Type (" + ControlTypeMap[ControlType] + ")" + value := packet.Children[1] + if len(packet.Children) == 3 { + value = packet.Children[2] + packet.Children[1].Description = "Criticality" + Criticality = packet.Children[1].Value.(bool) + } + + value.Description = "Control Value" + switch ControlType { + case ControlTypePaging: + value.Description += " (Paging)" + c := new(ControlPaging) + if value.Value != nil { + valueChildren := ber.DecodePacket(value.Data.Bytes()) + value.Data.Truncate(0) + value.Value = nil + value.AppendChild(valueChildren) + } + value = value.Children[0] + value.Description = "Search Control Value" + value.Children[0].Description = "Paging Size" + value.Children[1].Description = "Cookie" + c.PagingSize = uint32(value.Children[0].Value.(int64)) + c.Cookie = value.Children[1].Data.Bytes() + value.Children[1].Value = c.Cookie + return c + case ControlTypeBeheraPasswordPolicy: + value.Description += " (Password Policy - Behera)" + c := NewControlBeheraPasswordPolicy() + if value.Value != nil { + valueChildren := ber.DecodePacket(value.Data.Bytes()) + value.Data.Truncate(0) + value.Value = nil + value.AppendChild(valueChildren) + } + + sequence := value.Children[0] + + for _, child := range sequence.Children { + if child.Tag == 0 { + //Warning + child := child.Children[0] + packet := ber.DecodePacket(child.Data.Bytes()) + val, ok := packet.Value.(int64) + if ok { + if child.Tag == 0 { + //timeBeforeExpiration + c.Expire = val + child.Value = c.Expire + } else if child.Tag == 1 { + //graceAuthNsRemaining + c.Grace = val + child.Value = c.Grace + } + } + } else if child.Tag == 1 { + // Error + packet := ber.DecodePacket(child.Data.Bytes()) + val, ok := packet.Value.(int8) + if !ok { + // what to do? + val = -1 + } + c.Error = val + child.Value = c.Error + c.ErrorString = BeheraPasswordPolicyErrorMap[c.Error] + } + } + return c + case ControlTypeVChuPasswordMustChange: + c := &ControlVChuPasswordMustChange{MustChange: true} + return c + case ControlTypeVChuPasswordWarning: + c := &ControlVChuPasswordWarning{Expire: -1} + expireStr := ber.DecodeString(value.Data.Bytes()) + + expire, err := strconv.ParseInt(expireStr, 10, 64) + if err != nil { + return nil + } + c.Expire = expire + value.Value = c.Expire + + return c + } + c := new(ControlString) + c.ControlType = ControlType + c.Criticality = Criticality + c.ControlValue = value.Value.(string) + return c +} + +func NewControlString(controlType string, criticality bool, controlValue string) *ControlString { + return &ControlString{ + ControlType: controlType, + Criticality: criticality, + ControlValue: controlValue, + } +} + +func NewControlPaging(pagingSize uint32) *ControlPaging { + return &ControlPaging{PagingSize: pagingSize} +} + +func NewControlBeheraPasswordPolicy() *ControlBeheraPasswordPolicy { + return &ControlBeheraPasswordPolicy{ + Expire: -1, + Grace: -1, + Error: -1, + } +} + +func encodeControls(controls []Control) *ber.Packet { + packet := ber.Encode(ber.ClassContext, ber.TypeConstructed, 0, nil, "Controls") + for _, control := range controls { + packet.AppendChild(control.Encode()) + } + return packet +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go new file mode 100644 index 00000000000..b8a7ecbff1f --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/debug.go @@ -0,0 +1,24 @@ +package ldap + +import ( + "log" + + "gopkg.in/asn1-ber.v1" +) + +// debbuging type +// - has a Printf method to write the debug output +type debugging bool + +// write debug output +func (debug debugging) Printf(format string, args ...interface{}) { + if debug { + log.Printf(format, args...) + } +} + +func (debug debugging) PrintPacket(packet *ber.Packet) { + if debug { + ber.PrintPacket(packet) + } +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go new file mode 100644 index 00000000000..31d52db4b23 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn.go @@ -0,0 +1,155 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains DN parsing functionallity +// +// https://tools.ietf.org/html/rfc4514 +// +// distinguishedName = [ relativeDistinguishedName +// *( COMMA relativeDistinguishedName ) ] +// relativeDistinguishedName = attributeTypeAndValue +// *( PLUS attributeTypeAndValue ) +// attributeTypeAndValue = attributeType EQUALS attributeValue +// attributeType = descr / numericoid +// attributeValue = string / hexstring +// +// ; The following characters are to be escaped when they appear +// ; in the value to be encoded: ESC, one of , leading +// ; SHARP or SPACE, trailing SPACE, and NULL. +// string = [ ( leadchar / pair ) [ *( stringchar / pair ) +// ( trailchar / pair ) ] ] +// +// leadchar = LUTF1 / UTFMB +// LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A / +// %x3D / %x3F-5B / %x5D-7F +// +// trailchar = TUTF1 / UTFMB +// TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A / +// %x3D / %x3F-5B / %x5D-7F +// +// stringchar = SUTF1 / UTFMB +// SUTF1 = %x01-21 / %x23-2A / %x2D-3A / +// %x3D / %x3F-5B / %x5D-7F +// +// pair = ESC ( ESC / special / hexpair ) +// special = escaped / SPACE / SHARP / EQUALS +// escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE +// hexstring = SHARP 1*hexpair +// hexpair = HEX HEX +// +// where the productions , , , , +// , , , , , , , , +// , , and are defined in [RFC4512]. +// + +package ldap + +import ( + "bytes" + "errors" + "fmt" + "strings" + enchex "encoding/hex" + + ber "gopkg.in/asn1-ber.v1" +) + +type AttributeTypeAndValue struct { + Type string + Value string +} + +type RelativeDN struct { + Attributes []*AttributeTypeAndValue +} + +type DN struct { + RDNs []*RelativeDN +} + +func ParseDN(str string) (*DN, error) { + dn := new(DN) + dn.RDNs = make([]*RelativeDN, 0) + rdn := new (RelativeDN) + rdn.Attributes = make([]*AttributeTypeAndValue, 0) + buffer := bytes.Buffer{} + attribute := new(AttributeTypeAndValue) + escaping := false + + for i := 0; i < len(str); i++ { + char := str[i] + if escaping { + escaping = false + switch char { + case ' ', '"', '#', '+', ',', ';', '<', '=', '>', '\\': + buffer.WriteByte(char) + continue + } + // Not a special character, assume hex encoded octet + if len(str) == i+1 { + return nil, errors.New("Got corrupted escaped character") + } + + dst := []byte{0} + n, err := enchex.Decode([]byte(dst), []byte(str[i:i+2])) + if err != nil { + return nil, errors.New( + fmt.Sprintf("Failed to decode escaped character: %s", err)) + } else if n != 1 { + return nil, errors.New( + fmt.Sprintf("Expected 1 byte when un-escaping, got %d", n)) + } + buffer.WriteByte(dst[0]) + i++ + } else if char == '\\' { + escaping = true + } else if char == '=' { + attribute.Type = buffer.String() + buffer.Reset() + // Special case: If the first character in the value is # the + // following data is BER encoded so we can just fast forward + // and decode. + if len(str) > i+1 && str[i+1] == '#' { + i += 2 + index := strings.IndexAny(str[i:], ",+") + data := str + if index > 0 { + data = str[i:i+index] + } else { + data = str[i:] + } + raw_ber, err := enchex.DecodeString(data) + if err != nil { + return nil, errors.New( + fmt.Sprintf("Failed to decode BER encoding: %s", err)) + } + packet := ber.DecodePacket(raw_ber) + buffer.WriteString(packet.Data.String()) + i += len(data)-1 + } + } else if char == ',' || char == '+' { + // We're done with this RDN or value, push it + attribute.Value = buffer.String() + rdn.Attributes = append(rdn.Attributes, attribute) + attribute = new(AttributeTypeAndValue) + if char == ',' { + dn.RDNs = append(dn.RDNs, rdn) + rdn = new(RelativeDN) + rdn.Attributes = make([]*AttributeTypeAndValue, 0) + } + buffer.Reset() + } else { + buffer.WriteByte(char) + } + } + if buffer.Len() > 0 { + if len(attribute.Type) == 0 { + return nil, errors.New("DN ended with incomplete type, value pair") + } + attribute.Value = buffer.String() + rdn.Attributes = append(rdn.Attributes, attribute) + dn.RDNs = append(dn.RDNs, rdn) + } + return dn, nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go new file mode 100644 index 00000000000..6740e1819c7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/dn_test.go @@ -0,0 +1,70 @@ +package ldap + +import ( + "reflect" + "testing" +) + +func TestSuccessfulDNParsing(t *testing.T) { + testcases := map[string]DN { + "": DN{[]*RelativeDN{}}, + "cn=Jim\\2C \\22Hasse Hö\\22 Hansson!,dc=dummy,dc=com": DN{[]*RelativeDN{ + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"cn", "Jim, \"Hasse Hö\" Hansson!"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"dc", "dummy"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"dc", "com"}, }},}}, + "UID=jsmith,DC=example,DC=net": DN{[]*RelativeDN{ + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"UID", "jsmith"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "example"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, + "OU=Sales+CN=J. Smith,DC=example,DC=net": DN{[]*RelativeDN{ + &RelativeDN{[]*AttributeTypeAndValue{ + &AttributeTypeAndValue{"OU", "Sales"}, + &AttributeTypeAndValue{"CN", "J. Smith"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "example"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, + "1.3.6.1.4.1.1466.0=#04024869": DN{[]*RelativeDN{ + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"},}},}}, + "1.3.6.1.4.1.1466.0=#04024869,DC=net": DN{[]*RelativeDN{ + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"1.3.6.1.4.1.1466.0", "Hi"},}}, + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"DC", "net"}, }},}}, + "CN=Lu\\C4\\8Di\\C4\\87": DN{[]*RelativeDN{ + &RelativeDN{[]*AttributeTypeAndValue{&AttributeTypeAndValue{"CN", "Lučić"},}},}}, + } + + for test, answer := range testcases { + dn, err := ParseDN(test) + if err != nil { + t.Errorf(err.Error()) + continue + } + if !reflect.DeepEqual(dn, &answer) { + t.Errorf("Parsed DN %s is not equal to the expected structure", test) + for _, rdn := range dn.RDNs { + for _, attribs := range rdn.Attributes { + t.Logf("#%v\n", attribs) + } + } + } + } +} + +func TestErrorDNParsing(t *testing.T) { + testcases := map[string]string { + "*": "DN ended with incomplete type, value pair", + "cn=Jim\\0Test": "Failed to decode escaped character: encoding/hex: invalid byte: U+0054 'T'", + "cn=Jim\\0": "Got corrupted escaped character", + "DC=example,=net": "DN ended with incomplete type, value pair", + "1=#0402486": "Failed to decode BER encoding: encoding/hex: odd length hex string", + } + + for test, answer := range testcases { + _, err := ParseDN(test) + if err == nil { + t.Errorf("Expected %s to fail parsing but succeeded\n", test) + } else if err.Error() != answer { + t.Errorf("Unexpected error on %s:\n%s\nvs.\n%s\n", test, answer, err.Error()) + } + } +} + + diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go new file mode 100644 index 00000000000..f20d39bc99f --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/doc.go @@ -0,0 +1,4 @@ +/* +Package ldap provides basic LDAP v3 functionality. +*/ +package ldap diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go new file mode 100644 index 00000000000..0d441f43b99 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/example_test.go @@ -0,0 +1,305 @@ +package ldap_test + +import ( + "crypto/tls" + "fmt" + "log" + + "github.com/go-ldap/ldap" +) + +// ExampleConn_Bind demonstrats how to bind a connection to an ldap user +// allowing access to restricted attrabutes that user has access to +func ExampleConn_Bind() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + err = l.Bind("cn=read-only-admin,dc=example,dc=com", "password") + if err != nil { + log.Fatal(err) + } +} + +// ExampleConn_Search demonstrates how to use the search interface +func ExampleConn_Search() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + searchRequest := ldap.NewSearchRequest( + "dc=example,dc=com", // The base dn to search + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + "(&(objectClass=organizationalPerson))", // The filter to apply + []string{"dn", "cn"}, // A list attributes to retrieve + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + log.Fatal(err) + } + + for _, entry := range sr.Entries { + fmt.Printf("%s: %v\n", entry.DN, entry.GetAttributeValue("cn")) + } +} + +// ExampleStartTLS demonstrates how to start a TLS connection +func ExampleConn_StartTLS() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + // Reconnect with TLS + err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + log.Fatal(err) + } + + // Opertations via l are now encrypted +} + +// ExampleConn_Compare demonstrates how to comapre an attribute with a value +func ExampleConn_Compare() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + matched, err := l.Compare("cn=user,dc=example,dc=com", "uid", "someuserid") + if err != nil { + log.Fatal(err) + } + + fmt.Println(matched) +} + +func ExampleConn_PasswordModify_admin() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + err = l.Bind("cn=admin,dc=example,dc=com", "password") + if err != nil { + log.Fatal(err) + } + + passwordModifyRequest := ldap.NewPasswordModifyRequest("cn=user,dc=example,dc=com", "", "NewPassword") + _, err = l.PasswordModify(passwordModifyRequest) + + if err != nil { + log.Fatalf("Password could not be changed: %s", err.Error()) + } +} + +func ExampleConn_PasswordModify_generatedPassword() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + err = l.Bind("cn=user,dc=example,dc=com", "password") + if err != nil { + log.Fatal(err) + } + + passwordModifyRequest := ldap.NewPasswordModifyRequest("", "OldPassword", "") + passwordModifyResponse, err := l.PasswordModify(passwordModifyRequest) + if err != nil { + log.Fatalf("Password could not be changed: %s", err.Error()) + } + + generatedPassword := passwordModifyResponse.GeneratedPassword + log.Printf("Generated password: %s\n", generatedPassword) +} + +func ExampleConn_PasswordModify_setNewPassword() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + err = l.Bind("cn=user,dc=example,dc=com", "password") + if err != nil { + log.Fatal(err) + } + + passwordModifyRequest := ldap.NewPasswordModifyRequest("", "OldPassword", "NewPassword") + _, err = l.PasswordModify(passwordModifyRequest) + + if err != nil { + log.Fatalf("Password could not be changed: %s", err.Error()) + } +} + +func ExampleConn_Modify() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + // Add a description, and replace the mail attributes + modify := ldap.NewModifyRequest("cn=user,dc=example,dc=com") + modify.Add("description", []string{"An example user"}) + modify.Replace("mail", []string{"user@example.org"}) + + err = l.Modify(modify) + if err != nil { + log.Fatal(err) + } +} + +// Example User Authentication shows how a typical application can verify a login attempt +func Example_userAuthentication() { + // The username and password we want to check + username := "someuser" + password := "userpassword" + + bindusername := "readonly" + bindpassword := "password" + + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + // Reconnect with TLS + err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + log.Fatal(err) + } + + // First bind with a read only user + err = l.Bind(bindusername, bindpassword) + if err != nil { + log.Fatal(err) + } + + // Search for the given username + searchRequest := ldap.NewSearchRequest( + "dc=example,dc=com", + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + fmt.Sprintf("(&(objectClass=organizationalPerson)&(uid=%s))", username), + []string{"dn"}, + nil, + ) + + sr, err := l.Search(searchRequest) + if err != nil { + log.Fatal(err) + } + + if len(sr.Entries) != 1 { + log.Fatal("User does not exist or too many entries returned") + } + + userdn := sr.Entries[0].DN + + // Bind as the user to verify their password + err = l.Bind(userdn, password) + if err != nil { + log.Fatal(err) + } + + // Rebind as the read only user for any futher queries + err = l.Bind(bindusername, bindpassword) + if err != nil { + log.Fatal(err) + } +} + +func Example_beherappolicy() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + controls := []ldap.Control{} + controls = append(controls, ldap.NewControlBeheraPasswordPolicy()) + bindRequest := ldap.NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", controls) + + r, err := l.SimpleBind(bindRequest) + ppolicyControl := ldap.FindControl(r.Controls, ldap.ControlTypeBeheraPasswordPolicy) + + var ppolicy *ldap.ControlBeheraPasswordPolicy + if ppolicyControl != nil { + ppolicy = ppolicyControl.(*ldap.ControlBeheraPasswordPolicy) + } else { + log.Printf("ppolicyControl response not avaliable.\n") + } + if err != nil { + errStr := "ERROR: Cannot bind: " + err.Error() + if ppolicy != nil && ppolicy.Error >= 0 { + errStr += ":" + ppolicy.ErrorString + } + log.Print(errStr) + } else { + logStr := "Login Ok" + if ppolicy != nil { + if ppolicy.Expire >= 0 { + logStr += fmt.Sprintf(". Password expires in %d seconds\n", ppolicy.Expire) + } else if ppolicy.Grace >= 0 { + logStr += fmt.Sprintf(". Password expired, %d grace logins remain\n", ppolicy.Grace) + } + } + log.Print(logStr) + } +} + +func Example_vchuppolicy() { + l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389)) + if err != nil { + log.Fatal(err) + } + defer l.Close() + l.Debug = true + + bindRequest := ldap.NewSimpleBindRequest("cn=admin,dc=example,dc=com", "password", nil) + + r, err := l.SimpleBind(bindRequest) + + passwordMustChangeControl := ldap.FindControl(r.Controls, ldap.ControlTypeVChuPasswordMustChange) + var passwordMustChange *ldap.ControlVChuPasswordMustChange + if passwordMustChangeControl != nil { + passwordMustChange = passwordMustChangeControl.(*ldap.ControlVChuPasswordMustChange) + } + + if passwordMustChange != nil && passwordMustChange.MustChange { + log.Printf("Password Must be changed.\n") + } + + passwordWarningControl := ldap.FindControl(r.Controls, ldap.ControlTypeVChuPasswordWarning) + + var passwordWarning *ldap.ControlVChuPasswordWarning + if passwordWarningControl != nil { + passwordWarning = passwordWarningControl.(*ldap.ControlVChuPasswordWarning) + } else { + log.Printf("ppolicyControl response not available.\n") + } + if err != nil { + log.Print("ERROR: Cannot bind: " + err.Error()) + } else { + logStr := "Login Ok" + if passwordWarning != nil { + if passwordWarning.Expire >= 0 { + logStr += fmt.Sprintf(". Password expires in %d seconds\n", passwordWarning.Expire) + } + } + log.Print(logStr) + } +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go new file mode 100644 index 00000000000..1ee1ff89d87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter.go @@ -0,0 +1,252 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ldap + +import ( + "errors" + "fmt" + "strings" + + "gopkg.in/asn1-ber.v1" +) + +const ( + FilterAnd = 0 + FilterOr = 1 + FilterNot = 2 + FilterEqualityMatch = 3 + FilterSubstrings = 4 + FilterGreaterOrEqual = 5 + FilterLessOrEqual = 6 + FilterPresent = 7 + FilterApproxMatch = 8 + FilterExtensibleMatch = 9 +) + +var FilterMap = map[uint64]string{ + FilterAnd: "And", + FilterOr: "Or", + FilterNot: "Not", + FilterEqualityMatch: "Equality Match", + FilterSubstrings: "Substrings", + FilterGreaterOrEqual: "Greater Or Equal", + FilterLessOrEqual: "Less Or Equal", + FilterPresent: "Present", + FilterApproxMatch: "Approx Match", + FilterExtensibleMatch: "Extensible Match", +} + +const ( + FilterSubstringsInitial = 0 + FilterSubstringsAny = 1 + FilterSubstringsFinal = 2 +) + +var FilterSubstringsMap = map[uint64]string{ + FilterSubstringsInitial: "Substrings Initial", + FilterSubstringsAny: "Substrings Any", + FilterSubstringsFinal: "Substrings Final", +} + +func CompileFilter(filter string) (*ber.Packet, error) { + if len(filter) == 0 || filter[0] != '(' { + return nil, NewError(ErrorFilterCompile, errors.New("ldap: filter does not start with an '('")) + } + packet, pos, err := compileFilter(filter, 1) + if err != nil { + return nil, err + } + if pos != len(filter) { + return nil, NewError(ErrorFilterCompile, errors.New("ldap: finished compiling filter with extra at end: "+fmt.Sprint(filter[pos:]))) + } + return packet, nil +} + +func DecompileFilter(packet *ber.Packet) (ret string, err error) { + defer func() { + if r := recover(); r != nil { + err = NewError(ErrorFilterDecompile, errors.New("ldap: error decompiling filter")) + } + }() + ret = "(" + err = nil + childStr := "" + + switch packet.Tag { + case FilterAnd: + ret += "&" + for _, child := range packet.Children { + childStr, err = DecompileFilter(child) + if err != nil { + return + } + ret += childStr + } + case FilterOr: + ret += "|" + for _, child := range packet.Children { + childStr, err = DecompileFilter(child) + if err != nil { + return + } + ret += childStr + } + case FilterNot: + ret += "!" + childStr, err = DecompileFilter(packet.Children[0]) + if err != nil { + return + } + ret += childStr + + case FilterSubstrings: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "=" + for i, child := range packet.Children[1].Children { + if i == 0 && child.Tag != FilterSubstringsInitial { + ret += "*" + } + ret += ber.DecodeString(child.Data.Bytes()) + if child.Tag != FilterSubstringsFinal { + ret += "*" + } + } + case FilterEqualityMatch: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + case FilterGreaterOrEqual: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += ">=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + case FilterLessOrEqual: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "<=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + case FilterPresent: + ret += ber.DecodeString(packet.Data.Bytes()) + ret += "=*" + case FilterApproxMatch: + ret += ber.DecodeString(packet.Children[0].Data.Bytes()) + ret += "~=" + ret += ber.DecodeString(packet.Children[1].Data.Bytes()) + } + + ret += ")" + return +} + +func compileFilterSet(filter string, pos int, parent *ber.Packet) (int, error) { + for pos < len(filter) && filter[pos] == '(' { + child, newPos, err := compileFilter(filter, pos+1) + if err != nil { + return pos, err + } + pos = newPos + parent.AppendChild(child) + } + if pos == len(filter) { + return pos, NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) + } + + return pos + 1, nil +} + +func compileFilter(filter string, pos int) (*ber.Packet, int, error) { + var packet *ber.Packet + var err error + + defer func() { + if r := recover(); r != nil { + err = NewError(ErrorFilterCompile, errors.New("ldap: error compiling filter")) + } + }() + + newPos := pos + switch filter[pos] { + case '(': + packet, newPos, err = compileFilter(filter, pos+1) + newPos++ + return packet, newPos, err + case '&': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterAnd, nil, FilterMap[FilterAnd]) + newPos, err = compileFilterSet(filter, pos+1, packet) + return packet, newPos, err + case '|': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterOr, nil, FilterMap[FilterOr]) + newPos, err = compileFilterSet(filter, pos+1, packet) + return packet, newPos, err + case '!': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterNot, nil, FilterMap[FilterNot]) + var child *ber.Packet + child, newPos, err = compileFilter(filter, pos+1) + packet.AppendChild(child) + return packet, newPos, err + default: + attribute := "" + condition := "" + for newPos < len(filter) && filter[newPos] != ')' { + switch { + case packet != nil: + condition += fmt.Sprintf("%c", filter[newPos]) + case filter[newPos] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterEqualityMatch, nil, FilterMap[FilterEqualityMatch]) + case filter[newPos] == '>' && filter[newPos+1] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterGreaterOrEqual, nil, FilterMap[FilterGreaterOrEqual]) + newPos++ + case filter[newPos] == '<' && filter[newPos+1] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterLessOrEqual, nil, FilterMap[FilterLessOrEqual]) + newPos++ + case filter[newPos] == '~' && filter[newPos+1] == '=': + packet = ber.Encode(ber.ClassContext, ber.TypeConstructed, FilterApproxMatch, nil, FilterMap[FilterLessOrEqual]) + newPos++ + case packet == nil: + attribute += fmt.Sprintf("%c", filter[newPos]) + } + newPos++ + } + if newPos == len(filter) { + err = NewError(ErrorFilterCompile, errors.New("ldap: unexpected end of filter")) + return packet, newPos, err + } + if packet == nil { + err = NewError(ErrorFilterCompile, errors.New("ldap: error parsing filter")) + return packet, newPos, err + } + + switch { + case packet.Tag == FilterEqualityMatch && condition == "*": + packet = ber.NewString(ber.ClassContext, ber.TypePrimitive, FilterPresent, attribute, FilterMap[FilterPresent]) + case packet.Tag == FilterEqualityMatch && strings.Contains(condition, "*"): + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) + packet.Tag = FilterSubstrings + packet.Description = FilterMap[uint64(packet.Tag)] + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Substrings") + parts := strings.Split(condition, "*") + for i, part := range parts { + if part == "" { + continue + } + var tag ber.Tag + switch i { + case 0: + tag = FilterSubstringsInitial + case len(parts) - 1: + tag = FilterSubstringsFinal + default: + tag = FilterSubstringsAny + } + seq.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, tag, part, FilterSubstringsMap[uint64(tag)])) + } + packet.AppendChild(seq) + default: + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) + packet.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, condition, "Condition")) + } + + newPos++ + return packet, newPos, err + } +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go new file mode 100644 index 00000000000..673ef080235 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/filter_test.go @@ -0,0 +1,82 @@ +package ldap + +import ( + "testing" + + "gopkg.in/asn1-ber.v1" +) + +type compileTest struct { + filterStr string + filterType int +} + +var testFilters = []compileTest{ + compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd}, + compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr}, + compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot}, + compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch}, + compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=*i*le*)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=Mi*l*r)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=Mi*le*)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn=*i*ler)", filterType: FilterSubstrings}, + compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual}, + compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual}, + compileTest{filterStr: "(sn=*)", filterType: FilterPresent}, + compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch}, + // compileTest{ filterStr: "()", filterType: FilterExtensibleMatch }, +} + +func TestFilter(t *testing.T) { + // Test Compiler and Decompiler + for _, i := range testFilters { + filter, err := CompileFilter(i.filterStr) + if err != nil { + t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) + } else if filter.Tag != ber.Tag(i.filterType) { + t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)]) + } else { + o, err := DecompileFilter(filter) + if err != nil { + t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error()) + } else if i.filterStr != o { + t.Errorf("%q expected, got %q", i.filterStr, o) + } + } + } +} + +func BenchmarkFilterCompile(b *testing.B) { + b.StopTimer() + filters := make([]string, len(testFilters)) + + // Test Compiler and Decompiler + for idx, i := range testFilters { + filters[idx] = i.filterStr + } + + maxIdx := len(filters) + b.StartTimer() + for i := 0; i < b.N; i++ { + CompileFilter(filters[i%maxIdx]) + } +} + +func BenchmarkFilterDecompile(b *testing.B) { + b.StopTimer() + filters := make([]*ber.Packet, len(testFilters)) + + // Test Compiler and Decompiler + for idx, i := range testFilters { + filters[idx], _ = CompileFilter(i.filterStr) + } + + maxIdx := len(filters) + b.StartTimer() + for i := 0; i < b.N; i++ { + DecompileFilter(filters[i%maxIdx]) + } +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go new file mode 100644 index 00000000000..e91972ff4c1 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap.go @@ -0,0 +1,403 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ldap + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + + ber "gopkg.in/asn1-ber.v1" +) + +// LDAP Application Codes +const ( + ApplicationBindRequest = 0 + ApplicationBindResponse = 1 + ApplicationUnbindRequest = 2 + ApplicationSearchRequest = 3 + ApplicationSearchResultEntry = 4 + ApplicationSearchResultDone = 5 + ApplicationModifyRequest = 6 + ApplicationModifyResponse = 7 + ApplicationAddRequest = 8 + ApplicationAddResponse = 9 + ApplicationDelRequest = 10 + ApplicationDelResponse = 11 + ApplicationModifyDNRequest = 12 + ApplicationModifyDNResponse = 13 + ApplicationCompareRequest = 14 + ApplicationCompareResponse = 15 + ApplicationAbandonRequest = 16 + ApplicationSearchResultReference = 19 + ApplicationExtendedRequest = 23 + ApplicationExtendedResponse = 24 +) + +var ApplicationMap = map[uint8]string{ + ApplicationBindRequest: "Bind Request", + ApplicationBindResponse: "Bind Response", + ApplicationUnbindRequest: "Unbind Request", + ApplicationSearchRequest: "Search Request", + ApplicationSearchResultEntry: "Search Result Entry", + ApplicationSearchResultDone: "Search Result Done", + ApplicationModifyRequest: "Modify Request", + ApplicationModifyResponse: "Modify Response", + ApplicationAddRequest: "Add Request", + ApplicationAddResponse: "Add Response", + ApplicationDelRequest: "Del Request", + ApplicationDelResponse: "Del Response", + ApplicationModifyDNRequest: "Modify DN Request", + ApplicationModifyDNResponse: "Modify DN Response", + ApplicationCompareRequest: "Compare Request", + ApplicationCompareResponse: "Compare Response", + ApplicationAbandonRequest: "Abandon Request", + ApplicationSearchResultReference: "Search Result Reference", + ApplicationExtendedRequest: "Extended Request", + ApplicationExtendedResponse: "Extended Response", +} + +// LDAP Result Codes +const ( + LDAPResultSuccess = 0 + LDAPResultOperationsError = 1 + LDAPResultProtocolError = 2 + LDAPResultTimeLimitExceeded = 3 + LDAPResultSizeLimitExceeded = 4 + LDAPResultCompareFalse = 5 + LDAPResultCompareTrue = 6 + LDAPResultAuthMethodNotSupported = 7 + LDAPResultStrongAuthRequired = 8 + LDAPResultReferral = 10 + LDAPResultAdminLimitExceeded = 11 + LDAPResultUnavailableCriticalExtension = 12 + LDAPResultConfidentialityRequired = 13 + LDAPResultSaslBindInProgress = 14 + LDAPResultNoSuchAttribute = 16 + LDAPResultUndefinedAttributeType = 17 + LDAPResultInappropriateMatching = 18 + LDAPResultConstraintViolation = 19 + LDAPResultAttributeOrValueExists = 20 + LDAPResultInvalidAttributeSyntax = 21 + LDAPResultNoSuchObject = 32 + LDAPResultAliasProblem = 33 + LDAPResultInvalidDNSyntax = 34 + LDAPResultAliasDereferencingProblem = 36 + LDAPResultInappropriateAuthentication = 48 + LDAPResultInvalidCredentials = 49 + LDAPResultInsufficientAccessRights = 50 + LDAPResultBusy = 51 + LDAPResultUnavailable = 52 + LDAPResultUnwillingToPerform = 53 + LDAPResultLoopDetect = 54 + LDAPResultNamingViolation = 64 + LDAPResultObjectClassViolation = 65 + LDAPResultNotAllowedOnNonLeaf = 66 + LDAPResultNotAllowedOnRDN = 67 + LDAPResultEntryAlreadyExists = 68 + LDAPResultObjectClassModsProhibited = 69 + LDAPResultAffectsMultipleDSAs = 71 + LDAPResultOther = 80 + + ErrorNetwork = 200 + ErrorFilterCompile = 201 + ErrorFilterDecompile = 202 + ErrorDebugging = 203 + ErrorUnexpectedMessage = 204 + ErrorUnexpectedResponse = 205 +) + +var LDAPResultCodeMap = map[uint8]string{ + LDAPResultSuccess: "Success", + LDAPResultOperationsError: "Operations Error", + LDAPResultProtocolError: "Protocol Error", + LDAPResultTimeLimitExceeded: "Time Limit Exceeded", + LDAPResultSizeLimitExceeded: "Size Limit Exceeded", + LDAPResultCompareFalse: "Compare False", + LDAPResultCompareTrue: "Compare True", + LDAPResultAuthMethodNotSupported: "Auth Method Not Supported", + LDAPResultStrongAuthRequired: "Strong Auth Required", + LDAPResultReferral: "Referral", + LDAPResultAdminLimitExceeded: "Admin Limit Exceeded", + LDAPResultUnavailableCriticalExtension: "Unavailable Critical Extension", + LDAPResultConfidentialityRequired: "Confidentiality Required", + LDAPResultSaslBindInProgress: "Sasl Bind In Progress", + LDAPResultNoSuchAttribute: "No Such Attribute", + LDAPResultUndefinedAttributeType: "Undefined Attribute Type", + LDAPResultInappropriateMatching: "Inappropriate Matching", + LDAPResultConstraintViolation: "Constraint Violation", + LDAPResultAttributeOrValueExists: "Attribute Or Value Exists", + LDAPResultInvalidAttributeSyntax: "Invalid Attribute Syntax", + LDAPResultNoSuchObject: "No Such Object", + LDAPResultAliasProblem: "Alias Problem", + LDAPResultInvalidDNSyntax: "Invalid DN Syntax", + LDAPResultAliasDereferencingProblem: "Alias Dereferencing Problem", + LDAPResultInappropriateAuthentication: "Inappropriate Authentication", + LDAPResultInvalidCredentials: "Invalid Credentials", + LDAPResultInsufficientAccessRights: "Insufficient Access Rights", + LDAPResultBusy: "Busy", + LDAPResultUnavailable: "Unavailable", + LDAPResultUnwillingToPerform: "Unwilling To Perform", + LDAPResultLoopDetect: "Loop Detect", + LDAPResultNamingViolation: "Naming Violation", + LDAPResultObjectClassViolation: "Object Class Violation", + LDAPResultNotAllowedOnNonLeaf: "Not Allowed On Non Leaf", + LDAPResultNotAllowedOnRDN: "Not Allowed On RDN", + LDAPResultEntryAlreadyExists: "Entry Already Exists", + LDAPResultObjectClassModsProhibited: "Object Class Mods Prohibited", + LDAPResultAffectsMultipleDSAs: "Affects Multiple DSAs", + LDAPResultOther: "Other", +} + +// Ldap Behera Password Policy Draft 10 (https://tools.ietf.org/html/draft-behera-ldap-password-policy-10) +const ( + BeheraPasswordExpired = 0 + BeheraAccountLocked = 1 + BeheraChangeAfterReset = 2 + BeheraPasswordModNotAllowed = 3 + BeheraMustSupplyOldPassword = 4 + BeheraInsufficientPasswordQuality = 5 + BeheraPasswordTooShort = 6 + BeheraPasswordTooYoung = 7 + BeheraPasswordInHistory = 8 +) + +var BeheraPasswordPolicyErrorMap = map[int8]string{ + BeheraPasswordExpired: "Password expired", + BeheraAccountLocked: "Account locked", + BeheraChangeAfterReset: "Password must be changed", + BeheraPasswordModNotAllowed: "Policy prevents password modification", + BeheraMustSupplyOldPassword: "Policy requires old password in order to change password", + BeheraInsufficientPasswordQuality: "Password fails quality checks", + BeheraPasswordTooShort: "Password is too short for policy", + BeheraPasswordTooYoung: "Password has been changed too recently", + BeheraPasswordInHistory: "New password is in list of old passwords", +} + +// Adds descriptions to an LDAP Response packet for debugging +func addLDAPDescriptions(packet *ber.Packet) (err error) { + defer func() { + if r := recover(); r != nil { + err = NewError(ErrorDebugging, errors.New("ldap: cannot process packet to add descriptions")) + } + }() + packet.Description = "LDAP Response" + packet.Children[0].Description = "Message ID" + + application := uint8(packet.Children[1].Tag) + packet.Children[1].Description = ApplicationMap[application] + + switch application { + case ApplicationBindRequest: + addRequestDescriptions(packet) + case ApplicationBindResponse: + addDefaultLDAPResponseDescriptions(packet) + case ApplicationUnbindRequest: + addRequestDescriptions(packet) + case ApplicationSearchRequest: + addRequestDescriptions(packet) + case ApplicationSearchResultEntry: + packet.Children[1].Children[0].Description = "Object Name" + packet.Children[1].Children[1].Description = "Attributes" + for _, child := range packet.Children[1].Children[1].Children { + child.Description = "Attribute" + child.Children[0].Description = "Attribute Name" + child.Children[1].Description = "Attribute Values" + for _, grandchild := range child.Children[1].Children { + grandchild.Description = "Attribute Value" + } + } + if len(packet.Children) == 3 { + addControlDescriptions(packet.Children[2]) + } + case ApplicationSearchResultDone: + addDefaultLDAPResponseDescriptions(packet) + case ApplicationModifyRequest: + addRequestDescriptions(packet) + case ApplicationModifyResponse: + case ApplicationAddRequest: + addRequestDescriptions(packet) + case ApplicationAddResponse: + case ApplicationDelRequest: + addRequestDescriptions(packet) + case ApplicationDelResponse: + case ApplicationModifyDNRequest: + addRequestDescriptions(packet) + case ApplicationModifyDNResponse: + case ApplicationCompareRequest: + addRequestDescriptions(packet) + case ApplicationCompareResponse: + case ApplicationAbandonRequest: + addRequestDescriptions(packet) + case ApplicationSearchResultReference: + case ApplicationExtendedRequest: + addRequestDescriptions(packet) + case ApplicationExtendedResponse: + } + + return nil +} + +func addControlDescriptions(packet *ber.Packet) { + packet.Description = "Controls" + for _, child := range packet.Children { + child.Description = "Control" + child.Children[0].Description = "Control Type (" + ControlTypeMap[child.Children[0].Value.(string)] + ")" + value := child.Children[1] + if len(child.Children) == 3 { + child.Children[1].Description = "Criticality" + value = child.Children[2] + } + value.Description = "Control Value" + + switch child.Children[0].Value.(string) { + case ControlTypePaging: + value.Description += " (Paging)" + if value.Value != nil { + valueChildren := ber.DecodePacket(value.Data.Bytes()) + value.Data.Truncate(0) + value.Value = nil + valueChildren.Children[1].Value = valueChildren.Children[1].Data.Bytes() + value.AppendChild(valueChildren) + } + value.Children[0].Description = "Real Search Control Value" + value.Children[0].Children[0].Description = "Paging Size" + value.Children[0].Children[1].Description = "Cookie" + + case ControlTypeBeheraPasswordPolicy: + value.Description += " (Password Policy - Behera Draft)" + if value.Value != nil { + valueChildren := ber.DecodePacket(value.Data.Bytes()) + value.Data.Truncate(0) + value.Value = nil + value.AppendChild(valueChildren) + } + sequence := value.Children[0] + for _, child := range sequence.Children { + if child.Tag == 0 { + //Warning + child := child.Children[0] + packet := ber.DecodePacket(child.Data.Bytes()) + val, ok := packet.Value.(int64) + if ok { + if child.Tag == 0 { + //timeBeforeExpiration + value.Description += " (TimeBeforeExpiration)" + child.Value = val + } else if child.Tag == 1 { + //graceAuthNsRemaining + value.Description += " (GraceAuthNsRemaining)" + child.Value = val + } + } + } else if child.Tag == 1 { + // Error + packet := ber.DecodePacket(child.Data.Bytes()) + val, ok := packet.Value.(int8) + if !ok { + val = -1 + } + child.Description = "Error" + child.Value = val + } + } + } + } +} + +func addRequestDescriptions(packet *ber.Packet) { + packet.Description = "LDAP Request" + packet.Children[0].Description = "Message ID" + packet.Children[1].Description = ApplicationMap[uint8(packet.Children[1].Tag)] + if len(packet.Children) == 3 { + addControlDescriptions(packet.Children[2]) + } +} + +func addDefaultLDAPResponseDescriptions(packet *ber.Packet) { + resultCode := packet.Children[1].Children[0].Value.(int64) + packet.Children[1].Children[0].Description = "Result Code (" + LDAPResultCodeMap[uint8(resultCode)] + ")" + packet.Children[1].Children[1].Description = "Matched DN" + packet.Children[1].Children[2].Description = "Error Message" + if len(packet.Children[1].Children) > 3 { + packet.Children[1].Children[3].Description = "Referral" + } + if len(packet.Children) == 3 { + addControlDescriptions(packet.Children[2]) + } +} + +func DebugBinaryFile(fileName string) error { + file, err := ioutil.ReadFile(fileName) + if err != nil { + return NewError(ErrorDebugging, err) + } + ber.PrintBytes(os.Stdout, file, "") + packet := ber.DecodePacket(file) + addLDAPDescriptions(packet) + ber.PrintPacket(packet) + + return nil +} + +type Error struct { + Err error + ResultCode uint8 +} + +func (e *Error) Error() string { + return fmt.Sprintf("LDAP Result Code %d %q: %s", e.ResultCode, LDAPResultCodeMap[e.ResultCode], e.Err.Error()) +} + +func NewError(resultCode uint8, err error) error { + return &Error{ResultCode: resultCode, Err: err} +} + +func getLDAPResultCode(packet *ber.Packet) (code uint8, description string) { + if len(packet.Children) >= 2 { + response := packet.Children[1] + if response.ClassType == ber.ClassApplication && response.TagType == ber.TypeConstructed && len(response.Children) >= 3 { + return uint8(response.Children[0].Value.(int64)), response.Children[2].Value.(string) + } + } + + return ErrorNetwork, "Invalid packet format" +} + +var hex = "0123456789abcdef" + +func mustEscape(c byte) bool { + return c > 0x7f || c == '(' || c == ')' || c == '\\' || c == '*' || c == 0 +} + +// EscapeFilter escapes from the provided LDAP filter string the special +// characters in the set `()*\` and those out of the range 0 < c < 0x80, +// as defined in RFC4515. +func EscapeFilter(filter string) string { + escape := 0 + for i := 0; i < len(filter); i++ { + if mustEscape(filter[i]) { + escape++ + } + } + if escape == 0 { + return filter + } + buf := make([]byte, len(filter)+escape*2) + for i, j := 0, 0; i < len(filter); i++ { + c := filter[i] + if mustEscape(c) { + buf[j+0] = '\\' + buf[j+1] = hex[c>>4] + buf[j+2] = hex[c&0xf] + j += 3 + } else { + buf[j] = c + j++ + } + } + return string(buf) +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go new file mode 100644 index 00000000000..e9933f99a69 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/ldap_test.go @@ -0,0 +1,247 @@ +package ldap + +import ( + "crypto/tls" + "fmt" + "testing" +) + +var ldapServer = "ldap.itd.umich.edu" +var ldapPort = uint16(389) +var ldapTLSPort = uint16(636) +var baseDN = "dc=umich,dc=edu" +var filter = []string{ + "(cn=cis-fac)", + "(&(owner=*)(cn=cis-fac))", + "(&(objectclass=rfc822mailgroup)(cn=*Computer*))", + "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"} +var attributes = []string{ + "cn", + "description"} + +func TestDial(t *testing.T) { + fmt.Printf("TestDial: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + fmt.Printf("TestDial: finished...\n") +} + +func TestDialTLS(t *testing.T) { + fmt.Printf("TestDialTLS: starting...\n") + l, err := DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true}) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + fmt.Printf("TestDialTLS: finished...\n") +} + +func TestStartTLS(t *testing.T) { + fmt.Printf("TestStartTLS: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + t.Errorf(err.Error()) + return + } + fmt.Printf("TestStartTLS: finished...\n") +} + +func TestSearch(t *testing.T) { + fmt.Printf("TestSearch: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + + searchRequest := NewSearchRequest( + baseDN, + ScopeWholeSubtree, DerefAlways, 0, 0, false, + filter[0], + attributes, + nil) + + sr, err := l.Search(searchRequest) + if err != nil { + t.Errorf(err.Error()) + return + } + + fmt.Printf("TestSearch: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) +} + +func TestSearchStartTLS(t *testing.T) { + fmt.Printf("TestSearchStartTLS: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + + searchRequest := NewSearchRequest( + baseDN, + ScopeWholeSubtree, DerefAlways, 0, 0, false, + filter[0], + attributes, + nil) + + sr, err := l.Search(searchRequest) + if err != nil { + t.Errorf(err.Error()) + return + } + + fmt.Printf("TestSearchStartTLS: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) + + fmt.Printf("TestSearchStartTLS: upgrading with startTLS\n") + err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + t.Errorf(err.Error()) + return + } + + sr, err = l.Search(searchRequest) + if err != nil { + t.Errorf(err.Error()) + return + } + + fmt.Printf("TestSearchStartTLS: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) +} + +func TestSearchWithPaging(t *testing.T) { + fmt.Printf("TestSearchWithPaging: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + + err = l.Bind("", "") + if err != nil { + t.Errorf(err.Error()) + return + } + + searchRequest := NewSearchRequest( + baseDN, + ScopeWholeSubtree, DerefAlways, 0, 0, false, + filter[2], + attributes, + nil) + sr, err := l.SearchWithPaging(searchRequest, 5) + if err != nil { + t.Errorf(err.Error()) + return + } + + fmt.Printf("TestSearchWithPaging: %s -> num of entries = %d\n", searchRequest.Filter, len(sr.Entries)) +} + +func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) { + searchRequest := NewSearchRequest( + baseDN, + ScopeWholeSubtree, DerefAlways, 0, 0, false, + filter[i], + attributes, + nil) + sr, err := l.Search(searchRequest) + if err != nil { + t.Errorf(err.Error()) + results <- nil + return + } + results <- sr +} + +func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) { + fmt.Printf("TestMultiGoroutineSearch: starting...\n") + var l *Conn + var err error + if TLS { + l, err = DialTLS("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapTLSPort), &tls.Config{InsecureSkipVerify: true}) + if err != nil { + t.Errorf(err.Error()) + return + } + defer l.Close() + } else { + l, err = Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Errorf(err.Error()) + return + } + if startTLS { + fmt.Printf("TestMultiGoroutineSearch: using StartTLS...\n") + err := l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + t.Errorf(err.Error()) + return + } + + } + } + + results := make([]chan *SearchResult, len(filter)) + for i := range filter { + results[i] = make(chan *SearchResult) + go searchGoroutine(t, l, results[i], i) + } + for i := range filter { + sr := <-results[i] + if sr == nil { + t.Errorf("Did not receive results from goroutine for %q", filter[i]) + } else { + fmt.Printf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d\n", i, filter[i], len(sr.Entries)) + } + } +} + +func TestMultiGoroutineSearch(t *testing.T) { + testMultiGoroutineSearch(t, false, false) + testMultiGoroutineSearch(t, true, true) + testMultiGoroutineSearch(t, false, true) +} + +func TestEscapeFilter(t *testing.T) { + if got, want := EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want { + t.Errorf("Got %s, expected %s", want, got) + } + if got, want := EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want { + t.Errorf("Got %s, expected %s", want, got) + } +} + +func TestCompare(t *testing.T) { + fmt.Printf("TestCompare: starting...\n") + l, err := Dial("tcp", fmt.Sprintf("%s:%d", ldapServer, ldapPort)) + if err != nil { + t.Fatal(err.Error()) + } + defer l.Close() + + dn := "cn=math mich,ou=User Groups,ou=Groups,dc=umich,dc=edu" + attribute := "cn" + value := "math mich" + + sr, err := l.Compare(dn, attribute, value) + if err != nil { + t.Errorf(err.Error()) + return + } + + fmt.Printf("TestCompare: -> num of entries = %d\n", sr) +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go new file mode 100644 index 00000000000..4372a19dceb --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/modify.go @@ -0,0 +1,156 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains Modify functionality +// +// https://tools.ietf.org/html/rfc4511 +// +// ModifyRequest ::= [APPLICATION 6] SEQUENCE { +// object LDAPDN, +// changes SEQUENCE OF change SEQUENCE { +// operation ENUMERATED { +// add (0), +// delete (1), +// replace (2), +// ... }, +// modification PartialAttribute } } +// +// PartialAttribute ::= SEQUENCE { +// type AttributeDescription, +// vals SET OF value AttributeValue } +// +// AttributeDescription ::= LDAPString +// -- Constrained to +// -- [RFC4512] +// +// AttributeValue ::= OCTET STRING +// + +package ldap + +import ( + "errors" + "log" + + "gopkg.in/asn1-ber.v1" +) + +const ( + AddAttribute = 0 + DeleteAttribute = 1 + ReplaceAttribute = 2 +) + +type PartialAttribute struct { + attrType string + attrVals []string +} + +func (p *PartialAttribute) encode() *ber.Packet { + seq := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "PartialAttribute") + seq.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, p.attrType, "Type")) + set := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSet, nil, "AttributeValue") + for _, value := range p.attrVals { + set.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, value, "Vals")) + } + seq.AppendChild(set) + return seq +} + +type ModifyRequest struct { + dn string + addAttributes []PartialAttribute + deleteAttributes []PartialAttribute + replaceAttributes []PartialAttribute +} + +func (m *ModifyRequest) Add(attrType string, attrVals []string) { + m.addAttributes = append(m.addAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals}) +} + +func (m *ModifyRequest) Delete(attrType string, attrVals []string) { + m.deleteAttributes = append(m.deleteAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals}) +} + +func (m *ModifyRequest) Replace(attrType string, attrVals []string) { + m.replaceAttributes = append(m.replaceAttributes, PartialAttribute{attrType: attrType, attrVals: attrVals}) +} + +func (m ModifyRequest) encode() *ber.Packet { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationModifyRequest, nil, "Modify Request") + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, m.dn, "DN")) + changes := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Changes") + for _, attribute := range m.addAttributes { + change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") + change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(AddAttribute), "Operation")) + change.AppendChild(attribute.encode()) + changes.AppendChild(change) + } + for _, attribute := range m.deleteAttributes { + change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") + change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(DeleteAttribute), "Operation")) + change.AppendChild(attribute.encode()) + changes.AppendChild(change) + } + for _, attribute := range m.replaceAttributes { + change := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Change") + change.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(ReplaceAttribute), "Operation")) + change.AppendChild(attribute.encode()) + changes.AppendChild(change) + } + request.AppendChild(changes) + return request +} + +func NewModifyRequest( + dn string, +) *ModifyRequest { + return &ModifyRequest{ + dn: dn, + } +} + +func (l *Conn) Modify(modifyRequest *ModifyRequest) error { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + packet.AppendChild(modifyRequest.encode()) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return err + } + if channel == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Tag == ApplicationModifyResponse { + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return NewError(resultCode, errors.New(resultDescription)) + } + } else { + log.Printf("Unexpected Response: %d", packet.Children[1].Tag) + } + + l.Debug.Printf("%d: returning", messageID) + return nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go new file mode 100644 index 00000000000..508b11ed727 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/passwdmodify.go @@ -0,0 +1,137 @@ +// This file contains the password modify extended operation as specified in rfc 3062 +// +// https://tools.ietf.org/html/rfc3062 +// + +package ldap + +import ( + "errors" + "fmt" + + "gopkg.in/asn1-ber.v1" +) + +const ( + passwordModifyOID = "1.3.6.1.4.1.4203.1.11.1" +) + +type PasswordModifyRequest struct { + UserIdentity string + OldPassword string + NewPassword string +} + +type PasswordModifyResult struct { + GeneratedPassword string +} + +func (r *PasswordModifyRequest) encode() (*ber.Packet, error) { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Password Modify Extended Operation") + request.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, passwordModifyOID, "Extended Request Name: Password Modify OID")) + extendedRequestValue := ber.Encode(ber.ClassContext, ber.TypePrimitive, 1, nil, "Extended Request Value: Password Modify Request") + passwordModifyRequestValue := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Password Modify Request") + if r.UserIdentity != "" { + passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 0, r.UserIdentity, "User Identity")) + } + if r.OldPassword != "" { + passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 1, r.OldPassword, "Old Password")) + } + if r.NewPassword != "" { + passwordModifyRequestValue.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, 2, r.NewPassword, "New Password")) + } + + extendedRequestValue.AppendChild(passwordModifyRequestValue) + request.AppendChild(extendedRequestValue) + + return request, nil +} + +// Create a new PasswordModifyRequest +// +// According to the RFC 3602: +// userIdentity is a string representing the user associated with the request. +// This string may or may not be an LDAPDN (RFC 2253). +// If userIdentity is empty then the operation will act on the user associated +// with the session. +// +// oldPassword is the current user's password, it can be empty or it can be +// needed depending on the session user access rights (usually an administrator +// can change a user's password without knowing the current one) and the +// password policy (see pwdSafeModify password policy's attribute) +// +// newPassword is the desired user's password. If empty the server can return +// an error or generate a new password that will be available in the +// PasswordModifyResult.GeneratedPassword +// +func NewPasswordModifyRequest(userIdentity string, oldPassword string, newPassword string) *PasswordModifyRequest { + return &PasswordModifyRequest{ + UserIdentity: userIdentity, + OldPassword: oldPassword, + NewPassword: newPassword, + } +} + +func (l *Conn) PasswordModify(passwordModifyRequest *PasswordModifyRequest) (*PasswordModifyResult, error) { + messageID := l.nextMessageID() + + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + + encodedPasswordModifyRequest, err := passwordModifyRequest.encode() + if err != nil { + return nil, err + } + packet.AppendChild(encodedPasswordModifyRequest) + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return nil, err + } + if channel == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + result := &PasswordModifyResult{} + + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + + if packet == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return nil, err + } + ber.PrintPacket(packet) + } + + if packet.Children[1].Tag == ApplicationExtendedResponse { + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return nil, NewError(resultCode, errors.New(resultDescription)) + } + } else { + return nil, NewError(ErrorUnexpectedResponse, fmt.Errorf("Unexpected Response: %d", packet.Children[1].Tag)) + } + + extendedResponse := packet.Children[1] + for _, child := range extendedResponse.Children { + if child.Tag == 11 { + passwordModifyReponseValue := ber.DecodePacket(child.Data.Bytes()) + if len(passwordModifyReponseValue.Children) == 1 { + if passwordModifyReponseValue.Children[0].Tag == 0 { + result.GeneratedPassword = ber.DecodeString(passwordModifyReponseValue.Children[0].Data.Bytes()) + } + } + } + } + + return result, nil +} diff --git a/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go b/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go new file mode 100644 index 00000000000..5ae3c449477 --- /dev/null +++ b/Godeps/_workspace/src/github.com/go-ldap/ldap/search.go @@ -0,0 +1,369 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// File contains Search functionality +// +// https://tools.ietf.org/html/rfc4511 +// +// SearchRequest ::= [APPLICATION 3] SEQUENCE { +// baseObject LDAPDN, +// scope ENUMERATED { +// baseObject (0), +// singleLevel (1), +// wholeSubtree (2), +// ... }, +// derefAliases ENUMERATED { +// neverDerefAliases (0), +// derefInSearching (1), +// derefFindingBaseObj (2), +// derefAlways (3) }, +// sizeLimit INTEGER (0 .. maxInt), +// timeLimit INTEGER (0 .. maxInt), +// typesOnly BOOLEAN, +// filter Filter, +// attributes AttributeSelection } +// +// AttributeSelection ::= SEQUENCE OF selector LDAPString +// -- The LDAPString is constrained to +// -- in Section 4.5.1.8 +// +// Filter ::= CHOICE { +// and [0] SET SIZE (1..MAX) OF filter Filter, +// or [1] SET SIZE (1..MAX) OF filter Filter, +// not [2] Filter, +// equalityMatch [3] AttributeValueAssertion, +// substrings [4] SubstringFilter, +// greaterOrEqual [5] AttributeValueAssertion, +// lessOrEqual [6] AttributeValueAssertion, +// present [7] AttributeDescription, +// approxMatch [8] AttributeValueAssertion, +// extensibleMatch [9] MatchingRuleAssertion, +// ... } +// +// SubstringFilter ::= SEQUENCE { +// type AttributeDescription, +// substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE { +// initial [0] AssertionValue, -- can occur at most once +// any [1] AssertionValue, +// final [2] AssertionValue } -- can occur at most once +// } +// +// MatchingRuleAssertion ::= SEQUENCE { +// matchingRule [1] MatchingRuleId OPTIONAL, +// type [2] AttributeDescription OPTIONAL, +// matchValue [3] AssertionValue, +// dnAttributes [4] BOOLEAN DEFAULT FALSE } +// +// + +package ldap + +import ( + "errors" + "fmt" + "strings" + + "gopkg.in/asn1-ber.v1" +) + +const ( + ScopeBaseObject = 0 + ScopeSingleLevel = 1 + ScopeWholeSubtree = 2 +) + +var ScopeMap = map[int]string{ + ScopeBaseObject: "Base Object", + ScopeSingleLevel: "Single Level", + ScopeWholeSubtree: "Whole Subtree", +} + +const ( + NeverDerefAliases = 0 + DerefInSearching = 1 + DerefFindingBaseObj = 2 + DerefAlways = 3 +) + +var DerefMap = map[int]string{ + NeverDerefAliases: "NeverDerefAliases", + DerefInSearching: "DerefInSearching", + DerefFindingBaseObj: "DerefFindingBaseObj", + DerefAlways: "DerefAlways", +} + +type Entry struct { + DN string + Attributes []*EntryAttribute +} + +func (e *Entry) GetAttributeValues(attribute string) []string { + for _, attr := range e.Attributes { + if attr.Name == attribute { + return attr.Values + } + } + return []string{} +} + +func (e *Entry) GetRawAttributeValues(attribute string) [][]byte { + for _, attr := range e.Attributes { + if attr.Name == attribute { + return attr.ByteValues + } + } + return [][]byte{} +} + +func (e *Entry) GetAttributeValue(attribute string) string { + values := e.GetAttributeValues(attribute) + if len(values) == 0 { + return "" + } + return values[0] +} + +func (e *Entry) GetRawAttributeValue(attribute string) []byte { + values := e.GetRawAttributeValues(attribute) + if len(values) == 0 { + return []byte{} + } + return values[0] +} + +func (e *Entry) Print() { + fmt.Printf("DN: %s\n", e.DN) + for _, attr := range e.Attributes { + attr.Print() + } +} + +func (e *Entry) PrettyPrint(indent int) { + fmt.Printf("%sDN: %s\n", strings.Repeat(" ", indent), e.DN) + for _, attr := range e.Attributes { + attr.PrettyPrint(indent + 2) + } +} + +type EntryAttribute struct { + Name string + Values []string + ByteValues [][]byte +} + +func (e *EntryAttribute) Print() { + fmt.Printf("%s: %s\n", e.Name, e.Values) +} + +func (e *EntryAttribute) PrettyPrint(indent int) { + fmt.Printf("%s%s: %s\n", strings.Repeat(" ", indent), e.Name, e.Values) +} + +type SearchResult struct { + Entries []*Entry + Referrals []string + Controls []Control +} + +func (s *SearchResult) Print() { + for _, entry := range s.Entries { + entry.Print() + } +} + +func (s *SearchResult) PrettyPrint(indent int) { + for _, entry := range s.Entries { + entry.PrettyPrint(indent) + } +} + +type SearchRequest struct { + BaseDN string + Scope int + DerefAliases int + SizeLimit int + TimeLimit int + TypesOnly bool + Filter string + Attributes []string + Controls []Control +} + +func (s *SearchRequest) encode() (*ber.Packet, error) { + request := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationSearchRequest, nil, "Search Request") + request.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, s.BaseDN, "Base DN")) + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.Scope), "Scope")) + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagEnumerated, uint64(s.DerefAliases), "Deref Aliases")) + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.SizeLimit), "Size Limit")) + request.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, uint64(s.TimeLimit), "Time Limit")) + request.AppendChild(ber.NewBoolean(ber.ClassUniversal, ber.TypePrimitive, ber.TagBoolean, s.TypesOnly, "Types Only")) + // compile and encode filter + filterPacket, err := CompileFilter(s.Filter) + if err != nil { + return nil, err + } + request.AppendChild(filterPacket) + // encode attributes + attributesPacket := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "Attributes") + for _, attribute := range s.Attributes { + attributesPacket.AppendChild(ber.NewString(ber.ClassUniversal, ber.TypePrimitive, ber.TagOctetString, attribute, "Attribute")) + } + request.AppendChild(attributesPacket) + return request, nil +} + +func NewSearchRequest( + BaseDN string, + Scope, DerefAliases, SizeLimit, TimeLimit int, + TypesOnly bool, + Filter string, + Attributes []string, + Controls []Control, +) *SearchRequest { + return &SearchRequest{ + BaseDN: BaseDN, + Scope: Scope, + DerefAliases: DerefAliases, + SizeLimit: SizeLimit, + TimeLimit: TimeLimit, + TypesOnly: TypesOnly, + Filter: Filter, + Attributes: Attributes, + Controls: Controls, + } +} + +func (l *Conn) SearchWithPaging(searchRequest *SearchRequest, pagingSize uint32) (*SearchResult, error) { + if searchRequest.Controls == nil { + searchRequest.Controls = make([]Control, 0) + } + + pagingControl := NewControlPaging(pagingSize) + searchRequest.Controls = append(searchRequest.Controls, pagingControl) + searchResult := new(SearchResult) + for { + result, err := l.Search(searchRequest) + l.Debug.Printf("Looking for Paging Control...") + if err != nil { + return searchResult, err + } + if result == nil { + return searchResult, NewError(ErrorNetwork, errors.New("ldap: packet not received")) + } + + for _, entry := range result.Entries { + searchResult.Entries = append(searchResult.Entries, entry) + } + for _, referral := range result.Referrals { + searchResult.Referrals = append(searchResult.Referrals, referral) + } + for _, control := range result.Controls { + searchResult.Controls = append(searchResult.Controls, control) + } + + l.Debug.Printf("Looking for Paging Control...") + pagingResult := FindControl(result.Controls, ControlTypePaging) + if pagingResult == nil { + pagingControl = nil + l.Debug.Printf("Could not find paging control. Breaking...") + break + } + + cookie := pagingResult.(*ControlPaging).Cookie + if len(cookie) == 0 { + pagingControl = nil + l.Debug.Printf("Could not find cookie. Breaking...") + break + } + pagingControl.SetCookie(cookie) + } + + if pagingControl != nil { + l.Debug.Printf("Abandoning Paging...") + pagingControl.PagingSize = 0 + l.Search(searchRequest) + } + + return searchResult, nil +} + +func (l *Conn) Search(searchRequest *SearchRequest) (*SearchResult, error) { + messageID := l.nextMessageID() + packet := ber.Encode(ber.ClassUniversal, ber.TypeConstructed, ber.TagSequence, nil, "LDAP Request") + packet.AppendChild(ber.NewInteger(ber.ClassUniversal, ber.TypePrimitive, ber.TagInteger, messageID, "MessageID")) + // encode search request + encodedSearchRequest, err := searchRequest.encode() + if err != nil { + return nil, err + } + packet.AppendChild(encodedSearchRequest) + // encode search controls + if searchRequest.Controls != nil { + packet.AppendChild(encodeControls(searchRequest.Controls)) + } + + l.Debug.PrintPacket(packet) + + channel, err := l.sendMessage(packet) + if err != nil { + return nil, err + } + if channel == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not send message")) + } + defer l.finishMessage(messageID) + + result := &SearchResult{ + Entries: make([]*Entry, 0), + Referrals: make([]string, 0), + Controls: make([]Control, 0)} + + foundSearchResultDone := false + for !foundSearchResultDone { + l.Debug.Printf("%d: waiting for response", messageID) + packet = <-channel + l.Debug.Printf("%d: got response %p", messageID, packet) + if packet == nil { + return nil, NewError(ErrorNetwork, errors.New("ldap: could not retrieve message")) + } + + if l.Debug { + if err := addLDAPDescriptions(packet); err != nil { + return nil, err + } + ber.PrintPacket(packet) + } + + switch packet.Children[1].Tag { + case 4: + entry := new(Entry) + entry.DN = packet.Children[1].Children[0].Value.(string) + for _, child := range packet.Children[1].Children[1].Children { + attr := new(EntryAttribute) + attr.Name = child.Children[0].Value.(string) + for _, value := range child.Children[1].Children { + attr.Values = append(attr.Values, value.Value.(string)) + attr.ByteValues = append(attr.ByteValues, value.ByteValue) + } + entry.Attributes = append(entry.Attributes, attr) + } + result.Entries = append(result.Entries, entry) + case 5: + resultCode, resultDescription := getLDAPResultCode(packet) + if resultCode != 0 { + return result, NewError(resultCode, errors.New(resultDescription)) + } + if len(packet.Children) == 3 { + for _, child := range packet.Children[2].Children { + result.Controls = append(result.Controls, DecodeControl(child)) + } + } + foundSearchResultDone = true + case 19: + result.Referrals = append(result.Referrals, packet.Children[1].Children[0].Value.(string)) + } + } + l.Debug.Printf("%d: returning", messageID) + return result, nil +} diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE b/Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE new file mode 100644 index 00000000000..968b45384d0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2013 Vaughan Newton + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md b/Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md new file mode 100644 index 00000000000..d5cd4e74b00 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/README.md @@ -0,0 +1,70 @@ +go-ini +====== + +INI parsing library for Go (golang). + +View the API documentation [here](http://godoc.org/github.com/vaughan0/go-ini). + +Usage +----- + +Parse an INI file: + +```go +import "github.com/vaughan0/go-ini" + +file, err := ini.LoadFile("myfile.ini") +``` + +Get data from the parsed file: + +```go +name, ok := file.Get("person", "name") +if !ok { + panic("'name' variable missing from 'person' section") +} +``` + +Iterate through values in a section: + +```go +for key, value := range file["mysection"] { + fmt.Printf("%s => %s\n", key, value) +} +``` + +Iterate through sections in a file: + +```go +for name, section := range file { + fmt.Printf("Section name: %s\n", name) +} +``` + +File Format +----------- + +INI files are parsed by go-ini line-by-line. Each line may be one of the following: + + * A section definition: [section-name] + * A property: key = value + * A comment: #blahblah _or_ ;blahblah + * Blank. The line will be ignored. + +Properties defined before any section headers are placed in the default section, which has +the empty string as it's key. + +Example: + +```ini +# I am a comment +; So am I! + +[apples] +colour = red or green +shape = applish + +[oranges] +shape = square +colour = blue +``` diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go new file mode 100644 index 00000000000..81aeb32f8b2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini.go @@ -0,0 +1,123 @@ +// Package ini provides functions for parsing INI configuration files. +package ini + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "strings" +) + +var ( + sectionRegex = regexp.MustCompile(`^\[(.*)\]$`) + assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) +) + +// ErrSyntax is returned when there is a syntax error in an INI file. +type ErrSyntax struct { + Line int + Source string // The contents of the erroneous line, without leading or trailing whitespace +} + +func (e ErrSyntax) Error() string { + return fmt.Sprintf("invalid INI syntax on line %d: %s", e.Line, e.Source) +} + +// A File represents a parsed INI file. +type File map[string]Section + +// A Section represents a single section of an INI file. +type Section map[string]string + +// Returns a named Section. A Section will be created if one does not already exist for the given name. +func (f File) Section(name string) Section { + section := f[name] + if section == nil { + section = make(Section) + f[name] = section + } + return section +} + +// Looks up a value for a key in a section and returns that value, along with a boolean result similar to a map lookup. +func (f File) Get(section, key string) (value string, ok bool) { + if s := f[section]; s != nil { + value, ok = s[key] + } + return +} + +// Loads INI data from a reader and stores the data in the File. +func (f File) Load(in io.Reader) (err error) { + bufin, ok := in.(*bufio.Reader) + if !ok { + bufin = bufio.NewReader(in) + } + return parseFile(bufin, f) +} + +// Loads INI data from a named file and stores the data in the File. +func (f File) LoadFile(file string) (err error) { + in, err := os.Open(file) + if err != nil { + return + } + defer in.Close() + return f.Load(in) +} + +func parseFile(in *bufio.Reader, file File) (err error) { + section := "" + lineNum := 0 + for done := false; !done; { + var line string + if line, err = in.ReadString('\n'); err != nil { + if err == io.EOF { + done = true + } else { + return + } + } + lineNum++ + line = strings.TrimSpace(line) + if len(line) == 0 { + // Skip blank lines + continue + } + if line[0] == ';' || line[0] == '#' { + // Skip comments + continue + } + + if groups := assignRegex.FindStringSubmatch(line); groups != nil { + key, val := groups[1], groups[2] + key, val = strings.TrimSpace(key), strings.TrimSpace(val) + file.Section(section)[key] = val + } else if groups := sectionRegex.FindStringSubmatch(line); groups != nil { + name := strings.TrimSpace(groups[1]) + section = name + // Create the section if it does not exist + file.Section(section) + } else { + return ErrSyntax{lineNum, line} + } + + } + return nil +} + +// Loads and returns a File from a reader. +func Load(in io.Reader) (File, error) { + file := make(File) + err := file.Load(in) + return file, err +} + +// Loads and returns an INI File from a file on disk. +func LoadFile(filename string) (File, error) { + file := make(File) + err := file.LoadFile(filename) + return file, err +} diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go new file mode 100644 index 00000000000..38a6f0004cf --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_linux_test.go @@ -0,0 +1,43 @@ +package ini + +import ( + "reflect" + "syscall" + "testing" +) + +func TestLoadFile(t *testing.T) { + originalOpenFiles := numFilesOpen(t) + + file, err := LoadFile("test.ini") + if err != nil { + t.Fatal(err) + } + + if originalOpenFiles != numFilesOpen(t) { + t.Error("test.ini not closed") + } + + if !reflect.DeepEqual(file, File{"default": {"stuff": "things"}}) { + t.Error("file not read correctly") + } +} + +func numFilesOpen(t *testing.T) (num uint64) { + var rlimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit) + if err != nil { + t.Fatal(err) + } + maxFds := int(rlimit.Cur) + + var stat syscall.Stat_t + for i := 0; i < maxFds; i++ { + if syscall.Fstat(i, &stat) == nil { + num++ + } else { + return + } + } + return +} diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go new file mode 100644 index 00000000000..06a4d05eaf0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/ini_test.go @@ -0,0 +1,89 @@ +package ini + +import ( + "reflect" + "strings" + "testing" +) + +func TestLoad(t *testing.T) { + src := ` + # Comments are ignored + + herp = derp + + [foo] + hello=world + whitespace should = not matter + ; sneaky semicolon-style comment + multiple = equals = signs + + [bar] + this = that` + + file, err := Load(strings.NewReader(src)) + if err != nil { + t.Fatal(err) + } + check := func(section, key, expect string) { + if value, _ := file.Get(section, key); value != expect { + t.Errorf("Get(%q, %q): expected %q, got %q", section, key, expect, value) + } + } + + check("", "herp", "derp") + check("foo", "hello", "world") + check("foo", "whitespace should", "not matter") + check("foo", "multiple", "equals = signs") + check("bar", "this", "that") +} + +func TestSyntaxError(t *testing.T) { + src := ` + # Line 2 + [foo] + bar = baz + # Here's an error on line 6: + wut? + herp = derp` + _, err := Load(strings.NewReader(src)) + t.Logf("%T: %v", err, err) + if err == nil { + t.Fatal("expected an error, got nil") + } + syntaxErr, ok := err.(ErrSyntax) + if !ok { + t.Fatal("expected an error of type ErrSyntax") + } + if syntaxErr.Line != 6 { + t.Fatal("incorrect line number") + } + if syntaxErr.Source != "wut?" { + t.Fatal("incorrect source") + } +} + +func TestDefinedSectionBehaviour(t *testing.T) { + check := func(src string, expect File) { + file, err := Load(strings.NewReader(src)) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(file, expect) { + t.Errorf("expected %v, got %v", expect, file) + } + } + // No sections for an empty file + check("", File{}) + // Default section only if there are actually values for it + check("foo=bar", File{"": {"foo": "bar"}}) + // User-defined sections should always be present, even if empty + check("[a]\n[b]\nfoo=bar", File{ + "a": {}, + "b": {"foo": "bar"}, + }) + check("foo=bar\n[a]\nthis=that", File{ + "": {"foo": "bar"}, + "a": {"this": "that"}, + }) +} diff --git a/Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini b/Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini new file mode 100644 index 00000000000..d13c999e254 --- /dev/null +++ b/Godeps/_workspace/src/github.com/vaughan0/go-ini/test.ini @@ -0,0 +1,2 @@ +[default] +stuff = things diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml new file mode 100644 index 00000000000..8d7d11f325e --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/.travis.yml @@ -0,0 +1,12 @@ +language: go +go: + - 1.2 + - 1.3 + - tip +install: + - go list -f '{{range .Imports}}{{.}} {{end}}' ./... | xargs go get -v + - go list -f '{{range .TestImports}}{{.}} {{end}}' ./... | xargs go get -v + - go get code.google.com/p/go.tools/cmd/cover + - go build -v ./... +script: + - go test -v -cover ./... diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE new file mode 100644 index 00000000000..74487567632 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md new file mode 100644 index 00000000000..51fbf6546f1 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/README.md @@ -0,0 +1,18 @@ +[![GoDoc](https://godoc.org/gopkg.in/asn1-ber.v1?status.svg)](https://godoc.org/gopkg.in/asn1-ber.v1) [![Build Status](https://travis-ci.org/go-asn1-ber/asn1-ber.svg)](https://travis-ci.org/go-asn1-ber/asn1-ber) + + +ASN1 BER Encoding / Decoding Library for the GO programming language. +--------------------------------------------------------------------- + +Required libraries: + None + +Working: + Very basic encoding / decoding needed for LDAP protocol + +Tests Implemented: + A few + +TODO: + Fix all encoding / decoding to conform to ASN1 BER spec + Implement Tests / Benchmarks diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go new file mode 100644 index 00000000000..0eb82191105 --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber.go @@ -0,0 +1,528 @@ +package ber + +import ( + "bytes" + "fmt" + "io" + "os" + "reflect" +) + +type Packet struct { + ClassType Class + TagType Type + Tag Tag + Value interface{} + ByteValue []byte + Data *bytes.Buffer + Children []*Packet + Description string +} + +type Tag uint8 + +const ( + TagEOC Tag = 0x00 + TagBoolean Tag = 0x01 + TagInteger Tag = 0x02 + TagBitString Tag = 0x03 + TagOctetString Tag = 0x04 + TagNULL Tag = 0x05 + TagObjectIdentifier Tag = 0x06 + TagObjectDescriptor Tag = 0x07 + TagExternal Tag = 0x08 + TagRealFloat Tag = 0x09 + TagEnumerated Tag = 0x0a + TagEmbeddedPDV Tag = 0x0b + TagUTF8String Tag = 0x0c + TagRelativeOID Tag = 0x0d + TagSequence Tag = 0x10 + TagSet Tag = 0x11 + TagNumericString Tag = 0x12 + TagPrintableString Tag = 0x13 + TagT61String Tag = 0x14 + TagVideotexString Tag = 0x15 + TagIA5String Tag = 0x16 + TagUTCTime Tag = 0x17 + TagGeneralizedTime Tag = 0x18 + TagGraphicString Tag = 0x19 + TagVisibleString Tag = 0x1a + TagGeneralString Tag = 0x1b + TagUniversalString Tag = 0x1c + TagCharacterString Tag = 0x1d + TagBMPString Tag = 0x1e + TagBitmask Tag = 0x1f // xxx11111b +) + +var tagMap = map[Tag]string{ + TagEOC: "EOC (End-of-Content)", + TagBoolean: "Boolean", + TagInteger: "Integer", + TagBitString: "Bit String", + TagOctetString: "Octet String", + TagNULL: "NULL", + TagObjectIdentifier: "Object Identifier", + TagObjectDescriptor: "Object Descriptor", + TagExternal: "External", + TagRealFloat: "Real (float)", + TagEnumerated: "Enumerated", + TagEmbeddedPDV: "Embedded PDV", + TagUTF8String: "UTF8 String", + TagRelativeOID: "Relative-OID", + TagSequence: "Sequence and Sequence of", + TagSet: "Set and Set OF", + TagNumericString: "Numeric String", + TagPrintableString: "Printable String", + TagT61String: "T61 String", + TagVideotexString: "Videotex String", + TagIA5String: "IA5 String", + TagUTCTime: "UTC Time", + TagGeneralizedTime: "Generalized Time", + TagGraphicString: "Graphic String", + TagVisibleString: "Visible String", + TagGeneralString: "General String", + TagUniversalString: "Universal String", + TagCharacterString: "Character String", + TagBMPString: "BMP String", +} + +type Class uint8 + +const ( + ClassUniversal Class = 0 // 00xxxxxxb + ClassApplication Class = 64 // 01xxxxxxb + ClassContext Class = 128 // 10xxxxxxb + ClassPrivate Class = 192 // 11xxxxxxb + ClassBitmask Class = 192 // 11xxxxxxb +) + +var ClassMap = map[Class]string{ + ClassUniversal: "Universal", + ClassApplication: "Application", + ClassContext: "Context", + ClassPrivate: "Private", +} + +type Type uint8 + +const ( + TypePrimitive Type = 0 // xx0xxxxxb + TypeConstructed Type = 32 // xx1xxxxxb + TypeBitmask Type = 32 // xx1xxxxxb +) + +var TypeMap = map[Type]string{ + TypePrimitive: "Primitive", + TypeConstructed: "Constructed", +} + +var Debug bool = false + +func PrintBytes(out io.Writer, buf []byte, indent string) { + data_lines := make([]string, (len(buf)/30)+1) + num_lines := make([]string, (len(buf)/30)+1) + + for i, b := range buf { + data_lines[i/30] += fmt.Sprintf("%02x ", b) + num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100) + } + + for i := 0; i < len(data_lines); i++ { + out.Write([]byte(indent + data_lines[i] + "\n")) + out.Write([]byte(indent + num_lines[i] + "\n\n")) + } +} + +func PrintPacket(p *Packet) { + printPacket(os.Stdout, p, 0, false) +} + +func printPacket(out io.Writer, p *Packet, indent int, printBytes bool) { + indent_str := "" + + for len(indent_str) != indent { + indent_str += " " + } + + class_str := ClassMap[p.ClassType] + + tagtype_str := TypeMap[p.TagType] + + tag_str := fmt.Sprintf("0x%02X", p.Tag) + + if p.ClassType == ClassUniversal { + tag_str = tagMap[p.Tag] + } + + value := fmt.Sprint(p.Value) + description := "" + + if p.Description != "" { + description = p.Description + ": " + } + + fmt.Fprintf(out, "%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value) + + if printBytes { + PrintBytes(out, p.Bytes(), indent_str) + } + + for _, child := range p.Children { + printPacket(out, child, indent+1, printBytes) + } +} + +func resizeBuffer(in []byte, new_size int) (out []byte) { + out = make([]byte, new_size) + + copy(out, in) + + return +} + +func ReadPacket(reader io.Reader) (*Packet, error) { + var header [2]byte + buf := header[:] + _, err := io.ReadFull(reader, buf) + + if err != nil { + return nil, err + } + + idx := 2 + var datalen int + l := buf[1] + + if l&0x80 == 0 { + // The length is encoded in the bottom 7 bits. + datalen = int(l & 0x7f) + if Debug { + fmt.Printf("Read: datalen = %d len(buf) = %d\n ", l, len(buf)) + + for _, b := range buf { + fmt.Printf("%02X ", b) + } + + fmt.Printf("\n") + } + } else { + // Bottom 7 bits give the number of length bytes to follow. + numBytes := int(l & 0x7f) + if numBytes == 0 { + return nil, fmt.Errorf("invalid length found") + } + idx += numBytes + buf = resizeBuffer(buf, 2+numBytes) + _, err := io.ReadFull(reader, buf[2:]) + + if err != nil { + return nil, err + } + datalen = 0 + for i := 0; i < numBytes; i++ { + b := buf[2+i] + datalen <<= 8 + datalen |= int(b) + } + + if Debug { + fmt.Printf("Read: datalen = %d numbytes=%d len(buf) = %d\n ", datalen, numBytes, len(buf)) + + for _, b := range buf { + fmt.Printf("%02X ", b) + } + + fmt.Printf("\n") + } + } + + buf = resizeBuffer(buf, idx+datalen) + _, err = io.ReadFull(reader, buf[idx:]) + + if err != nil { + return nil, err + } + + if Debug { + fmt.Printf("Read: len( buf ) = %d idx=%d datalen=%d idx+datalen=%d\n ", len(buf), idx, datalen, idx+datalen) + + for _, b := range buf { + fmt.Printf("%02X ", b) + } + } + + p, _ := decodePacket(buf) + + return p, nil +} + +func DecodeString(data []byte) string { + return string(data) +} + +func parseInt64(bytes []byte) (ret int64, err error) { + if len(bytes) > 8 { + // We'll overflow an int64 in this case. + err = fmt.Errorf("integer too large") + return + } + for bytesRead := 0; bytesRead < len(bytes); bytesRead++ { + ret <<= 8 + ret |= int64(bytes[bytesRead]) + } + + // Shift up and down in order to sign extend the result. + ret <<= 64 - uint8(len(bytes))*8 + ret >>= 64 - uint8(len(bytes))*8 + return +} + +func encodeInteger(i int64) []byte { + n := int64Length(i) + out := make([]byte, n) + + var j int + for ; n > 0; n-- { + out[j] = (byte(i >> uint((n-1)*8))) + j++ + } + + return out +} + +func int64Length(i int64) (numBytes int) { + numBytes = 1 + + for i > 127 { + numBytes++ + i >>= 8 + } + + for i < -128 { + numBytes++ + i >>= 8 + } + + return +} + +func DecodePacket(data []byte) *Packet { + p, _ := decodePacket(data) + + return p +} + +func decodePacket(data []byte) (*Packet, []byte) { + if Debug { + fmt.Printf("decodePacket: enter %d\n", len(data)) + } + + p := new(Packet) + + p.ClassType = Class(data[0]) & ClassBitmask + p.TagType = Type(data[0]) & TypeBitmask + p.Tag = Tag(data[0]) & TagBitmask + + var datalen int + l := data[1] + datapos := 2 + if l&0x80 == 0 { + // The length is encoded in the bottom 7 bits. + datalen = int(l & 0x7f) + } else { + // Bottom 7 bits give the number of length bytes to follow. + numBytes := int(l & 0x7f) + if numBytes == 0 { + return nil, nil + } + datapos += numBytes + datalen = 0 + for i := 0; i < numBytes; i++ { + b := data[2+i] + datalen <<= 8 + datalen |= int(b) + } + } + + p.Data = new(bytes.Buffer) + + p.Children = make([]*Packet, 0, 2) + + p.Value = nil + + value_data := data[datapos : datapos+datalen] + + if p.TagType == TypeConstructed { + for len(value_data) != 0 { + var child *Packet + + child, value_data = decodePacket(value_data) + p.AppendChild(child) + } + } else if p.ClassType == ClassUniversal { + p.Data.Write(data[datapos : datapos+datalen]) + p.ByteValue = value_data + + switch p.Tag { + case TagEOC: + case TagBoolean: + val, _ := parseInt64(value_data) + + p.Value = val != 0 + case TagInteger: + p.Value, _ = parseInt64(value_data) + case TagBitString: + case TagOctetString: + // the actual string encoding is not known here + // (e.g. for LDAP value_data is already an UTF8-encoded + // string). Return the data without further processing + p.Value = DecodeString(value_data) + case TagNULL: + case TagObjectIdentifier: + case TagObjectDescriptor: + case TagExternal: + case TagRealFloat: + case TagEnumerated: + p.Value, _ = parseInt64(value_data) + case TagEmbeddedPDV: + case TagUTF8String: + case TagRelativeOID: + case TagSequence: + case TagSet: + case TagNumericString: + case TagPrintableString: + p.Value = DecodeString(value_data) + case TagT61String: + case TagVideotexString: + case TagIA5String: + case TagUTCTime: + case TagGeneralizedTime: + case TagGraphicString: + case TagVisibleString: + case TagGeneralString: + case TagUniversalString: + case TagCharacterString: + case TagBMPString: + } + } else { + p.Data.Write(data[datapos : datapos+datalen]) + } + + return p, data[datapos+datalen:] +} + +func (p *Packet) Bytes() []byte { + var out bytes.Buffer + + out.Write([]byte{byte(p.ClassType) | byte(p.TagType) | byte(p.Tag)}) + packet_length := encodeInteger(int64(p.Data.Len())) + + if p.Data.Len() > 127 || len(packet_length) > 1 { + out.Write([]byte{byte(len(packet_length) | 128)}) + out.Write(packet_length) + } else { + out.Write(packet_length) + } + + out.Write(p.Data.Bytes()) + + return out.Bytes() +} + +func (p *Packet) AppendChild(child *Packet) { + p.Data.Write(child.Bytes()) + p.Children = append(p.Children, child) +} + +func Encode(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet { + p := new(Packet) + + p.ClassType = ClassType + p.TagType = TagType + p.Tag = Tag + p.Data = new(bytes.Buffer) + + p.Children = make([]*Packet, 0, 2) + + p.Value = Value + p.Description = Description + + if Value != nil { + v := reflect.ValueOf(Value) + + if ClassType == ClassUniversal { + switch Tag { + case TagOctetString: + sv, ok := v.Interface().(string) + + if ok { + p.Data.Write([]byte(sv)) + } + } + } + } + + return p +} + +func NewSequence(Description string) *Packet { + return Encode(ClassUniversal, TypeConstructed, TagSequence, nil, Description) +} + +func NewBoolean(ClassType Class, TagType Type, Tag Tag, Value bool, Description string) *Packet { + intValue := int64(0) + + if Value { + intValue = 1 + } + + p := Encode(ClassType, TagType, Tag, nil, Description) + + p.Value = Value + p.Data.Write(encodeInteger(intValue)) + + return p +} + +func NewInteger(ClassType Class, TagType Type, Tag Tag, Value interface{}, Description string) *Packet { + p := Encode(ClassType, TagType, Tag, nil, Description) + + p.Value = Value + switch v := Value.(type) { + case int: + p.Data.Write(encodeInteger(int64(v))) + case uint: + p.Data.Write(encodeInteger(int64(v))) + case int64: + p.Data.Write(encodeInteger(v)) + case uint64: + // TODO : check range or add encodeUInt... + p.Data.Write(encodeInteger(int64(v))) + case int32: + p.Data.Write(encodeInteger(int64(v))) + case uint32: + p.Data.Write(encodeInteger(int64(v))) + case int16: + p.Data.Write(encodeInteger(int64(v))) + case uint16: + p.Data.Write(encodeInteger(int64(v))) + case int8: + p.Data.Write(encodeInteger(int64(v))) + case uint8: + p.Data.Write(encodeInteger(int64(v))) + default: + // TODO : add support for big.Int ? + panic(fmt.Sprintf("Invalid type %T, expected {u|}int{64|32|16|8}", v)) + } + + return p +} + +func NewString(ClassType Class, TagType Type, Tag Tag, Value, Description string) *Packet { + p := Encode(ClassType, TagType, Tag, nil, Description) + + p.Value = Value + p.Data.Write([]byte(Value)) + + return p +} diff --git a/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go new file mode 100644 index 00000000000..ccf4906889f --- /dev/null +++ b/Godeps/_workspace/src/gopkg.in/asn1-ber.v1/ber_test.go @@ -0,0 +1,158 @@ +package ber + +import ( + "bytes" + + "io" + "testing" +) + +func TestEncodeDecodeInteger(t *testing.T) { + for _, v := range []int64{0, 10, 128, 1024, -1, -100, -128, -1024} { + enc := encodeInteger(v) + dec, err := parseInt64(enc) + if err != nil { + t.Fatalf("Error decoding %d : %s", v, err) + } + if v != dec { + t.Error("TestEncodeDecodeInteger failed for %d (got %d)", v, dec) + } + + } +} + +func TestBoolean(t *testing.T) { + var value bool = true + + packet := NewBoolean(ClassUniversal, TypePrimitive, TagBoolean, value, "first Packet, True") + + newBoolean, ok := packet.Value.(bool) + if !ok || newBoolean != value { + t.Error("error during creating packet") + } + + encodedPacket := packet.Bytes() + + newPacket := DecodePacket(encodedPacket) + + newBoolean, ok = newPacket.Value.(bool) + if !ok || newBoolean != value { + t.Error("error during decoding packet") + } + +} + +func TestInteger(t *testing.T) { + var value int64 = 10 + + packet := NewInteger(ClassUniversal, TypePrimitive, TagInteger, value, "Integer, 10") + + { + newInteger, ok := packet.Value.(int64) + if !ok || newInteger != value { + t.Error("error creating packet") + } + } + + encodedPacket := packet.Bytes() + + newPacket := DecodePacket(encodedPacket) + + { + newInteger, ok := newPacket.Value.(int64) + if !ok || int64(newInteger) != value { + t.Error("error decoding packet") + } + } +} + +func TestString(t *testing.T) { + var value string = "Hic sunt dracones" + + packet := NewString(ClassUniversal, TypePrimitive, TagOctetString, value, "String") + + newValue, ok := packet.Value.(string) + if !ok || newValue != value { + t.Error("error during creating packet") + } + + encodedPacket := packet.Bytes() + + newPacket := DecodePacket(encodedPacket) + + newValue, ok = newPacket.Value.(string) + if !ok || newValue != value { + t.Error("error during decoding packet") + } + +} + +func TestSequenceAndAppendChild(t *testing.T) { + + p1 := NewString(ClassUniversal, TypePrimitive, TagOctetString, "HIC SVNT LEONES", "String") + p2 := NewString(ClassUniversal, TypePrimitive, TagOctetString, "HIC SVNT DRACONES", "String") + p3 := NewString(ClassUniversal, TypePrimitive, TagOctetString, "Terra Incognita", "String") + + sequence := NewSequence("a sequence") + sequence.AppendChild(p1) + sequence.AppendChild(p2) + sequence.AppendChild(p3) + + if len(sequence.Children) != 3 { + t.Error("wrong length for children array should be three =>", len(sequence.Children)) + } + + encodedSequence := sequence.Bytes() + + decodedSequence := DecodePacket(encodedSequence) + if len(decodedSequence.Children) != 3 { + t.Error("wrong length for children array should be three =>", len(decodedSequence.Children)) + } + +} + +func TestReadPacket(t *testing.T) { + packet := NewString(ClassUniversal, TypePrimitive, TagOctetString, "Ad impossibilia nemo tenetur", "string") + var buffer io.ReadWriter + buffer = new(bytes.Buffer) + + buffer.Write(packet.Bytes()) + + newPacket, err := ReadPacket(buffer) + if err != nil { + t.Error("error during ReadPacket", err) + } + newPacket.ByteValue = nil + if !bytes.Equal(newPacket.ByteValue, packet.ByteValue) { + t.Error("packets should be the same") + } +} + +func TestBinaryInteger(t *testing.T) { + // data src : http://luca.ntop.org/Teaching/Appunti/asn1.html 5.7 + var data = []struct { + v int64 + e []byte + }{ + {v: 0, e: []byte{0x02, 0x01, 0x00}}, + {v: 127, e: []byte{0x02, 0x01, 0x7F}}, + {v: 128, e: []byte{0x02, 0x02, 0x00, 0x80}}, + {v: 256, e: []byte{0x02, 0x02, 0x01, 0x00}}, + {v: -128, e: []byte{0x02, 0x01, 0x80}}, + {v: -129, e: []byte{0x02, 0x02, 0xFF, 0x7F}}, + } + + for _, d := range data { + if b := NewInteger(ClassUniversal, TypePrimitive, TagInteger, int64(d.v), "").Bytes(); !bytes.Equal(d.e, b) { + t.Errorf("Wrong binary generated for %d : got % X, expected % X", d.v, b, d.e) + } + } +} + +func TestBinaryOctetString(t *testing.T) { + // data src : http://luca.ntop.org/Teaching/Appunti/asn1.html 5.10 + + if !bytes.Equal([]byte{0x04, 0x08, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, NewString(ClassUniversal, TypePrimitive, TagOctetString, "\x01\x23\x45\x67\x89\xab\xcd\xef", "").Bytes()) { + t.Error("wrong binary generated") + } +} diff --git a/Gruntfile.js b/Gruntfile.js index 43cb1f09bd1..70defdeaf6d 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,6 +6,7 @@ module.exports = function (grunt) { pkg: grunt.file.readJSON('package.json'), baseDir: '.', srcDir: 'public', + genDir: 'public_gen', destDir: 'dist', tempDir: 'tmp', arch: os.arch(), diff --git a/README.md b/README.md index 0a7db318338..a346acaaa8a 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ If you have any problems please read the [troubleshooting guide](http://docs.gra Be sure to read the [getting started guide](http://docs.grafana.org/guides/gettingstarted/) and the other feature guides. ## Run from master -If you want to build a package your self, or contribute. Here is a guide for how to do that. You can always find +If you want to build a package yourself, or contribute. Here is a guide for how to do that. You can always find the latest master builds [here](http://grafana.org/download/builds) ### Dependencies diff --git a/bower.json b/bower.json index 3607de4d8f0..ac6990b2004 100644 --- a/bower.json +++ b/bower.json @@ -14,13 +14,14 @@ ], "dependencies": { "jquery": "~2.1.4", - "angular": "~1.4.0", - "angular-route": "~1.4.0", - "angular-mocks": "~1.4.0", - "angular-sanitize": "~1.4.0", - "angular-native-dragdrop": "~1.1.0", + "angular": "~1.4.3", + "angular-route": "~1.4.3", + "angular-mocks": "~1.4.3", + "angular-sanitize": "~1.4.3", + "angular-native-dragdrop": "~1.1.1", "angular-bindonce": "~0.3.3", "requirejs": "~2.1.18", - "requirejs-text": "~2.0.14" + "requirejs-text": "~2.0.14", + "aws-sdk": "~2.1.42" } } diff --git a/build.go b/build.go index d14b1458586..ca6f52ecd89 100644 --- a/build.go +++ b/build.go @@ -128,6 +128,7 @@ type linuxPackageOptions struct { binPath string configDir string configFilePath string + ldapFilePath string etcDefaultPath string etcDefaultFilePath string initdScriptFilePath string @@ -148,6 +149,7 @@ func createLinuxPackages() { binPath: "/usr/sbin/grafana-server", configDir: "/etc/grafana", configFilePath: "/etc/grafana/grafana.ini", + ldapFilePath: "/etc/grafana/ldap.toml", etcDefaultPath: "/etc/default", etcDefaultFilePath: "/etc/default/grafana-server", initdScriptFilePath: "/etc/init.d/grafana-server", @@ -167,6 +169,7 @@ func createLinuxPackages() { binPath: "/usr/sbin/grafana-server", configDir: "/etc/grafana", configFilePath: "/etc/grafana/grafana.ini", + ldapFilePath: "/etc/grafana/ldap.toml", etcDefaultPath: "/etc/sysconfig", etcDefaultFilePath: "/etc/sysconfig/grafana-server", initdScriptFilePath: "/etc/init.d/grafana-server", @@ -204,8 +207,10 @@ func createPackage(options linuxPackageOptions) { runPrint("cp", "-a", filepath.Join(workingDir, "tmp")+"/.", filepath.Join(packageRoot, options.homeDir)) // remove bin path runPrint("rm", "-rf", filepath.Join(packageRoot, options.homeDir, "bin")) - // copy sample ini file to /etc/opt/grafana + // copy sample ini file to /etc/grafana runPrint("cp", "conf/sample.ini", filepath.Join(packageRoot, options.configFilePath)) + // copy sample ldap toml config file to /etc/grafana/ldap.toml + runPrint("cp", "conf/ldap.toml", filepath.Join(packageRoot, options.ldapFilePath)) args := []string{ "-s", "dir", @@ -216,6 +221,7 @@ func createPackage(options linuxPackageOptions) { "--license", "Apache 2.0", "--maintainer", "contact@grafana.org", "--config-files", options.configFilePath, + "--config-files", options.ldapFilePath, "--config-files", options.initdScriptFilePath, "--config-files", options.etcDefaultFilePath, "--config-files", options.systemdServiceFilePath, diff --git a/circle.yml b/circle.yml index 32ace393c8b..bb22fcb0a6e 100644 --- a/circle.yml +++ b/circle.yml @@ -1,4 +1,6 @@ machine: + node: + version: 4.0 environment: GOPATH: "/home/ubuntu/.go_workspace" ORG_PATH: "github.com/grafana" diff --git a/conf/defaults.ini b/conf/defaults.ini index d6d81c8028c..d640820fe65 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -3,6 +3,7 @@ # Do not modify this file in grafana installs # +# possible values : production, development app_mode = production #################################### Paths #################################### @@ -66,7 +67,7 @@ path = grafana.db #################################### Session #################################### [session] -# Either "memory", "file", "redis", "mysql", "postgresql", default is "file" +# Either "memory", "file", "redis", "mysql", "postgres", default is "file" provider = file # Provider config options @@ -86,6 +87,7 @@ cookie_secure = false # Session life time, default is 86400 session_life_time = 86400 +gc_interval_time = 86400 #################################### Analytics #################################### [analytics] @@ -98,6 +100,9 @@ reporting_enabled = true # Google Analytics universal tracking code, only enabled if you specify an id here google_analytics_ua_id = +# Google Tag Manager ID, only enabled if you specify an id here +google_tag_manager_id = + #################################### Security #################################### [security] # default admin user, created on startup @@ -117,6 +122,9 @@ cookie_remember_name = grafana_remember # disable gravatar profile images disable_gravatar = false +# data source proxy whitelist (ip_or_domain:port seperated by spaces) +data_source_proxy_whitelist = + #################################### Users #################################### [users] # disable user signup / registration @@ -128,9 +136,12 @@ allow_org_create = true # Set to true to automatically assign new users to the default organization (id 1) auto_assign_org = true -# Default role new users will be automatically assigned (if disabled above is set to true) +# Default role new users will be automatically assigned (if auto_assign_org above is set to true) auto_assign_org_role = Viewer +# Require email validation before sign up completes +verify_email_enabled = false + #################################### Anonymous Auth ########################## [auth.anonymous] # enable anonymous access @@ -153,7 +164,6 @@ auth_url = https://github.com/login/oauth/authorize token_url = https://github.com/login/oauth/access_token api_url = https://api.github.com/user team_ids = -allowed_domains = allowed_organizations = #################################### Google Auth ########################## @@ -179,6 +189,11 @@ header_name = X-WEBAUTH-USER header_property = username auto_sign_up = true +#################################### Auth LDAP ########################## +[auth.ldap] +enabled = false +config_file = /etc/grafana/ldap.toml + #################################### SMTP / Emailing ########################## [smtp] enabled = false @@ -239,4 +254,37 @@ exchange = grafana_events enabled = false path = /var/lib/grafana/dashboards +#################################### Usage Quotas ########################## +[quota] +enabled = false +#### set quotas to -1 to make unlimited. #### +# limit number of users per Org. +org_user = 10 + +# limit number of dashboards per Org. +org_dashboard = 100 + +# limit number of data_sources per Org. +org_data_source = 10 + +# limit number of api_keys per Org. +org_api_key = 10 + +# limit number of orgs a user can create. +user_org = 10 + +# Global limit of users. +global_user = -1 + +# global limit of orgs. +global_org = -1 + +# global limit of dashboards +global_dashboard = -1 + +# global limit of api_keys +global_api_key = -1 + +# global limit on number of logged in users. +global_session = -1 diff --git a/conf/ldap.toml b/conf/ldap.toml new file mode 100644 index 00000000000..9455c8df3a1 --- /dev/null +++ b/conf/ldap.toml @@ -0,0 +1,46 @@ +# Set to true to log user information returned from LDAP +verbose_logging = false + +[[servers]] +# Ldap server host +host = "127.0.0.1" +# Default port is 389 or 636 if use_ssl = true +port = 389 +# Set to true if ldap server supports TLS +use_ssl = false +# set to true if you want to skip ssl cert validation +ssl_skip_verify = false + +# Search user bind dn +bind_dn = "cn=admin,dc=grafana,dc=org" +# Search user bind password +bind_password = 'grafana' + +# Search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" +search_filter = "(cn=%s)" +# An array of base dns to search through +search_base_dns = ["dc=grafana,dc=org"] + +# Specify names of the ldap attributes your ldap uses +[servers.attributes] +name = "givenName" +surname = "sn" +username = "cn" +member_of = "memberOf" +email = "email" + +# Map ldap groups to grafana org roles +[[servers.group_mappings]] +group_dn = "cn=admins,dc=grafana,dc=org" +org_role = "Admin" +# The Grafana organization database id, optional, if left out the default org (id 1) will be used +# org_id = 1 + +[[servers.group_mappings]] +group_dn = "cn=users,dc=grafana,dc=org" +org_role = "Editor" + +[[servers.group_mappings]] +# If you want to match all (or no ldap groups) then you can use wildcard +group_dn = "*" +org_role = "Viewer" diff --git a/conf/sample.ini b/conf/sample.ini index ef082ccff98..e8550c66c1a 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -3,6 +3,7 @@ # Everything has defaults so you only need to uncomment things you want to # change +# possible values : production, development ; app_mode = production #################################### Paths #################################### @@ -66,7 +67,7 @@ #################################### Session #################################### [session] -# Either "memory", "file", "redis", "mysql", "postgresql", default is "file" +# Either "memory", "file", "redis", "mysql", "postgres", default is "file" ;provider = file # Provider config options @@ -116,6 +117,9 @@ # disable gravatar profile images ;disable_gravatar = false +# data source proxy whitelist (ip_or_domain:port seperated by spaces) +;data_source_proxy_whitelist = + #################################### Users #################################### [users] # disable user signup / registration @@ -152,7 +156,6 @@ ;token_url = https://github.com/login/oauth/access_token ;api_url = https://api.github.com/user ;team_ids = -;allowed_domains = ;allowed_organizations = #################################### Google Auth ########################## @@ -178,6 +181,11 @@ [auth.basic] ;enabled = true +#################################### Auth LDAP ########################## +[auth.ldap] +;enabled = false +;config_file = /etc/grafana/ldap.toml + #################################### SMTP / Emailing ########################## [smtp] ;enabled = false diff --git a/docker/blocks/openldap/Dockerfile b/docker/blocks/openldap/Dockerfile index dbe4956146b..d16987cb3ab 100644 --- a/docker/blocks/openldap/Dockerfile +++ b/docker/blocks/openldap/Dockerfile @@ -1,33 +1,25 @@ -FROM phusion/baseimage:0.9.8 -MAINTAINER Nick Stenning +FROM debian:jessie -ENV HOME /root +MAINTAINER Christian Luginbühl -# Disable SSH -RUN rm -rf /etc/service/sshd /etc/my_init.d/00_regen_ssh_host_keys.sh +ENV OPENLDAP_VERSION 2.4.40 -# Use baseimage-docker's init system. -CMD ["/sbin/my_init"] +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \ + slapd=${OPENLDAP_VERSION}* && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* -# Configure apt -RUN echo 'deb http://us.archive.ubuntu.com/ubuntu/ precise universe' >> /etc/apt/sources.list -RUN apt-get -y update - -# Install slapd -RUN LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y slapd - -# Default configuration: can be overridden at the docker command line -ENV LDAP_ROOTPASS toor -ENV LDAP_ORG Acme Widgets Inc. -ENV LDAP_DOMAIN example.com +RUN mv /etc/ldap /etc/ldap.dist EXPOSE 389 -RUN mkdir /etc/service/slapd -ADD slapd.sh /etc/service/slapd/run +VOLUME ["/etc/ldap", "/var/lib/ldap"] -# To store the data outside the container, mount /var/lib/ldap as a data volume +COPY modules/ /etc/ldap.dist/modules -RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +COPY entrypoint.sh /entrypoint.sh -# vim:ts=8:noet: +ENTRYPOINT ["/entrypoint.sh"] + +CMD ["slapd", "-d", "32768", "-u", "openldap", "-g", "openldap"] diff --git a/docker/blocks/openldap/entrypoint.sh b/docker/blocks/openldap/entrypoint.sh new file mode 100755 index 00000000000..39a8b892de8 --- /dev/null +++ b/docker/blocks/openldap/entrypoint.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# When not limiting the open file descritors limit, the memory consumption of +# slapd is absurdly high. See https://github.com/docker/docker/issues/8231 +ulimit -n 8192 + + +set -e + +chown -R openldap:openldap /var/lib/ldap/ + +if [[ ! -d /etc/ldap/slapd.d ]]; then + + if [[ -z "$SLAPD_PASSWORD" ]]; then + echo -n >&2 "Error: Container not configured and SLAPD_PASSWORD not set. " + echo >&2 "Did you forget to add -e SLAPD_PASSWORD=... ?" + exit 1 + fi + + if [[ -z "$SLAPD_DOMAIN" ]]; then + echo -n >&2 "Error: Container not configured and SLAPD_DOMAIN not set. " + echo >&2 "Did you forget to add -e SLAPD_DOMAIN=... ?" + exit 1 + fi + + SLAPD_ORGANIZATION="${SLAPD_ORGANIZATION:-${SLAPD_DOMAIN}}" + + cp -a /etc/ldap.dist/* /etc/ldap + + cat <<-EOF | debconf-set-selections + slapd slapd/no_configuration boolean false + slapd slapd/password1 password $SLAPD_PASSWORD + slapd slapd/password2 password $SLAPD_PASSWORD + slapd shared/organization string $SLAPD_ORGANIZATION + slapd slapd/domain string $SLAPD_DOMAIN + slapd slapd/backend select HDB + slapd slapd/allow_ldap_v2 boolean false + slapd slapd/purge_database boolean false + slapd slapd/move_old_database boolean true +EOF + + dpkg-reconfigure -f noninteractive slapd >/dev/null 2>&1 + + dc_string="" + + IFS="."; declare -a dc_parts=($SLAPD_DOMAIN) + + for dc_part in "${dc_parts[@]}"; do + dc_string="$dc_string,dc=$dc_part" + done + + base_string="BASE ${dc_string:1}" + + sed -i "s/^#BASE.*/${base_string}/g" /etc/ldap/ldap.conf + + if [[ -n "$SLAPD_CONFIG_PASSWORD" ]]; then + password_hash=`slappasswd -s "${SLAPD_CONFIG_PASSWORD}"` + + sed_safe_password_hash=${password_hash//\//\\\/} + + slapcat -n0 -F /etc/ldap/slapd.d -l /tmp/config.ldif + sed -i "s/\(olcRootDN: cn=admin,cn=config\)/\1\nolcRootPW: ${sed_safe_password_hash}/g" /tmp/config.ldif + rm -rf /etc/ldap/slapd.d/* + slapadd -n0 -F /etc/ldap/slapd.d -l /tmp/config.ldif >/dev/null 2>&1 + fi + + if [[ -n "$SLAPD_ADDITIONAL_SCHEMAS" ]]; then + IFS=","; declare -a schemas=($SLAPD_ADDITIONAL_SCHEMAS) + + for schema in "${schemas[@]}"; do + slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/schema/${schema}.ldif" >/dev/null 2>&1 + done + fi + + if [[ -n "$SLAPD_ADDITIONAL_MODULES" ]]; then + IFS=","; declare -a modules=($SLAPD_ADDITIONAL_MODULES) + + for module in "${modules[@]}"; do + slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1 + done + fi + + chown -R openldap:openldap /etc/ldap/slapd.d/ +else + slapd_configs_in_env=`env | grep 'SLAPD_'` + + if [ -n "${slapd_configs_in_env:+x}" ]; then + echo "Info: Container already configured, therefore ignoring SLAPD_xxx environment variables" + fi +fi + +exec "$@" + diff --git a/docker/blocks/openldap/fig b/docker/blocks/openldap/fig index a3e1dadce60..b9528f2d4d7 100644 --- a/docker/blocks/openldap/fig +++ b/docker/blocks/openldap/fig @@ -1,8 +1,9 @@ openldap: - image: cnry/openldap + build: blocks/openldap environment: SLAPD_PASSWORD: grafana SLAPD_DOMAIN: grafana.org + SLAPD_ADDITIONAL_MODULES: memberof ports: - "389:389" diff --git a/docker/blocks/openldap/modules/memberof.ldif b/docker/blocks/openldap/modules/memberof.ldif new file mode 100644 index 00000000000..fd9cce957c3 --- /dev/null +++ b/docker/blocks/openldap/modules/memberof.ldif @@ -0,0 +1,33 @@ +dn: cn=module,cn=config +cn: module +objectClass: olcModuleList +objectClass: top +olcModulePath: /usr/lib/ldap +olcModuleLoad: memberof.la + +dn: olcOverlay={0}memberof,olcDatabase={1}hdb,cn=config +objectClass: olcConfig +objectClass: olcMemberOf +objectClass: olcOverlayConfig +objectClass: top +olcOverlay: memberof +olcMemberOfDangling: ignore +olcMemberOfRefInt: TRUE +olcMemberOfGroupOC: groupOfNames +olcMemberOfMemberAD: member +olcMemberOfMemberOfAD: memberOf + +dn: cn=module,cn=config +cn: module +objectClass: olcModuleList +objectClass: top +olcModulePath: /usr/lib/ldap +olcModuleLoad: refint.la + +dn: olcOverlay={1}refint,olcDatabase={1}hdb,cn=config +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcRefintConfig +objectClass: top +olcOverlay: {1}refint +olcRefintAttribute: memberof member manager owner diff --git a/docker/blocks/openldap/slapd.sh b/docker/blocks/openldap/slapd.sh deleted file mode 100755 index cb756568bc7..00000000000 --- a/docker/blocks/openldap/slapd.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/sh - -set -eu - -status () { - echo "---> ${@}" >&2 -} - -set -x -: LDAP_ROOTPASS=${LDAP_ROOTPASS} -: LDAP_DOMAIN=${LDAP_DOMAIN} -: LDAP_ORGANISATION=${LDAP_ORGANISATION} - -if [ ! -e /var/lib/ldap/docker_bootstrapped ]; then - status "configuring slapd for first run" - - cat < NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization. + +3. Click the `Add new` link in the top header. +4. Select `Graphite` from the dropdown. Name | Description ------------ | ------------- @@ -26,17 +28,21 @@ Default | Default data source means that it will be pre-selected for new panels. Url | The http protocol, ip and port of you graphite-web or graphite-api install. Access | Proxy = access via Grafana backend, Direct = access directory from browser. + +Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the brower. + +Direct access is still supported because in some cases it may be useful to access a Data Source directly depending on the use case and topology of Grafana, the user, and the Data Source. + + ## Metric editor ### Navigate metric segments - Click the ``Select metric`` link to start navigating the metric space. One you start you can continue using the mouse or keyboard arrow keys. You can select a wildcard and still continue. ![](/img/animated_gifs/graphite_query1.gif) ### Functions - Click the plus icon to the right to add a function. You can search for the function or select it from the menu. Once a function is selected it will be added and your focus will be in the text box of the first parameter. To later change a parameter just click on it and it will turn into a text box. To delete a function click the function name followed @@ -70,3 +76,6 @@ You can also create nested variables that use other variables in their definitio ![](/img/v2/templated_variable_parameter.png) + +## Query Reference +You can reference queries by the row “letter” that they’re on (similar to Microsoft Excel). If you add a second query to graph, you can reference the first query simply by typing in #A. This provides an easy and convenient way to build compounded queries. \ No newline at end of file diff --git a/docs/sources/datasources/influxdb.md b/docs/sources/datasources/influxdb.md index 05e627967eb..b48b2b0097a 100644 --- a/docs/sources/datasources/influxdb.md +++ b/docs/sources/datasources/influxdb.md @@ -10,14 +10,18 @@ There are currently two separate datasources for InfluxDB in Grafana: InfluxDB 0 The API and capabilities of InfluxDB 0.9.x are completely different from InfluxDB 0.8.x which is why Grafana handles them as different data sources. +InfluxDB 0.9 is rapidly evolving and we continue to track its API. InfluxDB 0.8 is no longer maintained by InfluxDB Inc, but we provide support as a convenience to existing users. + ## Adding the data source to Grafana -Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you -should find a link named `Data Sources`. If this link is missing in the side menu it means that your current -user does not have the `Admin` role for the current organization. +![](/img/v2/add_Influx.jpg) -![](/img/v2/add_datasource_influxdb.png) +1. Open the side menu by clicking the the Grafana icon in the top header. +2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`. -Now click the `Add new` link in the top header. + > NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization. + +3. Click the `Add new` link in the top header. +4. Select `InfluxDB 0.9.x` or `InfluxDB 0.8.x` from the dropdown. Name | Description ------------ | ------------- @@ -29,8 +33,10 @@ Database | Name of your influxdb database User | Name of your database user Password | Database user's password -> *Note* When using Proxy access mode the InfluxDB database, user and password will be hidden from the browser/frontend. When -> using direct access mode all users will be able to see the database user & password. + > Proxy access means that the Grafana backend will proxy all requests from the browser, and send them on to the Data Source. This is useful because it can eliminate CORS (Cross Origin Site Resource) issues, as well as eliminate the need to disseminate authentication details to the Data Source to the brower. + + > Direct access is still supported because in some cases it may be useful to access a Data Source directly depending on the use case and topology of Grafana, the user, and the Data Source. + ## InfluxDB 0.9.x @@ -45,7 +51,7 @@ the tag key and select `--remove tag filter--`. ### Regex matching You can type in regex patterns for metric names or tag filter values, be sure to wrap the regex pattern in forward slashes (`/`). Grafana -will automaticallay adjust the filter tag condition to use the InfluxDB regex match condition operator (`=~`). +will automatically adjust the filter tag condition to use the InfluxDB regex match condition operator (`=~`). ### Editor group by To group by a tag click the plus icon after the `GROUP BY ($interval)` text. Pick a tag from the dropdown that appears. @@ -62,6 +68,7 @@ You can switch to raw query mode by pressing the pen icon. - $m = replaced with measurement name - $measurement = replaced with measurement name +- $col = replaced with column name - $tag_hostname = replaced with the value of the hostname tag - You can also use [[tag_hostname]] pattern replacement syntax diff --git a/docs/sources/datasources/kairosdb.md b/docs/sources/datasources/kairosdb.md index f0d52b91548..7619c8e8b1f 100644 --- a/docs/sources/datasources/kairosdb.md +++ b/docs/sources/datasources/kairosdb.md @@ -5,15 +5,20 @@ page_keywords: grafana, kairosdb, documentation --- # KairosDB Guide +Grafana v2.1 brings initial support for KairosDB Datasources. While the process of adding the datasource is similar to adding a Graphite or OpenTSDB datasource type, Kairos DB does have a few different options for building queries. ## Adding the data source to Grafana -Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you -should find a link named `Data Sources`. If this link is missing in the side menu it means that your current -user does not have the `Admin` role for the current organization. +![](/img/v2/add_KairosDB.jpg) + +1. Open the side menu by clicking the the Grafana icon in the top header. +2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`. + + > NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization. + +3. Click the `Add new` link in the top header. +4. Select `KairosDB` from the dropdown. - -Now click the `Add new` link in the top header. Name | Description ------------ | ------------- @@ -25,23 +30,23 @@ Access | Proxy = access via Grafana backend, Direct = access directory from brow ## Query editor Open a graph in edit mode by click the title. - +![](/img/v2/kairos_query_editor.png) For details on KairosDB metric queries checkout the offical. - - [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html). ## Templated queries KairosDB Datasource Plugin provides following functions in `Variables values query` field in Templating Editor to query `metric names`, `tag names`, and `tag values` to kairosdb server. Name | Description ----- | ---- -`metrics(query)` | Returns a list of metric names. If nothing is given, returns a list of all metric names. -`tag_names(query)` | Returns a list of tag names. If nothing is given, returns a list of all tag names. -`tag_values(query)` | Returns a list of tag values. If nothing is given, returns a list of all tag values. +| ------- | --------| +`metrics(query)` | Returns a list of metric names matching `query`. If nothing is given, returns a list of all metric names. +`tag_names(query)` | Returns a list of tag names matching `query`. If nothing is given, returns a list of all tag names. +`tag_values(metric,tag)` | Returns a list of values for `tag` from the given `metric`. For details of `metric names`, `tag names`, and `tag values`, please refer to the KairosDB documentations. - [List Metric Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListMetricNames.html) - [List Tag Names - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagNames.html) - [List Tag Values - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/ListTagValues.html) +- [Query Metrics - KairosDB 0.9.4 documentation](http://kairosdb.github.io/kairosdocs/restapi/QueryMetrics.html). \ No newline at end of file diff --git a/docs/sources/datasources/opentsdb.md b/docs/sources/datasources/opentsdb.md index e9110418f4c..3368c39a0ae 100644 --- a/docs/sources/datasources/opentsdb.md +++ b/docs/sources/datasources/opentsdb.md @@ -5,15 +5,17 @@ page_keywords: grafana, opentsdb, documentation --- # OpenTSDB Guide +The newest release of Grafana adds additional functionality when using an OpenTSDB Data source. -## Adding the data source to Grafana -Open the side menu by clicking the the Grafana icon in the top header. In the side menu under the `Dashboards` link you -should find a link named `Data Sources`. If this link is missing in the side menu it means that your current -user does not have the `Admin` role for the current organization. +![](/img/v2/add_OpenTSDB.jpg) -![](/img/v2/add_datasource_opentsdb.png) +1. Open the side menu by clicking the the Grafana icon in the top header. +2. In the side menu under the `Dashboards` link you should find a link named `Data Sources`. -Now click the `Add new` link in the top header. + > NOTE: If this link is missing in the side menu it means that your current user does not have the `Admin` role for the current organization. + +3. Click the `Add new` link in the top header. +4. Select `OpenTSDB` from the dropdown. Name | Description ------------ | ------------- @@ -29,9 +31,12 @@ Open a graph in edit mode by click the title. ### Auto complete suggestions You should get auto complete suggestions for tags and tag values. If you do not you need to enable `tsd.core.meta.enable_realtime_ts` in -the OpentSDB server settings. This is required for the OpenTSDB `lookup` api to work. +the OpenTSDB server settings. + + > Note: This is required for the OpenTSDB `lookup` api to work. ## Templating queries +Grafana's OpenTSDB data source now supports template variable values queries. This means you can create template variables that fetch the values from OpenTSDB (for example metric names, tag names, or tag values). The query editor is also enhanced to limiting tags by metric. When using OpenTSDB with a template variable of `query` type you can use following syntax for lookup. @@ -39,9 +44,4 @@ When using OpenTSDB with a template variable of `query` type you can use followi tag_names(cpu) // return tag names (i.e. keys) for a specific cpu metric tag_values(cpu, hostname) // return tag values for metric cpu and tag key hostname -For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html) - - - - - +For details on opentsdb metric queries checkout the official [OpenTSDB documentation](http://opentsdb.net/docs/build/html/index.html) \ No newline at end of file diff --git a/docs/sources/datasources/overview.md b/docs/sources/datasources/overview.md new file mode 100644 index 00000000000..0badfdaad3d --- /dev/null +++ b/docs/sources/datasources/overview.md @@ -0,0 +1,22 @@ +---- +page_title: Data Source Overview +page_description: Data Source Overview +page_keywords: grafana, graphite, influxDB, KairosDB, OpenTSDB, documentation +--- + +# Data Source Overview +Grafana supports many different storage backends for your time series data (Data Source). Each Data Source has a specific Query Editor that is customized for the features and capabilities that the particular Data Source exposes. + + +## Querying +The query language and capabilities of each Data Source are obviously very different. You can combine data from multiple Data Sources onto a single Dashboard, but each Panel is tied to a specific Data Source that belongs to a particular Organization. + +## Supported Data Sources +The following datasources are officially supported: + +* [Graphite](/datasources/graphite/) +* [InfluxDB](/datasources/influxdb/) +* [OpenTSDB](/datasources/opentsdb/) +* [KairosDB](/datasources/kairosdb) + +Grafana can query any Elasticsearch index for annotation events, but at this time, it's not supported for metric queries. Learn more about [annotations](/reference/annotations/#elasticsearch-annotations) diff --git a/docs/sources/datasources/plugin_api.md b/docs/sources/datasources/plugin_api.md new file mode 100644 index 00000000000..cdcaca29460 --- /dev/null +++ b/docs/sources/datasources/plugin_api.md @@ -0,0 +1,40 @@ +---- +page_title: Data source Plugin API +page_description: Data Source Plugin Description +page_keywords: grafana, data source, plugin, api, docs +--- + +# Data source plugin API + +All data sources in Grafana are implemented as plugins. + +## Breaking change in 2.2 + +In Grafana 2.2 a breaking change was introduced for how data source query editors +are structured, defined and loaded. This was in order to support mixing multiple data sources +in the same panel. + +In Grafana 2.2, the query editor is no longer defined using the partials section in +`plugin.json`, but defined via an angular directive named using convention naming +scheme like `metricQueryEditor`. For example + +Graphite defines a directive like this: + +```javascript +module.directive('metricQueryEditorGraphite', function() { + return {controller: 'GraphiteQueryCtrl', templateUrl: 'app/plugins/datasource/graphite/partials/query.editor.html'}; +}); +``` + +Even though the data source type name is with lowercase `g`, the directive uses capital `G` in `Graphite` because +that is how angular directives needs to be named in order to match an element with name ``. +You also specify the query controller here instead of in the query.editor.html partial like before. + +### query.editor.html + +This partial needs to be updated, remove the `np-repeat` this is done in the outer partial now,m the query.editor.html +should only render a single query. Take a look at the Graphite or InfluxDB partials for `query.editor.html` for reference. +You should also add a `tight-form-item` with `{{target.refId}}`, all queries needs to be assigned a letter (`refId`). +These query reference letters are going to be utilized in a later feature. + + diff --git a/docs/sources/guides/basic_concepts.md b/docs/sources/guides/basic_concepts.md new file mode 100644 index 00000000000..9deda2ee97e --- /dev/null +++ b/docs/sources/guides/basic_concepts.md @@ -0,0 +1,96 @@ +---- +page_title: Graphite query guide +page_description: Graphite query guide +page_keywords: grafana, graphite, metrics, query, documentation +--- + +# Basic Concepts + +This document is a “bottom up” introduction to basic concepts in Grafana, and can be used as a starting point to get familiar with core features. + + +### ** Data Source ** +Grafana supports many different storage backends for your time series data (Data Source). Each Data Source has a specific Query Editor that is customized for the features and capabilities that the particular Data Source exposes. + +The following datasources are officially supported: [Graphite](/datasources/graphite/), [InfluxDB](/datasources/influxdb/), [OpenTSDB](/datasources/opentsdb/), and [KairosDB](/datasources/kairosdb) + +The query language and capabilities of each Data Source are obviously very different. You can combine data from multiple Data Sources onto a single Dashboard, but each Panel is tied to a specific Data Source that belongs to a particular Organization. + +### ** Organization ** +Grafana supports multiple organizations in order to support a wide variety of deployment models, including using a single Grafana instance to provide service to multiple potentially untrusted Organizations. + +In many cases, Grafana will be deployed with a single Organization. + +Each Organization can have one or more Data Sources. + +All Dashboards are owned by a particular Organization. + + > Note: It is important to remember that most metric databases to not provide any sort of per-user series authentication. Therefore, in Grafana, Data Sources and Dashboards are available to all Users in a particular Organization. + +For more details on the user model for Grafana, please refer to [Admin](/reference/admin/) + +### ** User ** +A User is a named account in Grafana. A user can belong to one or more Organizations, and can be assigned different levels of privileges through roles. + +Grafana supports a wide variety of internal and external ways for Users to authenticate themselves. These include from its own integrated database, from an external SQL server, or from an external LDAP server. + +For more details please refer to [User Auth](/reference/http_api/#users) + +### ** Row ** + +A Row is a logical divider within a Dashboard, and is used to group Panels together. + +Rows are always 12 “units” wide. These units are automatically scaled dependent on the horizontal resolution of your browser. You can control the relative width of Panels within a row by setting their own width. + +We utilize a unit abstraction so that Grafana looks great on all screens both small and huge. + + > Note: With MaxDataPoint functionality, Grafana can show you the perfect amount of datapoints no matter your resolution or time-range. + +Utilize the [Repeating Row functionality](/reference/templating/#utilizing-template-variables-with-repeating-panels-and-repeating-rows) to dynamically create or remove entire Rows (that can be filled with Panels), based on the Template variables selected. + +Rows can be collapsed by clicking on the Row Title. If you save a Dashboard with a Row collapsed, it will save in that state and will not preload those graphs until the row is expanded. + +### ** Panel ** + +The Panel is the basic visualization building block in Grafana. Each Panel provides a Query Editor (dependent on the Data Source selected in the panel) that allows you to extract the perfect visualization to display on the Panel by utilizing the Query Editor + +There are a wide variety of styling and formatting options that each Panel exposes to allow you to create the perfect picture. + +Panels can be dragged and dropped and rearranged on the Dashboard. They can also be resized. + +There are currently four Panel types: [Graph](/reference/graph/), [Singlestat](/reference/singlestat/), [Dashlist](/reference/dashlist/), and [Text](/reference/text/). + +Panels like the [Graph](/reference/graph/) panel allow you to graph as many metrics and series as you want. Other panels like [Singlestat](/reference/singlestat/) require a reduction of a single query into a single number. [Dashlist](/reference/dashlist/) and [Text](/reference/text/) are special panels that do not connect to any Data Source. + +Panels can be made more dynamic by utilizing [Dashboard Templating](/reference/templating/) variable strings within the panel configuration (including queries to your Data Source configured via the Query Editor). + +Utilize the [Repeating Panel](/reference/templating/#utilizing-template-variables-with-repeating-panels-and-repeating-rows) functionality to dynamically create or remove Panels based on the [Templating Variables](/reference/templating/#utilizing-template-variables-with-repeating-panels-and-repeating-rows) selected. + +The time range on Panels is normally what is set in the [Dashboard time picker](/reference/timerange/) but this can be overridden by utilizes [Panel specific time overrides](/reference/timerange/#panel-time-override). + +Panels (or an entire Dashboard) can be [Shared](/reference/sharing/) easily in a variety of ways. You can send a link to someone who has a login to your Grafana. You can use the [Snapshot](/reference/sharing/#snapshots) feature to encode all the data currently being viewed into a static and interactive JSON document; it's so much better than emailing a screenshot! + + +### ** Query Editor ** + +The Query Editor exposes capabilities of your Data Source and allows you to query the metrics that it contains. + +Use the Query Editor to build one or more queries (for one or more series) in your time series database. The panel will instantly update allowing you to effectively explore your data in real time and build a perfect query for that particular Panel. + +You can utilize [Template variables]((reference/templating/) in the Query Editor within the queries themselves. This provides a powerful way to explore data dynamically based on the Templating variables selected on the Dashboard. + +Grafana allows you to reference queries in the Query Editor by the row that they’re on. If you add a second query to graph, you can reference the first query simply by typing in #A. This provides an easy and convenient way to build compounded queries. + +### ** Dashboard ** + +The Dashboard is where it all comes together. Dashboards can be thought of as of a set of one or more Panels organized and arranged into one or more Rows. + +The time period for the Dashboard can be controlled by the [Dashboard time picker](/reference/timerange/) in the upper right of the Dashboard. + +Dashboards can utilize [Templating](/reference/templating/) to make them more dynamic and interactive. + +Dashboards can utilize [Annotations](/reference/annotations/) to display event data across Panels. This can help correlate the time series data in the Panel with other events. + +Dashboards (or a specific Panel) can be [Shared](/reference/sharing/) easily in a variety of ways. You can send a link to someone who has a login to your Grafana. You can use the [Snapshot](/reference/sharing/#snapshots) feature to encode all the data currently being viewed into a static and interactive JSON document; it's so much better than emailing a screenshot! + +Dashboards can be tagged, and the Dashboard picker provides quick, searchable access to all Dashboards in a particular Organization. diff --git a/docs/sources/guides/gettingstarted.md b/docs/sources/guides/gettingstarted.md index 4bb250ef55c..93d67048ad6 100644 --- a/docs/sources/guides/gettingstarted.md +++ b/docs/sources/guides/gettingstarted.md @@ -5,17 +5,21 @@ page_keywords: grafana, guide, documentation --- # Getting started -This guide will help you get started and acquainted with the Grafana user interface. It assumes you have a working -Grafana 2.0 instance, and have added at least one Grafana data source. +This guide will help you get started and acquainted with Grafana. It assumes you have a working Grafana 2.x instance, and have added at least one Grafana Data Source. ## Beginner guides -Watch the 10min [beginners guide to building dashboards](https://www.youtube.com/watch?v=sKNZMtoSHN4&index=7&list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2) -to get a quick intro to the dashboard & panel editing UI. +Watch the 10min [beginners guide to building dashboards](https://www.youtube.com/watch?v=sKNZMtoSHN4&index=7&list=PLDGkOdUX1Ujo3wHw9-z5Vo12YLqXRjzg2) to get a quick intro to setting up Dashboards and Panels. + +##Basic Concepts +Read the [Basic Concepts](/guides/basic_concepts) document to get a crash course in key Grafana concepts. + +### Top header + +Let's start with creating a new Dashboard. You can find the new Dashboard link at the bottom of the Dashboard picker. You now have a blank Dashboard. -## Top header -The image above shows you the top header for a dashboard. +The image above shows you the top header for a Dashboard. 1. Side menubar toggle: This toggles the side menu, allowing you to focus on the data presented in the dashboard. The side menu provides access to features unrelated to a Dashboard such as Users, Organizations, and Data Sources. 2. Dashboard dropdown: This dropdown shows you which Dashboard you are currently viewing, and allows you to easily switch to a new Dashboard. From here you can also create a new Dashboard, Import existing Dashboards, and manage Dashboard playlists. @@ -24,12 +28,21 @@ The image above shows you the top header for a dashboard. 5. Save dashboard: The current Dashboard will be saved with the current Dashboard name. 6. Settings: Manage Dashboard settings and features such as Templating and Annotations. -## Dashboard -Dashboards are at the core of what Grafana is all about. Dashboards are composed of individual Panels arranged on a number of Rows. -By adjusting the display properties of Panels and Rows, you can customize the perfect Dashboard for your exact needs. -Each panel can interact with data from any configured Grafana Data Source (currently InfluxDB, Graphite, OpenTSDB, and KairosDB). -This allows you to create a single dashboard that unifies the data across your organization. Panels use the time range specified -in the main Time Picker in the upper right, but they can also have relative time overrides. +## Dashboards, Panels, Rows, the building blocks of Grafana... +Dashboards are at the core of what Grafana is all about. Dashboards are composed of individual Panels arranged on a number of Rows. Grafana ships with a variety of Panels. Gafana makes it easy to construct the right queries, and customize the display properities so that you can create the perfect Dashboard for your need. Each Panel can interact with data from any configured Grafana Data Source (currently InfluxDB, Graphite, OpenTSDB, and KairosDB). The [Core Concepts](/guides/basic_concepts) guide explores these key ideas in detail. + + +## Adding & Editing Graphs and Panels + +![](/img/v2/graph_metrics_tab_graphite.png) + +1. You add panels via row menu. The row menu is the green icon to the left of each row. +2. To edit the graph you click on the graph title to open the panel menu, then `Edit`. +3. This should take you to the `Metrics` tab. In this tab you should see the editor for your default data source. + +When you click the `Metrics` tab, you are presented with a Query Editor that is specific to the Panel Data Source. Use the Query Editor to build your queries and Grafana will visualize them in real time. + + @@ -40,17 +53,9 @@ in the main Time Picker in the upper right, but they can also have relative time 5. Dashboard panel. You edit panels by clicking the panel title. 6. Graph legend. You can change series colors, y-axis and series visibility directly from the legend. -## Adding & Editing Graphs and Panels - -![](/img/v2/graph_metrics_tab_graphite.png) - -1. You add panels via row menu. The row menu is the green icon to the left of each row. -2. To edit the graph you click on the graph title to open the panel menu, then `Edit`. -3. This should take you to the `Metrics` tab. In this tab you should see the editor for your default data source. - ## Drag-and-Drop panels -You can Drag-and-Drop Panels within and between Rows. Click and hold the Panel title, and drag it to its new location. +You can Drag-and-Drop Panels within and between Rows. Click and hold the Panel title, and drag it to its new location. You can also easily resize panels by clicking the (-) and (+) icons. ![](/img/animated_gifs/drag_drop.gif) diff --git a/docs/sources/guides/screencasts.md b/docs/sources/guides/screencasts.md index d8c605a4245..edf3d7b8122 100644 --- a/docs/sources/guides/screencasts.md +++ b/docs/sources/guides/screencasts.md @@ -7,53 +7,60 @@ no_toc: true
-

Episode 1 - Building Graphite Queries

-
+

Episode 1 - Building Graphite Queries

+ Learn how the Graphite Query Editor works, and how to use different graphing functions. There's also an introduction to graph display settings. +
-

Episode 2 - Templated Graphite Queries

-
+

Episode 2 - Templated Graphite Queries

+ The screencast focuses on Templating with the Graphite Data Source. Learn how to make dynamic and adaptable Dashboards for your Graphite metrics. +
- +
-

Episode 3 - Whats New In Grafana 2.0

-
+

Episode 3 - Whats New In Grafana 2.0

+ This screencast highlights many of the great new features that were included in the Grafana 2.0 release. +
-

Episode 4 - Installation & Configuration on Ubuntu / Debian

-
+

Episode 4 - Installation & Configuration on Ubuntu / Debian

+ Learn how to easily install the dependencies and packages to get Grafana 2.0 up and running on Ubuntu or Debian in just a few mintues. +
- +
-

Episode 5 - Installation & Configuration on Redhat / Centos

-
+

Episode 5 - Installation & Configuration on Red Hat / CentOS

+ This screencasts shows how to get Grafana 2.0 installed and configured quickly on RPM-based Linux operating systems. +
-

Episode 6 - Adding data sources, users & organizations

-
+

Episode 6 - Adding data sources, users & organizations

+ Now that Grafana has been installed, learn about adding data sources and get a closer look at adding and managing Users and Organizations. +
- +
-

Episode 7 - Beginners guide to building dashboards

-
+

Episode 7 - Beginners guide to building dashboards

+ For newer users of Grafana, this screencast will familiarize you with the general UI and teach you how to build your first Dashboard. +
diff --git a/docs/sources/guides/whats-new-in-v2-1.md b/docs/sources/guides/whats-new-in-v2-1.md new file mode 100644 index 00000000000..6a6ebba98de --- /dev/null +++ b/docs/sources/guides/whats-new-in-v2-1.md @@ -0,0 +1,130 @@ +--- +page_title: What's New in Grafana v2.1 +page_description: What's new in Grafana v2.1 +page_keywords: grafana, new, changes, features, documentation +--- + +# What's new in Grafana v2.1 +Grafana 2.1 brings improvements in three core areas: dashboarding, authentication, and data sources. +As with every Grafana release, there is a whole slew of new features, enhancements, and bug fixes. + +## More Dynamic Dashboards +The Templating system is one of the most powerful and well-used features of Grafana. +The 2.1 release brings numerous improvements that make dashboards more dynamic than ever before. + +### Multi-Value Template Variables +A template variable with Multi-Value enabled allows for the selection of multiple values at the same time. +These variables can then be used in any Panel to make them more dynamic, and to give you the perfect view of your data. +Multi-Value variables is also enabling the new `row repeat` and `panel repeat` feature described below. + +![Multi-Value Select](/img/v2/multi-select.gif "Multi-Value Select") +

+ +### Repeating Rows and Panels +It’s now possible to create a dashboard that automatically adds (or removes) both rows and panels based +on selected variable values. Any row or any panel can be configured to repeat (duplicate itself) based +on a multi-value template variable.

+ +![Repeating Rows and Panels](/img/v2/panel-row-repeat.gif "Repeating Rows and Panels") +

+ +### Dashboard Links & Navigation +To support better navigation between dashboards, it's now possible to create custom and dynamic links from individual +panels to appropriate Dashboards. You also have the ability to create flexible top-level links on any +given dashboard thanks to the new dashboard navigation bar feature. + +![Dashboard Links](/img/v2/dash_links.png "Dashboard Links") + +Dashboard links can be added under dashboard settings. Either defined as static URLs with a custom icon or as dynamic +dashboard links or dropdowns based on custom dashboard search query. These links appear in the same +row under the top menu where template variables appear. + +- - - + +### Better local Dashboard support +Grafana can now index Dashboards saved locally as JSON from a given directory. These file based dashboards +will appear in the regular dashboard search along regular DB dashboards. + +> ***Note:*** Saving local dashboards back the folder is not supported; this feature is meant for statically generated JSON dashboards. + +- - - + +## New Authentication Options +New authentication methods add numerous options to manage users, roles and organizations. + +### LDAP support +This highly requested feature now allows your Grafana users to login with their LDAP credentials. +You can also specify mappings between LDAP group memberships and Grafana Organization user roles. + +### Basic Auth Support +You can now authenticate against the Grafana API utilizing a simple username and password with basic HTTP authentication. + +> ***Note:*** This can be useful for provisioning and config management systems that need +> to utilize the API without having to create an API key. + + +### Auth Proxy Support +You can now authenticate utilizing a header (eg. X-Authenticated-User, or X-WEBAUTH-USER) + +> ***Note:*** this can be useful in situations with reverse proxies. + + +### New “Read-only Editor” User Role +There is a new User role available in this version of Grafana: “Read-only Editor”. This role behaves just +like the Viewer role does in Grafana 2.0. That is you can edit graphs and queries but not save dashboards. +The Viewer role has been modified in Grafana 2.1 so that users assigned this role can no longer edit panels. + +- - - + +## Data source Improvements + +### InfluxDB 0.9 Support +Grafana 2.1 now comes with full support for InfluxDB 0.9. There is a new query editor designed from scratch +for the new features InfluxDB 0.9 enables. + +![InfluxDB Editor](/img/v2/influx_09_editor_anim.gif "InfluxDB Editor") + +
+ +### OpenTSDB Improvements +Grafana OpenTSDB data source now supports template variable values queries. This means you can create +template variables that fetches the values from OpenTSDB (for example metric names, tag names, or tag values). +The query editor is also enhanced to limiting tags by metric. + +> ***Note:*** OpenTSDB config option tsd.core.meta.enable_realtime_ts must enabled for OpenTSDB lookup api) + +### New Data Source: KairosDB +The Cassandra backed time series database KairosDB is now supported in Grafana out of the box. Thank you to +masaori335 for his hard work in getting it to this point. + +- - - + +## Panel Improvements + +Grafana 2.1 gives you even more flexibility customizing how individual panels render. +Overriding the colors of specific series using regular expressions, changing how series stack, +and allowing string values will help you better understand your data at a glance. + +### Graph Panel +Define series color using regex rule. This is useful when you have templated graphs with series names +that change depending selected template variables. Using a regex style override rule you could +for example make all series that contain the word **CPU** `red` and assigned to the second y axis. + +![Define series color using regex rule](/img/v2/regex_color_override.png "Define series color using regex rule") + +New series style override, negative-y transform and stack groups. Negative y tranform is +very useful if you want to plot a series on the negative y scale without affecting the legend values like min or max or +the values shown in the hover tooltip. + +![Negative-y Transform](/img/v2/negative-y.png "Negative-y Transform") + +![Negative-y Transform](/img/v2/negative-y-form.png "Negative-y Transform") + +### Singlestat Panel +Now support string values. Useful for time series database like InfluxDB that supports +string values. + +### Changelog +For a detailed list and link to github issues for everything included in the 2.1 release please +view the [CHANGELOG.md]("https://github.com/grafana/grafana/blob/master/CHANGELOG.md") file. + diff --git a/docs/sources/guides/whats-new-in-v2.md b/docs/sources/guides/whats-new-in-v2.md index a388aef6160..9e87ab55e6e 100644 --- a/docs/sources/guides/whats-new-in-v2.md +++ b/docs/sources/guides/whats-new-in-v2.md @@ -72,17 +72,17 @@ When you zoom or change the Dashboard time to a custom absolute time range, all The `Hide time override info` option allows you to hide the the override info text that is by default shown in the upper right of a panel when overridden time range options. -Currently you can only override the dashboard time with relative time ranges, not absolute time ranges. +Currently you can only override the dashboard time with relative time ranges, not absolute time ranges. ## Panel iframe embedding -You can embed a single panel on another web page or your own application using the panel share dialog. +You can embed a single panel on another web page or your own application using the panel share dialog. -Below you should see an iframe with a graph panel (taken from a Dashboard snapshot at [snapshot.raintank.io](http://snapshot.raintank.io). +Below you should see an iframe with a graph panel (taken from a Dashboard snapshot at [snapshot.raintank.io](http://snapshot.raintank.io). Try hovering or zooming on the panel below! - + This feature makes it easy to include interactive visualizations from your Grafana instance anywhere you want. @@ -130,7 +130,7 @@ latency, network traffic, and storage) ![](/img/v2/dashlist_starred.png) -The dashlist is a new panel in Grafana v2.0. It allows you to show your personal starred dashboards, as well as do custom searches based on search strings or tags. +The dashlist is a new panel in Grafana v2.0. It allows you to show your personal starred dashboards, as well as do custom searches based on search strings or tags. dashlist is used on the new Grafana Home screen. It is included as a reference Panel and is useful to provide basic linking between Dashboards. @@ -138,7 +138,7 @@ dashlist is used on the new Grafana Home screen. It is included as a reference P Data sources in Grafana v2.0 are no longer defined in a config file. Instead, they are added through the UI or the HTTP API. -The backend can now proxy data from Data Sources, which means that it is a lot easier to get started using Grafana with Graphite or OpenTSDB without having to spend time with CORS (Cross origin resource sharing) work-arounds. +The backend can now proxy data from Data Sources, which means that it is a lot easier to get started using Grafana with Graphite or OpenTSDB without having to spend time with CORS (Cross origin resource sharing) work-arounds. In addition, connections to Data Sources can be better controlled and secured, and authentication information no longer needs to be exposed to the browser. @@ -146,14 +146,14 @@ In addition, connections to Data Sources can be better controlled and secured, a A commonly reported problem has been graphs dipping to zero at the the end, because metric data for the last interval has yet to be written to the Data Source. These graphs then "self correct" once the data comes in, but can look deceiving or alarming at times. -You can avoid this problem by adding a `now delay` in `Dashboard Settings` > `Time Picker` tab. This new feature will cause Grafana to ignore the most recent data up to the set delay. +You can avoid this problem by adding a `now delay` in `Dashboard Settings` > `Time Picker` tab. This new feature will cause Grafana to ignore the most recent data up to the set delay. ![](/img/v2/timepicker_now_delay.jpg) The delay that may be necessary depends on how much latency you have in your collection pipeline. ## Dashboard overwrite protection -Grafana v2.0 protects Users from accidentally overwriting each others Dashboard changes. Similar protections are in place if you try to create a new Dashboard with the same name as an existing one. +Grafana v2.0 protects Users from accidentally overwriting each others Dashboard changes. Similar protections are in place if you try to create a new Dashboard with the same name as an existing one. ![](/img/v2/overwrite_protection.jpg) diff --git a/docs/sources/index.md b/docs/sources/index.md index 491ff31377d..6c1c1ce7837 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -12,7 +12,8 @@ Grafana is most commonly used for Internet infrastructure and application analyt Grafana features pluggable panels and data sources allowing easy extensibility. There is currently rich support for [Graphite](http://graphite.readthedocs.org/en/latest/), [InfluxDB](http://influxdb.org) and [OpenTSDB](http://opentsdb.net). There is also experimental support for [KairosDB](https://github.com/kairosdb/kairosdb), and SQL is on the roadmap. Grafana has a variety of panels, including a fully featured graph panel with rich visualization options. -Version 2.0 was released in April 2015: Grafana now ships with its own backend server that brings [many changes and features](../guides/whats-new-in-v2/). +Version 2.0 was released in April 2015: Grafana now ships with its own backend server that brings [many changes and features](../guides/whats-new-in-v2/). +Version 2.1 was released in July 2015 and added [even more features and enhancements](../guides/whats-new-in-v2-1/). ## Community Resources, Feedback, and Support @@ -24,7 +25,7 @@ Most of the new features and improvements that go into Grafana come from our use If you have any trouble with Grafana, whether you can't get it set up or you just want clarification on a feature, there are a number of ways to get help: -- [Troubleshooting guide](../troubleshooting) +- [Troubleshooting guide](/installation/troubleshooting/) - \#grafana IRC channel on the freenode network (chat.freenode.net) - Search closed and open [issues on GitHub](https://github.com/grafana/grafana/issues) - [Mailing list](https://groups.io/org/groupsio/grafana) diff --git a/docs/sources/installation/configuration.md b/docs/sources/installation/configuration.md index 6310ccb7973..31d029b1101 100644 --- a/docs/sources/installation/configuration.md +++ b/docs/sources/installation/configuration.md @@ -18,7 +18,7 @@ specified in a `.ini` configuration file or specified using environment variable > **Note.** If you have installed Grafana using the `deb` or `rpm` > packages, then your configuration file is located at > `/etc/grafana/grafana.ini`. This path is specified in the Grafana -> init.d script using `-config` file parameter. +> init.d script using `--config` file parameter. ## Using environment variables @@ -37,12 +37,12 @@ should be upper case, `.` should be replaced by `_`. For example, given these co client_secret = 0ldS3cretKey -Then you can override that using: +Then you can override them using: export GF_SECURITY_ADMIN_USER=true export GF_AUTH_GOOGLE_CLIENT_SECRET=newS3cretKey -
+
## [paths] @@ -63,7 +63,7 @@ file. ### http_addr -The IP address to bind to, if empty will bind to all interfaces +The IP address to bind to. If empty will bind to all interfaces ### http_port @@ -116,9 +116,9 @@ Path to the certificate file (if `protocol` is set to `https`). Path to the certificate key file (if `protocol` is set to `https`). -
+
-
+
## [database] @@ -158,7 +158,7 @@ The database user's password (not applicable for `sqlite3`). For `postgres` only, either `disable`, `require` or `verify-full`. -
+
## [security] @@ -184,7 +184,7 @@ Used for signing keep me logged in / remember me cookies. Set to `true` to disable the use of Gravatar for user profile images. Default is `false`. -
+
## [users] @@ -280,6 +280,8 @@ Grafana instance. For example: token_url = https://github.com/login/oauth/access_token allow_sign_up = false +
+ ## [auth.google] You need to create a Google project. You can do this in the [Google @@ -315,6 +317,40 @@ automatically signed up.
+## [auth.basic] +### enabled +When enabled is `true` (default) the http api will accept basic authentication. + +
+ +## [auth.ldap] +### enabled +Set to `true` to enable LDAP integration (default: `false`) + +### config_file +Path to the LDAP specific configuration file (default: `/etc/grafana/ldap.toml`) + +> For details on LDAP Configuration, go to the [LDAP Integration](ldap.md) page. + +
+ +## [auth.proxy] +This feature allows you to handle authentication in a http reverse proxy. + +### enabled +Defaults to `false` + +### header_name +Defaults to X-WEBAUTH-USER + +#### header_property +Defaults to username but can also be set to email + +### auto_sign_up +Set to `true` to enable auto sign up of users who do not exist in Grafana DB. Defaults to `true`. + +
+ ## [session] ### provider @@ -348,25 +384,30 @@ The name of the Grafana session cookie. ### cookie_secure -Set to true if you host Grafana behind HTTPs only. Defaults to `false`. +Set to true if you host Grafana behind HTTPS only. Defaults to `false`. ### session_life_time How long sessions lasts in seconds. Defaults to `86400` (24 hours). +
+ ## [analytics] ### reporting_enabled -When enabled Grafana will send anonymous usage statistics to `stats.grafana.org`. -No IP addresses are being tracked, only simple counters to track running instances, -versions, dashboard & error counts. It is very helpful to us, please leave this -enabled. Counters are sent every 24 hours. Default value is `true`. +When enabled Grafana will send anonymous usage statistics to +`stats.grafana.org`. No IP addresses are being tracked, only simple counters to +track running instances, versions, dashboard & error counts. It is very helpful +to us, so please leave this enabled. Counters are sent every 24 hours. Default +value is `true`. ### google_analytics_ua_id -If you want to track Grafana usage via Google analytics specify *your* Universal Analytics ID -here. By default this feature is disabled. +If you want to track Grafana usage via Google analytics specify *your* Universal +Analytics ID here. By default this feature is disabled. + +
## [dashboards.json] diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 07a7d451626..4693b5020f2 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -10,40 +10,41 @@ page_keywords: grafana, installation, debian, ubuntu, guide Description | Download ------------ | ------------- -.deb for Debian-based Linux | [grafana_2.0.2_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb) +.deb for Debian-based Linux | [grafana_2.1.2_amd64.deb](https://grafanarel.s3.amazonaws.com/builds/grafana_2.1.3_amd64.deb) ## Install - $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.0.2_amd64.deb + $ wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.1.3_amd64.deb $ sudo apt-get install -y adduser libfontconfig - $ sudo dpkg -i grafana_2.0.2_amd64.deb + $ sudo dpkg -i grafana_2.1.3_amd64.deb ## APT Repository + Add the following line to your `/etc/apt/sources.list` file. - deb https://packagecloud.io/grafana/stable/debian/ wheezy main + `deb https://packagecloud.io/grafana/stable/debian/ wheezy main` Use the above line even if you are on Ubuntu or another Debian version. There is also a testing repository if you want beta or release candidates. - deb https://packagecloud.io/grafana/testing/debian/ wheezy main + `deb https://packagecloud.io/grafana/testing/debian/ wheezy main` Then add the [Package Cloud](https://packagecloud.io/grafana) key. This allows you to install signed packages. - $ curl https://packagecloud.io/gpg.key | sudo apt-key add - + `$ curl https://packagecloud.io/gpg.key | sudo apt-key add -` Update your Apt repositories and install Grafana - $ sudo apt-get update - $ sudo apt-get install grafana + `$ sudo apt-get update` + `$ sudo apt-get install grafana` On some older versions of Ubuntu and Debian you may need to install the `apt-transport-https` package which is needed to fetch packages over HTTPS. - $ sudo apt-get install -y apt-transport-https + `$ sudo apt-get install -y apt-transport-https` ## Package details @@ -57,9 +58,9 @@ HTTPS. ## Start the server (init.d service) -You can start Grafana by running: +Start Grafana by running: - $ sudo service grafana-server start + `$ sudo service grafana-server start` This will start the `grafana-server` process as the `grafana` user, which was created during the package installation. The default HTTP port @@ -67,19 +68,19 @@ is `3000` and default user and group is `admin`. To configure the Grafana server to start at boot time: - $ sudo update-rc.d grafana-server defaults 95 10 + `$ sudo update-rc.d grafana-server defaults 95 10` ## Start the server (via systemd) -To start the service using systemd. +To start the service using systemd: - $ systemctl daemon-reload - $ systemctl start grafana-server - $ systemctl status grafana-server + `$ systemctl daemon-reload` + `$ systemctl start grafana-server` + `$ systemctl status grafana-server` Enable the systemd service so that Grafana starts at boot. - sudo systemctl enable grafana-server.service + `sudo systemctl enable grafana-server.service` ## Environment file @@ -96,7 +97,7 @@ By default Grafana will log to `/var/log/grafana` The default configuration specifies a sqlite3 database located at `/var/lib/grafana/grafana.db`. Please backup this database before -upgrades. You can also use MySQL or Postgres as the Grafana database. +upgrades. You can also use MySQL or Postgres as the Grafana database, as detailed on [the configuration page](configuration.md#database). ## Configuration @@ -112,11 +113,10 @@ those options. ## Installing from binary tar file -Start by [downloading](http://grafana.org/download/builds) the latest -`.tar.gz` file and extract it. This will extract into a folder named -after the version you downloaded. This folder contains all files -required to run Grafana. There are no init scripts or install scripts -in this package. +Download [the latest `.tar.gz` file](http://grafana.org/download/builds) and +extract it. This will extract into a folder named after the version you +downloaded. This folder contains all files required to run Grafana. There are +no init scripts or install scripts in this package. To configure Grafana add a configuration file named `custom.ini` to the `conf` folder and override any of the settings defined in diff --git a/docs/sources/installation/docker.md b/docs/sources/installation/docker.md index c11de0faa7b..53366bed44a 100644 --- a/docs/sources/installation/docker.md +++ b/docs/sources/installation/docker.md @@ -6,6 +6,9 @@ page_keywords: grafana, installation, docker, container, guide # Installing using Docker +> **2.0.2 -> 2.1.0 Upgrade NOTICE!** +> The data and log paths were not correct in the previous image. The grafana database was placed by default in /usr/share/grafana/data instead of the correct path /var/lib/grafana. This means it was not in a dir that was marked as a volume. So if you remove the container it will remove the grafana database. So before updating make sure you copy the /usr/share/grafana/data path from inside the container to the host. + ## Install from offical docker image Grafana has an official Docker container. diff --git a/docs/sources/installation/index.md b/docs/sources/installation/index.md index 8116a44d2bd..21c8c127b46 100644 --- a/docs/sources/installation/index.md +++ b/docs/sources/installation/index.md @@ -9,7 +9,7 @@ page_keywords: grafana, installation, documentation Grafana is easily installed via a Debian/Ubuntu package (.deb), via Redhat/Centos package (.rpm) or manually via a tarball that contains all required files and binaries. If you can't find a package or binary for -your platform you might be able to build one your self, read the [build +your platform, you might be able to build one yourself. Read the [build from source](../project/building_from_source) instructions for more information. diff --git a/docs/sources/installation/ldap.md b/docs/sources/installation/ldap.md new file mode 100644 index 00000000000..f0325a953fe --- /dev/null +++ b/docs/sources/installation/ldap.md @@ -0,0 +1,103 @@ +--- +page_title: LDAP Integration +page_description: LDAP Integrtaion guide for Grafana. +page_keywords: grafana, ldap, configuration, documentation, integration +--- + +# LDAP Integration + +Grafana 2.1 ships with a strong LDAP integration feature. The LDAP integration in Grafana allows your +Grafana users to login with their LDAP credentials. You can also specify mappings between LDAP +group memberships and Grafana Organization user roles. + +## Configuration +You turn on LDAP in the [main config file](../configuration/#authldap) as well as specify the path to the LDAP +specific configuration file (default: `/etc/grafana/ldap.toml`). + +### Example config + +```toml +# Set to true to log user information returned from LDAP +verbose_logging = false + +[[servers]] +# LDAP server host +host = "127.0.0.1" +# Usual port is 389, or, if TLS is supported, 636 +port = 389 +# Set to true if LDAP server supports TLS +use_ssl = false +# set to true if you want to skip SSL cert validation +ssl_skip_verify = false + +# Search user bind dn +bind_dn = "cn=admin,dc=grafana,dc=org" +# Search user bind password +bind_password = "grafana" + +# Search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" +search_filter = "(cn=%s)" +# An array of base dns to search through +search_base_dns = ["dc=grafana,dc=org"] + +# Map LDAP user attributes to Grafana user attributes +[servers.attributes] +name = "givenName" +surname = "sn" +username = "cn" +member_of = "memberOf" +email = "email" + +# Map LDAP groups to Grafana org roles +[[servers.group_mappings]] +group_dn = "cn=admins,dc=grafana,dc=org" +org_role = "Admin" +# The Grafana organization database id, optional, if left out, the default org (id 1) will be used +# org_id = 1 + +[[servers.group_mappings]] +group_dn = "cn=users,dc=grafana,dc=org" +org_role = "Editor" + +[[servers.group_mappings]] +# If you want to match all (or no LDAP groups) then you can use wildcard +group_dn = "*" +org_role = "Viewer" +``` + +## Bind & Bind Password + +By default the configuration expects you to specify a bind DN and bind password. This should be a read only user that can perform LDAP searches. +When the user DN is found a second bind is performed with the user provided username & password (in the normal Grafana login form). + +``` +bind_dn = "cn=admin,dc=grafana,dc=org" +bind_password = "grafana" +``` + +### Single Bind Example + +If you can provide a single bind expression that matches all possible users, you can skip the second bind and bind against the user DN directly. +This allows you to not specify a bind_password in the configuration file. + +``` +bind_dn = "cn=%s,o=users,dc=grafana,dc=org" +``` + +In this case you skip providing a `bind_password` and instead provide a `bind_dn` value with a `%s` somewhere. This will be replaced with the username entered in on the Grafana login page. +The search filter and search bases settings are still needed to perform the LDAP search to retreive the other LDAP information (like LDAP groups and email). + +## LDAP to Grafana Org Role Sync + +## Group Mappings +In `[[servers.group_mappings]]` you can map an LDAP group to a Grafana organization and role. These will be synced every time the user logs in, with LDAP being the authoratative source. +So, if you change a user's role in the Grafana Org. Users page, this change will be reset the next time the user logs in. If you change the LDAP groups of a user, the change will take effect the next time the user logs in. + +### Priority between Multiple Mappings +The first group mapping that an LDAP user is matched to will be used for the sync. If you have LDAP users that fit multiple mappings, the topmost mapping in the TOML config will be used. + +## Ldap to Grafana Org Role Sync +In the `[[servers.group_mappings]]` you can map a LDAP group to a grafana organization and role. These will be synced every time the user logs in. So +if you change a users role in the Grafana Org. Users page, this change will be reset the next time the user logs in. Similarly if you +can LDAP groups for a user in LDAP the change will take effect the next time the user logs in to Grafana. + diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 48f8ca075a3..7246033b640 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -6,7 +6,7 @@ page_keywords: grafana, installation, mac, osx, guide # Installing on Mac -There is currently no binary build for Mac. But read the [build from +There is currently no binary build for Mac, but Grafana will happily build on Mac. Read the [build from source](/project/building_from_source) page for instructions on how to build it yourself. diff --git a/docs/sources/installation/performance.md b/docs/sources/installation/performance.md index 6dcc38b5f78..51ab747bcd2 100644 --- a/docs/sources/installation/performance.md +++ b/docs/sources/installation/performance.md @@ -14,7 +14,7 @@ rendering times for large time ranges then it is most likely caused by running Graphite 0.9.12 or lower. The latest version of Graphite adds a `maxDataPoints` parameter to the -JSON render API, without this feature Graphite can return hundreds of +JSON render API. Without this feature Graphite can return hundreds of thousands of data points per graph, which can hang your browser. Be sure to upgrade to [0.9.13](http://graphite.readthedocs.org/en/latest/releases/0_9_13.html). diff --git a/docs/sources/installation/provisioning.md b/docs/sources/installation/provisioning.md index ea57b0d00dd..2ec79d54741 100644 --- a/docs/sources/installation/provisioning.md +++ b/docs/sources/installation/provisioning.md @@ -10,25 +10,21 @@ Here are links for how to install Grafana (and some include Graphite or InfluxDB as well) via a provisioning system. These are not maintained by any core Grafana team member and might be out of date. -## Puppet +Some of the linked cookbooks/manifests/etc. will install and configure Grafana 2.x, while some will only install the older Grafana 1.x versions. They've been broken apart below for your convenience. -* [forge.puppetlabs.com/bfraser/grafana](https://forge.puppetlabs.com/bfraser/grafana) +### Puppet -## Ansible +* [forge.puppetlabs.com/bfraser/grafana](https://forge.puppetlabs.com/bfraser/grafana) **Note:** The current version works with Grafana 2.x. To install older versions of Grafana use the 1.x series of releases. + +### Ansible -* [github.com/bobrik/ansible-grafana](https://github.com/bobrik/ansible-grafana) -* [github.com/bitmazk/ansible-digitalocean-influxdb-grafana](https://github.com/bitmazk/ansible-digitalocean-influxdb-grafana) * [github.com/picotrading/ansible-grafana](https://github.com/picotrading/ansible-grafana) -## Docker +### Docker +* [github.com/grafana/grafana-docker](https://github.com/grafana/grafana-docker) -* [github.com/kamon-io/docker-grafana-graphite](https://github.com/kamon-io/docker-grafana-graphite) -* [github.com/kamon-io/docker-grafana-influxdb](https://github.com/kamon-io/docker-grafana-influxdb) -* [github.com/tutumcloud/tutum-docker-grafana](https://github.com/tutumcloud/tutum-docker-grafana) -* [github.com/mingfang/docker-grafana](https://github.com/mingfang/docker-grafana) +### Chef -## Chef - -* [github.com/JonathanTron/chef-grafana](https://github.com/JonathanTron/chef-grafana) -* [github.com/dzautner/grafana-cookbook](https://github.com/dzautner/grafana-cookbook) +* [github.com/JonathanTron/chef-grafana](https://github.com/JonathanTron/chef-grafana) **Note:** The current version works with Grafana 2.x. To install older versions of Grafana use the 1.x series of releases. +* [github.com/Nordstrom/grafana2-cookbook](https://github.com/Nordstrom/grafana2-cookbook) diff --git a/docs/sources/installation/rpm.md b/docs/sources/installation/rpm.md index ab0f5e3be24..c112c4c9fea 100644 --- a/docs/sources/installation/rpm.md +++ b/docs/sources/installation/rpm.md @@ -10,18 +10,18 @@ page_keywords: grafana, installation, centos, fedora, opensuse, redhat, guide Description | Download ------------ | ------------- -.RPM for Fedora / RHEL / CentOS Linux | [grafana-2.0.2-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.2-1.x86_64.rpm) +.RPM for Fedora / RHEL / CentOS Linux | [grafana-2.1.3-1.x86_64.rpm](https://grafanarel.s3.amazonaws.com/builds/grafana-2.1.3-1.x86_64.rpm) ## Install from package file You can install Grafana using Yum directly. - $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.0.2-1.x86_64.rpm + $ sudo yum install https://grafanarel.s3.amazonaws.com/builds/grafana-2.1.3-1.x86_64.rpm Or install manually using `rpm`. $ sudo yum install initscripts fontconfig - $ sudo rpm -Uvh grafana-2.0.1-1.x86_64.rpm + $ sudo rpm -Uvh grafana-2.1.3-1.x86_64.rpm ## Install via YUM Repository @@ -100,7 +100,7 @@ By default Grafana will log to `/var/log/grafana` The default configuration specifies a sqlite3 database located at `/var/lib/grafana/grafana.db`. Please backup this database before -upgrades. You can also use MySQL or Postgres as the Grafana database. +upgrades. You can also use MySQL or Postgres as the Grafana database, as detailed on [the configuration page](configuration.md#database). ## Configuration diff --git a/docs/sources/installation/troubleshooting.md b/docs/sources/installation/troubleshooting.md index 7a6f8a9d8ee..7a6865b9da1 100644 --- a/docs/sources/installation/troubleshooting.md +++ b/docs/sources/installation/troubleshooting.md @@ -18,8 +18,8 @@ an error like this: ![](/img/v1/graph_timestore_error.png) -For some type of errors the `View details` link will show you error -details. But for many types of HTTP connection errors there is very +For some types of errors, the `View details` link will show you error +details. For many types of HTTP connection errors, however, there is very little information. The best way to troubleshoot these issues is use the [Chrome developer tools](https://developer.chrome.com/devtools/index). By pressing `F12` you can bring up the chrome dev tools. diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 97d128f61a0..1ef98ae4896 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -10,7 +10,7 @@ page_keywords: grafana, installation, windows guide Description | Download ------------ | ------------- -Zip package for Windows | [grafana.2.0.2.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.0.2.windows-x64.zip) +Zip package for Windows | [grafana.2.1.3.windows-x64.zip](https://grafanarel.s3.amazonaws.com/winbuilds/dist/grafana-2.1.3.windows-x64.zip) ## Configure diff --git a/docs/sources/project/building_from_source.md b/docs/sources/project/building_from_source.md index ee8d6e66c45..45ab26249c8 100644 --- a/docs/sources/project/building_from_source.md +++ b/docs/sources/project/building_from_source.md @@ -6,16 +6,16 @@ page_keywords: grafana, build, contribute, documentation # Building Grafana from source -Guide for creating packages from source, and for getting grafana up and running in -dev environment. +This guide will help you create packages from source and get grafana up and running in +dev environment. Grafana ships with its own required backend server; also completely open-source. It's written in [Go](http://golang.org) and has a full [HTTP API](/v2.1/reference/http_api/). ## Dependencies -- Go 1.4 -- NodeJS +- [Go 1.4](https://golang.org/dl/) +- [NodeJS](https://nodejs.org/download/) ## Get Code - +Create a directory for the project and set your path accordingly. Then download and install Grafana into your $GOPATH directory ``` export GOPATH=`pwd` go get github.com/grafana/grafana @@ -24,16 +24,16 @@ go get github.com/grafana/grafana ## Building the backend ``` cd $GOPATH/src/github.com/grafana/grafana -go run build.go setup (only needed once to install godep) -$GOPATH/bin/godep restore (will pull down all golang lib dependecies in your current GOPATH) -go build . +go run build.go setup # (only needed once to install godep) +$GOPATH/bin/godep restore # (will pull down all golang lib dependecies in your current GOPATH) +go run build.go build # (or 'go build .') ``` -## Building on Windows +#### Building on Windows The Grafana backend includes Sqlite3 which requires GCC to compile. So in order to compile Grafana on windows you need to install GCC. We recommend [TDM-GCC](http://tdm-gcc.tdragon.net/download). -## Building frontend assets +## Build the Front-end Assets To build less to css for the frontend you will need a recent version of of node (v0.12.0), npm (v2.5.0) and grunt (v0.4.5). Run the following: @@ -51,12 +51,24 @@ go get github.com/Unknwon/bra bra run ``` -## Running +## Running Grafana Locally +You can run a local instance of Grafana by running: ``` -./grafana-server +./bin/grafana-server ``` +If you built the binary with `go run build.go build`, run `./bin/grafana-server` -Open grafana in your browser (default http://localhost:3000) and login with admin user (default user/pass = admin/admin). +If you built it with `go build .`, run `./grafana` + +Open grafana in your browser (default [http://localhost:3000](http://localhost:3000)) and login with admin user (default user/pass = admin/admin). + +## Developing for Grafana +To add features, customize your config, etc, you'll need to rebuild on source change (requires that you executed [godep restore](#build-the-backend), as outlined above). +``` +go get github.com/Unknwon/bra +bra run +``` +You'll also need to run `grunt watch` to watch for changes to the front-end. ## Creating optimized release packages This step builds linux packages and requires that fpm is installed. Install fpm via `gem install fpm`. @@ -73,6 +85,10 @@ You only need to add the options you want to override. Config files are applied 1. grafana.ini 2. custom.ini -## Create a pull requests +Learn more about Grafana config options in the [Configuration section](/installation/configuration/) -Before or after your create a pull requests, sign the [contributor license agreement](/project/cla.html). +## Create a pull requests +Please contribute to the Grafana project and submit a pull request! Build new features, write or update documentation, fix bugs and generally make Grafana even more awesome. + +Before or after you create a pull request, sign the [contributor license agreement](/project/cla.html). +Together we can build amazing software faster. \ No newline at end of file diff --git a/docs/sources/project/cla.md b/docs/sources/project/cla.md index c1450bb6fa9..4554300249d 100644 --- a/docs/sources/project/cla.md +++ b/docs/sources/project/cla.md @@ -28,7 +28,7 @@ but protects both the contributor and the company / foundation behind the projec It also gives us the option to relicense the code with a more permissive license in the future. -If you have more questions, shoot us an email or drop by #grafana on IRC (freenode). +If you have more questions, shoot us an [email](mailto:torkel@grafana.org) or drop by #grafana on IRC (freenode). Many thanks to [RethinkDB](http://rethinkdb.com) for permission to re-use their CLA! @@ -82,4 +82,4 @@ You are not expected to provide support for your contributions, except to the ex The failure of either party to enforce its rights under this agreement for any period shall not be construed as a waiver of such rights. No changes or modifications or waivers to this Agreement will be effective unless in writing and signed by both parties. In the event that any provision of this agreement shall be determined to be illegal or unenforceable, that provision will be limited or eliminated to the minimum extent necessary so that this agreement shall otherwise remain in full force and effect and enforceable. This agreement shall be governed by and construed in accordance with the laws of the State of California in the United States without regard to the conflicts of laws provisions thereof. In any action or proceeding to enforce rights under this agreement, the prevailing party will be entitled to recover costs and attorneys’ fees. - + \ No newline at end of file diff --git a/docs/sources/reference/annotations.md b/docs/sources/reference/annotations.md index b0e84ef762b..51852abcdf2 100644 --- a/docs/sources/reference/annotations.md +++ b/docs/sources/reference/annotations.md @@ -5,14 +5,17 @@ page_keywords: grafana, annotations, guide, documentation --- # Annotations -![](/img/v1/annotated_graph1.png) Annotations provide a way to mark points on the graph with rich events. When you hover over an annotation you can get title, tags, and text information for the event. +![](/img/v1/annotated_graph1.png) + To add an annotation query click dashboard settings icon in top menu and select `Annotations` from the dropdown. This will open the `Annotations` edit view. Click the `Add` tab to add a new annotation query. +> Note: Annotations apply to all graphs in a given dashboard, not on a per-panel basis. + ## Graphite annotations Graphite supports two ways to query annotations. diff --git a/docs/sources/reference/dashlist.md b/docs/sources/reference/dashlist.md index 9a54a18e288..1f0794dabfc 100644 --- a/docs/sources/reference/dashlist.md +++ b/docs/sources/reference/dashlist.md @@ -7,14 +7,26 @@ page_keywords: grafana, dashlist, panel, documentation # Dashlist Panel ## Overview -![](/img/v2/dashboard_list_panel.png) -The dashboard list panel allows you to show a list of links to other dashboards. The list -can be based on a search query or dashboard tag query. You can also configure it to show your starred -dashboards. +The dashboard list panel allows you to display dynamic links to other dashboards. The list can be configured to use starred dashbaords, a search query and/or dashboard tags. + + + +> On each dashboard load, the dashlist panel will re-query the dashboard list, always providing the most up to date results. + +## Mode: Starred Dashboards + +The `starred` dashboard selection displays starred dashboards, up to the number specified in the `Limit Number to` field, in alphabetical order. On dashboard load, the dashlist panel will re-query the favorites to appear in dashboard list panel, always providing the most up to date results. + + + + +## Mode: Search Dashboards + +The panel may be configured to search by either string query or tag(s). On dashboard load, the dashlist panel will re-query the dashboard list, always providing the most up to date results. + +To configure dashboard list in this manner, select `search` from the Mode select box. When selected, the Search Options section will appear. -## Options -![](/img/v2/dashboard_list_panel_options.png) Name | Description ------------ | ------------- @@ -24,4 +36,17 @@ Tags | if in search mode specify dashboard tags to search for Limit number to | Specify the maximum number of dashboards +### Search by string + +To search by a string, enter a search query in the `Search Options: Query` field. Queries are case-insensitive, and partial values are accepted. + + +### Search by tag +To search by one or more tags, enter your selection in the `Search Options: Tags:` field. Note that existing tags will not appear as you type, and *are* case sensitive. To see a list of existing tags, you can always return to the dashboard, open the Dashboard Picker at the top and click `tags` link in the search bar. + + +> When multiple tags and strings appear, the dashboard list will display those matching ALL conditions. + + + diff --git a/docs/sources/reference/export_import.md b/docs/sources/reference/export_import.md index 8ae39cb0cf2..e83c68401d4 100644 --- a/docs/sources/reference/export_import.md +++ b/docs/sources/reference/export_import.md @@ -6,5 +6,80 @@ page_keywords: grafana, export, import, documentation # Export and Import -You find the import view in the bottom of the search dropdown. From this view you -can import local json files or migrate dashboards stored in Elasticsearch or InfluxDB. +## Exporting a dashboard + +Dashboards are exported in Grafana JSON format, and contain everything you need (layout, variables, styles, data sources, queries, etc)to import the dashboard at a later time. + +#### Export to file + +To export a dashboard, locate the settings menu within the desired dashboard and click the gear icon. The export option will always be available, and will open a browser save-as dialog window. + + + +#### Copy JSON + +The raw JSON may be accessed directly from within the interface and copy/pasted into an editor of your choice to be saved later. To view this JSON, locate the settings menu within the desired dashboard and click the gear icon. The View JSON option will always be available, and will open the raw JSON in a text area. To copy the entire JSON file, click into the text area, the select all `CTRL`+`A` (PC, Linux) or `⌘`+`A` (Mac). + + + +## Importing a dashboard + +Grafana 2.0 now has integrated dashboard storage engine that can be configured to use an internal sqlite3 database, MySQL, or Postgres. This eliminates the need to use Elasticsearch for dashboard storage for Graphite users. Grafana 2.0 does not support storing dashboards in InfluxDB. + +The import view can be found at the Dashboard Picker dropdown, next to the New Dashboard and Playlist buttons. + + + + +#### Import from a file + +To import a dashboard through a local JSON file, click the 'Choose file' button in the Import from File section. Note that JSON is not linted or validated prior during upload, so we recommend validating locally if you're editing. In a pinch, you can use http://jsonlint.com/, and if you are editing dashboard JSON frequently, there are linter plugins for popular text editors. + + +#### Importing dashboards from Elasticsearch + +Start by going to the `Data Sources` view (via the side menu), and make sure your Elasticsearch data source is added. Specify the Elasticsearch index name where your existing Grafana v1.x dashboards are stored (the default is `grafana-dash`). + +![](/img/v2/datasource_edit_elastic.jpg) + +#### Importing dashboards from InfluxDB + +Start by going to the `Data Sources` view (via the side menu), and make sure your InfluxDB data source is added. Specify the database name where your Grafana v1.x dashboards are stored, the default is `grafana`. + +### Import view + +In the Import view you find the section `Migrate dashboards`. Pick the data source you added (from Elasticsearch or InfluxDB), and click the `Import` button. + +![](/img/v2/migrate_dashboards.jpg) + +Your dashboards should be automatically imported into the Grafana 2.0 back-end. Dashboards will no longer be stored in your previous Elasticsearch or InfluxDB databases. + + +## Troubleshooting + +### Template variables could not be initialized. + +When importing a dashboard, keep an eye out for template variables in your JSON that may not exist in your instance of Grafana. For example, + + "templating": { + "list": [ + { + "allFormat": "glob", + "current": { + "tags": [], + "text": "google_com + monkey_id_au", + "value": [ + "google_com", + "monkey_id_au" + ] + }, + "datasource": null, + +To resolve this, remove any unnecessary JSON that may be specific to the instance you are exporting from. In this case, we can remove the entire "current" section entirely, and Grafana will populate default. + + "templating": { + "list": [ + { + "allFormat": "glob", + "datasource": null, + \ No newline at end of file diff --git a/docs/sources/reference/http_api.md b/docs/sources/reference/http_api.md index fb9e7600ecf..609f12269ca 100644 --- a/docs/sources/reference/http_api.md +++ b/docs/sources/reference/http_api.md @@ -11,8 +11,21 @@ dashboards, creating users and updating data sources. ## Authorization +### Tokens + Currently you can authenticate via an `API Token` or via a `Session cookie` (acquired using regular login or oauth). +### Basic Auth + +If basic auth is enabled (it is enabled by default) you can authenticate your HTTP request via +standard basic auth. + +curl example: +``` +?curl http://admin:admin@localhost:3000/api/org +{"id":1,"name":"Main Org."} +``` + ### Create API Token Open the sidemenu and click the organization dropdown and select the `API Keys` option. @@ -141,14 +154,106 @@ Will return the dashboard given the dashboard slug. Slug is the url friendly ver The above will delete the dashboard with the specified slug. The slug is the url friendly (unique) version of the dashboard title. +**Example Request**: + + DELETE /api/dashboards/db/test HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"title": "Test"} + ### Gets the home dashboard `GET /api/dashboards/home` +Will return the home dashboard. + +**Example Request**: + + GET /api/dashboards/home HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "meta": { + "isHome":true, + "canSave":false, + "canEdit":false, + "canStar":false, + "slug":"", + "expires":"0001-01-01T00:00:00Z", + "created":"0001-01-01T00:00:00Z" + }, + "dashboard": { + "editable":false, + "hideControls":true, + "nav":[ + { + "enable":false, + "type":"timepicker" + } + ], + "rows": [ + { + + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 + } + } + ### Tags for Dashboard + `GET /api/dashboards/tags` +Get all tabs of dashboards + +**Example Request**: + + GET /api/dashboards/home HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "term":"tag1", + "count":1 + }, + { + "term":"tag2", + "count":4 + } + ] + ### Dashboard from JSON file `GET /file/:file` @@ -171,46 +276,201 @@ Status Codes: Content-Type: application/json Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "id":1, + "title":"Production Overview", + "uri":"db/production-overview", + "type":"dash-db", + "tags":[], + "isStarred":false + } + ] + ## Data sources ### Get all datasources `GET /api/datasources` +**Example Request**: + + GET /api/datasources HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "id":1, + "orgId":1, + "name":"datasource_elastic", + "type":"elasticsearch", + "access":"proxy", + "url":"http://mydatasource.com", + "password":"", + "user":"", + "database":"grafana-dash", + "basicAuth":false, + "basicAuthUser":"", + "basicAuthPassword":"", + "isDefault":false, + "jsonData":null + } + ] + ### Get a single data sources by Id `GET /api/datasources/:datasourceId` +**Example Request**: + + GET /api/datasources/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "id":1, + "orgId":1, + "name":"test_datasource", + "type":"graphite", + "access":"proxy", + "url":"http://mydatasource.com", + "password":"", + "user":"", + "database":"", + "basicAuth":false, + "basicAuthUser":"", + "basicAuthPassword":"", + "isDefault":false, + "jsonData":null + } + ### Create data source `POST /api/datasources` +**Example Request**: + + POST /api/datasources HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "name":"test_datasource", + "type":"graphite", + "url":"http://mydatasource.com", + "access":"proxy", + "basicAuth":false + } + + **Example Response**: HTTP/1.1 200 Content-Type: application/json - {"message":"Datasource added"} + {"id":1,"message":"Datasource added"} ### Update an existing data source `PUT /api/datasources/:datasourceId` +**Example Request**: + + PUT /api/datasources/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "id":1, + "orgId":1, + "name":"test_datasource", + "type":"graphite", + "access":"proxy", + "url":"http://mydatasource.com", + "password":"", + "user":"", + "database":"", + "basicAuth":true, + "basicAuthUser":"basicuser", + "basicAuthPassword":"basicuser", + "isDefault":false, + "jsonData":null + } + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Datasource updated"} + ### Delete an existing data source `DELETE /api/datasources/:datasourceId` +**Example Request**: + + DELETE /api/datasources/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + **Example Response**: HTTP/1.1 200 Content-Type: application/json - {"message":"Data source deleted"} + {"message":"Data source deleted"} ### Available data source types `GET /api/datasources/plugins` +**Example Request**: + + GET /api/datasources/plugins HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "grafana":{ + "metrics":true,"module":"plugins/datasource/grafana/datasource", + "name":"Grafana (for testing)", + "partials":{ + "query":"app/plugins/datasource/grafana/partials/query.editor.html" + }, + "pluginType":"datasource", + "serviceName":"GrafanaDatasource", + "type":"grafana" + } + } + ## Data source proxy calls `GET /api/datasources/proxy/:datasourceId/*` @@ -223,27 +483,141 @@ Proxies all calls to the actual datasource. `GET /api/org` +**Example Request**: + + GET /api/org HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "id":1, + "name":"Main Org." + } + +### Update current Organisation + +`PUT /api/org` + +**Example Request**: + + PUT /api/org HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "name":"Main Org." + } + + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Organization updated"} + + ### Get all users within the actual organisation `GET /api/org/users` +**Example Request**: + + GET /api/org/users HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "orgId":1, + "userId":1, + "email":"admin@mygraf.com", + "login":"admin", + "role":"Admin" + } + ] + ### Add a new user to the actual organisation `POST /api/org/users` Adds a global user to the actual organisation. +**Example Request**: + + POST /api/org/users HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "role": "Admin", + "loginOrEmail": "admin" + } + + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User added to organization"} + ### Updates the given user `PATCH /api/org/users/:userId` +**Example Request**: + + PATCH /api/org/users/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "role": "Viewer", + } + + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Organization user updated"} + + ### Delete user in actual organisation `DELETE /api/org/users/:userId` -### Get all Users +**Example Request**: + + DELETE /api/org/users/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User removed from organization"} -`GET /api/org/users` ## Organisations @@ -251,57 +625,297 @@ Adds a global user to the actual organisation. `GET /api/orgs` +**Example Request**: + + GET /api/orgs HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "id":1, + "name":"Main Org." + } + ] + ### Update Organisation `PUT /api/orgs/:orgId` +Update Organisation, fields *Adress 1*, *Adress 2*, *City* are not implemented yet. + +**Example Request**: + + PUT /api/orgs/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "name":"Main Org 2." + } + + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Organization updated"} + ### Get Users in Organisation `GET /api/orgs/:orgId/users` +**Example Request**: + + GET /api/orgs/1/users HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + [ + { + "orgId":1, + "userId":1, + "email":"admin@mygraf.com", + "login":"admin", + "role":"Admin" + } + ] + ### Add User in Organisation `POST /api/orgs/:orgId/users` +**Example Request**: + + POST /api/orgs/1/users HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "loginOrEmail":"user", + "role":"Viewer" + } + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User added to organization"} + ### Update Users in Organisation `PATCH /api/orgs/:orgId/users/:userId` +**Example Request**: + + PATCH /api/orgs/1/users/2 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "role":"Admin" + } + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Organization user updated"} + ### Delete User in Organisation `DELETE /api/orgs/:orgId/users/:userId` +**Example Request**: + + DELETE /api/orgs/1/users/2 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User removed from organization"} + ## Users ### Search Users `GET /api/users` +**Example Request**: + + GET /api/users HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + [ + { + "id": 1, + "name": "Admin", + "login": "admin", + "email": "admin@mygraf.com", + "isAdmin": true + }, + { + "id": 2, + "name": "User", + "login": "user", + "email": "user@mygraf.com" + "isAdmin": false + } + ] + ### Get single user by Id `GET /api/users/:id` +**Example Request**: + + GET /api/users/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "email": "user@mygraf.com" + "name": "admin", + "login": "admin", + "theme": "light", + "orgId": 1, + "isGrafanaAdmin": true + } + ### User Update `PUT /api/users/:id` +**Example Request**: + + PUT /api/users/2 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "email":"user@mygraf.com", + "name":"User2", + "login":"user", + "theme":"light" + } +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User updated"} + + ### Get Organisations for user `GET /api/users/:id/orgs` +**Example Request**: + + GET /api/users/1/orgs HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "orgId":1, + "name":"Main Org.", + "role":"Admin" + } + ] + ## User +### Actual User + +`GET /api/user` + +**Example Request**: + + GET /api/user HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "email":"admin@mygraf.com", + "name":"Admin", + "login":"admin", + "theme":"light", + "orgId":1, + "isGrafanaAdmin":true + } + ### Change Password `PUT /api/user/password` Changes the password for the user -### Actual User +**Example Request**: -`GET /api/user` + PUT /api/user/password HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk -The above will return the current user. + { + "password": ""new_password" + } + + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User password updated"} ### Switch user context @@ -309,11 +923,45 @@ The above will return the current user. Switch user context to the given organisation. -### Organisations of the actual User +**Example Request**: + + POST /api/user/using/2 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Active organization changed"} + +### Organisations of the actual User `GET /api/user/orgs` -The above will return a list of all organisations of the current user. +Return a list of all organisations of the current user. + +**Example Request**: + + GET /api/user/orgs HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + [ + { + "orgId":1, + "name":"Main Org.", + "role":"Admin" + } + ] ### Star a dashboard @@ -321,11 +969,40 @@ The above will return a list of all organisations of the current user. Stars the given Dashboard for the actual user. +**Example Request**: + + POST /api/user/stars/dashboard/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Dashboard starred!"} + ### Unstar a dashboard `DELETE /api/user/stars/dashboard/:dashboardId` -Deletes the staring of the given Dashboard for the actual user. +Deletes the starring of the given Dashboard for the actual user. + +**Example Request**: + + DELETE /api/user/stars/dashboard/1 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Dashboard unstarred"} + ## Snapshots @@ -333,13 +1010,133 @@ Deletes the staring of the given Dashboard for the actual user. `POST /api/snapshots` -### Get Snapshot by Id +**Example Request**: + POST /api/snapshots HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "dashboard": { + "editable":false, + "hideControls":true, + "nav":[ + { + "enable":false, + "type":"timepicker" + } + ], + "rows": [ + { + + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 + } + "expires": 3600 + } + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + { + "deleteKey":"XXXXXXX", + "deleteUrl":"myurl/dashboard/snapshot/XXXXXXX", + "key":"YYYYYYY", + "url":"myurl/dashboard/snapshot/YYYYYYY" + } + +Keys: + +- **deleteKey** – Key generated to delete the snapshot +- **key** – Key generated to share the dashboard + +### Get Snapshot by Id + `GET /api/snapshots/:key` +**Example Request**: + + GET /api/snapshots/YYYYYYY HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "meta":{ + "isSnapshot":true, + "type":"snapshot", + "canSave":false, + "canEdit":false, + "canStar":false, + "slug":"", + "expires":"2200-13-32T25:23:23+02:00", + "created":"2200-13-32T28:24:23+02:00"}, + + { + "dashboard": { + "editable":false, + "hideControls":true, + "nav":[ + { + "enable":false, + "type":"timepicker" + } + ], + "rows": [ + { + + } + ], + "style":"dark", + "tags":[], + "templating":{ + "list":[ + ] + }, + "time":{ + }, + "timezone":"browser", + "title":"Home", + "version":5 + } + } + ### Delete Snapshot by Id -`DELETE /api/snapshots-delete/:key` +`GET /api/snapshots-delete/:key` + +**Example Request**: + + GET /api/snapshots/YYYYYYY HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"Snapshot deleted. It might take an hour before it's cleared from a CDN cache."} + ## Frontend Settings @@ -347,30 +1144,304 @@ Deletes the staring of the given Dashboard for the actual user. `GET /api/frontend/settings` +**Example Request**: + + GET /api/frontend/settings HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "allowOrgCreate":true, + "appSubUrl":"", + "buildInfo":{ + "buildstamp":xxxxxx, + "commit":"vyyyy", + "version":"zzzzz" + }, + "datasources":{ + "datasourcename":{ + "index":"grafana-dash", + "meta":{ + "annotations":true, + "module":"plugins/datasource/grafana/datasource", + "name":"Grafana", + "partials":{ + "annotations":"app/plugins/datasource/grafana/partials/annotations.editor.html", + "config":"app/plugins/datasource/grafana/partials/config.html" + }, + "pluginType":"datasource", + "serviceName":"Grafana", + "type":"grafanasearch" + } + } + } + + defaultDatasource: "Grafana" + } + ## Login ### Renew session based on remember cookie `GET /api/login/ping` +**Example Request**: + + GET /api/login/ping HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message": "Logged in"} + ## Admin ### Settings `GET /api/admin/settings` +**Example Request**: + + GET /api/admin/settings + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + { + "DEFAULT": + { + "app_mode":"production"}, + "analytics": + { + "google_analytics_ua_id":"", + "reporting_enabled":"false" + }, + "auth.anonymous":{ + "enabled":"true", + "org_name":"Main Org.", + "org_role":"Viewer" + }, + "auth.basic":{ + "enabled":"false" + }, + "auth.github":{ + "allow_sign_up":"false", + "allowed_domains":"", + "allowed_organizations":"", + "api_url":"https://api.github.com/user", + "auth_url":"https://github.com/login/oauth/authorize", + "client_id":"some_id", + "client_secret":"************", + "enabled":"false", + "scopes":"user:email", + "team_ids":"", + "token_url":"https://github.com/login/oauth/access_token" + }, + "auth.google":{ + "allow_sign_up":"false","allowed_domains":"", + "api_url":"https://www.googleapis.com/oauth2/v1/userinfo", + "auth_url":"https://accounts.google.com/o/oauth2/auth", + "client_id":"some_client_id", + "client_secret":"************", + "enabled":"false", + "scopes":"https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", + "token_url":"https://accounts.google.com/o/oauth2/token" + }, + "auth.ldap":{ + "config_file":"/etc/grafana/ldap.toml", + "enabled":"false" + }, + "auth.proxy":{ + "auto_sign_up":"true", + "enabled":"false", + "header_name":"X-WEBAUTH-USER", + "header_property":"username" + }, + "dashboards.json":{ + "enabled":"false", + "path":"/var/lib/grafana/dashboards" + }, + "database":{ + "host":"127.0.0.1:0000", + "name":"grafana", + "password":"************", + "path":"grafana.db", + "ssl_mode":"disable", + "type":"sqlite3", + "user":"root" + }, + "emails":{ + "templates_pattern":"emails/*.html", + "welcome_email_on_sign_up":"false" + }, + "event_publisher":{ + "enabled":"false", + "exchange":"grafana_events", + "rabbitmq_url":"amqp://localhost/" + }, + "log":{ + "buffer_len":"10000", + "level":"Info", + "mode":"file" + }, + "log.console":{ + "level":"" + }, + "log.file":{ + "daily_rotate":"true", + "file_name":"", + "level":"", + "log_rotate":"true", + "max_days":"7", + "max_lines":"1000000", + "max_lines_shift":"28", + "max_size_shift":"" + }, + "paths":{ + "data":"/tsdb/grafana", + "logs":"/logs/apps/grafana"}, + "security":{ + "admin_password":"************", + "admin_user":"admin", + "cookie_remember_name":"grafana_remember", + "cookie_username":"grafana_user", + "disable_gravatar":"false", + "login_remember_days":"7", + "secret_key":"************" + }, + "server":{ + "cert_file":"", + "cert_key":"", + "domain":"mygraf.com", + "enable_gzip":"false", + "enforce_domain":"false", + "http_addr":"127.0.0.1", + "http_port":"0000", + "protocol":"http", + "root_url":"%(protocol)s://%(domain)s:%(http_port)s/", + "router_logging":"true", + "static_root_path":"public" + }, + "session":{ + "cookie_name":"grafana_sess", + "cookie_secure":"false", + "gc_interval_time":"", + "provider":"file", + "provider_config":"sessions", + "session_life_time":"86400" + }, + "smtp":{ + "cert_file":"", + "enabled":"false", + "from_address":"admin@grafana.localhost", + "host":"localhost:25", + "key_file":"", + "password":"************", + "skip_verify":"false", + "user":""}, + "users":{ + "allow_org_create":"true", + "allow_sign_up":"false", + "auto_assign_org":"true", + "auto_assign_org_role":"Viewer" + } + } + ### Global Users `POST /api/admin/users` +Create new user + +**Example Request**: + + POST /api/admin/users HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + + { + "name":"User", + "email":"user@graf.com", + "login":"user", + "password":"userpassword" + } + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"message":"User created"} + ### Password for User `PUT /api/admin/users/:id/password` +Change password for specific user + +**Example Request**: + + PUT /api/admin/users/2/password HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {"password":"userpassword"} + ### Permissions `PUT /api/admin/users/:id/permissions` +**Example Request**: + + PUT /api/admin/users/2/permissions HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {message: "User permissions updated"} + ### Delete global User `DELETE /api/admin/users/:id` + +**Example Request**: + + DELETE /api/admin/users/2 HTTP/1.1 + Accept: application/json + Content-Type: application/json + Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk + +**Example Response**: + + HTTP/1.1 200 + Content-Type: application/json + + {message: "User deleted"} diff --git a/docs/sources/reference/keyboard_shortcuts.md b/docs/sources/reference/keyboard_shortcuts.md new file mode 100644 index 00000000000..b6e199e4e70 --- /dev/null +++ b/docs/sources/reference/keyboard_shortcuts.md @@ -0,0 +1,32 @@ +page_title: Kayboard Shortcuts +page_description: Kayboard Shortcuts for Grafana +page_keywords: grafana, export, import, documentation +--- + +# Keyboard Shortcuts + +No mouse? No problem. Grafana has extensive keyboard shortcuts to allow you to navigate throughout the interface. This comes in especially handy when dealing with dealing with single-purpose machines powering on-wall displays that may not have a mouse available. + +## Dashboard Keyboard Shortcuts + +Press `Shift`+`?` to open the keyboard shortcut dialog from anywhere within the dashboard views. + + + + +|Shortcut|Action| +|---|---| +|`Esc`|Exit fullscreen edit/view mode, close search or any editor view| +|`CTRL`+`F`|Open dashboard search view (also contains import/playlist controls)| +|`CTRL`+`S`|Save dashboard| +|`CTRL`+`H`|Hide row controls| +|`CTRL`+`Z`|Zoom out| +|`CTRL`+`R`|Refresh (Fetches new data and rerenders panels)| +|`CTRL`+`O`|Enable/Disable shared graph crosshair| + + + +**Note**: Grafana keyboard shortcuts are the same across operating system. For example, `CTRL`+`F` will open the dashboard picker on a Mac as well, **not** `⌘`+`F` + + +Have a suggestion for a new keyboard shortcut? Let us know. \ No newline at end of file diff --git a/docs/sources/reference/playlist.md b/docs/sources/reference/playlist.md index 29c7717939c..b87ebc25be0 100644 --- a/docs/sources/reference/playlist.md +++ b/docs/sources/reference/playlist.md @@ -4,15 +4,17 @@ page_description: Playlist guide for Grafana page_keywords: grafana, playlist, documentation --- -## About the Playlist +# Playlist -The Playlist is a special type of Dashboard that rotates through a particular list of two or more Dashboards. They can be a great way to build situational awareness or just show off your metrics to your team or visitors. Since Dashboards in Grafana automatically scale to any resolution they're perfect for big screens! +The Playlist is a special type of Dashboard that rotates through a list of Dashboards. A Playlist can be a great way to build situational awareness, or just show off your metrics to your team or visitors. + +Since Grafana automatically scales Dashboards to any resolution they're perfect for big screens! ## Configuring the Playlist The Playlist can be accessed from the main Dashboard picker. Click the 'Playlist' button at the bottom of the picker to access the Playlist functionality. -![](/img/v2/dashboard_search.jpg) + Since the Playlist is basically a list of Dashboards, ensure that all the Dashboards you want to appear in your Playlist are added here. You can search Dashboards by name (or use a regular expression). @@ -24,9 +26,9 @@ You can configure a time interval for Grafana to wait on a particular Dashboard ## Starting and controlling the Playlist -To start the Playlist, click the green "Start" button +To start the Playlist, click the green "Start" button -Playlists can also be manually controlled utilizing the Playlist controls at the top of screen when in Playlist mode. +Playlists can also be manually controlled utilizing the Playlist controls at the top of screen when in Playlist mode. Click the stop button to stop the Playlist, and exit to the current Dashboard. Click the next button to advance to the next Dashboard in the Playlist. diff --git a/docs/sources/reference/search.md b/docs/sources/reference/search.md index 159fe4056e8..3c4555b843e 100644 --- a/docs/sources/reference/search.md +++ b/docs/sources/reference/search.md @@ -1,34 +1,54 @@ ---- -page_title: Search guide -page_description: Search guide -page_keywords: grafana, time range, guide, documentation +page_title: Dashboard Search +page_description: Dashboard Search in Grafana +page_keywords: grafana, search, guide, documentation --- -# Search Guide +# Dashboard Search -To search and load dashboards click the open folder icon in the header or use the shortcut CTRL+F. +Dashboards can be searched by the dashboard name, filtered by one (or many) tags or filtered by starred status. The dashboard search is accessed through the dashboard picker, available in the dashboard top nav area. -## Tags -![](/img/v1/dashboards_tags2_search.png) + -Click on any dashboard or use the down arrow key to navigate the search result and hit enter to open the selected dashboard. +1. `Dashboard Picker`: The Dashboard Picker is your primary navigation tool to move between dashboards. It is present on all dashboards, and open the Dashboard Search. The dashboard picker also doubles as the title of the current dashboard. +2. `Search Bar`: The search bar allows you to enter any string and search both database and file based dashbaords in real-time. +3. `Starred`: The starred link allows you to filter the list to display only starred dashboards. +4. `Tags`: The tags filter allows you to filter the list by dashboard tags. -If you have a lot of dashboards use tags to organize them. You can add tags in the dashboards settings modal. +When using only a keyboard, you can use your keyboard arrow keys to navigate the results, hit enter to open the selected dashboard. -![](/img/v1/dashboards_tags1.png) +## Find by dashboard name -After you have added tags you can now view all available tags in the search popup. Click the tags link to the right in the search box, or just hit tab and then enter. You should now see a list of all tags. You can filter the tag list by continue writing in the search box, for example: "tags!:mongodb". + -![](/img/v1/dashboards_tags3_search.png) +To search and load dashboards click the open folder icon in the header or use the shortcut `CTRL`+`F`. Begin typing any part of the desired dashboard names. Search will return results for for any partial string match in real-time, as you type. -Click on any tag in the tag list to show dashboards with that tag (or just down arrow key to select a tag then enter key). +Dashboard search is: +- Real-time +- *Not* case senstitive +- Functional across stored *and* file based dashboards. -## Keyboard navigation - -While the search input has focus you can use your keyboard arrow keys to navigate the results, hit enter to open the selected dashboard. +## Filter by Tag(s) +Tags are a great way to organize your dashboards, especially as the number of dashbaords grow. Tags can be added and managed in the dashboard `Settings`. + +To filter the dashboard list by tag, click on any tag appearing in the right column. The list may be further filtered by cliking on additional tags: + + + +Alternately, to see a list of all available tags, click the tags link in the search bar. All tags will be shown, and when a tag is selected, the dashboard search will be instantly filtered: + + + +When using only a keybaord: `tab` to focus on the *tags* link, `▼` down arrow key to find a tag and select with the `Enter` key. + +**Note**: When multiple tags are selected, Grafana will show dashboards that include **all**. +## Filter by Starred +Starring is a great way to organize and find commonly used dashboards. To show only starred dashboards in the list, click the *starred* link in the search bar: + +When using only a keybaord: `tab` to focus on the *stars* link, `▼` down arrow key to find a tag and select with the `Enter` key. \ No newline at end of file diff --git a/docs/sources/reference/sharing.md b/docs/sources/reference/sharing.md index 16ddcd8f01b..e3a6ce3f2f5 100644 --- a/docs/sources/reference/sharing.md +++ b/docs/sources/reference/sharing.md @@ -30,7 +30,8 @@ after a certain time period. ## Share Panel Click a panel title to open the panel menu, then click share in the panel menu to open the Share Panel dialog. Here you have access to a link that will take you to exactly this panel with the current time range and selected template variables. -You also get a link to service side rendered PNG of the panel. Useful if you want to shara image of the panel. +You also get a link to service side rendered PNG of the panel. Useful if you want to share an image of the panel. +Please note that for OSX and Windows, you will need to ensure that a `phantomjs` binary is available under `vendor/phantomjs/phantomjs`. For Linux, a `phantomjs` binary is included - however, you should ensure that any requisite libraries (e.g. libfontconfig) are available. ### Embed Panel You can embed a panel using an iframe on another web site. This tab will show you the html that you need to use. @@ -42,4 +43,4 @@ Example: ``` Below there should be an interactive Grafana graph embedded in an iframe: - + diff --git a/docs/sources/reference/singlestat.md b/docs/sources/reference/singlestat.md index 4e5a38544c5..7a7c38da289 100644 --- a/docs/sources/reference/singlestat.md +++ b/docs/sources/reference/singlestat.md @@ -8,42 +8,41 @@ page_keywords: grafana, singlestat, panel, documentation ![](/img/v1/singlestat_panel2.png) -The singlestat panel allows you to show the one main summery stat of a single series (like max, min, avg, sum). It also -provides thresholds to color that singlestat metric or the panel background. +The Singlestat Panel allows you to show the one main summary stat of a SINGLE series. It reduces the series into a single number (by looking at the max, min, average, or sum of values in the series). Singlestat also provides thresholds to color the stat or the Panel background. It can also translate the single number into a text value, and show a sparkline summary of the series. -### Big Value Configuration +### Singlestat Panel Configuration -The big value configuration allows you to both customize the look of your singlestat metric, as well as add additional labels to contexualize the metric. +The singlestat panel has a normal query editor to allow you define your exact metric queries like many other Panels. Through the Options tab, you can access the Singlestat-specific functionality. -1. `Big Value`: Big Value refers to the collection of values displayed in the singlestat panel. -2. `Prefixes`: The Prefix fields let you define a custom label and font-size (as a %) to appear *before* the singlestat metric. -3. `Values`: The Value fields let you set the (min, max, average, current, total) and font-size (as a %) of the singlestat metric. -4. `Potsfixes`: The Postfix fields let you define a custom label and font-size (as a %) to appear *after* the singlestat metric. -5. `Units`: Units are appended to the the singlestat metric within the panel, and will respect the color and threshold settings for the Value. -6. `Decimals`: The Decimal field allows you to override automatic decimal precision, inceasing the digits displayed for your singlestat metric. +1. `Big Value`: Big Value refers to how we display the main stat for the Singlestat Panel. This is always a single value that is displayed in the Panel in between two strings, `Prefix` and `Suffix`. The single number is calculated by choosing a function (min,max,average,current,total) of your metric query. This functions reduces your query into a single numeric value. +2. `Font Size`: You can use this section +3. `Values`: The Value fields let you set the function (min, max, average, current, total) that your entire query is reduced into a single value with. You can also set the font size of theand font-size (as a %) of the metric query that the Panel is configured with. This reduces the entire query into a single summary value that is displayed. +4. `Postfixes`: The Postfix fields let you define a custom label and font-size (as a %) to appear *after* the value +5. `Units`: Units are appended to the the Singlestat within the panel, and will respect the color and threshold settings for the value. +6. `Decimals`: The Decimal field allows you to override the automatic decimal precision, and set it explicitely. ### Coloring -The coloring options of the singlestat config allow you to dynamically change the colors based on the displayed data. +The coloring options of the Singlestat Panel config allow you to dynamically change the colors based on the Singlestat value. -1. `Background`: The Background checkbox applies the configured thresholds and colors to the entirity of the singlestat panel background. -2. `Value`: The Value checkbox applies the configured thresholds and colors to the value within the singlestat panel. -3. `Thresholds`: Thresholds allow you to change the background and value colors dyanmically within the panel. The threshold field accepts **3 comma-separated** values, corresponding to the three colors directly to the right. -4. `Colors`: The color picker allows you to select a color and opacity +1. `Background`: This checkbox applies the configured thresholds and colors to the entirity of the Singlestat Panel background. +2. `Value`: This checkbox applies the configured thresholds and colors to the summary stat. +3. `Thresholds`: Change the background and value colors dyanmically within the panel, depending on the Singlestat value. The threshold field accepts **3 comma-separated** values, corresponding to the three colors directly to the right. +4. `Colors`: Select a color and opacity 5. `Invert order`: This link toggles the threshold color order.
For example: Green, Orange, Red () will become Red, Orange, Green (). ### Spark Lines -Spark lines are a great way of seeing the historical data associated with a single stat value, providing valuable context at a glance. Spark lines act differently than traditional graph panels and do not include x or y axis, coordinates, a legend, or ability to interact with the graph. +Sparklines are a great way of seeing the historical data related to the summary stat, providing valuable context at a glance. Sparklines act differently than traditional Graph Panels and do not include x or y axis, coordinates, a legend, or ability to interact with the graph. -1. `Show`: The show checkbox will toggle whether the spark line is shown in the panel. When unselected, only the value will appear. -2. `Background`: Check if you want the sparklines to take up the full panel width or uncheck if they should only be at the bottom. +1. `Show`: The show checkbox will toggle whether the spark line is shown in the Panel. When unselected, only the Singlestat value will appear. +2. `Background`: Check if you want the sparklines to take up the full panel width, or uncheck if they should be below the main Singlestat value. 3. `Line Color`: This color selection applies to the color of the sparkline itself. 4. `Fill Color`: This color selection applies to the area below the sparkline. @@ -51,7 +50,7 @@ Spark lines are a great way of seeing the historical data associated with a sing ### Value to text mapping -Value to text mapping allows you to translate values into explcit text. The text will respect all styling, thresholds and customization defined for the value. +Value to text mapping allows you to translate the value of the summary stat into explicit text. The text will respect all styling, thresholds and customization defined for the value. This can be useful to translate the number of the main Singlestat value into a context-specific human-readable word or message. diff --git a/docs/sources/reference/templating.md b/docs/sources/reference/templating.md index 397aaddec91..239cb33c378 100644 --- a/docs/sources/reference/templating.md +++ b/docs/sources/reference/templating.md @@ -1,47 +1,83 @@ ---- -page_title: Templated dashboards -page_description: Templated dashboards +page_title: Dashboard Templating +page_description: Dashboard Templating page_keywords: grafana, templating, variables, guide, documentation --- -# Templated Dashboards +# Dashboard Templating ![](/img/v2/templating_var_list.png) ## Overview -Templating allows you to create dashboard variables that can be used in your metric queries, series -names and panel titles. Use this feature to create generic dashboards that can quickly be -changed to show graphs for different servers or metrics. -You find this feature in the dashboard cog dropdown menu. +Dashboard Templating allows you to make your Dashboards more interactive and dynamic. + +They’re one of the most powerful and most used features of Grafana, and they’ve recently gotten even more attention in Grafana 2.0 and Grafana 2.1. + +You can create Dashboard Template variables that can be used practically anywhere in a Dashboard: data queries on individual Panels (within the Query Editor), the names in your legends, or titles in Panels and Rows. + +You can configure Dashboard Templating by clicking the dropdown cog on the top of the Dashboard while viewing it. + ## Variable types -There are three different types of template variables. They can all be used in the -same way but they differ in how the list variables values is created. + +There are three different types of Template variables: query, custom, and interval. + +They can all be used to create dynamic variables that you can use throughout the Dashboard, but they differ in how they get the data for their values. + ### Query -This is the most common type of variable. It allows you to create a variable -with values fetched directly from a data source via a metric exploration query. -For example a query like `prod.servers.*` will fill the variable with all possible -values that exists in the wildcard position (Graphite example). + > Note: The Query type is Data Source specific. Please consult the appropriate documentation for your particular Data Source. -You can also create nested variables that use other variables in their definition. For example -`apps.$app.servers.*` uses the variable `$app` in its query definition. +Query is the most common type of Template variable. Use the `Query` template type to generate a dynamic list of variables, simply by allowing Grafana to explore your Data Source metric namespace when the Dashboard loads. + +For example a query like `prod.servers.*` will fill the variable with all possible values that exists in that wildcard position (in the case of the Graphite Data Source). + +You can even create nested variables that use other variables in their definition. For example `apps.$app.servers.*` uses the variable $app in its own query definition. + +You can utilize the special ** All ** value to allow the Dashboard user to query for every single Query variable returned. Grafana will automatically translate ** All ** into the appropriate format for your Data Source. + +#### Multi-select +As of Grafana 2.1, it is now possible to select a subset of Query Template variables (previously it was possible to select an individual value or 'All', not multiple values that were less than All). This is accomplished via the Multi-Select option. If enabled, the Dashboard user will be able to enable and disable individual variables. + +The Multi-Select functionality is taken a step further with the introduction of Multi-Select Tagging. This functionality allows you to group individual Template variables together under a Tag or Group name. + +For example, if you were using Templating to list all 20 of your applications, you could use Multi-Select Tagging to group your applications by function or region or criticality, etc. + + > Note: Multi-Select Tagging functionality is currently experimental but is part of Grafana 2.1. To enable this feature click the enable icon when editing Template options for a particular variable. + + + +Grafana gets the list of tags and the list of values in each tag by performing two queries on your metric namespace. + +The Tags query returns a list of Tags. + +The Tag values query returns the values for a given Tag. + +Note: a proof of concept shim that translates the metric query into a SQL call is provided. This allows you to maintain your tag:value mapping independently of your Data Source. + +Once configured, Multi-Select Tagging provides a convenient way to group and your template variables, and slice your data in the exact way you want. The Tags can be seen on the right side of the template pull-down. + +![](/img/v2/multi-select.gif) -> For examples of template queries appropriate for your data source checkout the documentation -> page for your data source. ### Interval -This variable type is useful for time ranges like `1m`,`1h`, `1d`. There is also an auto -option that will change depending on the current time range, you can specify how many times -the current time range should be divided to calculate the current `auto` range. + +Use the `Interval` type to create Template variables aroundr time ranges (eg. `1m`,`1h`, `1d`). There is also a special `auto` option that will change depending on the current time range, you can specify how many times the current time range should be divided to calculate the current `auto` range. ![](/img/v2/templated_variable_parameter.png) ### Custom -This variable type allow you to manually specify all the different values as a comma seperated -string. + +Use the `Custom` type to manually create Template variables around explicit values that are hard-coded into the Dashboard, and not dependent on any Data Source. You can specify multiple Custom Template values by separating them with a comma. + +## Utilizing Template Variables with Repeating Panels and Repeating Rows + +Template Variables can be very useful to dynamically change what you're visualizing on a given panel. Sometimes, you might want to create entire new Panels (or Rows) based on what Template Variables have been selected. This is now possible in Grafana 2.1. + +Once you've got your Template variables (of any type) configured the way you'd like, check out the Repeating Panels and Repeating Row documentatione ## Screencast - Templated Graphite Queries + diff --git a/docs/sources/reference/timerange.md b/docs/sources/reference/timerange.md index d2f05f5a9d6..c253e0cd64c 100644 --- a/docs/sources/reference/timerange.md +++ b/docs/sources/reference/timerange.md @@ -4,13 +4,13 @@ page_description: Time range user guide page_keywords: grafana, time range, guide, documentation --- +# Time Range Controls + Grafana provides numerous ways to manage the time ranges of the data being visualized, both at the Dashboard-level and the Panel-level. -# Dashboard time picker + -![](/img/v1/time_range_controls.png) - -In the top right, you have the master Dashboard time picker (it's inbetween the 'Zoom out' and the 'Refresh' links). +In the top right, you have the master Dashboard time picker (it's in between the 'Zoom out' and the 'Refresh' links). From this dropdown you can: @@ -22,12 +22,26 @@ All of this applies to all Panels in the Dashboard (except those with Panel Time ## Customize relative time and auto auto-refresh options -It's possible to customize the options displayed for relative time and the auto-refresh options. +It's possible to customize the options displayed for relative time and the auto-refresh options. -From Dashboard settings, click the Timepicker tab. From here you can specify the relative and auto refresh intervals. The Timepicker tab settings are saved on a per Dashboard basis. Entries are comma separated and accept a number followed by one of the following units: s (seconds), m (minutes), h (hours), d (days), w (weeks), M (months), y (years). +From Dashboard settings, click the Timepicker tab. From here you can specify the relative and auto refresh intervals. The Timepicker tab settings are saved on a per Dashboard basis. Entries are comma separated and accept a number followed by one of the following units: `s (seconds)`, `m (minutes)`, `h (hours)`, `d (days)`, `w (weeks)`, `M (months)`, `y (years)`. -![](/img/v1/timepicker_editor.png) + -## Panel time override -In Grafana 2.0, it's now possible for individual Panels to override the Dashboard time picker. Please check out the [whats new in 2.0 guide](../guides/whats-new-in-v2/) for further information +## Panel time overrides & timeshift + +In Grafana v2.x you can now override the relative time range for individual panels, causing them to be different than what is selected in the Dashboard time picker in the upper right. You can also add a time shift to individual panels. This allows you to show metrics from different time periods or days at the same time. + + + +You control these overrides in panel editor mode and the new tab `Time Range`. + + + +When you zoom or change the Dashboard time to a custom absolute time range, all panel overrides will be disabled. The panel relative time override is only active when the dashboard time is also relative. The panel timeshift override however is always active, even when the dashboard time is absolute. + +The `Hide time override info` option allows you to hide the the override info text that is by default shown in the +upper right of a panel when overridden time range options. + +Currently you can only override the dashboard time with relative time ranges, not absolute time ranges. diff --git a/docs/sources/tutorials/hubot_howto.md b/docs/sources/tutorials/hubot_howto.md new file mode 100644 index 00000000000..b2f53d0bece --- /dev/null +++ b/docs/sources/tutorials/hubot_howto.md @@ -0,0 +1,126 @@ +--- +page_title: How To integrate Hubot and Grafana +page_description: Hubot Grafana install guide +page_keywords: grafana, tutorials, hubot, slack, hipchat, setup, install, config +author: Torkel Ödegaard +--- + +# How to integrate Hubot with Grafana + +Grafana 2.0 shipped with a great feature that enables it to render any graph or panel to a PNG image. +No matter what data source you are using, the PNG image of the Graph will look the same +as it does in your browser. + +This guide will show you how to install and configure the [Hubot-Grafana](https://github.com/stephenyeargin/hubot-grafana) +plugin. This plugin allows you to tell hubot to render any dashboard or graph right from a channel in +Slack, Hipchat or Basecamp. The bot will respond with an image of the graph and a link that will +take you to the graph. + +> *Amazon S3 Required*: The hubot-grafana script will upload the rendered graphs to Amazon S3. This +> is so Hipchat and Slack can show them reliably (they require the image to be publicly available). + +
+ +
+ +## What is Hubot? +[Hubot](https://hubot.github.com/) is an universal and extensible chat bot that can be used with many chat +services and has a huge library of third party plugins that allow you to automate anything from your +chat rooms. + +## Install Hubot +Hubot is very easy to install and host. If you do not already have a bot up and running please +read the official [Getting Started With Hubot](https://hubot.github.com/docs/) guide. + +## Install Hubot-Grafana script + +In your Hubot project repo install the Grafana plugin using `npm`: + + npm install hubot-grafana --save + +Edit the file external-scripts.json, and add hubot-grafana to the list of plugins. + +```json +[ +"hubot-pugme", +"hubot-shipit", +"hubot-grafana" +] +``` + +## Configure + +The `hubot-grafana` plugin requires a number of environment variables to be set in order to work properly. + + export HUBOT_GRAFANA_HOST=http://play.grafana.org + export HUBOT_GRAFANA_API_KEY=abcd01234deadbeef01234 + export HUBOT_GRAFANA_S3_BUCKET=mybucket + export HUBOT_GRAFANA_S3_ACCESS_KEY_ID=ABCDEF123456XYZ + export HUBOT_GRAFANA_S3_SECRET_ACCESS_KEY=aBcD01234dEaDbEef01234 + export HUBOT_GRAFANA_S3_PREFIX=graphs + export HUBOT_GRAFANA_S3_REGION=us-standard + +### Grafana server side rendering +The hubot plugin will take advantage of the Grafana server side rendering feature that can +render any panel on the server using phantomjs. Grafana ships with a phantomjs binary (linux only). + +To verify that this freature works try the `Direct link to rendered image` link in the panel share dialog. +If you do not get an image when opening this link verify that the required font packages are installed for phantomjs to work. + +### Grafana API Key + +You need to set the environment variable `HUBOT_GRAFANA_API_KEY` to a Grafana API Key. +You can add these from the API Keys page wich you find in the Organization dropdown. + +### Amazon S3 +The `S3` options are optional but for the images to work properly in services like Slack and Hipchat they need +to publicly available. By specifying the `S3` options the hubot-grafana script will publish the rendered +panel to `S3` and it will use that URL when it posts to Slack or Hipchat. + +## Hubot commands + +- `hubot graf list` + - Lists the available dashboards +- `hubot graf db graphite-carbon-metrics` + - Graph all panels in the dashboard +- `hubot graf db graphite-carbon-metrics:3` + - Graph only panel with id 3 of a particular dashboard +- `hubot graf db graphite-carbon-metrics:cpu` + - Graph only the panels containing "cpu" (case insensitive) in the title +- `hubot graf db graphite-carbon-metrics now-12hr` + - Get a dashboard with a window of 12 hours ago to now +- `hubot graf db graphite-carbon-metrics now-24hr now-12hr` + - Get a dashboard with a window of 24 hours ago to 12 hours ago +- `hubot graf db graphite-carbon-metrics:3 now-8d now-1d` + - Get only the third panel of a particular dashboard with a window of 8 days ago to yesterday +- `hubot graf db graphite-carbon-metrics host=carbon-a` + - Get a templated dashboard with the `$host` parameter set to `carbon-a` + +## Aliases +Some of the hubot commands above can lengthy and you might have to remember the dashboard slug (url id). +If you have a few favorite graphs you want to be able check up on often (let's say from your mobile) you +can create hubot command aliases with the hubot script `hubot-alias`. + +Install it: + + npm i --save hubot-alias + +Now add `hubot-alias` to the list of plugins in `external-scripts.json` and restart hubot. + +Now you can add an alias like this: + +- `hubot alias graf-lb=graf db loadbalancers:2 now-20m` + +
+ Using the alias:
+ +
+ +## Summary + +Grafana is going to ship with integrated Slack and Hiptchat features some day but you do +not have to wait for that. Grafana 2 shipped with a very clever server side rendering feature +that can render any panel to a png using phantomjs. The hubot plugin for Grafana is something +you can install and use today! + + diff --git a/docs/sources/tutorials/index.md b/docs/sources/tutorials/index.md new file mode 100644 index 00000000000..6c800457072 --- /dev/null +++ b/docs/sources/tutorials/index.md @@ -0,0 +1,19 @@ +--- +page_title: Grafana Tutorials +page_description: Tutorials +page_keywords: grafana, tutorials +--- + +# Tutorials + +This section of the docs contains a series for tutorials and stack setup guides. + +## Articles + +- [How to integrate Hubot with Grafana](hubot_howto.md) + +## External links + +- [Monitoring Urbancode deployments with Docker, Graphite, Grafana, collectd and chef!](http://cloud.boriskuschel.com/2015/08/monitoring-urbancode-deploments-with.html) +- [Scripting Grafana dashboards](http://anatolijd.blogspot.se/2014/07/scripting-grafana-dashboards.html) + diff --git a/docs/sources/tutorials/stack_guide_graphite.md b/docs/sources/tutorials/stack_guide_graphite.md new file mode 100644 index 00000000000..a9025cf9a71 --- /dev/null +++ b/docs/sources/tutorials/stack_guide_graphite.md @@ -0,0 +1,263 @@ +--- +page_title: Graphite + Grafana + StatsD - Stack Setup Guide +page_description: Installation and configuration guide & how to for Grafana, Graphite & StatsD +page_keywords: grafana, tutorials, graphite, statsd, setup, configuration, howto, installation +author: Torkel Ödegaard +--- + +# Stack Setup & Config Guide: Graphite + Grafana + StatsD + +This lengthy article will guide you through installation, configuration and getting started with the amazing metric +stack that is composed of Graphite, Grafana and StatsD. + +Graphite is still king when it comes to time series databases due to its simple data model, ingestion +with integrated aggregation & rollups, amazing query features and speed. No other time series +database has yet to match Graphite's query flexibility and analytics potential. + +Graphite has a reputation for being tricky to install and scale. This guide aims to show +that is not really the case, or, at least, that it is a lot better than you expect. + +> This guides does not only aim to be only be an install guide but to also teach you +> of the mechanics of metric collection, aggregation and querying. How Graphite +> stores and aggregates data is very important to understand in order to not +> get mislead by graphs. + +## Installation - Ubuntu + +To begin with we are going to install the 3 main components that define our metric stack. Later in the guide we will +install StatsD, but that is optional. + +- Carbon is the graphite ingestion deamon responsible for +receiving metrics and storing them. +- Graphite-api is light weight version of graphite-web with only the HTTP api and is +responsible for executing metric queries. +- Grafana as the frontend to visualize metrics and the tool to help you build metric +queries that will make the most out of your collected metrics. + +### Carbon + +Graphite & Carbon are written in python, so we will start by installing python packages. + +``` +apt-get install \ + git \ + build-essential \ + libffi-dev libcairo2-dev \ + python-django \ + python-django-tagging \ + python-simplejson \ + python-memcache \ + python-ldap \ + python-cairo \ + python-twisted \ + python-pysqlite2 \ + python-support \ + python-dev \ + python-pip +``` + +Next we will clone carbon and whisper and install these components. Whisper is just a lib used +by carbon to write metrics to disk. + + cd /usr/local/src + git clone https://github.com/graphite-project/carbon.git + git clone https://github.com/graphite-project/whisper.git + + cd whisper && python setup.py install && cd .. + cd carbon && python setup.py install && cd .. + +### Configure carbon.conf + +Copy example carbon config: +``` +cp /opt/graphite/conf/carbon.conf.example /opt/graphite/conf/carbon.conf +``` + +Edit the config file `/opt/graphite/conf/carbon.conf`, find line `ENABLE_UPD_LISTENER` and +change this setting to `True`. + +### Configure storage-schemas.conf + +Create a new file at `/opt/graphite/conf/storage-schemas.conf` with the following content: + +``` +[carbon] +pattern = ^carbon\..* +retentions = 1m:30d,10m:1y,1h:5y + +[default] +pattern = .* +retentions = 10s:1d,1m:7d,10m:1y +``` + +This config specifies the resolution of metrics and the retention periods. For example for all metrics begining with the word `carbon` receive metrics every minute and store for 30 days, then +roll them up into 10 minute buckets and store those for 1 year, then roll those up into 1 hour buckets and store those for 5 years. For all other metrics +the default rule will be applied with other retention periods. + +This configuration is very important, as the first retention period must match the rate of which you send metrics. The default rule has 10 seconds +as its first resolution so when configuring StatsD we should configure it to send metrics every 10 seconds. + +> If you send values more frequently than the highest resolution, for example if you send data every second but +> the storage schema rules defines the highest resolution to be 10 seconds, then the values you send will just +> overwrite each other and the last value sent during every 10 second period will be saved. StatsD can work around this +> problem. + +### Configure storage-aggregation.conf + +Copy the default config and open it in an editor. +``` +cp /opt/graphite/conf/storage-aggregation.conf.example /opt/graphite/conf/storage-aggregation.conf +``` + +Example config: +``` +[min] +pattern = \.min$ +xFilesFactor = 0.1 +aggregationMethod = min + +[max] +pattern = \.max$ +xFilesFactor = 0.1 +aggregationMethod = max + +[sum] +pattern = \.count$ +xFilesFactor = 0 +aggregationMethod = sum + +[default_average] +pattern = .* +xFilesFactor = 0.5 +aggregationMethod = average +``` + +You do not really need to change the default config, but is very important to +understand what the config controls and what implications that it has. Graphite +does rollups as part of the metric ingestion according to the rules defined in +`storage-schemas.conf`. For example, given storage schema rule `10s:1d,1m:7d`, +when aggregating 6 values (each representing 10 seconds) into a 1min bucket graphite +will use an `aggregationMethod` like for example `average`. What method to use +will be determined by the rules specified in `storage-aggregation.conf`. + +The default rules all look at the metric path ending. Does it end with `.count` then use `sum` when doing rollups, does it end with `max` then use `max` function, and if it does not +end with max, min or count then use average. This means that naming metrics is very important! But don't worry if you use StatsD it will send the correct names to graphite. + +### Start carbon +Lets install supervisord and let it start carbon. + +`apt-get install supervisor` + +Create a new file in `/etc/supervisor/conf.d/carbon.conf` with the following: + +``` +[program:carbon-cache] +command = /opt/graphite/bin/carbon-cache.py --debug start +stdout_logfile = /var/log/supervisor/%(program_name)s.log +stderr_logfile = /var/log/supervisor/%(program_name)s.log +autorestart = true +stopsignal = QUIT +``` + +``` +supervisorctl reload +``` + +### Graphite-api + +Graphite api is a light weight version of graphite-web with only the api component (no web ui). It is dead simple +to install. + +``` +pip install gunicorn graphite-api +``` + +You should now have a graphite-api daemon running with an open HTTP api port of 8888. + +### Configuring Graphite-api + +Create a file `/etc/graphite-api.yaml` with an editor and set it's content to: + +``` +search_index: /opt/graphite/storage/index +finders: + - graphite_api.finders.whisper.WhisperFinder +functions: + - graphite_api.functions.SeriesFunctions + - graphite_api.functions.PieFunctions +whisper: + directories: + - /opt/graphite/storage/whisper +time_zone: UTC +``` + +Lets create a supervisor file for graphite-api at `/etc/supervisor/graphite-api.conf` + +``` +[program:graphite-api] +command = gunicorn -b 0.0.0.0:8888 -w 2 --log-level info graphite_api.app:app +stdout_logfile = /var/log/supervisor/%(program_name)s.log +stderr_logfile = /var/log/supervisor/%(program_name)s.log +autorestart = true +stopsignal = QUIT +``` + +Reload supervisor + + supervisorctl reload + +A carbon-cache deamon and graphite-api should now be running. Type `supervisorctl status` to verify that they are running. You can +also open `http://your_server_ip:8888/metrics/find?query?*` in your browser. You should see a json snippet. + + +### Install Grafana + + cd /tmp/ + wget https://grafanarel.s3.amazonaws.com/builds/grafana_2.1.1_amd64.deb + sudo dpkg -i grafana_2.1.1_amd64.deb + sudo service grafana-server start + +Grafana should now be running with default config on port 3000. + +## Grafana - first steps + +### Add data source + +Open http://your_server_ip:3000 in your browser and login with the default user and password (`admin/admin`). + +- Click on `Data Sources` on the side menu. +- Click on `Add new` in the top menu +- Specify name `graphite` and check the `Default ` checkbox +- Specify Url `http://localhost:8888` and Access `proxy` +- Click `Add ` button + +### Your first dashboard + +- Click on `Dashboards` +- Click on `Home` button in the top menu, this should open the dashboard search dropdown +- Click on `New` button in the bottom of this dropdown + +### Add a graph + +- Click on the green icon to the left to open the row menu +- Select `Add Panel` > `Graph` from the row menu +- An empty graph panel should appear with title `no title (click here)`. Click on this title and then `Edit` +- This will open the graph in edit mode and take you to the metrics tab. +- There is one query already added (asigned letter A) but it is empty. +- Click on `select metric` to pick the first graphite metric node. A new `select metric` link will appear until you reached a leaf node. +- Try picking the metric paths for `carbon.agents..cpuUsage`, you should now see a line appear in the graph! + +## Writing metrics to Graphite +Graphite has the simples metric write protocol imaginable. Something that has surely contributed to its wide adoption by metric +frameworks and numerous integrations. + + prod.server1.requests.count 10 1398969187 + + + +There are hundreds of tools and instrumentation frameworks that can send metrics using this protocol. + +### Installing StatsD +StatsD is a metrics aggregation daemon that makes it easy for apps on many machines to send measurements like timings and counters and have them aggregated or percentiles calculated. + +### Sending metrics to StatsD diff --git a/emails/README.md b/emails/README.md new file mode 100644 index 00000000000..9702abce7e6 --- /dev/null +++ b/emails/README.md @@ -0,0 +1,9 @@ + +- npm install +- gem install premailer +- grunt (default task will build new inlines email templates) +- grunt watch (will build on source html or css change) + +assembled email templates will be in dist/ and final +inlined templates will be in ../public/emails/ + diff --git a/emails/assets/css/ink.css b/emails/assets/css/ink.css new file mode 100644 index 00000000000..e65aed19194 --- /dev/null +++ b/emails/assets/css/ink.css @@ -0,0 +1,688 @@ +/********************************************** +* Ink v1.0.5 - Copyright 2013 ZURB Inc * +**********************************************/ + +/* Client-specific Styles & Reset */ + +#outlook a { + padding:0; +} + +body{ + width:100% !important; + min-width: 100%; + -webkit-text-size-adjust:100%; + -ms-text-size-adjust:100%; + margin:0; + padding:0; +} + +.ExternalClass { + width:100%; +} + +.ExternalClass, +.ExternalClass p, +.ExternalClass span, +.ExternalClass font, +.ExternalClass td, +.ExternalClass div { + line-height: 100%; +} + +#backgroundTable { + margin:0; + padding:0; + width:100% !important; + line-height: 100% !important; +} + +img { + outline:none; + text-decoration:none; + -ms-interpolation-mode: bicubic; + width: auto; + max-width: 100%; + float: left; + clear: both; + display: block; +} + +center { + width: 100%; + min-width: 580px; +} + +a img { + border: none; +} + +p { + margin: 0 0 0 10px; +} + +table { + border-spacing: 0; + border-collapse: collapse; +} + +td { + word-break: break-word; + -webkit-hyphens: auto; + -moz-hyphens: auto; + hyphens: auto; + border-collapse: collapse !important; +} + +table, tr, td { + padding: 0; + vertical-align: top; + text-align: left; +} + +hr { + color: #d9d9d9; + background-color: #d9d9d9; + height: 1px; + border: none; +} + +/* Responsive Grid */ + +table.body { + height: 100%; + width: 100%; +} + +table.container { + width: 580px; + margin: 0 auto; + text-align: inherit; +} + +table.row { + padding: 0px; + width: 100%; + position: relative; +} + +table.container table.row { + display: block; +} + +td.wrapper { + padding: 10px 20px 0px 0px; + position: relative; +} + +table.columns, +table.column { + margin: 0 auto; +} + +table.columns td, +table.column td { + padding: 0px 0px 10px; +} + +table.columns td.sub-columns, +table.column td.sub-columns, +table.columns td.sub-column, +table.column td.sub-column { + padding-right: 10px; +} + +td.sub-column, td.sub-columns { + min-width: 0px; +} + +table.row td.last, +table.container td.last { + padding-right: 0px; +} + +table.one { width: 30px; } +table.two { width: 80px; } +table.three { width: 130px; } +table.four { width: 180px; } +table.five { width: 230px; } +table.six { width: 280px; } +table.seven { width: 330px; } +table.eight { width: 380px; } +table.nine { width: 430px; } +table.ten { width: 480px; } +table.eleven { width: 530px; } +table.twelve { width: 580px; } + +table.one center { min-width: 30px; } +table.two center { min-width: 80px; } +table.three center { min-width: 130px; } +table.four center { min-width: 180px; } +table.five center { min-width: 230px; } +table.six center { min-width: 280px; } +table.seven center { min-width: 330px; } +table.eight center { min-width: 380px; } +table.nine center { min-width: 430px; } +table.ten center { min-width: 480px; } +table.eleven center { min-width: 530px; } +table.twelve center { min-width: 580px; } + +table.one .panel center { min-width: 10px; } +table.two .panel center { min-width: 60px; } +table.three .panel center { min-width: 110px; } +table.four .panel center { min-width: 160px; } +table.five .panel center { min-width: 210px; } +table.six .panel center { min-width: 260px; } +table.seven .panel center { min-width: 310px; } +table.eight .panel center { min-width: 360px; } +table.nine .panel center { min-width: 410px; } +table.ten .panel center { min-width: 460px; } +table.eleven .panel center { min-width: 510px; } +table.twelve .panel center { min-width: 560px; } + +.body .columns td.one, +.body .column td.one { width: 8.333333%; } +.body .columns td.two, +.body .column td.two { width: 16.666666%; } +.body .columns td.three, +.body .column td.three { width: 25%; } +.body .columns td.four, +.body .column td.four { width: 33.333333%; } +.body .columns td.five, +.body .column td.five { width: 41.666666%; } +.body .columns td.six, +.body .column td.six { width: 50%; } +.body .columns td.seven, +.body .column td.seven { width: 58.333333%; } +.body .columns td.eight, +.body .column td.eight { width: 66.666666%; } +.body .columns td.nine, +.body .column td.nine { width: 75%; } +.body .columns td.ten, +.body .column td.ten { width: 83.333333%; } +.body .columns td.eleven, +.body .column td.eleven { width: 91.666666%; } +.body .columns td.twelve, +.body .column td.twelve { width: 100%; } + +td.offset-by-one { padding-left: 50px; } +td.offset-by-two { padding-left: 100px; } +td.offset-by-three { padding-left: 150px; } +td.offset-by-four { padding-left: 200px; } +td.offset-by-five { padding-left: 250px; } +td.offset-by-six { padding-left: 300px; } +td.offset-by-seven { padding-left: 350px; } +td.offset-by-eight { padding-left: 400px; } +td.offset-by-nine { padding-left: 450px; } +td.offset-by-ten { padding-left: 500px; } +td.offset-by-eleven { padding-left: 550px; } + +td.expander { + visibility: hidden; + width: 0px; + padding: 0 !important; +} + +table.columns .text-pad, +table.column .text-pad { + padding-left: 10px; + padding-right: 10px; +} + +table.columns .left-text-pad, +table.columns .text-pad-left, +table.column .left-text-pad, +table.column .text-pad-left { + padding-left: 10px; +} + +table.columns .right-text-pad, +table.columns .text-pad-right, +table.column .right-text-pad, +table.column .text-pad-right { + padding-right: 10px; +} + +/* Block Grid */ + +.block-grid { + width: 100%; + max-width: 580px; +} + +.block-grid td { + display: inline-block; + padding:10px; +} + +.two-up td { + width:270px; +} + +.three-up td { + width:173px; +} + +.four-up td { + width:125px; +} + +.five-up td { + width:96px; +} + +.six-up td { + width:76px; +} + +.seven-up td { + width:62px; +} + +.eight-up td { + width:52px; +} + +/* Alignment & Visibility Classes */ + +table.center, td.center { + text-align: center; +} + +h1.center, +h2.center, +h3.center, +h4.center, +h5.center, +h6.center { + text-align: center; +} + +span.center { + display: block; + width: 100%; + text-align: center; +} + +img.center { + margin: 0 auto; + float: none; +} + +.show-for-small, +.hide-for-desktop { + display: none; +} + +/* Typography */ + +body, table.body, h1, h2, h3, h4, h5, h6, p, td { + color: #222222; + font-family: "Helvetica", "Arial", sans-serif; + font-weight: normal; + padding:0; + margin: 0; + text-align: left; + line-height: 1.3; +} + +h1, h2, h3, h4, h5, h6 { + word-break: normal; +} + +h1 {font-size: 40px;} +h2 {font-size: 36px;} +h3 {font-size: 32px;} +h4 {font-size: 28px;} +h5 {font-size: 24px;} +h6 {font-size: 20px;} +body, table.body, p, td {font-size: 14px;line-height:19px;} + +p.lead, p.lede, p.leed { + font-size: 18px; + line-height:21px; +} + +p { + margin-bottom: 10px; +} + +small { + font-size: 10px; +} + +a { + color: #2ba6cb; + text-decoration: none; +} + +a:hover { + color: #2795b6 !important; +} + +a:active { + color: #2795b6 !important; +} + +a:visited { + color: #2ba6cb !important; +} + +h1 a, +h2 a, +h3 a, +h4 a, +h5 a, +h6 a { + color: #2ba6cb; +} + +h1 a:active, +h2 a:active, +h3 a:active, +h4 a:active, +h5 a:active, +h6 a:active { + color: #2ba6cb !important; +} + +h1 a:visited, +h2 a:visited, +h3 a:visited, +h4 a:visited, +h5 a:visited, +h6 a:visited { + color: #2ba6cb !important; +} + +/* Panels */ + +.panel { + background: #f2f2f2; + border: 1px solid #d9d9d9; + padding: 10px !important; +} + +.sub-grid table { + width: 100%; +} + +.sub-grid td.sub-columns { + padding-bottom: 0; +} + +/* Buttons */ + +table.button, +table.tiny-button, +table.small-button, +table.medium-button, +table.large-button { + width: 100%; + overflow: hidden; +} + +table.button td, +table.tiny-button td, +table.small-button td, +table.medium-button td, +table.large-button td { + display: block; + width: auto !important; + text-align: center; + background: #2ba6cb; + border: 1px solid #2284a1; + color: #ffffff; + padding: 8px 0; +} + +table.tiny-button td { + padding: 5px 0 4px; +} + +table.small-button td { + padding: 8px 0 7px; +} + +table.medium-button td { + padding: 12px 0 10px; +} + +table.large-button td { + padding: 21px 0 18px; +} + +table.button td a, +table.tiny-button td a, +table.small-button td a, +table.medium-button td a, +table.large-button td a { + font-weight: bold; + text-decoration: none; + font-family: Helvetica, Arial, sans-serif; + color: #ffffff; + font-size: 16px; +} + +table.tiny-button td a { + font-size: 12px; + font-weight: normal; +} + +table.small-button td a { + font-size: 16px; +} + +table.medium-button td a { + font-size: 20px; +} + +table.large-button td a { + font-size: 24px; +} + +table.button:hover td, +table.button:visited td, +table.button:active td { + background: #2795b6 !important; +} + +table.button:hover td a, +table.button:visited td a, +table.button:active td a { + color: #fff !important; +} + +table.button:hover td, +table.tiny-button:hover td, +table.small-button:hover td, +table.medium-button:hover td, +table.large-button:hover td { + background: #2795b6 !important; +} + +table.button:hover td a, +table.button:active td a, +table.button td a:visited, +table.tiny-button:hover td a, +table.tiny-button:active td a, +table.tiny-button td a:visited, +table.small-button:hover td a, +table.small-button:active td a, +table.small-button td a:visited, +table.medium-button:hover td a, +table.medium-button:active td a, +table.medium-button td a:visited, +table.large-button:hover td a, +table.large-button:active td a, +table.large-button td a:visited { + color: #ffffff !important; +} + +table.secondary td { + background: #e9e9e9; + border-color: #d0d0d0; + color: #555; +} + +table.secondary td a { + color: #555; +} + +table.secondary:hover td { + background: #d0d0d0 !important; + color: #555; +} + +table.secondary:hover td a, +table.secondary td a:visited, +table.secondary:active td a { + color: #555 !important; +} + +table.success td { + background: #5da423; + border-color: #457a1a; +} + +table.success:hover td { + background: #457a1a !important; +} + +table.alert td { + background: #c60f13; + border-color: #970b0e; +} + +table.alert:hover td { + background: #970b0e !important; +} + +table.radius td { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +table.round td { + -webkit-border-radius: 500px; + -moz-border-radius: 500px; + border-radius: 500px; +} + +/* Outlook First */ + +body.outlook p { + display: inline !important; +} + +/* Media Queries */ + +@media only screen and (max-width: 600px) { + + table[class="body"] img { + width: auto !important; + height: auto !important; + } + + table[class="body"] center { + min-width: 0 !important; + } + + table[class="body"] .container { + width: 95% !important; + } + + table[class="body"] .row { + width: 100% !important; + display: block !important; + } + + table[class="body"] .wrapper { + display: block !important; + padding-right: 0 !important; + } + + table[class="body"] .columns, + table[class="body"] .column { + table-layout: fixed !important; + float: none !important; + width: 100% !important; + padding-right: 0px !important; + padding-left: 0px !important; + display: block !important; + } + + table[class="body"] .wrapper.first .columns, + table[class="body"] .wrapper.first .column { + display: table !important; + } + + table[class="body"] table.columns td, + table[class="body"] table.column td { + width: 100% !important; + } + + table[class="body"] .columns td.one, + table[class="body"] .column td.one { width: 8.333333% !important; } + table[class="body"] .columns td.two, + table[class="body"] .column td.two { width: 16.666666% !important; } + table[class="body"] .columns td.three, + table[class="body"] .column td.three { width: 25% !important; } + table[class="body"] .columns td.four, + table[class="body"] .column td.four { width: 33.333333% !important; } + table[class="body"] .columns td.five, + table[class="body"] .column td.five { width: 41.666666% !important; } + table[class="body"] .columns td.six, + table[class="body"] .column td.six { width: 50% !important; } + table[class="body"] .columns td.seven, + table[class="body"] .column td.seven { width: 58.333333% !important; } + table[class="body"] .columns td.eight, + table[class="body"] .column td.eight { width: 66.666666% !important; } + table[class="body"] .columns td.nine, + table[class="body"] .column td.nine { width: 75% !important; } + table[class="body"] .columns td.ten, + table[class="body"] .column td.ten { width: 83.333333% !important; } + table[class="body"] .columns td.eleven, + table[class="body"] .column td.eleven { width: 91.666666% !important; } + table[class="body"] .columns td.twelve, + table[class="body"] .column td.twelve { width: 100% !important; } + + table[class="body"] td.offset-by-one, + table[class="body"] td.offset-by-two, + table[class="body"] td.offset-by-three, + table[class="body"] td.offset-by-four, + table[class="body"] td.offset-by-five, + table[class="body"] td.offset-by-six, + table[class="body"] td.offset-by-seven, + table[class="body"] td.offset-by-eight, + table[class="body"] td.offset-by-nine, + table[class="body"] td.offset-by-ten, + table[class="body"] td.offset-by-eleven { + padding-left: 0 !important; + } + + table[class="body"] table.columns td.expander { + width: 1px !important; + } + + table[class="body"] .right-text-pad, + table[class="body"] .text-pad-right { + padding-left: 10px !important; + } + + table[class="body"] .left-text-pad, + table[class="body"] .text-pad-left { + padding-right: 10px !important; + } + + table[class="body"] .hide-for-small, + table[class="body"] .show-for-desktop { + display: none !important; + } + + table[class="body"] .show-for-small, + table[class="body"] .hide-for-desktop { + display: inherit !important; + } +} diff --git a/emails/assets/css/style.css b/emails/assets/css/style.css new file mode 100644 index 00000000000..f138a0c1296 --- /dev/null +++ b/emails/assets/css/style.css @@ -0,0 +1,144 @@ + +body, table.body, h1, h2, h3, h4, h5, h6, p, td { + font-family: 'Open Sans', 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: none; +} + +h1 {font-size: 40px;} +h2 {font-size: 36px;} +h3 { + font-size: 22px; + margin-top: 20px; +} +h4 {font-size: 20px;} +h5 {font-size: 18px;} +h6 {font-size: 16px;} + +.emphasis { + font-weight: 600; +} + +a { + color: #E67612; + text-decoration: none; +} + +a:hover { + color: #ff8f2b !important; +} + +a:active { + color: #F2821E !important; +} + +a:visited { + color: #E67612 !important; +} + +table.facebook td { + background: #3b5998; + border-color: #2d4473; +} + +table.facebook:hover td { + background: #2d4473 !important; +} + +table.twitter td { + background: #00acee; + border-color: #0087bb; +} + +table.twitter:hover td { + background: #0087bb !important; +} + +table.google-plus td { + background-color: #DB4A39; + border-color: #CC0000; +} + +table.google-plus:hover td { + background: #CC0000 !important; +} + +.template-label { + color: #ffffff; + font-weight: bold; + font-size: 11px; +} + +.callout .wrapper { + padding-bottom: 20px; +} + +.callout .panel { + background: #ECF8FF; + border-color: #b9e5ff; +} + +.header { + background: #333; +} + +.footer { + margin-top: 20px; +} + +@media only screen and (max-width: 600px) { + table[class="body"] .right-text-pad { + padding-left: 10px !important; + } + + table[class="body"] .left-text-pad { + padding-right: 10px !important; + } +} + +table.better-button { + margin-top: 10px; + margin-bottom: 20px; +} + +table.columns td.better-button { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + padding-bottom: 0px; +} + +.better-button a { + text-decoration: none; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + + padding: 12px 25px; + border: 1px solid #ff8f2b; + display: inline-block; + color: #FFF; +} + +.better-button:hover a { + color: #FFFFFF !important; + background-color: #F2821E; + border: 1px solid #F2821E; +} + +.better-button:visited a { + color: #FFFFFF !important; +} + +.better-button:active a { + color: #FFFFFF !important; +} + +.verification-code { + background-color: #EEEEEE; + padding: 3px; + margin: 8px; + display: inline-block; + font-weight: bold; + font-size: 20px; +} diff --git a/emails/grunt/aliases.yaml b/emails/grunt/aliases.yaml new file mode 100644 index 00000000000..6a2e47777cd --- /dev/null +++ b/emails/grunt/aliases.yaml @@ -0,0 +1,8 @@ + +default: + - 'clean' + - 'assemble' + - 'replace' + - 'uncss' + - 'processhtml' + - 'premailer' diff --git a/emails/grunt/assemble.js b/emails/grunt/assemble.js new file mode 100644 index 00000000000..a4a6390c4c2 --- /dev/null +++ b/emails/grunt/assemble.js @@ -0,0 +1,16 @@ +module.exports = function() { + 'use strict'; + return { + options: { + layout: 'templates/layouts/default.html', + partials: ['templates/partials/*.hbs'], + helpers: ['templates/helpers/**/*.js'], + data: [], + flatten: true + }, + pages: { + src: ['templates/*.html'], + dest: 'dist/' + } + }; +}; diff --git a/tasks/options/useminPrepare.js b/emails/grunt/clean.js similarity index 56% rename from tasks/options/useminPrepare.js rename to emails/grunt/clean.js index f4b948a846d..60b8ff0a917 100644 --- a/tasks/options/useminPrepare.js +++ b/emails/grunt/clean.js @@ -1,7 +1,5 @@ module.exports = function(config) { return { - html: [ - 'tmp/index.html', - ] + dist: ['dist'], }; }; diff --git a/emails/grunt/premailer.js b/emails/grunt/premailer.js new file mode 100644 index 00000000000..c50ca1af015 --- /dev/null +++ b/emails/grunt/premailer.js @@ -0,0 +1,14 @@ +module.exports = { + main: { + options: { + verbose: true, + removeComments: true + }, + files: [{ + expand: true, // Enable dynamic expansion. + cwd: 'dist', // Src matches are relative to this path. + src: ['*.html'], // Actual pattern(s) to match. + dest: '../public/emails/', // Destination path prefix. + }], + } +}; diff --git a/emails/grunt/processhtml.js b/emails/grunt/processhtml.js new file mode 100644 index 00000000000..efe34626a49 --- /dev/null +++ b/emails/grunt/processhtml.js @@ -0,0 +1,10 @@ +module.exports = { + dist: { + files: [{ + expand: true, // Enable dynamic expansion. + cwd: 'dist', // Src matches are relative to this path. + src: ['*.html'], // Actual pattern(s) to match. + dest: 'dist/', // Destination path prefix. + }], + } +}; diff --git a/emails/grunt/replace.js b/emails/grunt/replace.js new file mode 100644 index 00000000000..7038802d34e --- /dev/null +++ b/emails/grunt/replace.js @@ -0,0 +1,13 @@ +module.exports = { + dist: { + overwrite: true, + src: ['dist/*.html'], + replacements: [{ + from: '[[', + to: '{{' + }, { + from: ']]', + to: '}}' + }] + } +}; diff --git a/emails/grunt/uncss.js b/emails/grunt/uncss.js new file mode 100644 index 00000000000..114af6942c5 --- /dev/null +++ b/emails/grunt/uncss.js @@ -0,0 +1,9 @@ +module.exports = { + dist: { + src: ['dist/*.html'], + dest: 'dist/css/tidy.css', + options: { + report: 'min' // optional: include to report savings + } + } +}; diff --git a/emails/grunt/watch.js b/emails/grunt/watch.js new file mode 100644 index 00000000000..65b3970da7f --- /dev/null +++ b/emails/grunt/watch.js @@ -0,0 +1,16 @@ +module.exports = { + src: { + files: [ + //what are the files that we want to watch + 'assets/css/*.css', + 'templates/**/*.html', + 'grunt/*.js', + ], + tasks: ['default'], + options: { + nospawn: true, + livereload: false, + } + } + +}; diff --git a/emails/gruntfile.js b/emails/gruntfile.js new file mode 100644 index 00000000000..b6d54cf651b --- /dev/null +++ b/emails/gruntfile.js @@ -0,0 +1,6 @@ +module.exports = function(grunt) { + + // load grunt config + require('load-grunt-config')(grunt); + +}; diff --git a/emails/package.json b/emails/package.json new file mode 100644 index 00000000000..96779fb5609 --- /dev/null +++ b/emails/package.json @@ -0,0 +1,24 @@ +{ + "name": "Grafana-Email-Campaign", + "version": "1.0.0", + "description": "Grafana Email templates based on Zurb Ink", + "repository": "dnnsldr/", + "author": { + "name": "dnnsldr", + "email": "delder@riester.com", + "url": "https://github.com/dnnsldr" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-premailer": "^0.2.10", + "grunt-processhtml": "^0.3.3", + "grunt-uncss": "^0.3.7", + "load-grunt-config": "^0.14.0", + "grunt-contrib-watch": "^0.6.1", + "grunt-text-replace": "^0.3.12" + }, + "dependencies": { + "grunt-assemble": "^0.4.0", + "grunt-contrib-clean": "^0.6.0" + } +} diff --git a/emails/templates/invited_to_org.html b/emails/templates/invited_to_org.html new file mode 100644 index 00000000000..59e76cd5a77 --- /dev/null +++ b/emails/templates/invited_to_org.html @@ -0,0 +1,47 @@ + + +[[Subject .Subject "[[.InvitedBy]] has added you to the [[.OrgName]] organization"]] + + + + + +
+ + + + + + +
+

You have been added to [[.OrgName]]

+
+ +
+ + + + + +
+ + + + + + + + +
+

[[.InvitedBy]] has added you to the [[.OrgName]] organization in Grafana. +

Once logged in, [[.OrgName]] will be available in the left side menu, in the dropdown below your username.

+
+ + + + +
Log in now
+
+
+ + diff --git a/emails/templates/layouts/default.html b/emails/templates/layouts/default.html new file mode 100644 index 00000000000..0fd5b35ea48 --- /dev/null +++ b/emails/templates/layouts/default.html @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + +
+
+ + + + + +
+
+ + + + + +
+ + + + + + +
+ +
+ +
+ +
+
+ + + + + + +
+ {{> body }} + + + + + + + + +
+
+
+ + diff --git a/emails/templates/new_user_invite.html b/emails/templates/new_user_invite.html new file mode 100644 index 00000000000..86948a665f6 --- /dev/null +++ b/emails/templates/new_user_invite.html @@ -0,0 +1,49 @@ + + +[[Subject .Subject "[[.InvitedBy]] has invited you to join Grafana"]] + + + + + +
+ + + + + + +
+

You're invited to join [[.OrgName]]

+
+ +
+ + + + + +
+ + + + + + + + + + + +
+

You've been invited to join the [[.OrgName]] organization by [[.InvitedBy]]. To accept your invitation and join the team, please click the link below:

+
+ + + + +
Accept Invitation
+
+

You can also copy/paste this link into your browser directly: [[.LinkUrl]]

+
+
\ No newline at end of file diff --git a/emails/templates/reset_password.html b/emails/templates/reset_password.html new file mode 100644 index 00000000000..f3aca5da95e --- /dev/null +++ b/emails/templates/reset_password.html @@ -0,0 +1,42 @@ +[[Subject .Subject "Reset your Grafana password - [[.Name]]"]] + + + + + +
+ + + + + + +
+

Hi [[.Name]]

+
+ +
+ + + + + +
+ + + + + +
+

+ Please click the following link to reset your password within [[.EmailCodeValidHours]] hours. +

+

+ [[.AppUrl]]user/password/reset?code=[[.Code]] +

+

Not working? Try copying and pasting it to your browser.

+
+ +
+ + diff --git a/emails/templates/signup_started.html b/emails/templates/signup_started.html new file mode 100644 index 00000000000..39b369a1f7c --- /dev/null +++ b/emails/templates/signup_started.html @@ -0,0 +1,46 @@ +[[Subject .Subject "Welcome to Grafana, please complete your sign up!"]] + + + + + +
+ + + + + + +
+

Complete the signup

+
+ +
+ + + + + +
+ + + + + + + + +
+ Copy and past the email verification code:
+ [[.Code]]
in + the sign up form or use the link below. +
+ + + + +
Complete Sign Up
+
+
+ + diff --git a/emails/templates/welcome_on_signup.html b/emails/templates/welcome_on_signup.html new file mode 100644 index 00000000000..b93c56b77b1 --- /dev/null +++ b/emails/templates/welcome_on_signup.html @@ -0,0 +1,37 @@ +[[Subject .Subject "Welcome to Grafana"]] + + + + + +
+ + + + + + +
+

Hi [[.Name]]

+
+ +
+ + + + + +
+ + + + + +
+

+ If you are new to Grafana please read the Getting Started guide. +

+
+
+ + diff --git a/latest.json b/latest.json index a85d79df539..79eb42a8527 100644 --- a/latest.json +++ b/latest.json @@ -1,3 +1,3 @@ { - "version": "2.0.2" + "version": "2.1.1" } diff --git a/main.go b/main.go index a732e1a166f..9639d432bea 100644 --- a/main.go +++ b/main.go @@ -8,10 +8,12 @@ import ( "path/filepath" "runtime" "strconv" + "syscall" "time" "github.com/grafana/grafana/pkg/cmd" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/eventpublisher" @@ -29,6 +31,7 @@ var buildstamp string var configFile = flag.String("config", "", "path to config file") var homePath = flag.String("homepath", "", "path to grafana install/home path, defaults to working directory") var pidFile = flag.String("pidfile", "", "path to pid file") +var exitChan = make(chan int) func init() { runtime.GOMAXPROCS(runtime.NumCPU()) @@ -41,19 +44,14 @@ func main() { setting.BuildCommit = commit setting.BuildStamp = buildstampInt64 - go func() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - os.Exit(0) - }() + go listenToSystemSignels() flag.Parse() - writePIDFile() initRuntime() search.Init() + login.Init() social.NewOAuthService() eventpublisher.Init() plugins.Init() @@ -67,17 +65,20 @@ func main() { } cmd.StartServer() - - log.Close() + exitChan <- 0 } func initRuntime() { - setting.NewConfigContext(&setting.CommandLineArgs{ + err := setting.NewConfigContext(&setting.CommandLineArgs{ Config: *configFile, HomePath: *homePath, Args: flag.Args(), }) + if err != nil { + log.Fatal(3, err.Error()) + } + log.Info("Starting Grafana") log.Info("Version: %v, Commit: %v, Build date: %v", setting.BuildVersion, setting.BuildCommit, time.Unix(setting.BuildStamp, 0)) setting.LogConfigurationInfo() @@ -103,3 +104,27 @@ func writePIDFile() { log.Fatal(3, "Failed to write pidfile", err) } } + +func listenToSystemSignels() { + signalChan := make(chan os.Signal, 1) + code := 0 + + signal.Notify(signalChan, os.Interrupt) + signal.Notify(signalChan, os.Kill) + signal.Notify(signalChan, syscall.SIGTERM) + + select { + case sig := <-signalChan: + log.Info("Received signal %s. shutting down", sig) + case code = <-exitChan: + switch code { + case 0: + log.Info("Shutting down") + default: + log.Warn("Shutting down") + } + } + + log.Close() + os.Exit(code) +} diff --git a/package.json b/package.json index 4200da6951a..9411aece50c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "company": "Coding Instinct AB" }, "name": "grafana", - "version": "2.1.0-pre1", + "version": "2.5.0-pre1", "repository": { "type": "git", "url": "http://github.com/torkelo/grafana.git" @@ -24,7 +24,7 @@ "grunt-contrib-htmlmin": "~0.1.3", "grunt-contrib-jshint": "~0.10.0", "grunt-contrib-less": "~0.7.0", - "grunt-contrib-requirejs": "~0.4.1", + "grunt-contrib-requirejs": "~0.4.4", "grunt-contrib-uglify": "~0.8.0", "grunt-contrib-watch": "^0.6.1", "grunt-filerev": "^0.2.1", @@ -32,6 +32,8 @@ "grunt-karma": "~0.8.3", "grunt-ng-annotate": "^0.9.2", "grunt-string-replace": "~0.2.4", + "grunt-tslint": "^2.5.0", + "grunt-typescript": "^0.7.0", "grunt-usemin": "3.0.0", "jshint-stylish": "~0.1.5", "karma": "~0.12.31", @@ -47,19 +49,21 @@ "load-grunt-tasks": "0.2.0", "mocha": "2.2.4", "requirejs": "2.1.17", - "rjs-build-analysis": "0.0.3" + "rjs-build-analysis": "0.0.3", + "tslint": "^2.5.0-beta" }, "engines": { - "node": "0.10.x", - "npm": "1.2.x" + "node": "0.4.x", + "npm": "2.14.x" }, "scripts": { "test": "grunt test", "coveralls": "grunt karma:coveralls && rm -rf ./coverage" }, - "license": "Apache License", + "license": "Apache-2.0", "dependencies": { "grunt-jscs": "~1.5.x", + "grunt-sync": "^0.4.1", "karma-sinon": "^1.0.3", "lodash": "^2.4.1", "sinon": "1.10.3" diff --git a/packaging/deb/control/postinst b/packaging/deb/control/postinst index 7585d3f879d..b93c8433490 100755 --- a/packaging/deb/control/postinst +++ b/packaging/deb/control/postinst @@ -5,14 +5,14 @@ set -e [ -f /etc/default/grafana-server ] && . /etc/default/grafana-server startGrafana() { - if [ -x /bin/systemctl ] ; then - /bin/systemctl daemon-reload - /bin/systemctl start grafana.service - elif [ -x "/etc/init.d/grafana" ]; then + if [ -x /bin/systemctl ]; then + /bin/systemctl daemon-reload + /bin/systemctl start grafana-server + elif [ -x "/etc/init.d/grafana-server" ]; then if [ -x "`which invoke-rc.d 2>/dev/null`" ]; then - invoke-rc.d grafana start || true + invoke-rc.d grafana-server start || true else - /etc/init.d/grafana start || true + /etc/init.d/grafana-server start || true fi fi } @@ -56,9 +56,9 @@ case "$1" in if [ -x /bin/systemctl ] ; then echo "### NOT starting on installation, please execute the following statements to configure grafana to start automatically using systemd" echo " sudo /bin/systemctl daemon-reload" - echo " sudo /bin/systemctl enable grafana-server.service" + echo " sudo /bin/systemctl enable grafana-server" echo "### You can start grafana-server by executing" - echo " sudo /bin/systemctl start grafana-server.service" + echo " sudo /bin/systemctl start grafana-server" elif [ -x /usr/sbin/update-rc.d ] ; then echo "### NOT starting grafana-server by default on bootup, please execute" diff --git a/packaging/deb/init.d/grafana-server b/packaging/deb/init.d/grafana-server index a4f6423a68d..9de4bf42092 100755 --- a/packaging/deb/init.d/grafana-server +++ b/packaging/deb/init.d/grafana-server @@ -36,6 +36,8 @@ MAX_OPEN_FILES=10000 PID_FILE=/var/run/$NAME.pid DAEMON=/usr/sbin/$NAME +umask 0027 + if [ `id -u` -ne 0 ]; then echo "You need root privileges to run this script" exit 4 diff --git a/packaging/deb/systemd/grafana-server.service b/packaging/deb/systemd/grafana-server.service index f99c96fcb13..16b975f23ea 100644 --- a/packaging/deb/systemd/grafana-server.service +++ b/packaging/deb/systemd/grafana-server.service @@ -12,10 +12,12 @@ Type=simple WorkingDirectory=/usr/share/grafana ExecStart=/usr/sbin/grafana-server \ --config=${CONF_FILE} \ + --pidfile=${PID_FILE} \ cfg:default.paths.logs=${LOG_DIR} \ cfg:default.paths.data=${DATA_DIR} \ LimitNOFILE=10000 TimeoutStopSec=20 +UMask=0027 [Install] WantedBy=multi-user.target diff --git a/packaging/rpm/systemd/grafana-server.service b/packaging/rpm/systemd/grafana-server.service index 56a958032b7..855dce53d08 100644 --- a/packaging/rpm/systemd/grafana-server.service +++ b/packaging/rpm/systemd/grafana-server.service @@ -12,6 +12,7 @@ Type=simple WorkingDirectory=/usr/share/grafana ExecStart=/usr/sbin/grafana-server \ --config=${CONF_FILE} \ + --pidfile=${PID_FILE} \ cfg:default.paths.logs=${LOG_DIR} \ cfg:default.paths.data=${DATA_DIR} \ LimitNOFILE=10000 diff --git a/pkg/api/api.go b/pkg/api/api.go index 679c0d1f760..27eb3c749db 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -14,14 +14,16 @@ func Register(r *macaron.Macaron) { reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true}) reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN) regOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN) + quota := middleware.Quota bind := binding.Bind // not logged in views r.Get("/", reqSignedIn, Index) r.Get("/logout", Logout) - r.Post("/login", bind(dtos.LoginCommand{}), LoginPost) - r.Get("/login/:name", OAuthLogin) + r.Post("/login", quota("session"), bind(dtos.LoginCommand{}), wrap(LoginPost)) + r.Get("/login/:name", quota("session"), OAuthLogin) r.Get("/login", LoginView) + r.Get("/invite/:code", Index) // authed views r.Get("/profile/", reqSignedIn, Index) @@ -36,11 +38,19 @@ func Register(r *macaron.Macaron) { r.Get("/admin/users", reqGrafanaAdmin, Index) r.Get("/admin/users/create", reqGrafanaAdmin, Index) r.Get("/admin/users/edit/:id", reqGrafanaAdmin, Index) + r.Get("/admin/orgs", reqGrafanaAdmin, Index) + r.Get("/admin/orgs/edit/:id", reqGrafanaAdmin, Index) r.Get("/dashboard/*", reqSignedIn, Index) // sign up r.Get("/signup", Index) - r.Post("/api/user/signup", bind(m.CreateUserCommand{}), wrap(SignUp)) + r.Get("/api/user/signup/options", wrap(GetSignUpOptions)) + r.Post("/api/user/signup", quota("user"), bind(dtos.SignUpForm{}), wrap(SignUp)) + r.Post("/api/user/signup/step2", bind(dtos.SignUpStep2Form{}), wrap(SignUpStep2)) + + // invited + r.Get("/api/user/invite/:code", wrap(GetInviteInfoByCode)) + r.Post("/api/user/invite/complete", bind(dtos.CompleteInviteForm{}), wrap(CompleteInvite)) // reset password r.Get("/user/password/send-reset-email", Index) @@ -57,7 +67,7 @@ func Register(r *macaron.Macaron) { r.Get("/api/snapshots-delete/:key", DeleteDashboardSnapshot) // api renew session based on remember cookie - r.Get("/api/login/ping", LoginApiPing) + r.Get("/api/login/ping", quota("session"), LoginApiPing) // authed api r.Group("/api", func() { @@ -71,6 +81,7 @@ func Register(r *macaron.Macaron) { r.Post("/stars/dashboard/:id", wrap(StarDashboard)) r.Delete("/stars/dashboard/:id", wrap(UnstarDashboard)) r.Put("/password", bind(m.ChangeUserPasswordCommand{}), wrap(ChangeUserPassword)) + r.Get("/quotas", wrap(GetUserQuotas)) }) // users (admin permission required) @@ -81,42 +92,58 @@ func Register(r *macaron.Macaron) { r.Put("/:id", bind(m.UpdateUserCommand{}), wrap(UpdateUser)) }, reqGrafanaAdmin) - // current org + // org information available to all users. r.Group("/org", func() { r.Get("/", wrap(GetOrgCurrent)) - r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrgCurrent)) - r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg)) + r.Get("/quotas", wrap(GetOrgQuotas)) + }) + + // current org + r.Group("/org", func() { + r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent)) + r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent)) + r.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg)) r.Get("/users", wrap(GetOrgUsersForCurrentOrg)) r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg)) r.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg)) + + // invites + r.Get("/invites", wrap(GetPendingOrgInvites)) + r.Post("/invites", quota("user"), bind(dtos.AddInviteForm{}), wrap(AddOrgInvite)) + r.Patch("/invites/:code/revoke", wrap(RevokeInvite)) }, regOrgAdmin) // create new org - r.Post("/orgs", bind(m.CreateOrgCommand{}), wrap(CreateOrg)) + r.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg)) // search all orgs r.Get("/orgs", reqGrafanaAdmin, wrap(SearchOrgs)) // orgs (admin routes) r.Group("/orgs/:orgId", func() { - r.Put("/", bind(m.UpdateOrgCommand{}), wrap(UpdateOrg)) + r.Get("/", wrap(GetOrgById)) + r.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrg)) + r.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddress)) + r.Delete("/", wrap(DeleteOrgById)) r.Get("/users", wrap(GetOrgUsers)) r.Post("/users", bind(m.AddOrgUserCommand{}), wrap(AddOrgUser)) r.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUser)) r.Delete("/users/:userId", wrap(RemoveOrgUser)) + r.Get("/quotas", wrap(GetOrgQuotas)) + r.Put("/quotas/:target", bind(m.UpdateOrgQuotaCmd{}), wrap(UpdateOrgQuota)) }, reqGrafanaAdmin) // auth api keys r.Group("/auth/keys", func() { r.Get("/", wrap(GetApiKeys)) - r.Post("/", bind(m.AddApiKeyCommand{}), wrap(AddApiKey)) + r.Post("/", quota("api_key"), bind(m.AddApiKeyCommand{}), wrap(AddApiKey)) r.Delete("/:id", wrap(DeleteApiKey)) }, regOrgAdmin) // Data sources r.Group("/datasources", func() { r.Get("/", GetDataSources) - r.Post("/", bind(m.AddDataSourceCommand{}), AddDataSource) + r.Post("/", quota("data_source"), bind(m.AddDataSourceCommand{}), AddDataSource) r.Put("/:id", bind(m.UpdateDataSourceCommand{}), UpdateDataSource) r.Delete("/:id", DeleteDataSource) r.Get("/:id", GetDataSourceById) @@ -141,6 +168,7 @@ func Register(r *macaron.Macaron) { // metrics r.Get("/metrics/test", GetTestMetrics) + }, reqSignedIn) // admin api @@ -150,6 +178,8 @@ func Register(r *macaron.Macaron) { r.Put("/users/:id/password", bind(dtos.AdminUpdateUserPasswordForm{}), AdminUpdateUserPassword) r.Put("/users/:id/permissions", bind(dtos.AdminUpdateUserPermissionsForm{}), AdminUpdateUserPermissions) r.Delete("/users/:id", AdminDeleteUser) + r.Get("/users/:id/quotas", wrap(GetUserQuotas)) + r.Put("/users/:id/quotas/:target", bind(m.UpdateUserQuotaCmd{}), wrap(UpdateUserQuota)) }, reqGrafanaAdmin) // rendering diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index a10c3c92f96..d7f9f2cd740 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -86,6 +86,19 @@ func DeleteDashboard(c *middleware.Context) { func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) { cmd.OrgId = c.OrgId + dash := cmd.GetDashboardModel() + if dash.Id == 0 { + limitReached, err := middleware.QuotaReached(c, "dashboard") + if err != nil { + c.JsonApiErr(500, "failed to get quota", err) + return + } + if limitReached { + c.JsonApiErr(403, "Quota reached", nil) + return + } + } + err := bus.Dispatch(&cmd) if err != nil { if err == m.ErrDashboardWithSameNameExists { diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 11075294b66..97eaa00d378 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) @@ -24,34 +25,37 @@ var dataProxyTransport = &http.Transport{ TLSHandshakeTimeout: 10 * time.Second, } -func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy { - target, _ := url.Parse(ds.Url) - +func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *httputil.ReverseProxy { director := func(req *http.Request) { - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - req.Host = target.Host + req.URL.Scheme = targetUrl.Scheme + req.URL.Host = targetUrl.Host + req.Host = targetUrl.Host reqQueryVals := req.URL.Query() if ds.Type == m.DS_INFLUXDB_08 { - req.URL.Path = util.JoinUrlFragments(target.Path, "db/"+ds.Database+"/"+proxyPath) + req.URL.Path = util.JoinUrlFragments(targetUrl.Path, "db/"+ds.Database+"/"+proxyPath) reqQueryVals.Add("u", ds.User) reqQueryVals.Add("p", ds.Password) req.URL.RawQuery = reqQueryVals.Encode() } else if ds.Type == m.DS_INFLUXDB { - req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath) + req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath) reqQueryVals.Add("db", ds.Database) - reqQueryVals.Add("u", ds.User) - reqQueryVals.Add("p", ds.Password) req.URL.RawQuery = reqQueryVals.Encode() + if !ds.BasicAuth { + req.Header.Add("Authorization", util.GetBasicAuthHeader(ds.User, ds.Password)) + } } else { - req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath) + req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath) } if ds.BasicAuth { req.Header.Add("Authorization", util.GetBasicAuthHeader(ds.BasicAuthUser, ds.BasicAuthPassword)) } + + // clear cookie headers + req.Header.Del("Cookie") + req.Header.Del("Set-Cookie") } return &httputil.ReverseProxy{Director: director} @@ -67,8 +71,21 @@ func ProxyDataSourceRequest(c *middleware.Context) { return } - proxyPath := c.Params("*") - proxy := NewReverseProxy(&query.Result, proxyPath) - proxy.Transport = dataProxyTransport - proxy.ServeHTTP(c.RW(), c.Req.Request) + ds := query.Result + targetUrl, _ := url.Parse(ds.Url) + if len(setting.DataProxyWhiteList) > 0 { + if _, exists := setting.DataProxyWhiteList[targetUrl.Host]; !exists { + c.JsonApiErr(403, "Data proxy hostname and ip are not included in whitelist", nil) + return + } + } + + if query.Result.Type == m.DS_CLOUDWATCH { + ProxyCloudWatchDataSourceRequest(c) + } else { + proxyPath := c.Params("*") + proxy := NewReverseProxy(&ds, proxyPath, targetUrl) + proxy.Transport = dataProxyTransport + proxy.ServeHTTP(c.RW(), c.Req.Request) + } } diff --git a/pkg/api/dataproxy_cloudwatch.go b/pkg/api/dataproxy_cloudwatch.go new file mode 100644 index 00000000000..3a0b4b16bc2 --- /dev/null +++ b/pkg/api/dataproxy_cloudwatch.go @@ -0,0 +1,107 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/grafana/grafana/pkg/middleware" +) + +func ProxyCloudWatchDataSourceRequest(c *middleware.Context) { + body, _ := ioutil.ReadAll(c.Req.Request.Body) + + reqInfo := &struct { + Region string `json:"region"` + Service string `json:"service"` + Action string `json:"action"` + }{} + json.Unmarshal([]byte(body), reqInfo) + + svc := cloudwatch.New(&aws.Config{Region: aws.String(reqInfo.Region)}) + + switch reqInfo.Action { + case "GetMetricStatistics": + reqParam := &struct { + Parameters struct { + Namespace string `json:"Namespace"` + MetricName string `json:"MetricName"` + Dimensions []map[string]string `json:"Dimensions"` + Statistics []string `json:"Statistics"` + StartTime int64 `json:"StartTime"` + EndTime int64 `json:"EndTime"` + Period int64 `json:"Period"` + } `json:"parameters"` + }{} + json.Unmarshal([]byte(body), reqParam) + + statistics := make([]*string, 0) + for k := range reqParam.Parameters.Statistics { + statistics = append(statistics, &reqParam.Parameters.Statistics[k]) + } + dimensions := make([]*cloudwatch.Dimension, 0) + for _, d := range reqParam.Parameters.Dimensions { + dimensions = append(dimensions, &cloudwatch.Dimension{ + Name: aws.String(d["Name"]), + Value: aws.String(d["Value"]), + }) + } + + params := &cloudwatch.GetMetricStatisticsInput{ + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Dimensions: dimensions, + Statistics: statistics, + StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), + EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), + Period: aws.Int64(reqParam.Parameters.Period), + } + + resp, err := svc.GetMetricStatistics(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + respJson, _ := json.Marshal(resp) + fmt.Fprint(c.RW(), string(respJson)) + case "ListMetrics": + reqParam := &struct { + Parameters struct { + Namespace string `json:"Namespace"` + MetricName string `json:"MetricName"` + Dimensions []map[string]string `json:"Dimensions"` + } `json:"parameters"` + }{} + json.Unmarshal([]byte(body), reqParam) + + dimensions := make([]*cloudwatch.DimensionFilter, 0) + for _, d := range reqParam.Parameters.Dimensions { + dimensions = append(dimensions, &cloudwatch.DimensionFilter{ + Name: aws.String(d["Name"]), + Value: aws.String(d["Value"]), + }) + } + + params := &cloudwatch.ListMetricsInput{ + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Dimensions: dimensions, + } + + resp, err := svc.ListMetrics(params) + if err != nil { + c.JsonApiErr(500, "Unable to call AWS API", err) + return + } + + respJson, _ := json.Marshal(resp) + fmt.Fprint(c.RW(), string(respJson)) + default: + c.JsonApiErr(500, "Unexpected CloudWatch action", errors.New(reqInfo.Action)) + } +} diff --git a/pkg/api/dataproxy_test.go b/pkg/api/dataproxy_test.go index 842730891ae..0e561c726e1 100644 --- a/pkg/api/dataproxy_test.go +++ b/pkg/api/dataproxy_test.go @@ -14,7 +14,8 @@ func TestDataSourceProxy(t *testing.T) { Convey("When getting graphite datasource proxy", t, func() { ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE} - proxy := NewReverseProxy(&ds, "/render") + targetUrl, _ := url.Parse(ds.Url) + proxy := NewReverseProxy(&ds, "/render", targetUrl) requestUrl, _ := url.Parse("http://grafana.com/sub") req := http.Request{URL: requestUrl} @@ -36,7 +37,8 @@ func TestDataSourceProxy(t *testing.T) { Password: "password", } - proxy := NewReverseProxy(&ds, "") + targetUrl, _ := url.Parse(ds.Url) + proxy := NewReverseProxy(&ds, "", targetUrl) requestUrl, _ := url.Parse("http://grafana.com/sub") req := http.Request{URL: requestUrl} diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index e0253df3cdb..717211d438d 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -112,5 +112,13 @@ func UpdateDataSource(c *middleware.Context, cmd m.UpdateDataSourceCommand) { } func GetDataSourcePlugins(c *middleware.Context) { - c.JSON(200, plugins.DataSources) + dsList := make(map[string]interface{}) + + for key, value := range plugins.DataSources { + if value.(map[string]interface{})["builtIn"] == nil { + dsList[key] = value + } + } + + c.JSON(200, dsList) } diff --git a/pkg/api/dtos/invite.go b/pkg/api/dtos/invite.go new file mode 100644 index 00000000000..3f002a8b157 --- /dev/null +++ b/pkg/api/dtos/invite.go @@ -0,0 +1,26 @@ +package dtos + +import m "github.com/grafana/grafana/pkg/models" + +type AddInviteForm struct { + LoginOrEmail string `json:"loginOrEmail" binding:"Required"` + Name string `json:"name"` + Role m.RoleType `json:"role" binding:"Required"` + SkipEmails bool `json:"skipEmails"` +} + +type InviteInfo struct { + Email string `json:"email"` + Name string `json:"name"` + Username string `json:"username"` + InvitedBy string `json:"invitedBy"` +} + +type CompleteInviteForm struct { + InviteCode string `json:"inviteCode"` + Email string `json:"email" binding:"Required"` + Name string `json:"name"` + Username string `json:"username"` + Password string `json:"password"` + ConfirmPassword string `json:"confirmPassword"` +} diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 3e1826f56fb..9a4400adbdf 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -17,6 +17,7 @@ type LoginCommand struct { type CurrentUser struct { IsSignedIn bool `json:"isSignedIn"` + Id int64 `json:"id"` Login string `json:"login"` Email string `json:"email"` Name string `json:"name"` diff --git a/pkg/api/dtos/org.go b/pkg/api/dtos/org.go new file mode 100644 index 00000000000..b4bb26ca78a --- /dev/null +++ b/pkg/api/dtos/org.go @@ -0,0 +1,14 @@ +package dtos + +type UpdateOrgForm struct { + Name string `json:"name" binding:"Required"` +} + +type UpdateOrgAddressForm struct { + Address1 string `json:"address1"` + Address2 string `json:"address2"` + City string `json:"city"` + ZipCode string `json:"zipcode"` + State string `json:"state"` + Country string `json:"country"` +} diff --git a/pkg/api/dtos/user.go b/pkg/api/dtos/user.go index 9b407535429..dbbe24a159b 100644 --- a/pkg/api/dtos/user.go +++ b/pkg/api/dtos/user.go @@ -1,5 +1,18 @@ package dtos +type SignUpForm struct { + Email string `json:"email" binding:"Required"` +} + +type SignUpStep2Form struct { + Email string `json:"email"` + Name string `json:"name"` + Username string `json:"username"` + Password string `json:"password"` + Code string `json:"code"` + OrgName string `json:"orgName"` +} + type AdminCreateUserForm struct { Email string `json:"email"` Login string `json:"login"` diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 7851f1d8f0d..3440ddb3715 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -86,13 +86,19 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro // add grafana backend data source grafanaDatasourceMeta, _ := plugins.DataSources["grafana"] - datasources["grafana"] = map[string]interface{}{ + datasources["-- Grafana --"] = map[string]interface{}{ "type": "grafana", "meta": grafanaDatasourceMeta, } + // add mixed backend data source + datasources["-- Mixed --"] = map[string]interface{}{ + "type": "mixed", + "meta": plugins.DataSources["mixed"], + } + if defaultDatasource == "" { - defaultDatasource = "grafana" + defaultDatasource = "-- Grafana --" } jsonObj := map[string]interface{}{ diff --git a/pkg/api/index.go b/pkg/api/index.go index 8f486c4b785..556db006b2f 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -13,6 +13,7 @@ func setIndexViewData(c *middleware.Context) error { } currentUser := &dtos.CurrentUser{ + Id: c.UserId, IsSignedIn: c.IsSignedIn, Login: c.Login, Email: c.Email, @@ -47,6 +48,10 @@ func setIndexViewData(c *middleware.Context) error { c.Data["GoogleAnalyticsId"] = setting.GoogleAnalyticsId } + if setting.GoogleTagManagerId != "" { + c.Data["GoogleTagManagerId"] = setting.GoogleTagManagerId + } + return nil } diff --git a/pkg/api/login.go b/pkg/api/login.go index 0fc5651d5f9..8863e1b10c1 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -6,6 +6,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" @@ -86,23 +87,22 @@ func LoginApiPing(c *middleware.Context) { c.JsonOK("Logged in") } -func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) { - userQuery := m.GetUserByLoginQuery{LoginOrEmail: cmd.User} - err := bus.Dispatch(&userQuery) - - if err != nil { - c.JsonApiErr(401, "Invalid username or password", err) - return +func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response { + authQuery := login.LoginUserQuery{ + Username: cmd.User, + Password: cmd.Password, } - user := userQuery.Result + if err := bus.Dispatch(&authQuery); err != nil { + if err == login.ErrInvalidCredentials { + return ApiError(401, "Invalid username or password", err) + } - passwordHashed := util.EncodePassword(cmd.Password, user.Salt) - if passwordHashed != user.Password { - c.JsonApiErr(401, "Invalid username or password", err) - return + return ApiError(500, "Error while trying to authenticate user", err) } + user := authQuery.User + loginUserWithUser(user, c) result := map[string]interface{}{ @@ -116,7 +116,7 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) { metrics.M_Api_Login_Post.Inc(1) - c.JSON(200, result) + return Json(200, result) } func loginUserWithUser(user *m.User, c *middleware.Context) { diff --git a/pkg/api/login_oauth.go b/pkg/api/login_oauth.go index 796599df864..4244feef664 100644 --- a/pkg/api/login_oauth.go +++ b/pkg/api/login_oauth.go @@ -74,7 +74,15 @@ func OAuthLogin(ctx *middleware.Context) { ctx.Redirect(setting.AppSubUrl + "/login") return } - + limitReached, err := middleware.QuotaReached(ctx, "user") + if err != nil { + ctx.Handle(500, "Failed to get user quota", err) + return + } + if limitReached { + ctx.Redirect(setting.AppSubUrl + "/login") + return + } cmd := m.CreateUserCommand{ Login: userInfo.Email, Email: userInfo.Email, diff --git a/pkg/api/org.go b/pkg/api/org.go index 746281c5138..6ed4a7c4a14 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -1,6 +1,7 @@ package api import ( + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" @@ -30,22 +31,34 @@ func getOrgHelper(orgId int64) Response { return ApiError(500, "Failed to get organization", err) } - org := m.OrgDTO{ - Id: query.Result.Id, - Name: query.Result.Name, + org := query.Result + result := m.OrgDetailsDTO{ + Id: org.Id, + Name: org.Name, + Address: m.Address{ + Address1: org.Address1, + Address2: org.Address2, + City: org.City, + ZipCode: org.ZipCode, + State: org.State, + Country: org.Country, + }, } - return Json(200, &org) + return Json(200, &result) } // POST /api/orgs func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response { if !c.IsSignedIn || (!setting.AllowUserOrgCreate && !c.IsGrafanaAdmin) { - return ApiError(401, "Access denied", nil) + return ApiError(403, "Access denied", nil) } cmd.UserId = c.UserId if err := bus.Dispatch(&cmd); err != nil { + if err == m.ErrOrgNameTaken { + return ApiError(400, "Organization name taken", err) + } return ApiError(500, "Failed to create organization", err) } @@ -58,25 +71,65 @@ func CreateOrg(c *middleware.Context, cmd m.CreateOrgCommand) Response { } // PUT /api/org -func UpdateOrgCurrent(c *middleware.Context, cmd m.UpdateOrgCommand) Response { - cmd.OrgId = c.OrgId - return updateOrgHelper(cmd) +func UpdateOrgCurrent(c *middleware.Context, form dtos.UpdateOrgForm) Response { + return updateOrgHelper(form, c.OrgId) } // PUT /api/orgs/:orgId -func UpdateOrg(c *middleware.Context, cmd m.UpdateOrgCommand) Response { - cmd.OrgId = c.ParamsInt64(":orgId") - return updateOrgHelper(cmd) +func UpdateOrg(c *middleware.Context, form dtos.UpdateOrgForm) Response { + return updateOrgHelper(form, c.ParamsInt64(":orgId")) } -func updateOrgHelper(cmd m.UpdateOrgCommand) Response { +func updateOrgHelper(form dtos.UpdateOrgForm, orgId int64) Response { + cmd := m.UpdateOrgCommand{Name: form.Name, OrgId: orgId} if err := bus.Dispatch(&cmd); err != nil { + if err == m.ErrOrgNameTaken { + return ApiError(400, "Organization name taken", err) + } return ApiError(500, "Failed to update organization", err) } return ApiSuccess("Organization updated") } +// PUT /api/org/address +func UpdateOrgAddressCurrent(c *middleware.Context, form dtos.UpdateOrgAddressForm) Response { + return updateOrgAddressHelper(form, c.OrgId) +} + +// PUT /api/orgs/:orgId/address +func UpdateOrgAddress(c *middleware.Context, form dtos.UpdateOrgAddressForm) Response { + return updateOrgAddressHelper(form, c.ParamsInt64(":orgId")) +} + +func updateOrgAddressHelper(form dtos.UpdateOrgAddressForm, orgId int64) Response { + cmd := m.UpdateOrgAddressCommand{ + OrgId: orgId, + Address: m.Address{ + Address1: form.Address1, + Address2: form.Address2, + City: form.City, + State: form.State, + ZipCode: form.ZipCode, + Country: form.Country, + }, + } + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to update org address", err) + } + + return ApiSuccess("Address updated") +} + +// GET /api/orgs/:orgId +func DeleteOrgById(c *middleware.Context) Response { + if err := bus.Dispatch(&m.DeleteOrgCommand{Id: c.ParamsInt64(":orgId")}); err != nil { + return ApiError(500, "Failed to update organization", err) + } + return ApiSuccess("Organization deleted") +} + func SearchOrgs(c *middleware.Context) Response { query := m.SearchOrgsQuery{ Query: c.Query("query"), diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go new file mode 100644 index 00000000000..8d916677ed2 --- /dev/null +++ b/pkg/api/org_invite.go @@ -0,0 +1,219 @@ +package api + +import ( + "fmt" + + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/events" + "github.com/grafana/grafana/pkg/metrics" + "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" +) + +func GetPendingOrgInvites(c *middleware.Context) Response { + query := m.GetTempUsersQuery{OrgId: c.OrgId, Status: m.TmpUserInvitePending} + + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to get invites from db", err) + } + + for _, invite := range query.Result { + invite.Url = setting.ToAbsUrl("invite/" + invite.Code) + } + + return Json(200, query.Result) +} + +func AddOrgInvite(c *middleware.Context, inviteDto dtos.AddInviteForm) Response { + if !inviteDto.Role.IsValid() { + return ApiError(400, "Invalid role specified", nil) + } + + // first try get existing user + userQuery := m.GetUserByLoginQuery{LoginOrEmail: inviteDto.LoginOrEmail} + if err := bus.Dispatch(&userQuery); err != nil { + if err != m.ErrUserNotFound { + return ApiError(500, "Failed to query db for existing user check", err) + } + } else { + return inviteExistingUserToOrg(c, userQuery.Result, &inviteDto) + } + + cmd := m.CreateTempUserCommand{} + cmd.OrgId = c.OrgId + cmd.Email = inviteDto.LoginOrEmail + cmd.Name = inviteDto.Name + cmd.Status = m.TmpUserInvitePending + cmd.InvitedByUserId = c.UserId + cmd.Code = util.GetRandomString(30) + cmd.Role = inviteDto.Role + cmd.RemoteAddr = c.Req.RemoteAddr + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to save invite to database", err) + } + + // send invite email + if !inviteDto.SkipEmails && util.IsEmail(inviteDto.LoginOrEmail) { + emailCmd := m.SendEmailCommand{ + To: []string{inviteDto.LoginOrEmail}, + Template: "new_user_invite.html", + Data: map[string]interface{}{ + "Name": util.StringsFallback2(cmd.Name, cmd.Email), + "OrgName": c.OrgName, + "Email": c.Email, + "LinkUrl": setting.ToAbsUrl("invite/" + cmd.Code), + "InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login), + }, + } + + if err := bus.Dispatch(&emailCmd); err != nil { + return ApiError(500, "Failed to send email invite", err) + } + + return ApiSuccess(fmt.Sprintf("Sent invite to %s", inviteDto.LoginOrEmail)) + } + + return ApiSuccess(fmt.Sprintf("Created invite for %s", inviteDto.LoginOrEmail)) +} + +func inviteExistingUserToOrg(c *middleware.Context, user *m.User, inviteDto *dtos.AddInviteForm) Response { + // user exists, add org role + createOrgUserCmd := m.AddOrgUserCommand{OrgId: c.OrgId, UserId: user.Id, Role: inviteDto.Role} + if err := bus.Dispatch(&createOrgUserCmd); err != nil { + if err == m.ErrOrgUserAlreadyAdded { + return ApiError(412, fmt.Sprintf("User %s is already added to organization", inviteDto.LoginOrEmail), err) + } + return ApiError(500, "Error while trying to create org user", err) + } else { + + if !inviteDto.SkipEmails && util.IsEmail(user.Email) { + emailCmd := m.SendEmailCommand{ + To: []string{user.Email}, + Template: "invited_to_org.html", + Data: map[string]interface{}{ + "Name": user.NameOrFallback(), + "OrgName": c.OrgName, + "InvitedBy": util.StringsFallback3(c.Name, c.Email, c.Login), + }, + } + + if err := bus.Dispatch(&emailCmd); err != nil { + return ApiError(500, "Failed to send email invited_to_org", err) + } + } + + return ApiSuccess(fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName)) + } +} + +func RevokeInvite(c *middleware.Context) Response { + if ok, rsp := updateTempUserStatus(c.Params(":code"), m.TmpUserRevoked); !ok { + return rsp + } + + return ApiSuccess("Invite revoked") +} + +func GetInviteInfoByCode(c *middleware.Context) Response { + query := m.GetTempUserByCodeQuery{Code: c.Params(":code")} + + if err := bus.Dispatch(&query); err != nil { + if err == m.ErrTempUserNotFound { + return ApiError(404, "Invite not found", nil) + } + return ApiError(500, "Failed to get invite", err) + } + + invite := query.Result + + return Json(200, dtos.InviteInfo{ + Email: invite.Email, + Name: invite.Name, + Username: invite.Email, + InvitedBy: util.StringsFallback3(invite.InvitedByName, invite.InvitedByLogin, invite.InvitedByEmail), + }) +} + +func CompleteInvite(c *middleware.Context, completeInvite dtos.CompleteInviteForm) Response { + query := m.GetTempUserByCodeQuery{Code: completeInvite.InviteCode} + + if err := bus.Dispatch(&query); err != nil { + if err == m.ErrTempUserNotFound { + return ApiError(404, "Invite not found", nil) + } + return ApiError(500, "Failed to get invite", err) + } + + invite := query.Result + if invite.Status != m.TmpUserInvitePending { + return ApiError(412, fmt.Sprintf("Invite cannot be used in status %s", invite.Status), nil) + } + + cmd := m.CreateUserCommand{ + Email: completeInvite.Email, + Name: completeInvite.Name, + Login: completeInvite.Username, + Password: completeInvite.Password, + SkipOrgSetup: true, + } + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "failed to create user", err) + } + + user := &cmd.Result + + bus.Publish(&events.SignUpCompleted{ + Name: user.NameOrFallback(), + Email: user.Email, + }) + + if ok, rsp := applyUserInvite(user, invite, true); !ok { + return rsp + } + + loginUserWithUser(user, c) + + metrics.M_Api_User_SignUpCompleted.Inc(1) + metrics.M_Api_User_SignUpInvite.Inc(1) + + return ApiSuccess("User created and logged in") +} + +func updateTempUserStatus(code string, status m.TempUserStatus) (bool, Response) { + // update temp user status + updateTmpUserCmd := m.UpdateTempUserStatusCommand{Code: code, Status: status} + if err := bus.Dispatch(&updateTmpUserCmd); err != nil { + return false, ApiError(500, "Failed to update invite status", err) + } + + return true, nil +} + +func applyUserInvite(user *m.User, invite *m.TempUserDTO, setActive bool) (bool, Response) { + // add to org + addOrgUserCmd := m.AddOrgUserCommand{OrgId: invite.OrgId, UserId: user.Id, Role: invite.Role} + if err := bus.Dispatch(&addOrgUserCmd); err != nil { + if err != m.ErrOrgUserAlreadyAdded { + return false, ApiError(500, "Error while trying to create org user", err) + } + } + + // update temp user status + if ok, rsp := updateTempUserStatus(invite.Code, m.TmpUserCompleted); !ok { + return false, rsp + } + + if setActive { + // set org to active + if err := bus.Dispatch(&m.SetUsingOrgCommand{OrgId: invite.OrgId, UserId: user.Id}); err != nil { + return false, ApiError(500, "Failed to set org as active", err) + } + } + + return true, nil +} diff --git a/pkg/api/quota.go b/pkg/api/quota.go new file mode 100644 index 00000000000..d8585435430 --- /dev/null +++ b/pkg/api/quota.go @@ -0,0 +1,68 @@ +package api + +import ( + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" +) + +func GetOrgQuotas(c *middleware.Context) Response { + if !setting.Quota.Enabled { + return ApiError(404, "Quotas not enabled", nil) + } + query := m.GetOrgQuotasQuery{OrgId: c.ParamsInt64(":orgId")} + + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to get org quotas", err) + } + + return Json(200, query.Result) +} + +func UpdateOrgQuota(c *middleware.Context, cmd m.UpdateOrgQuotaCmd) Response { + if !setting.Quota.Enabled { + return ApiError(404, "Quotas not enabled", nil) + } + cmd.OrgId = c.ParamsInt64(":orgId") + cmd.Target = c.Params(":target") + + if _, ok := setting.Quota.Org.ToMap()[cmd.Target]; !ok { + return ApiError(404, "Invalid quota target", nil) + } + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to update org quotas", err) + } + return ApiSuccess("Organization quota updated") +} + +func GetUserQuotas(c *middleware.Context) Response { + if !setting.Quota.Enabled { + return ApiError(404, "Quotas not enabled", nil) + } + query := m.GetUserQuotasQuery{UserId: c.ParamsInt64(":id")} + + if err := bus.Dispatch(&query); err != nil { + return ApiError(500, "Failed to get org quotas", err) + } + + return Json(200, query.Result) +} + +func UpdateUserQuota(c *middleware.Context, cmd m.UpdateUserQuotaCmd) Response { + if !setting.Quota.Enabled { + return ApiError(404, "Quotas not enabled", nil) + } + cmd.UserId = c.ParamsInt64(":id") + cmd.Target = c.Params(":target") + + if _, ok := setting.Quota.User.ToMap()[cmd.Target]; !ok { + return ApiError(404, "Invalid quota target", nil) + } + + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to update org quotas", err) + } + return ApiSuccess("Organization quota updated") +} diff --git a/pkg/api/signup.go b/pkg/api/signup.go index 77305caba70..767b9801154 100644 --- a/pkg/api/signup.go +++ b/pkg/api/signup.go @@ -1,38 +1,135 @@ package api import ( + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) +// GET /api/user/signup/options +func GetSignUpOptions(c *middleware.Context) Response { + return Json(200, util.DynMap{ + "verifyEmailEnabled": setting.VerifyEmailEnabled, + "autoAssignOrg": setting.AutoAssignOrg, + }) +} + // POST /api/user/signup -func SignUp(c *middleware.Context, cmd m.CreateUserCommand) Response { +func SignUp(c *middleware.Context, form dtos.SignUpForm) Response { if !setting.AllowUserSignUp { return ApiError(401, "User signup is disabled", nil) } - cmd.Login = cmd.Email - - if err := bus.Dispatch(&cmd); err != nil { - return ApiError(500, "failed to create user", err) + existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email} + if err := bus.Dispatch(&existing); err == nil { + return ApiError(422, "User with same email address already exists", nil) } - user := cmd.Result + cmd := m.CreateTempUserCommand{} + cmd.OrgId = -1 + cmd.Email = form.Email + cmd.Status = m.TmpUserSignUpStarted + cmd.InvitedByUserId = c.UserId + cmd.Code = util.GetRandomString(20) + cmd.RemoteAddr = c.Req.RemoteAddr - bus.Publish(&events.UserSignedUp{ - Id: user.Id, - Name: user.Name, - Email: user.Email, - Login: user.Login, + if err := bus.Dispatch(&cmd); err != nil { + return ApiError(500, "Failed to create signup", err) + } + + bus.Publish(&events.SignUpStarted{ + Email: form.Email, + Code: cmd.Code, }) - loginUserWithUser(&user, c) + metrics.M_Api_User_SignUpStarted.Inc(1) - metrics.M_Api_User_SignUp.Inc(1) + return Json(200, util.DynMap{"status": "SignUpCreated"}) +} - return ApiSuccess("User created and logged in") +func SignUpStep2(c *middleware.Context, form dtos.SignUpStep2Form) Response { + if !setting.AllowUserSignUp { + return ApiError(401, "User signup is disabled", nil) + } + + createUserCmd := m.CreateUserCommand{ + Email: form.Email, + Login: form.Username, + Name: form.Name, + Password: form.Password, + OrgName: form.OrgName, + } + + // verify email + if setting.VerifyEmailEnabled { + if ok, rsp := verifyUserSignUpEmail(form.Email, form.Code); !ok { + return rsp + } + createUserCmd.EmailVerified = true + } + + // check if user exists + existing := m.GetUserByLoginQuery{LoginOrEmail: form.Email} + if err := bus.Dispatch(&existing); err == nil { + return ApiError(401, "User with same email address already exists", nil) + } + + // dispatch create command + if err := bus.Dispatch(&createUserCmd); err != nil { + return ApiError(500, "Failed to create user", err) + } + + // publish signup event + user := &createUserCmd.Result + bus.Publish(&events.SignUpCompleted{ + Email: user.Email, + Name: user.NameOrFallback(), + }) + + // mark temp user as completed + if ok, rsp := updateTempUserStatus(form.Code, m.TmpUserCompleted); !ok { + return rsp + } + + // check for pending invites + invitesQuery := m.GetTempUsersQuery{Email: form.Email, Status: m.TmpUserInvitePending} + if err := bus.Dispatch(&invitesQuery); err != nil { + return ApiError(500, "Failed to query database for invites", err) + } + + apiResponse := util.DynMap{"message": "User sign up completed succesfully", "code": "redirect-to-landing-page"} + for _, invite := range invitesQuery.Result { + if ok, rsp := applyUserInvite(user, invite, false); !ok { + return rsp + } + apiResponse["code"] = "redirect-to-select-org" + } + + loginUserWithUser(user, c) + metrics.M_Api_User_SignUpCompleted.Inc(1) + + return Json(200, apiResponse) +} + +func verifyUserSignUpEmail(email string, code string) (bool, Response) { + query := m.GetTempUserByCodeQuery{Code: code} + + if err := bus.Dispatch(&query); err != nil { + if err == m.ErrTempUserNotFound { + return false, ApiError(404, "Invalid email verification code", nil) + } + return false, ApiError(500, "Failed to read temp user", err) + } + + tempUser := query.Result + if tempUser.Email != email { + return false, ApiError(404, "Email verification code does not match email", nil) + } + + return true, nil } diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go index c94661a5f9a..69843b6b095 100644 --- a/pkg/cmd/web.go +++ b/pkg/cmd/web.go @@ -33,7 +33,7 @@ func newMacaron() *macaron.Macaron { mapStatic(m, "css", "css") mapStatic(m, "img", "img") mapStatic(m, "fonts", "fonts") - mapStatic(m, "robots.txt", "robots.txxt") + mapStatic(m, "robots.txt", "robots.txt") m.Use(macaron.Renderer(macaron.RenderOptions{ Directory: path.Join(setting.StaticRootPath, "views"), diff --git a/pkg/events/events.go b/pkg/events/events.go index 5e82578b474..235de9c3130 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -70,11 +70,15 @@ type UserCreated struct { Email string `json:"email"` } -type UserSignedUp struct { +type SignUpStarted struct { + Timestamp time.Time `json:"timestamp"` + Email string `json:"email"` + Code string `json:"code"` +} + +type SignUpCompleted struct { Timestamp time.Time `json:"timestamp"` - Id int64 `json:"id"` Name string `json:"name"` - Login string `json:"login"` Email string `json:"email"` } diff --git a/pkg/log/file.go b/pkg/log/file.go index e9402815f0b..8f005d9723b 100644 --- a/pkg/log/file.go +++ b/pkg/log/file.go @@ -144,7 +144,7 @@ func (w *FileLogWriter) WriteMsg(msg string, skip, level int) error { func (w *FileLogWriter) createLogFile() (*os.File, error) { // Open the log file - return os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660) + return os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) } func (w *FileLogWriter) initFd() error { diff --git a/pkg/log/log.go b/pkg/log/log.go index d2e31cffb64..a7d26ce7913 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -82,7 +82,11 @@ func Fatal(skip int, format string, v ...interface{}) { func Close() { for _, l := range loggers { l.Close() + // delete the logger. + l = nil } + // clear the loggers slice. + loggers = nil } // .___ __ _____ diff --git a/pkg/login/auth.go b/pkg/login/auth.go new file mode 100644 index 00000000000..38d87444e23 --- /dev/null +++ b/pkg/login/auth.go @@ -0,0 +1,65 @@ +package login + +import ( + "errors" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" +) + +var ( + ErrInvalidCredentials = errors.New("Invalid Username or Password") +) + +type LoginUserQuery struct { + Username string + Password string + User *m.User +} + +func Init() { + bus.AddHandler("auth", AuthenticateUser) + loadLdapConfig() +} + +func AuthenticateUser(query *LoginUserQuery) error { + err := loginUsingGrafanaDB(query) + if err == nil || err != ErrInvalidCredentials { + return err + } + + if setting.LdapEnabled { + for _, server := range ldapCfg.Servers { + auther := NewLdapAuthenticator(server) + err = auther.login(query) + if err == nil || err != ErrInvalidCredentials { + return err + } + } + } + + return err +} + +func loginUsingGrafanaDB(query *LoginUserQuery) error { + userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username} + + if err := bus.Dispatch(&userQuery); err != nil { + if err == m.ErrUserNotFound { + return ErrInvalidCredentials + } + return err + } + + user := userQuery.Result + + passwordHashed := util.EncodePassword(query.Password, user.Salt) + if passwordHashed != user.Password { + return ErrInvalidCredentials + } + + query.User = user + return nil +} diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go new file mode 100644 index 00000000000..6305df6726c --- /dev/null +++ b/pkg/login/ldap.go @@ -0,0 +1,307 @@ +package login + +import ( + "crypto/tls" + "errors" + "fmt" + "strings" + + "github.com/davecgh/go-spew/spew" + "github.com/go-ldap/ldap" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" + m "github.com/grafana/grafana/pkg/models" +) + +type ldapAuther struct { + server *LdapServerConf + conn *ldap.Conn + requireSecondBind bool +} + +func NewLdapAuthenticator(server *LdapServerConf) *ldapAuther { + return &ldapAuther{server: server} +} + +func (a *ldapAuther) Dial() error { + address := fmt.Sprintf("%s:%d", a.server.Host, a.server.Port) + var err error + if a.server.UseSSL { + tlsCfg := &tls.Config{ + InsecureSkipVerify: a.server.SkipVerifySSL, + ServerName: a.server.Host, + } + a.conn, err = ldap.DialTLS("tcp", address, tlsCfg) + } else { + a.conn, err = ldap.Dial("tcp", address) + } + + return err +} + +func (a *ldapAuther) login(query *LoginUserQuery) error { + if err := a.Dial(); err != nil { + return err + } + defer a.conn.Close() + + // perform initial authentication + if err := a.initialBind(query.Username, query.Password); err != nil { + return err + } + + // find user entry & attributes + if ldapUser, err := a.searchForUser(query.Username); err != nil { + return err + } else { + if ldapCfg.VerboseLogging { + log.Info("Ldap User Info: %s", spew.Sdump(ldapUser)) + } + + // check if a second user bind is needed + if a.requireSecondBind { + if err := a.secondBind(ldapUser, query.Password); err != nil { + return err + } + } + + if grafanaUser, err := a.getGrafanaUserFor(ldapUser); err != nil { + return err + } else { + // sync org roles + if err := a.syncOrgRoles(grafanaUser, ldapUser); err != nil { + return err + } + query.User = grafanaUser + return nil + } + } +} + +func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error) { + // validate that the user has access + // if there are no ldap group mappings access is true + // otherwise a single group must match + access := len(a.server.LdapGroups) == 0 + for _, ldapGroup := range a.server.LdapGroups { + if ldapUser.isMemberOf(ldapGroup.GroupDN) { + access = true + break + } + } + + if !access { + log.Info("Ldap Auth: user %s does not belong in any of the specified ldap groups, ldapUser groups: %v", ldapUser.Username, ldapUser.MemberOf) + return nil, ErrInvalidCredentials + } + + // get user from grafana db + userQuery := m.GetUserByLoginQuery{LoginOrEmail: ldapUser.Username} + if err := bus.Dispatch(&userQuery); err != nil { + if err == m.ErrUserNotFound { + return a.createGrafanaUser(ldapUser) + } else { + return nil, err + } + } + + return userQuery.Result, nil +} + +func (a *ldapAuther) createGrafanaUser(ldapUser *ldapUserInfo) (*m.User, error) { + cmd := m.CreateUserCommand{ + Login: ldapUser.Username, + Email: ldapUser.Email, + Name: fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName), + } + + if err := bus.Dispatch(&cmd); err != nil { + return nil, err + } + + return &cmd.Result, nil +} + +func (a *ldapAuther) syncOrgRoles(user *m.User, ldapUser *ldapUserInfo) error { + if len(a.server.LdapGroups) == 0 { + return nil + } + + orgsQuery := m.GetUserOrgListQuery{UserId: user.Id} + if err := bus.Dispatch(&orgsQuery); err != nil { + return err + } + + // update or remove org roles + for _, org := range orgsQuery.Result { + match := false + + for _, group := range a.server.LdapGroups { + if org.OrgId != group.OrgId { + continue + } + + if ldapUser.isMemberOf(group.GroupDN) { + match = true + if org.Role != group.OrgRole { + // update role + cmd := m.UpdateOrgUserCommand{OrgId: org.OrgId, UserId: user.Id, Role: group.OrgRole} + if err := bus.Dispatch(&cmd); err != nil { + return err + } + } + // ignore subsequent ldap group mapping matches + break + } + } + + // remove role if no mappings match + if !match { + cmd := m.RemoveOrgUserCommand{OrgId: org.OrgId, UserId: user.Id} + if err := bus.Dispatch(&cmd); err != nil { + return err + } + } + } + + // add missing org roles + for _, group := range a.server.LdapGroups { + if !ldapUser.isMemberOf(group.GroupDN) { + continue + } + + match := false + for _, org := range orgsQuery.Result { + if group.OrgId == org.OrgId { + match = true + break + } + } + + if !match { + // add role + cmd := m.AddOrgUserCommand{UserId: user.Id, Role: group.OrgRole, OrgId: group.OrgId} + if err := bus.Dispatch(&cmd); err != nil { + return err + } + break + } + } + + return nil +} + +func (a *ldapAuther) secondBind(ldapUser *ldapUserInfo, userPassword string) error { + if err := a.conn.Bind(ldapUser.DN, userPassword); err != nil { + if ldapCfg.VerboseLogging { + log.Info("LDAP second bind failed, %v", err) + } + + if ldapErr, ok := err.(*ldap.Error); ok { + if ldapErr.ResultCode == 49 { + return ErrInvalidCredentials + } + } + return err + } + + return nil +} + +func (a *ldapAuther) initialBind(username, userPassword string) error { + if a.server.BindPassword != "" || a.server.BindDN == "" { + userPassword = a.server.BindPassword + a.requireSecondBind = true + } + + bindPath := a.server.BindDN + if strings.Contains(bindPath, "%s") { + bindPath = fmt.Sprintf(a.server.BindDN, username) + } + + if err := a.conn.Bind(bindPath, userPassword); err != nil { + if ldapCfg.VerboseLogging { + log.Info("LDAP initial bind failed, %v", err) + } + + if ldapErr, ok := err.(*ldap.Error); ok { + if ldapErr.ResultCode == 49 { + return ErrInvalidCredentials + } + } + return err + } + + return nil +} + +func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) { + var searchResult *ldap.SearchResult + var err error + + for _, searchBase := range a.server.SearchBaseDNs { + searchReq := ldap.SearchRequest{ + BaseDN: searchBase, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + Attributes: []string{ + a.server.Attr.Username, + a.server.Attr.Surname, + a.server.Attr.Email, + a.server.Attr.Name, + a.server.Attr.MemberOf, + }, + Filter: strings.Replace(a.server.SearchFilter, "%s", username, -1), + } + + searchResult, err = a.conn.Search(&searchReq) + if err != nil { + return nil, err + } + + if len(searchResult.Entries) > 0 { + break + } + } + + if len(searchResult.Entries) == 0 { + return nil, ErrInvalidCredentials + } + + if len(searchResult.Entries) > 1 { + return nil, errors.New("Ldap search matched more than one entry, please review your filter setting") + } + + return &ldapUserInfo{ + DN: searchResult.Entries[0].DN, + LastName: getLdapAttr(a.server.Attr.Surname, searchResult), + FirstName: getLdapAttr(a.server.Attr.Name, searchResult), + Username: getLdapAttr(a.server.Attr.Username, searchResult), + Email: getLdapAttr(a.server.Attr.Email, searchResult), + MemberOf: getLdapAttrArray(a.server.Attr.MemberOf, searchResult), + }, nil +} + +func getLdapAttr(name string, result *ldap.SearchResult) string { + for _, attr := range result.Entries[0].Attributes { + if attr.Name == name { + if len(attr.Values) > 0 { + return attr.Values[0] + } + } + } + return "" +} + +func getLdapAttrArray(name string, result *ldap.SearchResult) []string { + for _, attr := range result.Entries[0].Attributes { + if attr.Name == name { + return attr.Values + } + } + return []string{} +} + +func createUserFromLdapInfo() error { + return nil +} diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go new file mode 100644 index 00000000000..2e4ed3a32e1 --- /dev/null +++ b/pkg/login/ldap_test.go @@ -0,0 +1,261 @@ +package login + +import ( + "testing" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + . "github.com/smartystreets/goconvey/convey" +) + +func TestLdapAuther(t *testing.T) { + + Convey("When translating ldap user to grafana user", t, func() { + + Convey("Given no ldap group map match", func() { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{{}}, + }) + _, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{}) + + So(err, ShouldEqual, ErrInvalidCredentials) + }) + + var user1 = &m.User{} + + ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "*", OrgRole: "Admin"}, + }, + }) + + sc.userQueryReturns(user1) + + result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{}) + So(err, ShouldBeNil) + So(result, ShouldEqual, user1) + }) + + ldapAutherScenario("Given exact group match", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgRole: "Admin"}, + }, + }) + + sc.userQueryReturns(user1) + + result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{MemberOf: []string{"cn=users"}}) + So(err, ShouldBeNil) + So(result, ShouldEqual, user1) + }) + + ldapAutherScenario("Given no existing grafana user", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=admin", OrgRole: "Admin"}, + {GroupDN: "cn=editor", OrgRole: "Editor"}, + {GroupDN: "*", OrgRole: "Viewer"}, + }, + }) + + sc.userQueryReturns(nil) + + result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{ + Username: "torkelo", + Email: "my@email.com", + MemberOf: []string{"cn=editor"}, + }) + + So(err, ShouldBeNil) + + Convey("Should create new user", func() { + So(sc.createUserCmd.Login, ShouldEqual, "torkelo") + So(sc.createUserCmd.Email, ShouldEqual, "my@email.com") + }) + + Convey("Should return new user", func() { + So(result.Login, ShouldEqual, "torkelo") + }) + + }) + + }) + + Convey("When syncing ldap groups to grafana org roles", t, func() { + + ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgRole: "Admin"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=users"}, + }) + + Convey("Should create new org user", func() { + So(err, ShouldBeNil) + So(sc.addOrgUserCmd, ShouldNotBeNil) + So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN) + }) + }) + + ldapAutherScenario("given different current org role", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=users"}, + }) + + Convey("Should update org role", func() { + So(err, ShouldBeNil) + So(sc.updateOrgUserCmd, ShouldNotBeNil) + So(sc.updateOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN) + }) + }) + + ldapAutherScenario("given current org role is removed in ldap", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=other"}, + }) + + Convey("Should remove org role", func() { + So(err, ShouldBeNil) + So(sc.removeOrgUserCmd, ShouldNotBeNil) + }) + }) + + ldapAutherScenario("given org role is updated in config", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=admin", OrgId: 1, OrgRole: "Admin"}, + {GroupDN: "cn=users", OrgId: 1, OrgRole: "Viewer"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=users"}, + }) + + Convey("Should update org role", func() { + So(err, ShouldBeNil) + So(sc.removeOrgUserCmd, ShouldBeNil) + So(sc.updateOrgUserCmd, ShouldNotBeNil) + }) + }) + + ldapAutherScenario("given multiple matching ldap groups", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"}, + {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=admins"}, + }) + + Convey("Should take first match, and ignore subsequent matches", func() { + So(err, ShouldBeNil) + So(sc.updateOrgUserCmd, ShouldBeNil) + }) + }) + + ldapAutherScenario("given multiple matching ldap groups and no existing groups", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"}, + {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=admins"}, + }) + + Convey("Should take first match, and ignore subsequent matches", func() { + So(err, ShouldBeNil) + So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN) + }) + }) + + }) +} + +func ldapAutherScenario(desc string, fn scenarioFunc) { + Convey(desc, func() { + defer bus.ClearBusHandlers() + + sc := &scenarioContext{} + + bus.AddHandler("test", func(cmd *m.CreateUserCommand) error { + sc.createUserCmd = cmd + sc.createUserCmd.Result = m.User{Login: cmd.Login} + return nil + }) + + bus.AddHandler("test", func(cmd *m.AddOrgUserCommand) error { + sc.addOrgUserCmd = cmd + return nil + }) + + bus.AddHandler("test", func(cmd *m.UpdateOrgUserCommand) error { + sc.updateOrgUserCmd = cmd + return nil + }) + + bus.AddHandler("test", func(cmd *m.RemoveOrgUserCommand) error { + sc.removeOrgUserCmd = cmd + return nil + }) + + fn(sc) + }) +} + +type scenarioContext struct { + createUserCmd *m.CreateUserCommand + addOrgUserCmd *m.AddOrgUserCommand + updateOrgUserCmd *m.UpdateOrgUserCommand + removeOrgUserCmd *m.RemoveOrgUserCommand +} + +func (sc *scenarioContext) userQueryReturns(user *m.User) { + bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error { + if user == nil { + return m.ErrUserNotFound + } else { + query.Result = user + return nil + } + }) +} + +func (sc *scenarioContext) userOrgsQueryReturns(orgs []*m.UserOrgDTO) { + bus.AddHandler("test", func(query *m.GetUserOrgListQuery) error { + query.Result = orgs + return nil + }) +} + +type scenarioFunc func(c *scenarioContext) diff --git a/pkg/login/ldap_user.go b/pkg/login/ldap_user.go new file mode 100644 index 00000000000..5a57efe2687 --- /dev/null +++ b/pkg/login/ldap_user.go @@ -0,0 +1,23 @@ +package login + +type ldapUserInfo struct { + DN string + FirstName string + LastName string + Username string + Email string + MemberOf []string +} + +func (u *ldapUserInfo) isMemberOf(group string) bool { + if group == "*" { + return true + } + + for _, member := range u.MemberOf { + if member == group { + return true + } + } + return false +} diff --git a/pkg/login/settings.go b/pkg/login/settings.go new file mode 100644 index 00000000000..93ff04054c1 --- /dev/null +++ b/pkg/login/settings.go @@ -0,0 +1,90 @@ +package login + +import ( + "fmt" + + "github.com/BurntSushi/toml" + "github.com/grafana/grafana/pkg/log" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" +) + +type LdapConfig struct { + Servers []*LdapServerConf `toml:"servers"` + VerboseLogging bool `toml:"verbose_logging"` +} + +type LdapServerConf struct { + Host string `toml:"host"` + Port int `toml:"port"` + UseSSL bool `toml:"use_ssl"` + SkipVerifySSL bool `toml:"ssl_skip_verify"` + BindDN string `toml:"bind_dn"` + BindPassword string `toml:"bind_password"` + Attr LdapAttributeMap `toml:"attributes"` + + SearchFilter string `toml:"search_filter"` + SearchBaseDNs []string `toml:"search_base_dns"` + + LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"` +} + +type LdapAttributeMap struct { + Username string `toml:"username"` + Name string `toml:"name"` + Surname string `toml:"surname"` + Email string `toml:"email"` + MemberOf string `toml:"member_of"` +} + +type LdapGroupToOrgRole struct { + GroupDN string `toml:"group_dn"` + OrgId int64 `toml:"org_id"` + OrgRole m.RoleType `toml:"org_role"` +} + +var ldapCfg LdapConfig + +func loadLdapConfig() { + if !setting.LdapEnabled { + return + } + + log.Info("Login: Ldap enabled, reading config file: %s", setting.LdapConfigFile) + + _, err := toml.DecodeFile(setting.LdapConfigFile, &ldapCfg) + if err != nil { + log.Fatal(3, "Failed to load ldap config file: %s", err) + } + + if len(ldapCfg.Servers) == 0 { + log.Fatal(3, "ldap enabled but no ldap servers defined in config file: %s", setting.LdapConfigFile) + } + + // set default org id + for _, server := range ldapCfg.Servers { + assertNotEmptyCfg(server.SearchFilter, "search_filter") + assertNotEmptyCfg(server.SearchBaseDNs, "search_base_dns") + + for _, groupMap := range server.LdapGroups { + if groupMap.OrgId == 0 { + groupMap.OrgId = 1 + } + } + } +} + +func assertNotEmptyCfg(val interface{}, propName string) { + switch v := val.(type) { + case string: + if v == "" { + log.Fatal(3, "LDAP config file is missing option: %s", propName) + } + case []string: + if len(v) == 0 { + log.Fatal(3, "LDAP config file is missing option: %s", propName) + } + default: + fmt.Println("unknown") + } +} diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index f6dab8c8043..8e10b2428b4 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -13,13 +13,15 @@ var ( M_Api_Status_500 = NewComboCounterRef("api.status.500") M_Api_Status_404 = NewComboCounterRef("api.status.404") - M_Api_User_SignUp = NewComboCounterRef("api.user.signup") - M_Api_Dashboard_Get = NewComboCounterRef("api.dashboard.get") - M_Api_Dashboard_Post = NewComboCounterRef("api.dashboard.post") - M_Api_Admin_User_Create = NewComboCounterRef("api.admin.user_create") - M_Api_Login_Post = NewComboCounterRef("api.login.post") - M_Api_Login_OAuth = NewComboCounterRef("api.login.oauth") - M_Api_Org_Create = NewComboCounterRef("api.org.create") + M_Api_User_SignUpStarted = NewComboCounterRef("api.user.signup_started") + M_Api_User_SignUpCompleted = NewComboCounterRef("api.user.signup_completed") + M_Api_User_SignUpInvite = NewComboCounterRef("api.user.signup_invite") + M_Api_Dashboard_Get = NewComboCounterRef("api.dashboard.get") + M_Api_Dashboard_Post = NewComboCounterRef("api.dashboard.post") + M_Api_Admin_User_Create = NewComboCounterRef("api.admin.user_create") + M_Api_Login_Post = NewComboCounterRef("api.login.post") + M_Api_Login_OAuth = NewComboCounterRef("api.login.oauth") + M_Api_Org_Create = NewComboCounterRef("api.org.create") M_Api_Dashboard_Snapshot_Create = NewComboCounterRef("api.dashboard_snapshot.create") M_Api_Dashboard_Snapshot_External = NewComboCounterRef("api.dashboard_snapshot.external") diff --git a/pkg/middleware/auth.go b/pkg/middleware/auth.go index 9fb09a5c395..2497183b356 100644 --- a/pkg/middleware/auth.go +++ b/pkg/middleware/auth.go @@ -36,9 +36,19 @@ func getApiKey(c *Context) string { return "" } -func authDenied(c *Context) { +func accessForbidden(c *Context) { if c.IsApiRequest() { - c.JsonApiErr(401, "Access denied", nil) + c.JsonApiErr(403, "Permission denied", nil) + return + } + + c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubUrl+c.Req.RequestURI), 0, setting.AppSubUrl+"/") + c.Redirect(setting.AppSubUrl + "/login") +} + +func notAuthorized(c *Context) { + if c.IsApiRequest() { + c.JsonApiErr(401, "Unauthorized", nil) return } @@ -56,20 +66,20 @@ func RoleAuth(roles ...m.RoleType) macaron.Handler { } } if !ok { - authDenied(c) + accessForbidden(c) } } } func Auth(options *AuthOptions) macaron.Handler { return func(c *Context) { - if !c.IsGrafanaAdmin && options.ReqGrafanaAdmin { - authDenied(c) + if !c.IsSignedIn && options.ReqSignedIn && !c.AllowAnonymous { + notAuthorized(c) return } - if !c.IsSignedIn && options.ReqSignedIn && !c.AllowAnonymous { - authDenied(c) + if !c.IsGrafanaAdmin && options.ReqGrafanaAdmin { + accessForbidden(c) return } } diff --git a/pkg/middleware/auth_proxy.go b/pkg/middleware/auth_proxy.go index 2529fef67a9..d5218b09dda 100644 --- a/pkg/middleware/auth_proxy.go +++ b/pkg/middleware/auth_proxy.go @@ -2,6 +2,7 @@ package middleware import ( "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) @@ -39,8 +40,16 @@ func initContextWithAuthProxy(ctx *Context) bool { } } + // initialize session + if err := ctx.Session.Start(ctx); err != nil { + log.Error(3, "Failed to start session", err) + return false + } + ctx.SignedInUser = query.Result ctx.IsSignedIn = true + ctx.Session.Set(SESS_KEY_USERID, ctx.UserId) + return true } diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 8704ec5a787..8a61bdb3a0b 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -1,6 +1,7 @@ package middleware import ( + "fmt" "strconv" "strings" @@ -253,3 +254,96 @@ func (ctx *Context) JsonApiErr(status int, message string, err error) { ctx.JSON(status, resp) } + +func Quota(target string) macaron.Handler { + return func(c *Context) { + limitReached, err := QuotaReached(c, target) + if err != nil { + c.JsonApiErr(500, "failed to get quota", err) + return + } + if limitReached { + c.JsonApiErr(403, fmt.Sprintf("%s Quota reached", target), nil) + return + } + } +} + +func QuotaReached(c *Context, target string) (bool, error) { + if !setting.Quota.Enabled { + return false, nil + } + + // get the list of scopes that this target is valid for. Org, User, Global + scopes, err := m.GetQuotaScopes(target) + if err != nil { + return false, err + } + log.Info(fmt.Sprintf("checking quota for %s in scopes %v", target, scopes)) + + for _, scope := range scopes { + log.Info(fmt.Sprintf("checking scope %s", scope.Name)) + switch scope.Name { + case "global": + if scope.DefaultLimit < 0 { + continue + } + if scope.DefaultLimit == 0 { + return true, nil + } + if target == "session" { + usedSessions := sessionManager.Count() + if int64(usedSessions) > scope.DefaultLimit { + log.Info(fmt.Sprintf("%d sessions active, limit is %d", usedSessions, scope.DefaultLimit)) + return true, nil + } + continue + } + query := m.GetGlobalQuotaByTargetQuery{Target: scope.Target} + if err := bus.Dispatch(&query); err != nil { + return true, err + } + if query.Result.Used >= scope.DefaultLimit { + return true, nil + } + case "org": + if !c.IsSignedIn { + continue + } + query := m.GetOrgQuotaByTargetQuery{OrgId: c.OrgId, Target: scope.Target, Default: scope.DefaultLimit} + if err := bus.Dispatch(&query); err != nil { + return true, err + } + if query.Result.Limit < 0 { + continue + } + if query.Result.Limit == 0 { + return true, nil + } + + if query.Result.Used >= query.Result.Limit { + return true, nil + } + case "user": + if !c.IsSignedIn || c.UserId == 0 { + continue + } + query := m.GetUserQuotaByTargetQuery{UserId: c.UserId, Target: scope.Target, Default: scope.DefaultLimit} + if err := bus.Dispatch(&query); err != nil { + return true, err + } + if query.Result.Limit < 0 { + continue + } + if query.Result.Limit == 0 { + return true, nil + } + + if query.Result.Used >= query.Result.Limit { + return true, nil + } + } + } + + return false, nil +} diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go new file mode 100644 index 00000000000..ae6b414b248 --- /dev/null +++ b/pkg/middleware/quota_test.go @@ -0,0 +1,144 @@ +package middleware + +import ( + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" + "testing" +) + +func TestMiddlewareQuota(t *testing.T) { + + Convey("Given the grafana quota middleware", t, func() { + setting.Quota = setting.QuotaSettings{ + Enabled: true, + Org: &setting.OrgQuota{ + User: 5, + Dashboard: 5, + DataSource: 5, + ApiKey: 5, + }, + User: &setting.UserQuota{ + Org: 5, + }, + Global: &setting.GlobalQuota{ + Org: 5, + User: 5, + Dashboard: 5, + DataSource: 5, + ApiKey: 5, + Session: 5, + }, + } + + middlewareScenario("with user not logged in", func(sc *scenarioContext) { + bus.AddHandler("globalQuota", func(query *m.GetGlobalQuotaByTargetQuery) error { + query.Result = &m.GlobalQuotaDTO{ + Target: query.Target, + Limit: query.Default, + Used: 4, + } + return nil + }) + Convey("global quota not reached", func() { + sc.m.Get("/user", Quota("user"), sc.defaultHandler) + sc.fakeReq("GET", "/user").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + Convey("global quota reached", func() { + setting.Quota.Global.User = 4 + sc.m.Get("/user", Quota("user"), sc.defaultHandler) + sc.fakeReq("GET", "/user").exec() + So(sc.resp.Code, ShouldEqual, 403) + }) + Convey("global session quota not reached", func() { + setting.Quota.Global.Session = 10 + sc.m.Get("/user", Quota("session"), sc.defaultHandler) + sc.fakeReq("GET", "/user").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + Convey("global session quota reached", func() { + setting.Quota.Global.Session = 1 + sc.m.Get("/user", Quota("session"), sc.defaultHandler) + sc.fakeReq("GET", "/user").exec() + So(sc.resp.Code, ShouldEqual, 403) + }) + }) + + middlewareScenario("with user logged in", func(sc *scenarioContext) { + // log us in, so we have a user_id and org_id in the context + sc.fakeReq("GET", "/").handler(func(c *Context) { + c.Session.Set(SESS_KEY_USERID, int64(12)) + }).exec() + + bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { + query.Result = &m.SignedInUser{OrgId: 2, UserId: 12} + return nil + }) + bus.AddHandler("globalQuota", func(query *m.GetGlobalQuotaByTargetQuery) error { + query.Result = &m.GlobalQuotaDTO{ + Target: query.Target, + Limit: query.Default, + Used: 4, + } + return nil + }) + bus.AddHandler("userQuota", func(query *m.GetUserQuotaByTargetQuery) error { + query.Result = &m.UserQuotaDTO{ + Target: query.Target, + Limit: query.Default, + Used: 4, + } + return nil + }) + bus.AddHandler("orgQuota", func(query *m.GetOrgQuotaByTargetQuery) error { + query.Result = &m.OrgQuotaDTO{ + Target: query.Target, + Limit: query.Default, + Used: 4, + } + return nil + }) + Convey("global datasource quota reached", func() { + setting.Quota.Global.DataSource = 4 + sc.m.Get("/ds", Quota("data_source"), sc.defaultHandler) + sc.fakeReq("GET", "/ds").exec() + So(sc.resp.Code, ShouldEqual, 403) + }) + Convey("user Org quota not reached", func() { + setting.Quota.User.Org = 5 + sc.m.Get("/org", Quota("org"), sc.defaultHandler) + sc.fakeReq("GET", "/org").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + Convey("user Org quota reached", func() { + setting.Quota.User.Org = 4 + sc.m.Get("/org", Quota("org"), sc.defaultHandler) + sc.fakeReq("GET", "/org").exec() + So(sc.resp.Code, ShouldEqual, 403) + }) + Convey("org dashboard quota not reached", func() { + setting.Quota.Org.Dashboard = 10 + sc.m.Get("/dashboard", Quota("dashboard"), sc.defaultHandler) + sc.fakeReq("GET", "/dashboard").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + Convey("org dashboard quota reached", func() { + setting.Quota.Org.Dashboard = 4 + sc.m.Get("/dashboard", Quota("dashboard"), sc.defaultHandler) + sc.fakeReq("GET", "/dashboard").exec() + So(sc.resp.Code, ShouldEqual, 403) + }) + Convey("org dashboard quota reached but quotas disabled", func() { + setting.Quota.Org.Dashboard = 4 + setting.Quota.Enabled = false + sc.m.Get("/dashboard", Quota("dashboard"), sc.defaultHandler) + sc.fakeReq("GET", "/dashboard").exec() + So(sc.resp.Code, ShouldEqual, 200) + }) + + }) + + }) +} diff --git a/pkg/models/address.go b/pkg/models/address.go new file mode 100644 index 00000000000..0cf8aaf7ddb --- /dev/null +++ b/pkg/models/address.go @@ -0,0 +1,10 @@ +package models + +type Address struct { + Address1 string `json:"address1"` + Address2 string `json:"address2"` + City string `json:"city"` + ZipCode string `json:"zipCode"` + State string `json:"state"` + Country string `json:"country"` +} diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index c756faaba59..75e2134c09f 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -11,6 +11,7 @@ const ( DS_INFLUXDB_08 = "influxdb_08" DS_ES = "elasticsearch" DS_OPENTSDB = "opentsdb" + DS_CLOUDWATCH = "cloudwatch" DS_ACCESS_DIRECT = "direct" DS_ACCESS_PROXY = "proxy" ) diff --git a/pkg/models/org.go b/pkg/models/org.go index b2d18be9537..2a9fa8efa04 100644 --- a/pkg/models/org.go +++ b/pkg/models/org.go @@ -7,13 +7,22 @@ import ( // Typed errors var ( - ErrOrgNotFound = errors.New("Organization not found") + ErrOrgNotFound = errors.New("Organization not found") + ErrOrgNameTaken = errors.New("Organization name is taken") ) type Org struct { Id int64 Version int Name string + + Address1 string + Address2 string + City string + ZipCode string + State string + Country string + Created time.Time Updated time.Time } @@ -34,8 +43,13 @@ type DeleteOrgCommand struct { } type UpdateOrgCommand struct { - Name string `json:"name" binding:"Required"` - OrgId int64 `json:"-"` + Name string + OrgId int64 +} + +type UpdateOrgAddressCommand struct { + OrgId int64 + Address } type GetOrgByIdQuery struct { @@ -62,6 +76,12 @@ type OrgDTO struct { Name string `json:"name"` } +type OrgDetailsDTO struct { + Id int64 `json:"id"` + Name string `json:"name"` + Address Address `json:"address"` +} + type UserOrgDTO struct { OrgId int64 `json:"orgId"` Name string `json:"name"` diff --git a/pkg/models/org_user.go b/pkg/models/org_user.go index afbb10386c8..f456fa95373 100644 --- a/pkg/models/org_user.go +++ b/pkg/models/org_user.go @@ -7,9 +7,10 @@ import ( // Typed errors var ( - ErrInvalidRoleType = errors.New("Invalid role type") - ErrLastOrgAdmin = errors.New("Cannot remove last organization admin") - ErrOrgUserNotFound = errors.New("Cannot find the organization user") + ErrInvalidRoleType = errors.New("Invalid role type") + ErrLastOrgAdmin = errors.New("Cannot remove last organization admin") + ErrOrgUserNotFound = errors.New("Cannot find the organization user") + ErrOrgUserAlreadyAdded = errors.New("User is already added to organization") ) type RoleType string diff --git a/pkg/models/quotas.go b/pkg/models/quotas.go new file mode 100644 index 00000000000..85159d830d1 --- /dev/null +++ b/pkg/models/quotas.go @@ -0,0 +1,130 @@ +package models + +import ( + "errors" + "github.com/grafana/grafana/pkg/setting" + "time" +) + +var ErrInvalidQuotaTarget = errors.New("Invalid quota target") + +type Quota struct { + Id int64 + OrgId int64 + UserId int64 + Target string + Limit int64 + Created time.Time + Updated time.Time +} + +type QuotaScope struct { + Name string + Target string + DefaultLimit int64 +} + +type OrgQuotaDTO struct { + OrgId int64 `json:"org_id"` + Target string `json:"target"` + Limit int64 `json:"limit"` + Used int64 `json:"used"` +} + +type UserQuotaDTO struct { + UserId int64 `json:"user_id"` + Target string `json:"target"` + Limit int64 `json:"limit"` + Used int64 `json:"used"` +} + +type GlobalQuotaDTO struct { + Target string `json:"target"` + Limit int64 `json:"limit"` + Used int64 `json:"used"` +} + +type GetOrgQuotaByTargetQuery struct { + Target string + OrgId int64 + Default int64 + Result *OrgQuotaDTO +} + +type GetOrgQuotasQuery struct { + OrgId int64 + Result []*OrgQuotaDTO +} + +type GetUserQuotaByTargetQuery struct { + Target string + UserId int64 + Default int64 + Result *UserQuotaDTO +} + +type GetUserQuotasQuery struct { + UserId int64 + Result []*UserQuotaDTO +} + +type GetGlobalQuotaByTargetQuery struct { + Target string + Default int64 + Result *GlobalQuotaDTO +} + +type UpdateOrgQuotaCmd struct { + Target string `json:"target"` + Limit int64 `json:"limit"` + OrgId int64 `json:"-"` +} + +type UpdateUserQuotaCmd struct { + Target string `json:"target"` + Limit int64 `json:"limit"` + UserId int64 `json:"-"` +} + +func GetQuotaScopes(target string) ([]QuotaScope, error) { + scopes := make([]QuotaScope, 0) + switch target { + case "user": + scopes = append(scopes, + QuotaScope{Name: "global", Target: target, DefaultLimit: setting.Quota.Global.User}, + QuotaScope{Name: "org", Target: "org_user", DefaultLimit: setting.Quota.Org.User}, + ) + return scopes, nil + case "org": + scopes = append(scopes, + QuotaScope{Name: "global", Target: target, DefaultLimit: setting.Quota.Global.Org}, + QuotaScope{Name: "user", Target: "org_user", DefaultLimit: setting.Quota.User.Org}, + ) + return scopes, nil + case "dashboard": + scopes = append(scopes, + QuotaScope{Name: "global", Target: target, DefaultLimit: setting.Quota.Global.Dashboard}, + QuotaScope{Name: "org", Target: target, DefaultLimit: setting.Quota.Org.Dashboard}, + ) + return scopes, nil + case "data_source": + scopes = append(scopes, + QuotaScope{Name: "global", Target: target, DefaultLimit: setting.Quota.Global.DataSource}, + QuotaScope{Name: "org", Target: target, DefaultLimit: setting.Quota.Org.DataSource}, + ) + return scopes, nil + case "api_key": + scopes = append(scopes, + QuotaScope{Name: "global", Target: target, DefaultLimit: setting.Quota.Global.ApiKey}, + QuotaScope{Name: "org", Target: target, DefaultLimit: setting.Quota.Org.ApiKey}, + ) + return scopes, nil + case "session": + scopes = append(scopes, + QuotaScope{Name: "global", Target: target, DefaultLimit: setting.Quota.Global.Session}, + ) + return scopes, nil + default: + return scopes, ErrInvalidQuotaTarget + } +} diff --git a/pkg/models/temp_user.go b/pkg/models/temp_user.go new file mode 100644 index 00000000000..00c496c4844 --- /dev/null +++ b/pkg/models/temp_user.go @@ -0,0 +1,92 @@ +package models + +import ( + "errors" + "time" +) + +// Typed errors +var ( + ErrTempUserNotFound = errors.New("User not found") +) + +type TempUserStatus string + +const ( + TmpUserSignUpStarted TempUserStatus = "SignUpStarted" + TmpUserInvitePending TempUserStatus = "InvitePending" + TmpUserCompleted TempUserStatus = "Completed" + TmpUserRevoked TempUserStatus = "Revoked" +) + +// TempUser holds data for org invites and unconfirmed sign ups +type TempUser struct { + Id int64 + OrgId int64 + Version int + Email string + Name string + Role RoleType + InvitedByUserId int64 + Status TempUserStatus + + EmailSent bool + EmailSentOn time.Time + Code string + RemoteAddr string + + Created time.Time + Updated time.Time +} + +// --------------------- +// COMMANDS + +type CreateTempUserCommand struct { + Email string + Name string + OrgId int64 + InvitedByUserId int64 + Status TempUserStatus + Code string + Role RoleType + RemoteAddr string + + Result *TempUser +} + +type UpdateTempUserStatusCommand struct { + Code string + Status TempUserStatus +} + +type GetTempUsersQuery struct { + OrgId int64 + Email string + Status TempUserStatus + + Result []*TempUserDTO +} + +type GetTempUserByCodeQuery struct { + Code string + + Result *TempUserDTO +} + +type TempUserDTO struct { + Id int64 `json:"id"` + OrgId int64 `json:"orgId"` + Name string `json:"name"` + Email string `json:"email"` + Role RoleType `json:"role"` + InvitedByLogin string `json:"invitedByLogin"` + InvitedByEmail string `json:"invitedByEmail"` + InvitedByName string `json:"invitedByName"` + Code string `json:"code"` + Status TempUserStatus `json:"status"` + Url string `json:"url"` + EmailSent bool `json:"emailSent"` + EmailSentOn time.Time `json:"emailSentOn"` + Created time.Time `json:"createdOn"` +} diff --git a/pkg/models/user.go b/pkg/models/user.go index bf697676b32..64067d59654 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -44,14 +44,17 @@ func (u *User) NameOrFallback() string { // COMMANDS type CreateUserCommand struct { - Email string `json:"email" binding:"Required"` - Login string `json:"login"` - Name string `json:"name"` - Company string `json:"compay"` - Password string `json:"password" binding:"Required"` - IsAdmin bool `json:"-"` + Email string + Login string + Name string + Company string + OrgName string + Password string + EmailVerified bool + IsAdmin bool + SkipOrgSetup bool - Result User `json:"-"` + Result User } type UpdateUserCommand struct { diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go index 401cd812d5e..63ce7219618 100644 --- a/pkg/services/notifications/notifications.go +++ b/pkg/services/notifications/notifications.go @@ -3,7 +3,9 @@ package notifications import ( "bytes" "errors" + "fmt" "html/template" + "net/url" "path/filepath" "github.com/grafana/grafana/pkg/bus" @@ -16,6 +18,7 @@ import ( var mailTemplates *template.Template var tmplResetPassword = "reset_password.html" +var tmplSignUpStarted = "signup_started.html" var tmplWelcomeOnSignUp = "welcome_on_signup.html" func Init() error { @@ -25,7 +28,8 @@ func Init() error { bus.AddHandler("email", validateResetPasswordCode) bus.AddHandler("email", sendEmailCommandHandler) - bus.AddEventListener(userSignedUpHandler) + bus.AddEventListener(signUpStartedHandler) + bus.AddEventListener(signUpCompletedHandler) mailTemplates = template.New("name") mailTemplates.Funcs(template.FuncMap{ @@ -60,16 +64,28 @@ func sendEmailCommandHandler(cmd *m.SendEmailCommand) error { } var buffer bytes.Buffer + var err error + var subjectText interface{} + data := cmd.Data if data == nil { data = make(map[string]interface{}, 10) } setDefaultTemplateData(data, nil) - mailTemplates.ExecuteTemplate(&buffer, cmd.Template, data) + err = mailTemplates.ExecuteTemplate(&buffer, cmd.Template, data) + if err != nil { + return err + } - subjectTmplText := data["Subject"].(map[string]interface{})["value"].(string) - subjectTmpl, err := template.New("subject").Parse(subjectTmplText) + subjectData := data["Subject"].(map[string]interface{}) + subjectText, hasSubject := subjectData["value"] + + if !hasSubject { + return errors.New(fmt.Sprintf("Missing subject in Template %s", cmd.Template)) + } + + subjectTmpl, err := template.New("subject").Parse(subjectText.(string)) if err != nil { return err } @@ -120,9 +136,29 @@ func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error { return nil } -func userSignedUpHandler(evt *events.UserSignedUp) error { - log.Info("User signed up: %s, send_option: %s", evt.Email, setting.Smtp.SendWelcomeEmailOnSignUp) +func signUpStartedHandler(evt *events.SignUpStarted) error { + if !setting.VerifyEmailEnabled { + return nil + } + log.Info("User signup started: %s", evt.Email) + + if evt.Email == "" { + return nil + } + + return sendEmailCommandHandler(&m.SendEmailCommand{ + To: []string{evt.Email}, + Template: tmplSignUpStarted, + Data: map[string]interface{}{ + "Email": evt.Email, + "Code": evt.Code, + "SignUpUrl": setting.ToAbsUrl(fmt.Sprintf("signup/?email=%s&code=%s", url.QueryEscape(evt.Email), url.QueryEscape(evt.Code))), + }, + }) +} + +func signUpCompletedHandler(evt *events.SignUpCompleted) error { if evt.Email == "" || !setting.Smtp.SendWelcomeEmailOnSignUp { return nil } @@ -131,7 +167,7 @@ func userSignedUpHandler(evt *events.UserSignedUp) error { To: []string{evt.Email}, Template: tmplWelcomeOnSignUp, Data: map[string]interface{}{ - "Name": evt.Login, + "Name": evt.Name, }, }) } diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 7fdaace316e..bbec541589c 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -146,7 +146,7 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error { } if len(query.Title) > 0 { - sql.WriteString(" AND dashboard.title LIKE ?") + sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?") params = append(params, "%"+query.Title+"%") } @@ -198,11 +198,28 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error { } func DeleteDashboard(cmd *m.DeleteDashboardCommand) error { - sess := x.NewSession() - defer sess.Close() + return inTransaction2(func(sess *session) error { + dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId} + has, err := x.Get(&dashboard) + if err != nil { + return err + } else if has == false { + return m.ErrDashboardNotFound + } - rawSql := "DELETE FROM dashboard WHERE org_id=? and slug=?" - _, err := sess.Exec(rawSql, cmd.OrgId, cmd.Slug) + deletes := []string{ + "DELETE FROM dashboard_tag WHERE dashboard_id = ? ", + "DELETE FROM star WHERE dashboard_id = ? ", + "DELETE FROM dashboard WHERE id = ?", + } - return err + for _, sql := range deletes { + _, err := sess.Exec(sql, dashboard.Id) + if err != nil { + return err + } + } + + return nil + }) } diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 329c6187c9d..8f7054d3959 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -10,12 +10,14 @@ import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" func AddMigrations(mg *Migrator) { addMigrationLogMigrations(mg) addUserMigrations(mg) + addTempUserMigrations(mg) addStarMigrations(mg) addOrgMigrations(mg) addDashboardMigration(mg) addDataSourceMigration(mg) addApiKeyMigrations(mg) addDashboardSnapshotMigrations(mg) + addQuotaMigration(mg) } func addMigrationLogMigrations(mg *Migrator) { diff --git a/pkg/services/sqlstore/migrations/quota_mig.go b/pkg/services/sqlstore/migrations/quota_mig.go new file mode 100644 index 00000000000..d877bb4c3c1 --- /dev/null +++ b/pkg/services/sqlstore/migrations/quota_mig.go @@ -0,0 +1,28 @@ +package migrations + +import ( + . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +func addQuotaMigration(mg *Migrator) { + + var quotaV1 = Table{ + Name: "quota", + Columns: []*Column{ + {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, + {Name: "org_id", Type: DB_BigInt, Nullable: true}, + {Name: "user_id", Type: DB_BigInt, Nullable: true}, + {Name: "target", Type: DB_NVarchar, Length: 255, Nullable: false}, + {Name: "limit", Type: DB_BigInt, Nullable: false}, + {Name: "created", Type: DB_DateTime, Nullable: false}, + {Name: "updated", Type: DB_DateTime, Nullable: false}, + }, + Indices: []*Index{ + {Cols: []string{"org_id", "user_id", "target"}, Type: UniqueIndex}, + }, + } + mg.AddMigration("create quota table v1", NewAddTableMigration(quotaV1)) + + //------- indexes ------------------ + addTableIndicesMigrations(mg, "v1", quotaV1) +} diff --git a/pkg/services/sqlstore/migrations/temp_user.go b/pkg/services/sqlstore/migrations/temp_user.go new file mode 100644 index 00000000000..3cae5d82472 --- /dev/null +++ b/pkg/services/sqlstore/migrations/temp_user.go @@ -0,0 +1,38 @@ +package migrations + +import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + +func addTempUserMigrations(mg *Migrator) { + tempUserV1 := Table{ + Name: "temp_user", + Columns: []*Column{ + {Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true}, + {Name: "org_id", Type: DB_BigInt, Nullable: false}, + {Name: "version", Type: DB_Int, Nullable: false}, + {Name: "email", Type: DB_NVarchar, Length: 255}, + {Name: "name", Type: DB_NVarchar, Length: 255, Nullable: true}, + {Name: "role", Type: DB_NVarchar, Length: 20, Nullable: true}, + {Name: "code", Type: DB_NVarchar, Length: 255}, + {Name: "status", Type: DB_Varchar, Length: 20}, + {Name: "invited_by_user_id", Type: DB_BigInt, Nullable: true}, + {Name: "email_sent", Type: DB_Bool}, + {Name: "email_sent_on", Type: DB_DateTime, Nullable: true}, + {Name: "remote_addr", Type: DB_Varchar, Length: 255, Nullable: true}, + {Name: "created", Type: DB_DateTime}, + {Name: "updated", Type: DB_DateTime}, + }, + Indices: []*Index{ + {Cols: []string{"email"}, Type: IndexType}, + {Cols: []string{"org_id"}, Type: IndexType}, + {Cols: []string{"code"}, Type: IndexType}, + {Cols: []string{"status"}, Type: IndexType}, + }, + } + + // addDropAllIndicesMigrations(mg, "v7", tempUserV1) + // mg.AddMigration("Drop old table tempUser v7", NewDropTableMigration("temp_user")) + + // create table + mg.AddMigration("create temp user table v1-7", NewAddTableMigration(tempUserV1)) + addTableIndicesMigrations(mg, "v1-7", tempUserV1) +} diff --git a/pkg/services/sqlstore/migrator/dialect.go b/pkg/services/sqlstore/migrator/dialect.go index 9e3c3a11e16..f2fffbb16b3 100644 --- a/pkg/services/sqlstore/migrator/dialect.go +++ b/pkg/services/sqlstore/migrator/dialect.go @@ -16,6 +16,7 @@ type Dialect interface { ShowCreateNull() bool SqlType(col *Column) string SupportEngine() bool + LikeStr() string CreateIndexSql(tableName string, index *Index) string CreateTableSql(table *Table) string @@ -58,6 +59,10 @@ func (b *BaseDialect) AndStr() string { return "AND" } +func (b *BaseDialect) LikeStr() string { + return "LIKE" +} + func (b *BaseDialect) OrStr() string { return "OR" } diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index 2461d9dab0c..48000e34ca2 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -100,6 +100,7 @@ func (mg *Migrator) Start() error { } if err := mg.exec(m); err != nil { + log.Error(3, "Migrator: error: \n%s:\n%s", err, sql) record.Error = err.Error() mg.x.Insert(&record) return err diff --git a/pkg/services/sqlstore/migrator/postgres_dialect.go b/pkg/services/sqlstore/migrator/postgres_dialect.go index 64c6772c2d1..3ab13f49b49 100644 --- a/pkg/services/sqlstore/migrator/postgres_dialect.go +++ b/pkg/services/sqlstore/migrator/postgres_dialect.go @@ -28,6 +28,10 @@ func (db *Postgres) QuoteStr() string { return "\"" } +func (b *Postgres) LikeStr() string { + return "ILIKE" +} + func (db *Postgres) AutoIncrStr() string { return "" } diff --git a/pkg/services/sqlstore/org.go b/pkg/services/sqlstore/org.go index 725a21d7fad..6ac171127c9 100644 --- a/pkg/services/sqlstore/org.go +++ b/pkg/services/sqlstore/org.go @@ -5,7 +5,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/events" - "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" ) @@ -13,6 +12,7 @@ func init() { bus.AddHandler("sql", GetOrgById) bus.AddHandler("sql", CreateOrg) bus.AddHandler("sql", UpdateOrg) + bus.AddHandler("sql", UpdateOrgAddress) bus.AddHandler("sql", GetOrgByName) bus.AddHandler("sql", SearchOrgs) bus.AddHandler("sql", DeleteOrg) @@ -63,9 +63,31 @@ func GetOrgByName(query *m.GetOrgByNameQuery) error { return nil } +func isOrgNameTaken(name string, existingId int64, sess *session) (bool, error) { + // check if org name is taken + var org m.Org + exists, err := sess.Where("name=?", name).Get(&org) + + if err != nil { + return false, nil + } + + if exists && existingId != org.Id { + return true, nil + } + + return false, nil +} + func CreateOrg(cmd *m.CreateOrgCommand) error { return inTransaction2(func(sess *session) error { + if isNameTaken, err := isOrgNameTaken(cmd.Name, 0, sess); err != nil { + return err + } else if isNameTaken { + return m.ErrOrgNameTaken + } + org := m.Org{ Name: cmd.Name, Created: time.Now(), @@ -100,6 +122,12 @@ func CreateOrg(cmd *m.CreateOrgCommand) error { func UpdateOrg(cmd *m.UpdateOrgCommand) error { return inTransaction2(func(sess *session) error { + if isNameTaken, err := isOrgNameTaken(cmd.Name, cmd.OrgId, sess); err != nil { + return err + } else if isNameTaken { + return m.ErrOrgNameTaken + } + org := m.Org{ Name: cmd.Name, Updated: time.Now(), @@ -119,21 +147,48 @@ func UpdateOrg(cmd *m.UpdateOrgCommand) error { }) } +func UpdateOrgAddress(cmd *m.UpdateOrgAddressCommand) error { + return inTransaction2(func(sess *session) error { + org := m.Org{ + Address1: cmd.Address1, + Address2: cmd.Address2, + City: cmd.City, + ZipCode: cmd.ZipCode, + State: cmd.State, + Country: cmd.Country, + + Updated: time.Now(), + } + + if _, err := sess.Id(cmd.OrgId).Update(&org); err != nil { + return err + } + + sess.publishAfterCommit(&events.OrgUpdated{ + Timestamp: org.Updated, + Id: org.Id, + Name: org.Name, + }) + + return nil + }) +} + func DeleteOrg(cmd *m.DeleteOrgCommand) error { return inTransaction2(func(sess *session) error { deletes := []string{ - "DELETE FROM star WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ?)", - "DELETE FROM dashboard_tag WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ?)", + "DELETE FROM star WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ? AND star.dashboard_id = dashboard.id)", + "DELETE FROM dashboard_tag WHERE EXISTS (SELECT 1 FROM dashboard WHERE org_id = ? AND dashboard_tag.dashboard_id = dashboard.id)", "DELETE FROM dashboard WHERE org_id = ?", "DELETE FROM api_key WHERE org_id = ?", "DELETE FROM data_source WHERE org_id = ?", "DELETE FROM org_user WHERE org_id = ?", "DELETE FROM org WHERE id = ?", + "DELETE FROM temp_user WHERE org_id = ?", } for _, sql := range deletes { - log.Trace(sql) _, err := sess.Exec(sql, cmd.Id) if err != nil { return err diff --git a/pkg/services/sqlstore/org_users.go b/pkg/services/sqlstore/org_users.go index 2e8fc40cb7c..fdd671d0bfe 100644 --- a/pkg/services/sqlstore/org_users.go +++ b/pkg/services/sqlstore/org_users.go @@ -19,6 +19,12 @@ func init() { func AddOrgUser(cmd *m.AddOrgUserCommand) error { return inTransaction(func(sess *xorm.Session) error { + // check if user exists + if res, err := sess.Query("SELECT 1 from org_user WHERE org_id=? and user_id=?", cmd.OrgId, cmd.UserId); err != nil { + return err + } else if len(res) == 1 { + return m.ErrOrgUserAlreadyAdded + } entity := m.OrgUser{ OrgId: cmd.OrgId, diff --git a/pkg/services/sqlstore/quota.go b/pkg/services/sqlstore/quota.go new file mode 100644 index 00000000000..53ea8889c56 --- /dev/null +++ b/pkg/services/sqlstore/quota.go @@ -0,0 +1,239 @@ +package sqlstore + +import ( + "fmt" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" +) + +func init() { + bus.AddHandler("sql", GetOrgQuotaByTarget) + bus.AddHandler("sql", GetOrgQuotas) + bus.AddHandler("sql", UpdateOrgQuota) + bus.AddHandler("sql", GetUserQuotaByTarget) + bus.AddHandler("sql", GetUserQuotas) + bus.AddHandler("sql", UpdateUserQuota) + bus.AddHandler("sql", GetGlobalQuotaByTarget) +} + +type targetCount struct { + Count int64 +} + +func GetOrgQuotaByTarget(query *m.GetOrgQuotaByTargetQuery) error { + quota := m.Quota{ + Target: query.Target, + OrgId: query.OrgId, + } + has, err := x.Get("a) + if err != nil { + return err + } else if has == false { + quota.Limit = query.Default + } + + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(query.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql, query.OrgId).Find(&resp); err != nil { + return err + } + + query.Result = &m.OrgQuotaDTO{ + Target: query.Target, + Limit: quota.Limit, + OrgId: query.OrgId, + Used: resp[0].Count, + } + + return nil +} + +func GetOrgQuotas(query *m.GetOrgQuotasQuery) error { + quotas := make([]*m.Quota, 0) + sess := x.Table("quota") + if err := sess.Where("org_id=? AND user_id=0", query.OrgId).Find("as); err != nil { + return err + } + + defaultQuotas := setting.Quota.Org.ToMap() + + seenTargets := make(map[string]bool) + for _, q := range quotas { + seenTargets[q.Target] = true + } + + for t, v := range defaultQuotas { + if _, ok := seenTargets[t]; !ok { + quotas = append(quotas, &m.Quota{ + OrgId: query.OrgId, + Target: t, + Limit: v, + }) + } + } + + result := make([]*m.OrgQuotaDTO, len(quotas)) + for i, q := range quotas { + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where org_id=?", dialect.Quote(q.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql, q.OrgId).Find(&resp); err != nil { + return err + } + result[i] = &m.OrgQuotaDTO{ + Target: q.Target, + Limit: q.Limit, + OrgId: q.OrgId, + Used: resp[0].Count, + } + } + query.Result = result + return nil +} + +func UpdateOrgQuota(cmd *m.UpdateOrgQuotaCmd) error { + return inTransaction2(func(sess *session) error { + //Check if quota is already defined in the DB + quota := m.Quota{ + Target: cmd.Target, + OrgId: cmd.OrgId, + } + has, err := sess.Get("a) + if err != nil { + return err + } + quota.Limit = cmd.Limit + if has == false { + //No quota in the DB for this target, so create a new one. + if _, err := sess.Insert("a); err != nil { + return err + } + } else { + //update existing quota entry in the DB. + if _, err := sess.Id(quota.Id).Update("a); err != nil { + return err + } + } + + return nil + }) +} + +func GetUserQuotaByTarget(query *m.GetUserQuotaByTargetQuery) error { + quota := m.Quota{ + Target: query.Target, + UserId: query.UserId, + } + has, err := x.Get("a) + if err != nil { + return err + } else if has == false { + quota.Limit = query.Default + } + + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(query.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql, query.UserId).Find(&resp); err != nil { + return err + } + + query.Result = &m.UserQuotaDTO{ + Target: query.Target, + Limit: quota.Limit, + UserId: query.UserId, + Used: resp[0].Count, + } + + return nil +} + +func GetUserQuotas(query *m.GetUserQuotasQuery) error { + quotas := make([]*m.Quota, 0) + sess := x.Table("quota") + if err := sess.Where("user_id=? AND org_id=0", query.UserId).Find("as); err != nil { + return err + } + + defaultQuotas := setting.Quota.User.ToMap() + + seenTargets := make(map[string]bool) + for _, q := range quotas { + seenTargets[q.Target] = true + } + + for t, v := range defaultQuotas { + if _, ok := seenTargets[t]; !ok { + quotas = append(quotas, &m.Quota{ + UserId: query.UserId, + Target: t, + Limit: v, + }) + } + } + + result := make([]*m.UserQuotaDTO, len(quotas)) + for i, q := range quotas { + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s where user_id=?", dialect.Quote(q.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql, q.UserId).Find(&resp); err != nil { + return err + } + result[i] = &m.UserQuotaDTO{ + Target: q.Target, + Limit: q.Limit, + UserId: q.UserId, + Used: resp[0].Count, + } + } + query.Result = result + return nil +} + +func UpdateUserQuota(cmd *m.UpdateUserQuotaCmd) error { + return inTransaction2(func(sess *session) error { + //Check if quota is already defined in the DB + quota := m.Quota{ + Target: cmd.Target, + UserId: cmd.UserId, + } + has, err := sess.Get("a) + if err != nil { + return err + } + quota.Limit = cmd.Limit + if has == false { + //No quota in the DB for this target, so create a new one. + if _, err := sess.Insert("a); err != nil { + return err + } + } else { + //update existing quota entry in the DB. + if _, err := sess.Id(quota.Id).Update("a); err != nil { + return err + } + } + + return nil + }) +} + +func GetGlobalQuotaByTarget(query *m.GetGlobalQuotaByTargetQuery) error { + //get quota used. + rawSql := fmt.Sprintf("SELECT COUNT(*) as count from %s", dialect.Quote(query.Target)) + resp := make([]*targetCount, 0) + if err := x.Sql(rawSql).Find(&resp); err != nil { + return err + } + + query.Result = &m.GlobalQuotaDTO{ + Target: query.Target, + Limit: query.Default, + Used: resp[0].Count, + } + + return nil +} diff --git a/pkg/services/sqlstore/quota_test.go b/pkg/services/sqlstore/quota_test.go new file mode 100644 index 00000000000..5ef618e166d --- /dev/null +++ b/pkg/services/sqlstore/quota_test.go @@ -0,0 +1,171 @@ +package sqlstore + +import ( + "testing" + + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" +) + +func TestQuotaCommandsAndQueries(t *testing.T) { + + Convey("Testing Qutoa commands & queries", t, func() { + InitTestDB(t) + userId := int64(1) + orgId := int64(0) + + setting.Quota = setting.QuotaSettings{ + Enabled: true, + Org: &setting.OrgQuota{ + User: 5, + Dashboard: 5, + DataSource: 5, + ApiKey: 5, + }, + User: &setting.UserQuota{ + Org: 5, + }, + Global: &setting.GlobalQuota{ + Org: 5, + User: 5, + Dashboard: 5, + DataSource: 5, + ApiKey: 5, + Session: 5, + }, + } + + // create a new org and add user_id 1 as admin. + // we will then have an org with 1 user. and a user + // with 1 org. + userCmd := m.CreateOrgCommand{ + Name: "TestOrg", + UserId: 1, + } + err := CreateOrg(&userCmd) + So(err, ShouldBeNil) + orgId = userCmd.Result.Id + + Convey("Given saved org quota for users", func() { + orgCmd := m.UpdateOrgQuotaCmd{ + OrgId: orgId, + Target: "org_user", + Limit: 10, + } + err := UpdateOrgQuota(&orgCmd) + So(err, ShouldBeNil) + + Convey("Should be able to get saved quota by org id and target", func() { + query := m.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 1} + err = GetOrgQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Limit, ShouldEqual, 10) + }) + Convey("Should be able to get default quota by org id and target", func() { + query := m.GetOrgQuotaByTargetQuery{OrgId: 123, Target: "org_user", Default: 11} + err = GetOrgQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Limit, ShouldEqual, 11) + }) + Convey("Should be able to get used org quota when rows exist", func() { + query := m.GetOrgQuotaByTargetQuery{OrgId: orgId, Target: "org_user", Default: 11} + err = GetOrgQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Used, ShouldEqual, 1) + }) + Convey("Should be able to get used org quota when no rows exist", func() { + query := m.GetOrgQuotaByTargetQuery{OrgId: 2, Target: "org_user", Default: 11} + err = GetOrgQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Used, ShouldEqual, 0) + }) + Convey("Should be able to quota list for org", func() { + query := m.GetOrgQuotasQuery{OrgId: orgId} + err = GetOrgQuotas(&query) + + So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 4) + for _, res := range query.Result { + limit := 5 //default quota limit + used := 0 + if res.Target == "org_user" { + limit = 10 //customized quota limit. + used = 1 + } + So(res.Limit, ShouldEqual, limit) + So(res.Used, ShouldEqual, used) + + } + }) + }) + Convey("Given saved user quota for org", func() { + userQoutaCmd := m.UpdateUserQuotaCmd{ + UserId: userId, + Target: "org_user", + Limit: 10, + } + err := UpdateUserQuota(&userQoutaCmd) + So(err, ShouldBeNil) + + Convey("Should be able to get saved quota by user id and target", func() { + query := m.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 1} + err = GetUserQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Limit, ShouldEqual, 10) + }) + Convey("Should be able to get default quota by user id and target", func() { + query := m.GetUserQuotaByTargetQuery{UserId: 9, Target: "org_user", Default: 11} + err = GetUserQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Limit, ShouldEqual, 11) + }) + Convey("Should be able to get used user quota when rows exist", func() { + query := m.GetUserQuotaByTargetQuery{UserId: userId, Target: "org_user", Default: 11} + err = GetUserQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Used, ShouldEqual, 1) + }) + Convey("Should be able to get used user quota when no rows exist", func() { + query := m.GetUserQuotaByTargetQuery{UserId: 2, Target: "org_user", Default: 11} + err = GetUserQuotaByTarget(&query) + + So(err, ShouldBeNil) + So(query.Result.Used, ShouldEqual, 0) + }) + Convey("Should be able to quota list for user", func() { + query := m.GetUserQuotasQuery{UserId: userId} + err = GetUserQuotas(&query) + + So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 1) + So(query.Result[0].Limit, ShouldEqual, 10) + So(query.Result[0].Used, ShouldEqual, 1) + }) + }) + + Convey("Should be able to global user quota", func() { + query := m.GetGlobalQuotaByTargetQuery{Target: "user", Default: 5} + err = GetGlobalQuotaByTarget(&query) + So(err, ShouldBeNil) + + So(query.Result.Limit, ShouldEqual, 5) + So(query.Result.Used, ShouldEqual, 0) + }) + Convey("Should be able to global org quota", func() { + query := m.GetGlobalQuotaByTargetQuery{Target: "org", Default: 5} + err = GetGlobalQuotaByTarget(&query) + So(err, ShouldBeNil) + + So(query.Result.Limit, ShouldEqual, 5) + So(query.Result.Used, ShouldEqual, 1) + }) + }) +} diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 2dd483651c0..88c35d630f8 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -136,7 +136,7 @@ func getEngine() (*xorm.Engine, error) { return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type) } - log.Info("Database: %v, ConnectionString: %v", DbCfg.Type, cnnstr) + log.Info("Database: %v", DbCfg.Type) return xorm.NewEngine(DbCfg.Type, cnnstr) } diff --git a/pkg/services/sqlstore/sqlstore.goconvey b/pkg/services/sqlstore/sqlstore.goconvey index 92feb5268a5..8ca30789e4a 100644 --- a/pkg/services/sqlstore/sqlstore.goconvey +++ b/pkg/services/sqlstore/sqlstore.goconvey @@ -1 +1 @@ --timeout=10s +-timeout=20s diff --git a/pkg/services/sqlstore/temp_user.go b/pkg/services/sqlstore/temp_user.go new file mode 100644 index 00000000000..0fe5c9612f5 --- /dev/null +++ b/pkg/services/sqlstore/temp_user.go @@ -0,0 +1,121 @@ +package sqlstore + +import ( + "time" + + "github.com/go-xorm/xorm" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" +) + +func init() { + bus.AddHandler("sql", CreateTempUser) + bus.AddHandler("sql", GetTempUsersQuery) + bus.AddHandler("sql", UpdateTempUserStatus) + bus.AddHandler("sql", GetTempUserByCode) +} + +func UpdateTempUserStatus(cmd *m.UpdateTempUserStatusCommand) error { + return inTransaction(func(sess *xorm.Session) error { + var rawSql = "UPDATE temp_user SET status=? WHERE code=?" + _, err := sess.Exec(rawSql, string(cmd.Status), cmd.Code) + return err + }) +} + +func CreateTempUser(cmd *m.CreateTempUserCommand) error { + return inTransaction2(func(sess *session) error { + + // create user + user := &m.TempUser{ + Email: cmd.Email, + Name: cmd.Name, + OrgId: cmd.OrgId, + Code: cmd.Code, + Role: cmd.Role, + Status: cmd.Status, + RemoteAddr: cmd.RemoteAddr, + InvitedByUserId: cmd.InvitedByUserId, + Created: time.Now(), + Updated: time.Now(), + } + + if _, err := sess.Insert(user); err != nil { + return err + } + + cmd.Result = user + return nil + }) +} + +func GetTempUsersQuery(query *m.GetTempUsersQuery) error { + rawSql := `SELECT + tu.id as id, + tu.org_id as org_id, + tu.email as email, + tu.name as name, + tu.role as role, + tu.code as code, + tu.status as status, + tu.email_sent as email_sent, + tu.email_sent_on as email_sent_on, + tu.created as created, + u.login as invited_by_login, + u.name as invited_by_name, + u.email as invited_by_email + FROM ` + dialect.Quote("temp_user") + ` as tu + LEFT OUTER JOIN ` + dialect.Quote("user") + ` as u on u.id = tu.invited_by_user_id + WHERE tu.status=?` + params := []interface{}{string(query.Status)} + + if query.OrgId > 0 { + rawSql += ` AND tu.org_id=?` + params = append(params, query.OrgId) + } + + if query.Email != "" { + rawSql += ` AND tu.email=?` + params = append(params, query.Email) + } + + rawSql += " ORDER BY tu.created desc" + + query.Result = make([]*m.TempUserDTO, 0) + sess := x.Sql(rawSql, params...) + err := sess.Find(&query.Result) + return err +} + +func GetTempUserByCode(query *m.GetTempUserByCodeQuery) error { + var rawSql = `SELECT + tu.id as id, + tu.org_id as org_id, + tu.email as email, + tu.name as name, + tu.role as role, + tu.code as code, + tu.status as status, + tu.email_sent as email_sent, + tu.email_sent_on as email_sent_on, + tu.created as created, + u.login as invited_by_login, + u.name as invited_by_name, + u.email as invited_by_email + FROM ` + dialect.Quote("temp_user") + ` as tu + LEFT OUTER JOIN ` + dialect.Quote("user") + ` as u on u.id = tu.invited_by_user_id + WHERE tu.code=?` + + var tempUser m.TempUserDTO + sess := x.Sql(rawSql, query.Code) + has, err := sess.Get(&tempUser) + + if err != nil { + return err + } else if has == false { + return m.ErrTempUserNotFound + } + + query.Result = &tempUser + return err +} diff --git a/pkg/services/sqlstore/temp_user_test.go b/pkg/services/sqlstore/temp_user_test.go new file mode 100644 index 00000000000..ebf753890f6 --- /dev/null +++ b/pkg/services/sqlstore/temp_user_test.go @@ -0,0 +1,59 @@ +package sqlstore + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + m "github.com/grafana/grafana/pkg/models" +) + +func TestTempUserCommandsAndQueries(t *testing.T) { + + Convey("Testing Temp User commands & queries", t, func() { + InitTestDB(t) + + Convey("Given saved api key", func() { + cmd := m.CreateTempUserCommand{ + OrgId: 2256, + Name: "hello", + Code: "asd", + Email: "e@as.co", + Status: m.TmpUserInvitePending, + } + err := CreateTempUser(&cmd) + So(err, ShouldBeNil) + + Convey("Should be able to get temp users by org id", func() { + query := m.GetTempUsersQuery{OrgId: 2256, Status: m.TmpUserInvitePending} + err = GetTempUsersQuery(&query) + + So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 1) + }) + + Convey("Should be able to get temp users by email", func() { + query := m.GetTempUsersQuery{Email: "e@as.co", Status: m.TmpUserInvitePending} + err = GetTempUsersQuery(&query) + + So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 1) + }) + + Convey("Should be able to get temp users by code", func() { + query := m.GetTempUserByCodeQuery{Code: "asd"} + err = GetTempUserByCode(&query) + + So(err, ShouldBeNil) + So(query.Result.Name, ShouldEqual, "hello") + }) + + Convey("Should be able update status", func() { + cmd2 := m.UpdateTempUserStatusCommand{Code: "asd", Status: m.TmpUserRevoked} + err := UpdateTempUserStatus(&cmd2) + So(err, ShouldBeNil) + }) + + }) + }) +} diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index f5df6f9ff1f..6c6f581dcc4 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -1,7 +1,6 @@ package sqlstore import ( - "fmt" "strings" "time" @@ -30,7 +29,11 @@ func init() { bus.AddHandler("sql", UpdateUserPermissions) } -func getOrgIdForNewUser(userEmail string, sess *session) (int64, error) { +func getOrgIdForNewUser(cmd *m.CreateUserCommand, sess *session) (int64, error) { + if cmd.SkipOrgSetup { + return -1, nil + } + var org m.Org if setting.AutoAssignOrg { @@ -46,7 +49,10 @@ func getOrgIdForNewUser(userEmail string, sess *session) (int64, error) { org.Id = 1 } } else { - org.Name = userEmail + org.Name = cmd.OrgName + if len(org.Name) == 0 { + org.Name = util.StringsFallback2(cmd.Email, cmd.Login) + } } org.Created = time.Now() @@ -67,21 +73,26 @@ func getOrgIdForNewUser(userEmail string, sess *session) (int64, error) { func CreateUser(cmd *m.CreateUserCommand) error { return inTransaction2(func(sess *session) error { - orgId, err := getOrgIdForNewUser(cmd.Email, sess) + orgId, err := getOrgIdForNewUser(cmd, sess) if err != nil { return err } + if cmd.Email == "" { + cmd.Email = cmd.Login + } + // create user user := m.User{ - Email: cmd.Email, - Name: cmd.Name, - Login: cmd.Login, - Company: cmd.Company, - IsAdmin: cmd.IsAdmin, - OrgId: orgId, - Created: time.Now(), - Updated: time.Now(), + Email: cmd.Email, + Name: cmd.Name, + Login: cmd.Login, + Company: cmd.Company, + IsAdmin: cmd.IsAdmin, + OrgId: orgId, + EmailVerified: cmd.EmailVerified, + Created: time.Now(), + Updated: time.Now(), } if len(cmd.Password) > 0 { @@ -96,23 +107,6 @@ func CreateUser(cmd *m.CreateUserCommand) error { return err } - // create org user link - orgUser := m.OrgUser{ - OrgId: orgId, - UserId: user.Id, - Role: m.ROLE_ADMIN, - Created: time.Now(), - Updated: time.Now(), - } - - if setting.AutoAssignOrg && !user.IsAdmin { - orgUser.Role = m.RoleType(setting.AutoAssignOrgRole) - } - - if _, err = sess.Insert(&orgUser); err != nil { - return err - } - sess.publishAfterCommit(&events.UserCreated{ Timestamp: user.Created, Id: user.Id, @@ -122,6 +116,26 @@ func CreateUser(cmd *m.CreateUserCommand) error { }) cmd.Result = user + + // create org user link + if !cmd.SkipOrgSetup { + orgUser := m.OrgUser{ + OrgId: orgId, + UserId: user.Id, + Role: m.ROLE_ADMIN, + Created: time.Now(), + Updated: time.Now(), + } + + if setting.AutoAssignOrg && !user.IsAdmin { + orgUser.Role = m.RoleType(setting.AutoAssignOrgRole) + } + + if _, err = sess.Insert(&orgUser); err != nil { + return err + } + } + return nil }) } @@ -305,9 +319,19 @@ func SearchUsers(query *m.SearchUsersQuery) error { func DeleteUser(cmd *m.DeleteUserCommand) error { return inTransaction(func(sess *xorm.Session) error { - var rawSql = fmt.Sprintf("DELETE FROM %s WHERE id=?", x.Dialect().Quote("user")) - _, err := sess.Exec(rawSql, cmd.UserId) - return err + deletes := []string{ + "DELETE FROM star WHERE user_id = ?", + "DELETE FROM user WHERE id = ?", + } + + for _, sql := range deletes { + _, err := sess.Exec(sql, cmd.UserId) + if err != nil { + return err + } + } + + return nil }) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index d0f28fb809d..8d62e475fc1 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -6,6 +6,7 @@ package setting import ( "bytes" "encoding/json" + "errors" "fmt" "net/url" "os" @@ -73,12 +74,14 @@ var ( CookieRememberName string DisableGravatar bool EmailCodeValidMinutes int + DataProxyWhiteList map[string]bool // User settings AllowUserSignUp bool AllowUserOrgCreate bool AutoAssignOrg bool AutoAssignOrgRole string + VerifyEmailEnabled bool // Http auth AdminUser string @@ -114,11 +117,19 @@ var ( appliedCommandLineProperties []string appliedEnvOverrides []string - ReportingEnabled bool - GoogleAnalyticsId string + ReportingEnabled bool + GoogleAnalyticsId string + GoogleTagManagerId string + + // LDAP + LdapEnabled bool + LdapConfigFile string // SMTP email settings Smtp SmtpSettings + + // QUOTA + Quota QuotaSettings ) type CommandLineArgs struct { @@ -269,7 +280,7 @@ func loadSpecifedConfigFile(configFile string) { } defaultKey, err := defaultSec.GetKey(key.Name()) if err != nil { - log.Error(3, "Unknown config key %s defined in section %s, in file", key.Name(), section.Name(), configFile) + log.Error(3, "Unknown config key %s defined in section %s, in file %s", key.Name(), section.Name(), configFile) continue } defaultKey.SetValue(key.Value()) @@ -348,7 +359,26 @@ func setHomePath(args *CommandLineArgs) { } } -func NewConfigContext(args *CommandLineArgs) { +var skipStaticRootValidation bool = false + +func validateStaticRootPath() error { + if skipStaticRootValidation { + return nil + } + + if _, err := os.Stat(path.Join(StaticRootPath, "css")); err == nil { + return nil + } + + if _, err := os.Stat(StaticRootPath + "_gen/css"); err == nil { + StaticRootPath = StaticRootPath + "_gen" + return nil + } + + return errors.New("Failed to detect generated css or javascript files in static root (%s), have you executed default grunt task?") +} + +func NewConfigContext(args *CommandLineArgs) error { setHomePath(args) loadConfiguration(args) @@ -367,11 +397,16 @@ func NewConfigContext(args *CommandLineArgs) { Domain = server.Key("domain").MustString("localhost") HttpAddr = server.Key("http_addr").MustString("0.0.0.0") HttpPort = server.Key("http_port").MustString("3000") - StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath) RouterLogging = server.Key("router_logging").MustBool(false) EnableGzip = server.Key("enable_gzip").MustBool(false) EnforceDomain = server.Key("enforce_domain").MustBool(false) + StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath) + if err := validateStaticRootPath(); err != nil { + return err + } + + // read security settings security := Cfg.Section("security") SecretKey = security.Key("secret_key").String() LogInRememberDays = security.Key("login_remember_days").MustInt() @@ -379,6 +414,12 @@ func NewConfigContext(args *CommandLineArgs) { CookieRememberName = security.Key("cookie_remember_name").String() DisableGravatar = security.Key("disable_gravatar").MustBool(true) + // read data source proxy white list + DataProxyWhiteList = make(map[string]bool) + for _, hostAndIp := range security.Key("data_source_proxy_whitelist").Strings(" ") { + DataProxyWhiteList[hostAndIp] = true + } + // admin AdminUser = security.Key("admin_user").String() AdminPassword = security.Key("admin_password").String() @@ -387,7 +428,8 @@ func NewConfigContext(args *CommandLineArgs) { AllowUserSignUp = users.Key("allow_sign_up").MustBool(true) AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) - AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"}) + AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"}) + VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false) // anonymous access AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false) @@ -411,9 +453,21 @@ func NewConfigContext(args *CommandLineArgs) { analytics := Cfg.Section("analytics") ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() + GoogleTagManagerId = analytics.Key("google_tag_manager_id").String() + + ldapSec := Cfg.Section("auth.ldap") + LdapEnabled = ldapSec.Key("enabled").MustBool(false) + LdapConfigFile = ldapSec.Key("config_file").String() readSessionConfig() readSmtpSettings() + readQuotaSettings() + + if VerifyEmailEnabled && !Smtp.Enabled { + log.Warn("require_email_validation is enabled but smpt is disabled") + } + + return nil } func readSessionConfig() { @@ -448,6 +502,8 @@ var logLevels = map[string]int{ } func initLogging(args *CommandLineArgs) { + //close any existing log handlers. + log.Close() // Get and check log mode. LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",") LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath) @@ -532,7 +588,7 @@ func LogConfigurationInfo() { if len(appliedEnvOverrides) > 0 { text.WriteString("\tEnvironment variables used:\n") - for i, prop := range appliedCommandLineProperties { + for i, prop := range appliedEnvOverrides { text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop)) } } diff --git a/pkg/setting/setting_quota.go b/pkg/setting/setting_quota.go new file mode 100644 index 00000000000..49769d9930f --- /dev/null +++ b/pkg/setting/setting_quota.go @@ -0,0 +1,94 @@ +package setting + +import ( + "reflect" +) + +type OrgQuota struct { + User int64 `target:"org_user"` + DataSource int64 `target:"data_source"` + Dashboard int64 `target:"dashboard"` + ApiKey int64 `target:"api_key"` +} + +type UserQuota struct { + Org int64 `target:"org_user"` +} + +type GlobalQuota struct { + Org int64 `target:"org"` + User int64 `target:"user"` + DataSource int64 `target:"data_source"` + Dashboard int64 `target:"dashboard"` + ApiKey int64 `target:"api_key"` + Session int64 `target:"-"` +} + +func (q *OrgQuota) ToMap() map[string]int64 { + return quotaToMap(*q) +} + +func (q *UserQuota) ToMap() map[string]int64 { + return quotaToMap(*q) +} + +func (q *GlobalQuota) ToMap() map[string]int64 { + return quotaToMap(*q) +} + +func quotaToMap(q interface{}) map[string]int64 { + qMap := make(map[string]int64) + typ := reflect.TypeOf(q) + val := reflect.ValueOf(q) + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + name := field.Tag.Get("target") + if name == "" { + name = field.Name + } + if name == "-" { + continue + } + value := val.Field(i) + qMap[name] = value.Int() + } + return qMap +} + +type QuotaSettings struct { + Enabled bool + Org *OrgQuota + User *UserQuota + Global *GlobalQuota +} + +func readQuotaSettings() { + // set global defaults. + quota := Cfg.Section("quota") + Quota.Enabled = quota.Key("enabled").MustBool(false) + + // per ORG Limits + Quota.Org = &OrgQuota{ + User: quota.Key("org_user").MustInt64(10), + DataSource: quota.Key("org_data_source").MustInt64(10), + Dashboard: quota.Key("org_dashboard").MustInt64(10), + ApiKey: quota.Key("org_api_key").MustInt64(10), + } + + // per User limits + Quota.User = &UserQuota{ + Org: quota.Key("user_org").MustInt64(10), + } + + // Global Limits + Quota.Global = &GlobalQuota{ + User: quota.Key("global_user").MustInt64(-1), + Org: quota.Key("global_org").MustInt64(-1), + DataSource: quota.Key("global_data_source").MustInt64(-1), + Dashboard: quota.Key("global_dashboard").MustInt64(-1), + ApiKey: quota.Key("global_api_key").MustInt64(-1), + Session: quota.Key("global_session").MustInt64(-1), + } + +} diff --git a/pkg/setting/setting_test.go b/pkg/setting/setting_test.go index 00da9b4f416..ef44f55551c 100644 --- a/pkg/setting/setting_test.go +++ b/pkg/setting/setting_test.go @@ -11,9 +11,11 @@ import ( func TestLoadingSettings(t *testing.T) { Convey("Testing loading settings from ini file", t, func() { + skipStaticRootValidation = true Convey("Given the default ini files", func() { - NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + err := NewConfigContext(&CommandLineArgs{HomePath: "../../"}) + So(err, ShouldBeNil) So(AdminUser, ShouldEqual, "admin") }) diff --git a/pkg/util/strings.go b/pkg/util/strings.go new file mode 100644 index 00000000000..7e503a99118 --- /dev/null +++ b/pkg/util/strings.go @@ -0,0 +1,18 @@ +package util + +func StringsFallback2(val1 string, val2 string) string { + if val1 != "" { + return val1 + } + return val2 +} + +func StringsFallback3(val1 string, val2 string, val3 string) string { + if val1 != "" { + return val1 + } + if val2 != "" { + return val2 + } + return val3 +} diff --git a/public/app/app.js b/public/app/app.js index 4d1dec4f673..022d972511a 100644 --- a/public/app/app.js +++ b/public/app/app.js @@ -1,6 +1,3 @@ -/** - * main app level module - */ define([ 'angular', 'jquery', @@ -12,27 +9,21 @@ define([ 'angular-sanitize', 'angular-strap', 'angular-dragdrop', + 'angular-ui', 'extend-jquery', 'bindonce', + './core/core', ], function (angular, $, _, appLevelRequire) { - "use strict"; - var app = angular.module('grafana', []), - // we will keep a reference to each module defined before boot, so that we can - // go back and allow it to define new features later. Once we boot, this will be false - pre_boot_modules = [], - // these are the functions that we need to call to register different - // features if we define them after boot time - register_fns = {}; + var app = angular.module('grafana', []); + var register_fns = {}; + var preBootModules = []; // This stores the grafana version number app.constant('grafanaVersion',"@grafanaVersion@"); - // Use this for cache busting partials - app.constant('cacheBust',"cache-bust="+Date.now()); - /** * Tells the application to watch the module, once bootstraping has completed * the modules controller, service, etc. functions will be overwritten to register directly @@ -41,8 +32,8 @@ function (angular, $, _, appLevelRequire) { * @return {[type]} [description] */ app.useModule = function (module) { - if (pre_boot_modules) { - pre_boot_modules.push(module); + if (preBootModules) { + preBootModules.push(module); } else { _.extend(module, register_fns); } @@ -50,7 +41,6 @@ function (angular, $, _, appLevelRequire) { }; app.config(function($locationProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) { - // this is how the internet told me to dynamically add modules :/ register_fns.controller = $controllerProvider.register; register_fns.directive = $compileProvider.directive; register_fns.factory = $provide.factory; @@ -59,12 +49,14 @@ function (angular, $, _, appLevelRequire) { }); var apps_deps = [ + 'grafana.core', 'ngRoute', 'ngSanitize', '$strap.directives', 'ang-drag-drop', 'grafana', - 'pasvaz.bindonce' + 'pasvaz.bindonce', + 'ui.bootstrap.tabs', ]; var module_types = ['controllers', 'directives', 'factories', 'services', 'filters', 'routes']; @@ -81,10 +73,7 @@ function (angular, $, _, appLevelRequire) { 'services/all', 'features/all', 'controllers/all', - 'directives/all', - 'filters/all', 'components/partials', - 'routes/all', ]; app.boot = function() { @@ -99,10 +88,11 @@ function (angular, $, _, appLevelRequire) { .ready(function() { angular.bootstrap(document, apps_deps) .invoke(['$rootScope', function ($rootScope) { - _.each(pre_boot_modules, function (module) { + _.each(preBootModules, function (module) { _.extend(module, register_fns); }); - pre_boot_modules = false; + + preBootModules = null; $rootScope.requireContext = appLevelRequire; $rootScope.require = function (deps, fn) { diff --git a/public/app/components/kbn.js b/public/app/components/kbn.js index d817ada2ebe..e1047243d1f 100644 --- a/public/app/components/kbn.js +++ b/public/app/components/kbn.js @@ -205,7 +205,21 @@ function($, _, moment) { return kbn.parseDateMath(mathString, time); }; - kbn._timespanRegex = /^\d+[h,m,M,w,s,H,d]$/; + kbn.getRelativeTimeInfo = function(str) { + var info = {value: str}; + if (str === 'today') { + info.text = 'Today'; + info.from = 'today'; + info.to = 'now'; + } else { + info.text = 'Last ' + str; + info.from = 'now-'+str; + info.to = 'now'; + } + return info; + }; + + kbn._timespanRegex = /^(\d+[h,m,M,w,s,H,d])|today$/; kbn.isValidTimeSpan = function(str) { return kbn._timespanRegex.test(str); }; diff --git a/public/app/components/panelmeta.js b/public/app/components/panelmeta.ts similarity index 76% rename from public/app/components/panelmeta.js rename to public/app/components/panelmeta.ts index 7eee8fa970f..17a19b20dfb 100644 --- a/public/app/components/panelmeta.js +++ b/public/app/components/panelmeta.ts @@ -1,9 +1,13 @@ -define([ -], -function () { - "use strict"; +class PanelMeta { + description: any; + fullscreen: any; + editIcon: any; + panelName: any; + menu: any; + editorTabs: any; + extendedMenu: any; - function PanelMeta(options) { + constructor(options : any) { this.description = options.description; this.fullscreen = options.fullscreen; this.editIcon = options.editIcon; @@ -29,18 +33,17 @@ function () { this.addExtendedMenuItem('Panel JSON', '', 'editPanelJson(); dismiss();'); } - PanelMeta.prototype.addMenuItem = function(text, icon, click, role) { + addMenuItem (text, icon, click, role?) { this.menu.push({text: text, icon: icon, click: click, role: role}); - }; + } - PanelMeta.prototype.addExtendedMenuItem = function(text, icon, click, role) { + addExtendedMenuItem (text, icon, click, role?) { this.extendedMenu.push({text: text, icon: icon, click: click, role: role}); - }; + } - PanelMeta.prototype.addEditorTab = function(title, src) { + addEditorTab (title, src) { this.editorTabs.push({title: title, src: src}); - }; + } +} - return PanelMeta; - -}); +export = PanelMeta; diff --git a/public/app/components/require.config.js b/public/app/components/require.config.js index 12a72dd42e3..f255418414e 100644 --- a/public/app/components/require.config.js +++ b/public/app/components/require.config.js @@ -17,6 +17,7 @@ require.config({ 'angular-sanitize': '../vendor/angular-sanitize/angular-sanitize', 'angular-dragdrop': '../vendor/angular-native-dragdrop/draganddrop', 'angular-strap': '../vendor/angular-other/angular-strap', + 'angular-ui': '../vendor/angular-ui/angular-bootstrap', timepicker: '../vendor/angular-other/timepicker', datepicker: '../vendor/angular-other/datepicker', bindonce: '../vendor/angular-bindonce/bindonce', @@ -44,6 +45,7 @@ require.config({ modernizr: '../vendor/modernizr-2.6.1', 'bootstrap-tagsinput': '../vendor/tagsinput/bootstrap-tagsinput', + 'aws-sdk': '../vendor/aws-sdk/dist/aws-sdk.min', }, shim: { @@ -90,6 +92,7 @@ require.config({ 'angular-dragdrop': ['jquery', 'angular'], 'angular-mocks': ['angular'], 'angular-sanitize': ['angular'], + 'angular-ui': ['angular'], 'angular-route': ['angular'], 'angular-strap': ['angular', 'bootstrap','timepicker', 'datepicker'], 'bindonce': ['angular'], diff --git a/public/app/controllers/all.js b/public/app/controllers/all.js index 99b9a496484..1a313e6b019 100644 --- a/public/app/controllers/all.js +++ b/public/app/controllers/all.js @@ -1,11 +1,11 @@ define([ './grafanaCtrl', - './pulldown', './search', - './metricKeys', './inspectCtrl', './jsonEditorCtrl', './loginCtrl', + './invitedCtrl', + './signupCtrl', './resetPasswordCtrl', './sidemenuCtrl', './errorCtrl', diff --git a/public/app/controllers/errorCtrl.js b/public/app/controllers/errorCtrl.js index 9e70efb3ee4..13b64584226 100644 --- a/public/app/controllers/errorCtrl.js +++ b/public/app/controllers/errorCtrl.js @@ -1,6 +1,5 @@ define([ 'angular', - 'app', 'lodash' ], function (angular) { diff --git a/public/app/controllers/inspectCtrl.js b/public/app/controllers/inspectCtrl.js index d6034516a6b..e70a88323e9 100644 --- a/public/app/controllers/inspectCtrl.js +++ b/public/app/controllers/inspectCtrl.js @@ -1,8 +1,9 @@ define([ 'angular', - 'lodash' + 'lodash', + 'jquery', ], -function (angular, _) { +function (angular, _, $) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -30,7 +31,11 @@ function (angular, _) { } if (_.isString(model.error.data)) { - $scope.response = model.error.data; + $scope.response = $("
" + model.error.data + "
").text(); + } else if (model.error.data) { + $scope.response = angular.toJson(model.error.data, true); + } else if (model.error.message) { + $scope.message = model.error.message; } if (model.error.config && model.error.config.params) { @@ -44,43 +49,20 @@ function (angular, _) { $scope.stack_trace = model.error.stack; $scope.message = model.error.message; } - else if (model.error.config && model.error.config.data) { + + if (model.error.config && model.error.config.data) { $scope.editor.index = 1; - $scope.request_parameters = getParametersFromQueryString(model.error.config.data); - - if (model.error.data.indexOf('DOCTYPE') !== -1) { - $scope.response_html = model.error.data; + if (_.isString(model.error.config.data)) { + $scope.request_parameters = getParametersFromQueryString(model.error.config.data); + } else { + $scope.request_parameters = _.map(model.error.config.data, function(value, key) { + return {key: key, value: angular.toJson(value, true)}; + }); } } }; }); - angular - .module('grafana.directives') - .directive('iframeContent', function($parse) { - return { - restrict: 'A', - link: function($scope, elem, attrs) { - var getter = $parse(attrs.iframeContent), value = getter($scope); - - $scope.$on("$destroy",function() { - elem.remove(); - }); - - var iframe = document.createElement('iframe'); - iframe.width = '100%'; - iframe.height = '400px'; - iframe.style.border = 'none'; - iframe.src = 'about:blank'; - elem.append(iframe); - - iframe.contentWindow.document.open('text/html', 'replace'); - iframe.contentWindow.document.write(value); - iframe.contentWindow.document.close(); - } - }; - }); - }); diff --git a/public/app/controllers/invitedCtrl.js b/public/app/controllers/invitedCtrl.js new file mode 100644 index 00000000000..0b4a096fb1b --- /dev/null +++ b/public/app/controllers/invitedCtrl.js @@ -0,0 +1,41 @@ +define([ + 'angular', + 'config', +], +function (angular, config) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('InvitedCtrl', function($scope, $routeParams, contextSrv, backendSrv) { + + contextSrv.sidemenu = false; + + $scope.formModel = {}; + + $scope.init = function() { + backendSrv.get('/api/user/invite/' + $routeParams.code).then(function(invite) { + $scope.formModel.name = invite.name; + $scope.formModel.email = invite.email; + $scope.formModel.username = invite.email; + $scope.formModel.inviteCode = $routeParams.code; + + $scope.greeting = invite.name || invite.email || invite.username; + $scope.invitedBy = invite.invitedBy; + }); + }; + + $scope.submit = function() { + if (!$scope.inviteForm.$valid) { + return; + } + + backendSrv.post('/api/user/invite/complete', $scope.formModel).then(function() { + window.location.href = config.appSubUrl + '/'; + }); + }; + + $scope.init(); + + }); +}); diff --git a/public/app/controllers/loginCtrl.js b/public/app/controllers/loginCtrl.js index 40e8009b399..0ee370fb979 100644 --- a/public/app/controllers/loginCtrl.js +++ b/public/app/controllers/loginCtrl.js @@ -58,8 +58,12 @@ function (angular, config) { return; } - backendSrv.post('/api/user/signup', $scope.formModel).then(function() { - window.location.href = config.appSubUrl + '/'; + backendSrv.post('/api/user/signup', $scope.formModel).then(function(result) { + if (result.status === 'SignUpCreated') { + $location.path('/signup').search({email: $scope.formModel.email}); + } else { + window.location.href = config.appSubUrl + '/'; + } }); }; diff --git a/public/app/controllers/pulldown.js b/public/app/controllers/pulldown.js deleted file mode 100644 index 898d80c0479..00000000000 --- a/public/app/controllers/pulldown.js +++ /dev/null @@ -1,42 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash' -], -function (angular, app, _) { - 'use strict'; - - var module = angular.module('grafana.controllers'); - - module.controller('PulldownCtrl', function($scope, $rootScope, $timeout) { - var _d = { - collapse: false, - notice: false, - enable: true - }; - - _.defaults($scope.pulldown,_d); - - $scope.init = function() { - // Provide a combined skeleton for panels that must interact with panel and row. - // This might create name spacing issues. - $scope.panel = $scope.pulldown; - $scope.row = $scope.pulldown; - }; - - $scope.toggle_pulldown = function(pulldown) { - pulldown.collapse = pulldown.collapse ? false : true; - if (!pulldown.collapse) { - $timeout(function() { - $scope.$broadcast('render'); - }); - } else { - $scope.row.notice = false; - } - }; - - $scope.init(); - - }); - -}); \ No newline at end of file diff --git a/public/app/controllers/search.js b/public/app/controllers/search.js index a762af0f887..279b3a1d793 100644 --- a/public/app/controllers/search.js +++ b/public/app/controllers/search.js @@ -17,10 +17,6 @@ function (angular, _, config) { $scope.query = { query: '', tag: [], starred: false }; $scope.currentSearchId = 0; - if ($scope.dashboardViewState.fullscreen) { - $scope.exitFullscreen(); - } - $timeout(function() { $scope.giveSearchFocus = $scope.giveSearchFocus + 1; $scope.query.query = ''; diff --git a/public/app/controllers/signupCtrl.ts b/public/app/controllers/signupCtrl.ts new file mode 100644 index 00000000000..d59f1f0eb68 --- /dev/null +++ b/public/app/controllers/signupCtrl.ts @@ -0,0 +1,52 @@ +/// + +import angular = require('angular'); +import config = require('config'); + +var module = angular.module('grafana.controllers'); + +export class SignUpCtrl { + + constructor( + private $scope : any, + private $location : any, + private contextSrv : any, + private backendSrv : any) { + + contextSrv.sidemenu = false; + $scope.ctrl = this; + + $scope.formModel = {}; + + var params = $location.search(); + $scope.formModel.orgName = params.email; + $scope.formModel.email = params.email; + $scope.formModel.username = params.email; + $scope.formModel.code = params.code; + + $scope.verifyEmailEnabled = false; + $scope.autoAssignOrg = false; + + backendSrv.get('/api/user/signup/options').then(options => { + $scope.verifyEmailEnabled = options.verifyEmailEnabled; + $scope.autoAssignOrg = options.autoAssignOrg; + }); + } + + submit () { + if (!this.$scope.signUpForm.$valid) { + return; + } + + this.backendSrv.post('/api/user/signup/step2', this.$scope.formModel).then(rsp => { + if (rsp.code === 'redirect-to-select-org') { + window.location.href = config.appSubUrl + '/profile/select-org?signup=1'; + } else { + window.location.href = config.appSubUrl + '/'; + } + }); + }; +} + +module.controller('SignUpCtrl', SignUpCtrl); + diff --git a/public/app/core/core.ts b/public/app/core/core.ts new file mode 100644 index 00000000000..039b232dbf6 --- /dev/null +++ b/public/app/core/core.ts @@ -0,0 +1,25 @@ +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// + +export * from './directives/array_join' +export * from './directives/give_focus' + +export * from './routes/bundle_loader' +export * from './filters/filters' + + diff --git a/public/app/core/core_module.ts b/public/app/core/core_module.ts new file mode 100644 index 00000000000..926036c5149 --- /dev/null +++ b/public/app/core/core_module.ts @@ -0,0 +1,5 @@ +/// + +import angular = require('angular'); + +export = angular.module('grafana.core', ['ngRoute']); diff --git a/public/app/directives/annotationTooltip.js b/public/app/core/directives/annotation_tooltip.js similarity index 68% rename from public/app/directives/annotationTooltip.js rename to public/app/core/directives/annotation_tooltip.js index 25059d08274..c79ca97189c 100644 --- a/public/app/directives/annotationTooltip.js +++ b/public/app/core/directives/annotation_tooltip.js @@ -1,25 +1,34 @@ define([ - 'angular', 'jquery', - 'lodash' + 'lodash', + '../core_module', ], -function (angular, $, _) { +function ($, _, coreModule) { 'use strict'; - angular - .module('grafana.directives') - .directive('annotationTooltip', function($sanitize, dashboardSrv, $compile) { + coreModule.directive('annotationTooltip', function($sanitize, dashboardSrv, $compile) { + + function sanitizeString(str) { + try { + return $sanitize(str); + } + catch(err) { + console.log('Could not sanitize annotation string, html escaping instead'); + return _.escape(str); + } + } + return { link: function (scope, element) { var event = scope.event; - var title = $sanitize(event.title); + var title = sanitizeString(event.title); var dashboard = dashboardSrv.getCurrent(); var time = '' + dashboard.formatDate(event.min) + ''; var tooltip = '
' + title + ' ' + time + '
' ; if (event.text) { - var text = $sanitize(event.text); + var text = sanitizeString(event.text); tooltip += text.replace(/\n/g, '
') + '
'; } diff --git a/public/app/core/directives/array_join.ts b/public/app/core/directives/array_join.ts new file mode 100644 index 00000000000..b9fb15406f0 --- /dev/null +++ b/public/app/core/directives/array_join.ts @@ -0,0 +1,34 @@ +/// + +import angular = require('angular'); +import _ = require('lodash'); +import coreModule = require('../core_module'); + +export function arrayJoin() { + 'use strict'; + + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attr, ngModel) { + + function split_array(text) { + return (text || '').split(','); + } + + function join_array(text) { + if (_.isArray(text)) { + return (text || '').join(','); + } else { + return text; + } + } + + ngModel.$parsers.push(split_array); + ngModel.$formatters.push(join_array); + } + }; +} + +coreModule.directive('arrayJoin', arrayJoin); + diff --git a/public/app/core/directives/body_class.js b/public/app/core/directives/body_class.js new file mode 100644 index 00000000000..f9e748ea6c2 --- /dev/null +++ b/public/app/core/directives/body_class.js @@ -0,0 +1,48 @@ +define([ + 'lodash', + 'jquery', + '../core_module', +], +function (_, $, coreModule) { + 'use strict'; + + coreModule.directive('bodyClass', function() { + return { + link: function($scope, elem) { + + var lastHideControlsVal; + + // tooltip removal fix + $scope.$on("$routeChangeSuccess", function() { + $("#tooltip, .tooltip").remove(); + }); + + $scope.$watch('submenuEnabled', function() { + if (!$scope.dashboard) { + return; + } + + elem.toggleClass('submenu-controls-visible', $scope.submenuEnabled); + }); + + $scope.$watch('dashboard.hideControls', function() { + if (!$scope.dashboard) { + return; + } + + var hideControls = $scope.dashboard.hideControls || $scope.playlist_active; + + if (lastHideControlsVal !== hideControls) { + elem.toggleClass('hide-controls', hideControls); + lastHideControlsVal = hideControls; + } + }); + + $scope.$watch('playlistSrv', function(newValue) { + elem.toggleClass('playlist-active', _.isObject(newValue)); + }); + } + }; + }); + +}); diff --git a/public/app/core/directives/config_modal.js b/public/app/core/directives/config_modal.js new file mode 100644 index 00000000000..36129b0cc96 --- /dev/null +++ b/public/app/core/directives/config_modal.js @@ -0,0 +1,46 @@ +define([ + 'lodash', + 'jquery', + '../core_module', +], +function (_, $, coreModule) { + 'use strict'; + + coreModule.directive('configModal', function($modal, $q, $timeout) { + return { + restrict: 'A', + link: function(scope, elem, attrs) { + var partial = attrs.configModal; + var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id; + + elem.bind('click',function() { + if ($(id).length) { + elem.attr('data-target', id).attr('data-toggle', 'modal'); + scope.$apply(function() { scope.$broadcast('modal-opened'); }); + return; + } + + var panelModal = $modal({ + template: partial, + persist: false, + show: false, + scope: scope.$new(), + keyboard: false + }); + + $q.when(panelModal).then(function(modalEl) { + elem.attr('data-target', id).attr('data-toggle', 'modal'); + + $timeout(function () { + if (!modalEl.data('modal').isShown) { + modalEl.modal('show'); + } + }, 50); + }); + + scope.$apply(); + }); + } + }; + }); +}); diff --git a/public/app/directives/confirmClick.js b/public/app/core/directives/confirm_click.js similarity index 75% rename from public/app/directives/confirmClick.js rename to public/app/core/directives/confirm_click.js index a3fe1af29b4..f73a847af45 100644 --- a/public/app/directives/confirmClick.js +++ b/public/app/core/directives/confirm_click.js @@ -1,13 +1,10 @@ define([ - 'angular', - 'kbn' + '../core_module', ], -function (angular) { +function (coreModule) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('confirmClick', function() { + coreModule.directive('confirmClick', function() { return { restrict: 'A', link: function(scope, elem, attrs) { @@ -23,4 +20,4 @@ function (angular) { }, }; }); -}); \ No newline at end of file +}); diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js new file mode 100644 index 00000000000..9eaa3ab6287 --- /dev/null +++ b/public/app/core/directives/dash_edit_link.js @@ -0,0 +1,103 @@ +define([ + 'jquery', + '../core_module', +], +function ($, coreModule) { + 'use strict'; + + var editViewMap = { + 'settings': { src: 'app/features/dashboard/partials/settings.html', title: "Settings" }, + 'annotations': { src: 'app/features/annotations/partials/editor.html', title: "Annotations" }, + 'templating': { src: 'app/features/templating/partials/editor.html', title: "Templating" } + }; + + coreModule.directive('dashEditorLink', function($timeout) { + return { + restrict: 'A', + link: function(scope, elem, attrs) { + var partial = attrs.dashEditorLink; + + elem.bind('click',function() { + $timeout(function() { + var editorScope = attrs.editorScope === 'isolated' ? null : scope; + scope.appEvent('show-dash-editor', { src: partial, scope: editorScope }); + }); + }); + } + }; + }); + + coreModule.directive('dashEditorView', function($compile, $location) { + return { + restrict: 'A', + link: function(scope, elem) { + var editorScope; + var lastEditor; + + function hideEditorPane() { + if (editorScope) { editorScope.dismiss(); } + } + + function showEditorPane(evt, payload, editview) { + if (editview) { + scope.contextSrv.editview = editViewMap[editview]; + payload.src = scope.contextSrv.editview.src; + } + + if (lastEditor === payload.src) { + hideEditorPane(); + return; + } + + hideEditorPane(); + + scope.exitFullscreen(); + + lastEditor = payload.src; + editorScope = payload.scope ? payload.scope.$new() : scope.$new(); + + editorScope.dismiss = function() { + editorScope.$destroy(); + elem.empty(); + lastEditor = null; + editorScope = null; + + if (editview) { + var urlParams = $location.search(); + if (editview === urlParams.editview) { + delete urlParams.editview; + $location.search(urlParams); + } + } + }; + + var src = "'" + payload.src + "'"; + var view = $('
'); + + if (payload.cssClass) { + view.addClass(payload.cssClass); + } + + elem.append(view); + $compile(elem.contents())(editorScope); + } + + scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) { + if (newValue) { + showEditorPane(null, {}, newValue); + } else if (oldValue) { + scope.contextSrv.editview = null; + if (lastEditor === editViewMap[oldValue]) { + hideEditorPane(); + } + } + }); + + scope.contextSrv.editview = null; + scope.$on("$destroy", hideEditorPane); + scope.onAppEvent('hide-dash-editor', hideEditorPane); + scope.onAppEvent('show-dash-editor', showEditorPane); + } + }; + }); +}); diff --git a/public/app/directives/dashUpload.js b/public/app/core/directives/dash_upload.js similarity index 89% rename from public/app/directives/dashUpload.js rename to public/app/core/directives/dash_upload.js index 89d52c75916..4a42d38eea7 100644 --- a/public/app/directives/dashUpload.js +++ b/public/app/core/directives/dash_upload.js @@ -1,13 +1,11 @@ define([ - 'angular', - 'kbn' + 'kbn', + '../core_module', ], -function (angular, kbn) { +function (kbn, coreModule) { 'use strict'; - var module = angular.module('grafana.directives'); - - module.directive('dashUpload', function(timer, alertSrv, $location) { + coreModule.directive('dashUpload', function(timer, alertSrv, $location) { return { restrict: 'A', link: function(scope) { diff --git a/public/app/core/directives/dropdown_typeahead.js b/public/app/core/directives/dropdown_typeahead.js new file mode 100644 index 00000000000..c306b9ff8e5 --- /dev/null +++ b/public/app/core/directives/dropdown_typeahead.js @@ -0,0 +1,113 @@ +define([ + 'lodash', + 'jquery', + '../core_module', +], +function (_, $, coreModule) { + 'use strict'; + + coreModule.directive('dropdownTypeahead', function($compile) { + + var inputTemplate = ''; + + var buttonTemplate = ''; + + return { + scope: { + menuItems: "=dropdownTypeahead", + dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect", + model: '=ngModel' + }, + link: function($scope, elem, attrs) { + var $input = $(inputTemplate); + var $button = $(buttonTemplate); + $input.appendTo(elem); + $button.appendTo(elem); + + if (attrs.linkText) { + $button.html(attrs.linkText); + } + + if (attrs.ngModel) { + $scope.$watch('model', function(newValue) { + _.each($scope.menuItems, function(item) { + _.each(item.submenu, function(subItem) { + if (subItem.value === newValue) { + $button.html(subItem.text); + } + }); + }); + }); + } + + var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { + _.each(value.submenu, function(item, subIndex) { + item.click = 'menuItemSelected(' + index + ',' + subIndex + ')'; + memo.push(value.text + ' ' + item.text); + }); + return memo; + }, []); + + $scope.menuItemSelected = function(index, subIndex) { + var item = $scope.menuItems[index]; + $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]}); + }; + + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ + source: typeaheadValues, + minLength: 1, + items: 10, + updater: function (value) { + var result = {}; + _.each($scope.menuItems, function(menuItem) { + _.each(menuItem.submenu, function(submenuItem) { + if (value === (menuItem.text + ' ' + submenuItem.text)) { + result.$item = menuItem; + result.$subItem = submenuItem; + } + }); + }); + + if (result.$item) { + $scope.$apply(function() { + $scope.dropdownTypeaheadOnSelect(result); + }); + } + + $input.trigger('blur'); + return ''; + } + }); + + $button.click(function() { + $button.hide(); + $input.show(); + $input.focus(); + }); + + $input.keyup(function() { + elem.toggleClass('open', $input.val() === ''); + }); + + $input.blur(function() { + $input.hide(); + $input.val(''); + $button.show(); + $button.focus(); + // clicking the function dropdown menu wont + // work if you remove class at once + setTimeout(function() { + elem.removeClass('open'); + }, 200); + }); + + $compile(elem.contents())($scope); + } + }; + }); +}); diff --git a/public/app/core/directives/give_focus.ts b/public/app/core/directives/give_focus.ts new file mode 100644 index 00000000000..1a00f2b7078 --- /dev/null +++ b/public/app/core/directives/give_focus.ts @@ -0,0 +1,28 @@ +/// + +import angular = require('angular'); +import coreModule = require('../core_module'); + +coreModule.directive('giveFocus', function() { + return function(scope, element, attrs) { + element.click(function(e) { + e.stopPropagation(); + }); + + scope.$watch(attrs.giveFocus, function (newValue) { + if (!newValue) { + return; + } + setTimeout(function() { + element.focus(); + var domEl = element[0]; + if (domEl.setSelectionRange) { + var pos = element.val().length * 2; + domEl.setSelectionRange(pos, pos); + } + }, 200); + }, true); + }; +}); + +export default {}; diff --git a/public/app/core/directives/grafana_version_check.js b/public/app/core/directives/grafana_version_check.js new file mode 100644 index 00000000000..ad50b330587 --- /dev/null +++ b/public/app/core/directives/grafana_version_check.js @@ -0,0 +1,31 @@ +define([ + '../core_module', +], +function (coreModule) { + 'use strict'; + + coreModule.directive('grafanaVersionCheck', function($http, contextSrv) { + return { + restrict: 'A', + link: function(scope, elem) { + if (contextSrv.version === 'master') { + return; + } + + $http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' }) + .then(function(response) { + if (!response.data || !response.data.version) { + return; + } + + if (contextSrv.version !== response.data.version) { + elem.append(' ' + + ' ' + + 'New version available: ' + response.data.version + + ''); + } + }); + } + }; + }); +}); diff --git a/public/app/core/directives/metric_segment.js b/public/app/core/directives/metric_segment.js new file mode 100644 index 00000000000..6fdc171c1f8 --- /dev/null +++ b/public/app/core/directives/metric_segment.js @@ -0,0 +1,212 @@ +define([ + 'lodash', + 'jquery', + '../core_module', +], +function (_, $, coreModule) { + 'use strict'; + + coreModule.directive('metricSegment', function($compile, $sce) { + var inputTemplate = ''; + + var buttonTemplate = ''; + + return { + scope: { + segment: "=", + getOptions: "&", + onChange: "&", + }, + + link: function($scope, elem) { + var $input = $(inputTemplate); + var $button = $(buttonTemplate); + var segment = $scope.segment; + var options = null; + var cancelBlur = null; + + $input.appendTo(elem); + $button.appendTo(elem); + + $scope.updateVariableValue = function(value) { + if (value === '' || segment.value === value) { + return; + } + + $scope.$apply(function() { + var selected = _.findWhere($scope.altSegments, { value: value }); + if (selected) { + segment.value = selected.value; + segment.html = selected.html; + segment.fake = false; + segment.expandable = selected.expandable; + } + else if (segment.custom !== 'false') { + segment.value = value; + segment.html = $sce.trustAsHtml(value); + segment.expandable = true; + segment.fake = false; + } + + $scope.onChange(); + }); + }; + + $scope.switchToLink = function(now) { + if (now === true || cancelBlur) { + clearTimeout(cancelBlur); + cancelBlur = null; + $input.hide(); + $button.show(); + $scope.updateVariableValue($input.val()); + } + else { + // need to have long delay because the blur + // happens long before the click event on the typeahead options + cancelBlur = setTimeout($scope.switchToLink, 100); + } + }; + + $scope.source = function(query, callback) { + if (options) { return options; } + + $scope.$apply(function() { + $scope.getOptions().then(function(altSegments) { + $scope.altSegments = altSegments; + options = _.map($scope.altSegments, function(alt) { return alt.value; }); + + // add custom values + if (segment.custom !== 'false') { + if (!segment.fake && _.indexOf(options, segment.value) === -1) { + options.unshift(segment.value); + } + } + + callback(options); + }); + }); + }; + + $scope.updater = function(value) { + if (value === segment.value) { + clearTimeout(cancelBlur); + $input.focus(); + return value; + } + + $input.val(value); + $scope.switchToLink(true); + + return value; + }; + + $scope.matcher = function(item) { + var str = this.query; + if (str[0] === '/') { str = str.substring(1); } + if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); } + try { + return item.toLowerCase().match(str); + } catch(e) { + return false; + } + }; + + $input.attr('data-provide', 'typeahead'); + $input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater, matcher: $scope.matcher }); + + var typeahead = $input.data('typeahead'); + typeahead.lookup = function () { + this.query = this.$element.val() || ''; + var items = this.source(this.query, $.proxy(this.process, this)); + return items ? this.process(items) : items; + }; + + $button.keydown(function(evt) { + // trigger typeahead on down arrow or enter key + if (evt.keyCode === 40 || evt.keyCode === 13) { + $button.click(); + } + }); + + $button.click(function() { + options = null; + $input.css('width', ($button.width() + 16) + 'px'); + + $button.hide(); + $input.show(); + $input.focus(); + + var typeahead = $input.data('typeahead'); + if (typeahead) { + $input.val(''); + typeahead.lookup(); + } + }); + + $input.blur($scope.switchToLink); + + $compile(elem.contents())($scope); + } + }; + }); + + coreModule.directive('metricSegmentModel', function(uiSegmentSrv, $q) { + return { + template: '', + restrict: 'E', + scope: { + property: "=", + options: "=", + getOptions: "&", + onChange: "&", + }, + link: { + pre: function postLink($scope, elem, attrs) { + + $scope.valueToSegment = function(value) { + var option = _.findWhere($scope.options, {value: value}); + var segment = { + cssClass: attrs.cssClass, + custom: attrs.custom, + value: option ? option.text : value, + }; + return uiSegmentSrv.newSegment(segment); + }; + + $scope.getOptionsInternal = function() { + if ($scope.options) { + var optionSegments = _.map($scope.options, function(option) { + return uiSegmentSrv.newSegment({value: option.text}); + }); + return $q.when(optionSegments); + } else { + return $scope.getOptions(); + } + }; + + $scope.onSegmentChange = function() { + if ($scope.options) { + var option = _.findWhere($scope.options, {text: $scope.segment.value}); + if (option && option.value !== $scope.property) { + $scope.property = option.value; + } + } else { + $scope.property = $scope.segment.value; + } + + // needs to call this after digest so + // property is synced with outerscope + $scope.$$postDigest(function() { + $scope.onChange(); + }); + }; + + $scope.segment = $scope.valueToSegment($scope.property); + } + } + }; + }); +}); diff --git a/public/app/core/directives/misc.js b/public/app/core/directives/misc.js new file mode 100644 index 00000000000..5733774c3bd --- /dev/null +++ b/public/app/core/directives/misc.js @@ -0,0 +1,122 @@ +define([ + 'angular', + 'kbn', + '../core_module', +], +function (angular, kbn, coreModule) { + 'use strict'; + + coreModule.directive('tip', function($compile) { + return { + restrict: 'E', + link: function(scope, elem, attrs) { + var _t = ''; + _t = _t.replace(/{/g, '\\{').replace(/}/g, '\\}'); + elem.replaceWith($compile(angular.element(_t))(scope)); + } + }; + }); + + coreModule.directive('watchChange', function() { + return { + scope: { onchange: '&watchChange' }, + link: function(scope, element) { + element.on('input', function() { + scope.$apply(function () { + scope.onchange({ inputValue: element.val() }); + }); + }); + } + }; + }); + + coreModule.directive('editorOptBool', function($compile) { + return { + restrict: 'E', + link: function(scope, elem, attrs) { + var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : ''; + var tip = attrs.tip ? (' ' + attrs.tip + '') : ''; + var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : ''; + + var template = '
' + + ' ' + + '' + + ' '; + elem.replaceWith($compile(angular.element(template))(scope)); + } + }; + }); + + coreModule.directive('editorCheckbox', function($compile, $interpolate) { + return { + restrict: 'E', + link: function(scope, elem, attrs) { + var text = $interpolate(attrs.text)(scope); + var model = $interpolate(attrs.model)(scope); + var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : ''; + var tip = attrs.tip ? (' ' + attrs.tip + '') : ''; + var label = ''; + + var template = '' + + ' '; + + template = label + template; + elem.replaceWith($compile(angular.element(template))(scope)); + } + }; + }); + + coreModule.directive('gfDropdown', function ($parse, $compile, $timeout) { + function buildTemplate(items, placement) { + var upclass = placement === 'top' ? 'dropup' : ''; + var ul = [ + '' + ]; + + angular.forEach(items, function (item, index) { + if (item.divider) { + return ul.splice(index + 1, 0, '
  • '); + } + + var li = '' + + '' + (item.text || '') + ''; + + if (item.submenu && item.submenu.length) { + li += buildTemplate(item.submenu).join('\n'); + } + + li += ''; + ul.splice(index + 1, 0, li); + }); + return ul; + } + + return { + restrict: 'EA', + scope: true, + link: function postLink(scope, iElement, iAttrs) { + var getter = $parse(iAttrs.gfDropdown), items = getter(scope); + $timeout(function () { + var placement = iElement.data('placement'); + var dropdown = angular.element(buildTemplate(items, placement).join('')); + dropdown.insertAfter(iElement); + $compile(iElement.next('ul.dropdown-menu'))(scope); + }); + + iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown'); + } + }; + }); + +}); diff --git a/public/app/core/directives/ng_model_on_blur.js b/public/app/core/directives/ng_model_on_blur.js new file mode 100644 index 00000000000..3c0a6cd70de --- /dev/null +++ b/public/app/core/directives/ng_model_on_blur.js @@ -0,0 +1,54 @@ +define([ + 'kbn', + '../core_module', +], +function (kbn, coreModule) { + 'use strict'; + + coreModule.directive('ngModelOnblur', function() { + return { + restrict: 'A', + priority: 1, + require: 'ngModel', + link: function(scope, elm, attr, ngModelCtrl) { + if (attr.type === 'radio' || attr.type === 'checkbox') { + return; + } + + elm.off('input keydown change'); + elm.bind('blur', function() { + scope.$apply(function() { + ngModelCtrl.$setViewValue(elm.val()); + }); + }); + } + }; + }); + + coreModule.directive('emptyToNull', function () { + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + ctrl.$parsers.push(function (viewValue) { + if(viewValue === "") { return null; } + return viewValue; + }); + } + }; + }); + + coreModule.directive('validTimeSpan', function() { + return { + require: 'ngModel', + link: function(scope, elm, attrs, ctrl) { + ctrl.$validators.integer = function(modelValue, viewValue) { + if (ctrl.$isEmpty(modelValue)) { + return true; + } + return kbn.isValidTimeSpan(viewValue); + }; + } + }; + }); +}); diff --git a/public/app/core/directives/password_strenght.js b/public/app/core/directives/password_strenght.js new file mode 100644 index 00000000000..b53113f9365 --- /dev/null +++ b/public/app/core/directives/password_strenght.js @@ -0,0 +1,45 @@ +define([ + '../core_module', +], +function (coreModule) { + 'use strict'; + + coreModule.directive('passwordStrength', function() { + var template = '
    ' + + '{{strengthText}}' + + '
    '; + return { + template: template, + scope: { + password: "=", + }, + link: function($scope) { + + $scope.strengthClass = ''; + + function passwordChanged(newValue) { + if (!newValue) { + $scope.strengthText = ""; + $scope.strengthClass = "hidden"; + return; + } + if (newValue.length < 4) { + $scope.strengthText = "strength: weak sauce."; + $scope.strengthClass = "password-strength-bad"; + return; + } + if (newValue.length <= 8) { + $scope.strengthText = "strength: you can do better."; + $scope.strengthClass = "password-strength-ok"; + return; + } + + $scope.strengthText = "strength: strong like a bull."; + $scope.strengthClass = "password-strength-good"; + } + + $scope.$watch("password", passwordChanged); + } + }; + }); +}); diff --git a/public/app/core/directives/spectrum_picker.js b/public/app/core/directives/spectrum_picker.js new file mode 100644 index 00000000000..92fc3982d7a --- /dev/null +++ b/public/app/core/directives/spectrum_picker.js @@ -0,0 +1,41 @@ +define([ + 'angular', + '../core_module', + 'spectrum', +], +function (angular, coreModule) { + 'use strict'; + + coreModule.directive('spectrumPicker', function() { + return { + restrict: 'E', + require: 'ngModel', + scope: false, + replace: true, + template: "", + link: function(scope, element, attrs, ngModel) { + var input = element.find('input'); + var options = angular.extend({ + showAlpha: true, + showButtons: false, + color: ngModel.$viewValue, + change: function(color) { + scope.$apply(function() { + ngModel.$setViewValue(color.toRgbString()); + }); + } + }, scope.$eval(attrs.options)); + + ngModel.$render = function() { + input.spectrum('set', ngModel.$viewValue || ''); + }; + + input.spectrum(options); + + scope.$on('$destroy', function() { + input.spectrum('destroy'); + }); + } + }; + }); +}); diff --git a/public/app/directives/tags.js b/public/app/core/directives/tags.js similarity index 94% rename from public/app/directives/tags.js rename to public/app/core/directives/tags.js index f408a5e3864..01f4c4f3c37 100644 --- a/public/app/directives/tags.js +++ b/public/app/core/directives/tags.js @@ -1,9 +1,10 @@ define([ 'angular', 'jquery', - 'bootstrap-tagsinput' + '../core_module', + 'bootstrap-tagsinput', ], -function (angular, $) { +function (angular, $, coreModule) { 'use strict'; function djb2(str) { @@ -38,9 +39,7 @@ function (angular, $) { element.css("border-color", borderColor); } - angular - .module('grafana.directives') - .directive('tagColorFromName', function() { + coreModule.directive('tagColorFromName', function() { return { scope: { tagColorFromName: "=" }, link: function (scope, element) { @@ -49,9 +48,7 @@ function (angular, $) { }; }); - angular - .module('grafana.directives') - .directive('bootstrapTagsinput', function() { + coreModule.directive('bootstrapTagsinput', function() { function getItemProperty(scope, property) { if (!property) { diff --git a/public/app/core/directives/topnav.js b/public/app/core/directives/topnav.js new file mode 100644 index 00000000000..38aa9a9e6b3 --- /dev/null +++ b/public/app/core/directives/topnav.js @@ -0,0 +1,50 @@ +define([ + '../core_module', +], +function (coreModule) { + 'use strict'; + + coreModule.directive('topnav', function($rootScope, contextSrv) { + return { + restrict: 'E', + transclude: true, + scope: { + title: "@", + section: "@", + titleAction: "&", + subnav: "=", + }, + template: + '', + link: function(scope, elem, attrs) { + scope.icon = attrs.icon; + scope.contextSrv = contextSrv; + + scope.toggle = function() { + $rootScope.appEvent('toggle-sidemenu'); + }; + } + }; + }); + +}); diff --git a/public/app/core/directives/value_select_dropdown.js b/public/app/core/directives/value_select_dropdown.js new file mode 100644 index 00000000000..4f5076abd2f --- /dev/null +++ b/public/app/core/directives/value_select_dropdown.js @@ -0,0 +1,284 @@ +define([ + 'angular', + 'lodash', + '../core_module', +], +function (angular, _, coreModule) { + 'use strict'; + + coreModule.controller('ValueSelectDropdownCtrl', function($q) { + var vm = this; + + vm.show = function() { + vm.oldVariableText = vm.variable.current.text; + vm.highlightIndex = -1; + + vm.options = vm.variable.options; + vm.selectedValues = _.filter(vm.options, {selected: true}); + + vm.tags = _.map(vm.variable.tags, function(value) { + var tag = { text: value, selected: false }; + _.each(vm.variable.current.tags, function(tagObj) { + if (tagObj.text === value) { + tag = tagObj; + } + }); + return tag; + }); + + vm.search = { + query: '', + options: vm.options.slice(0, Math.min(vm.options.length, 1000)) + }; + + vm.dropdownVisible = true; + }; + + vm.updateLinkText = function() { + var current = vm.variable.current; + + if (current.tags && current.tags.length) { + // filer out values that are in selected tags + var selectedAndNotInTag = _.filter(vm.variable.options, function(option) { + if (!option.selected) { return false; } + for (var i = 0; i < current.tags.length; i++) { + var tag = current.tags[i]; + if (_.indexOf(tag.values, option.value) !== -1) { + return false; + } + } + return true; + }); + + // convert values to text + var currentTexts = _.pluck(selectedAndNotInTag, 'text'); + + // join texts + vm.linkText = currentTexts.join(' + '); + if (vm.linkText.length > 0) { + vm.linkText += ' + '; + } + } else { + vm.linkText = vm.variable.current.text; + } + }; + + vm.clearSelections = function() { + _.each(vm.options, function(option) { + option.selected = false; + }); + + vm.selectionsChanged(false); + }; + + vm.selectTag = function(tag) { + tag.selected = !tag.selected; + var tagValuesPromise; + if (!tag.values) { + tagValuesPromise = vm.getValuesForTag({tagKey: tag.text}); + } else { + tagValuesPromise = $q.when(tag.values); + } + + tagValuesPromise.then(function(values) { + tag.values = values; + tag.valuesText = values.join(' + '); + _.each(vm.options, function(option) { + if (_.indexOf(tag.values, option.value) !== -1) { + option.selected = tag.selected; + } + }); + + vm.selectionsChanged(false); + }); + }; + + vm.keyDown = function (evt) { + if (evt.keyCode === 27) { + vm.hide(); + } + if (evt.keyCode === 40) { + vm.moveHighlight(1); + } + if (evt.keyCode === 38) { + vm.moveHighlight(-1); + } + if (evt.keyCode === 13) { + if (vm.search.options.length === 0) { + vm.commitChanges(); + } else { + vm.selectValue(vm.search.options[vm.highlightIndex], {}, true, false); + } + } + if (evt.keyCode === 32) { + vm.selectValue(vm.search.options[vm.highlightIndex], {}, false, false); + } + }; + + vm.moveHighlight = function(direction) { + vm.highlightIndex = (vm.highlightIndex + direction) % vm.search.options.length; + }; + + vm.selectValue = function(option, event, commitChange, excludeOthers) { + if (!option) { return; } + + option.selected = !option.selected; + + commitChange = commitChange || false; + excludeOthers = excludeOthers || false; + + var setAllExceptCurrentTo = function(newValue) { + _.each(vm.options, function(other) { + if (option !== other) { other.selected = newValue; } + }); + }; + + // commit action (enter key), should not deselect it + if (commitChange) { + option.selected = true; + } + + if (option.text === 'All' || excludeOthers) { + setAllExceptCurrentTo(false); + commitChange = true; + } + else if (!vm.variable.multi) { + setAllExceptCurrentTo(false); + commitChange = true; + } else if (event.ctrlKey || event.metaKey || event.shiftKey) { + commitChange = true; + setAllExceptCurrentTo(false); + } + + vm.selectionsChanged(commitChange); + }; + + vm.selectionsChanged = function(commitChange) { + vm.selectedValues = _.filter(vm.options, {selected: true}); + + if (vm.selectedValues.length > 1 && vm.selectedValues.length !== vm.options.length) { + if (vm.selectedValues[0].text === 'All') { + vm.selectedValues[0].selected = false; + vm.selectedValues = vm.selectedValues.slice(1, vm.selectedValues.length); + } + } + + // validate selected tags + _.each(vm.tags, function(tag) { + if (tag.selected) { + _.each(tag.values, function(value) { + if (!_.findWhere(vm.selectedValues, {value: value})) { + tag.selected = false; + } + }); + } + }); + + vm.selectedTags = _.filter(vm.tags, {selected: true}); + vm.variable.current.value = _.pluck(vm.selectedValues, 'value'); + vm.variable.current.text = _.pluck(vm.selectedValues, 'text').join(' + '); + vm.variable.current.tags = vm.selectedTags; + + // only single value + if (vm.selectedValues.length === 1) { + vm.variable.current.value = vm.selectedValues[0].value; + } + + if (commitChange) { + vm.commitChanges(); + } + }; + + vm.commitChanges = function() { + // if we have a search query and no options use that + if (vm.search.options.length === 0 && vm.search.query.length > 0) { + vm.variable.current = {text: vm.search.query, value: vm.search.query}; + } + else if (vm.selectedValues.length === 0) { + // make sure one option is selected + vm.options[0].selected = true; + vm.selectionsChanged(false); + } + + vm.dropdownVisible = false; + vm.updateLinkText(); + + if (vm.variable.current.text !== vm.oldVariableText) { + vm.onUpdated(); + } + }; + + vm.queryChanged = function() { + vm.highlightIndex = -1; + vm.search.options = _.filter(vm.options, function(option) { + return option.text.toLowerCase().indexOf(vm.search.query.toLowerCase()) !== -1; + }); + + vm.search.options = vm.search.options.slice(0, Math.min(vm.search.options.length, 1000)); + }; + + vm.init = function() { + vm.selectedTags = vm.variable.current.tags || []; + vm.updateLinkText(); + }; + + }); + + coreModule.directive('valueSelectDropdown', function($compile, $window, $timeout, $rootScope) { + return { + scope: { variable: "=", onUpdated: "&", getValuesForTag: "&" }, + templateUrl: 'app/partials/valueSelectDropdown.html', + controller: 'ValueSelectDropdownCtrl', + controllerAs: 'vm', + bindToController: true, + link: function(scope, elem) { + var bodyEl = angular.element($window.document.body); + var linkEl = elem.find('.variable-value-link'); + var inputEl = elem.find('input'); + + function openDropdown() { + inputEl.css('width', Math.max(linkEl.width(), 30) + 'px'); + + inputEl.show(); + linkEl.hide(); + + inputEl.focus(); + $timeout(function() { bodyEl.on('click', bodyOnClick); }, 0, false); + } + + function switchToLink() { + inputEl.hide(); + linkEl.show(); + bodyEl.off('click', bodyOnClick); + } + + function bodyOnClick (e) { + if (elem.has(e.target).length === 0) { + scope.$apply(function() { + scope.vm.commitChanges(); + }); + } + } + + scope.$watch('vm.dropdownVisible', function(newValue) { + if (newValue) { + openDropdown(); + } else { + switchToLink(); + } + }); + + var cleanUp = $rootScope.$on('template-variable-value-updated', function() { + scope.vm.updateLinkText(); + }); + + scope.$on("$destroy", function() { + cleanUp(); + }); + + scope.vm.init(); + }, + }; + }); + +}); diff --git a/public/app/core/filters/filters.ts b/public/app/core/filters/filters.ts new file mode 100644 index 00000000000..7ebba903686 --- /dev/null +++ b/public/app/core/filters/filters.ts @@ -0,0 +1,74 @@ +/// + +import angular = require('angular'); +import jquery = require('jquery'); +import moment = require('moment'); +import _ = require('lodash'); +import coreModule = require('../core_module'); + +coreModule.filter('stringSort', function() { + return function(input) { + return input.sort(); + }; +}); + +coreModule.filter('slice', function() { + return function(arr, start, end) { + if (!_.isUndefined(arr)) { + return arr.slice(start, end); + } + }; +}); + +coreModule.filter('stringify', function() { + return function(arr) { + if (_.isObject(arr) && !_.isArray(arr)) { + return angular.toJson(arr); + } else { + return _.isNull(arr) ? null : arr.toString(); + } + }; +}); + +coreModule.filter('moment', function() { + return function(date, mode) { + switch (mode) { + case 'ago': + return moment(date).fromNow(); + } + return moment(date).fromNow(); + }; +}); + +coreModule.filter('noXml', function() { + var noXml = function(text) { + return _.isString(text) + ? text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + : text; + }; + return function(text) { + return _.isArray(text) + ? _.map(text, noXml) + : noXml(text); + }; +}); + +coreModule.filter('interpolateTemplateVars', function (templateSrv) { + var filterFunc : any = function (text, scope) { + if (scope.panel) { + return templateSrv.replaceWithText(text, scope.panel.scopedVars); + } else { + return templateSrv.replaceWithText(text, scope.row.scopedVars); + } + }; + + filterFunc.$stateful = true; + return filterFunc; +}); + +export {}; diff --git a/public/app/routes/all.js b/public/app/core/routes/all.js similarity index 76% rename from public/app/routes/all.js rename to public/app/core/routes/all.js index 026eccc1d45..a16af0836dd 100644 --- a/public/app/routes/all.js +++ b/public/app/core/routes/all.js @@ -1,14 +1,16 @@ define([ 'angular', - './dashLoadControllers', -], function(angular) { + '../core_module', + './bundle_loader', + './dashboard_loaders', +], function(angular, coreModule, BundleLoader) { "use strict"; - var module = angular.module('grafana.routes'); - - module.config(function($routeProvider, $locationProvider) { + coreModule.config(function($routeProvider, $locationProvider) { $locationProvider.html5Mode(true); + var loadOrgBundle = new BundleLoader.BundleLoader('features/org/all'); + $routeProvider .when('/', { templateUrl: 'app/partials/dashboard.html', @@ -41,30 +43,37 @@ define([ .when('/datasources', { templateUrl: 'app/features/org/partials/datasources.html', controller : 'DataSourcesCtrl', + resolve: loadOrgBundle, }) .when('/datasources/edit/:id', { templateUrl: 'app/features/org/partials/datasourceEdit.html', controller : 'DataSourceEditCtrl', + resolve: loadOrgBundle, }) .when('/datasources/new', { templateUrl: 'app/features/org/partials/datasourceEdit.html', controller : 'DataSourceEditCtrl', + resolve: loadOrgBundle, }) .when('/org', { templateUrl: 'app/features/org/partials/orgDetails.html', controller : 'OrgDetailsCtrl', + resolve: loadOrgBundle, }) .when('/org/new', { templateUrl: 'app/features/org/partials/newOrg.html', controller : 'NewOrgCtrl', + resolve: loadOrgBundle, }) .when('/org/users', { templateUrl: 'app/features/org/partials/orgUsers.html', controller : 'OrgUsersCtrl', + resolve: loadOrgBundle, }) .when('/org/apikeys', { templateUrl: 'app/features/org/partials/orgApiKeys.html', controller : 'OrgApiKeysCtrl', + resolve: loadOrgBundle, }) .when('/profile', { templateUrl: 'app/features/profile/partials/profile.html', @@ -74,13 +83,17 @@ define([ templateUrl: 'app/features/profile/partials/password.html', controller : 'ChangePasswordCtrl', }) + .when('/profile/select-org', { + templateUrl: 'app/features/profile/partials/select_org.html', + controller : 'SelectOrgCtrl', + }) .when('/admin/settings', { templateUrl: 'app/features/admin/partials/settings.html', controller : 'AdminSettingsCtrl', }) .when('/admin/users', { templateUrl: 'app/features/admin/partials/users.html', - controller : 'AdminUsersCtrl', + controller : 'AdminListUsersCtrl', }) .when('/admin/users/create', { templateUrl: 'app/features/admin/partials/new_user.html', @@ -92,11 +105,24 @@ define([ }) .when('/admin/orgs', { templateUrl: 'app/features/admin/partials/orgs.html', + controller : 'AdminListOrgsCtrl', + }) + .when('/admin/orgs/edit/:id', { + templateUrl: 'app/features/admin/partials/edit_org.html', + controller : 'AdminEditOrgCtrl', }) .when('/login', { templateUrl: 'app/partials/login.html', controller : 'LoginCtrl', }) + .when('/invite/:code', { + templateUrl: 'app/partials/signup_invited.html', + controller : 'InvitedCtrl', + }) + .when('/signup', { + templateUrl: 'app/partials/signup_step2.html', + controller : 'SignUpCtrl', + }) .when('/user/password/send-reset-email', { templateUrl: 'app/partials/reset_password.html', controller : 'ResetPasswordCtrl', diff --git a/public/app/core/routes/bundle_loader.ts b/public/app/core/routes/bundle_loader.ts new file mode 100644 index 00000000000..6f132d65619 --- /dev/null +++ b/public/app/core/routes/bundle_loader.ts @@ -0,0 +1,23 @@ +/// + +export class BundleLoader { + lazy: any; + loadingDefer: any; + + constructor(bundleName) { + this.lazy = ["$q", "$route", "$rootScope", ($q, $route, $rootScope) => { + if (this.loadingDefer) { + return this.loadingDefer.promise; + } + + this.loadingDefer = $q.defer(); + + require([bundleName], () => { + this.loadingDefer.resolve(); + }); + + return this.loadingDefer.promise; + }]; + + } +} diff --git a/public/app/routes/dashLoadControllers.js b/public/app/core/routes/dashboard_loaders.js similarity index 73% rename from public/app/routes/dashLoadControllers.js rename to public/app/core/routes/dashboard_loaders.js index 9741824dbce..e8a9b80237f 100644 --- a/public/app/routes/dashLoadControllers.js +++ b/public/app/core/routes/dashboard_loaders.js @@ -1,16 +1,10 @@ define([ - 'angular', - 'lodash', - 'kbn', - 'moment', - 'jquery', + '../core_module', ], -function (angular) { +function (coreModule) { "use strict"; - var module = angular.module('grafana.routes'); - - module.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv) { + coreModule.controller('LoadDashboardCtrl', function($scope, $routeParams, dashboardLoaderSrv, backendSrv) { if (!$routeParams.slug) { backendSrv.get('/api/dashboards/home').then(function(result) { @@ -27,7 +21,7 @@ function (angular) { }); - module.controller('DashFromImportCtrl', function($scope, $location, alertSrv) { + coreModule.controller('DashFromImportCtrl', function($scope, $location, alertSrv) { if (!window.grafanaImportDashboard) { alertSrv.set('Not found', 'Cannot reload page with unsaved imported dashboard', 'warning', 7000); $location.path(''); @@ -39,7 +33,7 @@ function (angular) { }, $scope); }); - module.controller('NewDashboardCtrl', function($scope) { + coreModule.controller('NewDashboardCtrl', function($scope) { $scope.initDashboard({ meta: { canStar: false, canShare: false }, dashboard: { diff --git a/public/app/directives/all.js b/public/app/directives/all.js deleted file mode 100644 index 13a8accffbd..00000000000 --- a/public/app/directives/all.js +++ /dev/null @@ -1,22 +0,0 @@ -define([ - './arrayJoin', - './dashUpload', - './grafanaSimplePanel', - './ngBlur', - './dashEditLink', - './ngModelOnBlur', - './misc', - './confirmClick', - './configModal', - './spectrumPicker', - './tags', - './bodyClass', - './valueSelectDropdown', - './metric.segment', - './grafanaVersionCheck', - './dropdown.typeahead', - './topnav', - './giveFocus', - './annotationTooltip', - './passwordStrenght', -], function () {}); diff --git a/public/app/directives/arrayJoin.js b/public/app/directives/arrayJoin.js deleted file mode 100644 index a2b5fbe59dd..00000000000 --- a/public/app/directives/arrayJoin.js +++ /dev/null @@ -1,35 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash' -], -function (angular, app, _) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('arrayJoin', function() { - return { - restrict: 'A', - require: 'ngModel', - link: function(scope, element, attr, ngModel) { - - function split_array(text) { - return (text || '').split(','); - } - - function join_array(text) { - if(_.isArray(text)) { - return (text || '').join(','); - } else { - return text; - } - } - - ngModel.$parsers.push(split_array); - ngModel.$formatters.push(join_array); - } - }; - }); - -}); diff --git a/public/app/directives/bodyClass.js b/public/app/directives/bodyClass.js deleted file mode 100644 index 495390a59e8..00000000000 --- a/public/app/directives/bodyClass.js +++ /dev/null @@ -1,50 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery' -], -function (angular, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('bodyClass', function() { - return { - link: function($scope, elem) { - - var lastHideControlsVal; - - // tooltip removal fix - $scope.$on("$routeChangeSuccess", function() { - $("#tooltip, .tooltip").remove(); - }); - - $scope.$watch('submenuEnabled', function() { - if (!$scope.dashboard) { - return; - } - - elem.toggleClass('submenu-controls-visible', $scope.submenuEnabled); - }); - - $scope.$watch('dashboard.hideControls', function() { - if (!$scope.dashboard) { - return; - } - - var hideControls = $scope.dashboard.hideControls || $scope.playlist_active; - - if (lastHideControlsVal !== hideControls) { - elem.toggleClass('hide-controls', hideControls); - lastHideControlsVal = hideControls; - } - }); - - $scope.$watch('playlistSrv', function(newValue) { - elem.toggleClass('playlist-active', _.isObject(newValue)); - }); - } - }; - }); - -}); diff --git a/public/app/directives/configModal.js b/public/app/directives/configModal.js deleted file mode 100644 index b7e579e7026..00000000000 --- a/public/app/directives/configModal.js +++ /dev/null @@ -1,48 +0,0 @@ -define([ - 'angular', - 'lodash', - 'jquery' -], -function (angular, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('configModal', function($modal, $q, $timeout) { - return { - restrict: 'A', - link: function(scope, elem, attrs) { - var partial = attrs.configModal; - var id = '#' + partial.replace('.html', '').replace(/[\/|\.|:]/g, '-') + '-' + scope.$id; - - elem.bind('click',function() { - if ($(id).length) { - elem.attr('data-target', id).attr('data-toggle', 'modal'); - scope.$apply(function() { scope.$broadcast('modal-opened'); }); - return; - } - - var panelModal = $modal({ - template: partial, - persist: true, - show: false, - scope: scope, - keyboard: false - }); - - $q.when(panelModal).then(function(modalEl) { - elem.attr('data-target', id).attr('data-toggle', 'modal'); - - $timeout(function () { - if (!modalEl.data('modal').isShown) { - modalEl.modal('show'); - } - }, 50); - }); - - scope.$apply(); - }); - } - }; - }); -}); diff --git a/public/app/directives/dashEditLink.js b/public/app/directives/dashEditLink.js deleted file mode 100644 index 6145fb46b57..00000000000 --- a/public/app/directives/dashEditLink.js +++ /dev/null @@ -1,123 +0,0 @@ -define([ - 'angular', - 'jquery' -], -function (angular, $) { - 'use strict'; - - var editViewMap = { - 'settings': { src: 'app/features/dashboard/partials/settings.html', title: "Settings" }, - 'annotations': { src: 'app/features/annotations/partials/editor.html', title: "Annotations" }, - 'templating': { src: 'app/features/templating/partials/editor.html', title: "Templating" } - }; - - angular - .module('grafana.directives') - .directive('dashEditorLink', function($timeout) { - return { - restrict: 'A', - link: function(scope, elem, attrs) { - var partial = attrs.dashEditorLink; - - elem.bind('click',function() { - $timeout(function() { - var editorScope = attrs.editorScope === 'isolated' ? null : scope; - scope.appEvent('show-dash-editor', { src: partial, scope: editorScope }); - }); - }); - } - }; - }); - - angular - .module('grafana.directives') - .directive('dashEditorView', function($compile, $location) { - return { - restrict: 'A', - link: function(scope, elem) { - var editorScope; - var lastEditor; - - function hideScrollbars(value) { - if (value) { - window.scrollTo(0,0); - document.documentElement.style.overflow = 'hidden'; // firefox, chrome - document.body.scroll = "no"; // ie only - } else { - document.documentElement.style.overflow = 'auto'; - document.body.scroll = "yes"; - } - } - - function hideEditorPane() { - hideScrollbars(false); - if (editorScope) { editorScope.dismiss(); } - } - - function showEditorPane(evt, payload, editview) { - if (editview) { - scope.contextSrv.editview = editViewMap[editview]; - payload.src = scope.contextSrv.editview.src; - } - - if (lastEditor === payload.src) { - hideEditorPane(); - return; - } - - hideEditorPane(); - - scope.exitFullscreen(); - - lastEditor = payload.src; - editorScope = payload.scope ? payload.scope.$new() : scope.$new(); - - editorScope.dismiss = function() { - editorScope.$destroy(); - elem.empty(); - lastEditor = null; - editorScope = null; - hideScrollbars(false); - - if (editview) { - var urlParams = $location.search(); - if (editview === urlParams.editview) { - delete urlParams.editview; - $location.search(urlParams); - } - } - }; - - // hide page scrollbars while edit pane is visible - hideScrollbars(true); - - var src = "'" + payload.src + "'"; - var view = $('
    '); - - if (payload.cssClass) { - view.addClass(payload.cssClass); - } - - elem.append(view); - $compile(elem.contents())(editorScope); - } - - scope.$watch("dashboardViewState.state.editview", function(newValue, oldValue) { - if (newValue) { - showEditorPane(null, {}, newValue); - } else if (oldValue) { - scope.contextSrv.editview = null; - if (lastEditor === editViewMap[oldValue]) { - hideEditorPane(); - } - } - }); - - scope.contextSrv.editview = null; - scope.$on("$destroy", hideEditorPane); - scope.onAppEvent('hide-dash-editor', hideEditorPane); - scope.onAppEvent('show-dash-editor', showEditorPane); - } - }; - }); -}); diff --git a/public/app/directives/dropdown.typeahead.js b/public/app/directives/dropdown.typeahead.js deleted file mode 100644 index 0ab2d0045fb..00000000000 --- a/public/app/directives/dropdown.typeahead.js +++ /dev/null @@ -1,116 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash', - 'jquery', -], -function (angular, app, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('dropdownTypeahead', function($compile) { - - var inputTemplate = ''; - - var buttonTemplate = ''; - - return { - scope: { - menuItems: "=dropdownTypeahead", - dropdownTypeaheadOnSelect: "&dropdownTypeaheadOnSelect", - model: '=ngModel' - }, - link: function($scope, elem, attrs) { - var $input = $(inputTemplate); - var $button = $(buttonTemplate); - $input.appendTo(elem); - $button.appendTo(elem); - - if (attrs.linkText) { - $button.html(attrs.linkText); - } - - if (attrs.ngModel) { - $scope.$watch('model', function(newValue) { - _.each($scope.menuItems, function(item) { - _.each(item.submenu, function(subItem) { - if (subItem.value === newValue) { - $button.html(subItem.text); - } - }); - }); - }); - } - - var typeaheadValues = _.reduce($scope.menuItems, function(memo, value, index) { - _.each(value.submenu, function(item, subIndex) { - item.click = 'menuItemSelected(' + index + ',' + subIndex + ')'; - memo.push(value.text + ' ' + item.text); - }); - return memo; - }, []); - - $scope.menuItemSelected = function(index, subIndex) { - var item = $scope.menuItems[index]; - $scope.dropdownTypeaheadOnSelect({$item: item, $subItem: item.submenu[subIndex]}); - }; - - $input.attr('data-provide', 'typeahead'); - $input.typeahead({ - source: typeaheadValues, - minLength: 1, - items: 10, - updater: function (value) { - var result = {}; - _.each($scope.menuItems, function(menuItem) { - _.each(menuItem.submenu, function(submenuItem) { - if (value === (menuItem.text + ' ' + submenuItem.text)) { - result.$item = menuItem; - result.$subItem = submenuItem; - } - }); - }); - - if (result.$item) { - $scope.$apply(function() { - $scope.dropdownTypeaheadOnSelect(result); - }); - } - - $input.trigger('blur'); - return ''; - } - }); - - $button.click(function() { - $button.hide(); - $input.show(); - $input.focus(); - }); - - $input.keyup(function() { - elem.toggleClass('open', $input.val() === ''); - }); - - $input.blur(function() { - $input.hide(); - $input.val(''); - $button.show(); - $button.focus(); - // clicking the function dropdown menu wont - // work if you remove class at once - setTimeout(function() { - elem.removeClass('open'); - }, 200); - }); - - $compile(elem.contents())($scope); - } - }; - }); -}); diff --git a/public/app/directives/giveFocus.js b/public/app/directives/giveFocus.js deleted file mode 100644 index ef395d27fbd..00000000000 --- a/public/app/directives/giveFocus.js +++ /dev/null @@ -1,26 +0,0 @@ -define([ - 'angular' -], -function (angular) { - 'use strict'; - - angular.module('grafana.directives').directive('giveFocus', function() { - return function(scope, element, attrs) { - element.click(function(e) { - e.stopPropagation(); - }); - - scope.$watch(attrs.giveFocus,function (newValue) { - if (!newValue) { - return; - } - setTimeout(function() { - element.focus(); - var pos = element.val().length * 2; - element[0].setSelectionRange(pos, pos); - }, 200); - },true); - }; - }); - -}); diff --git a/public/app/directives/grafanaSimplePanel.js b/public/app/directives/grafanaSimplePanel.js deleted file mode 100644 index ce34e2841e7..00000000000 --- a/public/app/directives/grafanaSimplePanel.js +++ /dev/null @@ -1,66 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('grafanaSimplePanel', function($compile) { - var panelLoading = '' + - ''+ - ' loading ...' + - ''+ - ''; - - return { - restrict: 'E', - link: function($scope, elem, attr) { - - // once we have the template, scan it for controllers and - // load the module.js if we have any - - // compile the module and uncloack. We're done - function loadModule($module) { - $module.appendTo(elem); - /* jshint indent:false */ - $compile(elem.contents())($scope); - elem.removeClass("ng-cloak"); - } - - function loadController(name) { - elem.addClass("ng-cloak"); - // load the panels module file, then render it in the dom. - var nameAsPath = name.replace(".", "/"); - $scope.require([ - 'jquery', - 'text!panels/'+nameAsPath+'/module.html' - ], function ($, moduleTemplate) { - var $module = $(moduleTemplate); - // top level controllers - var $controllers = $module.filter('ngcontroller, [ng-controller], .ng-controller'); - // add child controllers - $controllers = $controllers.add($module.find('ngcontroller, [ng-controller], .ng-controller')); - - if ($controllers.length) { - $controllers.first().prepend(panelLoading); - $scope.require([ - 'panels/'+nameAsPath+'/module' - ], function() { - loadModule($module); - }); - } else { - loadModule($module); - } - }); - } - - $scope.$watch(attr.type, function (name) { - loadController(name); - }); - - } - }; - }); - -}); diff --git a/public/app/directives/grafanaVersionCheck.js b/public/app/directives/grafanaVersionCheck.js deleted file mode 100644 index c98ce269381..00000000000 --- a/public/app/directives/grafanaVersionCheck.js +++ /dev/null @@ -1,33 +0,0 @@ -define([ - 'angular' -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('grafanaVersionCheck', function($http, contextSrv) { - return { - restrict: 'A', - link: function(scope, elem) { - if (contextSrv.version === 'master') { - return; - } - - $http({ method: 'GET', url: 'https://grafanarel.s3.amazonaws.com/latest.json' }) - .then(function(response) { - if (!response.data || !response.data.version) { - return; - } - - if (contextSrv.version !== response.data.version) { - elem.append(' ' + - ' ' + - 'New version available: ' + response.data.version + - ''); - } - }); - } - }; - }); -}); diff --git a/public/app/directives/metric.segment.js b/public/app/directives/metric.segment.js deleted file mode 100644 index 4202cfdc332..00000000000 --- a/public/app/directives/metric.segment.js +++ /dev/null @@ -1,155 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash', - 'jquery', -], -function (angular, app, _, $) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('metricSegment', function($compile, $sce) { - var inputTemplate = ''; - - var buttonTemplate = ''; - - return { - scope: { - segment: "=", - getAltSegments: "&", - onValueChanged: "&" - }, - - link: function($scope, elem) { - var $input = $(inputTemplate); - var $button = $(buttonTemplate); - var segment = $scope.segment; - var options = null; - var cancelBlur = null; - - $input.appendTo(elem); - $button.appendTo(elem); - - $scope.updateVariableValue = function(value) { - if (value === '' || segment.value === value) { - return; - } - - $scope.$apply(function() { - var selected = _.findWhere($scope.altSegments, { value: value }); - if (selected) { - segment.value = selected.value; - segment.html = selected.html; - segment.fake = false; - segment.expandable = selected.expandable; - } - else { - segment.value = value; - segment.html = $sce.trustAsHtml(value); - segment.expandable = true; - segment.fake = false; - } - $scope.onValueChanged(); - }); - }; - - $scope.switchToLink = function(now) { - if (now === true || cancelBlur) { - clearTimeout(cancelBlur); - cancelBlur = null; - $input.hide(); - $button.show(); - $scope.updateVariableValue($input.val()); - } - else { - // need to have long delay because the blur - // happens long before the click event on the typeahead options - cancelBlur = setTimeout($scope.switchToLink, 50); - } - }; - - $scope.source = function(query, callback) { - if (options) { return options; } - - $scope.$apply(function() { - $scope.getAltSegments().then(function(altSegments) { - $scope.altSegments = altSegments; - options = _.map($scope.altSegments, function(alt) { return alt.value; }); - - // add custom values - if (!segment.fake && _.indexOf(options, segment.value) === -1) { - options.unshift(segment.value); - } - - callback(options); - }); - }); - }; - - $scope.updater = function(value) { - if (value === segment.value) { - clearTimeout(cancelBlur); - $input.focus(); - return value; - } - - $input.val(value); - $scope.switchToLink(true); - - return value; - }; - - $scope.matcher = function(item) { - var str = this.query; - if (str[0] === '/') { str = str.substring(1); } - if (str[str.length - 1] === '/') { str = str.substring(0, str.length-1); } - try { - return item.toLowerCase().match(str); - } catch(e) { - return false; - } - }; - - $input.attr('data-provide', 'typeahead'); - $input.typeahead({ source: $scope.source, minLength: 0, items: 10000, updater: $scope.updater, matcher: $scope.matcher }); - - var typeahead = $input.data('typeahead'); - typeahead.lookup = function () { - this.query = this.$element.val() || ''; - var items = this.source(this.query, $.proxy(this.process, this)); - return items ? this.process(items) : items; - }; - - $button.keydown(function(evt) { - // trigger typeahead on down arrow or enter key - if (evt.keyCode === 40 || evt.keyCode === 13) { - $button.click(); - } - }); - - $button.click(function() { - options = null; - $input.css('width', ($button.width() + 16) + 'px'); - - $button.hide(); - $input.show(); - $input.focus(); - - var typeahead = $input.data('typeahead'); - if (typeahead) { - $input.val(''); - typeahead.lookup(); - } - }); - - $input.blur($scope.switchToLink); - - $compile(elem.contents())($scope); - } - }; - }); -}); diff --git a/public/app/directives/misc.js b/public/app/directives/misc.js deleted file mode 100644 index 11c1334d553..00000000000 --- a/public/app/directives/misc.js +++ /dev/null @@ -1,130 +0,0 @@ -define([ - 'angular', - 'kbn' -], -function (angular, kbn) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('tip', function($compile) { - return { - restrict: 'E', - link: function(scope, elem, attrs) { - var _t = ''; - elem.replaceWith($compile(angular.element(_t))(scope)); - } - }; - }); - - angular - .module('grafana.directives') - .directive('watchChange', function() { - return { - scope: { onchange: '&watchChange' }, - link: function(scope, element) { - element.on('input', function() { - scope.$apply(function () { - scope.onchange({ inputValue: element.val() }); - }); - }); - } - }; - }); - - angular - .module('grafana.directives') - .directive('editorOptBool', function($compile) { - return { - restrict: 'E', - link: function(scope, elem, attrs) { - var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : ''; - var tip = attrs.tip ? (' ' + attrs.tip + '') : ''; - var showIf = attrs.showIf ? (' ng-show="' + attrs.showIf + '" ') : ''; - - var template = '
    ' + - ' ' + - '' + - ' '; - elem.replaceWith($compile(angular.element(template))(scope)); - } - }; - }); - - angular - .module('grafana.directives') - .directive('editorCheckbox', function($compile, $interpolate) { - return { - restrict: 'E', - link: function(scope, elem, attrs) { - var text = $interpolate(attrs.text)(scope); - var ngchange = attrs.change ? (' ng-change="' + attrs.change + '"') : ''; - var tip = attrs.tip ? (' ' + attrs.tip + '') : ''; - var label = ''; - - var template = '' + - ' '; - - template = label + template; - elem.replaceWith($compile(angular.element(template))(scope)); - } - }; - }); - - angular - .module('grafana.directives') - .directive('gfDropdown', function ($parse, $compile, $timeout) { - - function buildTemplate(items, placement) { - var upclass = placement === 'top' ? 'dropup' : ''; - var ul = [ - '' - ]; - - angular.forEach(items, function (item, index) { - if (item.divider) { - return ul.splice(index + 1, 0, '
  • '); - } - - var li = '' + - '' + (item.text || '') + ''; - - if (item.submenu && item.submenu.length) { - li += buildTemplate(item.submenu).join('\n'); - } - - li += ''; - ul.splice(index + 1, 0, li); - }); - return ul; - } - - return { - restrict: 'EA', - scope: true, - link: function postLink(scope, iElement, iAttrs) { - var getter = $parse(iAttrs.gfDropdown), items = getter(scope); - $timeout(function () { - var placement = iElement.data('placement'); - var dropdown = angular.element(buildTemplate(items, placement).join('')); - dropdown.insertAfter(iElement); - $compile(iElement.next('ul.dropdown-menu'))(scope); - }); - - iElement.addClass('dropdown-toggle').attr('data-toggle', 'dropdown'); - } - }; - }); - -}); diff --git a/public/app/directives/ngBlur.js b/public/app/directives/ngBlur.js deleted file mode 100644 index aad125fe755..00000000000 --- a/public/app/directives/ngBlur.js +++ /dev/null @@ -1,20 +0,0 @@ -define([ - 'angular' -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('ngBlur', ['$parse', function($parse) { - return function(scope, element, attr) { - var fn = $parse(attr['ngBlur']); - element.bind('blur', function(event) { - scope.$apply(function() { - fn(scope, {$event:event}); - }); - }); - }; - }]); - -}); \ No newline at end of file diff --git a/public/app/directives/ngModelOnBlur.js b/public/app/directives/ngModelOnBlur.js deleted file mode 100644 index 8057c071bdb..00000000000 --- a/public/app/directives/ngModelOnBlur.js +++ /dev/null @@ -1,54 +0,0 @@ -define([ - 'angular', - 'kbn' -], -function (angular, kbn) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('ngModelOnblur', function() { - return { - restrict: 'A', - priority: 1, - require: 'ngModel', - link: function(scope, elm, attr, ngModelCtrl) { - if (attr.type === 'radio' || attr.type === 'checkbox') { - return; - } - - elm.off('input keydown change'); - elm.bind('blur', function() { - scope.$apply(function() { - ngModelCtrl.$setViewValue(elm.val()); - }); - }); - } - }; - }) - .directive('emptyToNull', function () { - return { - restrict: 'A', - require: 'ngModel', - link: function (scope, elm, attrs, ctrl) { - ctrl.$parsers.push(function (viewValue) { - if(viewValue === "") { return null; } - return viewValue; - }); - } - }; - }) - .directive('validTimeSpan', function() { - return { - require: 'ngModel', - link: function(scope, elm, attrs, ctrl) { - ctrl.$validators.integer = function(modelValue, viewValue) { - if (ctrl.$isEmpty(modelValue)) { - return true; - } - return kbn.isValidTimeSpan(viewValue); - }; - } - }; - }); -}); diff --git a/public/app/directives/passwordStrenght.js b/public/app/directives/passwordStrenght.js deleted file mode 100644 index f75a8fe8854..00000000000 --- a/public/app/directives/passwordStrenght.js +++ /dev/null @@ -1,47 +0,0 @@ -define([ - 'angular', -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('passwordStrength', function() { - var template = '
    ' + - '{{strengthText}}' + - '
    '; - return { - template: template, - scope: { - password: "=", - }, - link: function($scope) { - - $scope.strengthClass = ''; - - function passwordChanged(newValue) { - if (!newValue) { - $scope.strengthText = ""; - $scope.strengthClass = "hidden"; - return; - } - if (newValue.length < 4) { - $scope.strengthText = "strength: weak sauce."; - $scope.strengthClass = "password-strength-bad"; - return; - } - if (newValue.length <= 8) { - $scope.strengthText = "strength: you can do better."; - $scope.strengthClass = "password-strength-ok"; - return; - } - - $scope.strengthText = "strength: strong like a bull."; - $scope.strengthClass = "password-strength-good"; - } - - $scope.$watch("password", passwordChanged); - } - }; - }); -}); diff --git a/public/app/directives/spectrumPicker.js b/public/app/directives/spectrumPicker.js deleted file mode 100644 index c6d3de6f884..00000000000 --- a/public/app/directives/spectrumPicker.js +++ /dev/null @@ -1,42 +0,0 @@ -define([ - 'angular', - 'spectrum' -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('spectrumPicker', function() { - return { - restrict: 'E', - require: 'ngModel', - scope: false, - replace: true, - template: "", - link: function(scope, element, attrs, ngModel) { - var input = element.find('input'); - var options = angular.extend({ - showAlpha: true, - showButtons: false, - color: ngModel.$viewValue, - change: function(color) { - scope.$apply(function() { - ngModel.$setViewValue(color.toRgbString()); - }); - } - }, scope.$eval(attrs.options)); - - ngModel.$render = function() { - input.spectrum('set', ngModel.$viewValue || ''); - }; - - input.spectrum(options); - - scope.$on('$destroy', function() { - input.spectrum('destroy'); - }); - } - }; - }); -}); diff --git a/public/app/directives/topnav.js b/public/app/directives/topnav.js deleted file mode 100644 index 88b98d55984..00000000000 --- a/public/app/directives/topnav.js +++ /dev/null @@ -1,53 +0,0 @@ -define([ - 'angular', - 'kbn' -], -function (angular) { - 'use strict'; - - angular - .module('grafana.directives') - .directive('topnav', function($rootScope, contextSrv) { - return { - restrict: 'E', - transclude: true, - scope: { - title: "@", - section: "@", - titleAction: "&", - subnav: "=", - }, - template: - '', - link: function(scope, elem, attrs) { - scope.icon = attrs.icon; - scope.contextSrv = contextSrv; - - scope.toggle = function() { - $rootScope.appEvent('toggle-sidemenu'); - }; - } - }; - }); - -}); diff --git a/public/app/directives/valueSelectDropdown.js b/public/app/directives/valueSelectDropdown.js deleted file mode 100644 index 65b45135fd5..00000000000 --- a/public/app/directives/valueSelectDropdown.js +++ /dev/null @@ -1,282 +0,0 @@ -define([ - 'angular', - 'app', - 'lodash', - 'jquery', -], -function (angular, app, _) { - 'use strict'; - - angular - .module('grafana.controllers') - .controller('ValueSelectDropdownCtrl', function($q) { - var vm = this; - - vm.show = function() { - vm.oldVariableText = vm.variable.current.text; - vm.highlightIndex = -1; - - vm.options = vm.variable.options; - vm.selectedValues = _.filter(vm.options, {selected: true}); - - vm.tags = _.map(vm.variable.tags, function(value) { - var tag = { text: value, selected: false }; - _.each(vm.variable.current.tags, function(tagObj) { - if (tagObj.text === value) { - tag = tagObj; - } - }); - return tag; - }); - - vm.search = { - query: '', - options: vm.options.slice(0, Math.min(vm.options.length, 1000)) - }; - - vm.dropdownVisible = true; - }; - - vm.updateLinkText = function() { - var current = vm.variable.current; - - if (current.tags && current.tags.length) { - // filer out values that are in selected tags - var selectedAndNotInTag = _.filter(vm.variable.options, function(option) { - if (!option.selected) { return false; } - for (var i = 0; i < current.tags.length; i++) { - var tag = current.tags[i]; - if (_.indexOf(tag.values, option.value) !== -1) { - return false; - } - } - return true; - }); - - // convert values to text - var currentTexts = _.pluck(selectedAndNotInTag, 'text'); - - // join texts - vm.linkText = currentTexts.join(' + '); - if (vm.linkText.length > 0) { - vm.linkText += ' + '; - } - } else { - vm.linkText = vm.variable.current.text; - } - }; - - vm.clearSelections = function() { - _.each(vm.options, function(option) { - option.selected = false; - }); - - vm.selectionsChanged(false); - }; - - vm.selectTag = function(tag) { - tag.selected = !tag.selected; - var tagValuesPromise; - if (!tag.values) { - tagValuesPromise = vm.getValuesForTag({tagKey: tag.text}); - } else { - tagValuesPromise = $q.when(tag.values); - } - - tagValuesPromise.then(function(values) { - tag.values = values; - tag.valuesText = values.join(' + '); - _.each(vm.options, function(option) { - if (_.indexOf(tag.values, option.value) !== -1) { - option.selected = tag.selected; - } - }); - - vm.selectionsChanged(false); - }); - }; - - vm.keyDown = function (evt) { - if (evt.keyCode === 27) { - vm.hide(); - } - if (evt.keyCode === 40) { - vm.moveHighlight(1); - } - if (evt.keyCode === 38) { - vm.moveHighlight(-1); - } - if (evt.keyCode === 13) { - vm.optionSelected(vm.search.options[vm.highlightIndex], {}, true, false); - } - if (evt.keyCode === 32) { - vm.optionSelected(vm.search.options[vm.highlightIndex], {}, false, false); - } - }; - - vm.moveHighlight = function(direction) { - vm.highlightIndex = (vm.highlightIndex + direction) % vm.search.options.length; - }; - - vm.selectValue = function(option, event, commitChange, excludeOthers) { - if (!option) { return; } - - option.selected = !option.selected; - - commitChange = commitChange || false; - excludeOthers = excludeOthers || false; - - var setAllExceptCurrentTo = function(newValue) { - _.each(vm.options, function(other) { - if (option !== other) { other.selected = newValue; } - }); - }; - - // commit action (enter key), should not deselect it - if (commitChange) { - option.selected = true; - } - - if (option.text === 'All' || excludeOthers) { - setAllExceptCurrentTo(false); - commitChange = true; - } - else if (!vm.variable.multi) { - setAllExceptCurrentTo(false); - commitChange = true; - } else if (event.ctrlKey || event.metaKey || event.shiftKey) { - commitChange = true; - setAllExceptCurrentTo(false); - } - - vm.selectionsChanged(commitChange); - }; - - vm.selectionsChanged = function(commitChange) { - vm.selectedValues = _.filter(vm.options, {selected: true}); - - if (vm.selectedValues.length > 1 && vm.selectedValues.length !== vm.options.length) { - if (vm.selectedValues[0].text === 'All') { - vm.selectedValues[0].selected = false; - vm.selectedValues = vm.selectedValues.slice(1, vm.selectedValues.length); - } - } - - // validate selected tags - _.each(vm.tags, function(tag) { - if (tag.selected) { - _.each(tag.values, function(value) { - if (!_.findWhere(vm.selectedValues, {value: value})) { - tag.selected = false; - } - }); - } - }); - - vm.selectedTags = _.filter(vm.tags, {selected: true}); - vm.variable.current.value = _.pluck(vm.selectedValues, 'value'); - vm.variable.current.text = _.pluck(vm.selectedValues, 'text').join(' + '); - vm.variable.current.tags = vm.selectedTags; - - // only single value - if (vm.selectedValues.length === 1) { - vm.variable.current.value = vm.selectedValues[0].value; - } - - if (commitChange) { - vm.commitChanges(); - } - }; - - vm.commitChanges = function() { - // make sure one option is selected - if (vm.selectedValues.length === 0) { - vm.options[0].selected = true; - vm.selectionsChanged(false); - } - - vm.dropdownVisible = false; - vm.updateLinkText(); - - if (vm.variable.current.text !== vm.oldVariableText) { - vm.onUpdated(); - } - }; - - vm.queryChanged = function() { - vm.highlightIndex = -1; - vm.search.options = _.filter(vm.options, function(option) { - return option.text.toLowerCase().indexOf(vm.search.query.toLowerCase()) !== -1; - }); - - vm.search.options = vm.search.options.slice(0, Math.min(vm.search.options.length, 1000)); - }; - - vm.init = function() { - vm.selectedTags = vm.variable.current.tags || []; - vm.updateLinkText(); - }; - - }); - - angular - .module('grafana.directives') - .directive('valueSelectDropdown', function($compile, $window, $timeout, $rootScope) { - - return { - scope: { variable: "=", onUpdated: "&", getValuesForTag: "&" }, - templateUrl: 'app/partials/valueSelectDropdown.html', - controller: 'ValueSelectDropdownCtrl', - controllerAs: 'vm', - bindToController: true, - link: function(scope, elem) { - var bodyEl = angular.element($window.document.body); - var linkEl = elem.find('.variable-value-link'); - var inputEl = elem.find('input'); - - function openDropdown() { - inputEl.css('width', Math.max(linkEl.width(), 30) + 'px'); - - inputEl.show(); - linkEl.hide(); - - inputEl.focus(); - $timeout(function() { bodyEl.on('click', bodyOnClick); }, 0, false); - } - - function switchToLink() { - inputEl.hide(); - linkEl.show(); - bodyEl.off('click', bodyOnClick); - } - - function bodyOnClick (e) { - if (elem.has(e.target).length === 0) { - scope.$apply(function() { - scope.vm.commitChanges(); - }); - } - } - - scope.$watch('vm.dropdownVisible', function(newValue) { - if (newValue) { - openDropdown(); - } else { - switchToLink(); - } - }); - - var cleanUp = $rootScope.$on('template-variable-value-updated', function() { - scope.vm.updateLinkText(); - }); - - scope.$on("$destroy", function() { - cleanUp(); - }); - - scope.vm.init(); - }, - }; - }); - -}); diff --git a/public/app/features/admin/adminEditOrgCtrl.js b/public/app/features/admin/adminEditOrgCtrl.js new file mode 100644 index 00000000000..ec500704fb3 --- /dev/null +++ b/public/app/features/admin/adminEditOrgCtrl.js @@ -0,0 +1,52 @@ +define([ + 'angular', +], +function (angular) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('AdminEditOrgCtrl', function($scope, $routeParams, backendSrv, $location) { + + $scope.init = function() { + if ($routeParams.id) { + $scope.getOrg($routeParams.id); + $scope.getOrgUsers($routeParams.id); + } + }; + + $scope.getOrg = function(id) { + backendSrv.get('/api/orgs/' + id).then(function(org) { + $scope.org = org; + }); + }; + + $scope.getOrgUsers = function(id) { + backendSrv.get('/api/orgs/' + id + '/users').then(function(orgUsers) { + $scope.orgUsers = orgUsers; + }); + }; + + $scope.update = function() { + if (!$scope.orgDetailsForm.$valid) { return; } + + backendSrv.put('/api/orgs/' + $scope.org.id, $scope.org).then(function() { + $location.path('/admin/orgs'); + }); + }; + + $scope.updateOrgUser= function(orgUser) { + backendSrv.patch('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId, orgUser); + }; + + $scope.removeOrgUser = function(orgUser) { + backendSrv.delete('/api/orgs/' + orgUser.orgId + '/users/' + orgUser.userId).then(function() { + $scope.getOrgUsers($scope.org.id); + }); + }; + + $scope.init(); + + }); + +}); diff --git a/public/app/features/admin/adminListOrgsCtrl.js b/public/app/features/admin/adminListOrgsCtrl.js new file mode 100644 index 00000000000..c3f727191fc --- /dev/null +++ b/public/app/features/admin/adminListOrgsCtrl.js @@ -0,0 +1,39 @@ +define([ + 'angular', +], +function (angular) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('AdminListOrgsCtrl', function($scope, backendSrv) { + + $scope.init = function() { + $scope.getOrgs(); + }; + + $scope.getOrgs = function() { + backendSrv.get('/api/orgs').then(function(orgs) { + $scope.orgs = orgs; + }); + }; + + $scope.deleteOrg = function(org) { + $scope.appEvent('confirm-modal', { + title: 'Do you want to delete organization ' + org.name + '?', + text: 'All dashboards for this organization will be removed!', + icon: 'fa-trash', + yesText: 'Delete', + onConfirm: function() { + backendSrv.delete('/api/orgs/' + org.id).then(function() { + $scope.getOrgs(); + }); + } + }); + }; + + $scope.init(); + + }); + +}); diff --git a/public/app/features/admin/adminUsersCtrl.js b/public/app/features/admin/adminListUsersCtrl.js similarity index 90% rename from public/app/features/admin/adminUsersCtrl.js rename to public/app/features/admin/adminListUsersCtrl.js index 737812c5474..c89deafaf0f 100644 --- a/public/app/features/admin/adminUsersCtrl.js +++ b/public/app/features/admin/adminListUsersCtrl.js @@ -6,7 +6,7 @@ function (angular) { var module = angular.module('grafana.controllers'); - module.controller('AdminUsersCtrl', function($scope, backendSrv) { + module.controller('AdminListUsersCtrl', function($scope, backendSrv) { $scope.init = function() { $scope.getUsers(); diff --git a/public/app/features/admin/all.js b/public/app/features/admin/all.js index 35f1f4f5db9..14bff249b0e 100644 --- a/public/app/features/admin/all.js +++ b/public/app/features/admin/all.js @@ -1,5 +1,7 @@ define([ - './adminUsersCtrl', + './adminListUsersCtrl', + './adminListOrgsCtrl', + './adminEditOrgCtrl', './adminEditUserCtrl', './adminSettingsCtrl', ], function () {}); diff --git a/public/app/features/admin/partials/edit_org.html b/public/app/features/admin/partials/edit_org.html new file mode 100644 index 00000000000..aef042894ab --- /dev/null +++ b/public/app/features/admin/partials/edit_org.html @@ -0,0 +1,60 @@ + + + + +
    +
    +

    + Organization Details +

    + +
    +
    +
    +
      +
    • + Name +
    • +
    • + +
    • +
    +
    +
    +
    + +
    + +
    + +

    + Organization Users +

    + + + + + + + + + + + + + + +
    UsernameEmailRole
    {{orgUser.login}}{{orgUser.email}} + + + + + +
    + +
    +
    diff --git a/public/app/features/admin/partials/edit_user.html b/public/app/features/admin/partials/edit_user.html index a1a4cb989cd..674f1cf4b0d 100644 --- a/public/app/features/admin/partials/edit_user.html +++ b/public/app/features/admin/partials/edit_user.html @@ -9,7 +9,7 @@

    - User details + Edit User

    @@ -17,7 +17,7 @@
    • - Name + Name
    • @@ -28,7 +28,7 @@
      • - Email + Email
      • @@ -39,7 +39,7 @@
        • - Username + Username
        • @@ -53,16 +53,16 @@
        • -

          +

          Change password -

          +
          • - New password + New password
          • @@ -76,9 +76,9 @@
          • -

            +

            Permissions -

            +
            @@ -97,9 +97,9 @@
            -

            +

            Organizations -

            +
            diff --git a/public/app/features/admin/partials/orgs.html b/public/app/features/admin/partials/orgs.html index 1bb8996a825..fe87073eb6c 100644 --- a/public/app/features/admin/partials/orgs.html +++ b/public/app/features/admin/partials/orgs.html @@ -1,6 +1,6 @@ @@ -10,6 +10,26 @@ Organizations - View not implemented yet... + + + + + + + + + + +
            IdName
            {{org.name}} + + + Edit + +    + + + +
            +
            diff --git a/public/app/features/admin/partials/users.html b/public/app/features/admin/partials/users.html index 6d4f7a8671c..6f8365e75dd 100644 --- a/public/app/features/admin/partials/users.html +++ b/public/app/features/admin/partials/users.html @@ -1,6 +1,6 @@ diff --git a/public/app/features/all.js b/public/app/features/all.js index 92db77a9a5d..6519d112b74 100644 --- a/public/app/features/all.js +++ b/public/app/features/all.js @@ -7,6 +7,6 @@ define([ './panel/all', './profile/profileCtrl', './profile/changePasswordCtrl', - './org/all', + './profile/selectOrgCtrl', './admin/all', ], function () {}); diff --git a/public/app/features/annotations/editorCtrl.js b/public/app/features/annotations/editorCtrl.js index 39b072263fc..5a509fe4ca9 100644 --- a/public/app/features/annotations/editorCtrl.js +++ b/public/app/features/annotations/editorCtrl.js @@ -20,15 +20,13 @@ function (angular, _, $) { }; $scope.init = function() { - $scope.editor = { index: 0 }; + $scope.mode = 'list'; $scope.datasources = datasourceSrv.getAnnotationSources(); $scope.annotations = $scope.dashboard.annotations.list; $scope.reset(); - $scope.$watch('editor.index', function(newVal) { - if (newVal !== 2) { - $scope.reset(); - } + $scope.$watch('mode', function(newVal) { + if (newVal === 'new') { $scope.reset(); } }); }; @@ -43,8 +41,8 @@ function (angular, _, $) { $scope.currentAnnotation = annotation; $scope.currentIsNew = false; $scope.datasourceChanged(); + $scope.mode = 'edit'; - $scope.editor.index = 2; $(".tooltip.in").remove(); }; @@ -57,14 +55,14 @@ function (angular, _, $) { $scope.update = function() { $scope.reset(); - $scope.editor.index = 0; + $scope.mode = 'list'; $scope.broadcastRefresh(); }; $scope.add = function() { $scope.annotations.push($scope.currentAnnotation); $scope.reset(); - $scope.editor.index = 0; + $scope.mode = 'list'; $scope.updateSubmenuVisibility(); $scope.broadcastRefresh(); }; diff --git a/public/app/features/annotations/partials/editor.html b/public/app/features/annotations/partials/editor.html index 799b1f69fe4..851db4b4f01 100644 --- a/public/app/features/annotations/partials/editor.html +++ b/public/app/features/annotations/partials/editor.html @@ -6,9 +6,27 @@ Annotations
          -
          -
          -
          +
          -
          + +
          No annotations defined @@ -47,7 +66,7 @@
          -
          +
          @@ -72,15 +91,13 @@
          -
          -
          +
          - - + +

          -
          diff --git a/public/app/features/dashboard/all.js b/public/app/features/dashboard/all.js index d6e713070e9..0ee3a64a806 100644 --- a/public/app/features/dashboard/all.js +++ b/public/app/features/dashboard/all.js @@ -16,6 +16,7 @@ define([ './timeSrv', './unsavedChangesSrv', './directives/dashSearchView', + './timepicker/timepicker', './graphiteImportCtrl', './dynamicDashboardSrv', './importCtrl', diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js index 9ddce2dbea8..3767ba05176 100644 --- a/public/app/features/dashboard/dashboardSrv.js +++ b/public/app/features/dashboard/dashboardSrv.js @@ -31,8 +31,8 @@ function (angular, $, kbn, _, moment) { this.hideControls = data.hideControls || false; this.sharedCrosshair = data.sharedCrosshair || false; this.rows = data.rows || []; - this.nav = data.nav || []; this.time = data.time || { from: 'now-6h', to: 'now' }; + this.timepicker = data.timepicker || {}; this.templating = this._ensureListExist(data.templating); this.annotations = this._ensureListExist(data.annotations); this.refresh = data.refresh; @@ -40,11 +40,6 @@ function (angular, $, kbn, _, moment) { this.schemaVersion = data.schemaVersion || 0; this.version = data.version || 0; this.links = data.links || []; - - if (this.nav.length === 0) { - this.nav.push({ type: 'timepicker' }); - } - this._updateSchema(data); this._initMeta(meta); } @@ -174,26 +169,67 @@ function (angular, $, kbn, _, moment) { var newPanel = angular.copy(panel); newPanel.id = this.getNextPanelId(); + delete newPanel.repeat; + delete newPanel.repeatIteration; + delete newPanel.repeatPanelId; + delete newPanel.scopedVars; + var currentRow = this.rows[rowIndex]; currentRow.panels.push(newPanel); return newPanel; }; + p.getNextQueryLetter = function(panel) { + var letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + return _.find(letters, function(refId) { + return _.every(panel.targets, function(other) { + return other.refId !== refId; + }); + }); + }; + + p.addDataQueryTo = function(panel, datasource) { + var target = { + refId: this.getNextQueryLetter(panel) + }; + + if (datasource) { + target.datasource = datasource.name; + } + + panel.targets.push(target); + }; + + p.removeDataQuery = function (panel, query) { + panel.targets = _.without(panel.targets, query); + }; + + p.duplicateDataQuery = function(panel, query) { + var clone = angular.copy(query); + clone.refId = this.getNextQueryLetter(panel); + panel.targets.push(clone); + }; + + p.moveDataQuery = function(panel, fromIndex, toIndex) { + _.move(panel.targets, fromIndex, toIndex); + }; + p.formatDate = function(date, format) { format = format || 'YYYY-MM-DD HH:mm:ss'; return this.timezone === 'browser' ? - moment(date).format(format) : - moment.utc(date).format(format); + moment(date).format(format) : + moment.utc(date).format(format); }; p._updateSchema = function(old) { var i, j, k; var oldVersion = this.schemaVersion; var panelUpgrades = []; - this.schemaVersion = 6; + this.schemaVersion = 7; - if (oldVersion === 6) { + if (oldVersion === 7) { return; } @@ -288,6 +324,11 @@ function (angular, $, kbn, _, moment) { } } + if (oldVersion < 7 && old.nav && old.nav.length) { + this.timepicker = old.nav[0]; + delete this.nav; + } + if (panelUpgrades.length === 0) { return; } diff --git a/public/app/features/dashboard/dynamicDashboardSrv.js b/public/app/features/dashboard/dynamicDashboardSrv.js index bb58e2dbc06..8e3c24f7202 100644 --- a/public/app/features/dashboard/dynamicDashboardSrv.js +++ b/public/app/features/dashboard/dynamicDashboardSrv.js @@ -11,11 +11,15 @@ function (angular, _) { var self = this; this.init = function(dashboard) { + if (dashboard.snapshot) { return; } + this.iteration = new Date().getTime(); this.process(dashboard); }; this.update = function(dashboard) { + if (dashboard.snapshot) { return; } + this.iteration = this.iteration + 1; this.process(dashboard); }; diff --git a/public/app/features/dashboard/graphiteImportCtrl.js b/public/app/features/dashboard/graphiteImportCtrl.js index be912610e36..578d2981db1 100644 --- a/public/app/features/dashboard/graphiteImportCtrl.js +++ b/public/app/features/dashboard/graphiteImportCtrl.js @@ -90,7 +90,7 @@ function (angular, app, _, kbn) { }); window.grafanaImportDashboard = newDashboard; - $location.path('/dashboard/import/' + kbn.slugifyForUrl(newDashboard.title)); + $location.path('/dashboard-import/' + kbn.slugifyForUrl(newDashboard.title)); } }); }); diff --git a/public/app/features/dashboard/partials/dashboardTopNav.html b/public/app/features/dashboard/partials/dashboardTopNav.html index 58445abf505..f370ce324f5 100644 --- a/public/app/features/dashboard/partials/dashboardTopNav.html +++ b/public/app/features/dashboard/partials/dashboardTopNav.html @@ -61,9 +61,8 @@ Back to dashboard -
        • - - +
        • +
        diff --git a/public/app/features/dashboard/partials/import.html b/public/app/features/dashboard/partials/import.html index a942297ac59..4b44544dc7b 100644 --- a/public/app/features/dashboard/partials/import.html +++ b/public/app/features/dashboard/partials/import.html @@ -1,6 +1,6 @@ diff --git a/public/app/features/dashboard/partials/saveDashboardAs.html b/public/app/features/dashboard/partials/saveDashboardAs.html index 8d04280465e..9af18591fde 100644 --- a/public/app/features/dashboard/partials/saveDashboardAs.html +++ b/public/app/features/dashboard/partials/saveDashboardAs.html @@ -14,7 +14,7 @@

        New title

        - +

        diff --git a/public/app/features/dashboard/partials/settings.html b/public/app/features/dashboard/partials/settings.html index 5ddfb1c57e3..892eebbb927 100644 --- a/public/app/features/dashboard/partials/settings.html +++ b/public/app/features/dashboard/partials/settings.html @@ -5,9 +5,7 @@
        -
        -
        -
        +
        @@ -40,7 +38,7 @@
      -
      +
      • Timezone @@ -57,7 +55,7 @@
        Toggles
        -
        +
        • @@ -65,7 +63,7 @@
        • -
        • +
        @@ -110,9 +108,8 @@
        -
        - - +
        +
        diff --git a/public/app/features/dashboard/partials/snapshotTopNav.html b/public/app/features/dashboard/partials/snapshotTopNav.html index 68e8b9532d6..9b2af897cfb 100644 --- a/public/app/features/dashboard/partials/snapshotTopNav.html +++ b/public/app/features/dashboard/partials/snapshotTopNav.html @@ -26,9 +26,8 @@
      diff --git a/public/app/features/dashboard/rowCtrl.js b/public/app/features/dashboard/rowCtrl.js index c63017365bb..6839feb8f3b 100644 --- a/public/app/features/dashboard/rowCtrl.js +++ b/public/app/features/dashboard/rowCtrl.js @@ -46,7 +46,7 @@ function (angular, app, _, config) { $scope.appEvent('confirm-modal', { title: 'Are you sure you want to delete this row?', icon: 'fa-trash', - yesText: 'delete', + yesText: 'Delete', onConfirm: function() { $scope.dashboard.rows = _.without($scope.dashboard.rows, $scope.row); } @@ -98,7 +98,7 @@ function (angular, app, _, config) { }; $scope.updatePanelSpan = function(panel, span) { - panel.span = Math.min(Math.max(panel.span + span, 1), 12); + panel.span = Math.min(Math.max(Math.floor(panel.span + span), 1), 12); }; $scope.replacePanel = function(newPanel, oldPanel) { @@ -121,7 +121,7 @@ function (angular, app, _, config) { module.directive('rowHeight', function() { return function(scope, element) { scope.$watchGroup(['row.collapse', 'row.height'], function() { - element[0].style.minHeight = scope.row.collapse ? '5px' : scope.row.height; + element.css({ minHeight: scope.row.collapse ? '5px' : scope.row.height }); }); }; }); diff --git a/public/app/features/dashboard/saveDashboardAsCtrl.js b/public/app/features/dashboard/saveDashboardAsCtrl.js index 16dc291e9fa..92c93b7e485 100644 --- a/public/app/features/dashboard/saveDashboardAsCtrl.js +++ b/public/app/features/dashboard/saveDashboardAsCtrl.js @@ -14,16 +14,39 @@ function (angular) { $scope.clone.title = $scope.clone.title + " Copy"; }; + function saveDashboard(options) { + return backendSrv.saveDashboard($scope.clone, options).then(function(result) { + $scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + $scope.clone.title]); + + $location.url('/dashboard/db/' + result.slug); + + $scope.appEvent('dashboard-saved', $scope.clone); + $scope.dismiss(); + }); + } + + $scope.keyDown = function (evt) { + if (evt.keyCode === 13) { + $scope.saveClone(); + } + }; + $scope.saveClone = function() { - backendSrv.saveDashboard($scope.clone) - .then(function(result) { - $scope.appEvent('alert-success', ['Dashboard saved', 'Saved as ' + $scope.clone.title]); + saveDashboard({overwrite: false}).then(null, function(err) { + if (err.data && err.data.status === "name-exists") { + err.isHandled = true; - $location.url('/dashboard/db/' + result.slug); - - $scope.appEvent('dashboard-saved', $scope.clone); - $scope.dismiss(); - }); + $scope.appEvent('confirm-modal', { + title: 'Another dashboard with the same name exists', + text: "Would you still like to save this dashboard?", + yesText: "Save & Overwrite", + icon: "fa-warning", + onConfirm: function() { + saveDashboard({overwrite: true}); + } + }); + } + }); }; }); diff --git a/public/app/features/dashboard/shareSnapshotCtrl.js b/public/app/features/dashboard/shareSnapshotCtrl.js index 9601ab48f0b..903353ce9b4 100644 --- a/public/app/features/dashboard/shareSnapshotCtrl.js +++ b/public/app/features/dashboard/shareSnapshotCtrl.js @@ -19,7 +19,7 @@ function (angular, _) { $scope.expireOptions = [ {text: '1 Hour', value: 60*60}, {text: '1 Day', value: 60*60*24}, - {text: '7 Days', value: 60*60*7}, + {text: '7 Days', value: 60*60*24*7}, {text: 'Never', value: 0}, ]; @@ -29,7 +29,7 @@ function (angular, _) { {text: 'Public on the web', value: 3}, ]; - $scope.externalUrl = 'http://snapshots-origin.raintank.io'; + $scope.externalUrl = '//snapshots-origin.raintank.io'; $scope.apiUrl = '/api/snapshots'; $scope.createSnapshot = function(external) { diff --git a/public/app/panels/timepicker/custom.html b/public/app/features/dashboard/timepicker/custom.html similarity index 89% rename from public/app/panels/timepicker/custom.html rename to public/app/features/dashboard/timepicker/custom.html index c5532ddfb5d..587ee973b8f 100644 --- a/public/app/panels/timepicker/custom.html +++ b/public/app/features/dashboard/timepicker/custom.html @@ -59,7 +59,7 @@
      - +
      @@ -69,12 +69,12 @@ .
      -   Right Now +   Right Now

      - + Invalid date or range
      diff --git a/public/app/features/dashboard/timepicker/settings.html b/public/app/features/dashboard/timepicker/settings.html new file mode 100644 index 00000000000..b382c593094 --- /dev/null +++ b/public/app/features/dashboard/timepicker/settings.html @@ -0,0 +1,55 @@ +
      +
      +
      +
      +
        +
      • + Relative times +
      • +
      • + +
      • +
      +
      +
      +
      +
        +
      • + Auto-refresh +
      • +
      • + +
      • +
      +
      +
      + +
      +
        +
      • + Now delay +
      • +
      • + now- +
      • +
      • + +
      • + +
      +
      +
      + +
      + +

      +
      + + For these changes to fully take effect save and reload the dashboard. + +

      +
      diff --git a/public/app/features/dashboard/timepicker/timepicker.html b/public/app/features/dashboard/timepicker/timepicker.html new file mode 100644 index 00000000000..cc916dc4a9d --- /dev/null +++ b/public/app/features/dashboard/timepicker/timepicker.html @@ -0,0 +1,56 @@ + +
      + +
      +
      diff --git a/public/app/features/dashboard/timepicker/timepicker.ts b/public/app/features/dashboard/timepicker/timepicker.ts new file mode 100644 index 00000000000..217d92d73df --- /dev/null +++ b/public/app/features/dashboard/timepicker/timepicker.ts @@ -0,0 +1,220 @@ +/// + +import angular = require('angular'); +import _ = require('lodash'); +import moment = require('moment'); +import kbn = require('kbn'); + +export class TimePickerCtrl { + + static defaults = { + status : "Stable", + time_options : ['5m','15m','1h','6h','12h','24h','today', '2d','7d','30d'], + refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'], + }; + + static patterns = { + date: /^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/, + hour: /^([01]?[0-9]|2[0-3])$/, + minute: /^[0-5][0-9]$/, + second: /^[0-5][0-9]$/, + millisecond: /^[0-9]*$/ + }; + + constructor(private $scope : any, private $rootScope, private timeSrv) { + $scope.patterns = TimePickerCtrl.patterns; + $scope.timeSrv = timeSrv; + $scope.ctrl = this; + + $scope.$on('refresh', () => this.init()); + + this.init(); + } + + init() { + this.$scope.panel = this.$scope.dashboard.timepicker; + + _.defaults(this.$scope.panel, TimePickerCtrl.defaults); + + var time = this.timeSrv.timeRange(true); + this.$scope.panel.now = false; + + var unparsed = this.timeSrv.timeRange(false); + if (_.isString(unparsed.to) && unparsed.to.indexOf('now') === 0) { + this.$scope.panel.now = true; + } + + this.$scope.time = this.getScopeTimeObj(time.from, time.to); + + this.$scope.onAppEvent('zoom-out', function() { + this.$scope.zoom(2); + }); + } + + pad(n: number, width: number, z = 0): string { + var str = n.toString(); + return str.length >= width ? str : new Array(width - str.length + 1).join(z.toString()) + str; + } + + getTimeObj(date): any { + return { + date: new Date(date), + hour: this.pad(date.getHours(), 2), + minute: this.pad(date.getMinutes(), 2), + second: this.pad(date.getSeconds(), 2), + millisecond: this.pad(date.getMilliseconds(), 3) + }; + }; + + getScopeTimeObj(from, to) { + var model : any = {from: this.getTimeObj(from), to: this.getTimeObj(to)}; + + if (model.from.date) { + model.tooltip = this.$scope.dashboard.formatDate(model.from.date) + '
      to
      '; + model.tooltip += this.$scope.dashboard.formatDate(model.to.date); + } + else { + model.tooltip = 'Click to set time filter'; + } + + if (this.timeSrv.time) { + if (this.$scope.panel.now) { + if (this.timeSrv.time.from === 'today') { + model.rangeString = 'Today'; + } else { + model.rangeString = moment(model.from.date).fromNow() + ' to ' + + moment(model.to.date).fromNow(); + } + } + else { + model.rangeString = this.$scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY HH:mm:ss') + ' to ' + + this.$scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY HH:mm:ss'); + } + } + + return model; + } + + loadTimeOptions() { + this.$scope.time_options = _.map(this.$scope.panel.time_options, function(str) { + return kbn.getRelativeTimeInfo(str); + }); + + this.$scope.refreshMenuLeftSide = this.$scope.time.rangeString.length < 10; + } + + cloneTime(time) { + var _n = { from: _.clone(time.from), to: _.clone(time.to) }; + + // Create new dates as _.clone is shallow. + _n.from.date = new Date(_n.from.date); + _n.to.date = new Date(_n.to.date); + return _n; + } + + customTime() { + // Assume the form is valid since we're setting it to something valid + this.$scope.input.$setValidity("dummy", true); + this.$scope.temptime = this.cloneTime(this.$scope.time); + this.$scope.temptime.now = this.$scope.panel.now; + + this.$scope.temptime.from.date.setHours(0, 0, 0, 0); + this.$scope.temptime.to.date.setHours(0, 0, 0, 0); + + // Date picker needs the date to be at the start of the day + if (new Date().getTimezoneOffset() < 0) { + this.$scope.temptime.from.date = moment(this.$scope.temptime.from.date).add(1, 'days').toDate(); + this.$scope.temptime.to.date = moment(this.$scope.temptime.to.date).add(1, 'days').toDate(); + } + + this.$scope.appEvent('show-dash-editor', { + src: 'app/features/dashboard/timepicker/custom.html', + scope: this.$scope + }); + } + + setNow() { + this.$scope.time.to = this.getTimeObj(new Date()); + } + + setAbsoluteTimeFilter(time) { + // Create filter object + var _filter = _.clone(time); + + if (time.now) { + _filter.to = "now"; + } + + // Update our representation + this.$scope.time = this.getScopeTimeObj(time.from, time.to); + this.timeSrv.setTime(_filter); + } + + setRelativeFilter(timespan) { + this.$scope.panel.now = true; + + var range = {from: timespan.from, to: timespan.to}; + + if (this.$scope.panel.nowDelay) { + range.to = 'now-' + this.$scope.panel.nowDelay; + } + + this.timeSrv.setTime(range); + + this.$scope.time = this.getScopeTimeObj(kbn.parseDate(range.from), new Date()); + } + + validate(time): any { + // Assume the form is valid. There is a hidden dummy input for invalidating it programatically. + this.$scope.input.$setValidity("dummy", true); + + var _from = this.datepickerToLocal(time.from.date); + var _to = this.datepickerToLocal(time.to.date); + var _t = time; + + if (this.$scope.input.$valid) { + _from.setHours(_t.from.hour, _t.from.minute, _t.from.second, _t.from.millisecond); + _to.setHours(_t.to.hour, _t.to.minute, _t.to.second, _t.to.millisecond); + + // Check that the objects are valid and to is after from + if (isNaN(_from.getTime()) || isNaN(_to.getTime()) || _from.getTime() >= _to.getTime()) { + this.$scope.input.$setValidity("dummy", false); + return false; + } + } else { + return false; + } + + return { from: _from, to: _to, now: time.now }; + } + + datepickerToLocal(date) { + date = moment(date).clone().toDate(); + return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate(); + } + +} + +export function settingsDirective() { + 'use strict'; + return { + restrict: 'E', + templateUrl: 'app/features/dashboard/timepicker/settings.html', + controller: TimePickerCtrl, + scope: true, + }; +} + +export function timePickerDirective() { + 'use strict'; + return { + restrict: 'E', + templateUrl: 'app/features/dashboard/timepicker/timepicker.html', + controller: TimePickerCtrl, + scope: true + }; +} + + +angular.module('grafana.directives').directive('gfTimePickerSettings', settingsDirective); +angular.module('grafana.directives').directive('gfTimePicker', timePickerDirective); diff --git a/public/app/features/dashboard/unsavedChangesSrv.js b/public/app/features/dashboard/unsavedChangesSrv.js index 7318d4824d5..758c06b1975 100644 --- a/public/app/features/dashboard/unsavedChangesSrv.js +++ b/public/app/features/dashboard/unsavedChangesSrv.js @@ -7,7 +7,7 @@ function(angular, _) { var module = angular.module('grafana.services'); - module.service('unsavedChangesSrv', function($modal, $q, $location, $timeout, contextSrv, $window) { + module.service('unsavedChangesSrv', function($rootScope, $q, $location, $timeout, contextSrv, $window) { function Tracker(dashboard, scope) { var self = this; @@ -80,6 +80,9 @@ function(angular, _) { // remove scopedVars panel.scopedVars = null; + // ignore span changes + panel.span = null; + // ignore panel legend sort if (panel.legend) { delete panel.legend.sort; @@ -139,17 +142,10 @@ function(angular, _) { tracker.scope.$emit('save-dashboard'); }; - var confirmModal = $modal({ - template: './app/partials/unsaved-changes.html', - modalClass: 'confirm-modal', - persist: false, - show: false, + $rootScope.appEvent('show-modal', { + src: './app/partials/unsaved-changes.html', + modalClass: 'modal-no-header confirm-modal', scope: modalScope, - keyboard: false - }); - - $q.when(confirmModal).then(function(modalEl) { - modalEl.modal('show'); }); }; diff --git a/public/app/features/dashlinks/editor.html b/public/app/features/dashlinks/editor.html index 886550d9b9c..9f44d72206c 100644 --- a/public/app/features/dashlinks/editor.html +++ b/public/app/features/dashlinks/editor.html @@ -58,7 +58,7 @@
    • Tooltip
    • - +
    • Icon
    • @@ -67,7 +67,7 @@
    -
    +
    • diff --git a/public/app/features/dashlinks/module.js b/public/app/features/dashlinks/module.js index 9fc1bc4d4b5..23ce8c0ffae 100644 --- a/public/app/features/dashlinks/module.js +++ b/public/app/features/dashlinks/module.js @@ -114,7 +114,7 @@ function (angular, _) { title: linkDef.title, icon: iconMap[linkDef.icon], tooltip: linkDef.tooltip, - target: linkDef.targetBlank ? "_blank" : "", + target: linkDef.targetBlank ? "_blank" : "_self", keepTime: linkDef.keepTime, includeVars: linkDef.includeVars, }]); diff --git a/public/app/features/org/all.js b/public/app/features/org/all.js index ba51b99369b..d03d270709d 100644 --- a/public/app/features/org/all.js +++ b/public/app/features/org/all.js @@ -3,6 +3,7 @@ define([ './datasourceEditCtrl', './orgUsersCtrl', './newOrgCtrl', + './userInviteCtrl', './orgApiKeysCtrl', './orgDetailsCtrl', ], function () {}); diff --git a/public/app/features/org/datasourceEditCtrl.js b/public/app/features/org/datasourceEditCtrl.js index 9bb49edff42..09dd8606092 100644 --- a/public/app/features/org/datasourceEditCtrl.js +++ b/public/app/features/org/datasourceEditCtrl.js @@ -1,8 +1,9 @@ define([ 'angular', 'config', + 'lodash', ], -function (angular, config) { +function (angular, config, _) { 'use strict'; var module = angular.module('grafana.controllers'); @@ -12,12 +13,16 @@ function (angular, config) { $scope.httpConfigPartialSrc = 'app/features/org/partials/datasourceHttpConfig.html'; - var defaults = { - name: '', - type: 'graphite', - url: '', - access: 'proxy' - }; + var defaults = {name: '', type: 'graphite', url: '', access: 'proxy' }; + + $scope.indexPatternTypes = [ + {name: 'No pattern', value: undefined}, + {name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH'}, + {name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD'}, + {name: 'Weekly', value: 'Weekly', example: '[logstash-]GGGG.WW'}, + {name: 'Monthly', value: 'Monthly', example: '[logstash-]YYYY.MM'}, + {name: 'Yearly', value: 'Yearly', example: '[logstash-]YYYY'}, + ]; $scope.init = function() { $scope.isNew = true; @@ -117,6 +122,11 @@ function (angular, config) { } }; + $scope.indexPatternTypeChanged = function() { + var def = _.findWhere($scope.indexPatternTypes, {value: $scope.current.jsonData.interval}); + $scope.current.database = def.example || 'es-index-name'; + }; + $scope.init(); }); diff --git a/public/app/features/org/orgDetailsCtrl.js b/public/app/features/org/orgDetailsCtrl.js index 97c89d3ffed..b900e295b99 100644 --- a/public/app/features/org/orgDetailsCtrl.js +++ b/public/app/features/org/orgDetailsCtrl.js @@ -15,13 +15,20 @@ function (angular) { $scope.getOrgInfo = function() { backendSrv.get('/api/org').then(function(org) { $scope.org = org; + $scope.address = org.address; contextSrv.user.orgName = org.name; }); }; $scope.update = function() { if (!$scope.orgForm.$valid) { return; } - backendSrv.put('/api/org', $scope.org).then($scope.getOrgInfo); + var data = {name: $scope.org.name}; + backendSrv.put('/api/org', data).then($scope.getOrgInfo); + }; + + $scope.updateAddress = function() { + if (!$scope.addressForm.$valid) { return; } + backendSrv.put('/api/org/address', $scope.address).then($scope.getOrgInfo); }; $scope.init(); diff --git a/public/app/features/org/orgUsersCtrl.js b/public/app/features/org/orgUsersCtrl.js index 9623e2566a7..3d3e4d26ec3 100644 --- a/public/app/features/org/orgUsersCtrl.js +++ b/public/app/features/org/orgUsersCtrl.js @@ -13,14 +13,21 @@ function (angular) { role: 'Viewer', }; + $scope.users = []; + $scope.pendingInvites = []; + $scope.init = function() { $scope.get(); + $scope.editor = { index: 0 }; }; $scope.get = function() { backendSrv.get('/api/org/users').then(function(users) { $scope.users = users; }); + backendSrv.get('/api/org/invites').then(function(pendingInvites) { + $scope.pendingInvites = pendingInvites; + }); }; $scope.updateOrgUser = function(user) { @@ -31,9 +38,26 @@ function (angular) { backendSrv.delete('/api/org/users/' + user.userId).then($scope.get); }; - $scope.addUser = function() { - if (!$scope.form.$valid) { return; } - backendSrv.post('/api/org/users', $scope.user).then($scope.get); + $scope.revokeInvite = function(invite, evt) { + evt.stopPropagation(); + backendSrv.patch('/api/org/invites/' + invite.code + '/revoke').then($scope.get); + }; + + $scope.copyInviteToClipboard = function(evt) { + evt.stopPropagation(); + }; + + $scope.openInviteModal = function() { + var modalScope = $scope.$new(); + modalScope.invitesSent = function() { + $scope.get(); + }; + + $scope.appEvent('show-modal', { + src: './app/features/org/partials/invite.html', + modalClass: 'modal-no-header invite-modal', + scope: modalScope + }); }; $scope.init(); diff --git a/public/app/features/org/partials/datasourceHttpConfig.html b/public/app/features/org/partials/datasourceHttpConfig.html index 94c208d8370..4175e523fa1 100644 --- a/public/app/features/org/partials/datasourceHttpConfig.html +++ b/public/app/features/org/partials/datasourceHttpConfig.html @@ -9,7 +9,7 @@
    • - Access Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request + Access Direct = url is used directly from browser, Proxy = Grafana backend will proxy the request
    • @@ -23,9 +23,7 @@ Basic Auth
    • - Enable  - - +
    • User diff --git a/public/app/features/org/partials/invite.html b/public/app/features/org/partials/invite.html new file mode 100644 index 00000000000..808f722c081 --- /dev/null +++ b/public/app/features/org/partials/invite.html @@ -0,0 +1,70 @@ + + + diff --git a/public/app/features/org/partials/newOrg.html b/public/app/features/org/partials/newOrg.html index 56d08da701e..9171cb55e06 100644 --- a/public/app/features/org/partials/newOrg.html +++ b/public/app/features/org/partials/newOrg.html @@ -10,20 +10,18 @@

      Add Organization

      -
      -
      -
        -
      • - Org. name -
      • -
      • - -
      • -
      • -
      • -
      -
      -
      +
      +
        +
      • + Org. name +
      • +
      • + +
      • +
      • +
      • +
      +

      diff --git a/public/app/features/org/partials/orgDetails.html b/public/app/features/org/partials/orgDetails.html index 0537154aa9f..db965d27633 100644 --- a/public/app/features/org/partials/orgDetails.html +++ b/public/app/features/org/partials/orgDetails.html @@ -7,59 +7,90 @@
      -

      Organization info

      +

      Organization

      - -
      -
      +
      +
      Info
      + + +
      • - Org. name + Org. name
      • - -
      • -
      -
      -
      -
      -
        -
      • - Address 1 -
      • -
      • - -
      • -
      -
      -
      -
      -
        -
      • - Address 2 -
      • -
      • - -
      • -
      -
      -
      -
      -
        -
      • - City -
      • -
      • - +
      -
      -
      - - +
      + + +
      + +
      +
      Address
      + +
      +
      +
        +
      • + Address 1 +
      • +
      • + +
      • +
      • + Address 2 +
      • +
      • + +
      • +
      +
      +
      +
      +
        +
      • + City +
      • +
      • + +
      • +
      • + Postal code +
      • +
      • + +
      • +
      +
      +
      +
      +
        +
      • + State +
      • +
      • + +
      • +
      • + Country +
      • +
      • + +
      • +
      +
      +
      + +
      + + +
      +
      diff --git a/public/app/features/org/partials/orgUsers.html b/public/app/features/org/partials/orgUsers.html index b32ffb67081..355131c2dbb 100644 --- a/public/app/features/org/partials/orgUsers.html +++ b/public/app/features/org/partials/orgUsers.html @@ -9,53 +9,67 @@

      Organization users

      -
      -
      -
        -
      • - Username or Email -
      • -
      • - -
      • -
      • - role -
      • -
      • - -
      • -
      • - -
      • -
        -
      -
      -
      +
      - - - - - - - - - - - - - -
      LoginEmailRole
      {{user.login}}{{user.email}} - - - - - -
      + + + + + + + + + + + + + + + +
      LoginEmailRole
      {{user.login}}{{user.email}} + + + + + +
      +
      + +
      + {{invite.email}} + {{invite.name}} + + +   + + + + + +
      + {{invite.url}}
      + +   + + + Invited: {{invite.createdOn | date: 'shortDate'}} by {{invite.invitedBy}} + +
      +
      + +
      diff --git a/public/app/features/org/userInviteCtrl.js b/public/app/features/org/userInviteCtrl.js new file mode 100644 index 00000000000..52b307612b3 --- /dev/null +++ b/public/app/features/org/userInviteCtrl.js @@ -0,0 +1,48 @@ +define([ + 'angular', + 'lodash', +], +function (angular, _) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('UserInviteCtrl', function($scope, backendSrv) { + + $scope.invites = [ + {name: '', email: '', role: 'Editor'}, + ]; + + $scope.options = {skipEmails: false}; + $scope.init = function() { }; + + $scope.addInvite = function() { + $scope.invites.push({name: '', email: '', role: 'Editor'}); + }; + + $scope.removeInvite = function(invite) { + $scope.invites = _.without($scope.invites, invite); + }; + + $scope.sendInvites = function() { + if (!$scope.inviteForm.$valid) { return; } + $scope.sendSingleInvite(0); + }; + + $scope.sendSingleInvite = function(index) { + var invite = $scope.invites[index]; + invite.skipEmails = $scope.options.skipEmails; + + return backendSrv.post('/api/org/invites', invite).finally(function() { + index += 1; + + if (index === $scope.invites.length) { + $scope.invitesSent(); + $scope.dismiss(); + } else { + $scope.sendSingleInvite(index); + } + }); + }; + }); +}); diff --git a/public/app/features/panel/panelDirective.js b/public/app/features/panel/panelDirective.js index 7330bb627de..d4f510995c1 100644 --- a/public/app/features/panel/panelDirective.js +++ b/public/app/features/panel/panelDirective.js @@ -6,35 +6,184 @@ define([ function (angular, $, config) { 'use strict'; - angular - .module('grafana.directives') - .directive('panelLoader', function($compile, $parse) { - return { - restrict: 'E', - link: function(scope, elem, attr) { - var getter = $parse(attr.type), panelType = getter(scope); - var panelPath = config.panels[panelType].path; + var module = angular.module('grafana.directives'); - scope.require([panelPath + "/module"], function () { - var panelEl = angular.element(document.createElement('grafana-panel-' + panelType)); + module.directive('panelLoader', function($compile, $parse) { + return { + restrict: 'E', + link: function(scope, elem, attr) { + var getter = $parse(attr.type), panelType = getter(scope); + var panelPath = config.panels[panelType].path; + + scope.require([panelPath + "/module"], function () { + var panelEl = angular.element(document.createElement('grafana-panel-' + panelType)); + elem.append(panelEl); + $compile(panelEl)(scope); + }); + } + }; + }); + + module.directive('grafanaPanel', function() { + return { + restrict: 'E', + templateUrl: 'app/features/panel/partials/panel.html', + transclude: true, + link: function(scope, elem) { + var panelContainer = elem.find('.panel-container'); + + scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() { + panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' }); + elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false); + }); + } + }; + }); + + module.service('dynamicDirectiveSrv', function($compile, $parse, datasourceSrv) { + var self = this; + + this.addDirective = function(options, type, editorScope) { + var panelEl = angular.element(document.createElement(options.name + '-' + type)); + options.parentElem.append(panelEl); + $compile(panelEl)(editorScope); + }; + + this.define = function(options) { + var editorScope; + options.scope.$watch(options.datasourceProperty, function(newVal) { + if (editorScope) { + editorScope.$destroy(); + options.parentElem.empty(); + } + + editorScope = options.scope.$new(); + datasourceSrv.get(newVal).then(function(ds) { + self.addDirective(options, ds.meta.type, editorScope); + }); + }); + }; + }); + + module.directive('queryEditorLoader', function($compile, $parse, datasourceSrv) { + return { + restrict: 'E', + link: function(scope, elem) { + var editorScope; + + scope.$watch("panel.datasource", function() { + var datasource = scope.target.datasource || scope.panel.datasource; + + datasourceSrv.get(datasource).then(function(ds) { + if (editorScope) { + editorScope.$destroy(); + elem.empty(); + } + + editorScope = scope.$new(); + editorScope.datasource = ds; + + if (!scope.target.refId) { + scope.target.refId = 'A'; + } + + var panelEl = angular.element(document.createElement('metric-query-editor-' + ds.meta.type)); elem.append(panelEl); - $compile(panelEl)(scope); + $compile(panelEl)(editorScope); }); - } - }; - }).directive('grafanaPanel', function() { - return { - restrict: 'E', - templateUrl: 'app/features/panel/partials/panel.html', - transclude: true, - link: function(scope, elem) { - var panelContainer = elem.find('.panel-container'); + }); + } + }; + }); - scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() { - panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' }); - elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false); + module.directive('datasourceEditorView', function(dynamicDirectiveSrv) { + return { + restrict: 'E', + link: function(scope, elem, attrs) { + dynamicDirectiveSrv.define({ + datasourceProperty: attrs.datasource, + name: attrs.name, + scope: scope, + parentElem: elem, + }); + } + }; + }); + + module.directive('panelResizer', function($rootScope) { + return { + restrict: 'E', + template: '', + link: function(scope, elem) { + var resizing = false; + var lastPanel = false; + var handleOffset; + var originalHeight; + var originalWidth; + var maxWidth; + + function dragStartHandler(e) { + e.preventDefault(); + resizing = true; + + handleOffset = $(e.target).offset(); + originalHeight = parseInt(scope.row.height); + originalWidth = scope.panel.span; + maxWidth = $(document).width(); + + lastPanel = scope.row.panels[scope.row.panels.length - 1]; + + $('body').on('mousemove', moveHandler); + $('body').on('mouseup', dragEndHandler); + } + + function moveHandler(e) { + scope.row.height = originalHeight + (e.pageY - handleOffset.top); + scope.panel.span = originalWidth + (((e.pageX - handleOffset.left) / maxWidth) * 12); + scope.panel.span = Math.min(Math.max(scope.panel.span, 1), 12); + + var rowSpan = scope.dashboard.rowSpan(scope.row); + + // auto adjust other panels + if (Math.floor(rowSpan) < 14) { + // last panel should not push row down + if (lastPanel === scope.panel && rowSpan > 12) { + lastPanel.span -= rowSpan - 12; + } + // reduce width of last panel so total in row is 12 + else if (lastPanel !== scope.panel) { + lastPanel.span = lastPanel.span - (rowSpan - 12); + lastPanel.span = Math.min(Math.max(lastPanel.span, 1), 12); + } + } + + scope.$apply(function() { + scope.$broadcast('render'); }); } - }; - }); + + function dragEndHandler() { + // if close to 12 + var rowSpan = scope.dashboard.rowSpan(scope.row); + if (rowSpan < 12 && rowSpan > 11) { + lastPanel.span += 12 - rowSpan; + } + + scope.$apply(function() { + $rootScope.$broadcast('render'); + }); + + $('body').off('mousemove', moveHandler); + $('body').off('mouseup', dragEndHandler); + } + + elem.on('mousedown', dragStartHandler); + + scope.$on("$destroy", function() { + elem.off('mousedown', dragStartHandler); + }); + } + }; + }); + }); diff --git a/public/app/features/panel/panelHelper.js b/public/app/features/panel/panelHelper.js index 71e91c1d6a7..30cd88e67fc 100644 --- a/public/app/features/panel/panelHelper.js +++ b/public/app/features/panel/panelHelper.js @@ -69,8 +69,10 @@ function (angular, _, kbn, $) { } if (_.isString(scope.rangeUnparsed.from)) { - scope.panelMeta.timeInfo = "last " + scope.panel.timeFrom; - scope.rangeUnparsed.from = 'now-' + scope.panel.timeFrom; + var timeInfo = kbn.getRelativeTimeInfo(scope.panel.timeFrom); + scope.panelMeta.timeInfo = timeInfo.text; + scope.rangeUnparsed.from = timeInfo.from; + scope.rangeUnparsed.to = timeInfo.to; scope.range.from = kbn.parseDate(scope.rangeUnparsed.from); } } diff --git a/public/app/features/panel/panelMenu.js b/public/app/features/panel/panelMenu.js index c7711d99925..1e65214da37 100644 --- a/public/app/features/panel/panelMenu.js +++ b/public/app/features/panel/panelMenu.js @@ -56,7 +56,7 @@ function (angular, $, _) { if ($scope.panel.links) { _.each($scope.panel.links, function(link) { - var info = linkSrv.getPanelLinkAnchorInfo(link); + var info = linkSrv.getPanelLinkAnchorInfo(link, $scope.panel.scopedVars); menu.push({text: info.title, href: info.href, target: info.target }); }); } diff --git a/public/app/features/panel/panelSrv.js b/public/app/features/panel/panelSrv.js index 037125b4aad..d93a4575615 100644 --- a/public/app/features/panel/panelSrv.js +++ b/public/app/features/panel/panelSrv.js @@ -43,17 +43,41 @@ function (angular, _, config) { }); }; - $scope.addDataQuery = function() { - $scope.panel.targets.push({target: ''}); + $scope.addDataQuery = function(datasource) { + $scope.dashboard.addDataQueryTo($scope.panel, datasource); }; $scope.removeDataQuery = function (query) { - $scope.panel.targets = _.without($scope.panel.targets, query); + $scope.dashboard.removeDataQuery($scope.panel, query); $scope.get_data(); }; + $scope.duplicateDataQuery = function(query) { + $scope.dashboard.duplicateDataQuery($scope.panel, query); + }; + + $scope.moveDataQuery = function(fromIndex, toIndex) { + $scope.dashboard.moveDataQuery($scope.panel, fromIndex, toIndex); + }; + $scope.setDatasource = function(datasource) { - $scope.panel.datasource = datasource; + // switching to mixed + if (datasource.meta.mixed) { + _.each($scope.panel.targets, function(target) { + target.datasource = $scope.panel.datasource; + if (target.datasource === null) { + target.datasource = config.defaultDatasource; + } + }); + } + // switching from mixed + else if ($scope.datasource && $scope.datasource.meta.mixed) { + _.each($scope.panel.targets, function(target) { + delete target.datasource; + }); + } + + $scope.panel.datasource = datasource.value; $scope.datasource = null; $scope.get_data(); }; diff --git a/public/app/features/panel/partials/panel.html b/public/app/features/panel/partials/panel.html index 6fbab9de70a..a7fef0f5d67 100644 --- a/public/app/features/panel/partials/panel.html +++ b/public/app/features/panel/partials/panel.html @@ -16,6 +16,7 @@
      +
      diff --git a/public/app/features/panellinks/linkSrv.js b/public/app/features/panellinks/linkSrv.js index c11cdb40b52..413d3d9303b 100644 --- a/public/app/features/panellinks/linkSrv.js +++ b/public/app/features/panellinks/linkSrv.js @@ -59,20 +59,21 @@ function (angular, kbn, _) { return info; }; - this.getPanelLinkAnchorInfo = function(link) { + this.getPanelLinkAnchorInfo = function(link, scopedVars) { var info = {}; if (link.type === 'absolute') { - info.target = link.targetBlank ? '_blank' : ''; - info.href = templateSrv.replace(link.url || ''); - info.title = templateSrv.replace(link.title || ''); + info.target = link.targetBlank ? '_blank' : '_self'; + info.href = templateSrv.replace(link.url || '', scopedVars); + info.title = templateSrv.replace(link.title || '', scopedVars); info.href += '?'; } else if (link.dashUri) { info.href = 'dashboard/' + link.dashUri + '?'; - info.title = templateSrv.replace(link.title || ''); + info.title = templateSrv.replace(link.title || '', scopedVars); + info.target = link.targetBlank ? '_blank' : ''; } else { - info.title = templateSrv.replace(link.title || ''); + info.title = templateSrv.replace(link.title || '', scopedVars); var slug = kbn.slugifyForUrl(link.dashboard || ''); info.href = 'dashboard/db/' + slug + '?'; } @@ -86,12 +87,12 @@ function (angular, kbn, _) { } if (link.includeVars) { - templateSrv.fillVariableValuesForUrl(params); + templateSrv.fillVariableValuesForUrl(params, scopedVars); } info.href = this.addParamsToUrl(info.href, params); if (link.params) { - info.href += "&" + templateSrv.replace(link.params); + info.href += "&" + templateSrv.replace(link.params, scopedVars); } return info; diff --git a/public/app/features/profile/partials/profile.html b/public/app/features/profile/partials/profile.html index 5512245b275..d246b4d3914 100644 --- a/public/app/features/profile/partials/profile.html +++ b/public/app/features/profile/partials/profile.html @@ -8,63 +8,59 @@
      -

      Profile details

      +

      Profile

      -
      -
      -
        -
      • - Name -
      • -
      • - -
      • -
      -
      -
      -
      -
        -
      • - Email -
      • -
      • - -
      • -
      -
      -
      -
      -
        -
      • - Username -
      • -
      • - -
      • -
      -
      -
      - -
      -
        -
      • - UI Theme -
      • -
      • - -
      • -
      -
      -
      - +
      +
        +
      • + Name +
      • +
      • + +
      • +
      +
      +
      +
      +
        +
      • + Email +
      • +
      • + +
      • +
      +
      +
      +
      +
        +
      • + Username +
      • +
      • + +
      • +
      +
      +
      +
        +
      • + UI Theme +
      • +
      • + +
      • +
      +
      +

      -

      Organizations

      +

      Organizations

      diff --git a/public/app/features/profile/partials/select_org.html b/public/app/features/profile/partials/select_org.html new file mode 100644 index 00000000000..2e7a992b4fa --- /dev/null +++ b/public/app/features/profile/partials/select_org.html @@ -0,0 +1,53 @@ +
      + + + +
      + + + +
      + + {{org.name}} ({{org.role}}) + +
      +
      + +
      + + +
      + +
      + +
      + +
      + + diff --git a/public/app/features/profile/selectOrgCtrl.js b/public/app/features/profile/selectOrgCtrl.js new file mode 100644 index 00000000000..9a588151432 --- /dev/null +++ b/public/app/features/profile/selectOrgCtrl.js @@ -0,0 +1,33 @@ +define([ + 'angular', + 'config', +], +function (angular, config) { + 'use strict'; + + var module = angular.module('grafana.controllers'); + + module.controller('SelectOrgCtrl', function($scope, backendSrv, contextSrv) { + + contextSrv.sidemenu = false; + + $scope.init = function() { + $scope.getUserOrgs(); + }; + + $scope.getUserOrgs = function() { + backendSrv.get('/api/user/orgs').then(function(orgs) { + $scope.orgs = orgs; + }); + }; + + $scope.setUsingOrg = function(org) { + backendSrv.post('/api/user/using/' + org.orgId).then(function() { + window.location.href = config.appSubUrl + '/'; + }); + }; + + $scope.init(); + + }); +}); diff --git a/public/app/features/templating/editorCtrl.js b/public/app/features/templating/editorCtrl.js index f48452e4569..524cc9f86fd 100644 --- a/public/app/features/templating/editorCtrl.js +++ b/public/app/features/templating/editorCtrl.js @@ -22,13 +22,17 @@ function (angular, _) { }; $scope.init = function() { - $scope.editor = { index: 0 }; - $scope.datasources = datasourceSrv.getMetricSources(); + $scope.mode = 'list'; + + $scope.datasources = _.filter(datasourceSrv.getMetricSources(), function(ds) { + return !ds.meta.builtIn; + }); + $scope.variables = templateSrv.variables; $scope.reset(); - $scope.$watch('editor.index', function(index) { - if ($scope.currentIsNew === false && index === 1) { + $scope.$watch('mode', function(val) { + if (val === 'new') { $scope.reset(); } }); @@ -72,7 +76,7 @@ function (angular, _) { $scope.edit = function(variable) { $scope.current = variable; $scope.currentIsNew = false; - $scope.editor.index = 2; + $scope.mode = 'edit'; if ($scope.current.datasource === void 0) { $scope.current.datasource = null; @@ -85,7 +89,7 @@ function (angular, _) { if ($scope.isValid()) { $scope.runQuery().then(function() { $scope.reset(); - $scope.editor.index = 0; + $scope.mode = 'list'; }); } }; diff --git a/public/app/features/templating/partials/editor.html b/public/app/features/templating/partials/editor.html index b515f2a7535..ad8dca84f49 100644 --- a/public/app/features/templating/partials/editor.html +++ b/public/app/features/templating/partials/editor.html @@ -5,10 +5,27 @@ Templating
      +
    • - +
    @@ -246,7 +263,7 @@ Tag values query
  • - +
  • @@ -278,8 +295,8 @@
    - - + +
    diff --git a/public/app/features/templating/templateSrv.js b/public/app/features/templating/templateSrv.js index 40b661b0cbc..b00fba9e71c 100644 --- a/public/app/features/templating/templateSrv.js +++ b/public/app/features/templating/templateSrv.js @@ -115,17 +115,20 @@ function (angular, _) { }); }; - this.fillVariableValuesForUrl = function(params) { - var toUrlVal = function(current) { - if (current.text === 'All') { - return 'All'; - } else { - return current.value; - } - }; - + this.fillVariableValuesForUrl = function(params, scopedVars) { _.each(this.variables, function(variable) { - params['var-' + variable.name] = toUrlVal(variable.current); + var current = variable.current; + var value = current.value; + + if (current.text === 'All') { + value = 'All'; + } + + if (scopedVars && scopedVars[variable.name] !== void 0) { + value = scopedVars[variable.name].value; + } + + params['var-' + variable.name] = value; }); }; diff --git a/public/app/features/templating/templateValuesSrv.js b/public/app/features/templating/templateValuesSrv.js index a5767ffa312..13e85d08a33 100644 --- a/public/app/features/templating/templateValuesSrv.js +++ b/public/app/features/templating/templateValuesSrv.js @@ -82,8 +82,10 @@ function (angular, _, kbn) { variable.current.text = variable.current.value.join(' + '); } + self.selectOptionsForCurrentValue(variable); + templateSrv.updateTemplateData(); - return this.updateOptionsInChildVariables(variable); + return self.updateOptionsInChildVariables(variable); }; this.variableUpdated = function(variable) { @@ -128,6 +130,25 @@ function (angular, _, kbn) { .then(_.partial(this.validateVariableSelectionState, variable)); }; + this.selectOptionsForCurrentValue = function(variable) { + var i, y, value, option; + + for (i = 0; i < variable.options.length; i++) { + option = variable.options[i]; + option.selected = false; + if (_.isArray(variable.current.value)) { + for (y = 0; y < variable.current.value.length; y++) { + value = variable.current.value[y]; + if (option.value === value) { + option.selected = true; + } + } + } else if (option.value === variable.current.value) { + option.selected = true; + } + } + }; + this.validateVariableSelectionState = function(variable) { if (!variable.current) { if (!variable.options.length) { return; } @@ -135,15 +156,7 @@ function (angular, _, kbn) { } if (_.isArray(variable.current.value)) { - for (var i = 0; i < variable.current.value.length; i++) { - var value = variable.current.value[i]; - for (var y = 0; y < variable.options.length; y++) { - var option = variable.options[y]; - if (option.value === value) { - option.selected = true; - } - } - } + self.selectOptionsForCurrentValue(variable); } else { var currentOption = _.findWhere(variable.options, { text: variable.current.text }); if (currentOption) { @@ -218,10 +231,25 @@ function (angular, _, kbn) { } return _.map(_.keys(options).sort(), function(key) { - return { text: key, value: key }; + var option = { text: key, value: key }; + + // check if values need to be regex escaped + if (self.shouldRegexEscape(variable)) { + option.value = self.regexEscape(option.value); + } + + return option; }); }; + this.shouldRegexEscape = function(variable) { + return (variable.includeAll || variable.multi) && variable.allFormat.indexOf('regex') !== -1; + }; + + this.regexEscape = function(value) { + return value.replace(/[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&'); + }; + this.addAllOption = function(variable) { var allValue = ''; switch(variable.allFormat) { @@ -232,7 +260,9 @@ function (angular, _, kbn) { allValue = '.*'; break; case 'regex values': - allValue = '(' + _.pluck(variable.options, 'text').join('|') + ')'; + allValue = '(' + _.map(variable.options, function(option) { + return self.regexEscape(option.text); + }).join('|') + ')'; break; default: allValue = '{'; diff --git a/public/app/filters/all.js b/public/app/filters/all.js deleted file mode 100755 index c904c7c9449..00000000000 --- a/public/app/filters/all.js +++ /dev/null @@ -1,72 +0,0 @@ -define(['angular', 'jquery', 'lodash', 'moment'], function (angular, $, _, moment) { - 'use strict'; - - var module = angular.module('grafana.filters'); - - module.filter('stringSort', function() { - return function(input) { - return input.sort(); - }; - }); - - module.filter('slice', function() { - return function(arr, start, end) { - if(!_.isUndefined(arr)) { - return arr.slice(start, end); - } - }; - }); - - module.filter('stringify', function() { - return function(arr) { - if(_.isObject(arr) && !_.isArray(arr)) { - return angular.toJson(arr); - } else { - return _.isNull(arr) ? null : arr.toString(); - } - }; - }); - - module.filter('moment', function() { - return function(date,mode) { - switch(mode) { - case 'ago': - return moment(date).fromNow(); - } - return moment(date).fromNow(); - }; - }); - - module.filter('noXml', function() { - var noXml = function(text) { - return _.isString(text) - ? text - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/'/g, ''') - .replace(/"/g, '"') - : text; - }; - return function(text) { - return _.isArray(text) - ? _.map(text, noXml) - : noXml(text); - }; - }); - - module.filter('interpolateTemplateVars', function(templateSrv) { - function interpolateTemplateVars(text, scope) { - if (scope.panel) { - return templateSrv.replaceWithText(text, scope.panel.scopedVars); - } else { - return templateSrv.replaceWithText(text, scope.row.scopedVars); - } - } - - interpolateTemplateVars.$stateful = true; - - return interpolateTemplateVars; - }); - -}); diff --git a/public/app/headers/angularjs/angularjs.d.ts b/public/app/headers/angularjs/angularjs.d.ts new file mode 100644 index 00000000000..2e746389d90 --- /dev/null +++ b/public/app/headers/angularjs/angularjs.d.ts @@ -0,0 +1,1746 @@ +// Type definitions for Angular JS 1.4+ +// Project: http://angularjs.org +// Definitions by: Diego Vilar +// Definitions: https://github.com/borisyankov/DefinitelyTyped + + +/// + + +// Support for painless dependency injection +interface Function { + $inject?: string[]; +} + +// Collapse angular into ng +import ng = angular; +// Support AMD require +declare module 'angular' { + var angular: angular.IAngularStatic; + export = angular; +} + +/////////////////////////////////////////////////////////////////////////////// +// ng module (angular.js) +/////////////////////////////////////////////////////////////////////////////// +declare module angular { + + // not directly implemented, but ensures that constructed class implements $get + interface IServiceProviderClass { + new (...args: any[]): IServiceProvider; + } + + interface IServiceProviderFactory { + (...args: any[]): IServiceProvider; + } + + // All service providers extend this interface + interface IServiceProvider { + $get: any; + } + + interface IAngularBootstrapConfig { + strictDi?: boolean; + } + + /////////////////////////////////////////////////////////////////////////// + // AngularStatic + // see http://docs.angularjs.org/api + /////////////////////////////////////////////////////////////////////////// + interface IAngularStatic { + bind(context: any, fn: Function, ...args: any[]): Function; + + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: string, modules?: string, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: string, modules?: Function, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: string, modules?: string[], config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: JQuery, modules?: string, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: JQuery, modules?: Function, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: JQuery, modules?: string[], config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: Element, modules?: string, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: Element, modules?: Function, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: Element, modules?: string[], config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: Document, modules?: string, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: Document, modules?: Function, config?: IAngularBootstrapConfig): auto.IInjectorService; + /** + * Use this function to manually start up angular application. + * + * @param element DOM element which is the root of angular application. + * @param modules An array of modules to load into the application. + * Each item in the array should be the name of a predefined module or a (DI annotated) + * function that will be invoked by the injector as a run block. + * @param config an object for defining configuration options for the application. The following keys are supported: + * - `strictDi`: disable automatic function annotation for the application. This is meant to assist in finding bugs which break minified code. + */ + bootstrap(element: Document, modules?: string[], config?: IAngularBootstrapConfig): auto.IInjectorService; + + /** + * Creates a deep copy of source, which should be an object or an array. + * + * - If no destination is supplied, a copy of the object or array is created. + * - If a destination is provided, all of its elements (for array) or properties (for objects) are deleted and then all elements/properties from the source are copied to it. + * - If source is not an object or array (inc. null and undefined), source is returned. + * - If source is identical to 'destination' an exception will be thrown. + * + * @param source The source that will be used to make a copy. Can be any type, including primitives, null, and undefined. + * @param destination Destination into which the source is copied. If provided, must be of the same type as source. + */ + copy(source: T, destination?: T): T; + + /** + * Wraps a raw DOM element or HTML string as a jQuery element. + * + * If jQuery is available, angular.element is an alias for the jQuery function. If jQuery is not available, angular.element delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." + */ + element: IAugmentedJQueryStatic; + equals(value1: any, value2: any): boolean; + extend(destination: any, ...sources: any[]): any; + + /** + * Invokes the iterator function once for each item in obj collection, which can be either an object or an array. The iterator function is invoked with iterator(value, key), where value is the value of an object property or an array element and key is the object property key or array element index. Specifying a context for the function is optional. + * + * It is worth noting that .forEach does not iterate over inherited properties because it filters using the hasOwnProperty method. + * + * @param obj Object to iterate over. + * @param iterator Iterator function. + * @param context Object to become context (this) for the iterator function. + */ + forEach(obj: T[], iterator: (value: T, key: number) => any, context?: any): any; + /** + * Invokes the iterator function once for each item in obj collection, which can be either an object or an array. The iterator function is invoked with iterator(value, key), where value is the value of an object property or an array element and key is the object property key or array element index. Specifying a context for the function is optional. + * + * It is worth noting that .forEach does not iterate over inherited properties because it filters using the hasOwnProperty method. + * + * @param obj Object to iterate over. + * @param iterator Iterator function. + * @param context Object to become context (this) for the iterator function. + */ + forEach(obj: { [index: string]: T; }, iterator: (value: T, key: string) => any, context?: any): any; + /** + * Invokes the iterator function once for each item in obj collection, which can be either an object or an array. The iterator function is invoked with iterator(value, key), where value is the value of an object property or an array element and key is the object property key or array element index. Specifying a context for the function is optional. + * + * It is worth noting that .forEach does not iterate over inherited properties because it filters using the hasOwnProperty method. + * + * @param obj Object to iterate over. + * @param iterator Iterator function. + * @param context Object to become context (this) for the iterator function. + */ + forEach(obj: any, iterator: (value: any, key: any) => any, context?: any): any; + + fromJson(json: string): any; + identity(arg?: T): T; + injector(modules?: any[], strictDi?: boolean): auto.IInjectorService; + isArray(value: any): boolean; + isDate(value: any): boolean; + isDefined(value: any): boolean; + isElement(value: any): boolean; + isFunction(value: any): boolean; + isNumber(value: any): boolean; + isObject(value: any): boolean; + isString(value: any): boolean; + isUndefined(value: any): boolean; + lowercase(str: string): string; + + /** + * Deeply extends the destination object dst by copying own enumerable properties from the src object(s) to dst. You can specify multiple src objects. If you want to preserve original objects, you can do so by passing an empty object as the target: var object = angular.merge({}, object1, object2). + * + * Unlike extend(), merge() recursively descends into object properties of source objects, performing a deep copy. + * + * @param dst Destination object. + * @param src Source object(s). + */ + merge(dst: any, ...src: any[]): any; + + /** + * The angular.module is a global place for creating, registering and retrieving Angular modules. All modules (angular core or 3rd party) that should be available to an application must be registered using this mechanism. + * + * When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved. + * + * @param name The name of the module to create or retrieve. + * @param requires The names of modules this module depends on. If specified then new module is being created. If unspecified then the module is being retrieved for further configuration. + * @param configFn Optional configuration function for the module. + */ + module( + name: string, + requires?: string[], + configFn?: Function): IModule; + + noop(...args: any[]): void; + reloadWithDebugInfo(): void; + toJson(obj: any, pretty?: boolean): string; + uppercase(str: string): string; + version: { + full: string; + major: number; + minor: number; + dot: number; + codeName: string; + }; + } + + /////////////////////////////////////////////////////////////////////////// + // Module + // see http://docs.angularjs.org/api/angular.Module + /////////////////////////////////////////////////////////////////////////// + interface IModule { + animation(name: string, animationFactory: Function): IModule; + animation(name: string, inlineAnnotatedFunction: any[]): IModule; + animation(object: Object): IModule; + /** + * Use this method to register work which needs to be performed on module loading. + * + * @param configFn Execute this function on module load. Useful for service configuration. + */ + config(configFn: Function): IModule; + /** + * Use this method to register work which needs to be performed on module loading. + * + * @param inlineAnnotatedFunction Execute this function on module load. Useful for service configuration. + */ + config(inlineAnnotatedFunction: any[]): IModule; + /** + * Register a constant service, such as a string, a number, an array, an object or a function, with the $injector. Unlike value it can be injected into a module configuration function (see config) and it cannot be overridden by an Angular decorator. + * + * @param name The name of the constant. + * @param value The constant value. + */ + constant(name: string, value: any): IModule; + constant(object: Object): IModule; + /** + * The $controller service is used by Angular to create new controllers. + * + * This provider allows controller registration via the register method. + * + * @param name Controller name, or an object map of controllers where the keys are the names and the values are the constructors. + * @param controllerConstructor Controller constructor fn (optionally decorated with DI annotations in the array notation). + */ + controller(name: string, controllerConstructor: Function): IModule; + /** + * The $controller service is used by Angular to create new controllers. + * + * This provider allows controller registration via the register method. + * + * @param name Controller name, or an object map of controllers where the keys are the names and the values are the constructors. + * @param controllerConstructor Controller constructor fn (optionally decorated with DI annotations in the array notation). + */ + controller(name: string, inlineAnnotatedConstructor: any[]): IModule; + controller(object: Object): IModule; + /** + * Register a new directive with the compiler. + * + * @param name Name of the directive in camel-case (i.e. ngBind which will match as ng-bind) + * @param directiveFactory An injectable directive factory function. + */ + directive(name: string, directiveFactory: IDirectiveFactory): IModule; + /** + * Register a new directive with the compiler. + * + * @param name Name of the directive in camel-case (i.e. ngBind which will match as ng-bind) + * @param directiveFactory An injectable directive factory function. + */ + directive(name: string, inlineAnnotatedFunction: any[]): IModule; + directive(object: Object): IModule; + /** + * Register a service factory, which will be called to return the service instance. This is short for registering a service where its provider consists of only a $get property, which is the given service factory function. You should use $provide.factory(getFn) if you do not need to configure your service in a provider. + * + * @param name The name of the instance. + * @param $getFn The $getFn for the instance creation. Internally this is a short hand for $provide.provider(name, {$get: $getFn}). + */ + factory(name: string, $getFn: Function): IModule; + /** + * Register a service factory, which will be called to return the service instance. This is short for registering a service where its provider consists of only a $get property, which is the given service factory function. You should use $provide.factory(getFn) if you do not need to configure your service in a provider. + * + * @param name The name of the instance. + * @param inlineAnnotatedFunction The $getFn for the instance creation. Internally this is a short hand for $provide.provider(name, {$get: $getFn}). + */ + factory(name: string, inlineAnnotatedFunction: any[]): IModule; + factory(object: Object): IModule; + filter(name: string, filterFactoryFunction: Function): IModule; + filter(name: string, inlineAnnotatedFunction: any[]): IModule; + filter(object: Object): IModule; + provider(name: string, serviceProviderFactory: IServiceProviderFactory): IModule; + provider(name: string, serviceProviderConstructor: IServiceProviderClass): IModule; + provider(name: string, inlineAnnotatedConstructor: any[]): IModule; + provider(name: string, providerObject: IServiceProvider): IModule; + provider(object: Object): IModule; + /** + * Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests. + */ + run(initializationFunction: Function): IModule; + /** + * Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests. + */ + run(inlineAnnotatedFunction: any[]): IModule; + service(name: string, serviceConstructor: Function): IModule; + service(name: string, inlineAnnotatedConstructor: any[]): IModule; + service(object: Object): IModule; + /** + * Register a value service with the $injector, such as a string, a number, an array, an object or a function. This is short for registering a service where its provider's $get property is a factory function that takes no arguments and returns the value service. + + Value services are similar to constant services, except that they cannot be injected into a module configuration function (see config) but they can be overridden by an Angular decorator. + * + * @param name The name of the instance. + * @param value The value. + */ + value(name: string, value: any): IModule; + value(object: Object): IModule; + + /** + * Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service. + * @param name The name of the service to decorate + * @param decorator This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments: $delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to. + */ + decorator(name:string, decoratorConstructor: Function): IModule; + decorator(name:string, inlineAnnotatedConstructor: any[]): IModule; + + // Properties + name: string; + requires: string[]; + } + + /////////////////////////////////////////////////////////////////////////// + // Attributes + // see http://docs.angularjs.org/api/ng.$compile.directive.Attributes + /////////////////////////////////////////////////////////////////////////// + interface IAttributes { + /** + * this is necessary to be able to access the scoped attributes. it's not very elegant + * because you have to use attrs['foo'] instead of attrs.foo but I don't know of a better way + * this should really be limited to return string but it creates this problem: http://stackoverflow.com/q/17201854/165656 + */ + [name: string]: any; + + /** + * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with x- or data-) to its normalized, camelCase form. + * + * Also there is special case for Moz prefix starting with upper case letter. + * + * For further information check out the guide on @see https://docs.angularjs.org/guide/directive#matching-directives + */ + $normalize(name: string): void; + + /** + * Adds the CSS class value specified by the classVal parameter to the + * element. If animations are enabled then an animation will be triggered + * for the class addition. + */ + $addClass(classVal: string): void; + + /** + * Removes the CSS class value specified by the classVal parameter from the + * element. If animations are enabled then an animation will be triggered for + * the class removal. + */ + $removeClass(classVal: string): void; + + /** + * Set DOM element attribute value. + */ + $set(key: string, value: any): void; + + /** + * Observes an interpolated attribute. + * The observer function will be invoked once during the next $digest + * following compilation. The observer is then invoked whenever the + * interpolated value changes. + */ + $observe(name: string, fn: (value?: T) => any): Function; + + /** + * A map of DOM element attribute names to the normalized name. This is needed + * to do reverse lookup from normalized name back to actual name. + */ + $attr: Object; + } + + /** + * form.FormController - type in module ng + * see https://docs.angularjs.org/api/ng/type/form.FormController + */ + interface IFormController { + + /** + * Indexer which should return ng.INgModelController for most properties but cannot because of "All named properties must be assignable to string indexer type" constraint - see https://github.com/Microsoft/TypeScript/issues/272 + */ + [name: string]: any; + + $pristine: boolean; + $dirty: boolean; + $valid: boolean; + $invalid: boolean; + $submitted: boolean; + $error: any; + $addControl(control: INgModelController): void; + $removeControl(control: INgModelController): void; + $setValidity(validationErrorKey: string, isValid: boolean, control: INgModelController): void; + $setDirty(): void; + $setPristine(): void; + $commitViewValue(): void; + $rollbackViewValue(): void; + $setSubmitted(): void; + $setUntouched(): void; + } + + /////////////////////////////////////////////////////////////////////////// + // NgModelController + // see http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController + /////////////////////////////////////////////////////////////////////////// + interface INgModelController { + $render(): void; + $setValidity(validationErrorKey: string, isValid: boolean): void; + // Documentation states viewValue and modelValue to be a string but other + // types do work and it's common to use them. + $setViewValue(value: any, trigger?: string): void; + $setPristine(): void; + $setDirty(): void; + $validate(): void; + $setTouched(): void; + $setUntouched(): void; + $rollbackViewValue(): void; + $commitViewValue(): void; + $isEmpty(value: any): boolean; + + $viewValue: any; + + $modelValue: any; + + $parsers: IModelParser[]; + $formatters: IModelFormatter[]; + $viewChangeListeners: IModelViewChangeListener[]; + $error: any; + $name: string; + + $touched: boolean; + $untouched: boolean; + + $validators: IModelValidators; + $asyncValidators: IAsyncModelValidators; + + $pending: any; + $pristine: boolean; + $dirty: boolean; + $valid: boolean; + $invalid: boolean; + } + + interface IModelValidators { + /** + * viewValue is any because it can be an object that is called in the view like $viewValue.name:$viewValue.subName + */ + [index: string]: (modelValue: any, viewValue: any) => boolean; + } + + interface IAsyncModelValidators { + [index: string]: (modelValue: any, viewValue: any) => IPromise; + } + + interface IModelParser { + (value: any): any; + } + + interface IModelFormatter { + (value: any): any; + } + + interface IModelViewChangeListener { + (): void; + } + + /** + * $rootScope - $rootScopeProvider - service in module ng + * see https://docs.angularjs.org/api/ng/type/$rootScope.Scope and https://docs.angularjs.org/api/ng/service/$rootScope + */ + interface IRootScopeService { + [index: string]: any; + + $apply(): any; + $apply(exp: string): any; + $apply(exp: (scope: IScope) => any): any; + + $applyAsync(): any; + $applyAsync(exp: string): any; + $applyAsync(exp: (scope: IScope) => any): any; + + /** + * Dispatches an event name downwards to all child scopes (and their children) notifying the registered $rootScope.Scope listeners. + * + * The event life cycle starts at the scope on which $broadcast was called. All listeners listening for name event on this scope get notified. Afterwards, the event propagates to all direct and indirect scopes of the current scope and calls all registered listeners along the way. The event cannot be canceled. + * + * Any exception emitted from the listeners will be passed onto the $exceptionHandler service. + * + * @param name Event name to broadcast. + * @param args Optional one or more arguments which will be passed onto the event listeners. + */ + $broadcast(name: string, ...args: any[]): IAngularEvent; + $destroy(): void; + $digest(): void; + /** + * Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners. + * + * The event life cycle starts at the scope on which $emit was called. All listeners listening for name event on this scope get notified. Afterwards, the event traverses upwards toward the root scope and calls all registered listeners along the way. The event will stop propagating if one of the listeners cancels it. + * + * Any exception emitted from the listeners will be passed onto the $exceptionHandler service. + * + * @param name Event name to emit. + * @param args Optional one or more arguments which will be passed onto the event listeners. + */ + $emit(name: string, ...args: any[]): IAngularEvent; + + $eval(): any; + $eval(expression: string, locals?: Object): any; + $eval(expression: (scope: IScope) => any, locals?: Object): any; + + $evalAsync(): void; + $evalAsync(expression: string): void; + $evalAsync(expression: (scope: IScope) => any): void; + + // Defaults to false by the implementation checking strategy + $new(isolate?: boolean, parent?: IScope): IScope; + + /** + * Listens on events of a given type. See $emit for discussion of event life cycle. + * + * The event listener function format is: function(event, args...). + * + * @param name Event name to listen on. + * @param listener Function to call when the event is emitted. + */ + $on(name: string, listener: (event: IAngularEvent, ...args: any[]) => any): Function; + + $watch(watchExpression: string, listener?: string, objectEquality?: boolean): Function; + $watch(watchExpression: string, listener?: (newValue: T, oldValue: T, scope: IScope) => any, objectEquality?: boolean): Function; + $watch(watchExpression: (scope: IScope) => any, listener?: string, objectEquality?: boolean): Function; + $watch(watchExpression: (scope: IScope) => T, listener?: (newValue: T, oldValue: T, scope: IScope) => any, objectEquality?: boolean): Function; + + $watchCollection(watchExpression: string, listener: (newValue: T, oldValue: T, scope: IScope) => any): Function; + $watchCollection(watchExpression: (scope: IScope) => T, listener: (newValue: T, oldValue: T, scope: IScope) => any): Function; + + $watchGroup(watchExpressions: any[], listener: (newValue: any, oldValue: any, scope: IScope) => any): Function; + $watchGroup(watchExpressions: { (scope: IScope): any }[], listener: (newValue: any, oldValue: any, scope: IScope) => any): Function; + + $parent: IScope; + $root: IRootScopeService; + $id: number; + + // Hidden members + $$isolateBindings: any; + $$phase: any; + } + + interface IScope extends IRootScopeService { } + + /** + * $scope for ngRepeat directive. + * see https://docs.angularjs.org/api/ng/directive/ngRepeat + */ + interface IRepeatScope extends IScope { + + /** + * iterator offset of the repeated element (0..length-1). + */ + $index: number; + + /** + * true if the repeated element is first in the iterator. + */ + $first: boolean; + + /** + * true if the repeated element is between the first and last in the iterator. + */ + $middle: boolean; + + /** + * true if the repeated element is last in the iterator. + */ + $last: boolean; + + /** + * true if the iterator position $index is even (otherwise false). + */ + $even: boolean; + + /** + * true if the iterator position $index is odd (otherwise false). + */ + $odd: boolean; + + } + + interface IAngularEvent { + /** + * the scope on which the event was $emit-ed or $broadcast-ed. + */ + targetScope: IScope; + /** + * the scope that is currently handling the event. Once the event propagates through the scope hierarchy, this property is set to null. + */ + currentScope: IScope; + /** + * name of the event. + */ + name: string; + /** + * calling stopPropagation function will cancel further event propagation (available only for events that were $emit-ed). + */ + stopPropagation?: Function; + /** + * calling preventDefault sets defaultPrevented flag to true. + */ + preventDefault: Function; + /** + * true if preventDefault was called. + */ + defaultPrevented: boolean; + } + + /////////////////////////////////////////////////////////////////////////// + // WindowService + // see http://docs.angularjs.org/api/ng.$window + /////////////////////////////////////////////////////////////////////////// + interface IWindowService extends Window { + [key: string]: any; + } + + /////////////////////////////////////////////////////////////////////////// + // BrowserService + // TODO undocumented, so we need to get it from the source code + /////////////////////////////////////////////////////////////////////////// + interface IBrowserService { + defer: angular.ITimeoutService; + [key: string]: any; + } + + /////////////////////////////////////////////////////////////////////////// + // TimeoutService + // see http://docs.angularjs.org/api/ng.$timeout + /////////////////////////////////////////////////////////////////////////// + interface ITimeoutService { + (delay?: number, invokeApply?: boolean): IPromise; + (fn: (...args: any[]) => T, delay?: number, invokeApply?: boolean, ...args: any[]): IPromise; + cancel(promise?: IPromise): boolean; + } + + /////////////////////////////////////////////////////////////////////////// + // IntervalService + // see http://docs.angularjs.org/api/ng.$interval + /////////////////////////////////////////////////////////////////////////// + interface IIntervalService { + (func: Function, delay: number, count?: number, invokeApply?: boolean): IPromise; + cancel(promise: IPromise): boolean; + } + + /////////////////////////////////////////////////////////////////////////// + // AngularProvider + // see http://docs.angularjs.org/api/ng/provider/$animateProvider + /////////////////////////////////////////////////////////////////////////// + interface IAnimateProvider { + /** + * Registers a new injectable animation factory function. + * + * @param name The name of the animation. + * @param factory The factory function that will be executed to return the animation object. + */ + register(name: string, factory: () => IAnimateCallbackObject): void; + + /** + * Gets and/or sets the CSS class expression that is checked when performing an animation. + * + * @param expression The className expression which will be checked against all animations. + * @returns The current CSS className expression value. If null then there is no expression value. + */ + classNameFilter(expression?: RegExp): RegExp; + } + + /** + * The animation object which contains callback functions for each event that is expected to be animated. + */ + interface IAnimateCallbackObject { + eventFn(element: Node, doneFn: () => void): Function; + } + + /** + * $filter - $filterProvider - service in module ng + * + * Filters are used for formatting data displayed to the user. + * + * see https://docs.angularjs.org/api/ng/service/$filter + */ + interface IFilterService { + /** + * Usage: + * $filter(name); + * + * @param name Name of the filter function to retrieve + */ + (name: string): Function; + } + + /** + * $filterProvider - $filter - provider in module ng + * + * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To achieve this a filter definition consists of a factory function which is annotated with dependencies and is responsible for creating a filter function. + * + * see https://docs.angularjs.org/api/ng/provider/$filterProvider + */ + interface IFilterProvider extends IServiceProvider { + /** + * register(name); + * + * @param name Name of the filter function, or an object map of filters where the keys are the filter names and the values are the filter factories. Note: Filter names must be valid angular Expressions identifiers, such as uppercase or orderBy. Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace your filters, then you can use capitalization (myappSubsectionFilterx) or underscores (myapp_subsection_filterx). + */ + register(name: string | {}): IServiceProvider; + } + + /////////////////////////////////////////////////////////////////////////// + // LocaleService + // see http://docs.angularjs.org/api/ng.$locale + /////////////////////////////////////////////////////////////////////////// + interface ILocaleService { + id: string; + + // These are not documented + // Check angular's i18n files for exemples + NUMBER_FORMATS: ILocaleNumberFormatDescriptor; + DATETIME_FORMATS: ILocaleDateTimeFormatDescriptor; + pluralCat: (num: any) => string; + } + + interface ILocaleNumberFormatDescriptor { + DECIMAL_SEP: string; + GROUP_SEP: string; + PATTERNS: ILocaleNumberPatternDescriptor[]; + CURRENCY_SYM: string; + } + + interface ILocaleNumberPatternDescriptor { + minInt: number; + minFrac: number; + maxFrac: number; + posPre: string; + posSuf: string; + negPre: string; + negSuf: string; + gSize: number; + lgSize: number; + } + + interface ILocaleDateTimeFormatDescriptor { + MONTH: string[]; + SHORTMONTH: string[]; + DAY: string[]; + SHORTDAY: string[]; + AMPMS: string[]; + medium: string; + short: string; + fullDate: string; + longDate: string; + mediumDate: string; + shortDate: string; + mediumTime: string; + shortTime: string; + } + + /////////////////////////////////////////////////////////////////////////// + // LogService + // see http://docs.angularjs.org/api/ng.$log + // see http://docs.angularjs.org/api/ng.$logProvider + /////////////////////////////////////////////////////////////////////////// + interface ILogService { + debug: ILogCall; + error: ILogCall; + info: ILogCall; + log: ILogCall; + warn: ILogCall; + } + + interface ILogProvider extends IServiceProvider { + debugEnabled(): boolean; + debugEnabled(enabled: boolean): ILogProvider; + } + + // We define this as separate interface so we can reopen it later for + // the ngMock module. + interface ILogCall { + (...args: any[]): void; + } + + /////////////////////////////////////////////////////////////////////////// + // ParseService + // see http://docs.angularjs.org/api/ng.$parse + // see http://docs.angularjs.org/api/ng.$parseProvider + /////////////////////////////////////////////////////////////////////////// + interface IParseService { + (expression: string): ICompiledExpression; + } + + interface IParseProvider { + logPromiseWarnings(): boolean; + logPromiseWarnings(value: boolean): IParseProvider; + + unwrapPromises(): boolean; + unwrapPromises(value: boolean): IParseProvider; + } + + interface ICompiledExpression { + (context: any, locals?: any): any; + + // If value is not provided, undefined is gonna be used since the implementation + // does not check the parameter. Let's force a value for consistency. If consumer + // whants to undefine it, pass the undefined value explicitly. + assign(context: any, value: any): any; + } + + /** + * $location - $locationProvider - service in module ng + * see https://docs.angularjs.org/api/ng/service/$location + */ + interface ILocationService { + absUrl(): string; + hash(): string; + hash(newHash: string): ILocationService; + host(): string; + + /** + * Return path of current url + */ + path(): string; + + /** + * Change path when called with parameter and return $location. + * Note: Path should always begin with forward slash (/), this method will add the forward slash if it is missing. + * + * @param path New path + */ + path(path: string): ILocationService; + + port(): number; + protocol(): string; + replace(): ILocationService; + + /** + * Return search part (as object) of current url + */ + search(): any; + + /** + * Change search part when called with parameter and return $location. + * + * @param search When called with a single argument the method acts as a setter, setting the search component of $location to the specified value. + * + * If the argument is a hash object containing an array of values, these values will be encoded as duplicate search parameters in the url. + */ + search(search: any): ILocationService; + + /** + * Change search part when called with parameter and return $location. + * + * @param search New search params + * @param paramValue If search is a string or a Number, then paramValue will override only a single search property. If paramValue is null, the property specified via the first argument will be deleted. If paramValue is an array, it will override the property of the search component of $location specified via the first argument. If paramValue is true, the property specified via the first argument will be added with no value nor trailing equal sign. + */ + search(search: string, paramValue: string|number|string[]|boolean): ILocationService; + + state(): any; + state(state: any): ILocationService; + url(): string; + url(url: string): ILocationService; + } + + interface ILocationProvider extends IServiceProvider { + hashPrefix(): string; + hashPrefix(prefix: string): ILocationProvider; + html5Mode(): boolean; + + // Documentation states that parameter is string, but + // implementation tests it as boolean, which makes more sense + // since this is a toggler + html5Mode(active: boolean): ILocationProvider; + html5Mode(mode: { enabled?: boolean; requireBase?: boolean; rewriteLinks?: boolean; }): ILocationProvider; + } + + /////////////////////////////////////////////////////////////////////////// + // DocumentService + // see http://docs.angularjs.org/api/ng.$document + /////////////////////////////////////////////////////////////////////////// + interface IDocumentService extends IAugmentedJQuery {} + + /////////////////////////////////////////////////////////////////////////// + // ExceptionHandlerService + // see http://docs.angularjs.org/api/ng.$exceptionHandler + /////////////////////////////////////////////////////////////////////////// + interface IExceptionHandlerService { + (exception: Error, cause?: string): void; + } + + /////////////////////////////////////////////////////////////////////////// + // RootElementService + // see http://docs.angularjs.org/api/ng.$rootElement + /////////////////////////////////////////////////////////////////////////// + interface IRootElementService extends JQuery {} + + interface IQResolveReject { + (): void; + (value: T): void; + } + /** + * $q - service in module ng + * A promise/deferred implementation inspired by Kris Kowal's Q. + * See http://docs.angularjs.org/api/ng/service/$q + */ + interface IQService { + new (resolver: (resolve: IQResolveReject) => any): IPromise; + new (resolver: (resolve: IQResolveReject, reject: IQResolveReject) => any): IPromise; + (resolver: (resolve: IQResolveReject) => any): IPromise; + (resolver: (resolve: IQResolveReject, reject: IQResolveReject) => any): IPromise; + + /** + * Combines multiple promises into a single promise that is resolved when all of the input promises are resolved. + * + * Returns a single promise that will be resolved with an array of values, each value corresponding to the promise at the same index in the promises array. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value. + * + * @param promises An array of promises. + */ + all(promises: IPromise[]): IPromise; + /** + * Combines multiple promises into a single promise that is resolved when all of the input promises are resolved. + * + * Returns a single promise that will be resolved with a hash of values, each value corresponding to the promise at the same key in the promises hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value. + * + * @param promises A hash of promises. + */ + all(promises: { [id: string]: IPromise; }): IPromise<{ [id: string]: any; }>; + all(promises: { [id: string]: IPromise; }): IPromise; + /** + * Creates a Deferred object which represents a task which will finish in the future. + */ + defer(): IDeferred; + /** + * Creates a promise that is resolved as rejected with the specified reason. This api should be used to forward rejection in a chain of promises. If you are dealing with the last promise in a promise chain, you don't need to worry about it. + * + * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of reject as the throw keyword in JavaScript. This also means that if you "catch" an error via a promise error callback and you want to forward the error to the promise derived from the current promise, you have to "rethrow" the error by returning a rejection constructed via reject. + * + * @param reason Constant, message, exception or an object representing the rejection reason. + */ + reject(reason?: any): IPromise; + /** + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted. + * + * @param value Value or a promise + */ + when(value: IPromise|T): IPromise; + /** + * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted. + * + * @param value Value or a promise + */ + when(): IPromise; + } + + interface IPromise { + /** + * Regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected. + * The successCallBack may return IPromise for when a $q.reject() needs to be returned + * This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method. + */ + then(successCallback: (promiseValue: T) => IHttpPromise|IPromise|TResult|IPromise, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise; + + /** + * Shorthand for promise.then(null, errorCallback) + */ + catch(onRejected: (reason: any) => IHttpPromise|IPromise|TResult): IPromise; + + /** + * Allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful to release resources or do some clean-up that needs to be done whether the promise was rejected or resolved. See the full specification for more information. + * + * Because finally is a reserved word in JavaScript and reserved keywords are not supported as property names by ES3, you'll need to invoke the method like promise['finally'](callback) to make your code IE8 and Android 2.x compatible. + */ + finally(finallyCallback: () => any): IPromise; + } + + interface IDeferred { + resolve(value?: T): void; + reject(reason?: any): void; + notify(state?: any): void; + promise: IPromise; + } + + /////////////////////////////////////////////////////////////////////////// + // AnchorScrollService + // see http://docs.angularjs.org/api/ng.$anchorScroll + /////////////////////////////////////////////////////////////////////////// + interface IAnchorScrollService { + (): void; + (hash: string): void; + yOffset: any; + } + + interface IAnchorScrollProvider extends IServiceProvider { + disableAutoScrolling(): void; + } + + /** + * $cacheFactory - service in module ng + * + * Factory that constructs Cache objects and gives access to them. + * + * see https://docs.angularjs.org/api/ng/service/$cacheFactory + */ + interface ICacheFactoryService { + /** + * Factory that constructs Cache objects and gives access to them. + * + * @param cacheId Name or id of the newly created cache. + * @param optionsMap Options object that specifies the cache behavior. Properties: + * + * capacity — turns the cache into LRU cache. + */ + (cacheId: string, optionsMap?: { capacity?: number; }): ICacheObject; + + /** + * Get information about all the caches that have been created. + * @returns key-value map of cacheId to the result of calling cache#info + */ + info(): any; + + /** + * Get access to a cache object by the cacheId used when it was created. + * + * @param cacheId Name or id of a cache to access. + */ + get(cacheId: string): ICacheObject; + } + + /** + * $cacheFactory.Cache - type in module ng + * + * A cache object used to store and retrieve data, primarily used by $http and the script directive to cache templates and other data. + * + * see https://docs.angularjs.org/api/ng/type/$cacheFactory.Cache + */ + interface ICacheObject { + /** + * Retrieve information regarding a particular Cache. + */ + info(): { + /** + * the id of the cache instance + */ + id: string; + + /** + * the number of entries kept in the cache instance + */ + size: number; + + //...: any additional properties from the options object when creating the cache. + }; + + /** + * Inserts a named entry into the Cache object to be retrieved later, and incrementing the size of the cache if the key was not already present in the cache. If behaving like an LRU cache, it will also remove stale entries from the set. + * + * It will not insert undefined values into the cache. + * + * @param key the key under which the cached data is stored. + * @param value the value to store alongside the key. If it is undefined, the key will not be stored. + */ + put(key: string, value?: T): T; + + /** + * Retrieves named data stored in the Cache object. + * + * @param key the key of the data to be retrieved + */ + get(key: string): T; + + /** + * Removes an entry from the Cache object. + * + * @param key the key of the entry to be removed + */ + remove(key: string): void; + + /** + * Clears the cache object of any entries. + */ + removeAll(): void; + + /** + * Destroys the Cache object entirely, removing it from the $cacheFactory set. + */ + destroy(): void; + } + + /////////////////////////////////////////////////////////////////////////// + // CompileService + // see http://docs.angularjs.org/api/ng.$compile + // see http://docs.angularjs.org/api/ng.$compileProvider + /////////////////////////////////////////////////////////////////////////// + interface ICompileService { + (element: string, transclude?: ITranscludeFunction, maxPriority?: number): ITemplateLinkingFunction; + (element: Element, transclude?: ITranscludeFunction, maxPriority?: number): ITemplateLinkingFunction; + (element: JQuery, transclude?: ITranscludeFunction, maxPriority?: number): ITemplateLinkingFunction; + } + + interface ICompileProvider extends IServiceProvider { + directive(name: string, directiveFactory: Function): ICompileProvider; + + // Undocumented, but it is there... + directive(directivesMap: any): ICompileProvider; + + aHrefSanitizationWhitelist(): RegExp; + aHrefSanitizationWhitelist(regexp: RegExp): ICompileProvider; + + imgSrcSanitizationWhitelist(): RegExp; + imgSrcSanitizationWhitelist(regexp: RegExp): ICompileProvider; + + debugInfoEnabled(enabled?: boolean): any; + } + + interface ICloneAttachFunction { + // Let's hint but not force cloneAttachFn's signature + (clonedElement?: JQuery, scope?: IScope): any; + } + + // This corresponds to the "publicLinkFn" returned by $compile. + interface ITemplateLinkingFunction { + (scope: IScope, cloneAttachFn?: ICloneAttachFunction): IAugmentedJQuery; + } + + // This corresponds to $transclude (and also the transclude function passed to link). + interface ITranscludeFunction { + // If the scope is provided, then the cloneAttachFn must be as well. + (scope: IScope, cloneAttachFn: ICloneAttachFunction): IAugmentedJQuery; + // If one argument is provided, then it's assumed to be the cloneAttachFn. + (cloneAttachFn?: ICloneAttachFunction): IAugmentedJQuery; + } + + /////////////////////////////////////////////////////////////////////////// + // ControllerService + // see http://docs.angularjs.org/api/ng.$controller + // see http://docs.angularjs.org/api/ng.$controllerProvider + /////////////////////////////////////////////////////////////////////////// + interface IControllerService { + // Although the documentation doesn't state this, locals are optional + (controllerConstructor: Function, locals?: any, bindToController?: any): any; + (controllerName: string, locals?: any, bindToController?: any): any; + } + + interface IControllerProvider extends IServiceProvider { + register(name: string, controllerConstructor: Function): void; + register(name: string, dependencyAnnotatedConstructor: any[]): void; + allowGlobals(): void; + } + + /** + * HttpService + * see http://docs.angularjs.org/api/ng/service/$http + */ + interface IHttpService { + /** + * Object describing the request to be made and how it should be processed. + */ + (config: IRequestConfig): IHttpPromise; + + /** + * Shortcut method to perform GET request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param config Optional configuration object + */ + get(url: string, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Shortcut method to perform DELETE request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param config Optional configuration object + */ + delete(url: string, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Shortcut method to perform HEAD request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param config Optional configuration object + */ + head(url: string, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Shortcut method to perform JSONP request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param config Optional configuration object + */ + jsonp(url: string, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Shortcut method to perform POST request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param data Request content + * @param config Optional configuration object + */ + post(url: string, data: any, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Shortcut method to perform PUT request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param data Request content + * @param config Optional configuration object + */ + put(url: string, data: any, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Shortcut method to perform PATCH request. + * + * @param url Relative or absolute URL specifying the destination of the request + * @param data Request content + * @param config Optional configuration object + */ + patch(url: string, data: any, config?: IRequestShortcutConfig): IHttpPromise; + + /** + * Runtime equivalent of the $httpProvider.defaults property. Allows configuration of default headers, withCredentials as well as request and response transformations. + */ + defaults: IRequestConfig; + + /** + * Array of config objects for currently pending requests. This is primarily meant to be used for debugging purposes. + */ + pendingRequests: any[]; + } + + /** + * Object describing the request to be made and how it should be processed. + * see http://docs.angularjs.org/api/ng/service/$http#usage + */ + interface IRequestShortcutConfig { + /** + * {Object.} + * Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified. + */ + params?: any; + + /** + * Map of strings or functions which return strings representing HTTP headers to send to the server. If the return value of a function is null, the header will not be sent. + */ + headers?: any; + + /** + * Name of HTTP header to populate with the XSRF token. + */ + xsrfHeaderName?: string; + + /** + * Name of cookie containing the XSRF token. + */ + xsrfCookieName?: string; + + /** + * {boolean|Cache} + * If true, a default $http cache will be used to cache the GET request, otherwise if a cache instance built with $cacheFactory, this cache will be used for caching. + */ + cache?: any; + + /** + * whether to to set the withCredentials flag on the XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 for more information. + */ + withCredentials?: boolean; + + /** + * {string|Object} + * Data to be sent as the request message data. + */ + data?: any; + + /** + * {function(data, headersGetter)|Array.} + * Transform function or an array of such functions. The transform function takes the http request body and headers and returns its transformed (typically serialized) version. + */ + transformRequest?: any; + + /** + * {function(data, headersGetter)|Array.} + * Transform function or an array of such functions. The transform function takes the http response body and headers and returns its transformed (typically deserialized) version. + */ + transformResponse?: any; + + /** + * {number|Promise} + * Timeout in milliseconds, or promise that should abort the request when resolved. + */ + timeout?: any; + + /** + * See requestType. + */ + responseType?: string; + } + + /** + * Object describing the request to be made and how it should be processed. + * see http://docs.angularjs.org/api/ng/service/$http#usage + */ + interface IRequestConfig extends IRequestShortcutConfig { + /** + * HTTP method (e.g. 'GET', 'POST', etc) + */ + method: string; + /** + * Absolute or relative URL of the resource that is being requested. + */ + url: string; + } + + interface IHttpHeadersGetter { + (): { [name: string]: string; }; + (headerName: string): string; + } + + interface IHttpPromiseCallback { + (data: T, status: number, headers: IHttpHeadersGetter, config: IRequestConfig): void; + } + + interface IHttpPromiseCallbackArg { + data?: T; + status?: number; + headers?: IHttpHeadersGetter; + config?: IRequestConfig; + statusText?: string; + } + + interface IHttpPromise extends IPromise> { + success(callback: IHttpPromiseCallback): IHttpPromise; + error(callback: IHttpPromiseCallback): IHttpPromise; + then(successCallback: (response: IHttpPromiseCallbackArg) => IPromise|TResult, errorCallback?: (response: IHttpPromiseCallbackArg) => any): IPromise; + } + + /** + * Object that controls the defaults for $http provider + * https://docs.angularjs.org/api/ng/service/$http#defaults + */ + interface IHttpProviderDefaults { + cache?: boolean; + /** + * Transform function or an array of such functions. The transform function takes the http request body and + * headers and returns its transformed (typically serialized) version. + */ + transformRequest?: ((data: any, headersGetter?: any) => any)|((data: any, headersGetter?: any) => any)[]; + xsrfCookieName?: string; + xsrfHeaderName?: string; + withCredentials?: boolean; + headers?: { + common?: any; + post?: any; + put?: any; + patch?: any; + } + } + + interface IHttpProvider extends IServiceProvider { + defaults: IHttpProviderDefaults; + interceptors: any[]; + useApplyAsync(): boolean; + useApplyAsync(value: boolean): IHttpProvider; + + /** + * + * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods. + * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. + * otherwise, returns the current configured value. + */ + useLegacyPromiseExtensions(value:boolean) : boolean | IHttpProvider; + } + + /////////////////////////////////////////////////////////////////////////// + // HttpBackendService + // see http://docs.angularjs.org/api/ng.$httpBackend + // You should never need to use this service directly. + /////////////////////////////////////////////////////////////////////////// + interface IHttpBackendService { + // XXX Perhaps define callback signature in the future + (method: string, url: string, post?: any, callback?: Function, headers?: any, timeout?: number, withCredentials?: boolean): void; + } + + /////////////////////////////////////////////////////////////////////////// + // InterpolateService + // see http://docs.angularjs.org/api/ng.$interpolate + // see http://docs.angularjs.org/api/ng.$interpolateProvider + /////////////////////////////////////////////////////////////////////////// + interface IInterpolateService { + (text: string, mustHaveExpression?: boolean, trustedContext?: string, allOrNothing?: boolean): IInterpolationFunction; + endSymbol(): string; + startSymbol(): string; + } + + interface IInterpolationFunction { + (context: any): string; + } + + interface IInterpolateProvider extends IServiceProvider { + startSymbol(): string; + startSymbol(value: string): IInterpolateProvider; + endSymbol(): string; + endSymbol(value: string): IInterpolateProvider; + } + + /////////////////////////////////////////////////////////////////////////// + // TemplateCacheService + // see http://docs.angularjs.org/api/ng.$templateCache + /////////////////////////////////////////////////////////////////////////// + interface ITemplateCacheService extends ICacheObject {} + + /////////////////////////////////////////////////////////////////////////// + // SCEService + // see http://docs.angularjs.org/api/ng.$sce + /////////////////////////////////////////////////////////////////////////// + interface ISCEService { + getTrusted(type: string, mayBeTrusted: any): any; + getTrustedCss(value: any): any; + getTrustedHtml(value: any): any; + getTrustedJs(value: any): any; + getTrustedResourceUrl(value: any): any; + getTrustedUrl(value: any): any; + parse(type: string, expression: string): (context: any, locals: any) => any; + parseAsCss(expression: string): (context: any, locals: any) => any; + parseAsHtml(expression: string): (context: any, locals: any) => any; + parseAsJs(expression: string): (context: any, locals: any) => any; + parseAsResourceUrl(expression: string): (context: any, locals: any) => any; + parseAsUrl(expression: string): (context: any, locals: any) => any; + trustAs(type: string, value: any): any; + trustAsHtml(value: any): any; + trustAsJs(value: any): any; + trustAsResourceUrl(value: any): any; + trustAsUrl(value: any): any; + isEnabled(): boolean; + } + + /////////////////////////////////////////////////////////////////////////// + // SCEProvider + // see http://docs.angularjs.org/api/ng.$sceProvider + /////////////////////////////////////////////////////////////////////////// + interface ISCEProvider extends IServiceProvider { + enabled(value: boolean): void; + } + + /////////////////////////////////////////////////////////////////////////// + // SCEDelegateService + // see http://docs.angularjs.org/api/ng.$sceDelegate + /////////////////////////////////////////////////////////////////////////// + interface ISCEDelegateService { + getTrusted(type: string, mayBeTrusted: any): any; + trustAs(type: string, value: any): any; + valueOf(value: any): any; + } + + + /////////////////////////////////////////////////////////////////////////// + // SCEDelegateProvider + // see http://docs.angularjs.org/api/ng.$sceDelegateProvider + /////////////////////////////////////////////////////////////////////////// + interface ISCEDelegateProvider extends IServiceProvider { + resourceUrlBlacklist(blacklist: any[]): void; + resourceUrlWhitelist(whitelist: any[]): void; + resourceUrlBlacklist(): any[]; + resourceUrlWhitelist(): any[]; + } + + /** + * $templateRequest service + * see http://docs.angularjs.org/api/ng/service/$templateRequest + */ + interface ITemplateRequestService { + /** + * Downloads a template using $http and, upon success, stores the + * contents inside of $templateCache. + * + * If the HTTP request fails or the response data of the HTTP request is + * empty then a $compile error will be thrown (unless + * {ignoreRequestError} is set to true). + * + * @param tpl The template URL. + * @param ignoreRequestError Whether or not to ignore the exception + * when the request fails or the template is + * empty. + * + * @return A promise whose value is the template content. + */ + (tpl: string, ignoreRequestError?: boolean): IPromise; + /** + * total amount of pending template requests being downloaded. + * @type {number} + */ + totalPendingRequests: number; + } + + /////////////////////////////////////////////////////////////////////////// + // Directive + // see http://docs.angularjs.org/api/ng.$compileProvider#directive + // and http://docs.angularjs.org/guide/directive + /////////////////////////////////////////////////////////////////////////// + + interface IDirectiveFactory { + (...args: any[]): IDirective; + } + + interface IDirectiveLinkFn { + ( + scope: IScope, + instanceElement: IAugmentedJQuery, + instanceAttributes: IAttributes, + controller: {}, + transclude: ITranscludeFunction + ): void; + } + + interface IDirectivePrePost { + pre?: IDirectiveLinkFn; + post?: IDirectiveLinkFn; + } + + interface IDirectiveCompileFn { + ( + templateElement: IAugmentedJQuery, + templateAttributes: IAttributes, + transclude: ITranscludeFunction + ): IDirectivePrePost; + } + + interface IDirective { + compile?: IDirectiveCompileFn; + controller?: any; + controllerAs?: string; + bindToController?: boolean|Object; + link?: IDirectiveLinkFn | IDirectivePrePost; + name?: string; + priority?: number; + replace?: boolean; + require?: any; + restrict?: string; + scope?: any; + template?: any; + templateUrl?: any; + terminal?: boolean; + transclude?: any; + } + + /** + * angular.element + * when calling angular.element, angular returns a jQuery object, + * augmented with additional methods like e.g. scope. + * see: http://docs.angularjs.org/api/angular.element + */ + interface IAugmentedJQueryStatic extends JQueryStatic { + (selector: string, context?: any): IAugmentedJQuery; + (element: Element): IAugmentedJQuery; + (object: {}): IAugmentedJQuery; + (elementArray: Element[]): IAugmentedJQuery; + (object: JQuery): IAugmentedJQuery; + (func: Function): IAugmentedJQuery; + (array: any[]): IAugmentedJQuery; + (): IAugmentedJQuery; + } + + interface IAugmentedJQuery extends JQuery { + // TODO: events, how to define? + //$destroy + + find(selector: string): IAugmentedJQuery; + find(element: any): IAugmentedJQuery; + find(obj: JQuery): IAugmentedJQuery; + controller(): any; + controller(name: string): any; + injector(): any; + scope(): IScope; + isolateScope(): IScope; + + inheritedData(key: string, value: any): JQuery; + inheritedData(obj: { [key: string]: any; }): JQuery; + inheritedData(key?: string): any; + } + + /////////////////////////////////////////////////////////////////////// + // AnimateService + // see http://docs.angularjs.org/api/ng.$animate + /////////////////////////////////////////////////////////////////////// + interface IAnimateService { + addClass(element: JQuery, className: string, done?: Function): IPromise; + enter(element: JQuery, parent: JQuery, after: JQuery, done?: Function): void; + leave(element: JQuery, done?: Function): void; + move(element: JQuery, parent: JQuery, after: JQuery, done?: Function): void; + removeClass(element: JQuery, className: string, done?: Function): void; + } + + /////////////////////////////////////////////////////////////////////////// + // AUTO module (angular.js) + /////////////////////////////////////////////////////////////////////////// + export module auto { + + /////////////////////////////////////////////////////////////////////// + // InjectorService + // see http://docs.angularjs.org/api/AUTO.$injector + /////////////////////////////////////////////////////////////////////// + interface IInjectorService { + annotate(fn: Function): string[]; + annotate(inlineAnnotatedFunction: any[]): string[]; + get(name: string): T; + has(name: string): boolean; + instantiate(typeConstructor: Function, locals?: any): T; + invoke(inlineAnnotatedFunction: any[]): any; + invoke(func: Function, context?: any, locals?: any): any; + } + + /////////////////////////////////////////////////////////////////////// + // ProvideService + // see http://docs.angularjs.org/api/AUTO.$provide + /////////////////////////////////////////////////////////////////////// + interface IProvideService { + // Documentation says it returns the registered instance, but actual + // implementation does not return anything. + // constant(name: string, value: any): any; + /** + * Register a constant service, such as a string, a number, an array, an object or a function, with the $injector. Unlike value it can be injected into a module configuration function (see config) and it cannot be overridden by an Angular decorator. + * + * @param name The name of the constant. + * @param value The constant value. + */ + constant(name: string, value: any): void; + + /** + * Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service. + * + * @param name The name of the service to decorate. + * @param decorator This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments: + * + * $delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to. + */ + decorator(name: string, decorator: Function): void; + /** + * Register a service decorator with the $injector. A service decorator intercepts the creation of a service, allowing it to override or modify the behaviour of the service. The object returned by the decorator may be the original service, or a new service object which replaces or wraps and delegates to the original service. + * + * @param name The name of the service to decorate. + * @param inlineAnnotatedFunction This function will be invoked when the service needs to be instantiated and should return the decorated service instance. The function is called using the injector.invoke method and is therefore fully injectable. Local injection arguments: + * + * $delegate - The original service instance, which can be monkey patched, configured, decorated or delegated to. + */ + decorator(name: string, inlineAnnotatedFunction: any[]): void; + factory(name: string, serviceFactoryFunction: Function): IServiceProvider; + factory(name: string, inlineAnnotatedFunction: any[]): IServiceProvider; + provider(name: string, provider: IServiceProvider): IServiceProvider; + provider(name: string, serviceProviderConstructor: Function): IServiceProvider; + service(name: string, constructor: Function): IServiceProvider; + value(name: string, value: any): IServiceProvider; + } + + } +} diff --git a/public/app/headers/common.d.ts b/public/app/headers/common.d.ts new file mode 100644 index 00000000000..e9f5b80c50c --- /dev/null +++ b/public/app/headers/common.d.ts @@ -0,0 +1,17 @@ +/// +/// +/// +/// + +// dummy modules +declare module 'config' { + var config : any; + export = config; +} + +declare module 'kbn' { + var kbn : any; + export = kbn; +} + + diff --git a/public/app/headers/jquery/jquery.d.ts b/public/app/headers/jquery/jquery.d.ts new file mode 100644 index 00000000000..70737253efd --- /dev/null +++ b/public/app/headers/jquery/jquery.d.ts @@ -0,0 +1,3178 @@ +// Type definitions for jQuery 1.10.x / 2.0.x +// Project: http://jquery.com/ +// Definitions by: Boris Yankov , Christian Hoffmeister , Steve Fenton , Diullei Gomes , Tass Iliopoulos , Jason Swearingen , Sean Hill , Guus Goossens , Kelly Summerlin , Basarat Ali Syed , Nicholas Wolverson , Derek Cicerone , Andrew Gaspar , James Harrison Fisher , Seikichi Kondo , Benjamin Jackman , Poul Sorensen , Josh Strobl , John Reilly , Dick van den Brink +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/* ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +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 + +THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + + +/** + * Interface for the AJAX setting that will configure the AJAX request + */ +interface JQueryAjaxSettings { + /** + * The content type sent in the request header that tells the server what kind of response it will accept in return. If the accepts setting needs modification, it is recommended to do so once in the $.ajaxSetup() method. + */ + accepts?: any; + /** + * By default, all requests are sent asynchronously (i.e. this is set to true by default). If you need synchronous requests, set this option to false. Cross-domain requests and dataType: "jsonp" requests do not support synchronous operation. Note that synchronous requests may temporarily lock the browser, disabling any actions while the request is active. As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success(). + */ + async?: boolean; + /** + * A pre-request callback function that can be used to modify the jqXHR (in jQuery 1.4.x, XMLHTTPRequest) object before it is sent. Use this to set custom headers, etc. The jqXHR and settings objects are passed as arguments. This is an Ajax Event. Returning false in the beforeSend function will cancel the request. As of jQuery 1.5, the beforeSend option will be called regardless of the type of request. + */ + beforeSend? (jqXHR: JQueryXHR, settings: JQueryAjaxSettings): any; + /** + * If set to false, it will force requested pages not to be cached by the browser. Note: Setting cache to false will only work correctly with HEAD and GET requests. It works by appending "_={timestamp}" to the GET parameters. The parameter is not needed for other types of requests, except in IE8 when a POST is made to a URL that has already been requested by a GET. + */ + cache?: boolean; + /** + * A function to be called when the request finishes (after success and error callbacks are executed). The function gets passed two arguments: The jqXHR (in jQuery 1.4.x, XMLHTTPRequest) object and a string categorizing the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror"). As of jQuery 1.5, the complete setting can accept an array of functions. Each function will be called in turn. This is an Ajax Event. + */ + complete? (jqXHR: JQueryXHR, textStatus: string): any; + /** + * An object of string/regular-expression pairs that determine how jQuery will parse the response, given its content type. (version added: 1.5) + */ + contents?: { [key: string]: any; }; + //According to jQuery.ajax source code, ajax's option actually allows contentType to set to "false" + // https://github.com/borisyankov/DefinitelyTyped/issues/742 + /** + * When sending data to the server, use this content type. Default is "application/x-www-form-urlencoded; charset=UTF-8", which is fine for most cases. If you explicitly pass in a content-type to $.ajax(), then it is always sent to the server (even if no data is sent). The W3C XMLHttpRequest specification dictates that the charset is always UTF-8; specifying another charset will not force the browser to change the encoding. + */ + contentType?: any; + /** + * This object will be made the context of all Ajax-related callbacks. By default, the context is an object that represents the ajax settings used in the call ($.ajaxSettings merged with the settings passed to $.ajax). + */ + context?: any; + /** + * An object containing dataType-to-dataType converters. Each converter's value is a function that returns the transformed value of the response. (version added: 1.5) + */ + converters?: { [key: string]: any; }; + /** + * If you wish to force a crossDomain request (such as JSONP) on the same domain, set the value of crossDomain to true. This allows, for example, server-side redirection to another domain. (version added: 1.5) + */ + crossDomain?: boolean; + /** + * Data to be sent to the server. It is converted to a query string, if not already a string. It's appended to the url for GET-requests. See processData option to prevent this automatic processing. Object must be Key/Value pairs. If value is an Array, jQuery serializes multiple values with same key based on the value of the traditional setting (described below). + */ + data?: any; + /** + * A function to be used to handle the raw response data of XMLHttpRequest.This is a pre-filtering function to sanitize the response. You should return the sanitized data. The function accepts two arguments: The raw data returned from the server and the 'dataType' parameter. + */ + dataFilter? (data: any, ty: any): any; + /** + * The type of data that you're expecting back from the server. If none is specified, jQuery will try to infer it based on the MIME type of the response (an XML MIME type will yield XML, in 1.4 JSON will yield a JavaScript object, in 1.4 script will execute the script, and anything else will be returned as a string). + */ + dataType?: string; + /** + * A function to be called if the request fails. The function receives three arguments: The jqXHR (in jQuery 1.4.x, XMLHttpRequest) object, a string describing the type of error that occurred and an optional exception object, if one occurred. Possible values for the second argument (besides null) are "timeout", "error", "abort", and "parsererror". When an HTTP error occurs, errorThrown receives the textual portion of the HTTP status, such as "Not Found" or "Internal Server Error." As of jQuery 1.5, the error setting can accept an array of functions. Each function will be called in turn. Note: This handler is not called for cross-domain script and cross-domain JSONP requests. This is an Ajax Event. + */ + error? (jqXHR: JQueryXHR, textStatus: string, errorThrown: string): any; + /** + * Whether to trigger global Ajax event handlers for this request. The default is true. Set to false to prevent the global handlers like ajaxStart or ajaxStop from being triggered. This can be used to control various Ajax Events. + */ + global?: boolean; + /** + * An object of additional header key/value pairs to send along with requests using the XMLHttpRequest transport. The header X-Requested-With: XMLHttpRequest is always added, but its default XMLHttpRequest value can be changed here. Values in the headers setting can also be overwritten from within the beforeSend function. (version added: 1.5) + */ + headers?: { [key: string]: any; }; + /** + * Allow the request to be successful only if the response has changed since the last request. This is done by checking the Last-Modified header. Default value is false, ignoring the header. In jQuery 1.4 this technique also checks the 'etag' specified by the server to catch unmodified data. + */ + ifModified?: boolean; + /** + * Allow the current environment to be recognized as "local," (e.g. the filesystem), even if jQuery does not recognize it as such by default. The following protocols are currently recognized as local: file, *-extension, and widget. If the isLocal setting needs modification, it is recommended to do so once in the $.ajaxSetup() method. (version added: 1.5.1) + */ + isLocal?: boolean; + /** + * Override the callback function name in a jsonp request. This value will be used instead of 'callback' in the 'callback=?' part of the query string in the url. So {jsonp:'onJSONPLoad'} would result in 'onJSONPLoad=?' passed to the server. As of jQuery 1.5, setting the jsonp option to false prevents jQuery from adding the "?callback" string to the URL or attempting to use "=?" for transformation. In this case, you should also explicitly set the jsonpCallback setting. For example, { jsonp: false, jsonpCallback: "callbackName" } + */ + jsonp?: any; + /** + * Specify the callback function name for a JSONP request. This value will be used instead of the random name automatically generated by jQuery. It is preferable to let jQuery generate a unique name as it'll make it easier to manage the requests and provide callbacks and error handling. You may want to specify the callback when you want to enable better browser caching of GET requests. As of jQuery 1.5, you can also use a function for this setting, in which case the value of jsonpCallback is set to the return value of that function. + */ + jsonpCallback?: any; + /** + * The HTTP method to use for the request (e.g. "POST", "GET", "PUT"). (version added: 1.9.0) + */ + method?: string; + /** + * A mime type to override the XHR mime type. (version added: 1.5.1) + */ + mimeType?: string; + /** + * A password to be used with XMLHttpRequest in response to an HTTP access authentication request. + */ + password?: string; + /** + * By default, data passed in to the data option as an object (technically, anything other than a string) will be processed and transformed into a query string, fitting to the default content-type "application/x-www-form-urlencoded". If you want to send a DOMDocument, or other non-processed data, set this option to false. + */ + processData?: boolean; + /** + * Only applies when the "script" transport is used (e.g., cross-domain requests with "jsonp" or "script" dataType and "GET" type). Sets the charset attribute on the script tag used in the request. Used when the character set on the local page is not the same as the one on the remote script. + */ + scriptCharset?: string; + /** + * An object of numeric HTTP codes and functions to be called when the response has the corresponding code. f the request is successful, the status code functions take the same parameters as the success callback; if it results in an error (including 3xx redirect), they take the same parameters as the error callback. (version added: 1.5) + */ + statusCode?: { [key: string]: any; }; + /** + * A function to be called if the request succeeds. The function gets passed three arguments: The data returned from the server, formatted according to the dataType parameter; a string describing the status; and the jqXHR (in jQuery 1.4.x, XMLHttpRequest) object. As of jQuery 1.5, the success setting can accept an array of functions. Each function will be called in turn. This is an Ajax Event. + */ + success? (data: any, textStatus: string, jqXHR: JQueryXHR): any; + /** + * Set a timeout (in milliseconds) for the request. This will override any global timeout set with $.ajaxSetup(). The timeout period starts at the point the $.ajax call is made; if several other requests are in progress and the browser has no connections available, it is possible for a request to time out before it can be sent. In jQuery 1.4.x and below, the XMLHttpRequest object will be in an invalid state if the request times out; accessing any object members may throw an exception. In Firefox 3.0+ only, script and JSONP requests cannot be cancelled by a timeout; the script will run even if it arrives after the timeout period. + */ + timeout?: number; + /** + * Set this to true if you wish to use the traditional style of param serialization. + */ + traditional?: boolean; + /** + * The type of request to make ("POST" or "GET"), default is "GET". Note: Other HTTP request methods, such as PUT and DELETE, can also be used here, but they are not supported by all browsers. + */ + type?: string; + /** + * A string containing the URL to which the request is sent. + */ + url?: string; + /** + * A username to be used with XMLHttpRequest in response to an HTTP access authentication request. + */ + username?: string; + /** + * Callback for creating the XMLHttpRequest object. Defaults to the ActiveXObject when available (IE), the XMLHttpRequest otherwise. Override to provide your own implementation for XMLHttpRequest or enhancements to the factory. + */ + xhr?: any; + /** + * An object of fieldName-fieldValue pairs to set on the native XHR object. For example, you can use it to set withCredentials to true for cross-domain requests if needed. In jQuery 1.5, the withCredentials property was not propagated to the native XHR and thus CORS requests requiring it would ignore this flag. For this reason, we recommend using jQuery 1.5.1+ should you require the use of it. (version added: 1.5.1) + */ + xhrFields?: { [key: string]: any; }; +} + +/** + * Interface for the jqXHR object + */ +interface JQueryXHR extends XMLHttpRequest, JQueryPromise { + /** + * The .overrideMimeType() method may be used in the beforeSend() callback function, for example, to modify the response content-type header. As of jQuery 1.5.1, the jqXHR object also contains the overrideMimeType() method (it was available in jQuery 1.4.x, as well, but was temporarily removed in jQuery 1.5). + */ + overrideMimeType(mimeType: string): any; + /** + * Cancel the request. + * + * @param statusText A string passed as the textStatus parameter for the done callback. Default value: "canceled" + */ + abort(statusText?: string): void; + /** + * Incorporates the functionality of the .done() and .fail() methods, allowing (as of jQuery 1.8) the underlying Promise to be manipulated. Refer to deferred.then() for implementation details. + */ + then(doneCallback: (data: any, textStatus: string, jqXHR: JQueryXHR) => void, failCallback?: (jqXHR: JQueryXHR, textStatus: string, errorThrown: any) => void): JQueryPromise; + /** + * Property containing the parsed response if the response Content-Type is json + */ + responseJSON?: any; + /** + * A function to be called if the request fails. + */ + error(xhr: JQueryXHR, textStatus: string, errorThrown: string): void; +} + +/** + * Interface for the JQuery callback + */ +interface JQueryCallback { + /** + * Add a callback or a collection of callbacks to a callback list. + * + * @param callbacks A function, or array of functions, that are to be added to the callback list. + */ + add(callbacks: Function): JQueryCallback; + /** + * Add a callback or a collection of callbacks to a callback list. + * + * @param callbacks A function, or array of functions, that are to be added to the callback list. + */ + add(callbacks: Function[]): JQueryCallback; + + /** + * Disable a callback list from doing anything more. + */ + disable(): JQueryCallback; + + /** + * Determine if the callbacks list has been disabled. + */ + disabled(): boolean; + + /** + * Remove all of the callbacks from a list. + */ + empty(): JQueryCallback; + + /** + * Call all of the callbacks with the given arguments + * + * @param arguments The argument or list of arguments to pass back to the callback list. + */ + fire(...arguments: any[]): JQueryCallback; + + /** + * Determine if the callbacks have already been called at least once. + */ + fired(): boolean; + + /** + * Call all callbacks in a list with the given context and arguments. + * + * @param context A reference to the context in which the callbacks in the list should be fired. + * @param arguments An argument, or array of arguments, to pass to the callbacks in the list. + */ + fireWith(context?: any, ...args: any[]): JQueryCallback; + + /** + * Determine whether a supplied callback is in a list + * + * @param callback The callback to search for. + */ + has(callback: Function): boolean; + + /** + * Lock a callback list in its current state. + */ + lock(): JQueryCallback; + + /** + * Determine if the callbacks list has been locked. + */ + locked(): boolean; + + /** + * Remove a callback or a collection of callbacks from a callback list. + * + * @param callbacks A function, or array of functions, that are to be removed from the callback list. + */ + remove(callbacks: Function): JQueryCallback; + /** + * Remove a callback or a collection of callbacks from a callback list. + * + * @param callbacks A function, or array of functions, that are to be removed from the callback list. + */ + remove(callbacks: Function[]): JQueryCallback; +} + +/** + * Allows jQuery Promises to interop with non-jQuery promises + */ +interface JQueryGenericPromise { + /** + * Add handlers to be called when the Deferred object is resolved, rejected, or still in progress. + * + * @param doneFilter A function that is called when the Deferred is resolved. + * @param failFilter An optional function that is called when the Deferred is rejected. + */ + then(doneFilter: (value?: T, ...values: any[]) => U|JQueryPromise, failFilter?: (...reasons: any[]) => any, progressFilter?: (...progression: any[]) => any): JQueryPromise; + + /** + * Add handlers to be called when the Deferred object is resolved, rejected, or still in progress. + * + * @param doneFilter A function that is called when the Deferred is resolved. + * @param failFilter An optional function that is called when the Deferred is rejected. + */ + then(doneFilter: (value?: T, ...values: any[]) => void, failFilter?: (...reasons: any[]) => any, progressFilter?: (...progression: any[]) => any): JQueryPromise; +} + +/** + * Interface for the JQuery promise/deferred callbacks + */ +interface JQueryPromiseCallback { + (value?: T, ...args: any[]): void; +} + +interface JQueryPromiseOperator { + (callback1: JQueryPromiseCallback|JQueryPromiseCallback[], ...callbacksN: Array|JQueryPromiseCallback[]>): JQueryPromise; +} + +/** + * Interface for the JQuery promise, part of callbacks + */ +interface JQueryPromise extends JQueryGenericPromise { + /** + * Determine the current state of a Deferred object. + */ + state(): string; + /** + * Add handlers to be called when the Deferred object is either resolved or rejected. + * + * @param alwaysCallbacks1 A function, or array of functions, that is called when the Deferred is resolved or rejected. + * @param alwaysCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved or rejected. + */ + always(alwaysCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...alwaysCallbacksN: Array|JQueryPromiseCallback[]>): JQueryPromise; + /** + * Add handlers to be called when the Deferred object is resolved. + * + * @param doneCallbacks1 A function, or array of functions, that are called when the Deferred is resolved. + * @param doneCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved. + */ + done(doneCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...doneCallbackN: Array|JQueryPromiseCallback[]>): JQueryPromise; + /** + * Add handlers to be called when the Deferred object is rejected. + * + * @param failCallbacks1 A function, or array of functions, that are called when the Deferred is rejected. + * @param failCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is rejected. + */ + fail(failCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...failCallbacksN: Array|JQueryPromiseCallback[]>): JQueryPromise; + /** + * Add handlers to be called when the Deferred object generates progress notifications. + * + * @param progressCallbacks A function, or array of functions, to be called when the Deferred generates progress notifications. + */ + progress(progressCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...progressCallbackN: Array|JQueryPromiseCallback[]>): JQueryPromise; + + // Deprecated - given no typings + pipe(doneFilter?: (x: any) => any, failFilter?: (x: any) => any, progressFilter?: (x: any) => any): JQueryPromise; +} + +/** + * Interface for the JQuery deferred, part of callbacks + */ +interface JQueryDeferred extends JQueryGenericPromise { + /** + * Determine the current state of a Deferred object. + */ + state(): string; + /** + * Add handlers to be called when the Deferred object is either resolved or rejected. + * + * @param alwaysCallbacks1 A function, or array of functions, that is called when the Deferred is resolved or rejected. + * @param alwaysCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved or rejected. + */ + always(alwaysCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...alwaysCallbacksN: Array|JQueryPromiseCallback[]>): JQueryDeferred; + /** + * Add handlers to be called when the Deferred object is resolved. + * + * @param doneCallbacks1 A function, or array of functions, that are called when the Deferred is resolved. + * @param doneCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is resolved. + */ + done(doneCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...doneCallbackN: Array|JQueryPromiseCallback[]>): JQueryDeferred; + /** + * Add handlers to be called when the Deferred object is rejected. + * + * @param failCallbacks1 A function, or array of functions, that are called when the Deferred is rejected. + * @param failCallbacks2 Optional additional functions, or arrays of functions, that are called when the Deferred is rejected. + */ + fail(failCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...failCallbacksN: Array|JQueryPromiseCallback[]>): JQueryDeferred; + /** + * Add handlers to be called when the Deferred object generates progress notifications. + * + * @param progressCallbacks A function, or array of functions, to be called when the Deferred generates progress notifications. + */ + progress(progressCallback1?: JQueryPromiseCallback|JQueryPromiseCallback[], ...progressCallbackN: Array|JQueryPromiseCallback[]>): JQueryDeferred; + + /** + * Call the progressCallbacks on a Deferred object with the given args. + * + * @param args Optional arguments that are passed to the progressCallbacks. + */ + notify(value?: any, ...args: any[]): JQueryDeferred; + + /** + * Call the progressCallbacks on a Deferred object with the given context and args. + * + * @param context Context passed to the progressCallbacks as the this object. + * @param args Optional arguments that are passed to the progressCallbacks. + */ + notifyWith(context: any, value?: any, ...args: any[]): JQueryDeferred; + + /** + * Reject a Deferred object and call any failCallbacks with the given args. + * + * @param args Optional arguments that are passed to the failCallbacks. + */ + reject(value?: any, ...args: any[]): JQueryDeferred; + /** + * Reject a Deferred object and call any failCallbacks with the given context and args. + * + * @param context Context passed to the failCallbacks as the this object. + * @param args An optional array of arguments that are passed to the failCallbacks. + */ + rejectWith(context: any, value?: any, ...args: any[]): JQueryDeferred; + + /** + * Resolve a Deferred object and call any doneCallbacks with the given args. + * + * @param value First argument passed to doneCallbacks. + * @param args Optional subsequent arguments that are passed to the doneCallbacks. + */ + resolve(value?: T, ...args: any[]): JQueryDeferred; + + /** + * Resolve a Deferred object and call any doneCallbacks with the given context and args. + * + * @param context Context passed to the doneCallbacks as the this object. + * @param args An optional array of arguments that are passed to the doneCallbacks. + */ + resolveWith(context: any, value?: T, ...args: any[]): JQueryDeferred; + + /** + * Return a Deferred's Promise object. + * + * @param target Object onto which the promise methods have to be attached + */ + promise(target?: any): JQueryPromise; + + // Deprecated - given no typings + pipe(doneFilter?: (x: any) => any, failFilter?: (x: any) => any, progressFilter?: (x: any) => any): JQueryPromise; +} + +/** + * Interface of the JQuery extension of the W3C event object + */ +interface BaseJQueryEventObject extends Event { + data: any; + delegateTarget: Element; + isDefaultPrevented(): boolean; + isImmediatePropagationStopped(): boolean; + isPropagationStopped(): boolean; + namespace: string; + originalEvent: Event; + preventDefault(): any; + relatedTarget: Element; + result: any; + stopImmediatePropagation(): void; + stopPropagation(): void; + target: Element; + pageX: number; + pageY: number; + which: number; + metaKey: boolean; +} + +interface JQueryInputEventObject extends BaseJQueryEventObject { + altKey: boolean; + ctrlKey: boolean; + metaKey: boolean; + shiftKey: boolean; +} + +interface JQueryMouseEventObject extends JQueryInputEventObject { + button: number; + clientX: number; + clientY: number; + offsetX: number; + offsetY: number; + pageX: number; + pageY: number; + screenX: number; + screenY: number; +} + +interface JQueryKeyEventObject extends JQueryInputEventObject { + char: any; + charCode: number; + key: any; + keyCode: number; +} + +interface JQueryEventObject extends BaseJQueryEventObject, JQueryInputEventObject, JQueryMouseEventObject, JQueryKeyEventObject{ +} + +/* + Collection of properties of the current browser +*/ + +interface JQuerySupport { + ajax?: boolean; + boxModel?: boolean; + changeBubbles?: boolean; + checkClone?: boolean; + checkOn?: boolean; + cors?: boolean; + cssFloat?: boolean; + hrefNormalized?: boolean; + htmlSerialize?: boolean; + leadingWhitespace?: boolean; + noCloneChecked?: boolean; + noCloneEvent?: boolean; + opacity?: boolean; + optDisabled?: boolean; + optSelected?: boolean; + scriptEval? (): boolean; + style?: boolean; + submitBubbles?: boolean; + tbody?: boolean; +} + +interface JQueryParam { + /** + * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request. + * + * @param obj An array or object to serialize. + */ + (obj: any): string; + + /** + * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request. + * + * @param obj An array or object to serialize. + * @param traditional A Boolean indicating whether to perform a traditional "shallow" serialization. + */ + (obj: any, traditional: boolean): string; +} + +/** + * The interface used to construct jQuery events (with $.Event). It is + * defined separately instead of inline in JQueryStatic to allow + * overriding the construction function with specific strings + * returning specific event objects. + */ +interface JQueryEventConstructor { + (name: string, eventProperties?: any): JQueryEventObject; + new (name: string, eventProperties?: any): JQueryEventObject; +} + +/** + * The interface used to specify coordinates. + */ +interface JQueryCoordinates { + left: number; + top: number; +} + +/** + * Elements in the array returned by serializeArray() + */ +interface JQuerySerializeArrayElement { + name: string; + value: string; +} + +interface JQueryAnimationOptions { + /** + * A string or number determining how long the animation will run. + */ + duration?: any; + /** + * A string indicating which easing function to use for the transition. + */ + easing?: string; + /** + * A function to call once the animation is complete. + */ + complete?: Function; + /** + * A function to be called for each animated property of each animated element. This function provides an opportunity to modify the Tween object to change the value of the property before it is set. + */ + step?: (now: number, tween: any) => any; + /** + * A function to be called after each step of the animation, only once per animated element regardless of the number of animated properties. (version added: 1.8) + */ + progress?: (animation: JQueryPromise, progress: number, remainingMs: number) => any; + /** + * A function to call when the animation begins. (version added: 1.8) + */ + start?: (animation: JQueryPromise) => any; + /** + * A function to be called when the animation completes (its Promise object is resolved). (version added: 1.8) + */ + done?: (animation: JQueryPromise, jumpedToEnd: boolean) => any; + /** + * A function to be called when the animation fails to complete (its Promise object is rejected). (version added: 1.8) + */ + fail?: (animation: JQueryPromise, jumpedToEnd: boolean) => any; + /** + * A function to be called when the animation completes or stops without completing (its Promise object is either resolved or rejected). (version added: 1.8) + */ + always?: (animation: JQueryPromise, jumpedToEnd: boolean) => any; + /** + * A Boolean indicating whether to place the animation in the effects queue. If false, the animation will begin immediately. As of jQuery 1.7, the queue option can also accept a string, in which case the animation is added to the queue represented by that string. When a custom queue name is used the animation does not automatically start; you must call .dequeue("queuename") to start it. + */ + queue?: any; + /** + * A map of one or more of the CSS properties defined by the properties argument and their corresponding easing functions. (version added: 1.4) + */ + specialEasing?: Object; +} + +/** + * Static members of jQuery (those on $ and jQuery themselves) + */ +interface JQueryStatic { + + /** + * Perform an asynchronous HTTP (Ajax) request. + * + * @param settings A set of key/value pairs that configure the Ajax request. All settings are optional. A default can be set for any option with $.ajaxSetup(). + */ + ajax(settings: JQueryAjaxSettings): JQueryXHR; + /** + * Perform an asynchronous HTTP (Ajax) request. + * + * @param url A string containing the URL to which the request is sent. + * @param settings A set of key/value pairs that configure the Ajax request. All settings are optional. A default can be set for any option with $.ajaxSetup(). + */ + ajax(url: string, settings?: JQueryAjaxSettings): JQueryXHR; + + /** + * Handle custom Ajax options or modify existing options before each request is sent and before they are processed by $.ajax(). + * + * @param dataTypes An optional string containing one or more space-separated dataTypes + * @param handler A handler to set default values for future Ajax requests. + */ + ajaxPrefilter(dataTypes: string, handler: (opts: any, originalOpts: JQueryAjaxSettings, jqXHR: JQueryXHR) => any): void; + /** + * Handle custom Ajax options or modify existing options before each request is sent and before they are processed by $.ajax(). + * + * @param handler A handler to set default values for future Ajax requests. + */ + ajaxPrefilter(handler: (opts: any, originalOpts: JQueryAjaxSettings, jqXHR: JQueryXHR) => any): void; + + ajaxSettings: JQueryAjaxSettings; + + /** + * Set default values for future Ajax requests. Its use is not recommended. + * + * @param options A set of key/value pairs that configure the default Ajax request. All options are optional. + */ + ajaxSetup(options: JQueryAjaxSettings): void; + + /** + * Load data from the server using a HTTP GET request. + * + * @param url A string containing the URL to which the request is sent. + * @param success A callback function that is executed if the request succeeds. + * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, or html). + */ + get(url: string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR; + /** + * Load data from the server using a HTTP GET request. + * + * @param url A string containing the URL to which the request is sent. + * @param data A plain object or string that is sent to the server with the request. + * @param success A callback function that is executed if the request succeeds. + * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, or html). + */ + get(url: string, data?: Object|string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR; + /** + * Load JSON-encoded data from the server using a GET HTTP request. + * + * @param url A string containing the URL to which the request is sent. + * @param success A callback function that is executed if the request succeeds. + */ + getJSON(url: string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any): JQueryXHR; + /** + * Load JSON-encoded data from the server using a GET HTTP request. + * + * @param url A string containing the URL to which the request is sent. + * @param data A plain object or string that is sent to the server with the request. + * @param success A callback function that is executed if the request succeeds. + */ + getJSON(url: string, data?: Object|string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any): JQueryXHR; + /** + * Load a JavaScript file from the server using a GET HTTP request, then execute it. + * + * @param url A string containing the URL to which the request is sent. + * @param success A callback function that is executed if the request succeeds. + */ + getScript(url: string, success?: (script: string, textStatus: string, jqXHR: JQueryXHR) => any): JQueryXHR; + + /** + * Create a serialized representation of an array or object, suitable for use in a URL query string or Ajax request. + */ + param: JQueryParam; + + /** + * Load data from the server using a HTTP POST request. + * + * @param url A string containing the URL to which the request is sent. + * @param success A callback function that is executed if the request succeeds. Required if dataType is provided, but can be null in that case. + * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, text, html). + */ + post(url: string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR; + /** + * Load data from the server using a HTTP POST request. + * + * @param url A string containing the URL to which the request is sent. + * @param data A plain object or string that is sent to the server with the request. + * @param success A callback function that is executed if the request succeeds. Required if dataType is provided, but can be null in that case. + * @param dataType The type of data expected from the server. Default: Intelligent Guess (xml, json, script, text, html). + */ + post(url: string, data?: Object|string, success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any, dataType?: string): JQueryXHR; + + /** + * A multi-purpose callbacks list object that provides a powerful way to manage callback lists. + * + * @param flags An optional list of space-separated flags that change how the callback list behaves. + */ + Callbacks(flags?: string): JQueryCallback; + + /** + * Holds or releases the execution of jQuery's ready event. + * + * @param hold Indicates whether the ready hold is being requested or released + */ + holdReady(hold: boolean): void; + + /** + * Accepts a string containing a CSS selector which is then used to match a set of elements. + * + * @param selector A string containing a selector expression + * @param context A DOM Element, Document, or jQuery to use as context + */ + (selector: string, context?: Element|JQuery): JQuery; + + /** + * Accepts a string containing a CSS selector which is then used to match a set of elements. + * + * @param element A DOM element to wrap in a jQuery object. + */ + (element: Element): JQuery; + + /** + * Accepts a string containing a CSS selector which is then used to match a set of elements. + * + * @param elementArray An array containing a set of DOM elements to wrap in a jQuery object. + */ + (elementArray: Element[]): JQuery; + + /** + * Binds a function to be executed when the DOM has finished loading. + * + * @param callback A function to execute after the DOM is ready. + */ + (callback: (jQueryAlias?: JQueryStatic) => any): JQuery; + + /** + * Accepts a string containing a CSS selector which is then used to match a set of elements. + * + * @param object A plain object to wrap in a jQuery object. + */ + (object: {}): JQuery; + + /** + * Accepts a string containing a CSS selector which is then used to match a set of elements. + * + * @param object An existing jQuery object to clone. + */ + (object: JQuery): JQuery; + + /** + * Specify a function to execute when the DOM is fully loaded. + */ + (): JQuery; + + /** + * Creates DOM elements on the fly from the provided string of raw HTML. + * + * @param html A string of HTML to create on the fly. Note that this parses HTML, not XML. + * @param ownerDocument A document in which the new elements will be created. + */ + (html: string, ownerDocument?: Document): JQuery; + + /** + * Creates DOM elements on the fly from the provided string of raw HTML. + * + * @param html A string defining a single, standalone, HTML element (e.g.
    or
    ). + * @param attributes An object of attributes, events, and methods to call on the newly-created element. + */ + (html: string, attributes: Object): JQuery; + + /** + * Relinquish jQuery's control of the $ variable. + * + * @param removeAll A Boolean indicating whether to remove all jQuery variables from the global scope (including jQuery itself). + */ + noConflict(removeAll?: boolean): Object; + + /** + * Provides a way to execute callback functions based on one or more objects, usually Deferred objects that represent asynchronous events. + * + * @param deferreds One or more Deferred objects, or plain JavaScript objects. + */ + when(...deferreds: Array/* as JQueryDeferred */>): JQueryPromise; + + /** + * Hook directly into jQuery to override how particular CSS properties are retrieved or set, normalize CSS property naming, or create custom properties. + */ + cssHooks: { [key: string]: any; }; + cssNumber: any; + + /** + * Store arbitrary data associated with the specified element. Returns the value that was set. + * + * @param element The DOM element to associate with the data. + * @param key A string naming the piece of data to set. + * @param value The new data value. + */ + data(element: Element, key: string, value: T): T; + /** + * Returns value at named data store for the element, as set by jQuery.data(element, name, value), or the full data store for the element. + * + * @param element The DOM element to associate with the data. + * @param key A string naming the piece of data to set. + */ + data(element: Element, key: string): any; + /** + * Returns value at named data store for the element, as set by jQuery.data(element, name, value), or the full data store for the element. + * + * @param element The DOM element to associate with the data. + */ + data(element: Element): any; + + /** + * Execute the next function on the queue for the matched element. + * + * @param element A DOM element from which to remove and execute a queued function. + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + */ + dequeue(element: Element, queueName?: string): void; + + /** + * Determine whether an element has any jQuery data associated with it. + * + * @param element A DOM element to be checked for data. + */ + hasData(element: Element): boolean; + + /** + * Show the queue of functions to be executed on the matched element. + * + * @param element A DOM element to inspect for an attached queue. + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + */ + queue(element: Element, queueName?: string): any[]; + /** + * Manipulate the queue of functions to be executed on the matched element. + * + * @param element A DOM element where the array of queued functions is attached. + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + * @param newQueue An array of functions to replace the current queue contents. + */ + queue(element: Element, queueName: string, newQueue: Function[]): JQuery; + /** + * Manipulate the queue of functions to be executed on the matched element. + * + * @param element A DOM element on which to add a queued function. + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + * @param callback The new function to add to the queue. + */ + queue(element: Element, queueName: string, callback: Function): JQuery; + + /** + * Remove a previously-stored piece of data. + * + * @param element A DOM element from which to remove data. + * @param name A string naming the piece of data to remove. + */ + removeData(element: Element, name?: string): JQuery; + + /** + * A constructor function that returns a chainable utility object with methods to register multiple callbacks into callback queues, invoke callback queues, and relay the success or failure state of any synchronous or asynchronous function. + * + * @param beforeStart A function that is called just before the constructor returns. + */ + Deferred(beforeStart?: (deferred: JQueryDeferred) => any): JQueryDeferred; + + /** + * Effects + */ + fx: { + tick: () => void; + /** + * The rate (in milliseconds) at which animations fire. + */ + interval: number; + stop: () => void; + speeds: { slow: number; fast: number; }; + /** + * Globally disable all animations. + */ + off: boolean; + step: any; + }; + + /** + * Takes a function and returns a new one that will always have a particular context. + * + * @param fnction The function whose context will be changed. + * @param context The object to which the context (this) of the function should be set. + * @param additionalArguments Any number of arguments to be passed to the function referenced in the function argument. + */ + proxy(fnction: (...args: any[]) => any, context: Object, ...additionalArguments: any[]): any; + /** + * Takes a function and returns a new one that will always have a particular context. + * + * @param context The object to which the context (this) of the function should be set. + * @param name The name of the function whose context will be changed (should be a property of the context object). + * @param additionalArguments Any number of arguments to be passed to the function named in the name argument. + */ + proxy(context: Object, name: string, ...additionalArguments: any[]): any; + + Event: JQueryEventConstructor; + + /** + * Takes a string and throws an exception containing it. + * + * @param message The message to send out. + */ + error(message: any): JQuery; + + expr: any; + fn: any; //TODO: Decide how we want to type this + + isReady: boolean; + + // Properties + support: JQuerySupport; + + /** + * Check to see if a DOM element is a descendant of another DOM element. + * + * @param container The DOM element that may contain the other element. + * @param contained The DOM element that may be contained by (a descendant of) the other element. + */ + contains(container: Element, contained: Element): boolean; + + /** + * A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, from 0 to length-1. Other objects are iterated via their named properties. + * + * @param collection The object or array to iterate over. + * @param callback The function that will be executed on every object. + */ + each( + collection: T[], + callback: (indexInArray: number, valueOfElement: T) => any + ): any; + + /** + * A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, from 0 to length-1. Other objects are iterated via their named properties. + * + * @param collection The object or array to iterate over. + * @param callback The function that will be executed on every object. + */ + each( + collection: any, + callback: (indexInArray: any, valueOfElement: any) => any + ): any; + + /** + * Merge the contents of two or more objects together into the first object. + * + * @param target An object that will receive the new properties if additional objects are passed in or that will extend the jQuery namespace if it is the sole argument. + * @param object1 An object containing additional properties to merge in. + * @param objectN Additional objects containing properties to merge in. + */ + extend(target: any, object1?: any, ...objectN: any[]): any; + /** + * Merge the contents of two or more objects together into the first object. + * + * @param deep If true, the merge becomes recursive (aka. deep copy). + * @param target The object to extend. It will receive the new properties. + * @param object1 An object containing additional properties to merge in. + * @param objectN Additional objects containing properties to merge in. + */ + extend(deep: boolean, target: any, object1?: any, ...objectN: any[]): any; + + /** + * Execute some JavaScript code globally. + * + * @param code The JavaScript code to execute. + */ + globalEval(code: string): any; + + /** + * Finds the elements of an array which satisfy a filter function. The original array is not affected. + * + * @param array The array to search through. + * @param func The function to process each item against. The first argument to the function is the item, and the second argument is the index. The function should return a Boolean value. this will be the global window object. + * @param invert If "invert" is false, or not provided, then the function returns an array consisting of all elements for which "callback" returns true. If "invert" is true, then the function returns an array consisting of all elements for which "callback" returns false. + */ + grep(array: T[], func: (elementOfArray: T, indexInArray: number) => boolean, invert?: boolean): T[]; + + /** + * Search for a specified value within an array and return its index (or -1 if not found). + * + * @param value The value to search for. + * @param array An array through which to search. + * @param fromIndex he index of the array at which to begin the search. The default is 0, which will search the whole array. + */ + inArray(value: T, array: T[], fromIndex?: number): number; + + /** + * Determine whether the argument is an array. + * + * @param obj Object to test whether or not it is an array. + */ + isArray(obj: any): boolean; + /** + * Check to see if an object is empty (contains no enumerable properties). + * + * @param obj The object that will be checked to see if it's empty. + */ + isEmptyObject(obj: any): boolean; + /** + * Determine if the argument passed is a Javascript function object. + * + * @param obj Object to test whether or not it is a function. + */ + isFunction(obj: any): boolean; + /** + * Determines whether its argument is a number. + * + * @param obj The value to be tested. + */ + isNumeric(value: any): boolean; + /** + * Check to see if an object is a plain object (created using "{}" or "new Object"). + * + * @param obj The object that will be checked to see if it's a plain object. + */ + isPlainObject(obj: any): boolean; + /** + * Determine whether the argument is a window. + * + * @param obj Object to test whether or not it is a window. + */ + isWindow(obj: any): boolean; + /** + * Check to see if a DOM node is within an XML document (or is an XML document). + * + * @param node he DOM node that will be checked to see if it's in an XML document. + */ + isXMLDoc(node: Node): boolean; + + /** + * Convert an array-like object into a true JavaScript array. + * + * @param obj Any object to turn into a native Array. + */ + makeArray(obj: any): any[]; + + /** + * Translate all items in an array or object to new array of items. + * + * @param array The Array to translate. + * @param callback The function to process each item against. The first argument to the function is the array item, the second argument is the index in array The function can return any value. Within the function, this refers to the global (window) object. + */ + map(array: T[], callback: (elementOfArray: T, indexInArray: number) => U): U[]; + /** + * Translate all items in an array or object to new array of items. + * + * @param arrayOrObject The Array or Object to translate. + * @param callback The function to process each item against. The first argument to the function is the value; the second argument is the index or key of the array or object property. The function can return any value to add to the array. A returned array will be flattened into the resulting array. Within the function, this refers to the global (window) object. + */ + map(arrayOrObject: any, callback: (value: any, indexOrKey: any) => any): any; + + /** + * Merge the contents of two arrays together into the first array. + * + * @param first The first array to merge, the elements of second added. + * @param second The second array to merge into the first, unaltered. + */ + merge(first: T[], second: T[]): T[]; + + /** + * An empty function. + */ + noop(): any; + + /** + * Return a number representing the current time. + */ + now(): number; + + /** + * Takes a well-formed JSON string and returns the resulting JavaScript object. + * + * @param json The JSON string to parse. + */ + parseJSON(json: string): any; + + /** + * Parses a string into an XML document. + * + * @param data a well-formed XML string to be parsed + */ + parseXML(data: string): XMLDocument; + + /** + * Remove the whitespace from the beginning and end of a string. + * + * @param str Remove the whitespace from the beginning and end of a string. + */ + trim(str: string): string; + + /** + * Determine the internal JavaScript [[Class]] of an object. + * + * @param obj Object to get the internal JavaScript [[Class]] of. + */ + type(obj: any): string; + + /** + * Sorts an array of DOM elements, in place, with the duplicates removed. Note that this only works on arrays of DOM elements, not strings or numbers. + * + * @param array The Array of DOM elements. + */ + unique(array: Element[]): Element[]; + + /** + * Parses a string into an array of DOM nodes. + * + * @param data HTML string to be parsed + * @param context DOM element to serve as the context in which the HTML fragment will be created + * @param keepScripts A Boolean indicating whether to include scripts passed in the HTML string + */ + parseHTML(data: string, context?: HTMLElement, keepScripts?: boolean): any[]; + + /** + * Parses a string into an array of DOM nodes. + * + * @param data HTML string to be parsed + * @param context DOM element to serve as the context in which the HTML fragment will be created + * @param keepScripts A Boolean indicating whether to include scripts passed in the HTML string + */ + parseHTML(data: string, context?: Document, keepScripts?: boolean): any[]; +} + +/** + * The jQuery instance members + */ +interface JQuery { + /** + * Register a handler to be called when Ajax requests complete. This is an AjaxEvent. + * + * @param handler The function to be invoked. + */ + ajaxComplete(handler: (event: JQueryEventObject, XMLHttpRequest: XMLHttpRequest, ajaxOptions: any) => any): JQuery; + /** + * Register a handler to be called when Ajax requests complete with an error. This is an Ajax Event. + * + * @param handler The function to be invoked. + */ + ajaxError(handler: (event: JQueryEventObject, jqXHR: JQueryXHR, ajaxSettings: JQueryAjaxSettings, thrownError: any) => any): JQuery; + /** + * Attach a function to be executed before an Ajax request is sent. This is an Ajax Event. + * + * @param handler The function to be invoked. + */ + ajaxSend(handler: (event: JQueryEventObject, jqXHR: JQueryXHR, ajaxOptions: JQueryAjaxSettings) => any): JQuery; + /** + * Register a handler to be called when the first Ajax request begins. This is an Ajax Event. + * + * @param handler The function to be invoked. + */ + ajaxStart(handler: () => any): JQuery; + /** + * Register a handler to be called when all Ajax requests have completed. This is an Ajax Event. + * + * @param handler The function to be invoked. + */ + ajaxStop(handler: () => any): JQuery; + /** + * Attach a function to be executed whenever an Ajax request completes successfully. This is an Ajax Event. + * + * @param handler The function to be invoked. + */ + ajaxSuccess(handler: (event: JQueryEventObject, XMLHttpRequest: XMLHttpRequest, ajaxOptions: JQueryAjaxSettings) => any): JQuery; + + /** + * Load data from the server and place the returned HTML into the matched element. + * + * @param url A string containing the URL to which the request is sent. + * @param data A plain object or string that is sent to the server with the request. + * @param complete A callback function that is executed when the request completes. + */ + load(url: string, data?: string|Object, complete?: (responseText: string, textStatus: string, XMLHttpRequest: XMLHttpRequest) => any): JQuery; + + /** + * Encode a set of form elements as a string for submission. + */ + serialize(): string; + /** + * Encode a set of form elements as an array of names and values. + */ + serializeArray(): JQuerySerializeArrayElement[]; + + /** + * Adds the specified class(es) to each of the set of matched elements. + * + * @param className One or more space-separated classes to be added to the class attribute of each matched element. + */ + addClass(className: string): JQuery; + /** + * Adds the specified class(es) to each of the set of matched elements. + * + * @param function A function returning one or more space-separated class names to be added to the existing class name(s). Receives the index position of the element in the set and the existing class name(s) as arguments. Within the function, this refers to the current element in the set. + */ + addClass(func: (index: number, className: string) => string): JQuery; + + /** + * Add the previous set of elements on the stack to the current set, optionally filtered by a selector. + */ + addBack(selector?: string): JQuery; + + /** + * Get the value of an attribute for the first element in the set of matched elements. + * + * @param attributeName The name of the attribute to get. + */ + attr(attributeName: string): string; + /** + * Set one or more attributes for the set of matched elements. + * + * @param attributeName The name of the attribute to set. + * @param value A value to set for the attribute. + */ + attr(attributeName: string, value: string|number): JQuery; + /** + * Set one or more attributes for the set of matched elements. + * + * @param attributeName The name of the attribute to set. + * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old attribute value as arguments. + */ + attr(attributeName: string, func: (index: number, attr: string) => string|number): JQuery; + /** + * Set one or more attributes for the set of matched elements. + * + * @param attributes An object of attribute-value pairs to set. + */ + attr(attributes: Object): JQuery; + + /** + * Determine whether any of the matched elements are assigned the given class. + * + * @param className The class name to search for. + */ + hasClass(className: string): boolean; + + /** + * Get the HTML contents of the first element in the set of matched elements. + */ + html(): string; + /** + * Set the HTML contents of each element in the set of matched elements. + * + * @param htmlString A string of HTML to set as the content of each matched element. + */ + html(htmlString: string): JQuery; + /** + * Set the HTML contents of each element in the set of matched elements. + * + * @param func A function returning the HTML content to set. Receives the index position of the element in the set and the old HTML value as arguments. jQuery empties the element before calling the function; use the oldhtml argument to reference the previous content. Within the function, this refers to the current element in the set. + */ + html(func: (index: number, oldhtml: string) => string): JQuery; + /** + * Set the HTML contents of each element in the set of matched elements. + * + * @param func A function returning the HTML content to set. Receives the index position of the element in the set and the old HTML value as arguments. jQuery empties the element before calling the function; use the oldhtml argument to reference the previous content. Within the function, this refers to the current element in the set. + */ + + /** + * Get the value of a property for the first element in the set of matched elements. + * + * @param propertyName The name of the property to get. + */ + prop(propertyName: string): any; + /** + * Set one or more properties for the set of matched elements. + * + * @param propertyName The name of the property to set. + * @param value A value to set for the property. + */ + prop(propertyName: string, value: string|number|boolean): JQuery; + /** + * Set one or more properties for the set of matched elements. + * + * @param properties An object of property-value pairs to set. + */ + prop(properties: Object): JQuery; + /** + * Set one or more properties for the set of matched elements. + * + * @param propertyName The name of the property to set. + * @param func A function returning the value to set. Receives the index position of the element in the set and the old property value as arguments. Within the function, the keyword this refers to the current element. + */ + prop(propertyName: string, func: (index: number, oldPropertyValue: any) => any): JQuery; + + /** + * Remove an attribute from each element in the set of matched elements. + * + * @param attributeName An attribute to remove; as of version 1.7, it can be a space-separated list of attributes. + */ + removeAttr(attributeName: string): JQuery; + + /** + * Remove a single class, multiple classes, or all classes from each element in the set of matched elements. + * + * @param className One or more space-separated classes to be removed from the class attribute of each matched element. + */ + removeClass(className?: string): JQuery; + /** + * Remove a single class, multiple classes, or all classes from each element in the set of matched elements. + * + * @param function A function returning one or more space-separated class names to be removed. Receives the index position of the element in the set and the old class value as arguments. + */ + removeClass(func: (index: number, className: string) => string): JQuery; + + /** + * Remove a property for the set of matched elements. + * + * @param propertyName The name of the property to remove. + */ + removeProp(propertyName: string): JQuery; + + /** + * Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument. + * + * @param className One or more class names (separated by spaces) to be toggled for each element in the matched set. + * @param swtch A Boolean (not just truthy/falsy) value to determine whether the class should be added or removed. + */ + toggleClass(className: string, swtch?: boolean): JQuery; + /** + * Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument. + * + * @param swtch A boolean value to determine whether the class should be added or removed. + */ + toggleClass(swtch?: boolean): JQuery; + /** + * Add or remove one or more classes from each element in the set of matched elements, depending on either the class's presence or the value of the switch argument. + * + * @param func A function that returns class names to be toggled in the class attribute of each element in the matched set. Receives the index position of the element in the set, the old class value, and the switch as arguments. + * @param swtch A boolean value to determine whether the class should be added or removed. + */ + toggleClass(func: (index: number, className: string, swtch: boolean) => string, swtch?: boolean): JQuery; + + /** + * Get the current value of the first element in the set of matched elements. + */ + val(): any; + /** + * Set the value of each element in the set of matched elements. + * + * @param value A string of text or an array of strings corresponding to the value of each matched element to set as selected/checked. + */ + val(value: string|string[]): JQuery; + /** + * Set the value of each element in the set of matched elements. + * + * @param func A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments. + */ + val(func: (index: number, value: string) => string): JQuery; + + + /** + * Get the value of style properties for the first element in the set of matched elements. + * + * @param propertyName A CSS property. + */ + css(propertyName: string): string; + /** + * Set one or more CSS properties for the set of matched elements. + * + * @param propertyName A CSS property name. + * @param value A value to set for the property. + */ + css(propertyName: string, value: string|number): JQuery; + /** + * Set one or more CSS properties for the set of matched elements. + * + * @param propertyName A CSS property name. + * @param value A function returning the value to set. this is the current element. Receives the index position of the element in the set and the old value as arguments. + */ + css(propertyName: string, value: (index: number, value: string) => string|number): JQuery; + /** + * Set one or more CSS properties for the set of matched elements. + * + * @param properties An object of property-value pairs to set. + */ + css(properties: Object): JQuery; + + /** + * Get the current computed height for the first element in the set of matched elements. + */ + height(): number; + /** + * Set the CSS height of every matched element. + * + * @param value An integer representing the number of pixels, or an integer with an optional unit of measure appended (as a string). + */ + height(value: number|string): JQuery; + /** + * Set the CSS height of every matched element. + * + * @param func A function returning the height to set. Receives the index position of the element in the set and the old height as arguments. Within the function, this refers to the current element in the set. + */ + height(func: (index: number, height: number) => number|string): JQuery; + + /** + * Get the current computed height for the first element in the set of matched elements, including padding but not border. + */ + innerHeight(): number; + + /** + * Sets the inner height on elements in the set of matched elements, including padding but not border. + * + * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string). + */ + innerHeight(height: number|string): JQuery; + + /** + * Get the current computed width for the first element in the set of matched elements, including padding but not border. + */ + innerWidth(): number; + + /** + * Sets the inner width on elements in the set of matched elements, including padding but not border. + * + * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string). + */ + innerWidth(width: number|string): JQuery; + + /** + * Get the current coordinates of the first element in the set of matched elements, relative to the document. + */ + offset(): JQueryCoordinates; + /** + * An object containing the properties top and left, which are integers indicating the new top and left coordinates for the elements. + * + * @param coordinates An object containing the properties top and left, which are integers indicating the new top and left coordinates for the elements. + */ + offset(coordinates: JQueryCoordinates): JQuery; + /** + * An object containing the properties top and left, which are integers indicating the new top and left coordinates for the elements. + * + * @param func A function to return the coordinates to set. Receives the index of the element in the collection as the first argument and the current coordinates as the second argument. The function should return an object with the new top and left properties. + */ + offset(func: (index: number, coords: JQueryCoordinates) => JQueryCoordinates): JQuery; + + /** + * Get the current computed height for the first element in the set of matched elements, including padding, border, and optionally margin. Returns an integer (without "px") representation of the value or null if called on an empty set of elements. + * + * @param includeMargin A Boolean indicating whether to include the element's margin in the calculation. + */ + outerHeight(includeMargin?: boolean): number; + + /** + * Sets the outer height on elements in the set of matched elements, including padding and border. + * + * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string). + */ + outerHeight(height: number|string): JQuery; + + /** + * Get the current computed width for the first element in the set of matched elements, including padding and border. + * + * @param includeMargin A Boolean indicating whether to include the element's margin in the calculation. + */ + outerWidth(includeMargin?: boolean): number; + + /** + * Sets the outer width on elements in the set of matched elements, including padding and border. + * + * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string). + */ + outerWidth(width: number|string): JQuery; + + /** + * Get the current coordinates of the first element in the set of matched elements, relative to the offset parent. + */ + position(): JQueryCoordinates; + + /** + * Get the current horizontal position of the scroll bar for the first element in the set of matched elements or set the horizontal position of the scroll bar for every matched element. + */ + scrollLeft(): number; + /** + * Set the current horizontal position of the scroll bar for each of the set of matched elements. + * + * @param value An integer indicating the new position to set the scroll bar to. + */ + scrollLeft(value: number): JQuery; + + /** + * Get the current vertical position of the scroll bar for the first element in the set of matched elements or set the vertical position of the scroll bar for every matched element. + */ + scrollTop(): number; + /** + * Set the current vertical position of the scroll bar for each of the set of matched elements. + * + * @param value An integer indicating the new position to set the scroll bar to. + */ + scrollTop(value: number): JQuery; + + /** + * Get the current computed width for the first element in the set of matched elements. + */ + width(): number; + /** + * Set the CSS width of each element in the set of matched elements. + * + * @param value An integer representing the number of pixels, or an integer along with an optional unit of measure appended (as a string). + */ + width(value: number|string): JQuery; + /** + * Set the CSS width of each element in the set of matched elements. + * + * @param func A function returning the width to set. Receives the index position of the element in the set and the old width as arguments. Within the function, this refers to the current element in the set. + */ + width(func: (index: number, width: number) => number|string): JQuery; + + /** + * Remove from the queue all items that have not yet been run. + * + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + */ + clearQueue(queueName?: string): JQuery; + + /** + * Store arbitrary data associated with the matched elements. + * + * @param key A string naming the piece of data to set. + * @param value The new data value; it can be any Javascript type including Array or Object. + */ + data(key: string, value: any): JQuery; + /** + * Return the value at the named data store for the first element in the jQuery collection, as set by data(name, value) or by an HTML5 data-* attribute. + * + * @param key Name of the data stored. + */ + data(key: string): any; + /** + * Store arbitrary data associated with the matched elements. + * + * @param obj An object of key-value pairs of data to update. + */ + data(obj: { [key: string]: any; }): JQuery; + /** + * Return the value at the named data store for the first element in the jQuery collection, as set by data(name, value) or by an HTML5 data-* attribute. + */ + data(): any; + + /** + * Execute the next function on the queue for the matched elements. + * + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + */ + dequeue(queueName?: string): JQuery; + + /** + * Remove a previously-stored piece of data. + * + * @param name A string naming the piece of data to delete or space-separated string naming the pieces of data to delete. + */ + removeData(name: string): JQuery; + /** + * Remove a previously-stored piece of data. + * + * @param list An array of strings naming the pieces of data to delete. + */ + removeData(list: string[]): JQuery; + + /** + * Return a Promise object to observe when all actions of a certain type bound to the collection, queued or not, have finished. + * + * @param type The type of queue that needs to be observed. (default: fx) + * @param target Object onto which the promise methods have to be attached + */ + promise(type?: string, target?: Object): JQueryPromise; + + /** + * Perform a custom animation of a set of CSS properties. + * + * @param properties An object of CSS properties and values that the animation will move toward. + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + animate(properties: Object, duration?: string|number, complete?: Function): JQuery; + /** + * Perform a custom animation of a set of CSS properties. + * + * @param properties An object of CSS properties and values that the animation will move toward. + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. (default: swing) + * @param complete A function to call once the animation is complete. + */ + animate(properties: Object, duration?: string|number, easing?: string, complete?: Function): JQuery; + /** + * Perform a custom animation of a set of CSS properties. + * + * @param properties An object of CSS properties and values that the animation will move toward. + * @param options A map of additional options to pass to the method. + */ + animate(properties: Object, options: JQueryAnimationOptions): JQuery; + + /** + * Set a timer to delay execution of subsequent items in the queue. + * + * @param duration An integer indicating the number of milliseconds to delay execution of the next item in the queue. + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + */ + delay(duration: number, queueName?: string): JQuery; + + /** + * Display the matched elements by fading them to opaque. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + fadeIn(duration?: number|string, complete?: Function): JQuery; + /** + * Display the matched elements by fading them to opaque. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + fadeIn(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Display the matched elements by fading them to opaque. + * + * @param options A map of additional options to pass to the method. + */ + fadeIn(options: JQueryAnimationOptions): JQuery; + + /** + * Hide the matched elements by fading them to transparent. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + fadeOut(duration?: number|string, complete?: Function): JQuery; + /** + * Hide the matched elements by fading them to transparent. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + fadeOut(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Hide the matched elements by fading them to transparent. + * + * @param options A map of additional options to pass to the method. + */ + fadeOut(options: JQueryAnimationOptions): JQuery; + + /** + * Adjust the opacity of the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param opacity A number between 0 and 1 denoting the target opacity. + * @param complete A function to call once the animation is complete. + */ + fadeTo(duration: string|number, opacity: number, complete?: Function): JQuery; + /** + * Adjust the opacity of the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param opacity A number between 0 and 1 denoting the target opacity. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + fadeTo(duration: string|number, opacity: number, easing?: string, complete?: Function): JQuery; + + /** + * Display or hide the matched elements by animating their opacity. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + fadeToggle(duration?: number|string, complete?: Function): JQuery; + /** + * Display or hide the matched elements by animating their opacity. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + fadeToggle(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Display or hide the matched elements by animating their opacity. + * + * @param options A map of additional options to pass to the method. + */ + fadeToggle(options: JQueryAnimationOptions): JQuery; + + /** + * Stop the currently-running animation, remove all queued animations, and complete all animations for the matched elements. + * + * @param queue The name of the queue in which to stop animations. + */ + finish(queue?: string): JQuery; + + /** + * Hide the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + hide(duration?: number|string, complete?: Function): JQuery; + /** + * Hide the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + hide(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Hide the matched elements. + * + * @param options A map of additional options to pass to the method. + */ + hide(options: JQueryAnimationOptions): JQuery; + + /** + * Display the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + show(duration?: number|string, complete?: Function): JQuery; + /** + * Display the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + show(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Display the matched elements. + * + * @param options A map of additional options to pass to the method. + */ + show(options: JQueryAnimationOptions): JQuery; + + /** + * Display the matched elements with a sliding motion. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + slideDown(duration?: number|string, complete?: Function): JQuery; + /** + * Display the matched elements with a sliding motion. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + slideDown(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Display the matched elements with a sliding motion. + * + * @param options A map of additional options to pass to the method. + */ + slideDown(options: JQueryAnimationOptions): JQuery; + + /** + * Display or hide the matched elements with a sliding motion. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + slideToggle(duration?: number|string, complete?: Function): JQuery; + /** + * Display or hide the matched elements with a sliding motion. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + slideToggle(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Display or hide the matched elements with a sliding motion. + * + * @param options A map of additional options to pass to the method. + */ + slideToggle(options: JQueryAnimationOptions): JQuery; + + /** + * Hide the matched elements with a sliding motion. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + slideUp(duration?: number|string, complete?: Function): JQuery; + /** + * Hide the matched elements with a sliding motion. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + slideUp(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Hide the matched elements with a sliding motion. + * + * @param options A map of additional options to pass to the method. + */ + slideUp(options: JQueryAnimationOptions): JQuery; + + /** + * Stop the currently-running animation on the matched elements. + * + * @param clearQueue A Boolean indicating whether to remove queued animation as well. Defaults to false. + * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. Defaults to false. + */ + stop(clearQueue?: boolean, jumpToEnd?: boolean): JQuery; + /** + * Stop the currently-running animation on the matched elements. + * + * @param queue The name of the queue in which to stop animations. + * @param clearQueue A Boolean indicating whether to remove queued animation as well. Defaults to false. + * @param jumpToEnd A Boolean indicating whether to complete the current animation immediately. Defaults to false. + */ + stop(queue?: string, clearQueue?: boolean, jumpToEnd?: boolean): JQuery; + + /** + * Display or hide the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param complete A function to call once the animation is complete. + */ + toggle(duration?: number|string, complete?: Function): JQuery; + /** + * Display or hide the matched elements. + * + * @param duration A string or number determining how long the animation will run. + * @param easing A string indicating which easing function to use for the transition. + * @param complete A function to call once the animation is complete. + */ + toggle(duration?: number|string, easing?: string, complete?: Function): JQuery; + /** + * Display or hide the matched elements. + * + * @param options A map of additional options to pass to the method. + */ + toggle(options: JQueryAnimationOptions): JQuery; + /** + * Display or hide the matched elements. + * + * @param showOrHide A Boolean indicating whether to show or hide the elements. + */ + toggle(showOrHide: boolean): JQuery; + + /** + * Attach a handler to an event for the elements. + * + * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names. + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + bind(eventType: string, eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Attach a handler to an event for the elements. + * + * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names. + * @param handler A function to execute each time the event is triggered. + */ + bind(eventType: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Attach a handler to an event for the elements. + * + * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names. + * @param eventData An object containing data that will be passed to the event handler. + * @param preventBubble Setting the third argument to false will attach a function that prevents the default action from occurring and stops the event from bubbling. The default is true. + */ + bind(eventType: string, eventData: any, preventBubble: boolean): JQuery; + /** + * Attach a handler to an event for the elements. + * + * @param eventType A string containing one or more DOM event types, such as "click" or "submit," or custom event names. + * @param preventBubble Setting the third argument to false will attach a function that prevents the default action from occurring and stops the event from bubbling. The default is true. + */ + bind(eventType: string, preventBubble: boolean): JQuery; + /** + * Attach a handler to an event for the elements. + * + * @param events An object containing one or more DOM event types and functions to execute for them. + */ + bind(events: any): JQuery; + + /** + * Trigger the "blur" event on an element + */ + blur(): JQuery; + /** + * Bind an event handler to the "blur" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + blur(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "blur" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + blur(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "change" event on an element. + */ + change(): JQuery; + /** + * Bind an event handler to the "change" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + change(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "change" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + change(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "click" event on an element. + */ + click(): JQuery; + /** + * Bind an event handler to the "click" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + */ + click(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "click" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + click(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "dblclick" event on an element. + */ + dblclick(): JQuery; + /** + * Bind an event handler to the "dblclick" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + dblclick(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "dblclick" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + dblclick(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + delegate(selector: any, eventType: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + delegate(selector: any, eventType: string, eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "focus" event on an element. + */ + focus(): JQuery; + /** + * Bind an event handler to the "focus" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + focus(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "focus" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + focus(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Bind an event handler to the "focusin" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + focusin(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "focusin" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + focusin(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Bind an event handler to the "focusout" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + focusout(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "focusout" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + focusout(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Bind two handlers to the matched elements, to be executed when the mouse pointer enters and leaves the elements. + * + * @param handlerIn A function to execute when the mouse pointer enters the element. + * @param handlerOut A function to execute when the mouse pointer leaves the element. + */ + hover(handlerIn: (eventObject: JQueryEventObject) => any, handlerOut: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind a single handler to the matched elements, to be executed when the mouse pointer enters or leaves the elements. + * + * @param handlerInOut A function to execute when the mouse pointer enters or leaves the element. + */ + hover(handlerInOut: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "keydown" event on an element. + */ + keydown(): JQuery; + /** + * Bind an event handler to the "keydown" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + keydown(handler: (eventObject: JQueryKeyEventObject) => any): JQuery; + /** + * Bind an event handler to the "keydown" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + keydown(eventData?: any, handler?: (eventObject: JQueryKeyEventObject) => any): JQuery; + + /** + * Trigger the "keypress" event on an element. + */ + keypress(): JQuery; + /** + * Bind an event handler to the "keypress" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + keypress(handler: (eventObject: JQueryKeyEventObject) => any): JQuery; + /** + * Bind an event handler to the "keypress" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + keypress(eventData?: any, handler?: (eventObject: JQueryKeyEventObject) => any): JQuery; + + /** + * Trigger the "keyup" event on an element. + */ + keyup(): JQuery; + /** + * Bind an event handler to the "keyup" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + keyup(handler: (eventObject: JQueryKeyEventObject) => any): JQuery; + /** + * Bind an event handler to the "keyup" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + keyup(eventData?: any, handler?: (eventObject: JQueryKeyEventObject) => any): JQuery; + + /** + * Bind an event handler to the "load" JavaScript event. + * + * @param handler A function to execute when the event is triggered. + */ + load(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "load" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + load(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "mousedown" event on an element. + */ + mousedown(): JQuery; + /** + * Bind an event handler to the "mousedown" JavaScript event. + * + * @param handler A function to execute when the event is triggered. + */ + mousedown(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to the "mousedown" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mousedown(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Trigger the "mouseenter" event on an element. + */ + mouseenter(): JQuery; + /** + * Bind an event handler to be fired when the mouse enters an element. + * + * @param handler A function to execute when the event is triggered. + */ + mouseenter(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to be fired when the mouse enters an element. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mouseenter(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Trigger the "mouseleave" event on an element. + */ + mouseleave(): JQuery; + /** + * Bind an event handler to be fired when the mouse leaves an element. + * + * @param handler A function to execute when the event is triggered. + */ + mouseleave(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to be fired when the mouse leaves an element. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mouseleave(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Trigger the "mousemove" event on an element. + */ + mousemove(): JQuery; + /** + * Bind an event handler to the "mousemove" JavaScript event. + * + * @param handler A function to execute when the event is triggered. + */ + mousemove(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to the "mousemove" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mousemove(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Trigger the "mouseout" event on an element. + */ + mouseout(): JQuery; + /** + * Bind an event handler to the "mouseout" JavaScript event. + * + * @param handler A function to execute when the event is triggered. + */ + mouseout(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to the "mouseout" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mouseout(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Trigger the "mouseover" event on an element. + */ + mouseover(): JQuery; + /** + * Bind an event handler to the "mouseover" JavaScript event. + * + * @param handler A function to execute when the event is triggered. + */ + mouseover(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to the "mouseover" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mouseover(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Trigger the "mouseup" event on an element. + */ + mouseup(): JQuery; + /** + * Bind an event handler to the "mouseup" JavaScript event. + * + * @param handler A function to execute when the event is triggered. + */ + mouseup(handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + /** + * Bind an event handler to the "mouseup" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + mouseup(eventData: Object, handler: (eventObject: JQueryMouseEventObject) => any): JQuery; + + /** + * Remove an event handler. + */ + off(): JQuery; + /** + * Remove an event handler. + * + * @param events One or more space-separated event types and optional namespaces, or just namespaces, such as "click", "keydown.myPlugin", or ".myPlugin". + * @param selector A selector which should match the one originally passed to .on() when attaching event handlers. + * @param handler A handler function previously attached for the event(s), or the special value false. + */ + off(events: string, selector?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Remove an event handler. + * + * @param events One or more space-separated event types and optional namespaces, or just namespaces, such as "click", "keydown.myPlugin", or ".myPlugin". + * @param handler A handler function previously attached for the event(s), or the special value false. + */ + off(events: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Remove an event handler. + * + * @param events An object where the string keys represent one or more space-separated event types and optional namespaces, and the values represent handler functions previously attached for the event(s). + * @param selector A selector which should match the one originally passed to .on() when attaching event handlers. + */ + off(events: { [key: string]: any; }, selector?: string): JQuery; + + /** + * Attach an event handler function for one or more events to the selected elements. + * + * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin". + * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. Rest parameter args is for optional parameters passed to jQuery.trigger(). Note that the actual parameters on the event handler function must be marked as optional (? syntax). + */ + on(events: string, handler: (eventObject: JQueryEventObject, ...args: any[]) => any): JQuery; + /** + * Attach an event handler function for one or more events to the selected elements. + * + * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin". + * @param data Data to be passed to the handler in event.data when an event is triggered. + * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. + */ + on(events: string, data : any, handler: (eventObject: JQueryEventObject, ...args: any[]) => any): JQuery; + /** + * Attach an event handler function for one or more events to the selected elements. + * + * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin". + * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element. + * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. + */ + on(events: string, selector: string, handler: (eventObject: JQueryEventObject, ...eventData: any[]) => any): JQuery; + /** + * Attach an event handler function for one or more events to the selected elements. + * + * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin". + * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element. + * @param data Data to be passed to the handler in event.data when an event is triggered. + * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. + */ + on(events: string, selector: string, data: any, handler: (eventObject: JQueryEventObject, ...eventData: any[]) => any): JQuery; + /** + * Attach an event handler function for one or more events to the selected elements. + * + * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s). + * @param selector A selector string to filter the descendants of the selected elements that will call the handler. If the selector is null or omitted, the handler is always called when it reaches the selected element. + * @param data Data to be passed to the handler in event.data when an event occurs. + */ + on(events: { [key: string]: any; }, selector?: string, data?: any): JQuery; + /** + * Attach an event handler function for one or more events to the selected elements. + * + * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s). + * @param data Data to be passed to the handler in event.data when an event occurs. + */ + on(events: { [key: string]: any; }, data?: any): JQuery; + + /** + * Attach a handler to an event for the elements. The handler is executed at most once per element per event type. + * + * @param events A string containing one or more JavaScript event types, such as "click" or "submit," or custom event names. + * @param handler A function to execute at the time the event is triggered. + */ + one(events: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Attach a handler to an event for the elements. The handler is executed at most once per element per event type. + * + * @param events A string containing one or more JavaScript event types, such as "click" or "submit," or custom event names. + * @param data An object containing data that will be passed to the event handler. + * @param handler A function to execute at the time the event is triggered. + */ + one(events: string, data: Object, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Attach a handler to an event for the elements. The handler is executed at most once per element per event type. + * + * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin". + * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element. + * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. + */ + one(events: string, selector: string, handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Attach a handler to an event for the elements. The handler is executed at most once per element per event type. + * + * @param events One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin". + * @param selector A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element. + * @param data Data to be passed to the handler in event.data when an event is triggered. + * @param handler A function to execute when the event is triggered. The value false is also allowed as a shorthand for a function that simply does return false. + */ + one(events: string, selector: string, data: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Attach a handler to an event for the elements. The handler is executed at most once per element per event type. + * + * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s). + * @param selector A selector string to filter the descendants of the selected elements that will call the handler. If the selector is null or omitted, the handler is always called when it reaches the selected element. + * @param data Data to be passed to the handler in event.data when an event occurs. + */ + one(events: { [key: string]: any; }, selector?: string, data?: any): JQuery; + + /** + * Attach a handler to an event for the elements. The handler is executed at most once per element per event type. + * + * @param events An object in which the string keys represent one or more space-separated event types and optional namespaces, and the values represent a handler function to be called for the event(s). + * @param data Data to be passed to the handler in event.data when an event occurs. + */ + one(events: { [key: string]: any; }, data?: any): JQuery; + + + /** + * Specify a function to execute when the DOM is fully loaded. + * + * @param handler A function to execute after the DOM is ready. + */ + ready(handler: (jQueryAlias?: JQueryStatic) => any): JQuery; + + /** + * Trigger the "resize" event on an element. + */ + resize(): JQuery; + /** + * Bind an event handler to the "resize" JavaScript event. + * + * @param handler A function to execute each time the event is triggered. + */ + resize(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "resize" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + resize(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "scroll" event on an element. + */ + scroll(): JQuery; + /** + * Bind an event handler to the "scroll" JavaScript event. + * + * @param handler A function to execute each time the event is triggered. + */ + scroll(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "scroll" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + scroll(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "select" event on an element. + */ + select(): JQuery; + /** + * Bind an event handler to the "select" JavaScript event. + * + * @param handler A function to execute each time the event is triggered. + */ + select(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "select" JavaScript event. + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + select(eventData: Object, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Trigger the "submit" event on an element. + */ + submit(): JQuery; + /** + * Bind an event handler to the "submit" JavaScript event + * + * @param handler A function to execute each time the event is triggered. + */ + submit(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "submit" JavaScript event + * + * @param eventData An object containing data that will be passed to the event handler. + * @param handler A function to execute each time the event is triggered. + */ + submit(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Execute all handlers and behaviors attached to the matched elements for the given event type. + * + * @param eventType A string containing a JavaScript event type, such as click or submit. + * @param extraParameters Additional parameters to pass along to the event handler. + */ + trigger(eventType: string, extraParameters?: any[]|Object): JQuery; + /** + * Execute all handlers and behaviors attached to the matched elements for the given event type. + * + * @param event A jQuery.Event object. + * @param extraParameters Additional parameters to pass along to the event handler. + */ + trigger(event: JQueryEventObject, extraParameters?: any[]|Object): JQuery; + + /** + * Execute all handlers attached to an element for an event. + * + * @param eventType A string containing a JavaScript event type, such as click or submit. + * @param extraParameters An array of additional parameters to pass along to the event handler. + */ + triggerHandler(eventType: string, ...extraParameters: any[]): Object; + + /** + * Execute all handlers attached to an element for an event. + * + * @param event A jQuery.Event object. + * @param extraParameters An array of additional parameters to pass along to the event handler. + */ + triggerHandler(event: JQueryEventObject, ...extraParameters: any[]): Object; + + /** + * Remove a previously-attached event handler from the elements. + * + * @param eventType A string containing a JavaScript event type, such as click or submit. + * @param handler The function that is to be no longer executed. + */ + unbind(eventType?: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Remove a previously-attached event handler from the elements. + * + * @param eventType A string containing a JavaScript event type, such as click or submit. + * @param fls Unbinds the corresponding 'return false' function that was bound using .bind( eventType, false ). + */ + unbind(eventType: string, fls: boolean): JQuery; + /** + * Remove a previously-attached event handler from the elements. + * + * @param evt A JavaScript event object as passed to an event handler. + */ + unbind(evt: any): JQuery; + + /** + * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements. + */ + undelegate(): JQuery; + /** + * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements. + * + * @param selector A selector which will be used to filter the event results. + * @param eventType A string containing a JavaScript event type, such as "click" or "keydown" + * @param handler A function to execute at the time the event is triggered. + */ + undelegate(selector: string, eventType: string, handler?: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements. + * + * @param selector A selector which will be used to filter the event results. + * @param events An object of one or more event types and previously bound functions to unbind from them. + */ + undelegate(selector: string, events: Object): JQuery; + /** + * Remove a handler from the event for all elements which match the current selector, based upon a specific set of root elements. + * + * @param namespace A string containing a namespace to unbind all events from. + */ + undelegate(namespace: string): JQuery; + + /** + * Bind an event handler to the "unload" JavaScript event. (DEPRECATED from v1.8) + * + * @param handler A function to execute when the event is triggered. + */ + unload(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "unload" JavaScript event. (DEPRECATED from v1.8) + * + * @param eventData A plain object of data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + unload(eventData?: any, handler?: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * The DOM node context originally passed to jQuery(); if none was passed then context will likely be the document. (DEPRECATED from v1.10) + */ + context: Element; + + jquery: string; + + /** + * Bind an event handler to the "error" JavaScript event. (DEPRECATED from v1.8) + * + * @param handler A function to execute when the event is triggered. + */ + error(handler: (eventObject: JQueryEventObject) => any): JQuery; + /** + * Bind an event handler to the "error" JavaScript event. (DEPRECATED from v1.8) + * + * @param eventData A plain object of data that will be passed to the event handler. + * @param handler A function to execute when the event is triggered. + */ + error(eventData: any, handler: (eventObject: JQueryEventObject) => any): JQuery; + + /** + * Add a collection of DOM elements onto the jQuery stack. + * + * @param elements An array of elements to push onto the stack and make into a new jQuery object. + */ + pushStack(elements: any[]): JQuery; + /** + * Add a collection of DOM elements onto the jQuery stack. + * + * @param elements An array of elements to push onto the stack and make into a new jQuery object. + * @param name The name of a jQuery method that generated the array of elements. + * @param arguments The arguments that were passed in to the jQuery method (for serialization). + */ + pushStack(elements: any[], name: string, arguments: any[]): JQuery; + + /** + * Insert content, specified by the parameter, after each element in the set of matched elements. + * + * param content1 HTML string, DOM element, array of elements, or jQuery object to insert after each element in the set of matched elements. + * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert after each element in the set of matched elements. + */ + after(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery; + /** + * Insert content, specified by the parameter, after each element in the set of matched elements. + * + * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert after each element in the set of matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set. + */ + after(func: (index: number, html: string) => string|Element|JQuery): JQuery; + + /** + * Insert content, specified by the parameter, to the end of each element in the set of matched elements. + * + * param content1 DOM element, array of elements, HTML string, or jQuery object to insert at the end of each element in the set of matched elements. + * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the end of each element in the set of matched elements. + */ + append(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery; + /** + * Insert content, specified by the parameter, to the end of each element in the set of matched elements. + * + * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert at the end of each element in the set of matched elements. Receives the index position of the element in the set and the old HTML value of the element as arguments. Within the function, this refers to the current element in the set. + */ + append(func: (index: number, html: string) => string|Element|JQuery): JQuery; + + /** + * Insert every element in the set of matched elements to the end of the target. + * + * @param target A selector, element, HTML string, array of elements, or jQuery object; the matched set of elements will be inserted at the end of the element(s) specified by this parameter. + */ + appendTo(target: JQuery|any[]|Element|string): JQuery; + + /** + * Insert content, specified by the parameter, before each element in the set of matched elements. + * + * param content1 HTML string, DOM element, array of elements, or jQuery object to insert before each element in the set of matched elements. + * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert before each element in the set of matched elements. + */ + before(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery; + /** + * Insert content, specified by the parameter, before each element in the set of matched elements. + * + * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert before each element in the set of matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set. + */ + before(func: (index: number, html: string) => string|Element|JQuery): JQuery; + + /** + * Create a deep copy of the set of matched elements. + * + * param withDataAndEvents A Boolean indicating whether event handlers and data should be copied along with the elements. The default value is false. + * param deepWithDataAndEvents A Boolean indicating whether event handlers and data for all children of the cloned element should be copied. By default its value matches the first argument's value (which defaults to false). + */ + clone(withDataAndEvents?: boolean, deepWithDataAndEvents?: boolean): JQuery; + + /** + * Remove the set of matched elements from the DOM. + * + * param selector A selector expression that filters the set of matched elements to be removed. + */ + detach(selector?: string): JQuery; + + /** + * Remove all child nodes of the set of matched elements from the DOM. + */ + empty(): JQuery; + + /** + * Insert every element in the set of matched elements after the target. + * + * param target A selector, element, array of elements, HTML string, or jQuery object; the matched set of elements will be inserted after the element(s) specified by this parameter. + */ + insertAfter(target: JQuery|any[]|Element|Text|string): JQuery; + + /** + * Insert every element in the set of matched elements before the target. + * + * param target A selector, element, array of elements, HTML string, or jQuery object; the matched set of elements will be inserted before the element(s) specified by this parameter. + */ + insertBefore(target: JQuery|any[]|Element|Text|string): JQuery; + + /** + * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements. + * + * param content1 DOM element, array of elements, HTML string, or jQuery object to insert at the beginning of each element in the set of matched elements. + * param content2 One or more additional DOM elements, arrays of elements, HTML strings, or jQuery objects to insert at the beginning of each element in the set of matched elements. + */ + prepend(content1: JQuery|any[]|Element|Text|string, ...content2: any[]): JQuery; + /** + * Insert content, specified by the parameter, to the beginning of each element in the set of matched elements. + * + * param func A function that returns an HTML string, DOM element(s), or jQuery object to insert at the beginning of each element in the set of matched elements. Receives the index position of the element in the set and the old HTML value of the element as arguments. Within the function, this refers to the current element in the set. + */ + prepend(func: (index: number, html: string) => string|Element|JQuery): JQuery; + + /** + * Insert every element in the set of matched elements to the beginning of the target. + * + * @param target A selector, element, HTML string, array of elements, or jQuery object; the matched set of elements will be inserted at the beginning of the element(s) specified by this parameter. + */ + prependTo(target: JQuery|any[]|Element|string): JQuery; + + /** + * Remove the set of matched elements from the DOM. + * + * @param selector A selector expression that filters the set of matched elements to be removed. + */ + remove(selector?: string): JQuery; + + /** + * Replace each target element with the set of matched elements. + * + * @param target A selector string, jQuery object, DOM element, or array of elements indicating which element(s) to replace. + */ + replaceAll(target: JQuery|any[]|Element|string): JQuery; + + /** + * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed. + * + * param newContent The content to insert. May be an HTML string, DOM element, array of DOM elements, or jQuery object. + */ + replaceWith(newContent: JQuery|any[]|Element|Text|string): JQuery; + /** + * Replace each element in the set of matched elements with the provided new content and return the set of elements that was removed. + * + * param func A function that returns content with which to replace the set of matched elements. + */ + replaceWith(func: () => Element|JQuery): JQuery; + + /** + * Get the combined text contents of each element in the set of matched elements, including their descendants. + */ + text(): string; + /** + * Set the content of each element in the set of matched elements to the specified text. + * + * @param text The text to set as the content of each matched element. When Number or Boolean is supplied, it will be converted to a String representation. + */ + text(text: string|number|boolean): JQuery; + /** + * Set the content of each element in the set of matched elements to the specified text. + * + * @param func A function returning the text content to set. Receives the index position of the element in the set and the old text value as arguments. + */ + text(func: (index: number, text: string) => string): JQuery; + + /** + * Retrieve all the elements contained in the jQuery set, as an array. + */ + toArray(): any[]; + + /** + * Remove the parents of the set of matched elements from the DOM, leaving the matched elements in their place. + */ + unwrap(): JQuery; + + /** + * Wrap an HTML structure around each element in the set of matched elements. + * + * @param wrappingElement A selector, element, HTML string, or jQuery object specifying the structure to wrap around the matched elements. + */ + wrap(wrappingElement: JQuery|Element|string): JQuery; + /** + * Wrap an HTML structure around each element in the set of matched elements. + * + * @param func A callback function returning the HTML content or jQuery object to wrap around the matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set. + */ + wrap(func: (index: number) => string|JQuery): JQuery; + + /** + * Wrap an HTML structure around all elements in the set of matched elements. + * + * @param wrappingElement A selector, element, HTML string, or jQuery object specifying the structure to wrap around the matched elements. + */ + wrapAll(wrappingElement: JQuery|Element|string): JQuery; + wrapAll(func: (index: number) => string): JQuery; + + /** + * Wrap an HTML structure around the content of each element in the set of matched elements. + * + * @param wrappingElement An HTML snippet, selector expression, jQuery object, or DOM element specifying the structure to wrap around the content of the matched elements. + */ + wrapInner(wrappingElement: JQuery|Element|string): JQuery; + /** + * Wrap an HTML structure around the content of each element in the set of matched elements. + * + * @param func A callback function which generates a structure to wrap around the content of the matched elements. Receives the index position of the element in the set as an argument. Within the function, this refers to the current element in the set. + */ + wrapInner(func: (index: number) => string): JQuery; + + /** + * Iterate over a jQuery object, executing a function for each matched element. + * + * @param func A function to execute for each matched element. + */ + each(func: (index: number, elem: Element) => any): JQuery; + + /** + * Retrieve one of the elements matched by the jQuery object. + * + * @param index A zero-based integer indicating which element to retrieve. + */ + get(index: number): HTMLElement; + /** + * Retrieve the elements matched by the jQuery object. + */ + get(): any[]; + + /** + * Search for a given element from among the matched elements. + */ + index(): number; + /** + * Search for a given element from among the matched elements. + * + * @param selector A selector representing a jQuery collection in which to look for an element. + */ + index(selector: string|JQuery|Element): number; + + /** + * The number of elements in the jQuery object. + */ + length: number; + /** + * A selector representing selector passed to jQuery(), if any, when creating the original set. + * version deprecated: 1.7, removed: 1.9 + */ + selector: string; + [index: string]: any; + [index: number]: HTMLElement; + + /** + * Add elements to the set of matched elements. + * + * @param selector A string representing a selector expression to find additional elements to add to the set of matched elements. + * @param context The point in the document at which the selector should begin matching; similar to the context argument of the $(selector, context) method. + */ + add(selector: string, context?: Element): JQuery; + /** + * Add elements to the set of matched elements. + * + * @param elements One or more elements to add to the set of matched elements. + */ + add(...elements: Element[]): JQuery; + /** + * Add elements to the set of matched elements. + * + * @param html An HTML fragment to add to the set of matched elements. + */ + add(html: string): JQuery; + /** + * Add elements to the set of matched elements. + * + * @param obj An existing jQuery object to add to the set of matched elements. + */ + add(obj: JQuery): JQuery; + + /** + * Get the children of each element in the set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + children(selector?: string): JQuery; + + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * + * @param selector A string containing a selector expression to match elements against. + */ + closest(selector: string): JQuery; + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * + * @param selector A string containing a selector expression to match elements against. + * @param context A DOM element within which a matching element may be found. If no context is passed in then the context of the jQuery set will be used instead. + */ + closest(selector: string, context?: Element): JQuery; + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * + * @param obj A jQuery object to match elements against. + */ + closest(obj: JQuery): JQuery; + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * + * @param element An element to match elements against. + */ + closest(element: Element): JQuery; + + /** + * Get an array of all the elements and selectors matched against the current element up through the DOM tree. + * + * @param selectors An array or string containing a selector expression to match elements against (can also be a jQuery object). + * @param context A DOM element within which a matching element may be found. If no context is passed in then the context of the jQuery set will be used instead. + */ + closest(selectors: any, context?: Element): any[]; + + /** + * Get the children of each element in the set of matched elements, including text and comment nodes. + */ + contents(): JQuery; + + /** + * End the most recent filtering operation in the current chain and return the set of matched elements to its previous state. + */ + end(): JQuery; + + /** + * Reduce the set of matched elements to the one at the specified index. + * + * @param index An integer indicating the 0-based position of the element. OR An integer indicating the position of the element, counting backwards from the last element in the set. + * + */ + eq(index: number): JQuery; + + /** + * Reduce the set of matched elements to those that match the selector or pass the function's test. + * + * @param selector A string containing a selector expression to match the current set of elements against. + */ + filter(selector: string): JQuery; + /** + * Reduce the set of matched elements to those that match the selector or pass the function's test. + * + * @param func A function used as a test for each element in the set. this is the current DOM element. + */ + filter(func: (index: number, element: Element) => any): JQuery; + /** + * Reduce the set of matched elements to those that match the selector or pass the function's test. + * + * @param element An element to match the current set of elements against. + */ + filter(element: Element): JQuery; + /** + * Reduce the set of matched elements to those that match the selector or pass the function's test. + * + * @param obj An existing jQuery object to match the current set of elements against. + */ + filter(obj: JQuery): JQuery; + + /** + * Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element. + * + * @param selector A string containing a selector expression to match elements against. + */ + find(selector: string): JQuery; + /** + * Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element. + * + * @param element An element to match elements against. + */ + find(element: Element): JQuery; + /** + * Get the descendants of each element in the current set of matched elements, filtered by a selector, jQuery object, or element. + * + * @param obj A jQuery object to match elements against. + */ + find(obj: JQuery): JQuery; + + /** + * Reduce the set of matched elements to the first in the set. + */ + first(): JQuery; + + /** + * Reduce the set of matched elements to those that have a descendant that matches the selector or DOM element. + * + * @param selector A string containing a selector expression to match elements against. + */ + has(selector: string): JQuery; + /** + * Reduce the set of matched elements to those that have a descendant that matches the selector or DOM element. + * + * @param contained A DOM element to match elements against. + */ + has(contained: Element): JQuery; + + /** + * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments. + * + * @param selector A string containing a selector expression to match elements against. + */ + is(selector: string): boolean; + /** + * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments. + * + * @param func A function used as a test for the set of elements. It accepts one argument, index, which is the element's index in the jQuery collection.Within the function, this refers to the current DOM element. + */ + is(func: (index: number, element: Element) => boolean): boolean; + /** + * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments. + * + * @param obj An existing jQuery object to match the current set of elements against. + */ + is(obj: JQuery): boolean; + /** + * Check the current matched set of elements against a selector, element, or jQuery object and return true if at least one of these elements matches the given arguments. + * + * @param elements One or more elements to match the current set of elements against. + */ + is(elements: any): boolean; + + /** + * Reduce the set of matched elements to the final one in the set. + */ + last(): JQuery; + + /** + * Pass each element in the current matched set through a function, producing a new jQuery object containing the return values. + * + * @param callback A function object that will be invoked for each element in the current set. + */ + map(callback: (index: number, domElement: Element) => any): JQuery; + + /** + * Get the immediately following sibling of each element in the set of matched elements. If a selector is provided, it retrieves the next sibling only if it matches that selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + next(selector?: string): JQuery; + + /** + * Get all following siblings of each element in the set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + nextAll(selector?: string): JQuery; + + /** + * Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed. + * + * @param selector A string containing a selector expression to indicate where to stop matching following sibling elements. + * @param filter A string containing a selector expression to match elements against. + */ + nextUntil(selector?: string, filter?: string): JQuery; + /** + * Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed. + * + * @param element A DOM node or jQuery object indicating where to stop matching following sibling elements. + * @param filter A string containing a selector expression to match elements against. + */ + nextUntil(element?: Element, filter?: string): JQuery; + /** + * Get all following siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object passed. + * + * @param obj A DOM node or jQuery object indicating where to stop matching following sibling elements. + * @param filter A string containing a selector expression to match elements against. + */ + nextUntil(obj?: JQuery, filter?: string): JQuery; + + /** + * Remove elements from the set of matched elements. + * + * @param selector A string containing a selector expression to match elements against. + */ + not(selector: string): JQuery; + /** + * Remove elements from the set of matched elements. + * + * @param func A function used as a test for each element in the set. this is the current DOM element. + */ + not(func: (index: number, element: Element) => boolean): JQuery; + /** + * Remove elements from the set of matched elements. + * + * @param elements One or more DOM elements to remove from the matched set. + */ + not(...elements: Element[]): JQuery; + /** + * Remove elements from the set of matched elements. + * + * @param obj An existing jQuery object to match the current set of elements against. + */ + not(obj: JQuery): JQuery; + + /** + * Get the closest ancestor element that is positioned. + */ + offsetParent(): JQuery; + + /** + * Get the parent of each element in the current set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + parent(selector?: string): JQuery; + + /** + * Get the ancestors of each element in the current set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + parents(selector?: string): JQuery; + + /** + * Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or jQuery object. + * + * @param selector A string containing a selector expression to indicate where to stop matching ancestor elements. + * @param filter A string containing a selector expression to match elements against. + */ + parentsUntil(selector?: string, filter?: string): JQuery; + /** + * Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or jQuery object. + * + * @param element A DOM node or jQuery object indicating where to stop matching ancestor elements. + * @param filter A string containing a selector expression to match elements against. + */ + parentsUntil(element?: Element, filter?: string): JQuery; + /** + * Get the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector, DOM node, or jQuery object. + * + * @param obj A DOM node or jQuery object indicating where to stop matching ancestor elements. + * @param filter A string containing a selector expression to match elements against. + */ + parentsUntil(obj?: JQuery, filter?: string): JQuery; + + /** + * Get the immediately preceding sibling of each element in the set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + prev(selector?: string): JQuery; + + /** + * Get all preceding siblings of each element in the set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + prevAll(selector?: string): JQuery; + + /** + * Get all preceding siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object. + * + * @param selector A string containing a selector expression to indicate where to stop matching preceding sibling elements. + * @param filter A string containing a selector expression to match elements against. + */ + prevUntil(selector?: string, filter?: string): JQuery; + /** + * Get all preceding siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object. + * + * @param element A DOM node or jQuery object indicating where to stop matching preceding sibling elements. + * @param filter A string containing a selector expression to match elements against. + */ + prevUntil(element?: Element, filter?: string): JQuery; + /** + * Get all preceding siblings of each element up to but not including the element matched by the selector, DOM node, or jQuery object. + * + * @param obj A DOM node or jQuery object indicating where to stop matching preceding sibling elements. + * @param filter A string containing a selector expression to match elements against. + */ + prevUntil(obj?: JQuery, filter?: string): JQuery; + + /** + * Get the siblings of each element in the set of matched elements, optionally filtered by a selector. + * + * @param selector A string containing a selector expression to match elements against. + */ + siblings(selector?: string): JQuery; + + /** + * Reduce the set of matched elements to a subset specified by a range of indices. + * + * @param start An integer indicating the 0-based position at which the elements begin to be selected. If negative, it indicates an offset from the end of the set. + * @param end An integer indicating the 0-based position at which the elements stop being selected. If negative, it indicates an offset from the end of the set. If omitted, the range continues until the end of the set. + */ + slice(start: number, end?: number): JQuery; + + /** + * Show the queue of functions to be executed on the matched elements. + * + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + */ + queue(queueName?: string): any[]; + /** + * Manipulate the queue of functions to be executed, once for each matched element. + * + * @param newQueue An array of functions to replace the current queue contents. + */ + queue(newQueue: Function[]): JQuery; + /** + * Manipulate the queue of functions to be executed, once for each matched element. + * + * @param callback The new function to add to the queue, with a function to call that will dequeue the next item. + */ + queue(callback: Function): JQuery; + /** + * Manipulate the queue of functions to be executed, once for each matched element. + * + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + * @param newQueue An array of functions to replace the current queue contents. + */ + queue(queueName: string, newQueue: Function[]): JQuery; + /** + * Manipulate the queue of functions to be executed, once for each matched element. + * + * @param queueName A string containing the name of the queue. Defaults to fx, the standard effects queue. + * @param callback The new function to add to the queue, with a function to call that will dequeue the next item. + */ + queue(queueName: string, callback: Function): JQuery; +} +declare module "jquery" { + export = $; +} +declare var jQuery: JQueryStatic; +declare var $: JQueryStatic; diff --git a/public/app/headers/lodash/lodash.d.ts b/public/app/headers/lodash/lodash.d.ts new file mode 100644 index 00000000000..b6c95710bd9 --- /dev/null +++ b/public/app/headers/lodash/lodash.d.ts @@ -0,0 +1,8521 @@ +// Type definitions for Lo-Dash +// Project: http://lodash.com/ +// Definitions by: Brian Zengel , Ilya Mochalov +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare var _: _.LoDashStatic; + +declare module _ { + interface LoDashStatic { + /** + * Creates a lodash object which wraps the given value to enable intuitive method chaining. + * + * In addition to Lo-Dash methods, wrappers also have the following Array methods: + * concat, join, pop, push, reverse, shift, slice, sort, splice, and unshift + * + * Chaining is supported in custom builds as long as the value method is implicitly or + * explicitly included in the build. + * + * The chainable wrapper functions are: + * after, assign, bind, bindAll, bindKey, chain, chunk, compact, compose, concat, countBy, + * createCallback, curry, debounce, defaults, defer, delay, difference, filter, flatten, + * forEach, forEachRight, forIn, forInRight, forOwn, forOwnRight, functions, groupBy, + * indexBy, initial, intersection, invert, invoke, keys, map, max, memoize, merge, min, + * object, omit, once, pairs, partial, partialRight, pick, pluck, pull, push, range, reject, + * remove, rest, reverse, sample, shuffle, slice, sort, sortBy, splice, tap, throttle, times, + * toArray, transform, union, uniq, unshift, unzip, values, where, without, wrap, and zip + * + * The non-chainable wrapper functions are: + * clone, cloneDeep, contains, escape, every, find, findIndex, findKey, findLast, + * findLastIndex, findLastKey, has, identity, indexOf, isArguments, isArray, isBoolean, + * isDate, isElement, isEmpty, isEqual, isFinite, isFunction, isNaN, isNull, isNumber, + * isObject, isPlainObject, isRegExp, isString, isUndefined, join, lastIndexOf, mixin, + * noConflict, parseInt, pop, random, reduce, reduceRight, result, shift, size, some, + * sortedIndex, runInContext, template, unescape, uniqueId, and value + * + * The wrapper functions first and last return wrapped values when n is provided, otherwise + * they return unwrapped values. + * + * Explicit chaining can be enabled by using the _.chain method. + **/ + (value: number): LoDashWrapper; + (value: string): LoDashStringWrapper; + (value: boolean): LoDashWrapper; + (value: Array): LoDashNumberArrayWrapper; + (value: Array): LoDashArrayWrapper; + (value: T): LoDashObjectWrapper; + (value: any): LoDashWrapper; + + /** + * The semantic version number. + **/ + VERSION: string; + + /** + * An object used to flag environments features. + **/ + support: Support; + + /** + * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby + * (ERB). Change the following template settings to use alternative delimiters. + **/ + templateSettings: TemplateSettings; + } + + /** + * By default, the template delimiters used by Lo-Dash are similar to those in embedded Ruby + * (ERB). Change the following template settings to use alternative delimiters. + **/ + interface TemplateSettings { + /** + * The "escape" delimiter. + **/ + escape?: RegExp; + + /** + * The "evaluate" delimiter. + **/ + evaluate?: RegExp; + + /** + * An object to import into the template as local variables. + **/ + imports?: Dictionary; + + /** + * The "interpolate" delimiter. + **/ + interpolate?: RegExp; + + /** + * Used to reference the data object in the template text. + **/ + variable?: string; + } + + /** + * Creates a cache object to store key/value pairs. + */ + interface MapCache { + /** + * Removes `key` and its value from the cache. + * @param key The key of the value to remove. + * @return Returns `true` if the entry was removed successfully, else `false`. + */ + delete(key: string): boolean; + + /** + * Gets the cached value for `key`. + * @param key The key of the value to get. + * @return Returns the cached value. + */ + get(key: string): any; + + /** + * Checks if a cached value for `key` exists. + * @param key The key of the entry to check. + * @return Returns `true` if an entry for `key` exists, else `false`. + */ + has(key: string): boolean; + + /** + * Sets `value` to `key` of the cache. + * @param key The key of the value to cache. + * @param value The value to cache. + * @return Returns the cache object. + */ + set(key: string, value: any): _.Dictionary; + } + + /** + * An object used to flag environments features. + **/ + interface Support { + /** + * Detect if an arguments object's [[Class]] is resolvable (all but Firefox < 4, IE < 9). + **/ + argsClass: boolean; + + /** + * Detect if arguments objects are Object objects (all but Narwhal and Opera < 10.5). + **/ + argsObject: boolean; + + /** + * Detect if name or message properties of Error.prototype are enumerable by default. + * (IE < 9, Safari < 5.1) + **/ + enumErrorProps: boolean; + + /** + * Detect if prototype properties are enumerable by default. + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 (if the prototype or a property on the + * prototype has been set) incorrectly set the [[Enumerable]] value of a function’s prototype property to true. + **/ + enumPrototypes: boolean; + + /** + * Detect if Function#bind exists and is inferred to be fast (all but V8). + **/ + fastBind: boolean; + + /** + * Detect if functions can be decompiled by Function#toString (all but PS3 and older Opera + * mobile browsers & avoided in Windows 8 apps). + **/ + funcDecomp: boolean; + + /** + * Detect if Function#name is supported (all but IE). + **/ + funcNames: boolean; + + /** + * Detect if arguments object indexes are non-enumerable (Firefox < 4, IE < 9, PhantomJS, + * Safari < 5.1). + **/ + nonEnumArgs: boolean; + + /** + * Detect if properties shadowing those on Object.prototype are non-enumerable. + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are made + * non-enumerable as well (a.k.a the JScript [[DontEnum]] bug). + **/ + nonEnumShadows: boolean; + + /** + * Detect if own properties are iterated after inherited properties (all but IE < 9). + **/ + ownLast: boolean; + + /** + * Detect if Array#shift and Array#splice augment array-like objects correctly. + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array shift() and splice() + * functions that fail to remove the last element, value[0], of array-like objects even + * though the length property is set to 0. The shift() method is buggy in IE 8 compatibility + * mode, while splice() is buggy regardless of mode in IE < 9 and buggy in compatibility mode + * in IE 9. + **/ + spliceObjects: boolean; + + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index and IE 8 can only access characters by index on + * string literals. + **/ + unindexedChars: boolean; + } + + interface LoDashWrapperBase { + /** + * Produces the toString result of the wrapped value. + * @return Returns the string result. + **/ + toString(): string; + + /** + * Executes the chained sequence to extract the unwrapped value. + * @return Returns the resolved unwrapped value. + **/ + value(): T; + + /** + * @see _.value + **/ + run(): T; + + /** + * @see _.value + **/ + toJSON(): T; + + /** + * @see _.value + **/ + valueOf(): T; + + /** + * @see _.toPlainObject + */ + toPlainObject(): Object; + } + + interface LoDashWrapper extends LoDashWrapperBase> { } + + interface LoDashStringWrapper extends LoDashWrapper { } + + interface LoDashObjectWrapper extends LoDashWrapperBase> { } + + interface LoDashArrayWrapper extends LoDashWrapperBase> { + concat(...items: Array>): LoDashArrayWrapper; + join(seperator?: string): string; + pop(): T; + push(...items: T[]): LoDashArrayWrapper; + reverse(): LoDashArrayWrapper; + shift(): T; + slice(start: number, end?: number): LoDashArrayWrapper; + sort(compareFn?: (a: T, b: T) => number): LoDashArrayWrapper; + splice(start: number): LoDashArrayWrapper; + splice(start: number, deleteCount: number, ...items: any[]): LoDashArrayWrapper; + unshift(...items: T[]): LoDashArrayWrapper; + } + + interface LoDashNumberArrayWrapper extends LoDashArrayWrapper { } + + //_.chain + interface LoDashStatic { + /** + * Creates a lodash object that wraps the given value with explicit method chaining enabled. + * @param value The value to wrap. + * @return The wrapper object. + **/ + chain(value: number): LoDashWrapper; + chain(value: string): LoDashWrapper; + chain(value: boolean): LoDashWrapper; + chain(value: Array): LoDashArrayWrapper; + chain(value: T): LoDashObjectWrapper; + chain(value: any): LoDashWrapper; + } + + interface LoDashWrapperBase { + /** + * Enables explicit method chaining on the wrapper object. + * @see _.chain + * @return The wrapper object. + **/ + chain(): TWrapper; + } + + //_.tap + interface LoDashStatic { + /** + * Invokes interceptor with the value as the first argument and then returns value. The + * purpose of this method is to "tap into" a method chain in order to perform operations on + * intermediate results within the chain. + * @param value The value to provide to interceptor + * @param interceptor The function to invoke. + * @return value + **/ + tap( + value: T, + interceptor: (value: T) => void): T; + } + + interface LoDashWrapperBase { + /** + * @see _.tap + **/ + tap(interceptor: (value: T) => void): TWrapper; + } + + /********* + * Arrays * + **********/ + + //_.chunk + interface LoDashStatic { + /** + * Creates an array of elements split into groups the length of size. If collection can't be + * split evenly, the final chunk will be the remaining elements. + * @param array The array to process. + * @param size The length of each chunk. + * @return Returns the new array containing chunks. + **/ + chunk(array: Array, size?: number): T[][]; + + /** + * @see _.chunk + **/ + chunk(array: List, size?: number): T[][]; + } + + interface LoDashArrayWrapper { + /** + * @see _.chunk + **/ + chunk(size?: number): LoDashArrayWrapper; + } + + //_.compact + interface LoDashStatic { + /** + * Returns a copy of the array with all falsy values removed. In JavaScript, false, null, 0, "", + * undefined and NaN are all falsy. + * @param array Array to compact. + * @return (Array) Returns a new array of filtered values. + **/ + compact(array?: Array): T[]; + + /** + * @see _.compact + **/ + compact(array?: List): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.compact + **/ + compact(): LoDashArrayWrapper; + } + + //_.difference + interface LoDashStatic { + /** + * Creates an array excluding all values of the provided arrays using strict equality for comparisons + * , i.e. ===. + * @param array The array to process + * @param others The arrays of values to exclude. + * @return Returns a new array of filtered values. + **/ + difference( + array?: Array, + ...others: Array[]): T[]; + /** + * @see _.difference + **/ + difference( + array?: List, + ...others: List[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.difference + **/ + difference( + ...others: Array[]): LoDashArrayWrapper; + /** + * @see _.difference + **/ + difference( + ...others: List[]): LoDashArrayWrapper; + } + + //_.findIndex + interface LoDashStatic { + /** + * This method is like _.find except that it returns the index of the first element that passes + * the callback check, instead of the element itself. + * @param array The array to search. + * @param {(Function|Object|string)} callback The function called per iteration. If a property name or object is provided it will be + * used to create a ".pluck" or ".where" style callback, respectively. + * @param thisArg The this binding of callback. + * @return Returns the index of the found element, else -1. + **/ + findIndex( + array: Array, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: List, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: Array, + pluckValue: string): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: List, + pluckValue: string): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: Array, + whereDictionary: W): number; + + /** + * @see _.findIndex + **/ + findIndex( + array: List, + whereDictionary: W): number; + } + + //_.findLastIndex + interface LoDashStatic { + /** + * This method is like _.findIndex except that it iterates over elements of a collection from right to left. + * @param array The array to search. + * @param {(Function|Object|string)} callback The function called per iteration. If a property name or object is provided it will be + * used to create a ".pluck" or ".where" style callback, respectively. + * @param thisArg The this binding of callback. + * @return Returns the index of the found element, else -1. + **/ + findLastIndex( + array: Array, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: List, + callback: ListIterator, + thisArg?: any): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: Array, + pluckValue: string): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: List, + pluckValue: string): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: Array, + whereDictionary: Dictionary): number; + + /** + * @see _.findLastIndex + **/ + findLastIndex( + array: List, + whereDictionary: Dictionary): number; + } + + //_.first + interface LoDashStatic { + /** + * Gets the first element or first n elements of an array. If a callback is provided + * elements at the beginning of the array are returned as long as the callback returns + * truey. The callback is bound to thisArg and invoked with three arguments; (value, + * index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback + * will return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return ] + * true for elements that have the properties of the given object, else false. + * @param array Retrieves the first element of this array. + * @return Returns the first element of `array`. + **/ + first(array?: Array): T; + + /** + * @see _.first + **/ + first(array?: List): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + first( + array: Array, + n: number): T[]; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + first( + array: List, + n: number): T[]; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + first( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + first( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + first( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + first( + array: List, + pluckValue: string): T[]; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + first( + array: Array, + whereValue: W): T[]; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + first( + array: List, + whereValue: W): T[]; + + /** + * @see _.first + **/ + head(array: Array): T; + + /** + * @see _.first + **/ + head(array: List): T; + + /** + * @see _.first + **/ + head( + array: Array, + n: number): T[]; + + /** + * @see _.first + **/ + head( + array: List, + n: number): T[]; + + /** + * @see _.first + **/ + head( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + **/ + head( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.first + **/ + head( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.first + **/ + head( + array: List, + pluckValue: string): T[]; + + /** + * @see _.first + **/ + head( + array: Array, + whereValue: W): T[]; + + /** + * @see _.first + **/ + head( + array: List, + whereValue: W): T[]; + + /** + * @see _.first + **/ + take(array: Array): T[]; + + /** + * @see _.first + **/ + take(array: List): T[]; + + /** + * @see _.first + **/ + take( + array: Array, + n: number): T[]; + + /** + * @see _.first + **/ + take( + array: List, + n: number): T[]; + + /** + * Takes the first items from an array or list based on a predicate + * @param array The array or list of items on which the result set will be based + * @param predicate A predicate function to determine whether a value will be taken. Optional; defaults to identity. + * @param [thisArg] The this binding of predicate. + */ + takeWhile( + array: (Array|List), + predicate?: ListIterator, + thisArg?: any + ): T[]; + + /** + * Takes the first items from an array or list based on a predicate + * @param array The array or list of items on which the result set will be based + * @param pluckValue Uses a _.property style callback to return the property value of the given element + */ + takeWhile( + array: (Array|List), + pluckValue: string + ): any[]; + + /** + * Takes the first items from an array or list based on a predicate + * @param array The array or list of items on which the result set will be based + * @param whereValue Uses a _.matches style callback to return the first elements that match the given value + */ + takeWhile( + array: (Array|List), + whereValue: W + ): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.first + **/ + first(): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + first(n: number): LoDashArrayWrapper; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + first( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + first(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + first(whereValue: W): LoDashArrayWrapper; + + /** + * @see _.first + **/ + head(): T; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + head(n: number): LoDashArrayWrapper; + + /** + * @see _.first + * @param callback The function called per element. + * @param [thisArg] The this binding of callback. + **/ + head( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.first + * @param pluckValue "_.pluck" style callback value + **/ + head(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.first + * @param whereValue "_.where" style callback value + **/ + head(whereValue: W): LoDashArrayWrapper; + + /** + * @see _.first + **/ + take(): LoDashArrayWrapper; + + /** + * @see _.first + * @param n The number of elements to return. + **/ + take(n: number): LoDashArrayWrapper; + + /** + * Takes the first items based on a predicate + * @param predicate The function called per element. + * @param [thisArg] The this binding of callback. + **/ + takeWhile( + predicate: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * Takes the first items based on a predicate + * @param pluckValue Uses a _.property style callback to return the property value of the given element + **/ + takeWhile( + pluckValue: string): LoDashArrayWrapper; + + /** + * Takes the first items based on a predicate + * @param whereValue Uses a _.matches style callback to return the first elements that match the given value + **/ + takeWhile( + whereValue: W): LoDashArrayWrapper; + + } + + interface MaybeNestedList extends List> { } + interface RecursiveList extends List> { } + + //_.flatten + interface LoDashStatic { + /** + * Flattens a nested array a single level. + * + * _.flatten(x) is equivalent to _.flatten(x, false); + * + * @param array The array to flatten. + * @return `array` flattened. + **/ + flatten(array: MaybeNestedList): T[]; + + /** + * Flattens a nested array. If isDeep is true the array is recursively flattened, otherwise it is only + * flattened a single level. + * + * If you know whether or not this should be recursively at compile time, you typically want to use a + * version without a boolean parameter (i.e. `_.flatten(x)` or `_.flattenDeep(x)`). + * + * @param array The array to flatten. + * @param deep Specify a deep flatten. + * @return `array` flattened. + **/ + flatten(array: RecursiveList, isDeep: boolean): List | RecursiveList; + + /** + * Recursively flattens a nested array. + * + * _.flattenDeep(x) is equivalent to _.flatten(x, true); + * + * @param array The array to flatten + * @return `array` recursively flattened + */ + flattenDeep(array: RecursiveList): List + } + + interface LoDashArrayWrapper { + /** + * @see _.flatten + **/ + flatten(): LoDashArrayWrapper; + + /** + * @see _.flatten + **/ + flatten(isShallow: boolean): LoDashArrayWrapper; + + /** + * @see _.flattenDeep + */ + flattenDeep(): LoDashArrayWrapper; + } + + //_.indexOf + interface LoDashStatic { + /** + * Gets the index at which the first occurrence of value is found using strict equality + * for comparisons, i.e. ===. If the array is already sorted providing true for fromIndex + * will run a faster binary search. + * @param array The array to search. + * @param value The value to search for. + * @param fromIndex The index to search from. + * @return The index of `value` within `array`. + **/ + indexOf( + array: Array, + value: T): number; + + /** + * @see _.indexOf + **/ + indexOf( + array: List, + value: T): number; + + /** + * @see _.indexOf + * @param fromIndex The index to search from + **/ + indexOf( + array: Array, + value: T, + fromIndex: number): number; + + /** + * @see _.indexOf + * @param fromIndex The index to search from + **/ + indexOf( + array: List, + value: T, + fromIndex: number): number; + + /** + * @see _.indexOf + * @param isSorted True to perform a binary search on a sorted array. + **/ + indexOf( + array: Array, + value: T, + isSorted: boolean): number; + + /** + * @see _.indexOf + * @param isSorted True to perform a binary search on a sorted array. + **/ + indexOf( + array: List, + value: T, + isSorted: boolean): number; + } + + //_.initial + interface LoDashStatic { + /** + * Gets all but the last element or last n elements of an array. If a callback is provided + * elements at the end of the array are excluded from the result as long as the callback + * returns truey. The callback is bound to thisArg and invoked with three arguments; + * (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The array to query. + * @param n Leaves this many elements behind, optional. + * @return Returns everything but the last `n` elements of `array`. + **/ + initial( + array: Array): T[]; + + /** + * @see _.initial + **/ + initial( + array: List): T[]; + + /** + * @see _.initial + * @param n The number of elements to exclude. + **/ + initial( + array: Array, + n: number): T[]; + + /** + * @see _.initial + * @param n The number of elements to exclude. + **/ + initial( + array: List, + n: number): T[]; + + /** + * @see _.initial + * @param callback The function called per element + **/ + initial( + array: Array, + callback: ListIterator): T[]; + + /** + * @see _.initial + * @param callback The function called per element + **/ + initial( + array: List, + callback: ListIterator): T[]; + + /** + * @see _.initial + * @param pluckValue _.pluck style callback + **/ + initial( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.initial + * @param pluckValue _.pluck style callback + **/ + initial( + array: List, + pluckValue: string): T[]; + + /** + * @see _.initial + * @param whereValue _.where style callback + **/ + initial( + array: Array, + whereValue: W): T[]; + + /** + * @see _.initial + * @param whereValue _.where style callback + **/ + initial( + array: List, + whereValue: W): T[]; + } + + //_.intersection + interface LoDashStatic { + /** + * Creates an array of unique values present in all provided arrays using strict + * equality for comparisons, i.e. ===. + * @param arrays The arrays to inspect. + * @return Returns an array of composite values. + **/ + intersection(...arrays: Array[]): T[]; + + /** + * @see _.intersection + **/ + intersection(...arrays: List[]): T[]; + } + + //_.last + interface LoDashStatic { + /** + * Gets the last element of an array. + * @param array The array to query. + * @return Returns the last element of array. + **/ + last(array: Array): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.last + **/ + last(): T; + } + + //_.lastIndexOf + interface LoDashStatic { + /** + * Gets the index at which the last occurrence of value is found using strict equality + * for comparisons, i.e. ===. If fromIndex is negative, it is used as the offset from the + * end of the collection. + * @param array The array to search. + * @param value The value to search for. + * @param fromIndex The index to search from. + * @return The index of the matched value or -1. + **/ + lastIndexOf( + array: Array, + value: T, + fromIndex?: number): number; + + /** + * @see _.lastIndexOf + **/ + lastIndexOf( + array: List, + value: T, + fromIndex?: number): number; + } + + //_.pull + interface LoDashStatic { + /** + * Removes all provided values from the given array using strict equality for comparisons, + * i.e. ===. + * @param array The array to modify. + * @param values The values to remove. + * @return array. + **/ + pull( + array: Array, + ...values: T[]): T[]; + + /** + * @see _.pull + **/ + pull( + array: List, + ...values: T[]): T[]; + } + + interface LoDashStatic { + /** + * Removes all provided values from the given array using strict equality for comparisons, + * i.e. ===. + * @param array The array to modify. + * @param values The values to remove. + * @return array. + **/ + pullAt( + array: Array, + ...values: any[]): any[]; + + /** + * @see _.pull + **/ + pullAt( + array: List, + ...values: any[]): any[]; + } + + //_.remove + interface LoDashStatic { + /** + * Removes all elements from an array that the callback returns truey for and returns + * an array of removed elements. The callback is bound to thisArg and invoked with three + * arguments; (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The array to modify. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of removed elements. + **/ + remove( + array: Array, + callback?: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.remove + **/ + remove( + array: List, + callback?: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.remove + * @param pluckValue _.pluck style callback + **/ + remove( + array: Array, + pluckValue?: string): T[]; + + /** + * @see _.remove + * @param pluckValue _.pluck style callback + **/ + remove( + array: List, + pluckValue?: string): T[]; + + /** + * @see _.remove + * @param whereValue _.where style callback + **/ + remove( + array: Array, + wherealue?: Dictionary): T[]; + + /** + * @see _.remove + * @param whereValue _.where style callback + **/ + remove( + array: List, + wherealue?: Dictionary): T[]; + + /** + * @see _.remove + * @param item The item to remove + **/ + remove( + array:Array, + item:T): T[]; + } + + //_.rest + interface LoDashStatic { + /** + * The opposite of _.initial this method gets all but the first element or first n elements of + * an array. If a callback function is provided elements at the beginning of the array are excluded + * from the result as long as the callback returns truey. The callback is bound to thisArg and + * invoked with three arguments; (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true + * for elements that have the properties of the given object, else false. + * @param array The array to query. + * @param {(Function|Object|number|string)} [callback=1] The function called per element or the number + * of elements to exclude. If a property name or object is provided it will be used to create a + * ".pluck" or ".where" style callback, respectively. + * @param {*} [thisArg] The this binding of callback. + * @return Returns a slice of array. + **/ + rest(array: Array): T[]; + + /** + * @see _.rest + **/ + rest(array: List): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + n: number): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + n: number): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + rest( + array: Array, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + rest( + array: List, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + drop(array: Array): T[]; + + /** + * @see _.rest + **/ + drop(array: List): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + n: number): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + n: number): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + drop( + array: Array, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + drop( + array: List, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + tail(array: Array): T[]; + + /** + * @see _.rest + **/ + tail(array: List): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + n: number): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + n: number): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + pluckValue: string): T[]; + + /** + * @see _.rest + **/ + tail( + array: Array, + whereValue: W): T[]; + + /** + * @see _.rest + **/ + tail( + array: List, + whereValue: W): T[]; + } + + //_.sortedIndex + interface LoDashStatic { + /** + * Uses a binary search to determine the smallest index at which a value should be inserted + * into a given sorted array in order to maintain the sort order of the array. If a callback + * is provided it will be executed for value and each element of array to compute their sort + * ranking. The callback is bound to thisArg and invoked with one argument; (value). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array The sorted list. + * @param value The value to determine its index within `list`. + * @param callback Iterator to compute the sort ranking of each value, optional. + * @return The index at which value should be inserted into array. + **/ + sortedIndex( + array: Array, + value: T, + callback?: (x: T) => TSort, + thisArg?: any): number; + + /** + * @see _.sortedIndex + **/ + sortedIndex( + array: List, + value: T, + callback?: (x: T) => TSort, + thisArg?: any): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.pluck style callback + **/ + sortedIndex( + array: Array, + value: T, + pluckValue: string): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.pluck style callback + **/ + sortedIndex( + array: List, + value: T, + pluckValue: string): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.where style callback + **/ + sortedIndex( + array: Array, + value: T, + whereValue: W): number; + + /** + * @see _.sortedIndex + * @param pluckValue the _.where style callback + **/ + sortedIndex( + array: List, + value: T, + whereValue: W): number; + } + + //_.union + interface LoDashStatic { + /** + * Creates an array of unique values, in order, of the provided arrays using strict + * equality for comparisons, i.e. ===. + * @param arrays The arrays to inspect. + * @return Returns an array of composite values. + **/ + union(...arrays: Array[]): T[]; + + /** + * @see _.union + **/ + union(...arrays: List[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.union + **/ + union(...arrays: (Array|List)[]): LoDashArrayWrapper; + } + + //_.uniq + interface LoDashStatic { + /** + * Creates a duplicate-value-free version of an array using strict equality for comparisons, + * i.e. ===. If the array is sorted, providing true for isSorted will use a faster algorithm. + * If a callback is provided each element of array is passed through the callback before + * uniqueness is computed. The callback is bound to thisArg and invoked with three arguments; + * (value, index, array). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param array Array to remove duplicates from. + * @param isSorted True if `array` is already sorted, optiona, default = false. + * @param iterator Transform the elements of `array` before comparisons for uniqueness. + * @param context 'this' object in `iterator`, optional. + * @return Copy of `array` where all elements are unique. + **/ + uniq(array: Array, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + uniq(array: List, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: Array, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: List, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + uniq( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: Array, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: List, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + array: List, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: Array, + isSorted: boolean, + whereValue: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: List, + isSorted: boolean, + whereValue: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: Array, + whereValue: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + array: List, + whereValue: W): T[]; + + /** + * @see _.uniq + **/ + unique(array: Array, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + unique(array: List, isSorted?: boolean): T[]; + + /** + * @see _.uniq + **/ + unique( + array: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + unique( + array: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + unique( + array: Array, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + **/ + unique( + array: List, + isSorted: boolean, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: Array, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: List, + isSorted: boolean, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: Array, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + array: List, + pluckValue: string): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: Array, + whereValue?: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: List, + whereValue?: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: Array, + isSorted: boolean, + whereValue?: W): T[]; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + array: List, + isSorted: boolean, + whereValue?: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.uniq + **/ + uniq(isSorted?: boolean): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + uniq( + isSorted: boolean, + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + uniq( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq( + isSorted: boolean, + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + uniq(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + isSorted: boolean, + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + uniq( + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + unique(isSorted?: boolean): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + unique( + isSorted: boolean, + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + **/ + unique( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique( + isSorted: boolean, + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param pluckValue _.pluck style callback + **/ + unique(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + isSorted: boolean, + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.uniq + * @param whereValue _.where style callback + **/ + unique( + whereValue: W): LoDashArrayWrapper; + } + + //_.without + interface LoDashStatic { + /** + * Creates an array excluding all provided values using strict equality for comparisons, i.e. ===. + * @param array The array to filter. + * @param values The value(s) to exclude. + * @return A new array of filtered values. + **/ + without( + array: Array, + ...values: T[]): T[]; + + /** + * @see _.without + **/ + without( + array: List, + ...values: T[]): T[]; + } + + //_.xor + interface LoDashStatic { + /** + * Creates an array of unique values that is the symmetric difference of the provided arrays. + * @param arrays The arrays to inspect. + * @return Returns the new array of values. + */ + xor(...arrays: List[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.xor + */ + xor(...arrays: T[][]): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.xor + */ + xor(...arrays: T[]): LoDashObjectWrapper; + } + + //_.zip + interface LoDashStatic { + /** + * Creates an array of grouped elements, the first of which contains the first + * elements of the given arrays, the second of which contains the second elements + * of the given arrays, and so on. + * @param arrays Arrays to process. + * @return A new array of grouped elements. + **/ + zip(...arrays: any[][]): any[][]; + + /** + * @see _.zip + **/ + zip(...arrays: any[]): any[]; + + /** + * @see _.zip + **/ + unzip(...arrays: any[][]): any[][]; + + /** + * @see _.zip + **/ + unzip(...arrays: any[]): any[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.zip + **/ + zip(...arrays: any[][]): _.LoDashArrayWrapper; + + /** + * @see _.zip + **/ + unzip(...arrays: any[]): _.LoDashArrayWrapper; + } + + //_.zipObject + interface LoDashStatic { + /** + * The inverse of _.pairs; this method returns an object composed from arrays of property + * names and values. Provide either a single two dimensional array, e.g. [[key1, value1], + * [key2, value2]] or two arrays, one of property names and one of corresponding values. + * @param props The property names. + * @param values The property values. + * @return Returns the new object. + **/ + zipObject( + props: List, + values?: List): TResult; + + /** + * @see _.zipObject + **/ + zipObject(props: List>): Dictionary; + + /** + * @see _.zipObject + **/ + object( + props: List, + values?: List): TResult; + + /** + * @see _.zipObject + **/ + object(props: List>): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.zipObject + **/ + zipObject(values?: List): _.LoDashObjectWrapper>; + + /** + * @see _.zipObject + **/ + object(values?: List): _.LoDashObjectWrapper>; + } + + //_.zipWith + interface LoDashStatic { + /** + * This method is like _.zip except that it accepts an iteratee to specify how grouped values should be + * combined. The iteratee is bound to thisArg and invoked with four arguments: (accumulator, value, index, + * group). + * @param {...Array} [arrays] The arrays to process. + * @param {Function} [iteratee] The function to combine grouped values. + * @param {*} [thisArg] The `this` binding of `iteratee`. + * @return Returns the new array of grouped elements. + */ + zipWith(...args: any[]): TResult[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.zipWith + */ + zipWith(...args: any[]): LoDashArrayWrapper; + } + + /********* + * Chain * + *********/ + + //_.thru + interface LoDashStatic { + /** + * This method is like _.tap except that it returns the result of interceptor. + * @param value The value to provide to interceptor. + * @param interceptor The function to invoke. + * @param thisArg The this binding of interceptor. + * @return Returns the result of interceptor. + */ + thru( + value: T, + interceptor: (value: T) => TResult, + thisArg?: any): TResult; + } + + interface LoDashWrapperBase { + /** + * @see _.thru + */ + thru( + interceptor: (value: T) => TResult, + thisArg?: any): LoDashWrapper; + + /** + * @see _.thru + */ + thru( + interceptor: (value: T) => TResult, + thisArg?: any): LoDashWrapper; + + /** + * @see _.thru + */ + thru( + interceptor: (value: T) => TResult, + thisArg?: any): LoDashWrapper; + + /** + * @see _.thru + */ + thru( + interceptor: (value: T) => TResult, + thisArg?: any): LoDashObjectWrapper; + + /** + * @see _.thru + */ + thru( + interceptor: (value: T) => TResult[], + thisArg?: any): LoDashArrayWrapper; + } + + /************** + * Collection * + **************/ + + //_.at + interface LoDashStatic { + /** + * Creates an array of elements corresponding to the given keys, or indexes, of collection. Keys may be + * specified as individual arguments or as arrays of keys. + * + * @param collection The collection to iterate over. + * @param props The property names or indexes of elements to pick, specified individually or in arrays. + * @return Returns the new array of picked elements. + */ + at( + collection: List|Dictionary, + ...props: Array> + ): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.at + */ + at(...props: Array>): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.at + */ + at(...props: Array>): LoDashArrayWrapper; + } + + //_.contains + interface LoDashStatic { + /** + * Checks if a given value is present in a collection using strict equality for comparisons, + * i.e. ===. If fromIndex is negative, it is used as the offset from the end of the collection. + * @param collection The collection to iterate over. + * @param target The value to check for. + * @param fromIndex The index to search from. + * @return True if the target element is found, else false. + **/ + contains( + collection: Array, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + contains( + collection: List, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + * @param dictionary The dictionary to iterate over. + * @param value The value in the dictionary to search for. + **/ + contains( + dictionary: Dictionary, + value: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + * @param searchString the string to search + * @param targetString the string to search for + **/ + contains( + searchString: string, + targetString: string, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + collection: Array, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + collection: List, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + dictionary: Dictionary, + value: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include( + searchString: string, + targetString: string, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes( + collection: Array, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes( + collection: List, + target: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes( + dictionary: Dictionary, + value: T, + fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes( + searchString: string, + targetString: string, + fromIndex?: number): boolean; + } + + interface LoDashArrayWrapper { + /** + * @see _.contains + **/ + contains(target: T, fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include(target: T, fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes(target: T, fromIndex?: number): boolean; + } + + interface LoDashObjectWrapper { + /** + * @see _.contains + **/ + contains(target: TValue, fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include(target: TValue, fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes(target: TValue, fromIndex?: number): boolean; + } + + interface LoDashStringWrapper { + /** + * @see _.contains + **/ + contains(target: string, fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + include(target: string, fromIndex?: number): boolean; + + /** + * @see _.contains + **/ + includes(target: string, fromIndex?: number): boolean; + } + + //_.countBy + interface LoDashStatic { + /** + * Creates an object composed of keys generated from the results of running each element + * of collection through the callback. The corresponding value of each key is the number + * of times the key was returned by the callback. The callback is bound to thisArg and + * invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the composed aggregate object. + **/ + countBy( + collection: Array, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: List, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: Array, + callback: string, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: List, + callback: string, + thisArg?: any): Dictionary; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + collection: Dictionary, + callback: string, + thisArg?: any): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.countBy + **/ + countBy( + callback?: ListIterator, + thisArg?: any): LoDashObjectWrapper>; + + /** + * @see _.countBy + * @param callback Function name + **/ + countBy( + callback: string, + thisArg?: any): LoDashObjectWrapper>; + } + + //_.every + interface LoDashStatic { + /** + * Checks if the given callback returns truey value for all elements of a collection. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, + * collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return True if all elements passed the callback check, else false. + **/ + every( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + every( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + every( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + every( + collection: List, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + every( + collection: Dictionary, + whereValue: W): boolean; + + /** + * @see _.every + **/ + all( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + **/ + all( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.every + **/ + all( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + all( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + all( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.every + * @param pluckValue _.pluck style callback + **/ + all( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + all( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + all( + collection: List, + whereValue: W): boolean; + + /** + * @see _.every + * @param whereValue _.where style callback + **/ + all( + collection: Dictionary, + whereValue: W): boolean; + } + + //_.fill + interface LoDashStatic { + /** + * Fills elements of array with value from start up to, but not including, end. + * + * Note: This method mutates array. + * + * @param array (Array): The array to fill. + * @param value (*): The value to fill array with. + * @param [start=0] (number): The start position. + * @param [end=array.length] (number): The end position. + * @return (Array): Returns array. + */ + fill( + array: any[], + value: any, + start?: number, + end?: number): TResult[]; + + /** + * @see _.fill + */ + fill( + array: List, + value: any, + start?: number, + end?: number): List; + } + + interface LoDashArrayWrapper { + /** + * @see _.fill + */ + fill( + value: TResult, + start?: number, + end?: number): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.fill + */ + fill( + value: TResult, + start?: number, + end?: number): LoDashObjectWrapper>; + } + + //_.filter + interface LoDashStatic { + /** + * Iterates over elements of a collection, returning an array of all elements the + * identity function returns truey for. + * + * @param collection The collection to iterate over. + * @return Returns a new array of elements that passed the callback check. + **/ + filter( + collection: (Array|List)): T[]; + + /** + * Iterates over elements of a collection, returning an array of all elements the + * callback returns truey for. The callback is bound to thisArg and invoked with three + * arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param context The this binding of callback. + * @return Returns a new array of elements that passed the callback check. + **/ + filter( + collection: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + filter( + collection: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + filter( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Dictionary, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: List, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + collection: Dictionary, + whereValue: W): T[]; + + /** + * @see _.filter + **/ + select( + collection: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + select( + collection: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + **/ + select( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Dictionary, + pluckValue: string): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: List, + whereValue: W): T[]; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + collection: Dictionary, + whereValue: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.filter + **/ + filter(): LoDashArrayWrapper; + + /** + * @see _.filter + **/ + filter( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + filter( + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.filter + **/ + select( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.filter + * @param pluckValue _.pluck style callback + **/ + select( + whereValue: W): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.filter + **/ + filter( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + } + + //_.find + interface LoDashStatic { + /** + * Iterates over elements of collection, returning the first element predicate returns + * truthy for. The predicate is bound to thisArg and invoked with three arguments: + * (value, index|key, collection). + * + * If a property name is provided for predicate the created _.property style callback + * returns the property value of the given element. + * + * If a value is also provided for thisArg the created _.matchesProperty style callback + * returns true for elements that have a matching property value, else false. + * + * If an object is provided for predicate the created _.matches style callback returns + * true for elements that have the properties of the given object, else false. + * + * @param collection Searches for a value in this list. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The found element, else undefined. + **/ + find( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * Alias of _.find + * @see _.find + **/ + detect( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + find( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * Alias of _.find + * @see _.find + **/ + detect( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + find( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T; + + /** + * Alias of _.find + * @see _.find + **/ + detect( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.matches style callback + **/ + find( + collection: Array|List|Dictionary, + whereValue: W): T; + + /** + * Alias of _.find + * @see _.find + * @param _.matches style callback + **/ + detect( + collection: Array|List|Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.matchesProperty style callback + **/ + find( + collection: Array|List|Dictionary, + path: string, + srcValue: any): T; + + /** + * Alias of _.find + * @see _.find + * @param _.matchesProperty style callback + **/ + detect( + collection: Array|List|Dictionary, + path: string, + srcValue: any): T; + + /** + * @see _.find + * @param _.property style callback + **/ + find( + collection: Array|List|Dictionary, + pluckValue: string): T; + + /** + * Alias of _.find + * @see _.find + * @param _.property style callback + **/ + detect( + collection: Array|List|Dictionary, + pluckValue: string): T; + + /** + * @see _.find + **/ + findWhere( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findWhere( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findWhere( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.matches style callback + **/ + findWhere( + collection: Array, + whereValue: W): T; + + /** + * @see _.find + * @param _.matches style callback + **/ + findWhere( + collection: List, + whereValue: W): T; + + /** + * @see _.find + * @param _.matches style callback + **/ + findWhere( + collection: Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.property style callback + **/ + findWhere( + collection: Array, + pluckValue: string): T; + + /** + * @see _.find + * @param _.property style callback + **/ + findWhere( + collection: List, + pluckValue: string): T; + + /** + * @see _.find + * @param _.property style callback + **/ + findWhere( + collection: Dictionary, + pluckValue: string): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.find + */ + find( + callback: ListIterator, + thisArg?: any): T; + /** + * @see _.find + * @param _.matches style callback + */ + find( + whereValue: W): T; + /** + * @see _.find + * @param _.matchesProperty style callback + */ + find( + path: string, + srcValue: any): T; + /** + * @see _.find + * @param _.property style callback + */ + find( + pluckValue: string): T; + } + + //_.findLast + interface LoDashStatic { + /** + * This method is like _.find except that it iterates over elements of a collection from + * right to left. + * @param collection Searches for a value in this list. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The found element, else undefined. + **/ + findLast( + collection: Array, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findLast( + collection: List, + callback: ListIterator, + thisArg?: any): T; + + /** + * @see _.find + **/ + findLast( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findLast( + collection: Array, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findLast( + collection: List, + whereValue: W): T; + + /** + * @see _.find + * @param _.pluck style callback + **/ + findLast( + collection: Dictionary, + whereValue: W): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findLast( + collection: Array, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findLast( + collection: List, + pluckValue: string): T; + + /** + * @see _.find + * @param _.where style callback + **/ + findLast( + collection: Dictionary, + pluckValue: string): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.findLast + */ + findLast( + callback: ListIterator, + thisArg?: any): T; + /** + * @see _.findLast + * @param _.where style callback + */ + findLast( + whereValue: W): T; + + /** + * @see _.findLast + * @param _.where style callback + */ + findLast( + pluckValue: string): T; + } + + //_.forEach + interface LoDashStatic { + /** + * Iterates over elements of a collection, executing the callback for each element. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, + * collection). Callbacks may exit iteration early by explicitly returning false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + forEach( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEach + **/ + forEach( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEach + **/ + forEach( + object: Dictionary, + callback: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.each + **/ + forEach( + object: T, + callback: ObjectIterator, + thisArg?: any): T + + /** + * @see _.forEach + **/ + each( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEach + **/ + each( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEach + * @param object The object to iterate over + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + each( + object: Dictionary, + callback: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.each + **/ + each( + object: T, + callback: ObjectIterator, + thisArg?: any): T + } + + interface LoDashArrayWrapper { + /** + * @see _.forEach + **/ + forEach( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.forEach + **/ + each( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.forEach + **/ + forEach( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + + /** + * @see _.forEach + **/ + each( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + } + + //_.forEachRight + interface LoDashStatic { + /** + * This method is like _.forEach except that it iterates over elements of a + * collection from right to left. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + forEachRight( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEachRight + **/ + forEachRight( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEachRight + **/ + forEachRight( + object: Dictionary, + callback: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forEachRight + **/ + eachRight( + collection: Array, + callback: ListIterator, + thisArg?: any): Array; + + /** + * @see _.forEachRight + **/ + eachRight( + collection: List, + callback: ListIterator, + thisArg?: any): List; + + /** + * @see _.forEachRight + * @param object The object to iterate over + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + eachRight( + object: Dictionary, + callback: DictionaryIterator, + thisArg?: any): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.forEachRight + **/ + forEachRight( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.forEachRight + **/ + eachRight( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.forEachRight + **/ + forEachRight( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper>; + + /** + * @see _.forEachRight + * @param object The object to iterate over + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + **/ + eachRight( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper>; + } + + //_.groupBy + interface LoDashStatic { + /** + * Creates an object composed of keys generated from the results of running each element + * of a collection through the callback. The corresponding value of each key is an array + * of the elements responsible for generating the key. The callback is bound to thisArg + * and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the composed aggregate object. + **/ + groupBy( + collection: Array, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.groupBy + **/ + groupBy( + collection: List, + callback?: ListIterator, + thisArg?: any): Dictionary; + + /** + * @see _.groupBy + * @param pluckValue _.pluck style callback + **/ + groupBy( + collection: Array, + pluckValue: string): Dictionary; + + /** + * @see _.groupBy + * @param pluckValue _.pluck style callback + **/ + groupBy( + collection: List, + pluckValue: string): Dictionary; + + /** + * @see _.groupBy + * @param whereValue _.where style callback + **/ + groupBy( + collection: Array, + whereValue: W): Dictionary; + + /** + * @see _.groupBy + * @param whereValue _.where style callback + **/ + groupBy( + collection: List, + whereValue: W): Dictionary; + + /** + * @see _.groupBy + **/ + groupBy( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.groupBy + * @param pluckValue _.pluck style callback + **/ + groupBy( + collection: Dictionary, + pluckValue: string): Dictionary; + + /** + * @see _.groupBy + * @param whereValue _.where style callback + **/ + groupBy( + collection: Dictionary, + whereValue: W): Dictionary; + } + + interface LoDashArrayWrapper { + /** + * @see _.groupBy + **/ + groupBy( + callback: ListIterator, + thisArg?: any): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + pluckValue: string): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + whereValue: W): _.LoDashObjectWrapper<_.Dictionary>; + } + + interface LoDashObjectWrapper { + /** + * @see _.groupBy + **/ + groupBy( + callback: ListIterator, + thisArg?: any): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + pluckValue: string): _.LoDashObjectWrapper<_.Dictionary>; + + /** + * @see _.groupBy + **/ + groupBy( + whereValue: W): _.LoDashObjectWrapper<_.Dictionary>; + } + + //_.indexBy + interface LoDashStatic { + /** + * Creates an object composed of keys generated from the results of running each element + * of the collection through the given callback. The corresponding value of each key is + * the last element responsible for generating the key. The callback is bound to thisArg + * and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the composed aggregate object. + **/ + indexBy( + list: Array, + iterator: ListIterator, + context?: any): Dictionary; + + /** + * @see _.indexBy + **/ + indexBy( + list: List, + iterator: ListIterator, + context?: any): Dictionary; + + /** + * @see _.indexBy + * @param pluckValue _.pluck style callback + **/ + indexBy( + collection: Array, + pluckValue: string): Dictionary; + + /** + * @see _.indexBy + * @param pluckValue _.pluck style callback + **/ + indexBy( + collection: List, + pluckValue: string): Dictionary; + + /** + * @see _.indexBy + * @param whereValue _.where style callback + **/ + indexBy( + collection: Array, + whereValue: W): Dictionary; + + /** + * @see _.indexBy + * @param whereValue _.where style callback + **/ + indexBy( + collection: List, + whereValue: W): Dictionary; + } + + //_.invoke + interface LoDashStatic { + /** + * Invokes the method named by methodName on each element in the collection returning + * an array of the results of each invoked method. Additional arguments will be provided + * to each invoked method. If methodName is a function it will be invoked for, and this + * bound to, each element in the collection. + * @param collection The collection to iterate over. + * @param methodName The name of the method to invoke. + * @param args Arguments to invoke the method with. + **/ + invoke( + collection: Array, + methodName: string, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: List, + methodName: string, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: Dictionary, + methodName: string, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: Array, + method: Function, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: List, + method: Function, + ...args: any[]): any; + + /** + * @see _.invoke + **/ + invoke( + collection: Dictionary, + method: Function, + ...args: any[]): any; + } + + //_.map + interface LoDashStatic { + /** + * Creates an array of values by running each element in the collection through the callback. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, + * collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true + * for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param theArg The this binding of callback. + * @return The mapped array result. + **/ + map( + collection: Array, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + map( + collection: List, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg `this` object in `iterator`, optional. + * @return The mapped object result. + **/ + map( + object: Dictionary, + callback: DictionaryIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + * @param pluckValue _.pluck style callback + **/ + map( + collection: Array, + pluckValue: string): TResult[]; + + /** + * @see _.map + * @param pluckValue _.pluck style callback + **/ + map( + collection: List, + pluckValue: string): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: Array, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: List, + callback: ListIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + collect( + object: Dictionary, + callback: DictionaryIterator, + thisArg?: any): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: Array, + pluckValue: string): TResult[]; + + /** + * @see _.map + **/ + collect( + collection: List, + pluckValue: string): TResult[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.map + **/ + map( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.map + * @param pluckValue _.pluck style callback + **/ + map( + pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.map + **/ + collect( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.map + **/ + collect( + pluckValue: string): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.map + **/ + map( + callback: ObjectIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.map + **/ + collect( + callback: ObjectIterator, + thisArg?: any): LoDashArrayWrapper; + } + + //_.ceil + interface LoDashStatic { + /** + * Calculates n rounded up to precision. + * @param n The number to round up. + * @param precision The precision to round up to. + * @return Returns the rounded up number. + */ + ceil(n: number, precision?: number): number; + } + + interface LoDashWrapper { + /** + * @see _.ceil + */ + ceil(precision?: number): number; + } + + //_.floor + interface LoDashStatic { + /** + * Calculates n rounded down to precision. + * @param n The number to round down. + * @param precision The precision to round down to. + * @return Returns the rounded down number. + */ + floor(n: number, precision?: number): number; + } + + interface LoDashWrapper { + /** + * @see _.floor + */ + floor(precision?: number): number; + } + + //_.max + interface LoDashStatic { + /** + * Retrieves the maximum value of a collection. If the collection is empty or falsey -Infinity is + * returned. If a callback is provided it will be executed for each value in the collection to + * generate the criterion by which the value is ranked. The callback is bound to thisArg and invoked + * with three arguments; (value, index, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will return the + * property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true for + * elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the maximum value. + **/ + max( + collection: Array, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.max + **/ + max( + collection: List, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.max + **/ + max( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): T; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + collection: Array, + pluckValue: string): T; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + collection: List, + pluckValue: string): T; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + collection: Dictionary, + pluckValue: string): T; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + collection: Array, + whereValue: W): T; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + collection: List, + whereValue: W): T; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + collection: Dictionary, + whereValue: W): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.max + **/ + max( + callback?: ListIterator, + thisArg?: any): LoDashWrapper; + + /** + * @see _.max + * @param pluckValue _.pluck style callback + **/ + max( + pluckValue: string): LoDashWrapper; + + /** + * @see _.max + * @param whereValue _.where style callback + **/ + max( + whereValue: W): LoDashWrapper; + } + + //_.min + interface LoDashStatic { + /** + * Retrieves the minimum value of a collection. If the collection is empty or falsey + * Infinity is returned. If a callback is provided it will be executed for each value + * in the collection to generate the criterion by which the value is ranked. The callback + * is bound to thisArg and invoked with three arguments; (value, index, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback + * will return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will + * return true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return Returns the maximum value. + **/ + min( + collection: Array, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.min + **/ + min( + collection: List, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.min + **/ + min( + collection: Dictionary, + callback?: ListIterator, + thisArg?: any): T; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + collection: Array, + pluckValue: string): T; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + collection: List, + pluckValue: string): T; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + collection: Dictionary, + pluckValue: string): T; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + collection: Array, + whereValue: W): T; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + collection: List, + whereValue: W): T; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + collection: Dictionary, + whereValue: W): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.min + **/ + min( + callback?: ListIterator, + thisArg?: any): LoDashWrapper; + + /** + * @see _.min + * @param pluckValue _.pluck style callback + **/ + min( + pluckValue: string): LoDashWrapper; + + /** + * @see _.min + * @param whereValue _.where style callback + **/ + min( + whereValue: W): LoDashWrapper; + } + + //_.round + interface LoDashStatic { + /** + * Calculates n rounded to precision. + * @param n The number to round. + * @param precision The precision to round to. + * @return Returns the rounded number. + */ + round(n: number, precision?: number): number; + } + + interface LoDashWrapper { + /** + * @see _.round + */ + round(precision?: number): number; + } + + //_.sum + interface LoDashStatic { + /** + * Gets the sum of the values in collection. + * + * @param collection The collection to iterate over. + * @param iteratee The function invoked per iteration. + * @param thisArg The this binding of iteratee. + * @return Returns the sum. + **/ + sum( + collection: Array): number; + + /** + * @see _.sum + **/ + sum( + collection: List): number; + + /** + * @see _.sum + **/ + sum( + collection: Dictionary): number; + + /** + * @see _.sum + **/ + sum( + collection: Array, + iteratee: ListIterator, + thisArg?: any): number; + + /** + * @see _.sum + **/ + sum( + collection: List, + iteratee: ListIterator, + thisArg?: any): number; + + /** + * @see _.sum + **/ + sum( + collection: Dictionary, + iteratee: ObjectIterator, + thisArg?: any): number; + + /** + * @see _.sum + * @param property _.property callback shorthand. + **/ + sum( + collection: Array, + property: string): number; + + /** + * @see _.sum + * @param property _.property callback shorthand. + **/ + sum( + collection: List, + property: string): number; + + /** + * @see _.sum + * @param property _.property callback shorthand. + **/ + sum( + collection: Dictionary, + property: string): number; + } + + interface LoDashNumberArrayWrapper { + /** + * @see _.sum + **/ + sum(): number; + + /** + * @see _.sum + **/ + sum( + iteratee: ListIterator, + thisArg?: any): number; + } + + interface LoDashArrayWrapper { + /** + * @see _.sum + **/ + sum(): number; + + /** + * @see _.sum + **/ + sum( + iteratee: ListIterator, + thisArg?: any): number; + + /** + * @see _.sum + * @param property _.property callback shorthand. + **/ + sum( + property: string): number; + } + + interface LoDashObjectWrapper { + /** + * @see _.sum + **/ + sum(): number; + + /** + * @see _.sum + **/ + sum( + iteratee: ObjectIterator, + thisArg?: any): number; + + /** + * @see _.sum + * @param property _.property callback shorthand. + **/ + sum( + property: string): number; + } + + //_.pluck + interface LoDashStatic { + /** + * Retrieves the value of a specified property from all elements in the collection. + * @param collection The collection to iterate over. + * @param property The property to pluck. + * @return A new array of property values. + **/ + pluck( + collection: Array, + property: string|string[]): any[]; + + /** + * @see _.pluck + **/ + pluck( + collection: List, + property: string|string[]): any[]; + + /** + * @see _.pluck + **/ + pluck( + collection: Dictionary, + property: string|string[]): any[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.pluck + **/ + pluck( + property: string): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.pluck + **/ + pluck( + property: string): LoDashArrayWrapper; + } + + //_.partition + interface LoDashStatic { + /** + * Creates an array of elements split into two groups, the first of which contains elements predicate returns truthy for, + * while the second of which contains elements predicate returns falsey for. + * The predicate is bound to thisArg and invoked with three arguments: (value, index|key, collection). + * + * If a property name is provided for predicate the created _.property style callback + * returns the property value of the given element. + * + * If a value is also provided for thisArg the created _.matchesProperty style callback + * returns true for elements that have a matching property value, else false. + * + * If an object is provided for predicate the created _.matches style callback returns + * true for elements that have the properties of the given object, else false. + * + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of predicate. + * @return Returns the array of grouped elements. + **/ + partition( + collection: List, + callback: ListIterator, + thisArg?: any): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: List, + whereValue: W): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: Dictionary, + whereValue: W): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: List, + path: string, + srcValue: any): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: Dictionary, + path: string, + srcValue: any): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: List, + pluckValue: string): T[][]; + + /** + * @see _.partition + **/ + partition( + collection: Dictionary, + pluckValue: string): T[][]; + } + + interface LoDashStringWrapper { + /** + * @see _.partition + */ + partition( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + } + + interface LoDashArrayWrapper { + /** + * @see _.partition + */ + partition( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + /** + * @see _.partition + */ + partition( + whereValue: W): LoDashArrayWrapper; + /** + * @see _.partition + */ + partition( + path: string, + srcValue: any): LoDashArrayWrapper; + /** + * @see _.partition + */ + partition( + pluckValue: string): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.partition + */ + partition( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.partition + */ + partition( + callback: DictionaryIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.partition + */ + partition( + whereValue: W): LoDashArrayWrapper; + + /** + * @see _.partition + */ + partition( + path: string, + srcValue: any): LoDashArrayWrapper; + + /** + * @see _.partition + */ + partition( + pluckValue: string): LoDashArrayWrapper; + } + + //_.reduce + interface LoDashStatic { + /** + * Reduces a collection to a value which is the accumulated result of running each + * element in the collection through the callback, where each successive callback execution + * consumes the return value of the previous execution. If accumulator is not provided the + * first element of the collection will be used as the initial accumulator value. The callback + * is bound to thisArg and invoked with four arguments; (accumulator, value, index|key, collection). + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param accumulator Initial value of the accumulator. + * @param thisArg The this binding of callback. + * @return Returns the accumulated value. + **/ + reduce( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + } + + interface LoDashArrayWrapper { + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + thisArg?: any): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + reduce( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + inject( + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduce + **/ + foldl( + callback: MemoIterator, + thisArg?: any): TResult; + } + + //_.reduceRight + interface LoDashStatic { + /** + * This method is like _.reduce except that it iterates over elements of a collection from + * right to left. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param accumulator Initial value of the accumulator. + * @param thisArg The this binding of callback. + * @return The accumulated value. + **/ + reduceRight( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + reduceRight( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Array, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: List, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Dictionary, + callback: MemoIterator, + accumulator: TResult, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Array, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: List, + callback: MemoIterator, + thisArg?: any): TResult; + + /** + * @see _.reduceRight + **/ + foldr( + collection: Dictionary, + callback: MemoIterator, + thisArg?: any): TResult; + } + + //_.reject + interface LoDashStatic { + /** + * The opposite of _.filter this method returns the elements of a collection that + * the callback does not return truey for. + * + * If a property name is provided for callback the created "_.pluck" style callback + * will return the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will + * return true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of elements that failed the callback check. + **/ + reject( + collection: Array, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.reject + **/ + reject( + collection: List, + callback: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.reject + **/ + reject( + collection: Dictionary, + callback: DictionaryIterator, + thisArg?: any): T[]; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject( + collection: Dictionary, + pluckValue: string): T[]; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject( + collection: List, + whereValue: W): T[]; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject( + collection: Dictionary, + whereValue: W): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.reject + **/ + reject( + callback: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.reject + * @param pluckValue _.pluck style callback + **/ + reject(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.reject + * @param whereValue _.where style callback + **/ + reject(whereValue: W): LoDashArrayWrapper; + } + + //_.sample + interface LoDashStatic { + /** + * Retrieves a random element or n random elements from a collection. + * @param collection The collection to sample. + * @return Returns the random sample(s) of collection. + **/ + sample(collection: Array): T; + + /** + * @see _.sample + **/ + sample(collection: List): T; + + /** + * @see _.sample + **/ + sample(collection: Dictionary): T; + + /** + * @see _.sample + * @param n The number of elements to sample. + **/ + sample(collection: Array, n: number): T[]; + + /** + * @see _.sample + * @param n The number of elements to sample. + **/ + sample(collection: List, n: number): T[]; + + /** + * @see _.sample + * @param n The number of elements to sample. + **/ + sample(collection: Dictionary, n: number): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.sample + **/ + sample(n: number): LoDashArrayWrapper; + + /** + * @see _.sample + **/ + sample(): LoDashWrapper; + } + + //_.shuffle + interface LoDashStatic { + /** + * Creates an array of shuffled values, using a version of the Fisher-Yates shuffle. + * See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. + * @param collection The collection to shuffle. + * @return Returns a new shuffled collection. + **/ + shuffle(collection: Array): T[]; + + /** + * @see _.shuffle + **/ + shuffle(collection: List): T[]; + + /** + * @see _.shuffle + **/ + shuffle(collection: Dictionary): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.shuffle + **/ + shuffle(): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.shuffle + **/ + shuffle(): LoDashArrayWrapper; + } + + //_.size + interface LoDashStatic { + /** + * Gets the size of the collection by returning collection.length for arrays and array-like + * objects or the number of own enumerable properties for objects. + * @param collection The collection to inspect. + * @return collection.length + **/ + size(collection: Array): number; + + /** + * @see _.size + **/ + size(collection: List): number; + + /** + * @see _.size + * @param object The object to inspect + * @return The number of own enumerable properties. + **/ + size(object: T): number; + + /** + * @see _.size + * @param aString The string to inspect + * @return The length of aString + **/ + size(aString: string): number; + } + + interface LoDashArrayWrapper { + /** + * @see _.size + **/ + size(): number; + } + + interface LoDashObjectWrapper { + /** + * @see _.size + **/ + size(): number; + } + + //_.some + interface LoDashStatic { + /** + * Checks if the callback returns a truey value for any element of a collection. The function + * returns as soon as it finds a passing value and does not iterate over the entire collection. + * The callback is bound to thisArg and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will return + * the property value of the given element. + * + * If an object is provided for callback the created "_.where" style callback will return true for + * elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return True if any element passed the callback check, else false. + **/ + some( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + some( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + some( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + some( + collection: {}, + callback?: ListIterator<{}, boolean>, + thisArg?: any): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + some( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + some( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + some( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + some( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + some( + collection: List, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + some( + collection: Dictionary, + whereValue: W): boolean; + + /** + * @see _.some + **/ + any( + collection: Array, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + any( + collection: List, + callback?: ListIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + any( + collection: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): boolean; + + /** + * @see _.some + **/ + any( + collection: {}, + callback?: ListIterator<{}, boolean>, + thisArg?: any): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + any( + collection: Array, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + any( + collection: List, + pluckValue: string): boolean; + + /** + * @see _.some + * @param pluckValue _.pluck style callback + **/ + any( + collection: Dictionary, + pluckValue: string): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + any( + collection: Array, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + any( + collection: List, + whereValue: W): boolean; + + /** + * @see _.some + * @param whereValue _.where style callback + **/ + any( + collection: Dictionary, + whereValue: W): boolean; + } + + //_.sortBy + interface LoDashStatic { + /** + * Creates an array of elements, sorted in ascending order by the results of running each + * element in a collection through the callback. This method performs a stable sort, that + * is, it will preserve the original sort order of equal elements. The callback is bound + * to thisArg and invoked with three arguments; (value, index|key, collection). + * + * If a property name is provided for callback the created "_.pluck" style callback will + * return the property value of the given element. + * + * If a value is also provided for thisArg the created "_.matchesProperty" style callback + * returns true for elements that have a matching property value, else false. + * + * If an object is provided for an iteratee the created "_.matches" style callback returns + * true for elements that have the properties of the given object, else false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of sorted elements. + **/ + sortBy( + collection: Array, + iteratee?: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.sortBy + **/ + sortBy( + collection: List, + iteratee?: ListIterator, + thisArg?: any): T[]; + + /** + * @see _.sortBy + * @param pluckValue _.pluck style callback + **/ + sortBy( + collection: Array, + pluckValue: string): T[]; + + /** + * @see _.sortBy + * @param pluckValue _.pluck style callback + **/ + sortBy( + collection: List, + pluckValue: string): T[]; + + /** + * @see _.sortBy + * @param whereValue _.where style callback + **/ + sortBy( + collection: Array, + whereValue: W): T[]; + + /** + * @see _.sortBy + * @param whereValue _.where style callback + **/ + sortBy( + collection: List, + whereValue: W): T[]; + + /** + * Sorts by all the given arguments, using either ListIterator, pluckValue, or whereValue foramts + * @param args The rules by which to sort + */ + sortByAll( + collection: (Array|List), + ...args: (ListIterator|Object|string)[] + ): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.sortBy + **/ + sortBy( + iteratee?: ListIterator, + thisArg?: any): LoDashArrayWrapper; + + /** + * @see _.sortBy + * @param pluckValue _.pluck style callback + **/ + sortBy(pluckValue: string): LoDashArrayWrapper; + + /** + * @see _.sortBy + * @param whereValue _.where style callback + **/ + sortBy(whereValue: W): LoDashArrayWrapper; + + /** + * Sorts by all the given arguments, using either ListIterator, pluckValue, or whereValue foramts + * @param args The rules by which to sort + */ + sortByAll(...args: (ListIterator|Object|string)[]): LoDashArrayWrapper; + } + + //_.sortByAll + interface LoDashStatic { + /** + * This method is like "_.sortBy" except that it can sort by multiple iteratees or + * property names. + * + * If a property name is provided for an iteratee the created "_.property" style callback + * returns the property value of the given element. + * + * If a value is also provided for thisArg the created "_.matchesProperty" style callback + * returns true for elements that have a matching property value, else false. + * + * If an object is provided for an iteratee the created "_.matches" style callback returns + * true for elements that have the properties of the given object, else false. + * + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of sorted elements. + **/ + sortByAll( + collection: Array, + iteratees: (ListIterator|string|Object)[]): T[]; + + /** + * @see _.sortByAll + **/ + sortByAll( + collection: List, + iteratees: (ListIterator|string|Object)[]): T[]; + + /** + * @see _.sortByAll + **/ + sortByAll( + collection: Array, + ...iteratees: (ListIterator|string|Object)[]): T[]; + + /** + * @see _.sortByAll + **/ + sortByAll( + collection: List, + ...iteratees: (ListIterator|string|Object)[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.sortByAll + **/ + sortByAll( + iteratees: (ListIterator|string|Object)[]): LoDashArrayWrapper; + + /** + * @see _.sortByAll + **/ + sortByAll( + ...iteratees: (ListIterator|string|Object)[]): LoDashArrayWrapper; + } + + //_.sortByOrder + interface LoDashStatic { + /** + * This method is like "_.sortByAll" except that it allows specifying the sort orders of the + * iteratees to sort by. If orders is unspecified, all values are sorted in ascending order. + * Otherwise, a value is sorted in ascending order if its corresponding order is "asc", and + * descending if "desc". + * + * If a property name is provided for an iteratee the created "_.property" style callback + * returns the property value of the given element. + * + * If an object is provided for an iteratee the created "_.matches" style callback returns + * true for elements that have the properties of the given object, else false. + * + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return A new array of sorted elements. + **/ + sortByOrder( + collection: Array, + iteratees: (ListIterator|string|Object)[], + orders?: boolean[]): T[]; + + /** + * @see _.sortByOrder + **/ + sortByOrder( + collection: List, + iteratees: (ListIterator|string|Object)[], + orders?: boolean[]): T[]; + + /** + * @see _.sortByOrder + **/ + sortByOrder( + collection: Array, + iteratees: (ListIterator|string|Object)[], + orders?: string[]): T[]; + + /** + * @see _.sortByOrder + **/ + sortByOrder( + collection: List, + iteratees: (ListIterator|string|Object)[], + orders?: string[]): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.sortByOrder + **/ + sortByOrder( + iteratees: (ListIterator|string|Object)[], + orders?: boolean[]): LoDashArrayWrapper; + + /** + * @see _.sortByOrder + **/ + sortByOrder( + iteratees: (ListIterator|string|Object)[], + orders?: string[]): LoDashArrayWrapper; + } + + //_.toArray + interface LoDashStatic { + /** + * Converts the collection to an array. + * @param collection The collection to convert. + * @return The new converted array. + **/ + toArray(collection: Array): T[]; + + /** + * @see _.toArray + **/ + toArray(collection: List): T[]; + + /** + * @see _.toArray + **/ + toArray(collection: Dictionary): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.toArray + **/ + toArray(): LoDashArrayWrapper; + } + + interface LoDashObjectWrapper { + /** + * @see _.toArray + **/ + toArray(): LoDashArrayWrapper; + } + + //_.where + interface LoDashStatic { + /** + * Performs a deep comparison of each element in a collection to the given properties + * object, returning an array of all elements that have equivalent property values. + * @param collection The collection to iterate over. + * @param properties The object of property values to filter by. + * @return A new array of elements that have the given properties. + **/ + where( + list: Array, + properties: U): T[]; + + /** + * @see _.where + **/ + where( + list: List, + properties: U): T[]; + + /** + * @see _.where + **/ + where( + list: Dictionary, + properties: U): T[]; + } + + interface LoDashArrayWrapper { + /** + * @see _.where + **/ + where(properties: U): LoDashArrayWrapper; + } + + /******** + * Date * + ********/ + + //_.now + interface LoDashStatic { + /** + * Gets the number of milliseconds that have elapsed since the Unix epoch + * (1 January 1970 00:00:00 UTC). + * @return The number of milliseconds. + **/ + now(): number; + } + + /************* + * Functions * + *************/ + + //_.after + interface LoDashStatic { + /** + * Creates a function that executes func, with the this binding and arguments of the + * created function, only after being called n times. + * @param n The number of times the function must be called before func is executed. + * @param func The function to restrict. + * @return The new restricted function. + **/ + after( + n: number, + func: Function): Function; + } + + interface LoDashWrapper { + /** + * @see _.after + **/ + after(func: Function): LoDashObjectWrapper; + } + + //_.ary + interface LoDashStatic { + /** + * Creates a function that accepts up to n arguments ignoring any additional arguments. + * @param func The function to cap arguments for. + * @param n The arity cap. + * @param guard Enables use as a callback for functions like `_.map`. + * @returns Returns the new function. + */ + ary(func: Function, n?: number, guard?: Object): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.ary + */ + ary(n?: number, guard?: Object): LoDashObjectWrapper; + } + + //_.backflow + interface LoDashStatic { + /** + * @see _.flowRight + */ + backflow(...funcs: Function[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.flowRight + **/ + backflow(...funcs: Function[]): LoDashObjectWrapper; + } + + //_.before + interface LoDashStatic { + /** + * Creates a function that invokes func, with the this binding and arguments of the created function, while + * it is called less than n times. Subsequent calls to the created function return the result of the last func + * invocation. + * @param n The number of calls at which func is no longer invoked. + * @param func The function to restrict. + * @return Returns the new restricted function. + */ + before(n: number, func: TFunc): TFunc; + } + + interface LoDashWrapper { + /** + * @sed _.before + */ + before(func: TFunc): TFunc; + } + + //_.bind + interface LoDashStatic { + /** + * Creates a function that, when called, invokes func with the this binding of thisArg + * and prepends any additional bind arguments to those provided to the bound function. + * @param func The function to bind. + * @param thisArg The this binding of func. + * @param args Arguments to be partially applied. + * @return The new bound function. + **/ + bind( + func: Function, + thisArg: any, + ...args: any[]): (...args: any[]) => any; + } + + interface LoDashObjectWrapper { + /** + * @see _.bind + **/ + bind( + thisArg: any, + ...args: any[]): LoDashObjectWrapper<(...args: any[]) => any>; + } + + //_.bindAll + interface LoDashStatic { + /** + * Binds methods of an object to the object itself, overwriting the existing method. Method + * names may be specified as individual arguments or as arrays of method names. If no method + * names are provided all the function properties of object will be bound. + * @param object The object to bind and assign the bound methods to. + * @param methodNames The object method names to bind, specified as individual method names + * or arrays of method names. + * @return object + **/ + bindAll( + object: T, + ...methodNames: string[]): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.bindAll + **/ + bindAll(...methodNames: string[]): LoDashWrapper; + } + + //_.bindKey + interface LoDashStatic { + /** + * Creates a function that, when called, invokes the method at object[key] and prepends any + * additional bindKey arguments to those provided to the bound function. This method differs + * from _.bind by allowing bound functions to reference methods that will be redefined or don't + * yet exist. See http://michaux.ca/articles/lazy-function-definition-pattern. + * @param object The object the method belongs to. + * @param key The key of the method. + * @param args Arguments to be partially applied. + * @return The new bound function. + **/ + bindKey( + object: T, + key: string, + ...args: any[]): Function; + } + + interface LoDashObjectWrapper { + /** + * @see _.bindKey + **/ + bindKey( + key: string, + ...args: any[]): LoDashObjectWrapper; + } + + //_.compose + interface LoDashStatic { + /** + * @see _.flowRight + */ + compose(...funcs: Function[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.flowRight + */ + compose(...funcs: Function[]): LoDashObjectWrapper; + } + + //_.createCallback + interface LoDashStatic { + /** + * Produces a callback bound to an optional thisArg. If func is a property name the created + * callback will return the property value for a given element. If func is an object the created + * callback will return true for elements that contain the equivalent object properties, + * otherwise it will return false. + * @param func The value to convert to a callback. + * @param thisArg The this binding of the created callback. + * @param argCount The number of arguments the callback accepts. + * @return A callback function. + **/ + createCallback( + func: string, + thisArg?: any, + argCount?: number): () => any; + + /** + * @see _.createCallback + **/ + createCallback( + func: Dictionary, + thisArg?: any, + argCount?: number): () => boolean; + } + + interface LoDashWrapper { + /** + * @see _.createCallback + **/ + createCallback( + thisArg?: any, + argCount?: number): LoDashObjectWrapper<() => any>; + } + + interface LoDashObjectWrapper { + /** + * @see _.createCallback + **/ + createCallback( + thisArg?: any, + argCount?: number): LoDashObjectWrapper<() => any>; + } + + //_.curry + interface LoDashStatic { + /** + * Creates a function that accepts one or more arguments of func that when called either invokes func returning + * its result, if all func arguments have been provided, or returns a function that accepts one or more of the + * remaining func arguments, and so on. The arity of func may be specified if func.length is not sufficient. + * @param func The function to curry. + * @param arity The arity of func. + * @return Returns the new curried function. + */ + curry( + func: Function, + arity?: number): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.curry + **/ + curry(arity?: number): LoDashObjectWrapper; + } + + //_.curryRight + interface LoDashStatic { + /** + * This method is like _.curry except that arguments are applied to func in the manner of _.partialRight + * instead of _.partial. + * @param func The function to curry. + * @param arity The arity of func. + * @return Returns the new curried function. + */ + curryRight( + func: Function, + arity?: number): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.curryRight + **/ + curryRight(arity?: number): LoDashObjectWrapper; + } + + //_.debounce + interface LoDashStatic { + /** + * Creates a function that will delay the execution of func until after wait milliseconds have + * elapsed since the last time it was invoked. Provide an options object to indicate that func + * should be invoked on the leading and/or trailing edge of the wait timeout. Subsequent calls + * to the debounced function will return the result of the last func call. + * + * Note: If leading and trailing options are true func will be called on the trailing edge of + * the timeout only if the the debounced function is invoked more than once during the wait + * timeout. + * @param func The function to debounce. + * @param wait The number of milliseconds to delay. + * @param options The options object. + * @param options.leading Specify execution on the leading edge of the timeout. + * @param options.maxWait The maximum time func is allowed to be delayed before it's called. + * @param options.trailing Specify execution on the trailing edge of the timeout. + * @return The new debounced function. + **/ + debounce( + func: T, + wait: number, + options?: DebounceSettings): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.debounce + **/ + debounce( + wait: number, + options?: DebounceSettings): LoDashObjectWrapper; + } + + interface DebounceSettings { + /** + * Specify execution on the leading edge of the timeout. + **/ + leading?: boolean; + + /** + * The maximum time func is allowed to be delayed before it's called. + **/ + maxWait?: number; + + /** + * Specify execution on the trailing edge of the timeout. + **/ + trailing?: boolean; + } + + //_.defer + interface LoDashStatic { + /** + * Defers executing the func function until the current call stack has cleared. Additional + * arguments will be provided to func when it is invoked. + * @param func The function to defer. + * @param args Arguments to invoke the function with. + * @return The timer id. + **/ + defer( + func: Function, + ...args: any[]): number; + } + + interface LoDashObjectWrapper { + /** + * @see _.defer + **/ + defer(...args: any[]): LoDashWrapper; + } + + //_.delay + interface LoDashStatic { + /** + * Executes the func function after wait milliseconds. Additional arguments will be provided + * to func when it is invoked. + * @param func The function to delay. + * @param wait The number of milliseconds to delay execution. + * @param args Arguments to invoke the function with. + * @return The timer id. + **/ + delay( + func: Function, + wait: number, + ...args: any[]): number; + } + + interface LoDashObjectWrapper { + /** + * @see _.delay + **/ + delay( + wait: number, + ...args: any[]): LoDashWrapper; + } + + //_.flow + interface LoDashStatic { + /** + * Creates a function that returns the result of invoking the provided functions with the this binding of the + * created function, where each successive invocation is supplied the return value of the previous. + * @param funcs Functions to invoke. + * @return Returns the new function. + */ + flow(...funcs: Function[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.flow + **/ + flow(...funcs: Function[]): LoDashObjectWrapper; + } + + //_.flowRight + interface LoDashStatic { + /** + * This method is like _.flow except that it creates a function that invokes the provided functions from right + * to left. + * @param funcs Functions to invoke. + * @return Returns the new function. + */ + flowRight(...funcs: Function[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.flowRight + **/ + flowRight(...funcs: Function[]): LoDashObjectWrapper; + } + + //_.memoize + interface MemoizedFunction extends Function { + cache: MapCache; + } + + interface LoDashStatic { + /** + * Creates a function that memoizes the result of func. If resolver is provided it determines the cache key for + * storing the result based on the arguments provided to the memoized function. By default, the first argument + * provided to the memoized function is coerced to a string and used as the cache key. The func is invoked with + * the this binding of the memoized function. + * @param func The function to have its output memoized. + * @param resolver The function to resolve the cache key. + * @return Returns the new memoizing function. + */ + memoize( + func: Function, + resolver?: Function): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.memoize + */ + memoize(resolver?: Function): LoDashObjectWrapper; + } + + //_.modArgs + interface LoDashStatic { + /** + * Creates a function that runs each argument through a corresponding transform function. + * @param func The function to wrap. + * @param transforms The functions to transform arguments, specified as individual functions or arrays + * of functions. + * @return Returns the new function. + */ + modArgs( + func: T, + ...transforms: Function[] + ): TResult; + + /** + * @see _.modArgs + */ + modArgs( + func: T, + transforms: Function[] + ): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.modArgs + */ + modArgs(...transforms: Function[]): LoDashObjectWrapper; + + /** + * @see _.modArgs + */ + modArgs(transforms: Function[]): LoDashObjectWrapper; + } + + //_.negate + interface LoDashStatic { + /** + * Creates a function that negates the result of the predicate func. The func predicate is invoked with + * the this binding and arguments of the created function. + * @param predicate The predicate to negate. + * @return Returns the new function. + */ + negate(predicate: T): (...args: any[]) => boolean; + + /** + * @see _.negate + */ + negate(predicate: T): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.negate + */ + negate(): LoDashObjectWrapper<(...args: any[]) => boolean>; + + /** + * @see _.negate + */ + negate(): LoDashObjectWrapper; + } + + //_.once + interface LoDashStatic { + /** + * Creates a function that is restricted to invoking func once. Repeat calls to the function return the value + * of the first call. The func is invoked with the this binding and arguments of the created function. + * @param func The function to restrict. + * @return Returns the new restricted function. + */ + + once(func: T): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.once + */ + once(): LoDashObjectWrapper; + } + + //_.partial + interface LoDashStatic { + /** + * Creates a function that, when called, invokes func with any additional partial arguments + * prepended to those provided to the new function. This method is similar to _.bind except + * it does not alter the this binding. + * @param func The function to partially apply arguments to. + * @param args Arguments to be partially applied. + * @return The new partially applied function. + **/ + partial( + func: Function, + ...args: any[]): Function; + } + + //_.partialRight + interface LoDashStatic { + /** + * This method is like _.partial except that partial arguments are appended to those provided + * to the new function. + * @param func The function to partially apply arguments to. + * @param args Arguments to be partially applied. + * @return The new partially applied function. + **/ + partialRight( + func: Function, + ...args: any[]): Function; + } + + //_.rearg + interface LoDashStatic { + /** + * Creates a function that invokes func with arguments arranged according to the specified indexes where the + * argument value at the first index is provided as the first argument, the argument value at the second index + * is provided as the second argument, and so on. + * @param func The function to rearrange arguments for. + * @param indexes The arranged argument indexes, specified as individual indexes or arrays of indexes. + * @return Returns the new function. + */ + rearg(func: Function, indexes: number[]): TResult; + + /** + * @see _.rearg + */ + rearg(func: Function, ...indexes: number[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.rearg + */ + rearg(indexes: number[]): LoDashObjectWrapper; + + /** + * @see _.rearg + */ + rearg(...indexes: number[]): LoDashObjectWrapper; + } + + //_.restParam + interface LoDashStatic { + /** + * Creates a function that invokes func with the this binding of the created function and arguments from start + * and beyond provided as an array. + * @param func The function to apply a rest parameter to. + * @param start The start position of the rest parameter. + * @return Returns the new function. + */ + restParam(func: Function, start?: number): TResult; + + /** + * @see _.restParam + */ + restParam(func: TFunc, start?: number): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.restParam + */ + restParam(start?: number): LoDashObjectWrapper; + } + + //_.spread + interface LoDashStatic { + /** + * Creates a function that invokes func with the this binding of the created function and an array of arguments + * much like Function#apply. + * @param func The function to spread arguments over. + * @return Returns the new function. + */ + spread(func: Function): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.spread + */ + spread(): LoDashObjectWrapper; + } + + + //_.throttle + interface LoDashStatic { + /** + * Creates a function that, when executed, will only call the func function at most once per + * every wait milliseconds. Provide an options object to indicate that func should be invoked + * on the leading and/or trailing edge of the wait timeout. Subsequent calls to the throttled + * function will return the result of the last func call. + * + * Note: If leading and trailing options are true func will be called on the trailing edge of + * the timeout only if the the throttled function is invoked more than once during the wait timeout. + * @param func The function to throttle. + * @param wait The number of milliseconds to throttle executions to. + * @param options The options object. + * @param options.leading Specify execution on the leading edge of the timeout. + * @param options.trailing Specify execution on the trailing edge of the timeout. + * @return The new throttled function. + **/ + throttle( + func: T, + wait: number, + options?: ThrottleSettings): T; + } + + interface ThrottleSettings { + + /** + * If you'd like to disable the leading-edge call, pass this as false. + **/ + leading?: boolean; + + /** + * If you'd like to disable the execution on the trailing-edge, pass false. + **/ + trailing?: boolean; + } + + //_.wrap + interface LoDashStatic { + /** + * Creates a function that provides value to the wrapper function as its first argument. + * Additional arguments provided to the function are appended to those provided to the + * wrapper function. The wrapper is executed with the this binding of the created function. + * @param value The value to wrap. + * @param wrapper The wrapper function. + * @return The new function. + **/ + wrap( + value: any, + wrapper: (func: Function, ...args: any[]) => any): Function; + } + + /******** + * Lang * + ********/ + + //_.clone + interface LoDashStatic { + /** + * Creates a clone of value. If isDeep is true nested objects are cloned, otherwise they are assigned by + * reference. If customizer is provided it’s invoked to produce the cloned values. If customizer returns + * undefined cloning is handled by the method instead. The customizer is bound to thisArg and invoked with up + * to three argument; (value [, index|key, object]). + * Note: This method is loosely based on the structured clone algorithm. The enumerable properties of arguments + * objects and objects created by constructors other than Object are cloned to plain Object objects. An empty + * object is returned for uncloneable values such as functions, DOM nodes, Maps, Sets, and WeakMaps. + * @param value The value to clone. + * @param isDeep Specify a deep clone. + * @param customizer The function to customize cloning values. + * @param thisArg The this binding of customizer. + * @return Returns the cloned value. + */ + clone( + value: T, + isDeep?: boolean, + customizer?: (value: any) => any, + thisArg?: any): T; + + /** + * @see _.clone + */ + clone( + value: T, + customizer?: (value: any) => any, + thisArg?: any): T; + } + + interface LoDashWrapper { + /** + * @see _.clone + */ + clone( + isDeep?: boolean, + customizer?: (value: any) => any, + thisArg?: any): T; + + /** + * @see _.clone + */ + clone( + customizer?: (value: any) => any, + thisArg?: any): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.clone + */ + clone( + isDeep?: boolean, + customizer?: (value: any) => any, + thisArg?: any): T[]; + + /** + * @see _.clone + */ + clone( + customizer?: (value: any) => any, + thisArg?: any): T[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.clone + */ + clone( + isDeep?: boolean, + customizer?: (value: any) => any, + thisArg?: any): T; + + /** + * @see _.clone + */ + clone( + customizer?: (value: any) => any, + thisArg?: any): T; + } + + //_.cloneDeep + interface LoDashStatic { + /** + * Creates a deep clone of value. If customizer is provided it’s invoked to produce the cloned values. If + * customizer returns undefined cloning is handled by the method instead. The customizer is bound to thisArg + * and invoked with up to three argument; (value [, index|key, object]). + * Note: This method is loosely based on the structured clone algorithm. The enumerable properties of arguments + * objects and objects created by constructors other than Object are cloned to plain Object objects. An empty + * object is returned for uncloneable values such as functions, DOM nodes, Maps, Sets, and WeakMaps. + * @param value The value to deep clone. + * @param customizer The function to customize cloning values. + * @param thisArg The this binding of customizer. + * @return Returns the deep cloned value. + */ + cloneDeep( + value: T, + customizer?: (value: any) => any, + thisArg?: any): T; + } + + interface LoDashWrapper { + /** + * @see _.cloneDeep + */ + cloneDeep( + customizer?: (value: any) => any, + thisArg?: any): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.cloneDeep + */ + cloneDeep( + customizer?: (value: any) => any, + thisArg?: any): T[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.cloneDeep + */ + cloneDeep( + customizer?: (value: any) => any, + thisArg?: any): T; + } + + //_.gt + interface LoDashStatic { + /** + * Checks if value is greater than other. + * @param value The value to compare. + * @param other The other value to compare. + * @return Returns true if value is greater than other, else false. + */ + gt(value: any, other: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.gt + */ + gt(other: any): boolean; + } + + //_.gte + interface LoDashStatic { + /** + * Checks if value is greater than or equal to other. + * @param value The value to compare. + * @param other The other value to compare. + * @return Returns true if value is greater than or equal to other, else false. + */ + gte(value: any, other: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.gte + */ + gte(other: any): boolean; + } + + //_.isArguments + interface LoDashStatic { + /** + * Checks if value is classified as an arguments object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + */ + isArguments(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isArguments + */ + isArguments(): boolean; + } + + //_.isArray + interface LoDashStatic { + /** + * Checks if value is classified as an Array object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + **/ + isArray(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isArray + */ + isArray(): boolean; + } + + //_.isBoolean + interface LoDashStatic { + /** + * Checks if value is classified as a boolean primitive or object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + **/ + isBoolean(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isBoolean + */ + isBoolean(): boolean; + } + + //_.isDate + interface LoDashStatic { + /** + * Checks if value is classified as a Date object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + **/ + isDate(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isDate + */ + isDate(): boolean; + } + + //_.isElement + interface LoDashStatic { + /** + * Checks if value is a DOM element. + * @param value The value to check. + * @return Returns true if value is a DOM element, else false. + */ + isElement(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isElement + */ + isElement(): boolean; + } + + //_.isEmpty + interface LoDashStatic { + /** + * Checks if value is empty. A value is considered empty unless it’s an arguments object, array, string, or + * jQuery-like collection with a length greater than 0 or an object with own enumerable properties. + * @param value The value to inspect. + * @return Returns true if value is empty, else false. + **/ + isEmpty(value?: any[]|Dictionary|string|any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isEmpty + */ + isEmpty(): boolean; + } + + //_.isError + interface LoDashStatic { + /** + * Checks if value is an Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, or URIError + * object. + * @param value The value to check. + * @return Returns true if value is an error object, else false. + */ + isError(value: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isError + */ + isError(): boolean; + } + + //_.isFinite + interface LoDashStatic { + /** + * Checks if value is a finite primitive number. + * Note: This method is based on Number.isFinite. + * @param value The value to check. + * @return Returns true if value is a finite number, else false. + **/ + isFinite(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isFinite + */ + isFinite(): boolean; + } + + //_.isFunction + interface LoDashStatic { + /** + * Checks if value is classified as a Function object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + **/ + isFunction(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isFunction + */ + isFunction(): boolean; + } + + //_.isMatch + interface isMatchCustomizer { + (value: any, other: any, indexOrKey?: number|string): boolean; + } + + interface LoDashStatic { + /** + * Performs a deep comparison between object and source to determine if object contains equivalent property + * values. If customizer is provided it’s invoked to compare values. If customizer returns undefined + * comparisons are handled by the method instead. The customizer is bound to thisArg and invoked with three + * arguments: (value, other, index|key). + * @param object The object to inspect. + * @param source The object of property values to match. + * @param customizer The function to customize value comparisons. + * @param thisArg The this binding of customizer. + * @return Returns true if object is a match, else false. + */ + isMatch(object: Object, source: Object, customizer?: isMatchCustomizer, thisArg?: any): boolean; + } + + interface LoDashObjectWrapper { + /** + * @see _.isMatch + */ + isMatch(source: Object, customizer?: isMatchCustomizer, thisArg?: any): boolean; + } + + //_.isNaN + interface LoDashStatic { + /** + * Checks if value is NaN. + * Note: This method is not the same as isNaN which returns true for undefined and other non-numeric values. + * @param value The value to check. + * @return Returns true if value is NaN, else false. + */ + isNaN(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.isNaN + */ + isNaN(): boolean; + } + + //_.isNative + interface LoDashStatic { + /** + * Checks if value is a native function. + * @param value The value to check. + * @retrun Returns true if value is a native function, else false. + */ + isNative(value: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isNative + */ + isNative(): boolean; + } + + //_.isNull + interface LoDashStatic { + /** + * Checks if value is null. + * @param value The value to check. + * @return Returns true if value is null, else false. + **/ + isNull(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isNull + */ + isNull(): boolean; + } + + //_.isNumber + interface LoDashStatic { + /** + * Checks if value is classified as a Number primitive or object. + * Note: To exclude Infinity, -Infinity, and NaN, which are classified as numbers, use the _.isFinite method. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + */ + isNumber(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isNumber + */ + isNumber(): boolean; + } + + //_.isObject + interface LoDashStatic { + /** + * Checks if value is the language type of Object. (e.g. arrays, functions, objects, regexes, new Number(0), + * and new String('')) + * @param value The value to check. + * @return Returns true if value is an object, else false. + **/ + isObject(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isObject + */ + isObject(): boolean; + } + + //_.isPlainObject + interface LoDashStatic { + /** + * Checks if value is a plain object, that is, an object created by the Object constructor or one with a + * [[Prototype]] of null. + * + * Note: This method assumes objects created by the Object constructor have no inherited enumerable properties. + * + * @param value The value to check. + * @return Returns true if value is a plain object, else false. + */ + isPlainObject(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isPlainObject + */ + isPlainObject(): boolean; + } + + //_.isRegExp + interface LoDashStatic { + /** + * Checks if value is classified as a RegExp object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + */ + isRegExp(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isRegExp + */ + isRegExp(): boolean; + } + + //_.isString + interface LoDashStatic { + /** + * Checks if value is classified as a String primitive or object. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + **/ + isString(value?: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isString + */ + isString(): boolean; + } + + //_.isTypedArray + interface LoDashStatic { + /** + * Checks if value is classified as a typed array. + * @param value The value to check. + * @return Returns true if value is correctly classified, else false. + */ + isTypedArray(value: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isTypedArray + */ + isTypedArray(): boolean; + } + + //_.isUndefined + interface LoDashStatic { + /** + * Checks if value is undefined. + * @param value The value to check. + * @return Returns true if value is undefined, else false. + **/ + isUndefined(value: any): boolean; + } + + interface LoDashWrapperBase { + /** + * see _.isUndefined + */ + isUndefined(): boolean; + } + + //_.lt + interface LoDashStatic { + /** + * Checks if value is less than other. + * @param value The value to compare. + * @param other The other value to compare. + * @return Returns true if value is less than other, else false. + */ + lt(value: any, other: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.lt + */ + lt(other: any): boolean; + } + + //_.lte + interface LoDashStatic { + /** + * Checks if value is less than or equal to other. + * @param value The value to compare. + * @param other The other value to compare. + * @return Returns true if value is less than or equal to other, else false. + */ + lte(value: any, other: any): boolean; + } + + interface LoDashWrapperBase { + /** + * @see _.lte + */ + lte(other: any): boolean; + } + + //_.toPlainObject + interface LoDashStatic { + /** + * Converts value to a plain object flattening inherited enumerable properties of value to own properties + * of the plain object. + * @param value The value to convert. + * @return Returns the converted plain object. + */ + toPlainObject(value?: any): Object; + } + + /******** + * Math * + ********/ + + //_.add + interface LoDashStatic { + /** + * Adds two numbers. + * @param augend The first number to add. + * @param addend The second number to add. + * @return Returns the sum. + */ + add(augend: number, addend: number): number; + } + + interface LoDashWrapper { + /** + * @see _.add + */ + add(addend: number): number; + } + + /********** + * Number * + **********/ + + //_.inRange + interface LoDashStatic { + /** + * Checks if n is between start and up to but not including, end. If end is not specified it’s set to start + * with start then set to 0. + * @param n The number to check. + * @param start The start of the range. + * @param end The end of the range. + * @return Returns true if n is in the range, else false. + */ + inRange(n: number, start: number, end: number): boolean; + + + /** + * @see _.inRange + */ + inRange(n: number, end: number): boolean; + } + + interface LoDashWrapper { + /** + * @see _.inRange + */ + inRange(start: number, end: number): boolean; + + /** + * @see _.inRange + */ + inRange(end: number): boolean; + } + + /********** + * Object * + **********/ + + //_.assign + interface LoDashStatic { + /** + * Assigns own enumerable properties of source object(s) to the destination object. Subsequent + * sources will overwrite property assignments of previous sources. If a callback is provided + * it will be executed to produce the assigned values. The callback is bound to thisArg and + * invoked with two arguments; (objectValue, sourceValue). + * @param object The destination object. + * @param s1-8 The source object(s) + * @param callback The function to customize merging properties. + * @param thisArg The this binding of callback. + * @return The destination object. + **/ + assign( + object: T, + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + assign( + object: T, + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + assign( + object: T, + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + assign( + object: T, + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.assign + **/ + extend( + object: T, + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + } + + interface LoDashObjectWrapper { + /** + * @see _.assign + **/ + assign( + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + assign( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + s5: S5, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + /** + * @see _.assign + **/ + extend( + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + /** + * @see _.assign + **/ + extend( + s1: S1, + s2: S2, + s3: S3, + s4: S4, + s5: S5, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): TResult; + + } + + //_.create + interface LoDashStatic { + /** + * Creates an object that inherits from the given prototype object. If a properties object is provided its own + * enumerable properties are assigned to the created object. + * @param prototype The object to inherit from. + * @param properties The properties to assign to the object. + * @return Returns the new object. + */ + create(prototype: Object, properties?: Object): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.create + */ + create(properties?: Object): LoDashObjectWrapper; + } + + //_.defaults + interface LoDashStatic { + /** + * Assigns own enumerable properties of source object(s) to the destination object for all + * destination properties that resolve to undefined. Once a property is set, additional defaults + * of the same property will be ignored. + * @param object The destination object. + * @param sources The source objects. + * @return The destination object. + **/ + defaults( + object: T, + ...sources: any[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.defaults + **/ + defaults(...sources: any[]): LoDashObjectWrapper + } + + //_.defaultsDeep + interface LoDashStatic { + /** + * This method is like _.defaults except that it recursively assigns default properties. + * @param object The destination object. + * @param sources The source objects. + * @return Returns object. + **/ + defaultsDeep( + object: T, + ...sources: any[]): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.defaultsDeep + **/ + defaultsDeep(...sources: any[]): LoDashObjectWrapper + } + + //_.findKey + interface LoDashStatic { + /** + * This method is like _.findIndex except that it returns the key of the first element that + * passes the callback check, instead of the element itself. + * @param object The object to search. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The key of the found element, else undefined. + **/ + findKey( + object: any, + callback: (value: any) => boolean, + thisArg?: any): string; + + /** + * @see _.findKey + * @param pluckValue _.pluck style callback + **/ + findKey( + object: any, + pluckValue: string): string; + + /** + * @see _.findKey + * @param whereValue _.where style callback + **/ + findKey, T>( + object: T, + whereValue: W): string; + } + + //_.findLastKey + interface LoDashStatic { + /** + * This method is like _.findKey except that it iterates over elements of a collection in the opposite order. + * @param object The object to search. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return The key of the found element, else undefined. + **/ + findLastKey( + object: any, + callback: (value: any) => boolean, + thisArg?: any): string; + + /** + * @see _.findLastKey + * @param pluckValue _.pluck style callback + **/ + findLastKey( + object: any, + pluckValue: string): string; + + /** + * @see _.findLastKey + * @param whereValue _.where style callback + **/ + findLastKey, T>( + object: T, + whereValue: W): string; + } + + //_.forIn + interface LoDashStatic { + /** + * Iterates over own and inherited enumerable properties of an object, executing the callback for + * each property. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). Callbacks may exit iteration early by explicitly returning false. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forIn( + object: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forIn + **/ + forIn( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forIn + **/ + forIn( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.forInRight + interface LoDashStatic { + /** + * This method is like _.forIn except that it iterates over elements of a collection in the + * opposite order. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forInRight( + object: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forInRight + **/ + forInRight( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forInRight + **/ + forInRight( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.forOwn + interface LoDashStatic { + /** + * Iterates over own enumerable properties of an object, executing the callback for each + * property. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). Callbacks may exit iteration early by explicitly returning false. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forOwn( + object: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): Dictionary; + + /** + * @see _.forOwn + **/ + forOwn( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forOwn + **/ + forOwn( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.forOwnRight + interface LoDashStatic { + /** + * This method is like _.forOwn except that it iterates over elements of a collection in the + * opposite order. + * @param object The object to iterate over. + * @param callback The function called per iteration. + * @param thisArg The this binding of callback. + * @return object + **/ + forOwnRight( + object: Dictionary, + callback?: DictionaryIterator, + thisArg?: any): Dictionary; + /** + * @see _.forOwnRight + **/ + forOwnRight( + object: T, + callback?: ObjectIterator, + thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.forOwnRight + **/ + forOwnRight( + callback: ObjectIterator, + thisArg?: any): _.LoDashObjectWrapper; + } + + //_.functions + interface LoDashStatic { + /** + * Creates a sorted array of property names of all enumerable properties, own and inherited, of + * object that have function values. + * @param object The object to inspect. + * @return An array of property names that have function values. + **/ + functions(object: any): string[]; + + /** + * @see _functions + **/ + methods(object: any): string[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.functions + **/ + functions(): _.LoDashArrayWrapper; + + /** + * @see _.functions + **/ + methods(): _.LoDashArrayWrapper; + } + + //_.get + interface LoDashStatic { + /** + * Gets the property value at path of object. If the resolved + * value is undefined the defaultValue is used in its place. + * @param object The object to query. + * @param path The path of the property to get. + * @param defaultValue The value returned if the resolved value is undefined. + * @return Returns the resolved value. + **/ + get(object: Object, + path: string|number|boolean|Array, + defaultValue?:TResult + ): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.get + **/ + get(path: string|number|boolean|Array, + defaultValue?: TResult + ): TResult; + } + + //_.has + interface LoDashStatic { + /** + * Checks if path is a direct property. + * + * @param object The object to query. + * @param path The path to check. + * @return Returns true if path is a direct property, else false. + */ + has(object: any, path: string|number|boolean|Array): boolean; + } + + interface LoDashObjectWrapper { + /** + * @see _.has + */ + has(path: string|number|boolean|Array): boolean; + } + + //_.invert + interface LoDashStatic { + /** + * Creates an object composed of the inverted keys and values of the given object. + * @param object The object to invert. + * @return The created inverted object. + **/ + invert(object: any): any; + } + + //_.isEqual + interface EqCustomizer { + (value: any, other: any, indexOrKey?: number|string): boolean; + } + + interface LoDashStatic { + /** + * Performs a deep comparison between two values to determine if they are equivalent. If customizer is + * provided it is invoked to compare values. If customizer returns undefined comparisons are handled + * by the method instead. The customizer is bound to thisArg and invoked with three + * arguments: (value, other [, index|key]). + * @param value The value to compare. + * @param other The other value to compare. + * @param callback The function to customize value comparisons. + * @param thisArg The this binding of customizer. + * @return True if the values are equivalent, else false. + */ + isEqual(value?: any, + other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + + /** + * @see _.isEqual + */ + eq(value?: any, + other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + } + + interface LoDashWrapper { + /** + * @see _.isEqual + */ + isEqual(other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + + /** + * @see _.isEqual + */ + eq(other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + + } + + interface LoDashArrayWrapper { + /** + * @see _.isEqual + */ + isEqual(other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + + /** + * @see _.isEqual + */ + eq(other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + } + + interface LoDashObjectWrapper { + /** + * @see _.isEqual + */ + isEqual(other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + + /** + * @see _.isEqual + */ + eq(other?: any, + callback?: EqCustomizer, + thisArg?: any): boolean; + } + + //_.keys + interface LoDashStatic { + /** + * Creates an array composed of the own enumerable property names of an object. + * @param object The object to inspect. + * @return An array of property names. + **/ + keys(object?: any): string[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.keys + **/ + keys(): LoDashArrayWrapper + } + + //_.keysIn + interface LoDashStatic { + /** + * Creates an array of the own and inherited enumerable property names of object. + * @param object The object to query. + * @return An array of property names. + **/ + keysIn(object?: any): string[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.keysIn + **/ + keysIn(): LoDashArrayWrapper + } + + //_.mapValues + interface LoDashStatic { + /** + * Creates an object with the same keys as object and values generated by running each own + * enumerable property of object through iteratee. The iteratee function is bound to thisArg + * and invoked with three arguments: (value, key, object). + * + * If a property name is provided iteratee the created "_.property" style callback returns + * the property value of the given element. + * + * If a value is also provided for thisArg the creted "_.matchesProperty" style callback returns + * true for elements that have a matching property value, else false;. + * + * If an object is provided for iteratee the created "_.matches" style callback returns true + * for elements that have the properties of the given object, else false. + * + * @param {Object} object The object to iterate over. + * @param {Function|Object|string} [iteratee=_.identity] The function invoked per iteration. + * @param {Object} [thisArg] The `this` binding of `iteratee`. + * @return {Object} Returns the new mapped object. + */ + mapValues(obj: Dictionary, callback: ObjectIterator, thisArg?: any): Dictionary; + mapValues(obj: Dictionary, where: Dictionary): Dictionary; + mapValues(obj: T, pluck: string): TMapped; + mapValues(obj: T, callback: ObjectIterator, thisArg?: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.mapValues + * TValue is the type of the property values of T. + * TResult is the type output by the ObjectIterator function + */ + mapValues(callback: ObjectIterator, thisArg?: any): LoDashObjectWrapper>; + + /** + * @see _.mapValues + * TResult is the type of the property specified by pluck. + * T should be a Dictionary> + */ + mapValues(pluck: string): LoDashObjectWrapper>; + + /** + * @see _.mapValues + * TResult is the type of the properties on the object specified by pluck. + * T should be a Dictionary>> + */ + mapValues(pluck: string, where: Dictionary): LoDashArrayWrapper>; + + /** + * @see _.mapValues + * TResult is the type of the properties of each object in the values of T + * T should be a Dictionary> + */ + mapValues(where: Dictionary): LoDashArrayWrapper; + } + + //_.merge + interface LoDashStatic { + /** + * Recursively merges own enumerable properties of the source object(s), that don't resolve + * to undefined into the destination object. Subsequent sources will overwrite property + * assignments of previous sources. If a callback is provided it will be executed to produce + * the merged values of the destination and source properties. If the callback returns undefined + * merging will be handled by the method instead. The callback is bound to thisArg and invoked + * with two arguments; (objectValue, sourceValue). + * @param object The destination object. + * @param s1-8 The source object(s) + * @param callback The function to customize merging properties. + * @param thisArg The this binding of callback. + * @return The destination object. + **/ + merge( + object: T, + s1: S1, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.merge + **/ + merge( + object: T, + s1: S1, + s2: S2, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.merge + **/ + merge( + object: T, + s1: S1, + s2: S2, + s3: S3, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + + /** + * @see _.merge + **/ + merge( + object: T, + s1: S1, + s2: S2, + s3: S3, + s4: S4, + callback?: (objectValue: Value, sourceValue: Value) => Value, + thisArg?: any): Result; + } + + //_.omit + interface LoDashStatic { + /** + * Creates a shallow clone of object excluding the specified properties. Property names may be + * specified as individual arguments or as arrays of property names. If a callback is provided + * it will be executed for each property of object omitting the properties the callback returns + * truey for. The callback is bound to thisArg and invoked with three arguments; (value, key, + * object). + * @param object The source object. + * @param keys The properties to omit. + * @return An object without the omitted properties. + **/ + omit( + object: T, + ...keys: string[]): Omitted; + + /** + * @see _.omit + **/ + omit( + object: T, + keys: string[]): Omitted; + + /** + * @see _.omit + **/ + omit( + object: T, + callback: ObjectIterator, + thisArg?: any): Omitted; + } + + interface LoDashObjectWrapper { + /** + * @see _.omit + **/ + omit( + ...keys: string[]): LoDashObjectWrapper; + + /** + * @see _.omit + **/ + omit( + keys: string[]): LoDashObjectWrapper; + + /** + * @see _.omit + **/ + omit( + callback: ObjectIterator, + thisArg?: any): LoDashObjectWrapper; + } + + //_.pairs + interface LoDashStatic { + /** + * Creates a two dimensional array of an object's key-value pairs, + * i.e. [[key1, value1], [key2, value2]]. + * @param object The object to inspect. + * @return Aew array of key-value pairs. + **/ + pairs(object?: any): any[][]; + } + + interface LoDashObjectWrapper { + /** + * @see _.pairs + **/ + pairs(): LoDashArrayWrapper; + } + + //_.pick + interface LoDashStatic { + /** + * Creates an object composed of the picked object properties. Property names may be specified as individual + * arguments or as arrays of property names. If predicate is provided it’s invoked for each property of object + * picking the properties predicate returns truthy for. The predicate is bound to thisArg and invoked with + * three arguments: (value, key, object). + * + * @param object The source object. + * @param predicate The function invoked per iteration or property names to pick, specified as individual + * property names or arrays of property names. + * @param thisArg The this binding of predicate. + * @return An object composed of the picked properties. + */ + pick( + object: T, + predicate: ObjectIterator, + thisArg?: any + ): TResult; + + /** + * @see _.pick + */ + pick( + object: T, + ...predicate: Array> + ): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.pick + */ + pick( + predicate: ObjectIterator, + thisArg?: any + ): LoDashObjectWrapper; + + /** + * @see _.pick + */ + pick( + ...predicate: Array> + ): LoDashObjectWrapper; + } + + //_.set + interface LoDashStatic { + /** + * Sets the property value of path on object. If a portion of path does not exist it is created. + * @param object The object to augment. + * @param path The path of the property to set. + * @param value The value to set. + * @return Returns object. + **/ + set(object: T, + path: string|string[], + value: any): T; + } + + interface LoDashObjectWrapper { + /** + * @see _.set + **/ + set(path: string|string[], + value: any): LoDashObjectWrapper; + } + + //_.transform + interface LoDashStatic { + /** + * An alternative to _.reduce this method transforms object to a new accumulator object which is + * the result of running each of its elements through a callback, with each callback execution + * potentially mutating the accumulator object. The callback is bound to thisArg and invoked with + * four arguments; (accumulator, value, key, object). Callbacks may exit iteration early by + * explicitly returning false. + * @param collection The collection to iterate over. + * @param callback The function called per iteration. + * @param accumulator The custom accumulator value. + * @param thisArg The this binding of callback. + * @return The accumulated value. + **/ + transform( + collection: Array, + callback: MemoVoidIterator, + accumulator: Acc, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: List, + callback: MemoVoidIterator, + accumulator: Acc, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: Dictionary, + callback: MemoVoidIterator, + accumulator: Acc, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: Array, + callback?: MemoVoidIterator, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: List, + callback?: MemoVoidIterator, + thisArg?: any): Acc; + + /** + * @see _.transform + **/ + transform( + collection: Dictionary, + callback?: MemoVoidIterator, + thisArg?: any): Acc; + } + + //_.values + interface LoDashStatic { + /** + * Creates an array of the own enumerable property values of object. + * @param object The object to query. + * @return Returns an array of property values. + **/ + values(object?: any): T[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.values + **/ + values(): LoDashObjectWrapper; + } + + //_.valuesIn + interface LoDashStatic { + /** + * Creates an array of the own and inherited enumerable property values of object. + * @param object The object to query. + * @return Returns the array of property values. + **/ + valuesIn(object?: any): T[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.valuesIn + **/ + valuesIn(): LoDashObjectWrapper; + } + + /********** + * String * + **********/ + + //_.camelCase + interface LoDashStatic { + /** + * Converts string to camel case. + * @param string The string to convert. + * @return Returns the camel cased string. + */ + camelCase(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.camelCase + */ + camelCase(): string; + } + + //_.capitalize + interface LoDashStatic { + capitalize(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.capitalize + */ + capitalize(): string; + } + + //_.deburr + interface LoDashStatic { + /** + * Deburrs string by converting latin-1 supplementary letters to basic latin letters and removing combining + * diacritical marks. + * @param string The string to deburr. + * @return Returns the deburred string. + */ + deburr(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.deburr + */ + deburr(): string; + } + + //_.endsWith + interface LoDashStatic { + /** + * Checks if string ends with the given target string. + * @param string The string to search. + * @param target The string to search for. + * @param position The position to search from. + * @return Returns true if string ends with target, else false. + */ + endsWith(string?: string, target?: string, position?: number): boolean; + } + + interface LoDashWrapper { + /** + * @see _.endsWith + */ + endsWith(target?: string, position?: number): boolean; + } + + // _.escape + interface LoDashStatic { + /** + * Converts the characters "&", "<", ">", '"', "'", and "`", in string to their corresponding HTML entities. + * @param string The string to escape. + * @return Returns the escaped string. + */ + escape(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.escape + */ + escape(): string; + } + + // _.escapeRegExp + interface LoDashStatic { + /** + * Escapes the RegExp special characters "\", "/", "^", "$", ".", "|", "?", "*", "+", "(", ")", "[", "]", + * "{" and "}" in string. + * @param string The string to escape. + * @return Returns the escaped string. + */ + escapeRegExp(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.escapeRegExp + */ + escapeRegExp(): string; + } + + //_.kebabCase + interface LoDashStatic { + /** + * Converts string to kebab case. + * @param string The string to convert. + * @return Returns the kebab cased string. + */ + kebabCase(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.kebabCase + */ + kebabCase(): string; + } + + interface LoDashStatic { + /** + * + * @param string The string to pad. + * @param length The padding length. + * @param chars The string used as padding. + * @return Returns the padded string. + */ + pad(string?: string, length?: number, chars?: string): string; + } + + //_.pad + interface LoDashWrapper { + /** + * @see _.pad + */ + pad(length?: number, chars?: string): string; + } + + //_.padLeft + interface LoDashStatic { + /** + * Pads string on the left side if it’s shorter than length. Padding characters are truncated if they exceed + * length. + * @param string The string to pad. + * @param length The padding length. + * @param chars The string used as padding. + * @return Returns the padded string. + */ + padLeft(string?: string, length?: number, chars?: string): string; + } + + //_.padLeft + interface LoDashWrapper { + /** + * @see _.padLeft + */ + padLeft(length?: number, chars?: string): string; + } + + //_.padRight + interface LoDashStatic { + /** + * Pads string on the right side if it’s shorter than length. Padding characters are truncated if they exceed + * length. + * @param string The string to pad. + * @param length The padding length. + * @param chars The string used as padding. + * @return Returns the padded string. + */ + padRight(string?: string, length?: number, chars?: string): string; + } + + //_.padRight + interface LoDashWrapper { + /** + * @see _.padRight + */ + padRight(length?: number, chars?: string): string; + } + + //_.parseInt + interface LoDashStatic { + /** + * Converts string to an integer of the specified radix. If radix is undefined or 0, a radix of 10 is used + * unless value is a hexadecimal, in which case a radix of 16 is used. + * Note: This method aligns with the ES5 implementation of parseInt. + * @param string The string to convert. + * @param radix The radix to interpret value by. + * @return Returns the converted integer. + */ + parseInt(string: string, radix?: number): number; + } + + interface LoDashWrapper { + /** + * @see _.parseInt + */ + parseInt(radix?: number): number; + } + + //_.repeat + interface LoDashStatic { + /** + * Repeats the given string n times. + * @param string The string to repeat. + * @param n The number of times to repeat the string. + * @return Returns the repeated string. + */ + repeat(string?: string, n?: number): string; + } + + interface LoDashWrapper { + /** + * @see _.repeat + */ + repeat(n?: number): string; + } + + //_.snakeCase + interface LoDashStatic { + /** + * Converts string to snake case. + * @param string The string to convert. + * @return Returns the snake cased string. + */ + snakeCase(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.snakeCase + */ + snakeCase(): string; + } + + //_.startCase + interface LoDashStatic { + /** + * Converts string to start case. + * @param string The string to convert. + * @return Returns the start cased string. + */ + startCase(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.startCase + */ + startCase(): string; + } + + //_.startsWith + interface LoDashStatic { + /** + * Checks if string starts with the given target string. + * @param string The string to search. + * @param target The string to search for. + * @param position The position to search from. + * @return Returns true if string starts with target, else false. + */ + startsWith(string?: string, target?: string, position?: number): boolean; + } + + interface LoDashWrapper { + /** + * @see _.startsWith + */ + startsWith(target?: string, position?: number): boolean; + } + + //_.template + interface TemplateExecutor { + (data?: Object): string; + source: string; + } + + interface LoDashStatic { + /** + * Creates a compiled template function that can interpolate data properties in "interpolate" delimiters, + * HTML-escape interpolated data properties in "escape" delimiters, and execute JavaScript in "evaluate" + * delimiters. Data properties may be accessed as free variables in the template. If a setting object is + * provided it takes precedence over _.templateSettings values. + * + * Note: In the development build _.template utilizes + * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl) for easier + * debugging. + * + * For more information on precompiling templates see + * [lodash's custom builds documentation](https://lodash.com/custom-builds). + * + * For more information on Chrome extension sandboxes see + * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval). + * + * @param string The template string. + * @param options The options object. + * @param options.escape The HTML "escape" delimiter. + * @param options.evaluate The "evaluate" delimiter. + * @param options.imports An object to import into the template as free variables. + * @param options.interpolate The "interpolate" delimiter. + * @param options.variable The data object variable name. + * @return Returns the compiled template function. + */ + template( + string: string, + options?: TemplateSettings): TemplateExecutor; + } + + interface LoDashWrapper { + /** + * @see _.template + */ + template(options?: TemplateSettings): TemplateExecutor; + } + + //_.trim + interface LoDashStatic { + /** + * Removes leading and trailing whitespace or specified characters from string. + * @param string The string to trim. + * @param chars The characters to trim. + * @return Returns the trimmed string. + */ + trim(string?: string, chars?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.trim + */ + trim(chars?: string): string; + } + + //_.trimLeft + interface LoDashStatic { + /** + * Removes leading whitespace or specified characters from string. + * @param string The string to trim. + * @param chars The characters to trim. + * @return Returns the trimmed string. + */ + trimLeft(string?: string, chars?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.trimLeft + */ + trimLeft(chars?: string): string; + } + + //_.trimRight + interface LoDashStatic { + /** + * Removes trailing whitespace or specified characters from string. + * @param string The string to trim. + * @param chars The characters to trim. + * @return Returns the trimmed string. + */ + trimRight(string?: string, chars?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.trimRight + */ + trimRight(chars?: string): string; + } + + //_.trunc + interface TruncOptions { + /** The maximum string length. */ + length?: number; + /** The string to indicate text is omitted. */ + omission?: string; + /** The separator pattern to truncate to. */ + separator?: string|RegExp; + } + + interface LoDashStatic { + /** + * Truncates string if it’s longer than the given maximum string length. The last characters of the truncated + * string are replaced with the omission string which defaults to "…". + * @param string The string to truncate. + * @param options The options object or maximum string length. + * @return Returns the truncated string. + */ + trunc(string?: string, options?: TruncOptions|number): string; + } + + interface LoDashWrapper { + /** + * @see _.trunc + */ + trunc(options?: TruncOptions|number): string; + } + + //_.unescape + interface LoDashStatic { + /** + * The inverse of _.escape; this method converts the HTML entities &, <, >, ", ', and ` + * in string to their corresponding characters. + * @param string The string to unescape. + * @return Returns the unescaped string. + */ + unescape(string?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.unescape + */ + unescape(): string; + } + + //_.words + interface LoDashStatic { + /** + * Splits string into an array of its words. + * @param string The string to inspect. + * @param pattern The pattern to match words. + * @return Returns the words of string. + */ + words(string?: string, pattern?: string|RegExp): string[]; + } + + interface LoDashWrapper { + /** + * @see _.words + */ + words(pattern?: string|RegExp): string[]; + } + + /*********** + * Utility * + ***********/ + + //_.attempt + interface LoDashStatic { + /** + * Attempts to invoke func, returning either the result or the caught error object. Any additional arguments + * are provided to func when it’s invoked. + * @param func The function to attempt. + * @return Returns the func result or error object. + */ + attempt(func: (...args: any[]) => TResult): TResult|Error; + } + + interface LoDashObjectWrapper { + /** + * @see _.attempt + */ + attempt(): TResult|Error; + } + + //_.identity + interface LoDashStatic { + /** + * This method returns the first argument provided to it. + * @param value Any value. + * @return Returns value. + */ + identity(value?: T): T; + } + + interface LoDashWrapper { + /** + * @see _.identity + */ + identity(): T; + } + + interface LoDashArrayWrapper { + /** + * @see _.identity + */ + identity(): T[]; + } + + interface LoDashObjectWrapper { + /** + * @see _.identity + */ + identity(): T; + } + + //_.method + interface LoDashStatic { + /** + * Creates a function that invokes the method at path on a given object. Any additional arguments are provided + * to the invoked method. + * @param path The path of the method to invoke. + * @param args The arguments to invoke the method with. + * @return Returns the new function. + */ + method(path: string, ...args: any[]): (object: any) => TResult; + + /** + * @see _.method + */ + method(path: any[], ...args: any[]): (object: any) => TResult; + } + + interface LoDashWrapper { + /** + * @see _.method + */ + method(...args: any[]): LoDashWrapper<(object: any) => TResult>; + + /** + * @see _.method + */ + method(...args: any[]): LoDashWrapper<(object: any) => TResult>; + } + + interface LoDashArrayWrapper { + /** + * @see _.method + */ + method(...args: any[]): LoDashWrapper<(object: any) => TResult>; + + /** + * @see _.method + */ + method(...args: any[]): LoDashWrapper<(object: any) => TResult>; + } + + //_.methodOf + interface LoDashStatic { + /** + * The opposite of _.method; this method creates a function that invokes the method at a given path on object. + * Any additional arguments are provided to the invoked method. + * @param object The object to query. + * @param args The arguments to invoke the method with. + * @return Returns the new function. + */ + methodOf(object: Object, ...args: any[]): (path: string | any[]) => TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.methodOf + */ + methodOf(...args: any[]): LoDashObjectWrapper<(path: string | any[]) => TResult>; + } + + //_.mixin + interface MixinOptions { + chain?: boolean; + } + + interface LoDashStatic { + /** + * Adds all own enumerable function properties of a source object to the destination object. If object is a + * function then methods are added to its prototype as well. + * + * Note: Use _.runInContext to create a pristine lodash function to avoid conflicts caused by modifying + * the original. + * + * @param object The destination object. + * @param source The object of functions to add. + * @param options The options object. + * @param options.chain Specify whether the functions added are chainable. + * @return Returns object. + */ + mixin( + object: TObject, + source: Dictionary, + options?: MixinOptions + ): TResult; + + /** + * @see _.mixin + */ + mixin( + source: Dictionary, + options?: MixinOptions + ): TResult; + } + + interface LoDashObjectWrapper { + /** + * @see _.mixin + */ + mixin( + source: Dictionary, + options?: MixinOptions + ): LoDashObjectWrapper; + + /** + * @see _.mixin + */ + mixin( + options?: MixinOptions + ): LoDashObjectWrapper; + } + + //_.noConflict + interface LoDashStatic { + /** + * Reverts the '_' variable to its previous value and returns a reference to the lodash function. + * @return The lodash function. + **/ + noConflict(): typeof _; + } + + //_.noop + interface LoDashStatic { + /** + * A no-operation function that returns undefined regardless of the arguments it receives. + * @return undefined + */ + noop(...args: any[]): void; + } + + interface LoDashWrapperBase { + /** + * @see _.noop + */ + noop(...args: any[]): void; + } + + //_.property + interface LoDashStatic { + /** + * Creates a function that returns the property value at path on a given object. + * @param path The path of the property to get. + * @return Returns the new function. + */ + property(path: string|string[]): (obj: TObj) => TResult; + } + + interface LoDashStringWrapper { + /** + * @see _.property + */ + property(): LoDashObjectWrapper<(obj: TObj) => TResult>; + } + + interface LoDashArrayWrapper { + /** + * @see _.property + */ + property(): LoDashObjectWrapper<(obj: TObj) => TResult>; + } + + //_.propertyOf + interface LoDashStatic { + /** + * The opposite of _.property; this method creates a function that returns the property value at a given path + * on object. + * @param object The object to query. + * @return Returns the new function. + */ + propertyOf(object: T): (path: string|string[]) => any; + } + + interface LoDashObjectWrapper { + /** + * @see _.propertyOf + */ + propertyOf(): LoDashObjectWrapper<(path: string|string[]) => any>; + } + + //_.range + interface LoDashStatic { + /** + * Creates an array of numbers (positive and/or negative) progressing from start up to, but not including, end. + * If end is not specified it’s set to start with start then set to 0. If end is less than start a zero-length + * range is created unless a negative step is specified. + * @param start The start of the range. + * @param end The end of the range. + * @param step The value to increment or decrement by. + * @return Returns a new range array. + */ + range( + start: number, + end: number, + step?: number): number[]; + + /** + * @see _.range + */ + range( + end: number, + step?: number): number[]; + } + + interface LoDashWrapper { + /** + * @see _.range + */ + range( + end?: number, + step?: number): LoDashArrayWrapper; + } + + //_.random + interface LoDashStatic { + /** + * Produces a random number between min and max (inclusive). If only one argument is provided a + * number between 0 and the given number will be returned. If floating is truey or either min or + * max are floats a floating-point number will be returned instead of an integer. + * @param max The maximum possible value. + * @param floating Specify returning a floating-point number. + * @return A random number. + **/ + random(max: number, floating?: boolean): number; + + /** + * @see _.random + * @param min The minimum possible value. + * @return A random number between `min` and `max`. + **/ + random(min: number, max: number, floating?: boolean): number; + } + + //_.result + interface LoDashStatic { + /** + * Resolves the value of property on object. If property is a function it will be invoked with + * the this binding of object and its result returned, else the property value is returned. If + * object is false then undefined is returned. + * @param object The object to query. + * @param path The path of the property to resolve. + * @param defaultValue The value returned if the resolved value is undefined. + * @return The resolved value. + **/ + + result(object: any, path: string|string[], defaultValue?: T): T; + } + + //_.runInContext + interface LoDashStatic { + /** + * Create a new lodash function using the given context object. + * @param context The context object + * @returns The lodash function. + **/ + runInContext(context: any): typeof _; + } + + //_.times + interface LoDashStatic { + /** + * Invokes the iteratee function n times, returning an array of the results of each invocation. The iteratee is + * bound to thisArg and invoked with one argument; (index). + * + * @param n The number of times to invoke iteratee. + * @param iteratee The function invoked per iteration. + * @param thisArg The this binding of iteratee. + * @return Returns the array of results. + */ + times( + n: number, + iteratee: (num: number) => TResult, + thisArg?: any + ): TResult[]; + + /** + * @see _.times + */ + times(n: number): number[]; + } + + interface LoDashWrapper { + /** + * @see _.times + */ + times( + iteratee: (num: number) => TResult, + thisArgs?: any + ): LoDashArrayWrapper; + + /** + * @see _.times + */ + times(): LoDashArrayWrapper; + } + + //_.uniqueId + interface LoDashStatic { + /** + * Generates a unique ID. If prefix is provided the ID is appended to it. + * @param prefix The value to prefix the ID with. + * @return Returns the unique ID. + */ + uniqueId(prefix?: string): string; + } + + interface LoDashWrapper { + /** + * @see _.uniqueId + */ + uniqueId(): string; + } + + //_.constant + interface LoDashStatic { + /** + * Creates a function that returns value. + * @param value The value to return from the new function. + * @return Returns the new function. + */ + constant(value: T): () => T; + } + + interface LoDashWrapperBase { + /** + * @see _.constant + */ + constant(): () => TResult; + } + + interface ListIterator { + (value: T, index: number, collection: T[]): TResult; + } + + interface DictionaryIterator { + (value: T, key: string, collection: Dictionary): TResult; + } + + interface ObjectIterator { + (element: T, key: string, collection: any): TResult; + } + + interface MemoVoidIterator { + (prev: TResult, curr: T, indexOrKey: any, list?: T[]): void; + } + interface MemoIterator { + (prev: TResult, curr: T, indexOrKey: any, list?: T[]): TResult; + } + /* + interface MemoListIterator { + (prev: TResult, curr: T, index: number, list?: T[]): TResult; + } + interface MemoObjectIterator { + (prev: TResult, curr: T, index: string, object?: Dictionary): TResult; + } + */ + + //interface Collection {} + + // Common interface between Arrays and jQuery objects + interface List { + [index: number]: T; + length: number; + } + + interface Dictionary { + [index: string]: T; + } +} + +declare module "lodash" { + export = _; +} diff --git a/public/app/headers/moment/moment-node.d.ts b/public/app/headers/moment/moment-node.d.ts new file mode 100644 index 00000000000..b109893a3cf --- /dev/null +++ b/public/app/headers/moment/moment-node.d.ts @@ -0,0 +1,479 @@ +// Type definitions for Moment.js 2.8.0 +// Project: https://github.com/timrwood/moment +// Definitions by: Michael Lakerveld , Aaron King , Hiroki Horiuchi , Dick van den Brink , Adi Dahiya , Matt Brooks +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +declare module moment { + + interface MomentInput { + + /** Year */ + years?: number; + /** Year */ + year?: number; + /** Year */ + y?: number; + + /** Month */ + months?: number; + /** Month */ + month?: number; + /** Month */ + M?: number; + + /** Day/Date */ + days?: number; + /** Day/Date */ + day?: number; + /** Day/Date */ + date?: number; + /** Day/Date */ + d?: number; + + /** Hour */ + hours?: number; + /** Hour */ + hour?: number; + /** Hour */ + h?: number; + + /** Minute */ + minutes?: number; + /** Minute */ + minute?: number; + /** Minute */ + m?: number; + + /** Second */ + seconds?: number; + /** Second */ + second?: number; + /** Second */ + s?: number; + + /** Millisecond */ + milliseconds?: number; + /** Millisecond */ + millisecond?: number; + /** Millisecond */ + ms?: number; + + } + + interface Duration { + + humanize(withSuffix?: boolean): string; + + as(units: string): number; + + milliseconds(): number; + asMilliseconds(): number; + + seconds(): number; + asSeconds(): number; + + minutes(): number; + asMinutes(): number; + + hours(): number; + asHours(): number; + + days(): number; + asDays(): number; + + months(): number; + asMonths(): number; + + years(): number; + asYears(): number; + + add(n: number, p: string): Duration; + add(n: number): Duration; + add(d: Duration): Duration; + + subtract(n: number, p: string): Duration; + subtract(n: number): Duration; + subtract(d: Duration): Duration; + + toISOString(): string; + toJSON(): string; + + } + + interface Moment { + + format(format: string): string; + format(): string; + + fromNow(withoutSuffix?: boolean): string; + + startOf(unitOfTime: string): Moment; + endOf(unitOfTime: string): Moment; + + /** + * Mutates the original moment by adding time. (deprecated in 2.8.0) + * + * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc) + * @param amount the amount you want to add + */ + add(unitOfTime: string, amount: number): Moment; + /** + * Mutates the original moment by adding time. + * + * @param amount the amount you want to add + * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc) + */ + add(amount: number, unitOfTime: string): Moment; + /** + * Mutates the original moment by adding time. Note that the order of arguments can be flipped. + * + * @param amount the amount you want to add + * @param unitOfTime the unit of time you want to add (eg "years" / "hours" etc) + */ + add(amount: string, unitOfTime: string): Moment; + /** + * Mutates the original moment by adding time. + * + * @param objectLiteral an object literal that describes multiple time units {days:7,months:1} + */ + add(objectLiteral: MomentInput): Moment; + /** + * Mutates the original moment by adding time. + * + * @param duration a length of time + */ + add(duration: Duration): Moment; + + /** + * Mutates the original moment by subtracting time. (deprecated in 2.8.0) + * + * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc) + * @param amount the amount you want to subtract + */ + subtract(unitOfTime: string, amount: number): Moment; + /** + * Mutates the original moment by subtracting time. + * + * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc) + * @param amount the amount you want to subtract + */ + subtract(amount: number, unitOfTime: string): Moment; + /** + * Mutates the original moment by subtracting time. Note that the order of arguments can be flipped. + * + * @param amount the amount you want to add + * @param unitOfTime the unit of time you want to subtract (eg "years" / "hours" etc) + */ + subtract(amount: string, unitOfTime: string): Moment; + /** + * Mutates the original moment by subtracting time. + * + * @param objectLiteral an object literal that describes multiple time units {days:7,months:1} + */ + subtract(objectLiteral: MomentInput): Moment; + /** + * Mutates the original moment by subtracting time. + * + * @param duration a length of time + */ + subtract(duration: Duration): Moment; + + calendar(): string; + calendar(start: Moment): string; + + clone(): Moment; + + /** + * @return Unix timestamp, or milliseconds since the epoch. + */ + valueOf(): number; + + local(): Moment; // current date/time in local mode + + utc(): Moment; // current date/time in UTC mode + + isValid(): boolean; + invalidAt(): number; + + year(y: number): Moment; + year(): number; + quarter(): number; + quarter(q: number): Moment; + month(M: number): Moment; + month(M: string): Moment; + month(): number; + day(d: number): Moment; + day(d: string): Moment; + day(): number; + date(d: number): Moment; + date(): number; + hour(h: number): Moment; + hour(): number; + hours(h: number): Moment; + hours(): number; + minute(m: number): Moment; + minute(): number; + minutes(m: number): Moment; + minutes(): number; + second(s: number): Moment; + second(): number; + seconds(s: number): Moment; + seconds(): number; + millisecond(ms: number): Moment; + millisecond(): number; + milliseconds(ms: number): Moment; + milliseconds(): number; + weekday(): number; + weekday(d: number): Moment; + isoWeekday(): number; + isoWeekday(d: number): Moment; + weekYear(): number; + weekYear(d: number): Moment; + isoWeekYear(): number; + isoWeekYear(d: number): Moment; + week(): number; + week(d: number): Moment; + weeks(): number; + weeks(d: number): Moment; + isoWeek(): number; + isoWeek(d: number): Moment; + isoWeeks(): number; + isoWeeks(d: number): Moment; + weeksInYear(): number; + isoWeeksInYear(): number; + dayOfYear(): number; + dayOfYear(d: number): Moment; + + from(f: Moment|string|number|Date|number[], suffix?: boolean): string; + to(f: Moment|string|number|Date|number[], suffix?: boolean): string; + + diff(b: Moment): number; + diff(b: Moment, unitOfTime: string): number; + diff(b: Moment, unitOfTime: string, round: boolean): number; + + toArray(): number[]; + toDate(): Date; + toISOString(): string; + toJSON(): string; + unix(): number; + + isLeapYear(): boolean; + zone(): number; + zone(b: number): Moment; + zone(b: string): Moment; + utcOffset(): number; + utcOffset(b: number): Moment; + utcOffset(b: string): Moment; + daysInMonth(): number; + isDST(): boolean; + + isBefore(): boolean; + isBefore(b: Moment|string|number|Date|number[], granularity?: string): boolean; + + isAfter(): boolean; + isAfter(b: Moment|string|number|Date|number[], granularity?: string): boolean; + + isSame(b: Moment|string|number|Date|number[], granularity?: string): boolean; + isBetween(a: Moment|string|number|Date|number[], b: Moment|string|number|Date|number[], granularity?: string): boolean; + + // Deprecated as of 2.8.0. + lang(language: string): Moment; + lang(reset: boolean): Moment; + lang(): MomentLanguage; + + locale(language: string): Moment; + locale(reset: boolean): Moment; + locale(): string; + + localeData(language: string): Moment; + localeData(reset: boolean): Moment; + localeData(): MomentLanguage; + + // Deprecated as of 2.7.0. + max(date: Moment|string|number|Date|any[]): Moment; + max(date: string, format: string): Moment; + + // Deprecated as of 2.7.0. + min(date: Moment|string|number|Date|any[]): Moment; + min(date: string, format: string): Moment; + + get(unit: string): number; + set(unit: string, value: number): Moment; + + } + + interface MomentCalendar { + + lastDay: any; + sameDay: any; + nextDay: any; + lastWeek: any; + nextWeek: any; + sameElse: any; + + } + + interface BaseMomentLanguage { + months ?: any; + monthsShort ?: any; + weekdays ?: any; + weekdaysShort ?: any; + weekdaysMin ?: any; + relativeTime ?: MomentRelativeTime; + meridiem ?: (hour: number, minute: number, isLowercase: boolean) => string; + calendar ?: MomentCalendar; + ordinal ?: (num: number) => string; + } + + interface MomentLanguage extends BaseMomentLanguage { + longDateFormat?: MomentLongDateFormat; + } + + interface MomentLanguageData extends BaseMomentLanguage { + /** + * @param formatType should be L, LL, LLL, LLLL. + */ + longDateFormat(formatType: string): string; + } + + interface MomentLongDateFormat { + + L: string; + LL: string; + LLL: string; + LLLL: string; + LT: string; + l?: string; + ll?: string; + lll?: string; + llll?: string; + lt?: string; + + } + + interface MomentRelativeTime { + + future: any; + past: any; + s: any; + m: any; + mm: any; + h: any; + hh: any; + d: any; + dd: any; + M: any; + MM: any; + y: any; + yy: any; + + } + + interface MomentStatic { + + version: string; + fn: Moment; + + (): Moment; + (date: number): Moment; + (date: number[]): Moment; + (date: string, format?: string, strict?: boolean): Moment; + (date: string, format?: string, language?: string, strict?: boolean): Moment; + (date: string, formats: string[], strict?: boolean): Moment; + (date: string, formats: string[], language?: string, strict?: boolean): Moment; + (date: string, specialFormat: () => void, strict?: boolean): Moment; + (date: string, specialFormat: () => void, language?: string, strict?: boolean): Moment; + (date: string, formatsIncludingSpecial: any[], strict?: boolean): Moment; + (date: string, formatsIncludingSpecial: any[], language?: string, strict?: boolean): Moment; + (date: Date): Moment; + (date: Moment): Moment; + (date: Object): Moment; + + utc(): Moment; + utc(date: number): Moment; + utc(date: number[]): Moment; + utc(date: string, format?: string, strict?: boolean): Moment; + utc(date: string, format?: string, language?: string, strict?: boolean): Moment; + utc(date: string, formats: string[], strict?: boolean): Moment; + utc(date: string, formats: string[], language?: string, strict?: boolean): Moment; + utc(date: Date): Moment; + utc(date: Moment): Moment; + utc(date: Object): Moment; + + unix(timestamp: number): Moment; + + invalid(parsingFlags?: Object): Moment; + isMoment(): boolean; + isMoment(m: any): boolean; + isDate(m: any): boolean; + isDuration(): boolean; + isDuration(d: any): boolean; + + // Deprecated in 2.8.0. + lang(language?: string): string; + lang(language?: string, definition?: MomentLanguage): string; + + locale(language?: string): string; + locale(language?: string[]): string; + locale(language?: string, definition?: MomentLanguage): string; + + localeData(language?: string): MomentLanguageData; + + longDateFormat: any; + relativeTime: any; + meridiem: (hour: number, minute: number, isLowercase: boolean) => string; + calendar: any; + ordinal: (num: number) => string; + + duration(milliseconds: Number): Duration; + duration(num: Number, unitOfTime: string): Duration; + duration(input: MomentInput): Duration; + duration(object: any): Duration; + duration(): Duration; + + parseZone(date: string): Moment; + + months(): string[]; + months(index: number): string; + months(format: string): string[]; + months(format: string, index: number): string; + monthsShort(): string[]; + monthsShort(index: number): string; + monthsShort(format: string): string[]; + monthsShort(format: string, index: number): string; + + weekdays(): string[]; + weekdays(index: number): string; + weekdays(format: string): string[]; + weekdays(format: string, index: number): string; + weekdaysShort(): string[]; + weekdaysShort(index: number): string; + weekdaysShort(format: string): string[]; + weekdaysShort(format: string, index: number): string; + weekdaysMin(): string[]; + weekdaysMin(index: number): string; + weekdaysMin(format: string): string[]; + weekdaysMin(format: string, index: number): string; + + min(moments: Moment[]): Moment; + max(moments: Moment[]): Moment; + + normalizeUnits(unit: string): string; + relativeTimeThreshold(threshold: string): number|boolean; + relativeTimeThreshold(threshold: string, limit:number): boolean; + + /** + * Constant used to enable explicit ISO_8601 format parsing. + */ + ISO_8601(): void; + + defaultFormat: string; + + } + +} + +declare module 'moment' { + var moment: moment.MomentStatic; + export = moment; +} diff --git a/public/app/headers/moment/moment.d.ts b/public/app/headers/moment/moment.d.ts new file mode 100644 index 00000000000..e7afe769fdf --- /dev/null +++ b/public/app/headers/moment/moment.d.ts @@ -0,0 +1,7 @@ +// Type definitions for Moment.js 2.8.0 +// Project: https://github.com/timrwood/moment +// Definitions by: Michael Lakerveld , Aaron King , Hiroki Horiuchi , Dick van den Brink , Adi Dahiya , Matt Brooks +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/// + diff --git a/public/app/headers/require/require.d.ts b/public/app/headers/require/require.d.ts new file mode 100644 index 00000000000..c42a1bda101 --- /dev/null +++ b/public/app/headers/require/require.d.ts @@ -0,0 +1,369 @@ +// Type definitions for RequireJS 2.1.8 +// Project: http://requirejs.org/ +// Definitions by: Josh Baldwin +// Definitions: https://github.com/borisyankov/DefinitelyTyped + +/* +require-2.1.8.d.ts may be freely distributed under the MIT license. + +Copyright (c) 2013 Josh Baldwin https://github.com/jbaldwin/require.d.ts + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +declare module 'module' { + var mod: { + config: () => any; + id: string; + uri: string; + } + export = mod; +} + +interface RequireError extends Error { + + /** + * The error ID that maps to an ID on a web page. + **/ + requireType: string; + + /** + * Required modules. + **/ + requireModules: string[]; + + /** + * The original error, if there is one (might be null). + **/ + originalError: Error; +} + +interface RequireShim { + + /** + * List of dependencies. + **/ + deps?: string[]; + + /** + * Name the module will be exported as. + **/ + exports?: string; + + /** + * Initialize function with all dependcies passed in, + * if the function returns a value then that value is used + * as the module export value instead of the object + * found via the 'exports' string. + * @param dependencies + * @return + **/ + init?: (...dependencies: any[]) => any; +} + +interface RequireConfig { + + // The root path to use for all module lookups. + baseUrl?: string; + + // Path mappings for module names not found directly under + // baseUrl. + paths?: { [key: string]: any; }; + + // Dictionary of Shim's. + // does not cover case of key->string[] + shim?: { [key: string]: RequireShim; }; + + /** + * For the given module prefix, instead of loading the + * module with the given ID, substitude a different + * module ID. + * + * @example + * requirejs.config({ + * map: { + * 'some/newmodule': { + * 'foo': 'foo1.2' + * }, + * 'some/oldmodule': { + * 'foo': 'foo1.0' + * } + * } + * }); + **/ + map?: { + [id: string]: { + [id: string]: string; + }; + }; + + /** + * AMD configurations, use module.config() to access in + * define() functions + **/ + config?: { [id: string]: {}; }; + + /** + * Configures loading modules from CommonJS packages. + **/ + packages?: {}; + + /** + * The number of seconds to wait before giving up on loading + * a script. The default is 7 seconds. + **/ + waitSeconds?: number; + + /** + * A name to give to a loading context. This allows require.js + * to load multiple versions of modules in a page, as long as + * each top-level require call specifies a unique context string. + **/ + context?: string; + + /** + * An array of dependencies to load. + **/ + deps?: string[]; + + /** + * A function to pass to require that should be require after + * deps have been loaded. + * @param modules + **/ + callback?: (...modules: any[]) => void; + + /** + * If set to true, an error will be thrown if a script loads + * that does not call define() or have shim exports string + * value that can be checked. + **/ + enforceDefine?: boolean; + + /** + * If set to true, document.createElementNS() will be used + * to create script elements. + **/ + xhtml?: boolean; + + /** + * Extra query string arguments appended to URLs that RequireJS + * uses to fetch resources. Most useful to cachce bust when + * the browser or server is not configured correcty. + * + * @example + * urlArgs: "bust= + (new Date()).getTime() + **/ + urlArgs?: string; + + /** + * Specify the value for the type="" attribute used for script + * tags inserted into the document by RequireJS. Default is + * "text/javascript". To use Firefox's JavasScript 1.8 + * features, use "text/javascript;version=1.8". + **/ + scriptType?: string; + +} + +// todo: not sure what to do with this guy +interface RequireModule { + + /** + * + **/ + config(): {}; + +} + +/** +* +**/ +interface RequireMap { + + /** + * + **/ + prefix: string; + + /** + * + **/ + name: string; + + /** + * + **/ + parentMap: RequireMap; + + /** + * + **/ + url: string; + + /** + * + **/ + originalName: string; + + /** + * + **/ + fullName: string; +} + +interface Require { + + /** + * Configure require.js + **/ + config(config: RequireConfig): Require; + + /** + * CommonJS require call + * @param module Module to load + * @return The loaded module + */ + (module: string): any; + + /** + * Start the main app logic. + * Callback is optional. + * Can alternatively use deps and callback. + * @param modules Required modules to load. + **/ + (modules: string[]): void; + + /** + * @see Require() + * @param ready Called when required modules are ready. + **/ + (modules: string[], ready: Function): void; + + /** + * @see http://requirejs.org/docs/api.html#errbacks + * @param ready Called when required modules are ready. + **/ + (modules: string[], ready: Function, errback: Function): void; + + /** + * Generate URLs from require module + * @param module Module to URL + * @return URL string + **/ + toUrl(module: string): string; + + /** + * Returns true if the module has already been loaded and defined. + * @param module Module to check + **/ + defined(module: string): boolean; + + /** + * Returns true if the module has already been requested or is in the process of loading and should be available at some point. + * @param module Module to check + **/ + specified(module: string): boolean; + + /** + * On Error override + * @param err + **/ + onError(err: RequireError, errback?: (err: RequireError) => void): void; + + /** + * Undefine a module + * @param module Module to undefine. + **/ + undef(module: string): void; + + /** + * Semi-private function, overload in special instance of undef() + **/ + onResourceLoad(context: Object, map: RequireMap, depArray: RequireMap[]): void; +} + +interface RequireDefine { + + /** + * Define Simple Name/Value Pairs + * @param config Dictionary of Named/Value pairs for the config. + **/ + (config: { [key: string]: any; }): void; + + /** + * Define function. + * @param func: The function module. + **/ + (func: () => any): void; + + /** + * Define function with dependencies. + * @param deps List of dependencies module IDs. + * @param ready Callback function when the dependencies are loaded. + * callback param deps module dependencies + * callback return module definition + **/ + (deps: string[], ready: Function): void; + + /** + * Define module with simplified CommonJS wrapper. + * @param ready + * callback require requirejs instance + * callback exports exports object + * callback module module + * callback return module definition + **/ + (ready: (require: Require, exports: { [key: string]: any; }, module: RequireModule) => any): void; + + /** + * Define a module with a name and dependencies. + * @param name The name of the module. + * @param deps List of dependencies module IDs. + * @param ready Callback function when the dependencies are loaded. + * callback deps module dependencies + * callback return module definition + **/ + (name: string, deps: string[], ready: Function): void; + + /** + * Define a module with a name. + * @param name The name of the module. + * @param ready Callback function when the dependencies are loaded. + * callback return module definition + **/ + (name: string, ready: Function): void; + + /** + * Used to allow a clear indicator that a global define function (as needed for script src browser loading) conforms + * to the AMD API, any global define function SHOULD have a property called "amd" whose value is an object. + * This helps avoid conflict with any other existing JavaScript code that could have defined a define() function + * that does not conform to the AMD API. + * define.amd.jQuery is specific to jQuery and indicates that the loader is able to account for multiple version + * of jQuery being loaded simultaneously. + */ + amd: Object; +} + +// Ambient declarations for 'require' and 'define' +declare var requirejs: Require; +declare var require: Require; +declare var define: RequireDefine; diff --git a/public/app/panels/graph/graph.tooltip.js b/public/app/panels/graph/graph.tooltip.js index 33c29e4e14a..1154f662a7d 100644 --- a/public/app/panels/graph/graph.tooltip.js +++ b/public/app/panels/graph/graph.tooltip.js @@ -55,9 +55,11 @@ function ($) { hoverIndex = this.findHoverIndexFromData(pos.x, series); results.time = series.data[hoverIndex][0]; - if (scope.panel.stack) { + if (series.stack) { if (scope.panel.tooltip.value_type === 'individual') { value = series.data[hoverIndex][1]; + } else if (!series.stack) { + value = series.data[hoverIndex][1]; } else { last_value += series.data[hoverIndex][1]; value = last_value; @@ -67,7 +69,7 @@ function ($) { } // Highlighting multiple Points depending on the plot type - if (scope.panel.steppedLine || (scope.panel.stack && scope.panel.nullPointMode == "null")) { + if (scope.panel.steppedLine || scope.panel.stack) { // stacked and steppedLine plots can have series with different length. // Stacked series can increase its length on each new stacked serie if null points found, // to speed the index search we begin always on the last found hoverIndex. diff --git a/public/app/panels/graph/legend.js b/public/app/panels/graph/legend.js index beea27ec8b7..8f64f6b4395 100644 --- a/public/app/panels/graph/legend.js +++ b/public/app/panels/graph/legend.js @@ -1,13 +1,12 @@ define([ 'angular', - 'app', 'lodash', 'kbn', 'jquery', 'jquery.flot', 'jquery.flot.time', ], -function (angular, app, _, kbn, $) { +function (angular, _, kbn, $) { 'use strict'; var module = angular.module('grafana.panels.graph'); diff --git a/public/app/panels/graph/module.js b/public/app/panels/graph/module.js index 867f864123c..3fd53cedfd9 100644 --- a/public/app/panels/graph/module.js +++ b/public/app/panels/graph/module.js @@ -1,6 +1,5 @@ define([ 'angular', - 'app', 'jquery', 'lodash', 'kbn', @@ -11,7 +10,7 @@ define([ './graph', './legend', ], -function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { +function (angular, $, _, kbn, moment, TimeSeries, PanelMeta) { 'use strict'; var module = angular.module('grafana.panels.graph'); @@ -175,7 +174,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) { $scope.seriesHandler = function(seriesData, index) { var datapoints = seriesData.datapoints; var alias = seriesData.target; - var color = $scope.panel.aliasColors[alias] || $rootScope.colors[index]; + var colorIndex = index % $rootScope.colors.length; + var color = $scope.panel.aliasColors[alias] || $rootScope.colors[colorIndex]; var series = new TimeSeries({ datapoints: datapoints, diff --git a/public/app/panels/singlestat/module.js b/public/app/panels/singlestat/module.js index b38912605bb..f1ea8561b94 100644 --- a/public/app/panels/singlestat/module.js +++ b/public/app/panels/singlestat/module.js @@ -37,6 +37,7 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { // Set and populate defaults var _d = { links: [], + datasource: null, maxDataPoints: 100, interval: null, targets: [{}], @@ -186,7 +187,9 @@ function (angular, app, _, TimeSeries, kbn, PanelMeta) { data.flotpairs = []; if ($scope.series && $scope.series.length > 0) { - var lastValue = _.last($scope.series[0].datapoints)[0]; + var lastPoint = _.last($scope.series[0].datapoints); + var lastValue = _.isArray(lastPoint) ? lastPoint[0] : null; + if (_.isString(lastValue)) { data.value = 0; data.valueFormated = lastValue; diff --git a/public/app/panels/singlestat/singleStatPanel.js b/public/app/panels/singlestat/singleStatPanel.js index 2463ca1e38e..8ef74ba5820 100644 --- a/public/app/panels/singlestat/singleStatPanel.js +++ b/public/app/panels/singlestat/singleStatPanel.js @@ -183,7 +183,7 @@ function (angular, app, _, $) { elem.click(function() { if (panel.links.length === 0) { return; } var link = panel.links[0]; - var linkInfo = linkSrv.getPanelLinkAnchorInfo(link); + var linkInfo = linkSrv.getPanelLinkAnchorInfo(link, scope.panel.scopedVars); if (panel.links[0].targetBlank) { var redirectWindow = window.open(linkInfo.href, '_blank'); redirectWindow.location; diff --git a/public/app/panels/text/module.js b/public/app/panels/text/module.js index 436a9982b41..7dc98bcf136 100644 --- a/public/app/panels/text/module.js +++ b/public/app/panels/text/module.js @@ -93,7 +93,7 @@ function (angular, app, _, require, PanelMeta) { $scope.updateContent = function(html) { try { - $scope.content = $sce.trustAsHtml(templateSrv.replace(html)); + $scope.content = $sce.trustAsHtml(templateSrv.replace(html, $scope.panel.scopedVars)); } catch(e) { console.log('Text panel error: ', e); $scope.content = $sce.trustAsHtml(html); diff --git a/public/app/panels/timepicker/editor.html b/public/app/panels/timepicker/editor.html deleted file mode 100644 index 34712ad582d..00000000000 --- a/public/app/panels/timepicker/editor.html +++ /dev/null @@ -1,49 +0,0 @@ -
    -
    -
    -
    -
      -
    • - Relative time options -
    • -
    • - -
    • -
    • - Until -
    • -
    • - now- -
    • -
    • - -
    • -
    -
    -
    -
    -
      -
    • - Auto-refresh options -
    • -
    • - -
    • -
    -
    -
    -
    - -

    -
    - - For these changes to fully take effect save and reload the dashboard. - -

    -
    diff --git a/public/app/panels/timepicker/module.html b/public/app/panels/timepicker/module.html deleted file mode 100644 index e73516126ae..00000000000 --- a/public/app/panels/timepicker/module.html +++ /dev/null @@ -1,57 +0,0 @@ -
    - -
    - -
    -
    diff --git a/public/app/panels/timepicker/module.js b/public/app/panels/timepicker/module.js deleted file mode 100644 index 928c3dfe5fc..00000000000 --- a/public/app/panels/timepicker/module.js +++ /dev/null @@ -1,242 +0,0 @@ -/* - - ## Timepicker2 - - ### Parameters - * mode :: The default mode of the panel. Options: 'relative', 'absolute' 'since' Default: 'relative' - * time_options :: An array of possible time options. Default: ['5m','15m','1h','6h','12h','24h','2d','7d','30d'] - * timespan :: The default options selected for the relative view. Default: '15m' - * timefield :: The field in which time is stored in the document. - * refresh: Object containing refresh parameters - * enable :: true/false, enable auto refresh by default. Default: false - * interval :: Seconds between auto refresh. Default: 30 - * min :: The lowest interval a user may set -*/ -define([ - 'angular', - 'app', - 'lodash', - 'moment', - 'kbn' -], -function (angular, app, _, moment, kbn) { - 'use strict'; - - var module = angular.module('grafana.panels.timepicker', []); - app.useModule(module); - - module.controller('timepicker', function($scope, $rootScope, timeSrv) { - - $scope.panelMeta = { - status : "Stable", - description : "" - }; - - // Set and populate defaults - var _d = { - status : "Stable", - time_options : ['5m','15m','1h','6h','12h','24h','2d','7d','30d'], - refresh_intervals : ['5s','10s','30s','1m','5m','15m','30m','1h','2h','1d'], - }; - - _.defaults($scope.panel,_d); - - // ng-pattern regexs - $scope.patterns = { - date: /^[0-9]{2}\/[0-9]{2}\/[0-9]{4}$/, - hour: /^([01]?[0-9]|2[0-3])$/, - minute: /^[0-5][0-9]$/, - second: /^[0-5][0-9]$/, - millisecond: /^[0-9]*$/ - }; - - $scope.timeSrv = timeSrv; - - $scope.$on('refresh', function() { - $scope.init(); - }); - - $scope.init = function() { - var time = timeSrv.timeRange(true); - $scope.panel.now = false; - - var unparsed = timeSrv.timeRange(false); - if (_.isString(unparsed.to) && unparsed.to.indexOf('now') === 0) { - $scope.panel.now = true; - } - - $scope.time = getScopeTimeObj(time.from, time.to); - - $scope.onAppEvent('zoom-out', function() { - $scope.zoom(2); - }); - }; - - $scope.customTime = function() { - // Assume the form is valid since we're setting it to something valid - $scope.input.$setValidity("dummy", true); - $scope.temptime = cloneTime($scope.time); - $scope.temptime.now = $scope.panel.now; - - $scope.temptime.from.date.setHours(0,0,0,0); - $scope.temptime.to.date.setHours(0,0,0,0); - - // Date picker needs the date to be at the start of the day - if(new Date().getTimezoneOffset() < 0) { - $scope.temptime.from.date = moment($scope.temptime.from.date).add(1, 'days').toDate(); - $scope.temptime.to.date = moment($scope.temptime.to.date).add(1, 'days').toDate(); - } - - $scope.appEvent('show-dash-editor', {src: 'app/panels/timepicker/custom.html', scope: $scope }); - }; - - // Constantly validate the input of the fields. This function does not change any date variables - // outside of its own scope - $scope.validate = function(time) { - // Assume the form is valid. There is a hidden dummy input for invalidating it programatically. - $scope.input.$setValidity("dummy", true); - - var _from = datepickerToLocal(time.from.date), - _to = datepickerToLocal(time.to.date), - _t = time; - - if($scope.input.$valid) { - - _from.setHours(_t.from.hour,_t.from.minute,_t.from.second,_t.from.millisecond); - _to.setHours(_t.to.hour,_t.to.minute,_t.to.second,_t.to.millisecond); - - // Check that the objects are valid and to is after from - if(isNaN(_from.getTime()) || isNaN(_to.getTime()) || _from.getTime() >= _to.getTime()) { - $scope.input.$setValidity("dummy", false); - return false; - } - } else { - return false; - } - - return { from: _from, to:_to, now: time.now}; - }; - - $scope.setNow = function() { - $scope.time.to = getTimeObj(new Date()); - }; - - /* - time : { - from: Date - to: Date - } - */ - $scope.setAbsoluteTimeFilter = function (time) { - // Create filter object - var _filter = _.clone(time); - - if(time.now) { - _filter.to = "now"; - } - - // Set the filter - $scope.panel.filter_id = timeSrv.setTime(_filter); - - // Update our representation - $scope.time = getScopeTimeObj(time.from,time.to); - }; - - $scope.setRelativeFilter = function(timespan) { - $scope.panel.now = true; - - var _filter = { - from : "now-"+timespan, - to: "now" - }; - - if ($scope.panel.nowDelay) { - _filter.to = 'now-' + $scope.panel.nowDelay; - } - - timeSrv.setTime(_filter); - - $scope.time = getScopeTimeObj(kbn.parseDate(_filter.from),new Date()); - }; - - var pad = function(n, width, z) { - z = z || '0'; - n = n.toString(); - return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; - }; - - var cloneTime = function(time) { - var _n = { - from: _.clone(time.from), - to: _.clone(time.to) - }; - // Create new dates as _.clone is shallow. - _n.from.date = new Date(_n.from.date); - _n.to.date = new Date(_n.to.date); - return _n; - }; - - var getScopeTimeObj = function(from,to) { - var model = { from: getTimeObj(from), to: getTimeObj(to), }; - - if (model.from.date) { - model.tooltip = $scope.dashboard.formatDate(model.from.date) + '
    to
    '; - model.tooltip += $scope.dashboard.formatDate(model.to.date); - } - else { - model.tooltip = 'Click to set time filter'; - } - - if (timeSrv.time) { - if ($scope.panel.now) { - model.rangeString = moment(model.from.date).fromNow() + ' to ' + - moment(model.to.date).fromNow(); - } - else { - model.rangeString = $scope.dashboard.formatDate(model.from.date, 'MMM D, YYYY HH:mm:ss') + ' to ' + - $scope.dashboard.formatDate(model.to.date, 'MMM D, YYYY HH:mm:ss'); - } - } - - return model; - }; - - var getTimeObj = function(date) { - return { - date: new Date(date), - hour: pad(date.getHours(),2), - minute: pad(date.getMinutes(),2), - second: pad(date.getSeconds(),2), - millisecond: pad(date.getMilliseconds(),3) - }; - }; - - // Do not use the results of this function unless you plan to use setHour/Minutes/etc on the result - var datepickerToLocal = function(date) { - date = moment(date).clone().toDate(); - return moment(new Date(date.getTime() + date.getTimezoneOffset() * 60000)).toDate(); - }; - - $scope.zoom = function(factor) { - var range = timeSrv.timeRange(); - - var timespan = (range.to.valueOf() - range.from.valueOf()); - var center = range.to.valueOf() - timespan/2; - - var to = (center + (timespan*factor)/2); - var from = (center - (timespan*factor)/2); - - if(to > Date.now() && range.to <= Date.now()) { - var offset = to - Date.now(); - from = from - offset; - to = Date.now(); - } - - timeSrv.setTime({ - from: moment.utc(from).toDate(), - to: moment.utc(to).toDate(), - }); - }; - - }); -}); diff --git a/public/app/partials/bootstrap/tab.html b/public/app/partials/bootstrap/tab.html new file mode 100644 index 00000000000..d76dd67caf2 --- /dev/null +++ b/public/app/partials/bootstrap/tab.html @@ -0,0 +1,3 @@ +
  • + {{heading}} +
  • diff --git a/public/app/partials/bootstrap/tabset.html b/public/app/partials/bootstrap/tabset.html new file mode 100644 index 00000000000..acbad38390b --- /dev/null +++ b/public/app/partials/bootstrap/tabset.html @@ -0,0 +1,10 @@ +
    + +
    +
    +
    +
    +
    diff --git a/public/app/partials/confirm_modal.html b/public/app/partials/confirm_modal.html index 8730cfb1596..24802814be1 100644 --- a/public/app/partials/confirm_modal.html +++ b/public/app/partials/confirm_modal.html @@ -12,7 +12,7 @@ {{title}}
    -
    + diff --git a/public/app/partials/inspector.html b/public/app/partials/inspector.html index 14069f31961..7e7c25209f5 100644 --- a/public/app/partials/inspector.html +++ b/public/app/partials/inspector.html @@ -49,12 +49,10 @@
    -
    - -
    -
    -
    - +
    {{message}}
    +
    +{{response}}
    +			
    diff --git a/public/app/partials/login.html b/public/app/partials/login.html index a5894866d5a..630f295ecd3 100644 --- a/public/app/partials/login.html +++ b/public/app/partials/login.html @@ -16,8 +16,8 @@
    -