mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into docs_v5.0
This commit is contained in:
commit
f67bd368d4
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,8 +10,8 @@ awsconfig
|
||||
/public_gen
|
||||
/public/vendor/npm
|
||||
/tmp
|
||||
vendor/phantomjs/phantomjs
|
||||
vendor/phantomjs/phantomjs.exe
|
||||
tools/phantomjs/phantomjs
|
||||
tools/phantomjs/phantomjs.exe
|
||||
profile.out
|
||||
coverage.txt
|
||||
|
||||
|
39
CHANGELOG.md
39
CHANGELOG.md
@ -2,7 +2,7 @@
|
||||
|
||||
Grafana v5.0 is going to be the biggest and most foundational release Grafana has ever had, coming with a ton of UX improvements, a new dashboard grid engine, dashboard folders, user teams and permissions. Checkout out this [video preview](https://www.youtube.com/watch?v=BC_YRNpqj5k) of Grafana v5.
|
||||
|
||||
### New Features
|
||||
### New Major Features
|
||||
- **Dashboards** Dashboard folders, [#1611](https://github.com/grafana/grafana/issues/1611)
|
||||
- **Teams** User groups (teams) implemented. Can be used in folder & dashboard permission list.
|
||||
- **Dashboard grid**: Panels are now layed out in a two dimensional grid (with x, y, w, h). [#9093](https://github.com/grafana/grafana/issues/9093).
|
||||
@ -10,31 +10,22 @@ Grafana v5.0 is going to be the biggest and most foundational release Grafana ha
|
||||
- **UX**: Major update to page header and navigation
|
||||
- **Dashboard settings**: Combine dashboard settings views into one with side menu, [#9750](https://github.com/grafana/grafana/issues/9750)
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* **[dashboard.json]** have been replaced with [dashboard provisioning](http://docs.grafana.org/administration/provisioning/).
|
||||
Config files for provisioning datasources as configuration have changed from `/conf/datasources` to `/conf/provisioning/datasources`.
|
||||
From `/etc/grafana/datasources` to `/etc/grafana/provisioning/datasources` when installed with deb/rpm packages.
|
||||
|
||||
* **Pagerduty** The notifier now defaults to not auto resolve incidents. More details at [#10222](https://github.com/grafana/grafana/issues/10222)
|
||||
|
||||
## New Dashboard Grid
|
||||
|
||||
The new grid engine is major upgrade for how you can position and move panels. It enables new layouts and a much easier dashboard building experience. The change is backwards compatible. Grafana will automatically upgrade your dashboards to the new schema and position panels to match your existing layout. There might be minor differences in panel height.
|
||||
The new grid engine is a major upgrade for how you can position and move panels. It enables new layouts and a much easier dashboard building experience. The change is backward compatible. So you can upgrade your current version to 5.0 without breaking dashboards, but you cannot downgrade from 5.0 to previous versions. Grafana will automatically upgrade your dashboards to the new schema and position panels to match your existing layout. There might be minor differences in panel height. If you upgrade to 5.0 and for some reason want to rollback to the previous version you can restore dashboards to previous versions using dashboard history. But that should only be seen as an emergency solution.
|
||||
|
||||
Dashboard panels and rows are positioned using a gridPos object `{x: 0, y: 0, w: 24, h: 5}`. Units are in grid dimensions (24 columns, 1 height unit 30px). Rows and Panels objects exist (together) in a flat array directly on the dashboard root object. Rows are not needed for layouts anymore and are mainly there for backward compatibility. Some panel plugins that do not respect their panel height might require an update.
|
||||
|
||||
## New Features
|
||||
* **Alerting**: Add support for internal image store [#6922](https://github.com/grafana/grafana/issues/6922), thx [@FunkyM](https://github.com/FunkyM)
|
||||
|
||||
## Minor
|
||||
* **Graph**: Don't hide graph display options (Lines/Points) when draw mode is unchecked [#9770](https://github.com/grafana/grafana/issues/9770), thx [@Jonnymcc](https://github.com/Jonnymcc)
|
||||
* **Prometheus**: Show label name in paren after by/without/on/ignoring/group_left/group_right [#9664](https://github.com/grafana/grafana/pull/9664), thx [@mtanda](https://github.com/mtanda)
|
||||
|
||||
# 4.7.0 (unreleased / v4.7.x branch)
|
||||
|
||||
## Breaking changes
|
||||
|
||||
`[dashboard.json]` have been replaced with [dashboard provisioning](http://docs.grafana.org/administration/provisioning/).
|
||||
|
||||
Config files for provisioning datasources as configuration have changed from `/conf/datasources` to `/conf/provisioning/datasources`.
|
||||
From `/etc/grafana/datasources` to `/etc/grafana/provisioning/datasources` when installed with deb/rpm packages.
|
||||
|
||||
The pagerduty notifier now defaults to not auto resolve incidents. More details at [#10222](https://github.com/grafana/grafana/issues/10222)
|
||||
|
||||
## New Features
|
||||
* **Data Source Proxy**: Add support for whitelisting specified cookies that will be passed through to the data source when proxying data source requests [#5457](https://github.com/grafana/grafana/issues/5457), thanks [@robingustafsson](https://github.com/robingustafsson)
|
||||
* **Postgres/MySQL**: add __timeGroup macro for mysql [#9596](https://github.com/grafana/grafana/pull/9596), thanks [@svenklemm](https://github.com/svenklemm)
|
||||
* **Text**: Text panel are now edited in the ace editor. [#9698](https://github.com/grafana/grafana/pull/9698), thx [@mtanda](https://github.com/mtanda)
|
||||
@ -45,8 +36,11 @@ The pagerduty notifier now defaults to not auto resolve incidents. More details
|
||||
* **Dashboard as cfg**: Load dashboards from file into Grafana on startup/change [#9654](https://github.com/grafana/grafana/issues/9654) [#5269](https://github.com/grafana/grafana/issues/5269)
|
||||
* **Prometheus**: Grafana can now send alerts to Prometheus Alertmanager while firing [#7481](https://github.com/grafana/grafana/issues/7481), thx [@Thib17](https://github.com/Thib17) and [@mtanda](https://github.com/mtanda)
|
||||
* **Table**: Support multiple table formated queries in table panel [#9170](https://github.com/grafana/grafana/issues/9170), thx [@davkal](https://github.com/davkal)
|
||||
* **Security**: Protect against brute force (frequent) login attempts [#7616](https://github.com/grafana/grafana/issues/7616)
|
||||
|
||||
## Minor
|
||||
* **Graph**: Don't hide graph display options (Lines/Points) when draw mode is unchecked [#9770](https://github.com/grafana/grafana/issues/9770), thx [@Jonnymcc](https://github.com/Jonnymcc)
|
||||
* **Prometheus**: Show label name in paren after by/without/on/ignoring/group_left/group_right [#9664](https://github.com/grafana/grafana/pull/9664), thx [@mtanda](https://github.com/mtanda)
|
||||
* **Alert panel**: Adds placeholder text when no alerts are within the time range [#9624](https://github.com/grafana/grafana/issues/9624), thx [@straend](https://github.com/straend)
|
||||
* **Mysql**: MySQL enable MaxOpenCon and MaxIdleCon regards how constring is configured. [#9784](https://github.com/grafana/grafana/issues/9784), thx [@dfredell](https://github.com/dfredell)
|
||||
* **Cloudwatch**: Fixes broken query inspector for cloudwatch [#9661](https://github.com/grafana/grafana/issues/9661), thx [@mtanda](https://github.com/mtanda)
|
||||
@ -59,16 +53,15 @@ The pagerduty notifier now defaults to not auto resolve incidents. More details
|
||||
* **Azure**: Adds support for Azure blob storage as external image stor [#8955](https://github.com/grafana/grafana/issues/8955), thx [@saada](https://github.com/saada)
|
||||
* **Telegram**: Add support for inline image uploads to telegram notifier plugin [#9967](https://github.com/grafana/grafana/pull/9967), thx [@rburchell](https://github.com/rburchell)
|
||||
|
||||
## Tech
|
||||
* **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
|
||||
|
||||
|
||||
## Fixes
|
||||
* **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
|
||||
* **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu)
|
||||
* **Postgres/MySQL**: Control quoting in SQL-queries when using template variables [#9030](https://github.com/grafana/grafana/issues/9030), thanks [@svenklemm](https://github.com/svenklemm)
|
||||
* **Pagerduty**: Pagerduty dont auto resolve incidents by default anymore. [#10222](https://github.com/grafana/grafana/issues/10222)
|
||||
|
||||
## Tech
|
||||
* **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
|
||||
|
||||
# 4.6.3 (2017-12-14)
|
||||
|
||||
## Fixes
|
||||
|
229
Gopkg.lock
generated
229
Gopkg.lock
generated
@ -4,8 +4,8 @@
|
||||
[[projects]]
|
||||
name = "cloud.google.com/go"
|
||||
packages = ["compute/metadata"]
|
||||
revision = "050b16d2314d5fc3d4c9a51e4cd5c7468e77f162"
|
||||
version = "v0.17.0"
|
||||
revision = "767c40d6a2e058483c25fa193e963a22da17236d"
|
||||
version = "v0.18.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
@ -27,9 +27,39 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/ec2query","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/cloudwatch","service/ec2","service/ec2/ec2iface","service/s3","service/sts"]
|
||||
revision = "c13a879e75646fe750d21bcb05bf2cabea9791c1"
|
||||
version = "v1.12.65"
|
||||
packages = [
|
||||
"aws",
|
||||
"aws/awserr",
|
||||
"aws/awsutil",
|
||||
"aws/client",
|
||||
"aws/client/metadata",
|
||||
"aws/corehandlers",
|
||||
"aws/credentials",
|
||||
"aws/credentials/ec2rolecreds",
|
||||
"aws/credentials/endpointcreds",
|
||||
"aws/credentials/stscreds",
|
||||
"aws/defaults",
|
||||
"aws/ec2metadata",
|
||||
"aws/endpoints",
|
||||
"aws/request",
|
||||
"aws/session",
|
||||
"aws/signer/v4",
|
||||
"internal/shareddefaults",
|
||||
"private/protocol",
|
||||
"private/protocol/ec2query",
|
||||
"private/protocol/query",
|
||||
"private/protocol/query/queryutil",
|
||||
"private/protocol/rest",
|
||||
"private/protocol/restxml",
|
||||
"private/protocol/xml/xmlutil",
|
||||
"service/cloudwatch",
|
||||
"service/ec2",
|
||||
"service/ec2/ec2iface",
|
||||
"service/s3",
|
||||
"service/sts"
|
||||
]
|
||||
revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28"
|
||||
version = "v1.12.67"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -43,12 +73,6 @@
|
||||
packages = ["quantile"]
|
||||
revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/bitly/go-simplejson"
|
||||
packages = ["."]
|
||||
revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44"
|
||||
version = "v0.5.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/bmizerany/assert"
|
||||
@ -118,14 +142,19 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/go-macaron/session"
|
||||
packages = [".","memcache","mysql","postgres","redis"]
|
||||
packages = [
|
||||
".",
|
||||
"memcache",
|
||||
"mysql",
|
||||
"postgres",
|
||||
"redis"
|
||||
]
|
||||
revision = "b8e286a0dba8f4999042d6b258daf51b31d08938"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
packages = ["."]
|
||||
revision = "a0583e0143b1624142adab07e0e97fe106d99561"
|
||||
version = "v1.3"
|
||||
revision = "2cc627ac8defc45d65066ae98f898166f580f9a4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-stack/stack"
|
||||
@ -152,8 +181,14 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"]
|
||||
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
||||
packages = [
|
||||
"proto",
|
||||
"ptypes",
|
||||
"ptypes/any",
|
||||
"ptypes/duration",
|
||||
"ptypes/timestamp"
|
||||
]
|
||||
revision = "c65a0412e71e8b9b3bfd22925720d23c0f054237"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -183,7 +218,7 @@
|
||||
branch = "master"
|
||||
name = "github.com/hashicorp/go-hclog"
|
||||
packages = ["."]
|
||||
revision = "ca137eb4b4389c9bc6f1a6d887f056bf16c00510"
|
||||
revision = "5bcb0f17e36442247290887cc914a6e507afa5c4"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/hashicorp/go-plugin"
|
||||
@ -221,7 +256,10 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/klauspost/compress"
|
||||
packages = ["flate","gzip"]
|
||||
packages = [
|
||||
"flate",
|
||||
"gzip"
|
||||
]
|
||||
revision = "6c8db69c4b49dd4df1fff66996cf556176d0b9bf"
|
||||
version = "v1.2.1"
|
||||
|
||||
@ -252,8 +290,11 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/lib/pq"
|
||||
packages = [".","oid"]
|
||||
revision = "27ea5d92de30060e7121ddd543fe14e9a327e0cc"
|
||||
packages = [
|
||||
".",
|
||||
"oid"
|
||||
]
|
||||
revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mattn/go-colorable"
|
||||
@ -287,7 +328,11 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/opentracing/opentracing-go"
|
||||
packages = [".","ext","log"]
|
||||
packages = [
|
||||
".",
|
||||
"ext",
|
||||
"log"
|
||||
]
|
||||
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
|
||||
version = "v1.0.2"
|
||||
|
||||
@ -299,7 +344,12 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/prometheus/client_golang"
|
||||
packages = ["api","api/prometheus/v1","prometheus","prometheus/promhttp"]
|
||||
packages = [
|
||||
"api",
|
||||
"api/prometheus/v1",
|
||||
"prometheus",
|
||||
"prometheus/promhttp"
|
||||
]
|
||||
revision = "967789050ba94deca04a5e84cce8ad472ce313c1"
|
||||
version = "v0.9.0-pre1"
|
||||
|
||||
@ -312,14 +362,23 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/common"
|
||||
packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"]
|
||||
packages = [
|
||||
"expfmt",
|
||||
"internal/bitbucket.org/ww/goautoneg",
|
||||
"model"
|
||||
]
|
||||
revision = "89604d197083d4781071d3c65855d24ecfb0a563"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/procfs"
|
||||
packages = [".","xfs"]
|
||||
revision = "b15cd069a83443be3154b719d0cc9fe8117f09fb"
|
||||
packages = [
|
||||
".",
|
||||
"internal/util",
|
||||
"nfsd",
|
||||
"xfs"
|
||||
]
|
||||
revision = "85fadb6e89903ef7cca6f6a804474cd5ea85b6e1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
@ -335,19 +394,41 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/smartystreets/assertions"
|
||||
packages = [".","internal/go-render/render","internal/oglematchers"]
|
||||
packages = [
|
||||
".",
|
||||
"internal/go-render/render",
|
||||
"internal/oglematchers"
|
||||
]
|
||||
revision = "0b37b35ec7434b77e77a4bb29b79677cced992ea"
|
||||
version = "1.8.1"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/smartystreets/goconvey"
|
||||
packages = ["convey","convey/gotest","convey/reporting"]
|
||||
packages = [
|
||||
"convey",
|
||||
"convey/gotest",
|
||||
"convey/reporting"
|
||||
]
|
||||
revision = "9e8dc3f972df6c8fcc0375ef492c24d0bb204857"
|
||||
version = "1.6.3"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/uber/jaeger-client-go"
|
||||
packages = [".","config","internal/baggage","internal/baggage/remote","internal/spanlog","log","rpcmetrics","thrift-gen/agent","thrift-gen/baggage","thrift-gen/jaeger","thrift-gen/sampling","thrift-gen/zipkincore","utils"]
|
||||
packages = [
|
||||
".",
|
||||
"config",
|
||||
"internal/baggage",
|
||||
"internal/baggage/remote",
|
||||
"internal/spanlog",
|
||||
"log",
|
||||
"rpcmetrics",
|
||||
"thrift-gen/agent",
|
||||
"thrift-gen/baggage",
|
||||
"thrift-gen/jaeger",
|
||||
"thrift-gen/sampling",
|
||||
"thrift-gen/zipkincore",
|
||||
"utils"
|
||||
]
|
||||
revision = "3ac96c6e679cb60a74589b0d0aa7c70a906183f7"
|
||||
version = "v2.11.2"
|
||||
|
||||
@ -359,7 +440,10 @@
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/yudai/gojsondiff"
|
||||
packages = [".","formatter"]
|
||||
packages = [
|
||||
".",
|
||||
"formatter"
|
||||
]
|
||||
revision = "7b1b7adf999dab73a6eb02669c3d82dbb27a3dd6"
|
||||
version = "1.0.0"
|
||||
|
||||
@ -373,18 +457,33 @@
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["pbkdf2"]
|
||||
revision = "13931e22f9e72ea58bb73048bc752b48c6d4d4ac"
|
||||
revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","context/ctxhttp","http2","http2/hpack","idna","internal/timeseries","lex/httplex","trace"]
|
||||
packages = [
|
||||
"context",
|
||||
"context/ctxhttp",
|
||||
"http2",
|
||||
"http2/hpack",
|
||||
"idna",
|
||||
"internal/timeseries",
|
||||
"lex/httplex",
|
||||
"trace"
|
||||
]
|
||||
revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [".","google","internal","jws","jwt"]
|
||||
packages = [
|
||||
".",
|
||||
"google",
|
||||
"internal",
|
||||
"jws",
|
||||
"jwt"
|
||||
]
|
||||
revision = "b28fcf2b08a19742b43084fb40ab78ac6c3d8067"
|
||||
|
||||
[[projects]]
|
||||
@ -397,17 +496,44 @@
|
||||
branch = "master"
|
||||
name = "golang.org/x/sys"
|
||||
packages = ["unix"]
|
||||
revision = "2c42eef0765b9837fbdab12011af7830f55f88f0"
|
||||
revision = "af50095a40f9041b3b38960738837185c26e9419"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/text"
|
||||
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
|
||||
packages = [
|
||||
"collate",
|
||||
"collate/build",
|
||||
"internal/colltab",
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"secure/bidirule",
|
||||
"transform",
|
||||
"unicode/bidi",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
"unicode/rangetable"
|
||||
]
|
||||
revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
|
||||
packages = [
|
||||
".",
|
||||
"cloudsql",
|
||||
"internal",
|
||||
"internal/app_identity",
|
||||
"internal/base",
|
||||
"internal/datastore",
|
||||
"internal/log",
|
||||
"internal/modules",
|
||||
"internal/remote_api",
|
||||
"internal/urlfetch",
|
||||
"urlfetch"
|
||||
]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
@ -419,9 +545,34 @@
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/grpc"
|
||||
packages = [".","balancer","balancer/base","balancer/roundrobin","codes","connectivity","credentials","encoding","grpclb/grpc_lb_v1/messages","grpclog","health","health/grpc_health_v1","internal","keepalive","metadata","naming","peer","resolver","resolver/dns","resolver/passthrough","stats","status","tap","transport"]
|
||||
revision = "7cea4cc846bcf00cbb27595b07da5de875ef7de9"
|
||||
version = "v1.9.1"
|
||||
packages = [
|
||||
".",
|
||||
"balancer",
|
||||
"balancer/base",
|
||||
"balancer/roundrobin",
|
||||
"codes",
|
||||
"connectivity",
|
||||
"credentials",
|
||||
"encoding",
|
||||
"grpclb/grpc_lb_v1/messages",
|
||||
"grpclog",
|
||||
"health",
|
||||
"health/grpc_health_v1",
|
||||
"internal",
|
||||
"keepalive",
|
||||
"metadata",
|
||||
"naming",
|
||||
"peer",
|
||||
"resolver",
|
||||
"resolver/dns",
|
||||
"resolver/passthrough",
|
||||
"stats",
|
||||
"status",
|
||||
"tap",
|
||||
"transport"
|
||||
]
|
||||
revision = "6b51017f791ae1cfbec89c52efdf444b13b550ef"
|
||||
version = "v1.9.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "v3"
|
||||
@ -474,6 +625,6 @@
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "1824c50aa4685fb22643ab89d45457238d9e6c8f7980315c2a41bb35222ea1a7"
|
||||
inputs-digest = "98e8d8f5fb21fe448aeb3db41c9fed85fe3bf80400e553211cf39a9c05720e01"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
17
Gopkg.toml
17
Gopkg.toml
@ -1,4 +1,3 @@
|
||||
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
@ -20,6 +19,11 @@
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
|
||||
ignored = [
|
||||
"github.com/grafana/grafana/data/*",
|
||||
"github.com/grafana/grafana/public/*",
|
||||
"github.com/grafana/grafana/node_modules/*"
|
||||
]
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
@ -38,10 +42,6 @@
|
||||
branch = "master"
|
||||
name = "github.com/benbjohnson/clock"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/bitly/go-simplejson"
|
||||
version = "0.5.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/bmizerany/assert"
|
||||
@ -76,7 +76,8 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-sql-driver/mysql"
|
||||
version = "1.3.0"
|
||||
revision = "2cc627ac8defc45d65066ae98f898166f580f9a4"
|
||||
#version = "1.3.0" //keeping this since we would rather depend on version then commit
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/go-stack/stack"
|
||||
@ -174,7 +175,7 @@
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/gomail.v2"
|
||||
branch = "v2"
|
||||
branch = "v2"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/ini.v1"
|
||||
@ -190,5 +191,5 @@
|
||||
|
||||
[prune]
|
||||
non-go = true
|
||||
test-go = true
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
|
@ -9,7 +9,7 @@ machine:
|
||||
GOPATH: "/home/ubuntu/.go_workspace"
|
||||
ORG_PATH: "github.com/grafana"
|
||||
REPO_PATH: "${ORG_PATH}/grafana"
|
||||
GODIST: "go1.9.2.linux-amd64.tar.gz"
|
||||
GODIST: "go1.9.3.linux-amd64.tar.gz"
|
||||
post:
|
||||
- mkdir -p ~/download
|
||||
- mkdir -p ~/docker
|
||||
|
@ -174,6 +174,9 @@ disable_gravatar = false
|
||||
# data source proxy whitelist (ip_or_domain:port separated by spaces)
|
||||
data_source_proxy_whitelist =
|
||||
|
||||
# disable protection against brute force login attempts
|
||||
disable_brute_force_login_protection = false
|
||||
|
||||
#################################### Snapshots ###########################
|
||||
[snapshots]
|
||||
# snapshot sharing options
|
||||
@ -474,7 +477,7 @@ sampler_param = 1
|
||||
#################################### External Image Storage ##############
|
||||
[external_image_storage]
|
||||
# You can choose between (s3, webdav, gcs, azure_blob, local)
|
||||
provider = local
|
||||
provider =
|
||||
|
||||
[external_image_storage.s3]
|
||||
bucket_url =
|
||||
@ -501,4 +504,4 @@ account_key =
|
||||
container_name =
|
||||
|
||||
[external_image_storage.local]
|
||||
# does not require any configuration
|
||||
# does not require any configuration
|
||||
|
@ -162,6 +162,9 @@ log_queries =
|
||||
# data source proxy whitelist (ip_or_domain:port separated by spaces)
|
||||
;data_source_proxy_whitelist =
|
||||
|
||||
# disable protection against brute force login attempts
|
||||
;disable_brute_force_login_protection = false
|
||||
|
||||
#################################### Snapshots ###########################
|
||||
[snapshots]
|
||||
# snapshot sharing options
|
||||
|
@ -7,4 +7,7 @@
|
||||
MYSQL_PASSWORD: password
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
tmpfs: /var/lib/mysql:rw
|
||||
|
@ -1,3 +1,3 @@
|
||||
FROM prom/prometheus
|
||||
FROM prom/prometheus:v1.8.2
|
||||
ADD prometheus.yml /etc/prometheus/
|
||||
ADD alert.rules /etc/prometheus/
|
||||
|
@ -9,5 +9,6 @@ FROM grafana/docs-base:latest
|
||||
|
||||
COPY config.toml /site
|
||||
COPY awsconfig /site
|
||||
COPY versions.json /site/static/js
|
||||
|
||||
VOLUME ["/site/content"]
|
||||
|
@ -152,7 +152,7 @@ Prometheus Alertmanager | `prometheus-alertmanager` | no
|
||||
Grafana can render the panel associated with the alert rule and include that in the notification. Most Notification Channels require that this image be publicly accessable (Slack and PagerDuty for example). In order to include images in alert notifications, Grafana can upload the image to an image store. It currently supports
|
||||
Amazon S3, Webdav, Google Cloud Storage and Azure Blob Storage. So to set that up you need to configure the [external image uploader](/installation/configuration/#external-image-storage) in your grafana-server ini config file.
|
||||
|
||||
By default the local image store is used which allows Grafana to serve the images by itself. Be aware that some notifiers requires public access to the image to be able to include it in the notification.
|
||||
Be aware that some notifiers requires public access to the image to be able to include it in the notification. So make sure to enable public access to the images. If your using local image uploader, your Grafana instance need to be accessible by the internet.
|
||||
|
||||
Currently only the Email Channels attaches images if no external image store is specified. To include images in alert notifications for other channels then you need to set up an external image store.
|
||||
|
||||
|
@ -68,6 +68,23 @@ server {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### HAProxy configuration with sub path
|
||||
```bash
|
||||
frontend http-in
|
||||
bind *:80
|
||||
use_backend grafana_backend if { path /grafana } or { path_beg /grafana/ }
|
||||
|
||||
backend grafana_backend
|
||||
# Requires haproxy >= 1.6
|
||||
http-request set-path %[path,regsub(^/grafana/?,/)]
|
||||
|
||||
# Works for haproxy < 1.6
|
||||
# reqrep ^([^\ ]*\ /)grafana[/]?(.*) \1\2
|
||||
|
||||
server grafana localhost:3000
|
||||
```
|
||||
|
||||
### IIS URL Rewrite Rule (Windows) with Subpath
|
||||
|
||||
IIS requires that the URL Rewrite module is installed.
|
||||
|
@ -39,7 +39,7 @@ Click a panel title to open the panel menu, then click share in the panel menu t
|
||||
|
||||
### Direct Link Rendered Image
|
||||
|
||||
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.
|
||||
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 `tools/phantomjs/phantomjs`. For Linux, a `phantomjs` binary is included - however, you should ensure that any requisite libraries (e.g. libfontconfig) are available.
|
||||
|
||||
Example of a link to a server-side rendered PNG:
|
||||
|
||||
|
9
docs/versions.json
Normal file
9
docs/versions.json
Normal file
@ -0,0 +1,9 @@
|
||||
[
|
||||
{ "version": "v5.0", "path": "/v5.0", "archived": false },
|
||||
{ "version": "v4.6", "path": "/", "archived": false, "current": true },
|
||||
{ "version": "v4.5", "path": "/v4.5", "archived": true },
|
||||
{ "version": "v4.4", "path": "/v4.4", "archived": true },
|
||||
{ "version": "v4.3", "path": "/v4.3", "archived": true },
|
||||
{ "version": "v4.1", "path": "/v4.1", "archived": true },
|
||||
{ "version": "v3.1", "path": "/v3.1", "archived": true }
|
||||
]
|
@ -102,12 +102,13 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
|
||||
}
|
||||
|
||||
authQuery := login.LoginUserQuery{
|
||||
Username: cmd.User,
|
||||
Password: cmd.Password,
|
||||
Username: cmd.User,
|
||||
Password: cmd.Password,
|
||||
IpAddress: c.Req.RemoteAddr,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&authQuery); err != nil {
|
||||
if err == login.ErrInvalidCredentials {
|
||||
if err == login.ErrInvalidCredentials || err == login.ErrTooManyLoginAttempts {
|
||||
return ApiError(401, "Invalid username or password", err)
|
||||
}
|
||||
|
||||
|
@ -3,21 +3,20 @@ package login
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"crypto/subtle"
|
||||
"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")
|
||||
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
||||
ErrTooManyLoginAttempts = errors.New("Too many consecutive incorrect login attempts for user. Login for user temporarily blocked")
|
||||
)
|
||||
|
||||
type LoginUserQuery struct {
|
||||
Username string
|
||||
Password string
|
||||
User *m.User
|
||||
Username string
|
||||
Password string
|
||||
User *m.User
|
||||
IpAddress string
|
||||
}
|
||||
|
||||
func Init() {
|
||||
@ -26,41 +25,31 @@ func Init() {
|
||||
}
|
||||
|
||||
func AuthenticateUser(query *LoginUserQuery) error {
|
||||
err := loginUsingGrafanaDB(query)
|
||||
if err == nil || err != ErrInvalidCredentials {
|
||||
if err := validateLoginAttempts(query.Username); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.LdapEnabled {
|
||||
for _, server := range LdapCfg.Servers {
|
||||
author := NewLdapAuthenticator(server)
|
||||
err = author.Login(query)
|
||||
if err == nil || err != ErrInvalidCredentials {
|
||||
return err
|
||||
}
|
||||
err := loginUsingGrafanaDB(query)
|
||||
if err == nil || (err != m.ErrUserNotFound && err != ErrInvalidCredentials) {
|
||||
return err
|
||||
}
|
||||
|
||||
ldapEnabled, ldapErr := loginUsingLdap(query)
|
||||
if ldapEnabled {
|
||||
if ldapErr == nil || ldapErr != ErrInvalidCredentials {
|
||||
return ldapErr
|
||||
}
|
||||
|
||||
err = ldapErr
|
||||
}
|
||||
|
||||
if err == ErrInvalidCredentials {
|
||||
saveInvalidLoginAttempt(query)
|
||||
}
|
||||
|
||||
if err == m.ErrUserNotFound {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
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 subtle.ConstantTimeCompare([]byte(passwordHashed), []byte(user.Password)) != 1 {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
query.User = user
|
||||
return nil
|
||||
}
|
||||
|
214
pkg/login/auth_test.go
Normal file
214
pkg/login/auth_test.go
Normal file
@ -0,0 +1,214 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestAuthenticateUser(t *testing.T) {
|
||||
Convey("Authenticate user", t, func() {
|
||||
authScenario("When a user authenticates having too many login attempts", func(sc *authScenarioContext) {
|
||||
mockLoginAttemptValidation(ErrTooManyLoginAttempts, sc)
|
||||
mockLoginUsingGrafanaDB(nil, sc)
|
||||
mockLoginUsingLdap(true, nil, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, ErrTooManyLoginAttempts)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When grafana user authenticate with valid credentials", func(sc *authScenarioContext) {
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(nil, sc)
|
||||
mockLoginUsingLdap(true, ErrInvalidCredentials, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, nil)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When grafana user authenticate and unexpected error occurs", func(sc *authScenarioContext) {
|
||||
customErr := errors.New("custom")
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(customErr, sc)
|
||||
mockLoginUsingLdap(true, ErrInvalidCredentials, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, customErr)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeFalse)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) {
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
|
||||
mockLoginUsingLdap(false, nil, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) {
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
|
||||
mockLoginUsingLdap(true, ErrInvalidCredentials, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) {
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
|
||||
mockLoginUsingLdap(true, nil, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldBeNil)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) {
|
||||
customErr := errors.New("custom")
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(m.ErrUserNotFound, sc)
|
||||
mockLoginUsingLdap(true, customErr, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, customErr)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
|
||||
authScenario("When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) {
|
||||
mockLoginAttemptValidation(nil, sc)
|
||||
mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc)
|
||||
mockLoginUsingLdap(true, ErrInvalidCredentials, sc)
|
||||
mockSaveInvalidLoginAttempt(sc)
|
||||
|
||||
err := AuthenticateUser(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
So(sc.loginAttemptValidationWasCalled, ShouldBeTrue)
|
||||
So(sc.grafanaLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.ldapLoginWasCalled, ShouldBeTrue)
|
||||
So(sc.saveInvalidLoginAttemptWasCalled, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type authScenarioContext struct {
|
||||
loginUserQuery *LoginUserQuery
|
||||
grafanaLoginWasCalled bool
|
||||
ldapLoginWasCalled bool
|
||||
loginAttemptValidationWasCalled bool
|
||||
saveInvalidLoginAttemptWasCalled bool
|
||||
}
|
||||
|
||||
type authScenarioFunc func(sc *authScenarioContext)
|
||||
|
||||
func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) {
|
||||
loginUsingGrafanaDB = func(query *LoginUserQuery) error {
|
||||
sc.grafanaLoginWasCalled = true
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func mockLoginUsingLdap(enabled bool, err error, sc *authScenarioContext) {
|
||||
loginUsingLdap = func(query *LoginUserQuery) (bool, error) {
|
||||
sc.ldapLoginWasCalled = true
|
||||
return enabled, err
|
||||
}
|
||||
}
|
||||
|
||||
func mockLoginAttemptValidation(err error, sc *authScenarioContext) {
|
||||
validateLoginAttempts = func(username string) error {
|
||||
sc.loginAttemptValidationWasCalled = true
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func mockSaveInvalidLoginAttempt(sc *authScenarioContext) {
|
||||
saveInvalidLoginAttempt = func(query *LoginUserQuery) {
|
||||
sc.saveInvalidLoginAttemptWasCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
func authScenario(desc string, fn authScenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origLoginUsingGrafanaDB := loginUsingGrafanaDB
|
||||
origLoginUsingLdap := loginUsingLdap
|
||||
origValidateLoginAttempts := validateLoginAttempts
|
||||
origSaveInvalidLoginAttempt := saveInvalidLoginAttempt
|
||||
|
||||
sc := &authScenarioContext{
|
||||
loginUserQuery: &LoginUserQuery{
|
||||
Username: "user",
|
||||
Password: "pwd",
|
||||
IpAddress: "192.168.1.1:56433",
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
loginUsingGrafanaDB = origLoginUsingGrafanaDB
|
||||
loginUsingLdap = origLoginUsingLdap
|
||||
validateLoginAttempts = origValidateLoginAttempts
|
||||
saveInvalidLoginAttempt = origSaveInvalidLoginAttempt
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
48
pkg/login/brute_force_login_protection.go
Normal file
48
pkg/login/brute_force_login_protection.go
Normal file
@ -0,0 +1,48 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
maxInvalidLoginAttempts int64 = 5
|
||||
loginAttemptsWindow time.Duration = time.Minute * 5
|
||||
)
|
||||
|
||||
var validateLoginAttempts = func(username string) error {
|
||||
if setting.DisableBruteForceLoginProtection {
|
||||
return nil
|
||||
}
|
||||
|
||||
loginAttemptCountQuery := m.GetUserLoginAttemptCountQuery{
|
||||
Username: username,
|
||||
Since: time.Now().Add(-loginAttemptsWindow),
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&loginAttemptCountQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if loginAttemptCountQuery.Result >= maxInvalidLoginAttempts {
|
||||
return ErrTooManyLoginAttempts
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var saveInvalidLoginAttempt = func(query *LoginUserQuery) {
|
||||
if setting.DisableBruteForceLoginProtection {
|
||||
return
|
||||
}
|
||||
|
||||
loginAttemptCommand := m.CreateLoginAttemptCommand{
|
||||
Username: query.Username,
|
||||
IpAddress: query.IpAddress,
|
||||
}
|
||||
|
||||
bus.Dispatch(&loginAttemptCommand)
|
||||
}
|
125
pkg/login/brute_force_login_protection_test.go
Normal file
125
pkg/login/brute_force_login_protection_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestLoginAttemptsValidation(t *testing.T) {
|
||||
Convey("Validate login attempts", t, func() {
|
||||
Convey("Given brute force login protection enabled", func() {
|
||||
setting.DisableBruteForceLoginProtection = false
|
||||
|
||||
Convey("When user login attempt count equals max-1 ", func() {
|
||||
withLoginAttempts(maxInvalidLoginAttempts - 1)
|
||||
err := validateLoginAttempts("user")
|
||||
|
||||
Convey("it should not result in error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user login attempt count equals max ", func() {
|
||||
withLoginAttempts(maxInvalidLoginAttempts)
|
||||
err := validateLoginAttempts("user")
|
||||
|
||||
Convey("it should result in too many login attempts error", func() {
|
||||
So(err, ShouldEqual, ErrTooManyLoginAttempts)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user login attempt count is greater than max ", func() {
|
||||
withLoginAttempts(maxInvalidLoginAttempts + 5)
|
||||
err := validateLoginAttempts("user")
|
||||
|
||||
Convey("it should result in too many login attempts error", func() {
|
||||
So(err, ShouldEqual, ErrTooManyLoginAttempts)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving invalid login attempt", func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
createLoginAttemptCmd := &m.CreateLoginAttemptCommand{}
|
||||
|
||||
bus.AddHandler("test", func(cmd *m.CreateLoginAttemptCommand) error {
|
||||
createLoginAttemptCmd = cmd
|
||||
return nil
|
||||
})
|
||||
|
||||
saveInvalidLoginAttempt(&LoginUserQuery{
|
||||
Username: "user",
|
||||
Password: "pwd",
|
||||
IpAddress: "192.168.1.1:56433",
|
||||
})
|
||||
|
||||
Convey("it should dispatch command", func() {
|
||||
So(createLoginAttemptCmd, ShouldNotBeNil)
|
||||
So(createLoginAttemptCmd.Username, ShouldEqual, "user")
|
||||
So(createLoginAttemptCmd.IpAddress, ShouldEqual, "192.168.1.1:56433")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given brute force login protection disabled", func() {
|
||||
setting.DisableBruteForceLoginProtection = true
|
||||
|
||||
Convey("When user login attempt count equals max-1 ", func() {
|
||||
withLoginAttempts(maxInvalidLoginAttempts - 1)
|
||||
err := validateLoginAttempts("user")
|
||||
|
||||
Convey("it should not result in error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user login attempt count equals max ", func() {
|
||||
withLoginAttempts(maxInvalidLoginAttempts)
|
||||
err := validateLoginAttempts("user")
|
||||
|
||||
Convey("it should not result in error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When user login attempt count is greater than max ", func() {
|
||||
withLoginAttempts(maxInvalidLoginAttempts + 5)
|
||||
err := validateLoginAttempts("user")
|
||||
|
||||
Convey("it should not result in error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When saving invalid login attempt", func() {
|
||||
defer bus.ClearBusHandlers()
|
||||
createLoginAttemptCmd := (*m.CreateLoginAttemptCommand)(nil)
|
||||
|
||||
bus.AddHandler("test", func(cmd *m.CreateLoginAttemptCommand) error {
|
||||
createLoginAttemptCmd = cmd
|
||||
return nil
|
||||
})
|
||||
|
||||
saveInvalidLoginAttempt(&LoginUserQuery{
|
||||
Username: "user",
|
||||
Password: "pwd",
|
||||
IpAddress: "192.168.1.1:56433",
|
||||
})
|
||||
|
||||
Convey("it should not dispatch command", func() {
|
||||
So(createLoginAttemptCmd, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func withLoginAttempts(loginAttempts int64) {
|
||||
bus.AddHandler("test", func(query *m.GetUserLoginAttemptCountQuery) error {
|
||||
query.Result = loginAttempts
|
||||
return nil
|
||||
})
|
||||
}
|
35
pkg/login/grafana_login.go
Normal file
35
pkg/login/grafana_login.go
Normal file
@ -0,0 +1,35 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var validatePassword = func(providedPassword string, userPassword string, userSalt string) error {
|
||||
passwordHashed := util.EncodePassword(providedPassword, userSalt)
|
||||
if subtle.ConstantTimeCompare([]byte(passwordHashed), []byte(userPassword)) != 1 {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var loginUsingGrafanaDB = func(query *LoginUserQuery) error {
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user := userQuery.Result
|
||||
|
||||
if err := validatePassword(query.Password, user.Password, user.Salt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.User = user
|
||||
return nil
|
||||
}
|
139
pkg/login/grafana_login_test.go
Normal file
139
pkg/login/grafana_login_test.go
Normal file
@ -0,0 +1,139 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestGrafanaLogin(t *testing.T) {
|
||||
Convey("Login using Grafana DB", t, func() {
|
||||
grafanaLoginScenario("When login with non-existing user", func(sc *grafanaLoginScenarioContext) {
|
||||
sc.withNonExistingUser()
|
||||
err := loginUsingGrafanaDB(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in user not found error", func() {
|
||||
So(err, ShouldEqual, m.ErrUserNotFound)
|
||||
})
|
||||
|
||||
Convey("it should not call password validation", func() {
|
||||
So(sc.validatePasswordCalled, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("it should not pupulate user object", func() {
|
||||
So(sc.loginUserQuery.User, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
grafanaLoginScenario("When login with invalid credentials", func(sc *grafanaLoginScenarioContext) {
|
||||
sc.withInvalidPassword()
|
||||
err := loginUsingGrafanaDB(sc.loginUserQuery)
|
||||
|
||||
Convey("it should result in invalid credentials error", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
})
|
||||
|
||||
Convey("it should call password validation", func() {
|
||||
So(sc.validatePasswordCalled, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("it should not pupulate user object", func() {
|
||||
So(sc.loginUserQuery.User, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
grafanaLoginScenario("When login with valid credentials", func(sc *grafanaLoginScenarioContext) {
|
||||
sc.withValidCredentials()
|
||||
err := loginUsingGrafanaDB(sc.loginUserQuery)
|
||||
|
||||
Convey("it should not result in error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("it should call password validation", func() {
|
||||
So(sc.validatePasswordCalled, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("it should pupulate user object", func() {
|
||||
So(sc.loginUserQuery.User, ShouldNotBeNil)
|
||||
So(sc.loginUserQuery.User.Login, ShouldEqual, sc.loginUserQuery.Username)
|
||||
So(sc.loginUserQuery.User.Password, ShouldEqual, sc.loginUserQuery.Password)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type grafanaLoginScenarioContext struct {
|
||||
loginUserQuery *LoginUserQuery
|
||||
validatePasswordCalled bool
|
||||
}
|
||||
|
||||
type grafanaLoginScenarioFunc func(c *grafanaLoginScenarioContext)
|
||||
|
||||
func grafanaLoginScenario(desc string, fn grafanaLoginScenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origValidatePassword := validatePassword
|
||||
|
||||
sc := &grafanaLoginScenarioContext{
|
||||
loginUserQuery: &LoginUserQuery{
|
||||
Username: "user",
|
||||
Password: "pwd",
|
||||
IpAddress: "192.168.1.1:56433",
|
||||
},
|
||||
validatePasswordCalled: false,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
validatePassword = origValidatePassword
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func mockPasswordValidation(valid bool, sc *grafanaLoginScenarioContext) {
|
||||
validatePassword = func(providedPassword string, userPassword string, userSalt string) error {
|
||||
sc.validatePasswordCalled = true
|
||||
|
||||
if !valid {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *grafanaLoginScenarioContext) getUserByLoginQueryReturns(user *m.User) {
|
||||
bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error {
|
||||
if user == nil {
|
||||
return m.ErrUserNotFound
|
||||
}
|
||||
|
||||
query.Result = user
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (sc *grafanaLoginScenarioContext) withValidCredentials() {
|
||||
sc.getUserByLoginQueryReturns(&m.User{
|
||||
Id: 1,
|
||||
Login: sc.loginUserQuery.Username,
|
||||
Password: sc.loginUserQuery.Password,
|
||||
Salt: "salt",
|
||||
})
|
||||
mockPasswordValidation(true, sc)
|
||||
}
|
||||
|
||||
func (sc *grafanaLoginScenarioContext) withNonExistingUser() {
|
||||
sc.getUserByLoginQueryReturns(nil)
|
||||
}
|
||||
|
||||
func (sc *grafanaLoginScenarioContext) withInvalidPassword() {
|
||||
sc.getUserByLoginQueryReturns(&m.User{
|
||||
Password: sc.loginUserQuery.Password,
|
||||
Salt: "salt",
|
||||
})
|
||||
mockPasswordValidation(false, sc)
|
||||
}
|
21
pkg/login/ldap_login.go
Normal file
21
pkg/login/ldap_login.go
Normal file
@ -0,0 +1,21 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var loginUsingLdap = func(query *LoginUserQuery) (bool, error) {
|
||||
if !setting.LdapEnabled {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, server := range LdapCfg.Servers {
|
||||
author := NewLdapAuthenticator(server)
|
||||
err := author.Login(query)
|
||||
if err == nil || err != ErrInvalidCredentials {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
return true, ErrInvalidCredentials
|
||||
}
|
172
pkg/login/ldap_login_test.go
Normal file
172
pkg/login/ldap_login_test.go
Normal file
@ -0,0 +1,172 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestLdapLogin(t *testing.T) {
|
||||
Convey("Login using ldap", t, func() {
|
||||
Convey("Given ldap enabled and a server configured", func() {
|
||||
setting.LdapEnabled = true
|
||||
LdapCfg.Servers = append(LdapCfg.Servers,
|
||||
&LdapServerConf{
|
||||
Host: "",
|
||||
})
|
||||
|
||||
ldapLoginScenario("When login with invalid credentials", func(sc *ldapLoginScenarioContext) {
|
||||
sc.withLoginResult(false)
|
||||
enabled, err := loginUsingLdap(sc.loginUserQuery)
|
||||
|
||||
Convey("it should return true", func() {
|
||||
So(enabled, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("it should return invalid credentials error", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
})
|
||||
|
||||
Convey("it should call ldap login", func() {
|
||||
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
||||
ldapLoginScenario("When login with valid credentials", func(sc *ldapLoginScenarioContext) {
|
||||
sc.withLoginResult(true)
|
||||
enabled, err := loginUsingLdap(sc.loginUserQuery)
|
||||
|
||||
Convey("it should return true", func() {
|
||||
So(enabled, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("it should not return error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("it should call ldap login", func() {
|
||||
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given ldap enabled and no server configured", func() {
|
||||
setting.LdapEnabled = true
|
||||
LdapCfg.Servers = make([]*LdapServerConf, 0)
|
||||
|
||||
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
|
||||
sc.withLoginResult(true)
|
||||
enabled, err := loginUsingLdap(sc.loginUserQuery)
|
||||
|
||||
Convey("it should return true", func() {
|
||||
So(enabled, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("it should return invalid credentials error", func() {
|
||||
So(err, ShouldEqual, ErrInvalidCredentials)
|
||||
})
|
||||
|
||||
Convey("it should not call ldap login", func() {
|
||||
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given ldap disabled", func() {
|
||||
setting.LdapEnabled = false
|
||||
|
||||
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
|
||||
sc.withLoginResult(false)
|
||||
enabled, err := loginUsingLdap(&LoginUserQuery{
|
||||
Username: "user",
|
||||
Password: "pwd",
|
||||
})
|
||||
|
||||
Convey("it should return false", func() {
|
||||
So(enabled, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("it should not return error", func() {
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("it should not call ldap login", func() {
|
||||
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func mockLdapAuthenticator(valid bool) *mockLdapAuther {
|
||||
mock := &mockLdapAuther{
|
||||
validLogin: valid,
|
||||
}
|
||||
|
||||
NewLdapAuthenticator = func(server *LdapServerConf) ILdapAuther {
|
||||
return mock
|
||||
}
|
||||
|
||||
return mock
|
||||
}
|
||||
|
||||
type mockLdapAuther struct {
|
||||
validLogin bool
|
||||
loginCalled bool
|
||||
}
|
||||
|
||||
func (a *mockLdapAuther) Login(query *LoginUserQuery) error {
|
||||
a.loginCalled = true
|
||||
|
||||
if !a.validLogin {
|
||||
return ErrInvalidCredentials
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *mockLdapAuther) SyncSignedInUser(signedInUser *m.SignedInUser) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *mockLdapAuther) GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (a *mockLdapAuther) SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type ldapLoginScenarioContext struct {
|
||||
loginUserQuery *LoginUserQuery
|
||||
ldapAuthenticatorMock *mockLdapAuther
|
||||
}
|
||||
|
||||
type ldapLoginScenarioFunc func(c *ldapLoginScenarioContext)
|
||||
|
||||
func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewLdapAuthenticator := NewLdapAuthenticator
|
||||
|
||||
sc := &ldapLoginScenarioContext{
|
||||
loginUserQuery: &LoginUserQuery{
|
||||
Username: "user",
|
||||
Password: "pwd",
|
||||
IpAddress: "192.168.1.1:56433",
|
||||
},
|
||||
ldapAuthenticatorMock: &mockLdapAuther{},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
NewLdapAuthenticator = origNewLdapAuthenticator
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func (sc *ldapLoginScenarioContext) withLoginResult(valid bool) {
|
||||
sc.ldapAuthenticatorMock = mockLdapAuthenticator(valid)
|
||||
}
|
36
pkg/models/login_attempt.go
Normal file
36
pkg/models/login_attempt.go
Normal file
@ -0,0 +1,36 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoginAttempt struct {
|
||||
Id int64
|
||||
Username string
|
||||
IpAddress string
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// COMMANDS
|
||||
|
||||
type CreateLoginAttemptCommand struct {
|
||||
Username string
|
||||
IpAddress string
|
||||
|
||||
Result LoginAttempt
|
||||
}
|
||||
|
||||
type DeleteOldLoginAttemptsCommand struct {
|
||||
OlderThan time.Time
|
||||
DeletedRows int64
|
||||
}
|
||||
|
||||
// ---------------------
|
||||
// QUERIES
|
||||
|
||||
type GetUserLoginAttemptCountQuery struct {
|
||||
Username string
|
||||
Since time.Time
|
||||
Result int64
|
||||
}
|
@ -2,6 +2,7 @@ package wrapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/null"
|
||||
@ -67,11 +68,16 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
|
||||
}
|
||||
|
||||
for _, r := range pbres.Results {
|
||||
res.Results[r.RefId] = &tsdb.QueryResult{
|
||||
qr := &tsdb.QueryResult{
|
||||
RefId: r.RefId,
|
||||
Series: []*tsdb.TimeSeries{},
|
||||
}
|
||||
|
||||
if r.Error != "" {
|
||||
qr.Error = errors.New(r.Error)
|
||||
qr.ErrorString = r.Error
|
||||
}
|
||||
|
||||
for _, s := range r.GetSeries() {
|
||||
points := tsdb.TimeSeriesPoints{}
|
||||
|
||||
@ -80,7 +86,7 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
|
||||
points = append(points, po)
|
||||
}
|
||||
|
||||
res.Results[r.RefId].Series = append(res.Results[r.RefId].Series, &tsdb.TimeSeries{
|
||||
qr.Series = append(qr.Series, &tsdb.TimeSeries{
|
||||
Name: s.Name,
|
||||
Tags: s.Tags,
|
||||
Points: points,
|
||||
@ -91,7 +97,9 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.Results[r.RefId].Tables = mappedTables
|
||||
qr.Tables = mappedTables
|
||||
|
||||
res.Results[r.RefId] = qr
|
||||
}
|
||||
|
||||
return res, nil
|
||||
|
@ -46,6 +46,7 @@ func (service *CleanUpService) start(ctx context.Context) error {
|
||||
service.cleanUpTmpFiles()
|
||||
service.deleteExpiredSnapshots()
|
||||
service.deleteExpiredDashboardVersions()
|
||||
service.deleteOldLoginAttempts()
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
@ -88,3 +89,18 @@ func (service *CleanUpService) deleteExpiredSnapshots() {
|
||||
func (service *CleanUpService) deleteExpiredDashboardVersions() {
|
||||
bus.Dispatch(&m.DeleteExpiredVersionsCommand{})
|
||||
}
|
||||
|
||||
func (service *CleanUpService) deleteOldLoginAttempts() {
|
||||
if setting.DisableBruteForceLoginProtection {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := m.DeleteOldLoginAttemptsCommand{
|
||||
OlderThan: time.Now().Add(time.Minute * -10),
|
||||
}
|
||||
if err := bus.Dispatch(&cmd); err != nil {
|
||||
service.log.Error("Problem deleting expired login attempts", "error", err.Error())
|
||||
} else {
|
||||
service.log.Debug("Deleted expired login attempts", "rows affected", cmd.DeletedRows)
|
||||
}
|
||||
}
|
||||
|
@ -5,20 +5,25 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type configReader struct {
|
||||
path string
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
|
||||
var dashboards []*DashboardsAsConfig
|
||||
|
||||
files, err := ioutil.ReadDir(cr.path)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
cr.log.Error("cant read dashboard provisioning files from directory", "path", cr.path)
|
||||
return dashboards, nil
|
||||
}
|
||||
|
||||
var dashboards []*DashboardsAsConfig
|
||||
for _, file := range files {
|
||||
if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") {
|
||||
continue
|
||||
@ -30,13 +35,13 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var datasource []*DashboardsAsConfig
|
||||
err = yaml.Unmarshal(yamlFile, &datasource)
|
||||
var dashCfg []*DashboardsAsConfig
|
||||
err = yaml.Unmarshal(yamlFile, &dashCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dashboards = append(dashboards, datasource...)
|
||||
dashboards = append(dashboards, dashCfg...)
|
||||
}
|
||||
|
||||
for i := range dashboards {
|
||||
|
@ -3,6 +3,7 @@ package dashboards
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
@ -16,8 +17,8 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
|
||||
Convey("Can read config file", func() {
|
||||
|
||||
cfgProvifer := configReader{path: simpleDashboardConfig}
|
||||
cfg, err := cfgProvifer.readConfig()
|
||||
cfgProvider := configReader{path: simpleDashboardConfig, log: log.New("test-logger")}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
@ -47,16 +48,26 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
|
||||
})
|
||||
|
||||
Convey("Should skip broken config files", func() {
|
||||
Convey("Should skip invalid path", func() {
|
||||
|
||||
cfgProvifer := configReader{path: brokenConfigs}
|
||||
cfg, err := cfgProvifer.readConfig()
|
||||
cfgProvider := configReader{path: "/invalid-directory", log: log.New("test-logger")}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(cfg), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should skip broken config files", func() {
|
||||
|
||||
cfgProvider := configReader{path: brokenConfigs, log: log.New("test-logger")}
|
||||
cfg, err := cfgProvider.readConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(cfg), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -14,9 +14,10 @@ type DashboardProvisioner struct {
|
||||
}
|
||||
|
||||
func Provision(ctx context.Context, configDirectory string) (*DashboardProvisioner, error) {
|
||||
log := log.New("provisioning.dashboard")
|
||||
d := &DashboardProvisioner{
|
||||
cfgReader: &configReader{path: configDirectory},
|
||||
log: log.New("provisioning.dashboard"),
|
||||
cfgReader: &configReader{path: configDirectory, log: log},
|
||||
log: log,
|
||||
ctx: ctx,
|
||||
}
|
||||
|
||||
|
@ -25,13 +25,13 @@ func Provision(configDirectory string) error {
|
||||
|
||||
type DatasourceProvisioner struct {
|
||||
log log.Logger
|
||||
cfgProvider configReader
|
||||
cfgProvider *configReader
|
||||
}
|
||||
|
||||
func newDatasourceProvisioner(log log.Logger) DatasourceProvisioner {
|
||||
return DatasourceProvisioner{
|
||||
log: log,
|
||||
cfgProvider: configReader{},
|
||||
cfgProvider: &configReader{log: log},
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,15 +95,19 @@ func (dc *DatasourceProvisioner) deleteDatasources(dsToDelete []*DeleteDatasourc
|
||||
return nil
|
||||
}
|
||||
|
||||
type configReader struct{}
|
||||
type configReader struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
|
||||
var datasources []*DatasourcesAsConfig
|
||||
|
||||
func (configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
cr.log.Error("cant read datasource provisioning files from directory", "path", path)
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
var datasources []*DatasourcesAsConfig
|
||||
for _, file := range files {
|
||||
if strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml") {
|
||||
filename, _ := filepath.Abs(filepath.Join(path, file.Name()))
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
logger log.Logger = log.New("fake.logger")
|
||||
logger log.Logger = log.New("fake.log")
|
||||
oneDatasourcesConfig string = ""
|
||||
twoDatasourcesConfig string = "./test-configs/two-datasources"
|
||||
twoDatasourcesConfigPurgeOthers string = "./test-configs/insert-two-delete-two"
|
||||
@ -115,12 +115,23 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("broken yaml should return error", func() {
|
||||
_, err := configReader{}.readConfig(brokenYaml)
|
||||
reader := &configReader{}
|
||||
_, err := reader.readConfig(brokenYaml)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("skip invalid directory", func() {
|
||||
cfgProvifer := &configReader{log: log.New("test logger")}
|
||||
cfg, err := cfgProvifer.readConfig("./invalid-directory")
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(cfg), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("can read all properties", func() {
|
||||
cfgProvifer := configReader{}
|
||||
cfgProvifer := &configReader{log: log.New("test logger")}
|
||||
cfg, err := cfgProvifer.readConfig(allProperties)
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
|
@ -176,6 +176,7 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
folder.has_acl = ` + dialect.BooleanStr(false) + `
|
||||
) AND
|
||||
da.dashboard_id = -1
|
||||
ORDER BY 1 ASC
|
||||
`
|
||||
|
||||
query.Result = make([]*m.DashboardAclInfoDTO, 0)
|
||||
|
91
pkg/services/sqlstore/login_attempt.go
Normal file
91
pkg/services/sqlstore/login_attempt.go
Normal file
@ -0,0 +1,91 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
var getTimeNow = time.Now
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", CreateLoginAttempt)
|
||||
bus.AddHandler("sql", DeleteOldLoginAttempts)
|
||||
bus.AddHandler("sql", GetUserLoginAttemptCount)
|
||||
}
|
||||
|
||||
func CreateLoginAttempt(cmd *m.CreateLoginAttemptCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
loginAttempt := m.LoginAttempt{
|
||||
Username: cmd.Username,
|
||||
IpAddress: cmd.IpAddress,
|
||||
Created: getTimeNow(),
|
||||
}
|
||||
|
||||
if _, err := sess.Insert(&loginAttempt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = loginAttempt
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteOldLoginAttempts(cmd *m.DeleteOldLoginAttemptsCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var maxId int64
|
||||
sql := "SELECT max(id) as id FROM login_attempt WHERE created < " + dialect.DateTimeFunc("?")
|
||||
result, err := sess.Query(sql, cmd.OlderThan)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
maxId = toInt64(result[0]["id"])
|
||||
|
||||
if maxId == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sql = "DELETE FROM login_attempt WHERE id <= ?"
|
||||
|
||||
if result, err := sess.Exec(sql, maxId); err != nil {
|
||||
return err
|
||||
} else if cmd.DeletedRows, err = result.RowsAffected(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func GetUserLoginAttemptCount(query *m.GetUserLoginAttemptCountQuery) error {
|
||||
loginAttempt := new(m.LoginAttempt)
|
||||
total, err := x.
|
||||
Where("username = ?", query.Username).
|
||||
And("created >="+dialect.DateTimeFunc("?"), query.Since).
|
||||
Count(loginAttempt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query.Result = total
|
||||
return nil
|
||||
}
|
||||
|
||||
func toInt64(i interface{}) int64 {
|
||||
switch i.(type) {
|
||||
case []byte:
|
||||
n, _ := strconv.ParseInt(string(i.([]byte)), 10, 64)
|
||||
return n
|
||||
case int:
|
||||
return int64(i.(int))
|
||||
case int64:
|
||||
return i.(int64)
|
||||
}
|
||||
return 0
|
||||
}
|
125
pkg/services/sqlstore/login_attempt_test.go
Normal file
125
pkg/services/sqlstore/login_attempt_test.go
Normal file
@ -0,0 +1,125 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func mockTime(mock time.Time) time.Time {
|
||||
getTimeNow = func() time.Time { return mock }
|
||||
return mock
|
||||
}
|
||||
|
||||
func TestLoginAttempts(t *testing.T) {
|
||||
Convey("Testing Login Attempts DB Access", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
user := "user"
|
||||
beginningOfTime := mockTime(time.Date(2017, 10, 22, 8, 0, 0, 0, time.Local))
|
||||
|
||||
err := CreateLoginAttempt(&m.CreateLoginAttemptCommand{
|
||||
Username: user,
|
||||
IpAddress: "192.168.0.1",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
timePlusOneMinute := mockTime(beginningOfTime.Add(time.Minute * 1))
|
||||
|
||||
err = CreateLoginAttempt(&m.CreateLoginAttemptCommand{
|
||||
Username: user,
|
||||
IpAddress: "192.168.0.1",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
timePlusTwoMinutes := mockTime(beginningOfTime.Add(time.Minute * 2))
|
||||
|
||||
err = CreateLoginAttempt(&m.CreateLoginAttemptCommand{
|
||||
Username: user,
|
||||
IpAddress: "192.168.0.1",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should return a total count of zero login attempts when comparing since beginning of time + 2min and 1s", func() {
|
||||
query := m.GetUserLoginAttemptCountQuery{
|
||||
Username: user,
|
||||
Since: timePlusTwoMinutes.Add(time.Second * 1),
|
||||
}
|
||||
err := GetUserLoginAttemptCount(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return the total count of login attempts since beginning of time", func() {
|
||||
query := m.GetUserLoginAttemptCountQuery{
|
||||
Username: user,
|
||||
Since: beginningOfTime,
|
||||
}
|
||||
err := GetUserLoginAttemptCount(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result, ShouldEqual, 3)
|
||||
})
|
||||
|
||||
Convey("Should return the total count of login attempts since beginning of time + 1min", func() {
|
||||
query := m.GetUserLoginAttemptCountQuery{
|
||||
Username: user,
|
||||
Since: timePlusOneMinute,
|
||||
}
|
||||
err := GetUserLoginAttemptCount(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result, ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should return the total count of login attempts since beginning of time + 2min", func() {
|
||||
query := m.GetUserLoginAttemptCountQuery{
|
||||
Username: user,
|
||||
Since: timePlusTwoMinutes,
|
||||
}
|
||||
err := GetUserLoginAttemptCount(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Should return deleted rows older than beginning of time", func() {
|
||||
cmd := m.DeleteOldLoginAttemptsCommand{
|
||||
OlderThan: beginningOfTime,
|
||||
}
|
||||
err := DeleteOldLoginAttempts(&cmd)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.DeletedRows, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should return deleted rows older than beginning of time + 1min", func() {
|
||||
cmd := m.DeleteOldLoginAttemptsCommand{
|
||||
OlderThan: timePlusOneMinute,
|
||||
}
|
||||
err := DeleteOldLoginAttempts(&cmd)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.DeletedRows, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Should return deleted rows older than beginning of time + 2min", func() {
|
||||
cmd := m.DeleteOldLoginAttemptsCommand{
|
||||
OlderThan: timePlusTwoMinutes,
|
||||
}
|
||||
err := DeleteOldLoginAttempts(&cmd)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.DeletedRows, ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should return deleted rows older than beginning of time + 2min and 1s", func() {
|
||||
cmd := m.DeleteOldLoginAttemptsCommand{
|
||||
OlderThan: timePlusTwoMinutes.Add(time.Second * 1),
|
||||
}
|
||||
err := DeleteOldLoginAttempts(&cmd)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.DeletedRows, ShouldEqual, 3)
|
||||
})
|
||||
})
|
||||
}
|
23
pkg/services/sqlstore/migrations/login_attempt_mig.go
Normal file
23
pkg/services/sqlstore/migrations/login_attempt_mig.go
Normal file
@ -0,0 +1,23 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addLoginAttemptMigrations(mg *Migrator) {
|
||||
loginAttemptV1 := Table{
|
||||
Name: "login_attempt",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "username", Type: DB_NVarchar, Length: 190, Nullable: false},
|
||||
{Name: "ip_address", Type: DB_NVarchar, Length: 30, Nullable: false},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"username"}},
|
||||
},
|
||||
}
|
||||
|
||||
// create table
|
||||
mg.AddMigration("create login attempt table", NewAddTableMigration(loginAttemptV1))
|
||||
// add indices
|
||||
mg.AddMigration("add index login_attempt.username", NewAddIndexMigration(loginAttemptV1, loginAttemptV1.Indices[0]))
|
||||
}
|
@ -29,6 +29,7 @@ func AddMigrations(mg *Migrator) {
|
||||
addTeamMigrations(mg)
|
||||
addDashboardAclMigrations(mg)
|
||||
addTagMigration(mg)
|
||||
addLoginAttemptMigrations(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
@ -19,6 +19,7 @@ type Dialect interface {
|
||||
LikeStr() string
|
||||
Default(col *Column) string
|
||||
BooleanStr(bool) string
|
||||
DateTimeFunc(string) string
|
||||
|
||||
CreateIndexSql(tableName string, index *Index) string
|
||||
CreateTableSql(table *Table) string
|
||||
@ -78,6 +79,10 @@ func (b *BaseDialect) Default(col *Column) string {
|
||||
return col.Default
|
||||
}
|
||||
|
||||
func (db *BaseDialect) DateTimeFunc(value string) string {
|
||||
return value
|
||||
}
|
||||
|
||||
func (b *BaseDialect) CreateTableSql(table *Table) string {
|
||||
var sql string
|
||||
sql = "CREATE TABLE IF NOT EXISTS "
|
||||
|
@ -36,6 +36,10 @@ func (db *Sqlite3) BooleanStr(value bool) string {
|
||||
return "0"
|
||||
}
|
||||
|
||||
func (db *Sqlite3) DateTimeFunc(value string) string {
|
||||
return "datetime(" + value + ")"
|
||||
}
|
||||
|
||||
func (db *Sqlite3) SqlType(c *Column) string {
|
||||
switch c.Type {
|
||||
case DB_Date, DB_DateTime, DB_TimeStamp, DB_Time:
|
||||
|
@ -12,7 +12,7 @@ type TestDB struct {
|
||||
}
|
||||
|
||||
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:?_loc=Local"}
|
||||
var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci"}
|
||||
var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci&loc=Local"}
|
||||
var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
|
||||
|
||||
func CleanDB(x *xorm.Engine) {
|
||||
|
@ -75,13 +75,14 @@ var (
|
||||
EnforceDomain bool
|
||||
|
||||
// Security settings.
|
||||
SecretKey string
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
DisableGravatar bool
|
||||
EmailCodeValidMinutes int
|
||||
DataProxyWhiteList map[string]bool
|
||||
SecretKey string
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
DisableGravatar bool
|
||||
EmailCodeValidMinutes int
|
||||
DataProxyWhiteList map[string]bool
|
||||
DisableBruteForceLoginProtection bool
|
||||
|
||||
// Snapshots
|
||||
ExternalSnapshotUrl string
|
||||
@ -514,6 +515,7 @@ func NewConfigContext(args *CommandLineArgs) error {
|
||||
CookieUserName = security.Key("cookie_username").String()
|
||||
CookieRememberName = security.Key("cookie_remember_name").String()
|
||||
DisableGravatar = security.Key("disable_gravatar").MustBool(true)
|
||||
DisableBruteForceLoginProtection = security.Key("disable_brute_force_login_protection").MustBool(false)
|
||||
|
||||
// read snapshots settings
|
||||
snapshots := Cfg.Section("snapshots")
|
||||
@ -578,7 +580,7 @@ func NewConfigContext(args *CommandLineArgs) error {
|
||||
|
||||
// PhantomJS rendering
|
||||
ImagesDir = filepath.Join(DataPath, "png")
|
||||
PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
|
||||
PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
|
||||
|
||||
analytics := Cfg.Section("analytics")
|
||||
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
|
||||
|
@ -215,6 +215,21 @@ func transformToTable(data []suggestData, result *tsdb.QueryResult) {
|
||||
result.Meta.Set("rowCount", len(data))
|
||||
}
|
||||
|
||||
func parseMultiSelectValue(input string) []string {
|
||||
trimmedInput := strings.TrimSpace(input)
|
||||
|
||||
if strings.HasPrefix(trimmedInput, "{") {
|
||||
values := strings.Split(strings.TrimRight(strings.TrimLeft(trimmedInput, "{"), "}"), ",")
|
||||
trimValues := make([]string, len(values))
|
||||
for i, v := range values {
|
||||
trimValues[i] = strings.TrimSpace(v)
|
||||
}
|
||||
return trimValues
|
||||
} else {
|
||||
return []string{trimmedInput}
|
||||
}
|
||||
}
|
||||
|
||||
// Whenever this list is updated, frontend list should also be updated.
|
||||
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
|
||||
func (e *CloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
|
||||
@ -378,15 +393,19 @@ func (e *CloudWatchExecutor) handleGetEbsVolumeIds(ctx context.Context, paramete
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instanceIds := []*string{aws.String(instanceId)}
|
||||
instanceIds := aws.StringSlice(parseMultiSelectValue(instanceId))
|
||||
instances, err := e.ec2DescribeInstances(region, nil, instanceIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]suggestData, 0)
|
||||
for _, mapping := range instances.Reservations[0].Instances[0].BlockDeviceMappings {
|
||||
result = append(result, suggestData{Text: *mapping.Ebs.VolumeId, Value: *mapping.Ebs.VolumeId})
|
||||
for _, reservation := range instances.Reservations {
|
||||
for _, instance := range reservation.Instances {
|
||||
for _, mapping := range instance.BlockDeviceMappings {
|
||||
result = append(result, suggestData{Text: *mapping.Ebs.VolumeId, Value: *mapping.Ebs.VolumeId})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
@ -114,4 +115,85 @@ func TestCloudWatchMetrics(t *testing.T) {
|
||||
So(result[0].Text, ShouldEqual, "i-12345678")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When calling handleGetEbsVolumeIds", t, func() {
|
||||
|
||||
executor := &CloudWatchExecutor{
|
||||
ec2Svc: mockedEc2{Resp: ec2.DescribeInstancesOutput{
|
||||
Reservations: []*ec2.Reservation{
|
||||
{
|
||||
Instances: []*ec2.Instance{
|
||||
{
|
||||
InstanceId: aws.String("i-1"),
|
||||
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-1-1")}},
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-1-2")}},
|
||||
},
|
||||
},
|
||||
{
|
||||
InstanceId: aws.String("i-2"),
|
||||
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-2-1")}},
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-2-2")}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Instances: []*ec2.Instance{
|
||||
{
|
||||
InstanceId: aws.String("i-3"),
|
||||
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-3-1")}},
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-3-2")}},
|
||||
},
|
||||
},
|
||||
{
|
||||
InstanceId: aws.String("i-4"),
|
||||
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-4-1")}},
|
||||
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-4-2")}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
json := simplejson.New()
|
||||
json.Set("region", "us-east-1")
|
||||
json.Set("instanceId", "{i-1, i-2, i-3, i-4}")
|
||||
result, _ := executor.handleGetEbsVolumeIds(context.Background(), json, &tsdb.TsdbQuery{})
|
||||
|
||||
Convey("Should return all 8 VolumeIds", func() {
|
||||
So(len(result), ShouldEqual, 8)
|
||||
So(result[0].Text, ShouldEqual, "vol-1-1")
|
||||
So(result[1].Text, ShouldEqual, "vol-1-2")
|
||||
So(result[2].Text, ShouldEqual, "vol-2-1")
|
||||
So(result[3].Text, ShouldEqual, "vol-2-2")
|
||||
So(result[4].Text, ShouldEqual, "vol-3-1")
|
||||
So(result[5].Text, ShouldEqual, "vol-3-2")
|
||||
So(result[6].Text, ShouldEqual, "vol-4-1")
|
||||
So(result[7].Text, ShouldEqual, "vol-4-2")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestParseMultiSelectValue(t *testing.T) {
|
||||
|
||||
var values []string
|
||||
|
||||
values = parseMultiSelectValue(" i-someInstance ")
|
||||
assert.Equal(t, []string{"i-someInstance"}, values)
|
||||
|
||||
values = parseMultiSelectValue("{i-05}")
|
||||
assert.Equal(t, []string{"i-05"}, values)
|
||||
|
||||
values = parseMultiSelectValue(" {i-01, i-03, i-04} ")
|
||||
assert.Equal(t, []string{"i-01", "i-03", "i-04"}, values)
|
||||
|
||||
values = parseMultiSelectValue("i-{01}")
|
||||
assert.Equal(t, []string{"i-{01}"}, values)
|
||||
|
||||
}
|
||||
|
@ -71,6 +71,7 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
|
||||
body.toggleClass('sidemenu-open', sidemenuOpen);
|
||||
|
||||
appEvents.on('toggle-sidemenu', () => {
|
||||
sidemenuOpen = scope.contextSrv.sidemenu;
|
||||
body.toggleClass('sidemenu-open');
|
||||
});
|
||||
|
||||
@ -167,6 +168,8 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
|
||||
// mouse and keyboard is user activity
|
||||
body.mousemove(userActivityDetected);
|
||||
body.keydown(userActivityDetected);
|
||||
// set useCapture = true to catch event here
|
||||
document.addEventListener('wheel', userActivityDetected, true);
|
||||
// treat tab change as activity
|
||||
document.addEventListener('visibilitychange', userActivityDetected);
|
||||
|
||||
|
@ -45,12 +45,13 @@
|
||||
|
||||
<div class="search-filter-box">
|
||||
<a href="dashboard/new" class="search-filter-box-link">
|
||||
<i class="gicon gicon-dashboard-new"></i>
|
||||
New dashboard
|
||||
<i class="gicon gicon-dashboard-new"></i> New dashboard
|
||||
</a>
|
||||
<a href="dashboards/folder/new" class="search-filter-box-link">
|
||||
<i class="gicon gicon-folder-new"></i>
|
||||
New folder
|
||||
<i class="gicon gicon-folder-new"></i> New folder
|
||||
</a>
|
||||
<a href="dashboard/import" class="search-filter-box-link">
|
||||
<i class="gicon gicon-dashboard-import"></i> Import dashboard
|
||||
</a>
|
||||
<a class="search-filter-box-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
|
||||
<img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find dashboards on Grafana.com
|
||||
|
@ -89,7 +89,7 @@ function (angular, _, coreModule) {
|
||||
if (addTemplateVars) {
|
||||
_.each(templateSrv.variables, function(variable) {
|
||||
if (variableTypeFilter === void 0 || variableTypeFilter === variable.type) {
|
||||
segments.unshift(self.newSegment({ type: 'template', value: '$' + variable.name, expandable: true }));
|
||||
segments.unshift(self.newSegment({ type: 'value', value: '$' + variable.name, expandable: true }));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -352,8 +352,10 @@ export class DashboardModel {
|
||||
copy.scopedVars[variable.name] = option;
|
||||
|
||||
if (panel.repeatDirection === REPEAT_DIR_VERTICAL) {
|
||||
if (index > 0) {
|
||||
yPos += copy.gridPos.h;
|
||||
}
|
||||
copy.gridPos.y = yPos;
|
||||
yPos += copy.gridPos.h;
|
||||
} else {
|
||||
// set width based on how many are selected
|
||||
// assumed the repeated panels should take up full row width
|
||||
@ -370,6 +372,15 @@ export class DashboardModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update gridPos for panels below
|
||||
let yOffset = yPos - panel.gridPos.y;
|
||||
if (yOffset > 0) {
|
||||
let panelBelowIndex = panelIndex + selectedOptions.length;
|
||||
for (let i = panelBelowIndex; i < this.panels.length; i++) {
|
||||
this.panels[i].gridPos.y += yOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repeatRow(panel: PanelModel, panelIndex: number, variable) {
|
||||
|
@ -27,6 +27,7 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
|
||||
this.toggle = this.toggle.bind(this);
|
||||
this.openSettings = this.openSettings.bind(this);
|
||||
this.delete = this.delete.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
@ -37,13 +38,18 @@ export class DashboardRow extends React.Component<DashboardRowProps, any> {
|
||||
});
|
||||
}
|
||||
|
||||
update() {
|
||||
this.dashboard.processRepeats();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
openSettings() {
|
||||
appEvents.emit('show-modal', {
|
||||
templateHtml: `<row-options row="model.row" on-updated="model.onUpdated()" dismiss="dismiss()"></row-options>`,
|
||||
modalClass: 'modal--narrow',
|
||||
model: {
|
||||
row: this.props.panel,
|
||||
onUpdated: this.forceUpdate.bind(this),
|
||||
onUpdated: this.update.bind(this),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -142,12 +142,9 @@ describe('given dashboard with panel repeat in vertical direction', function() {
|
||||
beforeEach(function() {
|
||||
dashboard = new DashboardModel({
|
||||
panels: [
|
||||
{
|
||||
id: 2,
|
||||
repeat: 'apps',
|
||||
repeatDirection: 'v',
|
||||
gridPos: { x: 5, y: 0, h: 2, w: 8 },
|
||||
},
|
||||
{ id: 1, type: 'row', gridPos: { x: 0, y: 0, h: 1, w: 24 } },
|
||||
{ id: 2, repeat: 'apps', repeatDirection: 'v', gridPos: { x: 5, y: 1, h: 2, w: 8 } },
|
||||
{ id: 3, type: 'row', gridPos: { x: 0, y: 3, h: 1, w: 24 } },
|
||||
],
|
||||
templating: {
|
||||
list: [
|
||||
@ -171,24 +168,95 @@ describe('given dashboard with panel repeat in vertical direction', function() {
|
||||
});
|
||||
|
||||
it('should place on items on top of each other and keep witdh', function() {
|
||||
expect(dashboard.panels[0].gridPos).toMatchObject({
|
||||
x: 5,
|
||||
y: 0,
|
||||
h: 2,
|
||||
w: 8,
|
||||
});
|
||||
expect(dashboard.panels[1].gridPos).toMatchObject({
|
||||
x: 5,
|
||||
y: 2,
|
||||
h: 2,
|
||||
w: 8,
|
||||
});
|
||||
expect(dashboard.panels[2].gridPos).toMatchObject({
|
||||
x: 5,
|
||||
y: 4,
|
||||
h: 2,
|
||||
w: 8,
|
||||
});
|
||||
expect(dashboard.panels[0].gridPos).toMatchObject({ x: 0, y: 0, h: 1, w: 24 }); // first row
|
||||
|
||||
expect(dashboard.panels[1].gridPos).toMatchObject({ x: 5, y: 1, h: 2, w: 8 });
|
||||
expect(dashboard.panels[2].gridPos).toMatchObject({ x: 5, y: 3, h: 2, w: 8 });
|
||||
expect(dashboard.panels[3].gridPos).toMatchObject({ x: 5, y: 5, h: 2, w: 8 });
|
||||
|
||||
expect(dashboard.panels[4].gridPos).toMatchObject({ x: 0, y: 7, h: 1, w: 24 }); // last row
|
||||
});
|
||||
});
|
||||
|
||||
describe('given dashboard with row repeat and panel repeat in horizontal direction', () => {
|
||||
let dashboard, dashboardJSON;
|
||||
|
||||
beforeEach(() => {
|
||||
dashboardJSON = {
|
||||
panels: [
|
||||
{ id: 1, type: 'row', repeat: 'region', gridPos: { x: 0, y: 0, h: 1, w: 24 } },
|
||||
{ id: 2, type: 'graph', repeat: 'app', gridPos: { x: 0, y: 1, h: 2, w: 6 } },
|
||||
],
|
||||
templating: {
|
||||
list: [
|
||||
{
|
||||
name: 'region',
|
||||
current: {
|
||||
text: 'reg1, reg2',
|
||||
value: ['reg1', 'reg2'],
|
||||
},
|
||||
options: [{ text: 'reg1', value: 'reg1', selected: true }, { text: 'reg2', value: 'reg2', selected: true }],
|
||||
},
|
||||
{
|
||||
name: 'app',
|
||||
current: {
|
||||
text: 'se1, se2, se3, se4, se5, se6',
|
||||
value: ['se1', 'se2', 'se3', 'se4', 'se5', 'se6'],
|
||||
},
|
||||
options: [
|
||||
{ text: 'se1', value: 'se1', selected: true },
|
||||
{ text: 'se2', value: 'se2', selected: true },
|
||||
{ text: 'se3', value: 'se3', selected: true },
|
||||
{ text: 'se4', value: 'se4', selected: true },
|
||||
{ text: 'se5', value: 'se5', selected: true },
|
||||
{ text: 'se6', value: 'se6', selected: true },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
dashboard = new DashboardModel(dashboardJSON);
|
||||
dashboard.processRepeats(false);
|
||||
});
|
||||
|
||||
it('should panels in self row', () => {
|
||||
const panel_types = _.map(dashboard.panels, 'type');
|
||||
expect(panel_types).toEqual([
|
||||
'row',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'row',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
'graph',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should be placed in their places', function() {
|
||||
expect(dashboard.panels[0].gridPos).toMatchObject({ x: 0, y: 0, h: 1, w: 24 }); // 1st row
|
||||
|
||||
expect(dashboard.panels[1].gridPos).toMatchObject({ x: 0, y: 1, h: 2, w: 6 });
|
||||
expect(dashboard.panels[2].gridPos).toMatchObject({ x: 6, y: 1, h: 2, w: 6 });
|
||||
expect(dashboard.panels[3].gridPos).toMatchObject({ x: 12, y: 1, h: 2, w: 6 });
|
||||
expect(dashboard.panels[4].gridPos).toMatchObject({ x: 18, y: 1, h: 2, w: 6 });
|
||||
expect(dashboard.panels[5].gridPos).toMatchObject({ x: 0, y: 3, h: 2, w: 6 }); // next row
|
||||
expect(dashboard.panels[6].gridPos).toMatchObject({ x: 6, y: 3, h: 2, w: 6 });
|
||||
|
||||
expect(dashboard.panels[7].gridPos).toMatchObject({ x: 0, y: 5, h: 1, w: 24 });
|
||||
|
||||
expect(dashboard.panels[8].gridPos).toMatchObject({ x: 0, y: 6, h: 2, w: 6 }); // 2nd row
|
||||
expect(dashboard.panels[9].gridPos).toMatchObject({ x: 6, y: 6, h: 2, w: 6 });
|
||||
expect(dashboard.panels[10].gridPos).toMatchObject({ x: 12, y: 6, h: 2, w: 6 });
|
||||
expect(dashboard.panels[11].gridPos).toMatchObject({ x: 18, y: 6, h: 2, w: 6 }); // next row
|
||||
expect(dashboard.panels[12].gridPos).toMatchObject({ x: 0, y: 8, h: 2, w: 6 });
|
||||
expect(dashboard.panels[13].gridPos).toMatchObject({ x: 6, y: 8, h: 2, w: 6 });
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
import coreModule from 'app/core/core_module';
|
||||
import { variableTypes } from './variable';
|
||||
import appEvents from 'app/core/app_events';
|
||||
|
||||
export class VariableEditorCtrl {
|
||||
/** @ngInject **/
|
||||
@ -56,16 +57,13 @@ export class VariableEditorCtrl {
|
||||
}
|
||||
|
||||
if (!$scope.current.name.match(/^\w+$/)) {
|
||||
$scope.appEvent('alert-warning', [
|
||||
'Validation',
|
||||
'Only word and digit characters are allowed in variable names',
|
||||
]);
|
||||
appEvents.emit('alert-warning', ['Validation', 'Only word and digit characters are allowed in variable names']);
|
||||
return false;
|
||||
}
|
||||
|
||||
var sameName = _.find($scope.variables, { name: $scope.current.name });
|
||||
if (sameName && sameName !== $scope.current) {
|
||||
$scope.appEvent('alert-warning', ['Validation', 'Variable with the same name already exists']);
|
||||
appEvents.emit('alert-warning', ['Validation', 'Variable with the same name already exists']);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -73,7 +71,7 @@ export class VariableEditorCtrl {
|
||||
$scope.current.type === 'query' &&
|
||||
$scope.current.query.match(new RegExp('\\$' + $scope.current.name + '(/| |$)'))
|
||||
) {
|
||||
$scope.appEvent('alert-warning', [
|
||||
appEvents.emit('alert-warning', [
|
||||
'Validation',
|
||||
'Query cannot contain a reference to itself. Variable: $' + $scope.current.name,
|
||||
]);
|
||||
@ -96,11 +94,11 @@ export class VariableEditorCtrl {
|
||||
};
|
||||
|
||||
$scope.runQuery = function() {
|
||||
return variableSrv.updateOptions($scope.current).then(null, function(err) {
|
||||
return variableSrv.updateOptions($scope.current).catch(err => {
|
||||
if (err.data && err.data.message) {
|
||||
err.message = err.data.message;
|
||||
}
|
||||
$scope.appEvent('alert-error', ['Templating', 'Template variables could not be initialized: ' + err.message]);
|
||||
appEvents.emit('alert-error', ['Templating', 'Template variables could not be initialized: ' + err.message]);
|
||||
});
|
||||
};
|
||||
|
||||
|
40
public/app/features/templating/specs/editor_ctrl.jest.ts
Normal file
40
public/app/features/templating/specs/editor_ctrl.jest.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { VariableEditorCtrl } from '../editor_ctrl';
|
||||
|
||||
let mockEmit;
|
||||
jest.mock('app/core/app_events', () => {
|
||||
mockEmit = jest.fn();
|
||||
return {
|
||||
emit: mockEmit,
|
||||
};
|
||||
});
|
||||
|
||||
describe('VariableEditorCtrl', () => {
|
||||
let scope = {
|
||||
runQuery: () => {
|
||||
return Promise.resolve({});
|
||||
},
|
||||
};
|
||||
|
||||
describe('When running a variable query and the data source returns an error', () => {
|
||||
beforeEach(() => {
|
||||
const variableSrv = {
|
||||
updateOptions: () => {
|
||||
return Promise.reject({
|
||||
data: { message: 'error' },
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
return new VariableEditorCtrl(scope, {}, variableSrv, {});
|
||||
});
|
||||
|
||||
it('should emit an error', () => {
|
||||
return scope.runQuery().then(res => {
|
||||
expect(mockEmit).toBeCalled();
|
||||
expect(mockEmit.mock.calls[0][0]).toBe('alert-error');
|
||||
expect(mockEmit.mock.calls[0][1][0]).toBe('Templating');
|
||||
expect(mockEmit.mock.calls[0][1][1]).toBe('Template variables could not be initialized: error');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -36,7 +36,7 @@ export default class CloudWatchDatasource {
|
||||
item.region = this.templateSrv.replace(this.getActualRegion(item.region), options.scopedVars);
|
||||
item.namespace = this.templateSrv.replace(item.namespace, options.scopedVars);
|
||||
item.metricName = this.templateSrv.replace(item.metricName, options.scopedVars);
|
||||
item.dimensions = this.convertDimensionFormat(item.dimensions, options.scopeVars);
|
||||
item.dimensions = this.convertDimensionFormat(item.dimensions, options.scopedVars);
|
||||
item.period = String(this.getPeriod(item, options)); // use string format for period in graph query, and alerting
|
||||
|
||||
return _.extend(
|
||||
|
@ -181,6 +181,22 @@ export default class GraphiteQuery {
|
||||
var nestedSeriesRefRegex = /\#([A-Z])/g;
|
||||
var targetWithNestedQueries = target.target;
|
||||
|
||||
// Use ref count to track circular references
|
||||
function countTargetRefs(targetsByRefId, refId) {
|
||||
let refCount = 0;
|
||||
_.each(targetsByRefId, (t, id) => {
|
||||
if (id !== refId) {
|
||||
let match = nestedSeriesRefRegex.exec(t.target);
|
||||
let count = match && match.length ? match.length - 1 : 0;
|
||||
refCount += count;
|
||||
}
|
||||
});
|
||||
targetsByRefId[refId].refCount = refCount;
|
||||
}
|
||||
_.each(targetsByRefId, (t, id) => {
|
||||
countTargetRefs(targetsByRefId, id);
|
||||
});
|
||||
|
||||
// Keep interpolating until there are no query references
|
||||
// The reason for the loop is that the referenced query might contain another reference to another query
|
||||
while (targetWithNestedQueries.match(nestedSeriesRefRegex)) {
|
||||
@ -191,7 +207,11 @@ export default class GraphiteQuery {
|
||||
}
|
||||
|
||||
// no circular references
|
||||
delete targetsByRefId[g1];
|
||||
if (t.refCount === 0) {
|
||||
delete targetsByRefId[g1];
|
||||
}
|
||||
t.refCount--;
|
||||
|
||||
return t.target;
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,47 @@
|
||||
import gfunc from '../gfunc';
|
||||
import GraphiteQuery from '../graphite_query';
|
||||
|
||||
describe('Graphite query model', () => {
|
||||
let ctx: any = {
|
||||
datasource: {
|
||||
getFuncDef: gfunc.getFuncDef,
|
||||
getFuncDefs: jest.fn().mockReturnValue(Promise.resolve(gfunc.getFuncDefs('1.0'))),
|
||||
waitForFuncDefsLoaded: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||
createFuncInstance: gfunc.createFuncInstance,
|
||||
},
|
||||
templateSrv: {},
|
||||
targets: [],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
ctx.target = { refId: 'A', target: 'scaleToSeconds(#A, 60)' };
|
||||
ctx.queryModel = new GraphiteQuery(ctx.datasource, ctx.target, ctx.templateSrv);
|
||||
});
|
||||
|
||||
describe('when updating targets with nested queries', () => {
|
||||
beforeEach(() => {
|
||||
ctx.target = { refId: 'D', target: 'asPercent(#A, #C)' };
|
||||
ctx.targets = [
|
||||
{ refId: 'A', target: 'first.query.count' },
|
||||
{ refId: 'B', target: 'second.query.count' },
|
||||
{ refId: 'C', target: 'diffSeries(#A, #B)' },
|
||||
{ refId: 'D', target: 'asPercent(#A, #C)' },
|
||||
];
|
||||
ctx.queryModel = new GraphiteQuery(ctx.datasource, ctx.target, ctx.templateSrv);
|
||||
});
|
||||
|
||||
it('targetFull should include nested queries', () => {
|
||||
ctx.queryModel.updateRenderedTarget(ctx.target, ctx.targets);
|
||||
const targetFullExpected = 'asPercent(first.query.count, diffSeries(first.query.count, second.query.count))';
|
||||
expect(ctx.queryModel.target.targetFull).toBe(targetFullExpected);
|
||||
});
|
||||
|
||||
it('should not hang on circular references', () => {
|
||||
ctx.target.target = 'asPercent(#A, #B)';
|
||||
ctx.targets = [{ refId: 'A', target: 'asPercent(#B, #C)' }, { refId: 'B', target: 'asPercent(#A, #C)' }];
|
||||
ctx.queryModel.updateRenderedTarget(ctx.target, ctx.targets);
|
||||
// Just ensure updateRenderedTarget() is completed and doesn't hang
|
||||
expect(ctx.queryModel.target.targetFull).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
@ -255,7 +255,7 @@ export class InfluxQueryCtrl extends QueryCtrl {
|
||||
for (let variable of this.templateSrv.variables) {
|
||||
segments.unshift(
|
||||
this.uiSegmentSrv.newSegment({
|
||||
type: 'template',
|
||||
type: 'value',
|
||||
value: '/^$' + variable.name + '$/',
|
||||
expandable: true,
|
||||
})
|
||||
|
@ -355,33 +355,16 @@ function graphDirective(timeSrv, popoverSrv, contextSrv) {
|
||||
function sortSeries(series, panel) {
|
||||
var sortBy = panel.legend.sort;
|
||||
var sortOrder = panel.legend.sortDesc;
|
||||
var haveSortBy = sortBy !== null || sortBy !== undefined;
|
||||
var haveSortOrder = sortOrder !== null || sortOrder !== undefined;
|
||||
var haveSortBy = sortBy !== null && sortBy !== undefined;
|
||||
var haveSortOrder = sortOrder !== null && sortOrder !== undefined;
|
||||
var shouldSortBy = panel.stack && haveSortBy && haveSortOrder;
|
||||
var sortDesc = panel.legend.sortDesc === true ? -1 : 1;
|
||||
|
||||
series.sort((x, y) => {
|
||||
if (x.zindex > y.zindex) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (x.zindex < y.zindex) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (shouldSortBy) {
|
||||
if (x.stats[sortBy] > y.stats[sortBy]) {
|
||||
return 1 * sortDesc;
|
||||
}
|
||||
if (x.stats[sortBy] < y.stats[sortBy]) {
|
||||
return -1 * sortDesc;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return series;
|
||||
if (shouldSortBy) {
|
||||
return _.sortBy(series, s => s.stats[sortBy] * sortDesc);
|
||||
} else {
|
||||
return _.sortBy(series, s => s.zindex);
|
||||
}
|
||||
}
|
||||
|
||||
function translateFillOption(fill) {
|
||||
|
@ -29,7 +29,7 @@
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.prefix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
|
||||
<select class="gf-form-input" ng-model="ctrl.panel.prefixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="ctrl.canChangeFontSize()"></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -39,7 +39,7 @@
|
||||
<input type="text" class="gf-form-input width-12" ng-model="ctrl.panel.postfix" ng-change="ctrl.render()" ng-model-onblur>
|
||||
<label class="gf-form-label width-6">Font size</label>
|
||||
<div class="gf-form-select-wrapper">
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()"></select>
|
||||
<select class="input-small gf-form-input" ng-model="ctrl.panel.postfixFontSize" ng-options="f for f in ctrl.fontSizes" ng-change="ctrl.render()" ng-disabled="ctrl.canChangeFontSize()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
|
@ -198,6 +198,10 @@ class SingleStatCtrl extends MetricsPanelCtrl {
|
||||
this.setValueMapping(data);
|
||||
}
|
||||
|
||||
canChangeFontSize() {
|
||||
return this.panel.gauge.show;
|
||||
}
|
||||
|
||||
setColoring(options) {
|
||||
if (options.background) {
|
||||
this.panel.colorValue = false;
|
||||
|
@ -1,17 +1,16 @@
|
||||
|
||||
<div class="edit-tab-with-sidemenu">
|
||||
<aside class="edit-sidemenu-aside">
|
||||
<ul class="edit-sidemenu">
|
||||
<aside class="edit-sidemenu-aside">
|
||||
<ul class="edit-sidemenu">
|
||||
<li ng-repeat="style in editor.panel.styles" ng-class="{active: editor.activeStyleIndex === $index}">
|
||||
<a ng-click="editor.activeStyleIndex = $index" >{{style.pattern || 'New rule'}}</a>
|
||||
<a ng-click="editor.activeStyleIndex = $index">{{style.pattern || 'New rule'}}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="pointer" ng-click="editor.addColumnStyle()">
|
||||
<i class="fa fa-plus"></i> Add
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<div class="edit-tab-content" ng-repeat="style in editor.panel.styles" ng-if="editor.activeStyleIndex === $index">
|
||||
|
||||
@ -20,7 +19,9 @@
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-13">Apply to columns named</label>
|
||||
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'" bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur data-placement="right">
|
||||
<input type="text" placeholder="Name or regex" class="gf-form-input width-13" ng-model="style.pattern" bs-tooltip="'Specify regex using /my.*regex/ syntax'"
|
||||
bs-typeahead="editor.getColumnNames" ng-blur="editor.render()" data-min-length=0 data-items=100 ng-model-onblur
|
||||
data-placement="right">
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="style.type !== 'hidden'">
|
||||
@ -39,18 +40,20 @@
|
||||
<select class="gf-form-input" ng-model="style.type" ng-options="c.value as c.text for c in editor.columnTypes" ng-change="editor.render()"></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form" ng-if="style.type === 'date'">
|
||||
<div class="gf-form" ng-if="style.type === 'date'">
|
||||
<label class="gf-form-label width-11">Date Format</label>
|
||||
<div class="gf-form-select-wrapper width-16">
|
||||
<select class="gf-form-input" ng-model="style.dateFormat" ng-options="c.value as c.text for c in editor.dateFormats" ng-change="editor.render()"></select>
|
||||
</div>
|
||||
<gf-form-dropdown model="style.dateFormat" css-class="gf-form-input width-16" lookup-text="true"
|
||||
get-options="editor.dateFormats" on-change="editor.render()" allow-custom="true">
|
||||
</gf-form-dropdown>
|
||||
</div>
|
||||
|
||||
<div ng-if="style.type === 'string'">
|
||||
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize" change="editor.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Sanitize HTML" checked="style.sanitize"
|
||||
change="editor.render()"></gf-form-switch>
|
||||
</div>
|
||||
<div ng-if="style.type === 'string'">
|
||||
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Preserve Formatting" checked="style.preserveFormat" change="editor.render()"></gf-form-switch>
|
||||
<gf-form-switch class="gf-form" label-class="width-11" ng-if="style.type === 'string'" label="Preserve Formatting" checked="style.preserveFormat"
|
||||
change="editor.render()"></gf-form-switch>
|
||||
</div>
|
||||
|
||||
<div ng-if="style.type === 'number'">
|
||||
@ -60,16 +63,20 @@
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-11">Decimals</label>
|
||||
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()" ng-model-onblur>
|
||||
<input type="number" class="gf-form-input width-4" data-placement="right" ng-model="style.decimals" ng-change="editor.render()"
|
||||
ng-model-onblur>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section gf-form-group" ng-if="style.type === 'number'">
|
||||
<div class="section gf-form-group" ng-if="style.type === 'number'">
|
||||
<h5 class="section-heading">Thresholds</h5>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Thresholds<tip>Comma separated values</tip></label>
|
||||
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()" array-join>
|
||||
<label class="gf-form-label width-8">Thresholds
|
||||
<tip>Comma separated values</tip>
|
||||
</label>
|
||||
<input type="text" class="gf-form-input width-10" ng-model="style.thresholds" placeholder="50,80" ng-blur="editor.render()"
|
||||
array-join>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-8">Color Mode</label>
|
||||
@ -102,21 +109,23 @@
|
||||
<info-popover mode="right-absolute">
|
||||
<p>Specify an URL (relative or absolute)</p>
|
||||
<span>
|
||||
Use special variables to specify cell values: <br>
|
||||
<em>$__cell</em> refers to current cell value <br>
|
||||
Use special variables to specify cell values:
|
||||
<br>
|
||||
<em>$__cell</em> refers to current cell value
|
||||
<br>
|
||||
<em>$__cell_n</em> refers to Nth column value in current row. Column indexes are started from 0. For instance,
|
||||
<em>$__cell_1</em> refers to second column's value.
|
||||
<em>$__cell_1</em> refers to second column's value.
|
||||
</span>
|
||||
</info-popover>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label width-9">Tooltip</label>
|
||||
<input type="text" class="gf-form-input width-29" ng-model="style.linkTooltip" ng-blur="editor.render()" ng-model-onblur data-placement="right">
|
||||
<input type="text" class="gf-form-input width-29" ng-model="style.linkTooltip" ng-blur="editor.render()" ng-model-onblur
|
||||
data-placement="right">
|
||||
<info-popover mode="right-absolute">
|
||||
<p>Specify text for link tooltip.</p>
|
||||
<span>
|
||||
This title appears when user hovers pointer over the cell with link.
|
||||
Use the same variables as for URL.
|
||||
This title appears when user hovers pointer over the cell with link. Use the same variables as for URL.
|
||||
</span>
|
||||
</info-popover>
|
||||
</div>
|
||||
|
@ -150,6 +150,7 @@ $table-bg-hover: $dark-4; // for hover
|
||||
$table-border: $dark-3; // table and cell border
|
||||
|
||||
$table-bg-odd: $dark-2;
|
||||
$table-bg-hover: $dark-3;
|
||||
|
||||
// Buttons
|
||||
// -------------------------
|
||||
|
@ -70,12 +70,7 @@ $text-shadow-faint: none;
|
||||
$textShadow: none;
|
||||
|
||||
// gradients
|
||||
$brand-gradient: linear-gradient(
|
||||
to right,
|
||||
rgba(255, 213, 0, 1) 0%,
|
||||
rgba(255, 68, 0, 1) 99%,
|
||||
rgba(255, 68, 0, 1) 100%
|
||||
);
|
||||
$brand-gradient: linear-gradient(to right, rgba(255, 213, 0, 1) 0%, rgba(255, 68, 0, 1) 99%, rgba(255, 68, 0, 1) 100%);
|
||||
$page-gradient: linear-gradient(-60deg, transparent 70%, $gray-7 98%);
|
||||
|
||||
// Links
|
||||
@ -106,13 +101,7 @@ $component-active-bg: $brand-primary !default;
|
||||
$panel-bg: $white;
|
||||
$panel-border-color: $gray-5;
|
||||
$panel-border: solid 1px $panel-border-color;
|
||||
$panel-drop-zone-bg: repeating-linear-gradient(
|
||||
-128deg,
|
||||
$body-bg,
|
||||
$body-bg 10px,
|
||||
$gray-6 10px,
|
||||
$gray-6 20px
|
||||
);
|
||||
$panel-drop-zone-bg: repeating-linear-gradient(-128deg, $body-bg, $body-bg 10px, $gray-6 10px, $gray-6 20px);
|
||||
$panel-header-hover-bg: $gray-6;
|
||||
$panel-header-menu-hover-bg: $gray-4;
|
||||
$panel-edit-shadow: 0 0 30px 20px $black;
|
||||
@ -137,8 +126,7 @@ $code-tag-border: darken($code-tag-bg, 3%);
|
||||
// cards
|
||||
$card-background: linear-gradient(135deg, $gray-6, $gray-5);
|
||||
$card-background-hover: linear-gradient(135deg, $gray-5, $gray-6);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1),
|
||||
1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
$card-shadow: -1px -1px 0 0 hsla(0, 0%, 100%, 0.1), 1px 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
// Lists
|
||||
$list-item-bg: linear-gradient(135deg, $gray-5, $gray-6); //$card-background;
|
||||
@ -154,7 +142,8 @@ $table-bg-hover: $gray-5; // for hover
|
||||
$table-bg-active: $table-bg-hover !default;
|
||||
$table-border: $gray-3; // table and cell border
|
||||
|
||||
$table-bg-odd: $gray-5;
|
||||
$table-bg-odd: $gray-6;
|
||||
$table-bg-hover: $gray-5;
|
||||
|
||||
// Scrollbars
|
||||
$scrollbarBackground: $gray-5;
|
||||
@ -187,7 +176,7 @@ $btn-link-color: $gray-1;
|
||||
|
||||
$btn-divider-left: $gray-4;
|
||||
$btn-divider-right: $gray-7;
|
||||
$btn-drag-image: "../img/grab_light.svg";
|
||||
$btn-drag-image: '../img/grab_light.svg';
|
||||
|
||||
$iconContainerBackground: $white;
|
||||
|
||||
@ -332,14 +321,10 @@ $tooltipLinkColor: lighten($popover-help-color, 5%);
|
||||
$graph-tooltip-bg: $gray-5;
|
||||
|
||||
// images
|
||||
$checkboxImageUrl: "../img/checkbox_white.png";
|
||||
$checkboxImageUrl: '../img/checkbox_white.png';
|
||||
|
||||
// info box
|
||||
$info-box-background: linear-gradient(
|
||||
100deg,
|
||||
$blue-dark,
|
||||
darken($blue-dark, 5%)
|
||||
);
|
||||
$info-box-background: linear-gradient(100deg, $blue-dark, darken($blue-dark, 5%));
|
||||
$info-box-color: $gray-7;
|
||||
|
||||
// footer
|
||||
|
@ -33,7 +33,7 @@
|
||||
border-top: 4px solid $text-color-weak;
|
||||
border-right: 4px solid transparent;
|
||||
border-left: 4px solid transparent;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
|
||||
// Place the caret
|
||||
@ -218,7 +218,7 @@
|
||||
.caret {
|
||||
border-top: 0;
|
||||
border-bottom: 4px solid $black;
|
||||
content: "";
|
||||
content: '';
|
||||
}
|
||||
// Different positioning for bottom up menu
|
||||
.dropdown-menu {
|
||||
@ -255,9 +255,9 @@
|
||||
}
|
||||
|
||||
// Caret to indicate there is a submenu
|
||||
.dropdown-submenu > a::after {
|
||||
.dropdown-submenu > a::before {
|
||||
display: block;
|
||||
content: " ";
|
||||
content: ' ';
|
||||
float: right;
|
||||
width: 0;
|
||||
height: 0;
|
||||
@ -312,7 +312,7 @@
|
||||
width: 2rem;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
content: "\f11c";
|
||||
content: '\f11c';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@
|
||||
|
||||
&--hover {
|
||||
tbody tr:hover {
|
||||
background: $dark-3;
|
||||
background: $table-bg-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.search-container {
|
||||
left: $side-menu-width;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
@ -38,13 +38,6 @@
|
||||
background-color: $navbarButtonBackground;
|
||||
flex-grow: 10;
|
||||
}
|
||||
|
||||
// .tag-filter {
|
||||
// .Select-control {
|
||||
// width: 300px;
|
||||
// background-color: $navbarBackground;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.search-field-spacer {
|
||||
@ -58,7 +51,7 @@
|
||||
|
||||
.search-dropdown {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
height: calc(100% - #{$navbarHeight});
|
||||
}
|
||||
|
||||
@ -74,9 +67,8 @@
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
padding-top: 16px;
|
||||
display: flex;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-filter-box {
|
||||
@ -85,7 +77,6 @@
|
||||
padding: $spacer*1.5;
|
||||
min-width: 340px;
|
||||
margin-bottom: $spacer * 1.5;
|
||||
margin-left: $spacer * 1.5;
|
||||
}
|
||||
|
||||
.search-filter-box__header {
|
||||
@ -215,7 +206,8 @@
|
||||
}
|
||||
|
||||
.search-item__tags {
|
||||
padding: 10px;
|
||||
display: none;
|
||||
//padding: 10px;
|
||||
}
|
||||
|
||||
.search-item__actions {
|
||||
@ -248,16 +240,46 @@
|
||||
background: $panel-bg;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
@include media-breakpoint-up(sm) {
|
||||
.search-container {
|
||||
left: 0;
|
||||
left: $side-menu-width;
|
||||
}
|
||||
|
||||
.search-dropdown__col_2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.search-item__tags {
|
||||
display: none;
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.search-dropdown__col_2 {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
max-width: 700px;
|
||||
height: 260px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.search-dropdown__col_1 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.search-filter-box {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
.search-dropdown {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.search-dropdown__col_2 {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search-filter-box {
|
||||
margin-left: $spacer * 1.5;
|
||||
margin-bottom: $spacer * 1.5;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ RUN gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A170311380
|
||||
RUN curl --silent --location https://rpm.nodesource.com/setup_6.x | bash - && \
|
||||
yum install -y nodejs --nogpgcheck
|
||||
|
||||
ENV GOLANG_VERSION 1.9.2
|
||||
ENV GOLANG_VERSION 1.9.3
|
||||
|
||||
RUN wget https://dl.yarnpkg.com/rpm/yarn.repo -O /etc/yum.repos.d/yarn.repo && \
|
||||
yum install -y yarn --nogpgcheck && \
|
||||
|
@ -3,7 +3,7 @@ module.exports = function(config,grunt) {
|
||||
|
||||
grunt.registerTask('phantomjs', 'Copy phantomjs binary to vendor/', function() {
|
||||
|
||||
var dest = './vendor/phantomjs/phantomjs';
|
||||
var dest = './tools/phantomjs/phantomjs';
|
||||
var confDir = './node_modules/phantomjs-prebuilt/lib/';
|
||||
|
||||
if (process.platform === "win32") {
|
||||
|
@ -26,7 +26,7 @@ module.exports = function(grunt) {
|
||||
});
|
||||
grunt.config('copy.backend_files', {
|
||||
expand: true,
|
||||
src: ['conf/**', 'vendor/phantomjs/*', 'scripts/*'],
|
||||
src: ['conf/**', 'tools/phantomjs/*', 'scripts/*'],
|
||||
options: { mode: true},
|
||||
dest: '<%= tempDir %>'
|
||||
});
|
||||
|
86
tools/phantomjs/render.js
Normal file
86
tools/phantomjs/render.js
Normal file
@ -0,0 +1,86 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var page = require('webpage').create();
|
||||
var args = require('system').args;
|
||||
var params = {};
|
||||
var regexp = /^([^=]+)=([^$]+)/;
|
||||
|
||||
args.forEach(function(arg) {
|
||||
var parts = arg.match(regexp);
|
||||
if (!parts) { return; }
|
||||
params[parts[1]] = parts[2];
|
||||
});
|
||||
|
||||
var usage = "url=<url> png=<filename> width=<width> height=<height> renderKey=<key>";
|
||||
|
||||
if (!params.url || !params.png || !params.renderKey || !params.domain) {
|
||||
console.log(usage);
|
||||
phantom.exit();
|
||||
}
|
||||
|
||||
phantom.addCookie({
|
||||
'name': 'renderKey',
|
||||
'value': params.renderKey,
|
||||
'domain': params.domain,
|
||||
});
|
||||
|
||||
page.viewportSize = {
|
||||
width: params.width || '800',
|
||||
height: params.height || '400'
|
||||
};
|
||||
|
||||
var timeoutMs = (parseInt(params.timeout) || 10) * 1000;
|
||||
var waitBetweenReadyCheckMs = 50;
|
||||
var totalWaitMs = 0;
|
||||
|
||||
page.open(params.url, function (status) {
|
||||
console.log('Loading a web page: ' + params.url + ' status: ' + status, timeoutMs);
|
||||
|
||||
page.onError = function(msg, trace) {
|
||||
var msgStack = ['ERROR: ' + msg];
|
||||
if (trace && trace.length) {
|
||||
msgStack.push('TRACE:');
|
||||
trace.forEach(function(t) {
|
||||
msgStack.push(' -> ' + t.file + ': ' + t.line + (t.function ? ' (in function "' + t.function +'")' : ''));
|
||||
});
|
||||
}
|
||||
console.error(msgStack.join('\n'));
|
||||
};
|
||||
|
||||
function checkIsReady() {
|
||||
var panelsRendered = page.evaluate(function() {
|
||||
if (!window.angular) { return false; }
|
||||
var body = window.angular.element(document.body);
|
||||
if (!body.injector) { return false; }
|
||||
if (!body.injector()) { return false; }
|
||||
|
||||
var rootScope = body.injector().get('$rootScope');
|
||||
if (!rootScope) {return false;}
|
||||
var panels = angular.element('div.panel:visible').length;
|
||||
return rootScope.panelsRendered >= panels;
|
||||
});
|
||||
|
||||
if (panelsRendered || totalWaitMs > timeoutMs) {
|
||||
var bb = page.evaluate(function () {
|
||||
return document.getElementsByClassName("main-view")[0].getBoundingClientRect();
|
||||
});
|
||||
|
||||
page.clipRect = {
|
||||
top: bb.top,
|
||||
left: bb.left,
|
||||
width: bb.width,
|
||||
height: bb.height
|
||||
};
|
||||
|
||||
page.render(params.png);
|
||||
phantom.exit();
|
||||
} else {
|
||||
totalWaitMs += waitBetweenReadyCheckMs;
|
||||
setTimeout(checkIsReady, waitBetweenReadyCheckMs);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(checkIsReady, waitBetweenReadyCheckMs);
|
||||
});
|
||||
})();
|
152
vendor/cloud.google.com/go/CONTRIBUTING.md
generated
vendored
152
vendor/cloud.google.com/go/CONTRIBUTING.md
generated
vendored
@ -1,152 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
1. Sign one of the contributor license agreements below.
|
||||
1. `go get golang.org/x/review/git-codereview` to install the code reviewing tool.
|
||||
1. You will need to ensure that your `GOBIN` directory (by default
|
||||
`$GOPATH/bin`) is in your `PATH` so that git can find the command.
|
||||
1. If you would like, you may want to set up aliases for git-codereview,
|
||||
such that `git codereview change` becomes `git change`. See the
|
||||
[godoc](https://godoc.org/golang.org/x/review/git-codereview) for details.
|
||||
1. Should you run into issues with the git-codereview tool, please note
|
||||
that all error messages will assume that you have set up these
|
||||
aliases.
|
||||
1. Get the cloud package by running `go get -d cloud.google.com/go`.
|
||||
1. If you have already checked out the source, make sure that the remote git
|
||||
origin is https://code.googlesource.com/gocloud:
|
||||
|
||||
git remote set-url origin https://code.googlesource.com/gocloud
|
||||
1. Make sure your auth is configured correctly by visiting
|
||||
https://code.googlesource.com, clicking "Generate Password", and following
|
||||
the directions.
|
||||
1. Make changes and create a change by running `git codereview change <name>`,
|
||||
provide a commit message, and use `git codereview mail` to create a Gerrit CL.
|
||||
1. Keep amending to the change with `git codereview change` and mail as your receive
|
||||
feedback. Each new mailed amendment will create a new patch set for your change in Gerrit.
|
||||
|
||||
## Integration Tests
|
||||
|
||||
In addition to the unit tests, you may run the integration test suite.
|
||||
|
||||
To run the integrations tests, creating and configuration of a project in the
|
||||
Google Developers Console is required.
|
||||
|
||||
After creating a project, you must [create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount).
|
||||
Ensure the project-level **Owner**
|
||||
[IAM role](console.cloud.google.com/iam-admin/iam/project) role is added to the
|
||||
service account. Alternatively, the account can be granted all of the following roles:
|
||||
- **Editor**
|
||||
- **Logs Configuration Writer**
|
||||
- **PubSub Admin**
|
||||
|
||||
Once you create a project, set the following environment variables to be able to
|
||||
run the against the actual APIs.
|
||||
|
||||
- **GCLOUD_TESTS_GOLANG_PROJECT_ID**: Developers Console project's ID (e.g. bamboo-shift-455)
|
||||
- **GCLOUD_TESTS_GOLANG_KEY**: The path to the JSON key file.
|
||||
- **GCLOUD_TESTS_API_KEY**: Your API key.
|
||||
|
||||
Firestore requires a different project and key:
|
||||
|
||||
- **GCLOUD_TESTS_GOLANG_FIRESTORE_PROJECT_ID**: Developers Console project's ID
|
||||
supporting Firestore
|
||||
- **GCLOUD_TESTS_GOLANG_FIRESTORE_KEY**: The path to the JSON key file.
|
||||
|
||||
Install the [gcloud command-line tool][gcloudcli] to your machine and use it
|
||||
to create some resources used in integration tests.
|
||||
|
||||
From the project's root directory:
|
||||
|
||||
``` sh
|
||||
# Set the default project in your env.
|
||||
$ gcloud config set project $GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Authenticate the gcloud tool with your account.
|
||||
$ gcloud auth login
|
||||
|
||||
# Create the indexes used in the datastore integration tests.
|
||||
$ gcloud preview datastore create-indexes datastore/testdata/index.yaml
|
||||
|
||||
# Create a Google Cloud storage bucket with the same name as your test project,
|
||||
# and with the Stackdriver Logging service account as owner, for the sink
|
||||
# integration tests in logging.
|
||||
$ gsutil mb gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
$ gsutil acl ch -g cloud-logs@google.com:O gs://$GCLOUD_TESTS_GOLANG_PROJECT_ID
|
||||
|
||||
# Create a PubSub topic for integration tests of storage notifications.
|
||||
$ gcloud beta pubsub topics create go-storage-notification-test
|
||||
|
||||
# Create a Spanner instance for the spanner integration tests.
|
||||
$ gcloud beta spanner instances create go-integration-test --config regional-us-central1 --nodes 1 --description 'Instance for go client test'
|
||||
# NOTE: Spanner instances are priced by the node-hour, so you may want to delete
|
||||
# the instance after testing with 'gcloud beta spanner instances delete'.
|
||||
|
||||
|
||||
```
|
||||
|
||||
Once you've set the environment variables, you can run the integration tests by
|
||||
running:
|
||||
|
||||
``` sh
|
||||
$ go test -v cloud.google.com/go/...
|
||||
```
|
||||
|
||||
## Contributor License Agreements
|
||||
|
||||
Before we can accept your pull requests you'll need to sign a Contributor
|
||||
License Agreement (CLA):
|
||||
|
||||
- **If you are an individual writing original source code** and **you own the
|
||||
intellectual property**, then you'll need to sign an [individual CLA][indvcla].
|
||||
- **If you work for a company that wants to allow you to contribute your
|
||||
work**, then you'll need to sign a [corporate CLA][corpcla].
|
||||
|
||||
You can sign these electronically (just scroll to the bottom). After that,
|
||||
we'll be able to accept your pull requests.
|
||||
|
||||
## Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
|
||||
[gcloudcli]: https://developers.google.com/cloud/sdk/gcloud/
|
||||
[indvcla]: https://developers.google.com/open-source/cla/individual
|
||||
[corpcla]: https://developers.google.com/open-source/cla/corporate
|
1
vendor/cloud.google.com/go/CONTRIBUTORS
generated
vendored
1
vendor/cloud.google.com/go/CONTRIBUTORS
generated
vendored
@ -27,6 +27,7 @@ Jonathan Amsterdam <jba@google.com>
|
||||
Kunpei Sakai <namusyaka@gmail.com>
|
||||
Luna Duclos <luna.duclos@palmstonegames.com>
|
||||
Magnus Hiie <magnus.hiie@gmail.com>
|
||||
Mario Castro <mariocaster@gmail.com>
|
||||
Michael McGreevy <mcgreevy@golang.org>
|
||||
Omar Jarjur <ojarjur@google.com>
|
||||
Paweł Knap <pawelknap88@gmail.com>
|
||||
|
2
vendor/cloud.google.com/go/LICENSE
generated
vendored
2
vendor/cloud.google.com/go/LICENSE
generated
vendored
@ -187,7 +187,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2014 Google Inc.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
54
vendor/cloud.google.com/go/MIGRATION.md
generated
vendored
54
vendor/cloud.google.com/go/MIGRATION.md
generated
vendored
@ -1,54 +0,0 @@
|
||||
# Code Changes
|
||||
|
||||
## v0.10.0
|
||||
|
||||
- pubsub: Replace
|
||||
|
||||
```
|
||||
sub.ModifyPushConfig(ctx, pubsub.PushConfig{Endpoint: "https://example.com/push"})
|
||||
```
|
||||
|
||||
with
|
||||
|
||||
```
|
||||
sub.Update(ctx, pubsub.SubscriptionConfigToUpdate{
|
||||
PushConfig: &pubsub.PushConfig{Endpoint: "https://example.com/push"},
|
||||
})
|
||||
```
|
||||
|
||||
- trace: traceGRPCServerInterceptor will be provided from *trace.Client.
|
||||
Given an initialized `*trace.Client` named `tc`, instead of
|
||||
|
||||
```
|
||||
s := grpc.NewServer(grpc.UnaryInterceptor(trace.GRPCServerInterceptor(tc)))
|
||||
```
|
||||
|
||||
write
|
||||
|
||||
```
|
||||
s := grpc.NewServer(grpc.UnaryInterceptor(tc.GRPCServerInterceptor()))
|
||||
```
|
||||
|
||||
- trace trace.GRPCClientInterceptor will also provided from *trace.Client.
|
||||
Instead of
|
||||
|
||||
```
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithUnaryInterceptor(trace.GRPCClientInterceptor()))
|
||||
```
|
||||
|
||||
write
|
||||
|
||||
```
|
||||
conn, err := grpc.Dial(srv.Addr, grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor()))
|
||||
```
|
||||
|
||||
- trace: We removed the deprecated `trace.EnableGRPCTracing`. Use the gRPC
|
||||
interceptor as a dial option as shown below when initializing Cloud package
|
||||
clients:
|
||||
|
||||
```
|
||||
c, err := pubsub.NewClient(ctx, "project-id", option.WithGRPCDialOption(grpc.WithUnaryInterceptor(tc.GRPCClientInterceptor())))
|
||||
if err != nil {
|
||||
...
|
||||
}
|
||||
```
|
502
vendor/cloud.google.com/go/README.md
generated
vendored
502
vendor/cloud.google.com/go/README.md
generated
vendored
@ -1,502 +0,0 @@
|
||||
# Google Cloud Client Libraries for Go
|
||||
|
||||
[](https://godoc.org/cloud.google.com/go)
|
||||
|
||||
Go packages for [Google Cloud Platform](https://cloud.google.com) services.
|
||||
|
||||
``` go
|
||||
import "cloud.google.com/go"
|
||||
```
|
||||
|
||||
To install the packages on your system,
|
||||
|
||||
```
|
||||
$ go get -u cloud.google.com/go/...
|
||||
```
|
||||
|
||||
**NOTE:** Some of these packages are under development, and may occasionally
|
||||
make backwards-incompatible changes.
|
||||
|
||||
**NOTE:** Github repo is a mirror of [https://code.googlesource.com/gocloud](https://code.googlesource.com/gocloud).
|
||||
|
||||
* [News](#news)
|
||||
* [Supported APIs](#supported-apis)
|
||||
* [Go Versions Supported](#go-versions-supported)
|
||||
* [Authorization](#authorization)
|
||||
* [Cloud Datastore](#cloud-datastore-)
|
||||
* [Cloud Storage](#cloud-storage-)
|
||||
* [Cloud Pub/Sub](#cloud-pub-sub-)
|
||||
* [Cloud BigQuery](#cloud-bigquery-)
|
||||
* [Stackdriver Logging](#stackdriver-logging-)
|
||||
* [Cloud Spanner](#cloud-spanner-)
|
||||
|
||||
|
||||
## News
|
||||
|
||||
_December 11, 2017_
|
||||
|
||||
*v0.17.0*
|
||||
|
||||
- firestore BREAKING CHANGES:
|
||||
- Remove UpdateMap and UpdateStruct; rename UpdatePaths to Update.
|
||||
Change
|
||||
`docref.UpdateMap(ctx, map[string]interface{}{"a.b", 1})`
|
||||
to
|
||||
`docref.Update(ctx, []firestore.Update{{Path: "a.b", Value: 1}})`
|
||||
|
||||
Change
|
||||
`docref.UpdateStruct(ctx, []string{"Field"}, aStruct)`
|
||||
to
|
||||
`docref.Update(ctx, []firestore.Update{{Path: "Field", Value: aStruct.Field}})`
|
||||
- Rename MergePaths to Merge; require args to be FieldPaths
|
||||
- A value stored as an integer can be read into a floating-point field, and vice versa.
|
||||
- bigtable/cmd/cbt:
|
||||
- Support deleting a column.
|
||||
- Add regex option for row read.
|
||||
- spanner: Mark stable.
|
||||
- storage:
|
||||
- Add Reader.ContentEncoding method.
|
||||
- Fix handling of SignedURL headers.
|
||||
- bigquery:
|
||||
- If Uploader.Put is called with no rows, it returns nil without making a
|
||||
call.
|
||||
- Schema inference supports the "nullable" option in struct tags for
|
||||
non-required fields.
|
||||
- TimePartitioning supports "Field".
|
||||
|
||||
|
||||
_October 30, 2017_
|
||||
|
||||
*v0.16.0*
|
||||
|
||||
- Other bigquery changes:
|
||||
- `JobIterator.Next` returns `*Job`; removed `JobInfo` (BREAKING CHANGE).
|
||||
- UseStandardSQL is deprecated; set UseLegacySQL to true if you need
|
||||
Legacy SQL.
|
||||
- Uploader.Put will generate a random insert ID if you do not provide one.
|
||||
- Support time partitioning for load jobs.
|
||||
- Support dry-run queries.
|
||||
- A `Job` remembers its last retrieved status.
|
||||
- Support retrieving job configuration.
|
||||
- Support labels for jobs and tables.
|
||||
- Support dataset access lists.
|
||||
- Improve support for external data sources, including data from Bigtable and
|
||||
Google Sheets, and tables with external data.
|
||||
- Support updating a table's view configuration.
|
||||
- Fix uploading civil times with nanoseconds.
|
||||
|
||||
- storage:
|
||||
- Support PubSub notifications.
|
||||
- Support Requester Pays buckets.
|
||||
|
||||
- profiler: Support goroutine and mutex profile types.
|
||||
|
||||
|
||||
_October 3, 2017_
|
||||
|
||||
*v0.15.0*
|
||||
|
||||
- firestore: beta release. See the
|
||||
[announcement](https://firebase.googleblog.com/2017/10/introducing-cloud-firestore.html).
|
||||
|
||||
- errorreporting: The existing package has been redesigned.
|
||||
|
||||
- errors: This package has been removed. Use errorreporting.
|
||||
|
||||
|
||||
[Older news](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/old-news.md)
|
||||
|
||||
## Supported APIs
|
||||
|
||||
Google API | Status | Package
|
||||
---------------------------------|--------------|-----------------------------------------------------------
|
||||
[Datastore][cloud-datastore] | stable | [`cloud.google.com/go/datastore`][cloud-datastore-ref]
|
||||
[Firestore][cloud-firestore] | beta | [`cloud.google.com/go/firestore`][cloud-firestore-ref]
|
||||
[Storage][cloud-storage] | stable | [`cloud.google.com/go/storage`][cloud-storage-ref]
|
||||
[Bigtable][cloud-bigtable] | beta | [`cloud.google.com/go/bigtable`][cloud-bigtable-ref]
|
||||
[BigQuery][cloud-bigquery] | beta | [`cloud.google.com/go/bigquery`][cloud-bigquery-ref]
|
||||
[Logging][cloud-logging] | stable | [`cloud.google.com/go/logging`][cloud-logging-ref]
|
||||
[Monitoring][cloud-monitoring] | beta | [`cloud.google.com/go/monitoring/apiv3`][cloud-monitoring-ref]
|
||||
[Pub/Sub][cloud-pubsub] | beta | [`cloud.google.com/go/pubsub`][cloud-pubsub-ref]
|
||||
[Vision][cloud-vision] | stable | [`cloud.google.com/go/vision/apiv1`][cloud-vision-ref]
|
||||
[Language][cloud-language] | stable | [`cloud.google.com/go/language/apiv1`][cloud-language-ref]
|
||||
[Speech][cloud-speech] | stable | [`cloud.google.com/go/speech/apiv1`][cloud-speech-ref]
|
||||
[Spanner][cloud-spanner] | stable | [`cloud.google.com/go/spanner`][cloud-spanner-ref]
|
||||
[Translation][cloud-translation] | stable | [`cloud.google.com/go/translate`][cloud-translation-ref]
|
||||
[Trace][cloud-trace] | alpha | [`cloud.google.com/go/trace`][cloud-trace-ref]
|
||||
[Video Intelligence][cloud-video]| beta | [`cloud.google.com/go/videointelligence/apiv1beta1`][cloud-video-ref]
|
||||
[ErrorReporting][cloud-errors] | alpha | [`cloud.google.com/go/errorreporting`][cloud-errors-ref]
|
||||
|
||||
|
||||
> **Alpha status**: the API is still being actively developed. As a
|
||||
> result, it might change in backward-incompatible ways and is not recommended
|
||||
> for production use.
|
||||
>
|
||||
> **Beta status**: the API is largely complete, but still has outstanding
|
||||
> features and bugs to be addressed. There may be minor backwards-incompatible
|
||||
> changes where necessary.
|
||||
>
|
||||
> **Stable status**: the API is mature and ready for production use. We will
|
||||
> continue addressing bugs and feature requests.
|
||||
|
||||
Documentation and examples are available at
|
||||
https://godoc.org/cloud.google.com/go
|
||||
|
||||
Visit or join the
|
||||
[google-api-go-announce group](https://groups.google.com/forum/#!forum/google-api-go-announce)
|
||||
for updates on these packages.
|
||||
|
||||
## Go Versions Supported
|
||||
|
||||
We support the two most recent major versions of Go. If Google App Engine uses
|
||||
an older version, we support that as well. You can see which versions are
|
||||
currently supported by looking at the lines following `go:` in
|
||||
[`.travis.yml`](.travis.yml).
|
||||
|
||||
## Authorization
|
||||
|
||||
By default, each API will use [Google Application Default Credentials][default-creds]
|
||||
for authorization credentials used in calling the API endpoints. This will allow your
|
||||
application to run in many environments without requiring explicit configuration.
|
||||
|
||||
[snip]:# (auth)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
```
|
||||
|
||||
To authorize using a
|
||||
[JSON key file](https://cloud.google.com/iam/docs/managing-service-account-keys),
|
||||
pass
|
||||
[`option.WithServiceAccountFile`](https://godoc.org/google.golang.org/api/option#WithServiceAccountFile)
|
||||
to the `NewClient` function of the desired package. For example:
|
||||
|
||||
[snip]:# (auth-JSON)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx, option.WithServiceAccountFile("path/to/keyfile.json"))
|
||||
```
|
||||
|
||||
You can exert more control over authorization by using the
|
||||
[`golang.org/x/oauth2`](https://godoc.org/golang.org/x/oauth2) package to
|
||||
create an `oauth2.TokenSource`. Then pass
|
||||
[`option.WithTokenSource`](https://godoc.org/google.golang.org/api/option#WithTokenSource)
|
||||
to the `NewClient` function:
|
||||
[snip]:# (auth-ts)
|
||||
```go
|
||||
tokenSource := ...
|
||||
client, err := storage.NewClient(ctx, option.WithTokenSource(tokenSource))
|
||||
```
|
||||
|
||||
## Cloud Datastore [](https://godoc.org/cloud.google.com/go/datastore)
|
||||
|
||||
- [About Cloud Datastore][cloud-datastore]
|
||||
- [Activating the API for your project][cloud-datastore-activation]
|
||||
- [API documentation][cloud-datastore-docs]
|
||||
- [Go client documentation](https://godoc.org/cloud.google.com/go/datastore)
|
||||
- [Complete sample program](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/datastore/tasks)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `datastore.Client` to use throughout your application:
|
||||
|
||||
[snip]:# (datastore-1)
|
||||
```go
|
||||
client, err := datastore.NewClient(ctx, "my-project-id")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
Then use that client to interact with the API:
|
||||
|
||||
[snip]:# (datastore-2)
|
||||
```go
|
||||
type Post struct {
|
||||
Title string
|
||||
Body string `datastore:",noindex"`
|
||||
PublishedAt time.Time
|
||||
}
|
||||
keys := []*datastore.Key{
|
||||
datastore.NameKey("Post", "post1", nil),
|
||||
datastore.NameKey("Post", "post2", nil),
|
||||
}
|
||||
posts := []*Post{
|
||||
{Title: "Post 1", Body: "...", PublishedAt: time.Now()},
|
||||
{Title: "Post 2", Body: "...", PublishedAt: time.Now()},
|
||||
}
|
||||
if _, err := client.PutMulti(ctx, keys, posts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Cloud Storage [](https://godoc.org/cloud.google.com/go/storage)
|
||||
|
||||
- [About Cloud Storage][cloud-storage]
|
||||
- [API documentation][cloud-storage-docs]
|
||||
- [Go client documentation](https://godoc.org/cloud.google.com/go/storage)
|
||||
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/storage)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `storage.Client` to use throughout your application:
|
||||
|
||||
[snip]:# (storage-1)
|
||||
```go
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
[snip]:# (storage-2)
|
||||
```go
|
||||
// Read the object1 from bucket.
|
||||
rc, err := client.Bucket("bucket").Object("object1").NewReader(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rc.Close()
|
||||
body, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Cloud Pub/Sub [](https://godoc.org/cloud.google.com/go/pubsub)
|
||||
|
||||
- [About Cloud Pubsub][cloud-pubsub]
|
||||
- [API documentation][cloud-pubsub-docs]
|
||||
- [Go client documentation](https://godoc.org/cloud.google.com/go/pubsub)
|
||||
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/pubsub)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `pubsub.Client` to use throughout your application:
|
||||
|
||||
[snip]:# (pubsub-1)
|
||||
```go
|
||||
client, err := pubsub.NewClient(ctx, "project-id")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
Then use the client to publish and subscribe:
|
||||
|
||||
[snip]:# (pubsub-2)
|
||||
```go
|
||||
// Publish "hello world" on topic1.
|
||||
topic := client.Topic("topic1")
|
||||
res := topic.Publish(ctx, &pubsub.Message{
|
||||
Data: []byte("hello world"),
|
||||
})
|
||||
// The publish happens asynchronously.
|
||||
// Later, you can get the result from res:
|
||||
...
|
||||
msgID, err := res.Get(ctx)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Use a callback to receive messages via subscription1.
|
||||
sub := client.Subscription("subscription1")
|
||||
err = sub.Receive(ctx, func(ctx context.Context, m *pubsub.Message) {
|
||||
fmt.Println(m.Data)
|
||||
m.Ack() // Acknowledge that we've consumed the message.
|
||||
})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Cloud BigQuery [](https://godoc.org/cloud.google.com/go/bigquery)
|
||||
|
||||
- [About Cloud BigQuery][cloud-bigquery]
|
||||
- [API documentation][cloud-bigquery-docs]
|
||||
- [Go client documentation][cloud-bigquery-ref]
|
||||
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/bigquery)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `bigquery.Client` to use throughout your application:
|
||||
[snip]:# (bq-1)
|
||||
```go
|
||||
c, err := bigquery.NewClient(ctx, "my-project-ID")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
```
|
||||
|
||||
Then use that client to interact with the API:
|
||||
[snip]:# (bq-2)
|
||||
```go
|
||||
// Construct a query.
|
||||
q := c.Query(`
|
||||
SELECT year, SUM(number)
|
||||
FROM [bigquery-public-data:usa_names.usa_1910_2013]
|
||||
WHERE name = "William"
|
||||
GROUP BY year
|
||||
ORDER BY year
|
||||
`)
|
||||
// Execute the query.
|
||||
it, err := q.Read(ctx)
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// Iterate through the results.
|
||||
for {
|
||||
var values []bigquery.Value
|
||||
err := it.Next(&values)
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
fmt.Println(values)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Stackdriver Logging [](https://godoc.org/cloud.google.com/go/logging)
|
||||
|
||||
- [About Stackdriver Logging][cloud-logging]
|
||||
- [API documentation][cloud-logging-docs]
|
||||
- [Go client documentation][cloud-logging-ref]
|
||||
- [Complete sample programs](https://github.com/GoogleCloudPlatform/golang-samples/tree/master/logging)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `logging.Client` to use throughout your application:
|
||||
[snip]:# (logging-1)
|
||||
```go
|
||||
ctx := context.Background()
|
||||
client, err := logging.NewClient(ctx, "my-project")
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
```
|
||||
|
||||
Usually, you'll want to add log entries to a buffer to be periodically flushed
|
||||
(automatically and asynchronously) to the Stackdriver Logging service.
|
||||
[snip]:# (logging-2)
|
||||
```go
|
||||
logger := client.Logger("my-log")
|
||||
logger.Log(logging.Entry{Payload: "something happened!"})
|
||||
```
|
||||
|
||||
Close your client before your program exits, to flush any buffered log entries.
|
||||
[snip]:# (logging-3)
|
||||
```go
|
||||
err = client.Close()
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
```
|
||||
|
||||
## Cloud Spanner [](https://godoc.org/cloud.google.com/go/spanner)
|
||||
|
||||
- [About Cloud Spanner][cloud-spanner]
|
||||
- [API documentation][cloud-spanner-docs]
|
||||
- [Go client documentation](https://godoc.org/cloud.google.com/go/spanner)
|
||||
|
||||
### Example Usage
|
||||
|
||||
First create a `spanner.Client` to use throughout your application:
|
||||
|
||||
[snip]:# (spanner-1)
|
||||
```go
|
||||
client, err := spanner.NewClient(ctx, "projects/P/instances/I/databases/D")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
[snip]:# (spanner-2)
|
||||
```go
|
||||
// Simple Reads And Writes
|
||||
_, err = client.Apply(ctx, []*spanner.Mutation{
|
||||
spanner.Insert("Users",
|
||||
[]string{"name", "email"},
|
||||
[]interface{}{"alice", "a@example.com"})})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
row, err := client.Single().ReadRow(ctx, "Users",
|
||||
spanner.Key{"alice"}, []string{"email"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome. Please, see the
|
||||
[CONTRIBUTING](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md)
|
||||
document for details. We're using Gerrit for our code reviews. Please don't open pull
|
||||
requests against this repo, new pull requests will be automatically closed.
|
||||
|
||||
Please note that this project is released with a Contributor Code of Conduct.
|
||||
By participating in this project you agree to abide by its terms.
|
||||
See [Contributor Code of Conduct](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/CONTRIBUTING.md#contributor-code-of-conduct)
|
||||
for more information.
|
||||
|
||||
[cloud-datastore]: https://cloud.google.com/datastore/
|
||||
[cloud-datastore-ref]: https://godoc.org/cloud.google.com/go/datastore
|
||||
[cloud-datastore-docs]: https://cloud.google.com/datastore/docs
|
||||
[cloud-datastore-activation]: https://cloud.google.com/datastore/docs/activate
|
||||
|
||||
[cloud-firestore]: https://cloud.google.com/firestore/
|
||||
[cloud-firestore-ref]: https://godoc.org/cloud.google.com/go/firestore
|
||||
[cloud-firestore-docs]: https://cloud.google.com/firestore/docs
|
||||
[cloud-firestore-activation]: https://cloud.google.com/firestore/docs/activate
|
||||
|
||||
[cloud-pubsub]: https://cloud.google.com/pubsub/
|
||||
[cloud-pubsub-ref]: https://godoc.org/cloud.google.com/go/pubsub
|
||||
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
|
||||
|
||||
[cloud-storage]: https://cloud.google.com/storage/
|
||||
[cloud-storage-ref]: https://godoc.org/cloud.google.com/go/storage
|
||||
[cloud-storage-docs]: https://cloud.google.com/storage/docs
|
||||
[cloud-storage-create-bucket]: https://cloud.google.com/storage/docs/cloud-console#_creatingbuckets
|
||||
|
||||
[cloud-bigtable]: https://cloud.google.com/bigtable/
|
||||
[cloud-bigtable-ref]: https://godoc.org/cloud.google.com/go/bigtable
|
||||
|
||||
[cloud-bigquery]: https://cloud.google.com/bigquery/
|
||||
[cloud-bigquery-docs]: https://cloud.google.com/bigquery/docs
|
||||
[cloud-bigquery-ref]: https://godoc.org/cloud.google.com/go/bigquery
|
||||
|
||||
[cloud-logging]: https://cloud.google.com/logging/
|
||||
[cloud-logging-docs]: https://cloud.google.com/logging/docs
|
||||
[cloud-logging-ref]: https://godoc.org/cloud.google.com/go/logging
|
||||
|
||||
[cloud-monitoring]: https://cloud.google.com/monitoring/
|
||||
[cloud-monitoring-ref]: https://godoc.org/cloud.google.com/go/monitoring/apiv3
|
||||
|
||||
[cloud-vision]: https://cloud.google.com/vision
|
||||
[cloud-vision-ref]: https://godoc.org/cloud.google.com/go/vision/apiv1
|
||||
|
||||
[cloud-language]: https://cloud.google.com/natural-language
|
||||
[cloud-language-ref]: https://godoc.org/cloud.google.com/go/language/apiv1
|
||||
|
||||
[cloud-speech]: https://cloud.google.com/speech
|
||||
[cloud-speech-ref]: https://godoc.org/cloud.google.com/go/speech/apiv1
|
||||
|
||||
[cloud-spanner]: https://cloud.google.com/spanner/
|
||||
[cloud-spanner-ref]: https://godoc.org/cloud.google.com/go/spanner
|
||||
[cloud-spanner-docs]: https://cloud.google.com/spanner/docs
|
||||
|
||||
[cloud-translation]: https://cloud.google.com/translation
|
||||
[cloud-translation-ref]: https://godoc.org/cloud.google.com/go/translation
|
||||
|
||||
[cloud-trace]: https://cloud.google.com/trace/
|
||||
[cloud-trace-ref]: https://godoc.org/cloud.google.com/go/trace
|
||||
|
||||
[cloud-video]: https://cloud.google.com/video-intelligence/
|
||||
[cloud-video-ref]: https://godoc.org/cloud.google.com/go/videointelligence/apiv1beta1
|
||||
|
||||
[cloud-errors]: https://cloud.google.com/error-reporting/
|
||||
[cloud-errors-ref]: https://godoc.org/cloud.google.com/go/errorreporting
|
||||
|
||||
[default-creds]: https://developers.google.com/identity/protocols/application-default-credentials
|
20
vendor/cloud.google.com/go/cloud.go
generated
vendored
20
vendor/cloud.google.com/go/cloud.go
generated
vendored
@ -1,20 +0,0 @@
|
||||
// Copyright 2014 Google Inc. 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
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package cloud is the root of the packages used to access Google Cloud
|
||||
// Services. See https://godoc.org/cloud.google.com/go for a full list
|
||||
// of sub-packages.
|
||||
//
|
||||
// This package documents how to authorize and authenticate the sub packages.
|
||||
package cloud // import "cloud.google.com/go"
|
BIN
vendor/cloud.google.com/go/keys.tar.enc
generated
vendored
BIN
vendor/cloud.google.com/go/keys.tar.enc
generated
vendored
Binary file not shown.
557
vendor/cloud.google.com/go/old-news.md
generated
vendored
557
vendor/cloud.google.com/go/old-news.md
generated
vendored
@ -1,557 +0,0 @@
|
||||
_September 28, 2017_
|
||||
|
||||
*v0.14.0*
|
||||
|
||||
- bigquery BREAKING CHANGES:
|
||||
- Standard SQL is the default for queries and views.
|
||||
- `Table.Create` takes `TableMetadata` as a second argument, instead of
|
||||
options.
|
||||
- `Dataset.Create` takes `DatasetMetadata` as a second argument.
|
||||
- `DatasetMetadata` field `ID` renamed to `FullID`
|
||||
- `TableMetadata` field `ID` renamed to `FullID`
|
||||
|
||||
- Other bigquery changes:
|
||||
- The client will append a random suffix to a provided job ID if you set
|
||||
`AddJobIDSuffix` to true in a job config.
|
||||
- Listing jobs is supported.
|
||||
- Better retry logic.
|
||||
|
||||
- vision, language, speech: clients are now stable
|
||||
|
||||
- monitoring: client is now beta
|
||||
|
||||
- profiler:
|
||||
- Rename InstanceName to Instance, ZoneName to Zone
|
||||
- Auto-detect service name and version on AppEngine.
|
||||
|
||||
_September 8, 2017_
|
||||
|
||||
*v0.13.0*
|
||||
|
||||
- bigquery: UseLegacySQL options for CreateTable and QueryConfig. Use these
|
||||
options to continue using Legacy SQL after the client switches its default
|
||||
to Standard SQL.
|
||||
|
||||
- bigquery: Support for updating dataset labels.
|
||||
|
||||
- bigquery: Set DatasetIterator.ProjectID to list datasets in a project other
|
||||
than the client's. DatasetsInProject is no longer needed and is deprecated.
|
||||
|
||||
- bigtable: Fail ListInstances when any zones fail.
|
||||
|
||||
- spanner: support decoding of slices of basic types (e.g. []string, []int64,
|
||||
etc.)
|
||||
|
||||
- logging/logadmin: UpdateSink no longer creates a sink if it is missing
|
||||
(actually a change to the underlying service, not the client)
|
||||
|
||||
- profiler: Service and ServiceVersion replace Target in Config.
|
||||
|
||||
_August 22, 2017_
|
||||
|
||||
*v0.12.0*
|
||||
|
||||
- pubsub: Subscription.Receive now uses streaming pull.
|
||||
|
||||
- pubsub: add Client.TopicInProject to access topics in a different project
|
||||
than the client.
|
||||
|
||||
- errors: renamed errorreporting. The errors package will be removed shortly.
|
||||
|
||||
- datastore: improved retry behavior.
|
||||
|
||||
- bigquery: support updates to dataset metadata, with etags.
|
||||
|
||||
- bigquery: add etag support to Table.Update (BREAKING: etag argument added).
|
||||
|
||||
- bigquery: generate all job IDs on the client.
|
||||
|
||||
- storage: support bucket lifecycle configurations.
|
||||
|
||||
|
||||
_July 31, 2017_
|
||||
|
||||
*v0.11.0*
|
||||
|
||||
- Clients for spanner, pubsub and video are now in beta.
|
||||
|
||||
- New client for DLP.
|
||||
|
||||
- spanner: performance and testing improvements.
|
||||
|
||||
- storage: requester-pays buckets are supported.
|
||||
|
||||
- storage, profiler, bigtable, bigquery: bug fixes and other minor improvements.
|
||||
|
||||
- pubsub: bug fixes and other minor improvements
|
||||
|
||||
_June 17, 2017_
|
||||
|
||||
|
||||
*v0.10.0*
|
||||
|
||||
- pubsub: Subscription.ModifyPushConfig replaced with Subscription.Update.
|
||||
|
||||
- pubsub: Subscription.Receive now runs concurrently for higher throughput.
|
||||
|
||||
- vision: cloud.google.com/go/vision is deprecated. Use
|
||||
cloud.google.com/go/vision/apiv1 instead.
|
||||
|
||||
- translation: now stable.
|
||||
|
||||
- trace: several changes to the surface. See the link below.
|
||||
|
||||
[Code changes required from v0.9.0.](https://github.com/GoogleCloudPlatform/google-cloud-go/blob/master/MIGRATION.md)
|
||||
|
||||
|
||||
_March 17, 2017_
|
||||
|
||||
Breaking Pubsub changes.
|
||||
* Publish is now asynchronous
|
||||
([announcement](https://groups.google.com/d/topic/google-api-go-announce/aaqRDIQ3rvU/discussion)).
|
||||
* Subscription.Pull replaced by Subscription.Receive, which takes a callback ([announcement](https://groups.google.com/d/topic/google-api-go-announce/8pt6oetAdKc/discussion)).
|
||||
* Message.Done replaced with Message.Ack and Message.Nack.
|
||||
|
||||
_February 14, 2017_
|
||||
|
||||
Release of a client library for Spanner. See
|
||||
the
|
||||
[blog post](https://cloudplatform.googleblog.com/2017/02/introducing-Cloud-Spanner-a-global-database-service-for-mission-critical-applications.html).
|
||||
|
||||
Note that although the Spanner service is beta, the Go client library is alpha.
|
||||
|
||||
_December 12, 2016_
|
||||
|
||||
Beta release of BigQuery, DataStore, Logging and Storage. See the
|
||||
[blog post](https://cloudplatform.googleblog.com/2016/12/announcing-new-google-cloud-client.html).
|
||||
|
||||
Also, BigQuery now supports structs. Read a row directly into a struct with
|
||||
`RowIterator.Next`, and upload a row directly from a struct with `Uploader.Put`.
|
||||
You can also use field tags. See the [package documentation][cloud-bigquery-ref]
|
||||
for details.
|
||||
|
||||
_December 5, 2016_
|
||||
|
||||
More changes to BigQuery:
|
||||
|
||||
* The `ValueList` type was removed. It is no longer necessary. Instead of
|
||||
```go
|
||||
var v ValueList
|
||||
... it.Next(&v) ..
|
||||
```
|
||||
use
|
||||
|
||||
```go
|
||||
var v []Value
|
||||
... it.Next(&v) ...
|
||||
```
|
||||
|
||||
* Previously, repeatedly calling `RowIterator.Next` on the same `[]Value` or
|
||||
`ValueList` would append to the slice. Now each call resets the size to zero first.
|
||||
|
||||
* Schema inference will infer the SQL type BYTES for a struct field of
|
||||
type []byte. Previously it inferred STRING.
|
||||
|
||||
* The types `uint`, `uint64` and `uintptr` are no longer supported in schema
|
||||
inference. BigQuery's integer type is INT64, and those types may hold values
|
||||
that are not correctly represented in a 64-bit signed integer.
|
||||
|
||||
* The SQL types DATE, TIME and DATETIME are now supported. They correspond to
|
||||
the `Date`, `Time` and `DateTime` types in the new `cloud.google.com/go/civil`
|
||||
package.
|
||||
|
||||
_November 17, 2016_
|
||||
|
||||
Change to BigQuery: values from INTEGER columns will now be returned as int64,
|
||||
not int. This will avoid errors arising from large values on 32-bit systems.
|
||||
|
||||
_November 8, 2016_
|
||||
|
||||
New datastore feature: datastore now encodes your nested Go structs as Entity values,
|
||||
instead of a flattened list of the embedded struct's fields.
|
||||
This means that you may now have twice-nested slices, eg.
|
||||
```go
|
||||
type State struct {
|
||||
Cities []struct{
|
||||
Populations []int
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/79jtrdeuJAg) for
|
||||
more details.
|
||||
|
||||
_November 8, 2016_
|
||||
|
||||
Breaking changes to datastore: contexts no longer hold namespaces; instead you
|
||||
must set a key's namespace explicitly. Also, key functions have been changed
|
||||
and renamed.
|
||||
|
||||
* The WithNamespace function has been removed. To specify a namespace in a Query, use the Query.Namespace method:
|
||||
```go
|
||||
q := datastore.NewQuery("Kind").Namespace("ns")
|
||||
```
|
||||
|
||||
* All the fields of Key are exported. That means you can construct any Key with a struct literal:
|
||||
```go
|
||||
k := &Key{Kind: "Kind", ID: 37, Namespace: "ns"}
|
||||
```
|
||||
|
||||
* As a result of the above, the Key methods Kind, ID, d.Name, Parent, SetParent and Namespace have been removed.
|
||||
|
||||
* `NewIncompleteKey` has been removed, replaced by `IncompleteKey`. Replace
|
||||
```go
|
||||
NewIncompleteKey(ctx, kind, parent)
|
||||
```
|
||||
with
|
||||
```go
|
||||
IncompleteKey(kind, parent)
|
||||
```
|
||||
and if you do use namespaces, make sure you set the namespace on the returned key.
|
||||
|
||||
* `NewKey` has been removed, replaced by `NameKey` and `IDKey`. Replace
|
||||
```go
|
||||
NewKey(ctx, kind, name, 0, parent)
|
||||
NewKey(ctx, kind, "", id, parent)
|
||||
```
|
||||
with
|
||||
```go
|
||||
NameKey(kind, name, parent)
|
||||
IDKey(kind, id, parent)
|
||||
```
|
||||
and if you do use namespaces, make sure you set the namespace on the returned key.
|
||||
|
||||
* The `Done` variable has been removed. Replace `datastore.Done` with `iterator.Done`, from the package `google.golang.org/api/iterator`.
|
||||
|
||||
* The `Client.Close` method will have a return type of error. It will return the result of closing the underlying gRPC connection.
|
||||
|
||||
See [the announcement](https://groups.google.com/forum/#!topic/google-api-go-announce/hqXtM_4Ix-0) for
|
||||
more details.
|
||||
|
||||
_October 27, 2016_
|
||||
|
||||
Breaking change to bigquery: `NewGCSReference` is now a function,
|
||||
not a method on `Client`.
|
||||
|
||||
New bigquery feature: `Table.LoaderFrom` now accepts a `ReaderSource`, enabling
|
||||
loading data into a table from a file or any `io.Reader`.
|
||||
|
||||
_October 21, 2016_
|
||||
|
||||
Breaking change to pubsub: removed `pubsub.Done`.
|
||||
|
||||
Use `iterator.Done` instead, where `iterator` is the package
|
||||
`google.golang.org/api/iterator`.
|
||||
|
||||
_October 19, 2016_
|
||||
|
||||
Breaking changes to cloud.google.com/go/bigquery:
|
||||
|
||||
* Client.Table and Client.OpenTable have been removed.
|
||||
Replace
|
||||
```go
|
||||
client.OpenTable("project", "dataset", "table")
|
||||
```
|
||||
with
|
||||
```go
|
||||
client.DatasetInProject("project", "dataset").Table("table")
|
||||
```
|
||||
|
||||
* Client.CreateTable has been removed.
|
||||
Replace
|
||||
```go
|
||||
client.CreateTable(ctx, "project", "dataset", "table")
|
||||
```
|
||||
with
|
||||
```go
|
||||
client.DatasetInProject("project", "dataset").Table("table").Create(ctx)
|
||||
```
|
||||
|
||||
* Dataset.ListTables have been replaced with Dataset.Tables.
|
||||
Replace
|
||||
```go
|
||||
tables, err := ds.ListTables(ctx)
|
||||
```
|
||||
with
|
||||
```go
|
||||
it := ds.Tables(ctx)
|
||||
for {
|
||||
table, err := it.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: use table.
|
||||
}
|
||||
```
|
||||
|
||||
* Client.Read has been replaced with Job.Read, Table.Read and Query.Read.
|
||||
Replace
|
||||
```go
|
||||
it, err := client.Read(ctx, job)
|
||||
```
|
||||
with
|
||||
```go
|
||||
it, err := job.Read(ctx)
|
||||
```
|
||||
and similarly for reading from tables or queries.
|
||||
|
||||
* The iterator returned from the Read methods is now named RowIterator. Its
|
||||
behavior is closer to the other iterators in these libraries. It no longer
|
||||
supports the Schema method; see the next item.
|
||||
Replace
|
||||
```go
|
||||
for it.Next(ctx) {
|
||||
var vals ValueList
|
||||
if err := it.Get(&vals); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: use vals.
|
||||
}
|
||||
if err := it.Err(); err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
```
|
||||
with
|
||||
```
|
||||
for {
|
||||
var vals ValueList
|
||||
err := it.Next(&vals)
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: Handle error.
|
||||
}
|
||||
// TODO: use vals.
|
||||
}
|
||||
```
|
||||
Instead of the `RecordsPerRequest(n)` option, write
|
||||
```go
|
||||
it.PageInfo().MaxSize = n
|
||||
```
|
||||
Instead of the `StartIndex(i)` option, write
|
||||
```go
|
||||
it.StartIndex = i
|
||||
```
|
||||
|
||||
* ValueLoader.Load now takes a Schema in addition to a slice of Values.
|
||||
Replace
|
||||
```go
|
||||
func (vl *myValueLoader) Load(v []bigquery.Value)
|
||||
```
|
||||
with
|
||||
```go
|
||||
func (vl *myValueLoader) Load(v []bigquery.Value, s bigquery.Schema)
|
||||
```
|
||||
|
||||
|
||||
* Table.Patch is replace by Table.Update.
|
||||
Replace
|
||||
```go
|
||||
p := table.Patch()
|
||||
p.Description("new description")
|
||||
metadata, err := p.Apply(ctx)
|
||||
```
|
||||
with
|
||||
```go
|
||||
metadata, err := table.Update(ctx, bigquery.TableMetadataToUpdate{
|
||||
Description: "new description",
|
||||
})
|
||||
```
|
||||
|
||||
* Client.Copy is replaced by separate methods for each of its four functions.
|
||||
All options have been replaced by struct fields.
|
||||
|
||||
* To load data from Google Cloud Storage into a table, use Table.LoaderFrom.
|
||||
|
||||
Replace
|
||||
```go
|
||||
client.Copy(ctx, table, gcsRef)
|
||||
```
|
||||
with
|
||||
```go
|
||||
table.LoaderFrom(gcsRef).Run(ctx)
|
||||
```
|
||||
Instead of passing options to Copy, set fields on the Loader:
|
||||
```go
|
||||
loader := table.LoaderFrom(gcsRef)
|
||||
loader.WriteDisposition = bigquery.WriteTruncate
|
||||
```
|
||||
|
||||
* To extract data from a table into Google Cloud Storage, use
|
||||
Table.ExtractorTo. Set fields on the returned Extractor instead of
|
||||
passing options.
|
||||
|
||||
Replace
|
||||
```go
|
||||
client.Copy(ctx, gcsRef, table)
|
||||
```
|
||||
with
|
||||
```go
|
||||
table.ExtractorTo(gcsRef).Run(ctx)
|
||||
```
|
||||
|
||||
* To copy data into a table from one or more other tables, use
|
||||
Table.CopierFrom. Set fields on the returned Copier instead of passing options.
|
||||
|
||||
Replace
|
||||
```go
|
||||
client.Copy(ctx, dstTable, srcTable)
|
||||
```
|
||||
with
|
||||
```go
|
||||
dst.Table.CopierFrom(srcTable).Run(ctx)
|
||||
```
|
||||
|
||||
* To start a query job, create a Query and call its Run method. Set fields
|
||||
on the query instead of passing options.
|
||||
|
||||
Replace
|
||||
```go
|
||||
client.Copy(ctx, table, query)
|
||||
```
|
||||
with
|
||||
```go
|
||||
query.Run(ctx)
|
||||
```
|
||||
|
||||
* Table.NewUploader has been renamed to Table.Uploader. Instead of options,
|
||||
configure an Uploader by setting its fields.
|
||||
Replace
|
||||
```go
|
||||
u := table.NewUploader(bigquery.UploadIgnoreUnknownValues())
|
||||
```
|
||||
with
|
||||
```go
|
||||
u := table.NewUploader(bigquery.UploadIgnoreUnknownValues())
|
||||
u.IgnoreUnknownValues = true
|
||||
```
|
||||
|
||||
_October 10, 2016_
|
||||
|
||||
Breaking changes to cloud.google.com/go/storage:
|
||||
|
||||
* AdminClient replaced by methods on Client.
|
||||
Replace
|
||||
```go
|
||||
adminClient.CreateBucket(ctx, bucketName, attrs)
|
||||
```
|
||||
with
|
||||
```go
|
||||
client.Bucket(bucketName).Create(ctx, projectID, attrs)
|
||||
```
|
||||
|
||||
* BucketHandle.List replaced by BucketHandle.Objects.
|
||||
Replace
|
||||
```go
|
||||
for query != nil {
|
||||
objs, err := bucket.List(d.ctx, query)
|
||||
if err != nil { ... }
|
||||
query = objs.Next
|
||||
for _, obj := range objs.Results {
|
||||
fmt.Println(obj)
|
||||
}
|
||||
}
|
||||
```
|
||||
with
|
||||
```go
|
||||
iter := bucket.Objects(d.ctx, query)
|
||||
for {
|
||||
obj, err := iter.Next()
|
||||
if err == iterator.Done {
|
||||
break
|
||||
}
|
||||
if err != nil { ... }
|
||||
fmt.Println(obj)
|
||||
}
|
||||
```
|
||||
(The `iterator` package is at `google.golang.org/api/iterator`.)
|
||||
|
||||
Replace `Query.Cursor` with `ObjectIterator.PageInfo().Token`.
|
||||
|
||||
Replace `Query.MaxResults` with `ObjectIterator.PageInfo().MaxSize`.
|
||||
|
||||
|
||||
* ObjectHandle.CopyTo replaced by ObjectHandle.CopierFrom.
|
||||
Replace
|
||||
```go
|
||||
attrs, err := src.CopyTo(ctx, dst, nil)
|
||||
```
|
||||
with
|
||||
```go
|
||||
attrs, err := dst.CopierFrom(src).Run(ctx)
|
||||
```
|
||||
|
||||
Replace
|
||||
```go
|
||||
attrs, err := src.CopyTo(ctx, dst, &storage.ObjectAttrs{ContextType: "text/html"})
|
||||
```
|
||||
with
|
||||
```go
|
||||
c := dst.CopierFrom(src)
|
||||
c.ContextType = "text/html"
|
||||
attrs, err := c.Run(ctx)
|
||||
```
|
||||
|
||||
* ObjectHandle.ComposeFrom replaced by ObjectHandle.ComposerFrom.
|
||||
Replace
|
||||
```go
|
||||
attrs, err := dst.ComposeFrom(ctx, []*storage.ObjectHandle{src1, src2}, nil)
|
||||
```
|
||||
with
|
||||
```go
|
||||
attrs, err := dst.ComposerFrom(src1, src2).Run(ctx)
|
||||
```
|
||||
|
||||
* ObjectHandle.Update's ObjectAttrs argument replaced by ObjectAttrsToUpdate.
|
||||
Replace
|
||||
```go
|
||||
attrs, err := obj.Update(ctx, &storage.ObjectAttrs{ContextType: "text/html"})
|
||||
```
|
||||
with
|
||||
```go
|
||||
attrs, err := obj.Update(ctx, storage.ObjectAttrsToUpdate{ContextType: "text/html"})
|
||||
```
|
||||
|
||||
* ObjectHandle.WithConditions replaced by ObjectHandle.If.
|
||||
Replace
|
||||
```go
|
||||
obj.WithConditions(storage.Generation(gen), storage.IfMetaGenerationMatch(mgen))
|
||||
```
|
||||
with
|
||||
```go
|
||||
obj.Generation(gen).If(storage.Conditions{MetagenerationMatch: mgen})
|
||||
```
|
||||
|
||||
Replace
|
||||
```go
|
||||
obj.WithConditions(storage.IfGenerationMatch(0))
|
||||
```
|
||||
with
|
||||
```go
|
||||
obj.If(storage.Conditions{DoesNotExist: true})
|
||||
```
|
||||
|
||||
* `storage.Done` replaced by `iterator.Done` (from package `google.golang.org/api/iterator`).
|
||||
|
||||
_October 6, 2016_
|
||||
|
||||
Package preview/logging deleted. Use logging instead.
|
||||
|
||||
_September 27, 2016_
|
||||
|
||||
Logging client replaced with preview version (see below).
|
||||
|
||||
_September 8, 2016_
|
||||
|
||||
* New clients for some of Google's Machine Learning APIs: Vision, Speech, and
|
||||
Natural Language.
|
||||
|
||||
* Preview version of a new [Stackdriver Logging][cloud-logging] client in
|
||||
[`cloud.google.com/go/preview/logging`](https://godoc.org/cloud.google.com/go/preview/logging).
|
||||
This client uses gRPC as its transport layer, and supports log reading, sinks
|
||||
and metrics. It will replace the current client at `cloud.google.com/go/logging` shortly.
|
||||
|
88
vendor/cloud.google.com/go/run-tests.sh
generated
vendored
88
vendor/cloud.google.com/go/run-tests.sh
generated
vendored
@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Selectively run tests for this repo, based on what has changed
|
||||
# in a commit. Runs short tests for the whole repo, and full tests
|
||||
# for changed directories.
|
||||
|
||||
set -e
|
||||
|
||||
prefix=cloud.google.com/go
|
||||
|
||||
dryrun=false
|
||||
if [[ $1 == "-n" ]]; then
|
||||
dryrun=true
|
||||
shift
|
||||
fi
|
||||
|
||||
if [[ $1 == "" ]]; then
|
||||
echo >&2 "usage: $0 [-n] COMMIT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Files or directories that cause all tests to run if modified.
|
||||
declare -A run_all
|
||||
run_all=([.travis.yml]=1 [run-tests.sh]=1)
|
||||
|
||||
function run {
|
||||
if $dryrun; then
|
||||
echo $*
|
||||
else
|
||||
(set -x; $*)
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Find all the packages that have changed in this commit.
|
||||
declare -A changed_packages
|
||||
|
||||
for f in $(git diff-tree --no-commit-id --name-only -r $1); do
|
||||
if [[ ${run_all[$f]} == 1 ]]; then
|
||||
# This change requires a full test. Do it and exit.
|
||||
run go test -race -v $prefix/...
|
||||
exit
|
||||
fi
|
||||
# Map, e.g., "spanner/client.go" to "$prefix/spanner".
|
||||
d=$(dirname $f)
|
||||
if [[ $d == "." ]]; then
|
||||
pkg=$prefix
|
||||
else
|
||||
pkg=$prefix/$d
|
||||
fi
|
||||
changed_packages[$pkg]=1
|
||||
done
|
||||
|
||||
echo "changed packages: ${!changed_packages[*]}"
|
||||
|
||||
|
||||
# Reports whether its argument, a package name, depends (recursively)
|
||||
# on a changed package.
|
||||
function depends_on_changed_package {
|
||||
# According to go list, a package does not depend on itself, so
|
||||
# we test that separately.
|
||||
if [[ ${changed_packages[$1]} == 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
for dep in $(go list -f '{{range .Deps}}{{.}} {{end}}' $1); do
|
||||
if [[ ${changed_packages[$dep]} == 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# Collect the packages into two separate lists. (It is faster go test a list of
|
||||
# packages than to individually go test each one.)
|
||||
|
||||
shorts=
|
||||
fulls=
|
||||
for pkg in $(go list $prefix/...); do # for each package in the repo
|
||||
if depends_on_changed_package $pkg; then # if it depends on a changed package
|
||||
fulls="$fulls $pkg" # run the full test
|
||||
else # otherwise
|
||||
shorts="$shorts $pkg" # run the short test
|
||||
fi
|
||||
done
|
||||
run go test -race -v -short $shorts
|
||||
if [[ $fulls != "" ]]; then
|
||||
run go test -race -v $fulls
|
||||
fi
|
5
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
5
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
@ -1,5 +0,0 @@
|
||||
TAGS
|
||||
tags
|
||||
.*.swp
|
||||
tomlcheck/tomlcheck
|
||||
toml.test
|
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
@ -1,15 +0,0 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- tip
|
||||
install:
|
||||
- go install ./...
|
||||
- go get github.com/BurntSushi/toml-test
|
||||
script:
|
||||
- export PATH="$PATH:$HOME/gopath/bin"
|
||||
- make test
|
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
@ -1,3 +0,0 @@
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
|
||||
|
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
@ -1,19 +0,0 @@
|
||||
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
|
||||
|
218
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
218
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
@ -1,218 +0,0 @@
|
||||
## 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/toml-lang/toml
|
||||
|
||||
Compatible with TOML version
|
||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
||||
|
||||
Documentation: https://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
|
||||
```
|
||||
|
||||
[](https://travis-ci.org/BurntSushi/toml) [](https://godoc.org/github.com/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}`.
|
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
generated
vendored
Normal file
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
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.
|
||||
|
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
generated
vendored
Normal file
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
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.
|
||||
|
14
vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
generated
vendored
Normal file
14
vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
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.
|
||||
|
1447
vendor/github.com/BurntSushi/toml/decode_test.go
generated
vendored
1447
vendor/github.com/BurntSushi/toml/decode_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
615
vendor/github.com/BurntSushi/toml/encode_test.go
generated
vendored
615
vendor/github.com/BurntSushi/toml/encode_test.go
generated
vendored
@ -1,615 +0,0 @@
|
||||
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 {
|
||||
t.Logf("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",
|
||||
},
|
||||
"embedded non-struct": {
|
||||
input: struct{ NonStruct }{5},
|
||||
wantOutput: "NonStruct = 5\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{
|
||||
{
|
||||
"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) 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 {
|
||||
Bool bool `toml:"bool,omitempty"`
|
||||
String string `toml:"string,omitempty"`
|
||||
Array [0]byte `toml:"array,omitempty"`
|
||||
Slice []int `toml:"slice,omitempty"`
|
||||
Map map[string]string `toml:"map,omitempty"`
|
||||
}
|
||||
|
||||
var v simple
|
||||
encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
|
||||
v = simple{
|
||||
Bool: true,
|
||||
String: " ",
|
||||
Slice: []int{2, 3, 4},
|
||||
Map: map[string]string{"foo": "bar"},
|
||||
}
|
||||
expected := `bool = true
|
||||
string = " "
|
||||
slice = [2, 3, 4]
|
||||
|
||||
[map]
|
||||
foo = "bar"
|
||||
`
|
||||
encodeExpected(t, "fields with omitempty are not omitted when non-empty",
|
||||
v, 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 TestEncodeOmitemptyWithEmptyName(t *testing.T) {
|
||||
type simple struct {
|
||||
S []int `toml:",omitempty"`
|
||||
}
|
||||
v := simple{[]int{1, 2, 3}}
|
||||
expected := "S = [1, 2, 3]\n"
|
||||
encodeExpected(t, "simple with omitempty, no name, non-empty field",
|
||||
v, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeAnonymousStruct(t *testing.T) {
|
||||
type Inner struct{ N int }
|
||||
type Outer0 struct{ Inner }
|
||||
type Outer1 struct {
|
||||
Inner `toml:"inner"`
|
||||
}
|
||||
|
||||
v0 := Outer0{Inner{3}}
|
||||
expected := "N = 3\n"
|
||||
encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
|
||||
|
||||
v1 := Outer1{Inner{3}}
|
||||
expected = "[inner]\n N = 3\n"
|
||||
encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeAnonymousStructPointerField(t *testing.T) {
|
||||
type Inner struct{ N int }
|
||||
type Outer0 struct{ *Inner }
|
||||
type Outer1 struct {
|
||||
*Inner `toml:"inner"`
|
||||
}
|
||||
|
||||
v0 := Outer0{}
|
||||
expected := ""
|
||||
encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
|
||||
|
||||
v0 = Outer0{&Inner{3}}
|
||||
expected = "N = 3\n"
|
||||
encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
|
||||
|
||||
v1 := Outer1{}
|
||||
expected = ""
|
||||
encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
|
||||
|
||||
v1 = Outer1{&Inner{3}}
|
||||
expected = "[inner]\n N = 3\n"
|
||||
encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
|
||||
}
|
||||
|
||||
func TestEncodeIgnoredFields(t *testing.T) {
|
||||
type simple struct {
|
||||
Number int `toml:"-"`
|
||||
}
|
||||
value := simple{}
|
||||
expected := ""
|
||||
encodeExpected(t, "ignored field", 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"
|
||||
}
|
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
@ -1 +0,0 @@
|
||||
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
24
vendor/github.com/Unknwon/com/.gitignore
generated
vendored
24
vendor/github.com/Unknwon/com/.gitignore
generated
vendored
@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
.idea
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.iml
|
20
vendor/github.com/Unknwon/com/README.md
generated
vendored
20
vendor/github.com/Unknwon/com/README.md
generated
vendored
@ -1,20 +0,0 @@
|
||||
Common Functions
|
||||
================
|
||||
|
||||
[](https://travis-ci.org/Unknwon/com) [](http://gowalker.org/github.com/Unknwon/com)
|
||||
|
||||
This is an open source project for commonly used functions for the Go programming language.
|
||||
|
||||
This package need >= **go 1.2**
|
||||
|
||||
Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention).
|
||||
|
||||
## Contribute
|
||||
|
||||
Your contribute is welcome, but you have to check following steps after you added some functions and commit them:
|
||||
|
||||
1. Make sure you wrote user-friendly comments for **all functions** .
|
||||
2. Make sure you wrote test cases with any possible condition for **all functions** in file `*_test.go`.
|
||||
3. Make sure you wrote benchmarks for **all functions** in file `*_test.go`.
|
||||
4. Make sure you wrote useful examples for **all functions** in file `example_test.go`.
|
||||
5. Make sure you ran `go test` and got **PASS** .
|
56
vendor/github.com/apache/thrift/.clang-format
generated
vendored
56
vendor/github.com/apache/thrift/.clang-format
generated
vendored
@ -1,56 +0,0 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
ConstructorInitializerIndentWidth: 2
|
||||
AlignEscapedNewlinesLeft: false
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
BreakBeforeBinaryOperators: true
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BinPackParameters: false
|
||||
ColumnLimit: 100
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
DerivePointerAlignment: false
|
||||
IndentCaseLabels: false
|
||||
IndentWrappedFunctionNames: false
|
||||
IndentFunctionDeclarationAfterType: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
NamespaceIndentation: None
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakBeforeFirstCallParameter: 190
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakString: 10000
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 1200
|
||||
PointerAlignment: Left
|
||||
SpacesBeforeTrailingComments: 1
|
||||
Cpp11BracedListStyle: true
|
||||
Standard: Auto
|
||||
IndentWidth: 2
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
BreakBeforeBraces: Attach
|
||||
SpacesInParentheses: false
|
||||
SpacesInAngles: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
ContinuationIndentWidth: 4
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
|
||||
SpaceBeforeParens: ControlStatements
|
||||
DisableFormat: false
|
||||
...
|
||||
|
1
vendor/github.com/apache/thrift/.dockerignore
generated
vendored
1
vendor/github.com/apache/thrift/.dockerignore
generated
vendored
@ -1 +0,0 @@
|
||||
.git/
|
1
vendor/github.com/apache/thrift/.gitattributes
generated
vendored
1
vendor/github.com/apache/thrift/.gitattributes
generated
vendored
@ -1 +0,0 @@
|
||||
* text=auto
|
326
vendor/github.com/apache/thrift/.gitignore
generated
vendored
326
vendor/github.com/apache/thrift/.gitignore
generated
vendored
@ -1,326 +0,0 @@
|
||||
# generic ignores
|
||||
*.la
|
||||
*.lo
|
||||
*.o
|
||||
*.deps
|
||||
*.dirstamp
|
||||
*.libs
|
||||
*.log
|
||||
*.trs
|
||||
*.suo
|
||||
*.pyc
|
||||
*.cache
|
||||
*.user
|
||||
*.ipch
|
||||
*.sdf
|
||||
*.jar
|
||||
*.exe
|
||||
*.dll
|
||||
*_ReSharper*
|
||||
*.opensdf
|
||||
*.swp
|
||||
*.hi
|
||||
*~
|
||||
|
||||
.*project
|
||||
junit*.properties
|
||||
.idea
|
||||
gen-*
|
||||
Makefile
|
||||
Makefile.in
|
||||
aclocal.m4
|
||||
acinclude.m4
|
||||
autom4te.cache
|
||||
cmake-*
|
||||
node_modules
|
||||
compile
|
||||
test-driver
|
||||
erl_crash.dump
|
||||
|
||||
.sonar
|
||||
.DS_Store
|
||||
.svn
|
||||
.vagrant
|
||||
|
||||
/contrib/.vagrant/
|
||||
/aclocal/libtool.m4
|
||||
/aclocal/lt*.m4
|
||||
/autoscan.log
|
||||
/autoscan-*.log
|
||||
/cmake_*
|
||||
/compiler/cpp/compiler.VC.db
|
||||
/compiler/cpp/compiler.VC.VC.opendb
|
||||
/compiler/cpp/test/plugin/t_cpp_generator.cc
|
||||
/compiler/cpp/src/thrift/plugin/plugin_constants.cpp
|
||||
/compiler/cpp/src/thrift/plugin/plugin_constants.h
|
||||
/compiler/cpp/src/thrift/plugin/plugin_types.cpp
|
||||
/compiler/cpp/src/thrift/plugin/plugin_types.h
|
||||
/compiler/cpp/test/*test
|
||||
/compiler/cpp/test/thrift-gen-*
|
||||
/compiler/cpp/src/thrift/thrift-bootstrap
|
||||
/compiler/cpp/src/thrift/plugin/gen.stamp
|
||||
/compiler/cpp/Debug
|
||||
/compiler/cpp/Release
|
||||
/compiler/cpp/src/thrift/libparse.a
|
||||
/compiler/cpp/src/thrift/thriftl.cc
|
||||
/compiler/cpp/src/thrift/thrifty.cc
|
||||
/compiler/cpp/src/thrift/thrifty.hh
|
||||
/compiler/cpp/src/thrift/windows/version.h
|
||||
/compiler/cpp/thrift
|
||||
/compiler/cpp/thriftl.cc
|
||||
/compiler/cpp/thrifty.cc
|
||||
/compiler/cpp/lex.yythriftl.cc
|
||||
/compiler/cpp/thrifty.h
|
||||
/compiler/cpp/thrifty.hh
|
||||
/compiler/cpp/src/thrift/version.h
|
||||
/config.*
|
||||
/configure
|
||||
/configure.lineno
|
||||
/configure.scan
|
||||
/contrib/fb303/config.cache
|
||||
/contrib/fb303/config.log
|
||||
/contrib/fb303/config.status
|
||||
/contrib/fb303/configure
|
||||
/contrib/fb303/cpp/libfb303.a
|
||||
/contrib/fb303/java/build/
|
||||
/contrib/fb303/py/build/
|
||||
/contrib/fb303/py/fb303/FacebookService-remote
|
||||
/contrib/fb303/py/fb303/FacebookService.py
|
||||
/contrib/fb303/py/fb303/__init__.py
|
||||
/contrib/fb303/py/fb303/constants.py
|
||||
/contrib/fb303/py/fb303/ttypes.py
|
||||
/depcomp
|
||||
/install-sh
|
||||
/lib/cpp/Debug/
|
||||
/lib/cpp/Debug-mt/
|
||||
/lib/cpp/Release/
|
||||
/lib/cpp/Release-mt/
|
||||
/lib/cpp/src/thrift/qt/moc_TQTcpServer.cpp
|
||||
/lib/cpp/src/thrift/qt/moc__TQTcpServer.cpp
|
||||
/lib/cpp/src/thrift/config.h
|
||||
/lib/cpp/src/thrift/stamp-h2
|
||||
/lib/cpp/test/Benchmark
|
||||
/lib/cpp/test/AllProtocolsTest
|
||||
/lib/cpp/test/DebugProtoTest
|
||||
/lib/cpp/test/DenseProtoTest
|
||||
/lib/cpp/test/EnumTest
|
||||
/lib/cpp/test/JSONProtoTest
|
||||
/lib/cpp/test/OptionalRequiredTest
|
||||
/lib/cpp/test/SecurityTest
|
||||
/lib/cpp/test/SpecializationTest
|
||||
/lib/cpp/test/ReflectionTest
|
||||
/lib/cpp/test/RecursiveTest
|
||||
/lib/cpp/test/TFDTransportTest
|
||||
/lib/cpp/test/TFileTransportTest
|
||||
/lib/cpp/test/TInterruptTest
|
||||
/lib/cpp/test/TNonblockingServerTest
|
||||
/lib/cpp/test/TPipedTransportTest
|
||||
/lib/cpp/test/TServerIntegrationTest
|
||||
/lib/cpp/test/TSocketInterruptTest
|
||||
/lib/cpp/test/TransportTest
|
||||
/lib/cpp/test/UnitTests
|
||||
/lib/cpp/test/ZlibTest
|
||||
/lib/cpp/test/OpenSSLManualInitTest
|
||||
/lib/cpp/test/concurrency_test
|
||||
/lib/cpp/test/link_test
|
||||
/lib/cpp/test/processor_test
|
||||
/lib/cpp/test/tests.xml
|
||||
/lib/cpp/concurrency_test
|
||||
/lib/cpp/*.pc
|
||||
/lib/cpp/x64/Debug/
|
||||
/lib/cpp/x64/Debug-mt/
|
||||
/lib/cpp/x64/Release
|
||||
/lib/cpp/x64/Release-mt
|
||||
/lib/c_glib/*.gcda
|
||||
/lib/c_glib/*.gcno
|
||||
/lib/c_glib/*.loT
|
||||
/lib/c_glib/src/thrift/config.h
|
||||
/lib/c_glib/src/thrift/stamp-h3
|
||||
/lib/c_glib/test/*.gcno
|
||||
/lib/c_glib/test/testwrapper.sh
|
||||
/lib/c_glib/test/testwrapper-test*
|
||||
/lib/c_glib/test/testapplicationexception
|
||||
/lib/c_glib/test/testbinaryprotocol
|
||||
/lib/c_glib/test/testcompactprotocol
|
||||
/lib/c_glib/test/testbufferedtransport
|
||||
/lib/c_glib/test/testcontainertest
|
||||
/lib/c_glib/test/testdebugproto
|
||||
/lib/c_glib/test/testfdtransport
|
||||
/lib/c_glib/test/testframedtransport
|
||||
/lib/c_glib/test/testmemorybuffer
|
||||
/lib/c_glib/test/testoptionalrequired
|
||||
/lib/c_glib/test/testsimpleserver
|
||||
/lib/c_glib/test/teststruct
|
||||
/lib/c_glib/test/testthrifttest
|
||||
/lib/c_glib/test/testthrifttestclient
|
||||
/lib/c_glib/test/testtransportsocket
|
||||
/lib/c_glib/test/testserialization
|
||||
/lib/c_glib/thriftc.pc
|
||||
/lib/c_glib/thrift_c_glib.pc
|
||||
/lib/csharp/**/bin/
|
||||
/lib/csharp/**/obj/
|
||||
/lib/csharp/src/packages
|
||||
/lib/d/test/*.pem
|
||||
/lib/d/libthriftd*.a
|
||||
/lib/d/test/async_test
|
||||
/lib/d/test/client_pool_test
|
||||
/lib/d/test/serialization_benchmark
|
||||
/lib/d/test/stress_test_server
|
||||
/lib/d/test/thrift_test_client
|
||||
/lib/d/test/thrift_test_server
|
||||
/lib/d/test/transport_test
|
||||
/lib/d/unittest/
|
||||
/lib/dart/coverage
|
||||
/lib/dart/**/.packages
|
||||
/lib/dart/**/packages
|
||||
/lib/dart/**/.pub/
|
||||
/lib/dart/**/pubspec.lock
|
||||
/lib/delphi/src/*.dcu
|
||||
/lib/delphi/test/*.identcache
|
||||
/lib/delphi/test/*.local
|
||||
/lib/delphi/test/*.dcu
|
||||
/lib/delphi/test/*.2007
|
||||
/lib/delphi/test/*.dproj
|
||||
/lib/delphi/test/*.dproj
|
||||
/lib/delphi/test/codegen/*.bat
|
||||
/lib/delphi/test/skip/*.local
|
||||
/lib/delphi/test/skip/*.identcache
|
||||
/lib/delphi/test/skip/*.identcache
|
||||
/lib/delphi/test/skip/*.dproj
|
||||
/lib/delphi/test/skip/*.dproj
|
||||
/lib/delphi/test/skip/*.2007
|
||||
/lib/delphi/test/serializer/*.identcache
|
||||
/lib/delphi/test/serializer/*.dproj
|
||||
/lib/delphi/test/serializer/*.local
|
||||
/lib/delphi/test/serializer/*.2007
|
||||
/lib/delphi/test/serializer/*.dcu
|
||||
/lib/delphi/test/multiplexed/*.dproj
|
||||
/lib/delphi/test/multiplexed/*.2007
|
||||
/lib/delphi/test/multiplexed/*.local
|
||||
/lib/delphi/test/multiplexed/*.identcache
|
||||
/lib/delphi/test/multiplexed/*.dcu
|
||||
/lib/delphi/test/typeregistry/*.2007
|
||||
/lib/delphi/test/typeregistry/*.dproj
|
||||
/lib/delphi/test/typeregistry/*.identcache
|
||||
/lib/delphi/test/typeregistry/*.local
|
||||
/lib/delphi/test/typeregistry/*.dcu
|
||||
/lib/erl/.generated
|
||||
/lib/erl/.eunit
|
||||
/lib/erl/ebin
|
||||
/lib/erl/deps/
|
||||
/lib/erl/src/thrift.app.src
|
||||
/lib/erl/test/*.hrl
|
||||
/lib/erl/test/*.beam
|
||||
/lib/haxe/test/bin
|
||||
/lib/hs/dist
|
||||
/lib/java/build
|
||||
/lib/js/test/build
|
||||
/lib/nodejs/coverage
|
||||
/lib/nodejs/node_modules/
|
||||
/lib/perl/MANIFEST
|
||||
/lib/perl/MYMETA.json
|
||||
/lib/perl/MYMETA.yml
|
||||
/lib/perl/Makefile-perl.mk
|
||||
/lib/perl/blib
|
||||
/lib/perl/pm_to_blib
|
||||
/lib/py/build
|
||||
/lib/py/thrift.egg-info/
|
||||
/lib/rb/Gemfile.lock
|
||||
/lib/rb/debug_proto_test
|
||||
/lib/rb/.config
|
||||
/lib/rb/ext/conftest.dSYM/
|
||||
/lib/rb/ext/mkmf.log
|
||||
/lib/rb/ext/thrift_native.bundle
|
||||
/lib/rb/ext/thrift_native.so
|
||||
/lib/rb/test/
|
||||
/lib/rb/thrift-*.gem
|
||||
/lib/php/src/ext/thrift_protocol/Makefile.*
|
||||
/lib/php/src/ext/thrift_protocol/build/
|
||||
/lib/php/src/ext/thrift_protocol/config.*
|
||||
/lib/php/src/ext/thrift_protocol/configure
|
||||
/lib/php/src/ext/thrift_protocol/configure.in
|
||||
/lib/php/src/ext/thrift_protocol/install-sh
|
||||
/lib/php/src/ext/thrift_protocol/libtool
|
||||
/lib/php/src/ext/thrift_protocol/ltmain.sh
|
||||
/lib/php/src/ext/thrift_protocol/missing
|
||||
/lib/php/src/ext/thrift_protocol/mkinstalldirs
|
||||
/lib/php/src/ext/thrift_protocol/modules/
|
||||
/lib/php/src/ext/thrift_protocol/php_thrift_protocol.lo
|
||||
/lib/php/src/ext/thrift_protocol/run-tests.php
|
||||
/lib/php/src/ext/thrift_protocol/thrift_protocol.la
|
||||
/lib/php/src/ext/thrift_protocol/tmp-php.ini
|
||||
/lib/php/src/packages/
|
||||
/lib/php/test/TEST-*.xml
|
||||
/lib/php/test/packages/
|
||||
/lib/py/dist/
|
||||
/lib/erl/logs/
|
||||
/lib/go/test/gopath/
|
||||
/lib/go/test/ThriftTest.thrift
|
||||
/libtool
|
||||
/ltmain.sh
|
||||
/missing
|
||||
/node_modules/
|
||||
/stamp-h1
|
||||
/test/features/results.json
|
||||
/test/results.json
|
||||
/test/c_glib/test_client
|
||||
/test/c_glib/test_server
|
||||
/test/cpp/StressTest
|
||||
/test/cpp/StressTestNonBlocking
|
||||
/test/cpp/TestClient
|
||||
/test/cpp/TestServer
|
||||
/test/dart/**/.packages
|
||||
/test/dart/**/packages
|
||||
/test/dart/**/.pub/
|
||||
/test/dart/**/pubspec.lock
|
||||
/test/log/
|
||||
/test/test.log
|
||||
/test/erl/.generated
|
||||
/test/erl/ebin
|
||||
/test/go/bin/
|
||||
/test/go/ThriftTest.thrift
|
||||
/test/go/gopath
|
||||
/test/go/pkg/
|
||||
/test/go/src/code.google.com/
|
||||
/test/go/src/github.com/golang/
|
||||
/test/go/src/gen/
|
||||
/test/go/src/thrift
|
||||
/test/haxe/bin
|
||||
/test/hs/TestClient
|
||||
/test/hs/TestServer
|
||||
/test/py.twisted/_trial_temp/
|
||||
/test/rb/Gemfile.lock
|
||||
/tutorial/cpp/TutorialClient
|
||||
/tutorial/cpp/TutorialServer
|
||||
/tutorial/c_glib/tutorial_client
|
||||
/tutorial/c_glib/tutorial_server
|
||||
/tutorial/csharp/CsharpServer/obj
|
||||
/tutorial/csharp/CsharpServer/bin
|
||||
/tutorial/csharp/CsharpClient/obj
|
||||
/tutorial/csharp/CsharpClient/bin
|
||||
/tutorial/d/async_client
|
||||
/tutorial/d/client
|
||||
/tutorial/d/server
|
||||
/tutorial/dart/**/.packages
|
||||
/tutorial/dart/**/packages
|
||||
/tutorial/dart/**/.pub/
|
||||
/tutorial/dart/**/pubspec.lock
|
||||
/tutorial/delphi/*.dsk
|
||||
/tutorial/delphi/*.local
|
||||
/tutorial/delphi/*.tvsconfig
|
||||
/tutorial/delphi/DelphiClient/dcu
|
||||
/tutorial/delphi/DelphiServer/dcu
|
||||
/tutorial/delphi/DelphiClient/*.local
|
||||
/tutorial/delphi/DelphiClient/*.identcache
|
||||
/tutorial/delphi/DelphiServer/*.identcache
|
||||
/tutorial/delphi/DelphiServer/*.local
|
||||
/tutorial/go/go-tutorial
|
||||
/tutorial/go/calculator-remote
|
||||
/tutorial/go/src/shared
|
||||
/tutorial/go/src/tutorial
|
||||
/tutorial/go/src/git.apache.org
|
||||
/tutorial/haxe/bin
|
||||
/tutorial/hs/dist/
|
||||
/tutorial/java/build/
|
||||
/tutorial/js/build/
|
||||
/ylwrap
|
2366
vendor/github.com/apache/thrift/CHANGES
generated
vendored
2366
vendor/github.com/apache/thrift/CHANGES
generated
vendored
File diff suppressed because it is too large
Load Diff
117
vendor/github.com/apache/thrift/CMakeLists.txt
generated
vendored
117
vendor/github.com/apache/thrift/CMakeLists.txt
generated
vendored
@ -1,117 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
|
||||
project("Apache Thrift")
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${CMAKE_CURRENT_SOURCE_DIR}/build/cmake")
|
||||
|
||||
# TODO: add `git rev-parse --short HEAD`
|
||||
# Read the version information from the Autoconf file
|
||||
file (STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/configure.ac" CONFIGURE_AC REGEX "AC_INIT\\(.*\\)" )
|
||||
|
||||
# The following variable is used in the version.h.in file
|
||||
string(REGEX REPLACE "AC_INIT\\(\\[.*\\], \\[([0-9]+\\.[0-9]+\\.[0-9]+(-dev)?)\\]\\)" "\\1" PACKAGE_VERSION ${CONFIGURE_AC})
|
||||
message(STATUS "Parsed Thrift package version: ${PACKAGE_VERSION}")
|
||||
|
||||
# These are internal to CMake
|
||||
string(REGEX REPLACE "([0-9]+\\.[0-9]+\\.[0-9]+)(-dev)?" "\\1" thrift_VERSION ${PACKAGE_VERSION})
|
||||
string(REGEX REPLACE "([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" thrift_VERSION_MAJOR ${thrift_VERSION})
|
||||
string(REGEX REPLACE "[0-9]+\\.([0-9])+\\.[0-9]+" "\\1" thrift_VERSION_MINOR ${thrift_VERSION})
|
||||
string(REGEX REPLACE "[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" thrift_VERSION_PATCH ${thrift_VERSION})
|
||||
message(STATUS "Parsed Thrift version: ${thrift_VERSION} (${thrift_VERSION_MAJOR}.${thrift_VERSION_MINOR}.${thrift_VERSION_PATCH})")
|
||||
|
||||
# Some default settings
|
||||
include(DefineCMakeDefaults)
|
||||
|
||||
# Build time options are defined here
|
||||
include(DefineOptions)
|
||||
include(DefineInstallationPaths)
|
||||
|
||||
# Based on the options set some platform specifics
|
||||
include(DefinePlatformSpecifc)
|
||||
|
||||
# Generate the config.h file
|
||||
include(ConfigureChecks)
|
||||
|
||||
# Package it
|
||||
include(CPackConfig)
|
||||
|
||||
|
||||
find_package(Threads)
|
||||
|
||||
include(CTest)
|
||||
if(BUILD_TESTING)
|
||||
message(STATUS "Building with unittests")
|
||||
|
||||
enable_testing()
|
||||
# Define "make check" as alias for "make test"
|
||||
add_custom_target(check COMMAND ctest)
|
||||
else ()
|
||||
message(STATUS "Building without tests")
|
||||
endif ()
|
||||
|
||||
if(BUILD_COMPILER)
|
||||
if(NOT EXISTS ${THRIFT_COMPILER})
|
||||
set(THRIFT_COMPILER $<TARGET_FILE:thrift-compiler>)
|
||||
endif()
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/compiler/cpp)
|
||||
elseif(EXISTS ${THRIFT_COMPILER})
|
||||
add_executable(thrift-compiler IMPORTED)
|
||||
set_property(TARGET thrift-compiler PROPERTY IMPORTED_LOCATION ${THRIFT_COMPILER})
|
||||
endif()
|
||||
|
||||
if(BUILD_CPP)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/cpp)
|
||||
if(BUILD_TUTORIALS)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/tutorial/cpp)
|
||||
endif()
|
||||
if(BUILD_TESTING)
|
||||
if(WITH_LIBEVENT AND WITH_ZLIB AND WITH_OPENSSL)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/cpp)
|
||||
else()
|
||||
message(WARNING "libevent and/or ZLIB and/or OpenSSL not found or disabled; will not build some tests")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_C_GLIB)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/c_glib)
|
||||
endif()
|
||||
|
||||
if(BUILD_JAVA)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/java)
|
||||
endif()
|
||||
|
||||
if(BUILD_PYTHON)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/py)
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/py)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_HASKELL)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/hs)
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/hs)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
PRINT_CONFIG_SUMMARY()
|
49
vendor/github.com/apache/thrift/CONTRIBUTING.md
generated
vendored
49
vendor/github.com/apache/thrift/CONTRIBUTING.md
generated
vendored
@ -1,49 +0,0 @@
|
||||
## How to contribute
|
||||
1. Help to review and verify existing patches
|
||||
1. Make sure your issue is not all ready in the [Jira issue tracker](http://issues.apache.org/jira/browse/THRIFT)
|
||||
1. If not, create a ticket describing the change you're proposing in the [Jira issue tracker](http://issues.apache.org/jira/browse/THRIFT)
|
||||
1. Contribute your patch using one of the two methods below
|
||||
|
||||
### Contributing via a patch
|
||||
|
||||
1. Check out the latest version of the source code
|
||||
|
||||
* git clone https://git-wip-us.apache.org/repos/asf/thrift.git thrift
|
||||
|
||||
1. Modify the source to include the improvement/bugfix
|
||||
|
||||
* Remember to provide *tests* for all submited changes
|
||||
* When bugfixing: add test that will isolate bug *before* applying change that fixes it
|
||||
* Verify that you follow [Thrift Coding Standards](/docs/coding_standards) (you can run 'make style', which ensures proper format for some languages)
|
||||
|
||||
1. Create a patch from project root directory (e.g. you@dev:~/thrift $ ):
|
||||
|
||||
* git diff > ../thrift-XXX-my-new-feature.patch
|
||||
|
||||
1. Attach the newly generated patch to the issue
|
||||
1. Wait for other contributors or committers to review your new addition
|
||||
1. Wait for a committer to commit your patch
|
||||
|
||||
### Contributing via GitHub pull requests
|
||||
|
||||
1. Create a fork for http://github.com/apache/thrift
|
||||
1. Create a branch for your changes(best practice is issue as branch name, e.g. THRIFT-9999)
|
||||
1. Modify the source to include the improvement/bugfix
|
||||
|
||||
* Remember to provide *tests* for all submited changes
|
||||
* When bugfixing: add test that will isolate bug *before* applying change that fixes it
|
||||
* Verify that you follow [Thrift Coding Standards](/docs/coding_standards) (you can run 'make style', which ensures proper format for some languages)
|
||||
* Verify that your change works on other platforms by adding a GitHub service hook to [Travis CI](http://docs.travis-ci.com/user/getting-started/#Step-one%3A-Sign-in) and [AppVeyor](http://www.appveyor.com/docs)
|
||||
|
||||
1. Commit and push changes to your branch (please use issue name and description as commit title, e.g. THRIFT-9999 make it perfect)
|
||||
1. Issue a pull request with the jira ticket number you are working on in it's name
|
||||
1. Wait for other contributors or committers to review your new addition
|
||||
1. Wait for a committer to commit your patch
|
||||
|
||||
### More info
|
||||
|
||||
Plenty of information on why and how to contribute is available on the Apache Software Foundation (ASF) web site. In particular, we recommend the following:
|
||||
|
||||
* [Contributors Tech Guide](http://www.apache.org/dev/contributors)
|
||||
* [Get involved!](http://www.apache.org/foundation/getinvolved.html)
|
||||
* [Legal aspects on Submission of Contributions (Patches)](http://www.apache.org/licenses/LICENSE-2.0.html#contributions)
|
61
vendor/github.com/apache/thrift/Dockerfile
generated
vendored
61
vendor/github.com/apache/thrift/Dockerfile
generated
vendored
@ -1,61 +0,0 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
# Goal: provide a thrift-compiler Docker image
|
||||
#
|
||||
# Usage:
|
||||
# docker run -v "${PWD}:/data" thrift/thrift-compiler -gen cpp -o /data/ /data/test/ThriftTest.thrift
|
||||
#
|
||||
# further details on docker for thrift is here build/docker/
|
||||
#
|
||||
# TODO: push to apache/thrift-compiler instead of thrift/thrift-compiler
|
||||
|
||||
FROM debian:jessie
|
||||
MAINTAINER Apache Thrift <dev@thrift.apache.org>
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
ADD . /thrift
|
||||
|
||||
RUN buildDeps=" \
|
||||
flex \
|
||||
bison \
|
||||
g++ \
|
||||
make \
|
||||
cmake \
|
||||
curl \
|
||||
"; \
|
||||
apt-get update && apt-get install -y --no-install-recommends $buildDeps \
|
||||
&& mkdir /tmp/cmake-build && cd /tmp/cmake-build \
|
||||
&& cmake \
|
||||
-DBUILD_COMPILER=ON \
|
||||
-DBUILD_LIBRARIES=OFF \
|
||||
-DBUILD_TESTING=OFF \
|
||||
-DBUILD_EXAMPLES=OFF \
|
||||
/thrift \
|
||||
&& cmake --build . --config Release \
|
||||
&& make install \
|
||||
&& curl -k -sSL "https://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz" -o /tmp/go.tar.gz \
|
||||
&& tar xzf /tmp/go.tar.gz -C /tmp \
|
||||
&& cp /tmp/go/bin/gofmt /usr/bin/gofmt \
|
||||
&& apt-get purge -y --auto-remove $buildDeps \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /tmp/* \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENTRYPOINT ["thrift"]
|
131
vendor/github.com/apache/thrift/Makefile.am
generated
vendored
131
vendor/github.com/apache/thrift/Makefile.am
generated
vendored
@ -1,131 +0,0 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
ACLOCAL_AMFLAGS = -I ./aclocal
|
||||
|
||||
if WITH_PLUGIN
|
||||
# To enable bootstrap, build order is lib/cpp -> compiler -> others
|
||||
SUBDIRS = lib/cpp compiler/cpp lib
|
||||
if WITH_TESTS
|
||||
SUBDIRS += lib/cpp/test
|
||||
endif
|
||||
else
|
||||
SUBDIRS = compiler/cpp lib
|
||||
endif
|
||||
|
||||
if WITH_TESTS
|
||||
SUBDIRS += test
|
||||
endif
|
||||
|
||||
if WITH_TUTORIAL
|
||||
SUBDIRS += tutorial
|
||||
endif
|
||||
|
||||
dist-hook:
|
||||
find $(distdir) -type f \( -iname ".DS_Store" -or -iname "._*" -or -iname ".gitignore" \) | xargs rm -rf
|
||||
find $(distdir) -type d \( -iname ".deps" -or -iname ".libs" \) | xargs rm -rf
|
||||
find $(distdir) -type d \( -iname ".svn" -or -iname ".git" \) | xargs rm -rf
|
||||
|
||||
print-version:
|
||||
@echo $(VERSION)
|
||||
|
||||
.PHONY: precross cross
|
||||
precross-%: all
|
||||
$(MAKE) -C $* precross
|
||||
precross: all precross-test precross-lib
|
||||
|
||||
empty :=
|
||||
space := $(empty) $(empty)
|
||||
comma := ,
|
||||
|
||||
CROSS_LANGS = @MAYBE_CPP@ @MAYBE_C_GLIB@ @MAYBE_D@ @MAYBE_JAVA@ @MAYBE_CSHARP@ @MAYBE_PYTHON@ @MAYBE_PY3@ @MAYBE_RUBY@ @MAYBE_HASKELL@ @MAYBE_PERL@ @MAYBE_PHP@ @MAYBE_GO@ @MAYBE_NODEJS@ @MAYBE_DART@ @MAYBE_ERLANG@ @MAYBE_LUA@
|
||||
CROSS_LANGS_COMMA_SEPARATED = $(subst $(space),$(comma),$(CROSS_LANGS))
|
||||
|
||||
if WITH_PY3
|
||||
CROSS_PY=$(PYTHON3)
|
||||
else
|
||||
CROSS_PY=$(PYTHON)
|
||||
endif
|
||||
|
||||
if WITH_PYTHON
|
||||
crossfeature: precross
|
||||
$(CROSS_PY) test/test.py --retry-count 3 --features .* --skip-known-failures --server $(CROSS_LANGS_COMMA_SEPARATED)
|
||||
else
|
||||
# feature test needs python build
|
||||
crossfeature:
|
||||
endif
|
||||
|
||||
cross-%: precross crossfeature
|
||||
$(CROSS_PY) test/test.py --retry-count 3 --skip-known-failures --server $(CROSS_LANGS_COMMA_SEPARATED) --client $(CROSS_LANGS_COMMA_SEPARATED) --regex "$*"
|
||||
|
||||
cross: cross-.*
|
||||
|
||||
TIMES = 1 2 3
|
||||
fail: precross
|
||||
$(CROSS_PY) test/test.py || true
|
||||
$(CROSS_PY) test/test.py --update-expected-failures=overwrite
|
||||
$(foreach var,$(TIMES),test/test.py -s || true;test/test.py --update-expected-failures=merge;)
|
||||
|
||||
codespell_skip_files = \
|
||||
*.jar \
|
||||
*.class \
|
||||
*.so \
|
||||
*.a \
|
||||
*.la \
|
||||
*.o \
|
||||
*.p12 \
|
||||
*OCamlMakefile \
|
||||
.keystore \
|
||||
.truststore \
|
||||
CHANGES \
|
||||
config.sub \
|
||||
configure \
|
||||
depcomp \
|
||||
libtool.m4 \
|
||||
output.* \
|
||||
rebar \
|
||||
thrift
|
||||
|
||||
skipped_files = $(subst $(space),$(comma),$(codespell_skip_files))
|
||||
|
||||
style-local:
|
||||
codespell --write-changes --skip=$(skipped_files) --disable-colors
|
||||
|
||||
EXTRA_DIST = \
|
||||
.clang-format \
|
||||
.editorconfig \
|
||||
.travis.yml \
|
||||
appveyor.yml \
|
||||
bower.json \
|
||||
build \
|
||||
CMakeLists.txt \
|
||||
composer.json \
|
||||
contrib \
|
||||
CONTRIBUTING.md \
|
||||
debian \
|
||||
doc \
|
||||
doap.rdf \
|
||||
package.json \
|
||||
sonar-project.properties \
|
||||
Dockerfile \
|
||||
LICENSE \
|
||||
CHANGES \
|
||||
NOTICE \
|
||||
README.md \
|
||||
Thrift.podspec
|
166
vendor/github.com/apache/thrift/README.md
generated
vendored
166
vendor/github.com/apache/thrift/README.md
generated
vendored
@ -1,166 +0,0 @@
|
||||
Apache Thrift
|
||||
=============
|
||||
|
||||
+[](https://travis-ci.org/apache/thrift)
|
||||
- +[](https://ci.appveyor.com/project/apache/thrift)
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Thrift is a lightweight, language-independent software stack with an
|
||||
associated code generation mechanism for RPC. Thrift provides clean
|
||||
abstractions for data transport, data serialization, and application
|
||||
level processing. The code generation system takes a simple definition
|
||||
language as its input and generates code across programming languages that
|
||||
uses the abstracted stack to build interoperable RPC clients and servers.
|
||||
|
||||
Thrift is specifically designed to support non-atomic version changes
|
||||
across client and server code.
|
||||
|
||||
For more details on Thrift's design and implementation, take a gander at
|
||||
the Thrift whitepaper included in this distribution or at the README.md files
|
||||
in your particular subdirectory of interest.
|
||||
|
||||
Hierarchy
|
||||
=========
|
||||
|
||||
thrift/
|
||||
|
||||
compiler/
|
||||
|
||||
Contains the Thrift compiler, implemented in C++.
|
||||
|
||||
lib/
|
||||
|
||||
Contains the Thrift software library implementation, subdivided by
|
||||
language of implementation.
|
||||
|
||||
cpp/
|
||||
go/
|
||||
java/
|
||||
php/
|
||||
py/
|
||||
rb/
|
||||
|
||||
test/
|
||||
|
||||
Contains sample Thrift files and test code across the target programming
|
||||
languages.
|
||||
|
||||
tutorial/
|
||||
|
||||
Contains a basic tutorial that will teach you how to develop software
|
||||
using Thrift.
|
||||
|
||||
Requirements
|
||||
============
|
||||
|
||||
See http://thrift.apache.org/docs/install for an up-to-date list of build requirements.
|
||||
|
||||
Resources
|
||||
=========
|
||||
|
||||
More information about Thrift can be obtained on the Thrift webpage at:
|
||||
|
||||
http://thrift.apache.org
|
||||
|
||||
Acknowledgments
|
||||
===============
|
||||
|
||||
Thrift was inspired by pillar, a lightweight RPC tool written by Adam D'Angelo,
|
||||
and also by Google's protocol buffers.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
If you are building from the first time out of the source repository, you will
|
||||
need to generate the configure scripts. (This is not necessary if you
|
||||
downloaded a tarball.) From the top directory, do:
|
||||
|
||||
./bootstrap.sh
|
||||
|
||||
Once the configure scripts are generated, thrift can be configured.
|
||||
From the top directory, do:
|
||||
|
||||
./configure
|
||||
|
||||
You may need to specify the location of the boost files explicitly.
|
||||
If you installed boost in /usr/local, you would run configure as follows:
|
||||
|
||||
./configure --with-boost=/usr/local
|
||||
|
||||
Note that by default the thrift C++ library is typically built with debugging
|
||||
symbols included. If you want to customize these options you should use the
|
||||
CXXFLAGS option in configure, as such:
|
||||
|
||||
./configure CXXFLAGS='-g -O2'
|
||||
./configure CFLAGS='-g -O2'
|
||||
./configure CPPFLAGS='-DDEBUG_MY_FEATURE'
|
||||
|
||||
To enable gcov required options -fprofile-arcs -ftest-coverage enable them:
|
||||
|
||||
./configure --enable-coverage
|
||||
|
||||
Run ./configure --help to see other configuration options
|
||||
|
||||
Please be aware that the Python library will ignore the --prefix option
|
||||
and just install wherever Python's distutils puts it (usually along
|
||||
the lines of /usr/lib/pythonX.Y/site-packages/). If you need to control
|
||||
where the Python modules are installed, set the PY_PREFIX variable.
|
||||
(DESTDIR is respected for Python and C++.)
|
||||
|
||||
Make thrift:
|
||||
|
||||
make
|
||||
|
||||
From the top directory, become superuser and do:
|
||||
|
||||
make install
|
||||
|
||||
Note that some language packages must be installed manually using build tools
|
||||
better suited to those languages (at the time of this writing, this applies
|
||||
to Java, Ruby, PHP).
|
||||
|
||||
Look for the README.md file in the lib/<language>/ folder for more details on the
|
||||
installation of each language library package.
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
There are a large number of client library tests that can all be run
|
||||
from the top-level directory.
|
||||
|
||||
make -k check
|
||||
|
||||
This will make all of the libraries (as necessary), and run through
|
||||
the unit tests defined in each of the client libraries. If a single
|
||||
language fails, the make check will continue on and provide a synopsis
|
||||
at the end.
|
||||
|
||||
To run the cross-language test suite, please run:
|
||||
|
||||
make cross
|
||||
|
||||
This will run a set of tests that use different language clients and
|
||||
servers.
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user