Compare commits

..

1678 Commits

Author SHA1 Message Date
Torkel Ödegaard
fcd3af30ec updated version to 2.6.0 2015-12-14 15:18:01 +01:00
Torkel Ödegaard
e334107c7d docs(): added whats new in 2.6 article 2015-12-14 15:15:56 +01:00
carl bergquist
f5eb54e595 feat(elasticsearch): only show pipeline agg for es version >= 2 2015-12-14 15:08:34 +01:00
Torkel Ödegaard
56c3dd3652 docs(influxdb): updated influxdb docs 2015-12-14 14:16:22 +01:00
Torkel Ödegaard
6ddd458402 docs(elasticsearch): updated elasticsearch with info about templating and pipeline metrics 2015-12-14 13:55:16 +01:00
Torkel Ödegaard
ee328667d0 docs(table): updated table docs 2015-12-14 13:40:06 +01:00
Torkel Ödegaard
3dee75603c Merge pull request #3502 from mattttt/table-panel-doc
New table panel doc
2015-12-14 10:22:49 +01:00
Torkel Ödegaard
c7e3ed096f fix(postgres): fixes db migration issue with_credentials column for postgres, fixes #3505 2015-12-14 10:19:53 +01:00
Matt Toback
62fe5cdbaa New table panel doc 2015-12-11 14:20:21 -05:00
Torkel Ödegaard
10f66fa78f feat(elasticsearch): adds support for inline script and missing options to all elasticsearch metrics, closes #3500 2015-12-11 18:20:53 +01:00
Torkel Ödegaard
0cbc493113 Merge pull request #3499 from discordianfish/clarify-template-auto-interval-tooltip
Clarify template option auto interval in tooltip
2015-12-11 16:50:30 +01:00
Johannes 'fish' Ziemke
c30c9fbca9 Clarify template option auto interval in tooltip 2015-12-11 16:47:05 +01:00
carl bergquist
f4fd3f48f0 feat(changelog): adds info about merge PR 2015-12-11 16:26:11 +01:00
Carl Bergquist
4ee9e48e7c Merge pull request #3405 from piotr1212/master
Add more info in route logging
2015-12-11 16:23:59 +01:00
Carl Bergquist
55fab0493d Merge pull request #3393 from pepl/master
Fixed spelling error
2015-12-11 16:12:19 +01:00
carl bergquist
22c4e605cf Merge remote-tracking branch 'upstream/master' 2015-12-11 15:25:34 +01:00
carl bergquist
67737b1556 feat(changelog): add info about merge PR to changelog 2015-12-11 15:24:22 +01:00
carl bergquist
cdfd3449ed Merge branch 'tmonk42-ldap_login_hints' 2015-12-11 15:18:29 +01:00
carl bergquist
fab1062c0b Merge branch 'ldap_login_hints' of https://github.com/tmonk42/grafana into tmonk42-ldap_login_hints
Conflicts:
	conf/defaults.ini
	pkg/setting/setting.go

closes #2571
closes #2494
2015-12-11 15:16:57 +01:00
Torkel Ödegaard
766b9d5270 Merge branch 'master' of github.com:grafana/grafana 2015-12-11 14:40:18 +01:00
Torkel Ödegaard
023fa2b2cc fix(influxdb): minor fix to new editor, there were 5min as a selectable option in derivative function 2015-12-11 14:39:59 +01:00
carl bergquist
8910351f5c fix(changelog): add info about merged PR's 2015-12-11 14:38:50 +01:00
Torkel Ödegaard
1b42e3015e fix(timepicker): mini change, removed unneeded space 2015-12-11 14:34:56 +01:00
Torkel Ödegaard
4e9e18d4c0 Merge pull request #3498 from bergquist/always_show_refresh_button
feat(timepicker): always show refresh button
2015-12-11 14:33:50 +01:00
carl bergquist
ba5b127684 feat(timepicker): always show refresh button
closes #1628
closes #1208
2015-12-11 14:26:38 +01:00
Carl Bergquist
225e7a6a32 Merge pull request #3161 from mischief/syslog
syslog support closes #3160
2015-12-11 14:03:21 +01:00
Torkel Ödegaard
7e9963ae05 fix(elasticsearch): minor markup fix 2015-12-11 12:51:39 +01:00
Torkel Ödegaard
b7c0f3ea95 Merge branch 'master' of github.com:grafana/grafana 2015-12-11 12:45:01 +01:00
Torkel Ödegaard
a8e2610d7d updated changelog 2015-12-11 12:44:47 +01:00
Torkel Ödegaard
b934ace1fc Merge branch 'moving_avg_es_support' 2015-12-11 12:43:02 +01:00
Torkel Ödegaard
82cbfc9905 feat(elasticsearch): added pipeline aggregations feature to changelog 2015-12-11 12:42:55 +01:00
Torkel Ödegaard
9f294e3565 feat(elasticsearch): completed initial implementation of moving average and derivative pipleline aggregations, closes #3451 2015-12-11 12:41:40 +01:00
Torkel Ödegaard
68775182f0 Merge pull request #3494 from mtanda/cloudwatch_cleanup
remove aws-sdk-js require setting
2015-12-11 12:28:26 +01:00
Mitsuhiro Tanda
dc3e172eb4 remove aws-sdk-js require setting 2015-12-11 20:27:24 +09:00
Torkel Ödegaard
5bd128ba85 Merge pull request #3493 from bergquist/table_panel_color_invert_order
Table Panel: adds invert color order functionality
2015-12-11 12:05:35 +01:00
carl bergquist
1a8579cd6d feat(table_panel): adds invert color order functionality 2015-12-11 12:01:04 +01:00
Torkel Ödegaard
5921bb0589 Merge pull request #3490 from mtanda/cloudwatch_metric_update
(cloudwatch) update suggest metrics and dimensions
2015-12-11 11:09:45 +01:00
Mitsuhiro Tanda
c8e894e8f8 sort metrics and dimensions 2015-12-11 17:56:35 +09:00
carl bergquist
141f22bedf feat(elasticsearch): display more info in options 2015-12-11 09:44:37 +01:00
Mitsuhiro Tanda
d9844350fe update supported metrics and dimensions 2015-12-11 17:42:40 +09:00
Mitsuhiro Tanda
2d9d11a89c reorder supported metrics and dimensions 2015-12-11 17:19:56 +09:00
carl bergquist
4fa92198a0 feat(elasticsearch): add support for model setting 2015-12-11 09:19:05 +01:00
carl bergquist
662430d5db feat(elasticsearch): adds last class for derivatives 2015-12-11 09:14:40 +01:00
carl bergquist
139b19f9ac feat(elasticsearch): make series naming generic for pipeline aggs 2015-12-10 17:42:31 +01:00
carl bergquist
6e50e2412e feat(elasticsearch): remove pipeline aggs as possible sources 2015-12-10 17:18:22 +01:00
carl bergquist
005e14a060 refactor(elasticsearch): mavg naming -> pipeline agg 2015-12-10 17:12:52 +01:00
carl bergquist
c8c9e0a7e7 feat(elasticsearch): improve pipeline aggs structure 2015-12-10 17:05:23 +01:00
carl bergquist
0f65cb2b79 feat(elasticsearch): update pipeline aggs if type change
make it possible to switch between different pipeline
aggregates without causing problms in the ui
2015-12-10 17:01:29 +01:00
Carl Bergquist
85094fc74d feat(elasticsearch): add pipeline settings 2015-12-10 13:34:49 +01:00
Torkel Ödegaard
5227dc679d fix(invite): removed resend invite button, button logic was not implemented, fixes #3484 2015-12-10 13:31:24 +01:00
Torkel Ödegaard
77c510c364 fix(graph legend): fixed issue with escaping html text in graph legend, and in function param, fixes #3482 2015-12-10 13:27:57 +01:00
Carl Bergquist
2d2ad8b237 show pipeline agg source for derivative 2015-12-10 13:06:46 +01:00
Torkel Ödegaard
6ea00a4f7c fix(timpicker): another name change to make quick ranges nameing more consistent, fixes #3465 2015-12-10 13:01:41 +01:00
Torkel Ödegaard
1bb0530c69 feat(elasticsearch): metric options alignement 2015-12-10 12:15:11 +01:00
Torkel Ödegaard
b36f644628 feat(elasticsearch): added pipleline aggregation derivative 2015-12-10 11:46:19 +01:00
Torkel Ödegaard
2dee9c8d74 Merge branch 'moving_avg_es_support' of https://github.com/bergquist/grafana into bergquist-moving_avg_es_support 2015-12-10 11:19:39 +01:00
Carl Bergquist
9c6eb7736f move pipeline options outside main switch 2015-12-10 11:17:14 +01:00
Carl Bergquist
e86dfcf55c rename mavgoptions to more generic pipelineaggs 2015-12-10 10:43:00 +01:00
Torkel Ödegaard
d6311aefb7 Merge branch 'moving_avg_es_support' of https://github.com/bergquist/grafana into bergquist-moving_avg_es_support 2015-12-09 16:46:29 +01:00
carl bergquist
8e18f2c5d2 refactor es pipeline aggregation variables to match ES 2015-12-09 16:25:05 +01:00
carl bergquist
0644bfe27c improves timeseries naming for moving average series 2015-12-09 14:58:26 +01:00
carl bergquist
0b285845d1 adds spec for query builder 2015-12-09 14:21:48 +01:00
carl bergquist
d3ff4bf75e changes to using an array for mavg options 2015-12-09 13:55:06 +01:00
Torkel Ödegaard
aa4ac9fa05 fix(timerange): fixed broken unit tests 2015-12-09 13:38:53 +01:00
Torkel Ödegaard
0339a5ad01 fix(graph): removed experimental mockup code that was accidently merged master 2015-12-09 13:36:15 +01:00
Torkel Ödegaard
bffb217d37 fix(panel): removed accidentally commited test markup 2015-12-09 13:08:47 +01:00
Torkel Ödegaard
40f85e5ca3 refactor(http data source options): #3473 2015-12-09 13:05:05 +01:00
Torkel Ödegaard
2dba2f4a95 Merge branch 'with_credentials' of https://github.com/mtanda/grafana into mtanda-with_credentials 2015-12-09 12:54:00 +01:00
Torkel Ödegaard
70542fb730 refactor(table views): #3466 2015-12-09 12:04:51 +01:00
Mitsuhiro Tanda
cd742979b1 add withCredentials checkbox 2015-12-09 19:43:59 +09:00
Torkel Ödegaard
736d58e35f Merge branch 'updated-list-views' of https://github.com/nchristus/grafana into nchristus-updated-list-views 2015-12-09 10:43:51 +01:00
Torkel Ödegaard
64138bd424 fix(timepicker): changed name of Day so far -> Today so far, Week to date > Week so far, closes #3465 2015-12-09 10:35:39 +01:00
carl bergquist
f51d74fa68 change the way options are added 2015-12-09 09:47:56 +01:00
carl bergquist
78c6ce842e revert elastic response parser 2015-12-09 09:10:50 +01:00
carl bergquist
ad79df9b74 changes implementation direction
moving average will now be based on another metric
instead of having moving average on itself
2015-12-09 09:04:48 +01:00
carl bergquist
b4763290de move target extraction logic to query def 2015-12-09 09:00:06 +01:00
Mitsuhiro Tanda
8264c76642 add separate datasource parameter withCredentials 2015-12-09 14:44:28 +09:00
Mitsuhiro Tanda
6edd6c8f03 add with_credentials to datasource model 2015-12-09 14:44:28 +09:00
Torkel Ödegaard
f1bc87133d Merge branch 'cloudwatch_aws-sdk-go-v1.0.0' of https://github.com/mtanda/grafana into mtanda-cloudwatch_aws-sdk-go-v1.0.0 2015-12-08 18:09:40 +01:00
Carl Bergquist
dd7a13930f adds null check for response parser 2015-12-08 18:04:30 +01:00
Carl Bergquist
8ad10149ab adds basic support for moving avg in es queries 2015-12-08 18:04:30 +01:00
Carl Bergquist
0731064375 adds support for moving avg support in es queries 2015-12-08 18:04:30 +01:00
Torkel Ödegaard
e194461420 feat(grunt watch): optimized grunt watch, now only operates on changed files 2015-12-08 17:56:02 +01:00
Torkel Ödegaard
45c69fa638 fix(influxdb): fixed issue where Group By label was only showed on first query, fixes #3453 2015-12-08 16:40:41 +01:00
Torkel Ödegaard
cdc36dd171 Updated version 2015-12-08 15:41:34 +01:00
Torkel Ödegaard
a1d66d98dc Merge pull request #3448 from utkarshcmu/tips
Fixed closing tags
2015-12-08 12:10:05 +01:00
Torkel Ödegaard
dd3295da60 Merge pull request #3449 from EricSmekens/patch-1
Cleaned up jquery.flot.events.js
2015-12-08 12:09:47 +01:00
Nick Christus
282aaa5cf0 Merge branch 'master' into updated-list-views 2015-12-07 16:30:02 -06:00
Nick Christus
462a939438 Merge branch 'master' of github.com:grafana/grafana 2015-12-07 16:29:51 -06:00
Nick Christus
030f902ca5 removed redundant labels 2015-12-07 16:26:18 -06:00
Eric Smekens
58093941c0 Cleaned up jquery.flot.events.js
_lastRange was confused with lastRange. This declared '_lastRange' somewhere globally.

Was also not used, so removed both.

(I know this is a library, but it was heavily adjusted anyway.)
2015-12-07 20:16:52 +01:00
utkarshcmu
005271d24b Fixed missing closing tag 2015-12-07 05:41:18 -08:00
utkarshcmu
2b263bcb5d Fixed closing tag 2015-12-07 05:06:52 -08:00
Torkel Ödegaard
1ebb18ae57 Merge pull request #3432 from toni-moreno/add_rpm_deb_separate_commands
add option pkg-rpm and pkg-deb to create separately both packages #3407
2015-12-07 11:46:00 +01:00
Torkel Ödegaard
fad4875f11 Merge pull request #3436 from utkarshcmu/templating
Removed if condition to fix 3370
2015-12-07 11:45:32 +01:00
Torkel Ödegaard
d68e1fd308 Merge pull request #3444 from mtanda/cloudwatch_fix_period
(cloudwatch) fix period overwrite
2015-12-07 06:43:33 +01:00
Mitsuhiro Tanda
217d5df276 (cloudwatch) fix period overwrite 2015-12-07 13:40:54 +09:00
Mitsuhiro Tanda
d1ccf83236 (cloudwatch) use paging 2015-12-06 21:35:20 +09:00
Mitsuhiro Tanda
28968144d6 update aws-sdk-go v1.0.0 2015-12-06 21:35:19 +09:00
Torkel Ödegaard
4c5cfd51d7 fix(metric_editors): Fixes clicking timing issue for typeahead auto dropdown option, fixes #3428 2015-12-06 10:13:47 +01:00
utkarshcmu
b6044910ec Removed if condition to fix 3370 2015-12-05 06:34:03 -08:00
Torkel Ödegaard
5aa90eb086 Merge branch 'master' of github.com:grafana/grafana 2015-12-05 14:22:54 +01:00
Torkel Ödegaard
ce58486850 fix(html): removed unneeded tag 2015-12-05 14:22:39 +01:00
toni-moreno
7e63978a3f fix formatting for CircleCI test 2015-12-04 22:55:23 +01:00
toni-moreno
c9674f84e8 add option pkg-rpm and pkg-deb to create separately both packages #3407 2015-12-04 22:36:38 +01:00
Torkel Ödegaard
654d1f939e Merge pull request #3424 from grafana/code-tag
add variable for codeTagBackground to control look for both themes.
2015-12-04 18:17:24 +01:00
Trent White
477b876a1f add variable for codeTagBackground to control look for both themes. Fixes #3399 2015-12-04 11:55:20 -05:00
Torkel Ödegaard
df0a5cf52f updated appveyor again 2015-12-04 16:56:37 +01:00
Torkel Ödegaard
caa6337952 updated appveyor 2015-12-04 16:48:35 +01:00
Torkel Ödegaard
700a3f9f84 Merge branch 'master' of github.com:grafana/grafana 2015-12-04 15:45:06 +01:00
Torkel Ödegaard
f9b13791de change(table panel): changed default transform mode 2015-12-04 15:38:49 +01:00
Torkel Ödegaard
7886318341 Merge pull request #3417 from mtanda/cloudwatch_dimension_filter_fix
(cloudwatch) null check of dimension
2015-12-04 12:30:13 +01:00
Mitsuhiro Tanda
573632012e cloudwatch dimension null check 2015-12-04 20:16:31 +09:00
Torkel Ödegaard
002455da07 fix(table): minor fix to table panel and transform time series to aggregations 2015-12-04 12:15:42 +01:00
Torkel Ödegaard
e9e6ac64bd minor change to build script 2015-12-04 11:21:38 +01:00
Torkel Ödegaard
36ab8ae19c fix(table): minor fix for table panel 2015-12-04 10:44:17 +01:00
Torkel Ödegaard
df366da721 updated version to 2.6.0-beta1 2015-12-04 10:39:50 +01:00
Torkel Ödegaard
67dc761344 fix(security): do not print ENV config values when they are passwords, fixes #3337 2015-12-04 10:38:27 +01:00
Torkel Ödegaard
0b4552a8e7 fix(timerange): fix handling of invalid dates in from/to url parameters, fixes #3345 2015-12-04 10:32:23 +01:00
Torkel Ödegaard
2345b41a74 feat(elasticsearch): added min_doc_count option for date histogram, closes #3416 2015-12-04 10:06:44 +01:00
Torkel Ödegaard
141e395489 polish(influxdb): minor improvements to influxdb editor raw query editor 2015-12-04 09:20:29 +01:00
Nick Christus
91139672cc merge with master, conflicts fixed 2015-12-03 20:34:05 -06:00
Nick Christus
3d178c8eb6 Merge branch 'master' of github.com:grafana/grafana 2015-12-03 20:21:15 -06:00
Nick Christus
c9bd45ba13 removed filters and actions for now 2015-12-03 20:20:54 -06:00
Torkel Ödegaard
db9c288050 fix(elasticsearch): refactoring of #3321 2015-12-03 18:30:36 +01:00
Torkel Ödegaard
f1cb8f2f25 Merge branch 'keep_es_version' of https://github.com/replay/grafana into replay-keep_es_version 2015-12-03 18:08:26 +01:00
Torkel Ödegaard
7ec2c09249 Merge branch 'master' of github.com:grafana/grafana 2015-12-03 18:05:19 +01:00
Torkel Ödegaard
354bfcea15 Merge branch 'master' of https://github.com/skbkontur/grafana 2015-12-03 18:03:55 +01:00
Torkel Ödegaard
891d7c134c Merge pull request #3205 from mtanda/cloudwatch_suggest_fix
(cloudwatch) fix dimension value suggestion
2015-12-03 17:17:07 +01:00
Mitsuhiro Tanda
eb5822bc7c update cloudwatch docs 2015-12-04 01:04:50 +09:00
Mitsuhiro Tanda
154d70e4e2 remove dimensionPart 2015-12-04 01:02:25 +09:00
Torkel Ödegaard
0011beb654 Merge branch 'master' of github.com:grafana/grafana 2015-12-03 16:32:58 +01:00
Torkel Ödegaard
419251ed35 fix(elasticsearch): fixed issue with default state of elasticsearch query, result in error before query controller could set defaults, moved defaults to query builder, also removed raw query mode as it is pretty broken, fixes #3396 2015-12-03 16:32:35 +01:00
Torkel Ödegaard
bd850d8158 Merge pull request #3410 from bergquist/ignore_npm_log
add npm-debug.log to gitignore
2015-12-03 15:56:38 +01:00
carl bergquist
d7c7f27207 add npm-debug.log to gitignore 2015-12-03 15:29:28 +01:00
Torkel Ödegaard
0fb95297a2 Merge branch 'master' of github.com:grafana/grafana 2015-12-03 15:10:23 +01:00
Torkel Ödegaard
cf1f43dc9d feat(influxdb): support for table queries, closes #3409, #3219 2015-12-03 15:09:39 +01:00
Alexey Larkov
d6935847b4 Web. Fix double slash 2015-12-03 17:36:29 +05:00
Alexey Larkov
0715ba3e5e Merge pull request #1 from grafana/master
Merge with original source
2015-12-03 17:29:00 +05:00
Torkel Ödegaard
efbbb31370 Update README.md 2015-12-03 12:03:06 +01:00
Piotr Popieluch
207c1a20ee router logger, log username taken from cookie 2015-12-03 11:05:50 +01:00
Piotr Popieluch
579bc1c2c8 Add more info in route logging
- Add remote address
 - Add method
 - Add protocol
 - Add response size
 - Use consistent unit for response time (us)
2015-12-03 09:28:42 +01:00
Nick Christus
d86b2c4fb4 Merge branch 'master' of github.com:grafana/grafana 2015-12-02 21:33:06 -06:00
Torkel Ödegaard
8eb3e48bc7 fix(build): fixed build issues with concat not including require_config 2015-12-02 18:22:47 +01:00
Michael Kröll
1bb1ddcd29 Fixed spelling error 2015-12-02 11:14:28 +01:00
Torkel Ödegaard
24324939e1 Merge branch 'fix_refresh_variable_from_url' of https://github.com/toni-moreno/grafana into toni-moreno-fix_refresh_variable_from_url 2015-12-01 15:28:48 +01:00
Torkel Ödegaard
2436cda7ac fix(graph panel): minor spelling change 2015-12-01 15:28:34 +01:00
Torkel Ödegaard
75b83af08f refactoring(ui): minor ui improvement to graph axis tab 2015-12-01 14:04:44 +01:00
Torkel Ödegaard
82d8e3c2b6 feat(graph panel): refactoring of hide zero option, #3336 2015-12-01 13:34:42 +01:00
Torkel Ödegaard
c351f46e47 Merge branch 'hide-zero' of https://github.com/utkarshcmu/grafana into utkarshcmu-hide-zero 2015-12-01 13:08:37 +01:00
Torkel Ödegaard
fb9e8d2166 feat(table panel): fixed issue with column selection for new table panel 2015-12-01 10:16:11 +01:00
Torkel Ödegaard
3668cb6dd4 fix(readme): minor readme update 2015-12-01 09:23:30 +01:00
Torkel Ödegaard
93f3e30cac fix(log): removed logging accidentlally checked in 2015-12-01 09:19:22 +01:00
Torkel Ödegaard
52af346ec8 Merge pull request #3375 from hartfordfive/issue-3374
Add flag option to display app version (Issue 3374)
2015-12-01 09:18:37 +01:00
Torkel Ödegaard
8e0bba4f99 fix(templating): minor fix to default property name, fixes #3378 2015-12-01 09:01:44 +01:00
Torkel Ödegaard
4cc61d10ff fix(docs): fixed typo in installing docs fixes #3379 2015-12-01 08:59:30 +01:00
Al Lefebvre
8deb6d9246 Updated README 2015-11-30 13:09:29 -05:00
Al Lefebvre
20eb6df9e8 Removed comment line 2015-11-30 12:24:14 -05:00
Al Lefebvre
a921eeb5e2 Merge branch 'master' of github.com:hartfordfive/grafana into issue-3374 2015-11-30 12:20:47 -05:00
Al Lefebvre
f3f34e1835 Merge pull request #1 from grafana/master
Updating forked copy from official Grafana master
2015-11-30 12:20:32 -05:00
Al Lefebvre
4a29d459da Added flag to display version number and exit 2015-11-30 12:10:47 -05:00
Torkel Ödegaard
0f867a3484 Merge pull request #3230 from utkarshcmu/time-units
Added currency units, time units, tests
2015-11-30 17:29:51 +01:00
Torkel Ödegaard
6eeddaa564 Merge pull request #3369 from matschaffer/cw/alias-fix
Use of `<dimension name>` seems to confuse angular so changing to DIMENSION_NAME
2015-11-30 17:28:41 +01:00
Torkel Ödegaard
d80458ef81 Merge branch 'refresh' of https://github.com/utkarshcmu/grafana into utkarshcmu-refresh 2015-11-30 17:23:50 +01:00
Torkel Ödegaard
ad15df7222 Merge branch 'influxdb_editor_v3' 2015-11-30 16:29:20 +01:00
Torkel Ödegaard
98f7febed1 feat(influxdb): added all functions 2015-11-30 16:28:56 +01:00
Torkel Ödegaard
4f04eaec3a feat(influxdb): moved query builder tests 2015-11-30 15:27:38 +01:00
Torkel Ödegaard
5a2b9b1f44 feat(influxdb): worked on schema upgrade for new influx query editor 2015-11-30 15:09:18 +01:00
Torkel Ödegaard
721b37a08e feat(influxdb): new editor now supports field and tag lookup again 2015-11-30 10:14:42 +01:00
Torkel Ödegaard
b3d494d4c8 feat(influxdb): minor fixes to new editor 2015-11-30 08:40:11 +01:00
Mat Schaffer
85ec70e92b Use of <dimension name> seems to confuse angular so changing to DIMENSION_NAME 2015-11-30 15:55:07 +09:00
Torkel Ödegaard
e2f0ff9e88 Merge pull request #3367 from utkarshcmu/orgs
Fixed #3357
2015-11-30 07:40:07 +01:00
utkarshcmu
0dbd7d0e17 Fixed #3357 2015-11-29 03:28:07 -08:00
utkarshcmu
8cdaa044e1 Removed repeating test 2015-11-29 03:14:26 -08:00
utkarshcmu
712a420217 Fixed refresh setting for absolute time 2015-11-29 03:12:48 -08:00
Torkel Ödegaard
72d9fcdcb4 feat(influxdb): progress with new influxdb editor 2015-11-27 16:35:40 +01:00
Torkel Ödegaard
aa13a80d83 fix(influxdb): fixed issue with metric segment component that caused double events 2015-11-26 09:28:59 +01:00
Torkel Ödegaard
9e2ef543ed Merge pull request #3343 from mtanda/cloudwatch_fix_credential_check
(cloudwatch) fix EC2RoleProvider parameter
2015-11-26 08:20:50 +01:00
Mitsuhiro Tanda
2aabb387b1 fix EC2RoleProvider parameter 2015-11-26 16:01:33 +09:00
Mitsuhiro Tanda
f972863f49 add credential setting to handleDescribeInstances 2015-11-26 13:21:38 +09:00
Torkel Ödegaard
f00320c8b9 feat(influxdb): query editor is starting to work, can now add group by parts 2015-11-25 14:27:22 +01:00
utkarshcmu
1f57cf08a7 Added an option to hide zero values 2015-11-25 04:38:54 -08:00
utkarshcmu
0ff5ff5dbe Enabled refresh interval for absolute time range 2015-11-25 04:05:40 -08:00
Torkel Ödegaard
5ba19144d5 feat(influxdb): more work on query editor 2015-11-25 12:30:56 +01:00
Torkel Ödegaard
c9ba856c52 feat(influxdb): more work on influxdb editor 2015-11-25 10:22:20 +01:00
Mauro Stettler
a30ceefa6b add tests for elastic search versioning in query builder and make es version 2 default 2015-11-25 16:23:28 +09:00
Torkel Ödegaard
31e2a8b8e9 feat(influxdb): more work onnew influxdb editor 2015-11-24 17:01:18 +01:00
Torkel Ödegaard
9b4150509c feat(influxdb): minor progress on new editor 2015-11-24 11:02:49 +01:00
Torkel Ödegaard
c68cd7d19a Merge branch 'master' into influxdb_editor_v3 2015-11-24 10:06:06 +01:00
Torkel Ödegaard
85382dc2e0 fix(table): fixed table height alignment 2015-11-24 10:04:01 +01:00
Torkel Ödegaard
7315a0ecec Merge pull request #3319 from VibyJocke/master
Fixed some broken HTML.
2015-11-24 09:45:13 +01:00
Torkel Ödegaard
411bd55cd4 Merge pull request #3318 from utkarshcmu/validated-responses
Validated HTTP responses
2015-11-24 09:31:23 +01:00
Joakim Lahtinen
9c0141e84e Fixed some broken HTML.
Simplified some control flow.
2015-11-24 07:29:52 +01:00
utkarshcmu
ca5d0496ee Validated HTTP responses 2015-11-23 19:19:26 -08:00
Torkel Ödegaard
24b9bc1e55 fix(missing files): added missing files, oops 2015-11-23 18:20:12 +01:00
Torkel Ödegaard
cf1e167430 feat(table panel): table panel can now show nested object data, closes #3263 2015-11-23 16:10:32 +01:00
Torkel Ödegaard
a1afd2328d fix(elasticsearch): made interval template variable appear in group by time interval dropdown, fixes #3241 2015-11-23 14:19:10 +01:00
Mauro Stettler
ada9bfcae8 keep track of elastic search version and generate query according to version 2015-11-22 21:39:56 +09:00
Torkel Ödegaard
1fa8b74595 Merge pull request #3303 from utkarshcmu/db-update
Return correct updated+created timestamps to frontend
2015-11-21 14:10:50 +01:00
utkarshcmu
5559ad1238 Return correct updated+created timestamps to frontend 2015-11-21 05:03:00 -08:00
Torkel Ödegaard
396f53d20e fix(tests): removed it.only accidentally cecked in 2015-11-21 13:48:12 +01:00
Torkel Ödegaard
fc78e42a78 Merge pull request #3297 from utkarshcmu/db-update
Fixed created & updated columns update in dashboard table
2015-11-21 10:25:29 +01:00
Torkel Ödegaard
9d1906d333 fix(elasticsearch): fixed issue with disabling (hiding) query, fixes #3300 2015-11-21 10:22:59 +01:00
Torkel Ödegaard
4a3f50cef7 fix(docs): minor fix to http docs, fixes #3301 2015-11-21 10:07:33 +01:00
Torkel Ödegaard
4751e4b94e feat(elasticsearch): a lot of work to support aggregation queries without date_histogram, queries that return metric aggregations now work with the table panel (json data type), #3219 2015-11-20 16:26:44 +01:00
utkarshcmu
1b7f4f31ca Tired of gofmt 2015-11-20 04:52:50 -08:00
utkarshcmu
ca01604b43 Added statements in constructor 2015-11-20 04:37:24 -08:00
utkarshcmu
93f9a0c39c Fixed created & updated columns in dashboard table 2015-11-20 03:10:24 -08:00
Torkel Ödegaard
93977316ce revert #3288 for now since it breaks Elasticsearch 1.7 2015-11-20 10:48:10 +01:00
Torkel Ödegaard
7e49bdf5a8 revert #3288 for now since it breaks Elasticsearch 1.7 2015-11-20 10:45:48 +01:00
Torkel Ödegaard
c0beef7572 Merge pull request #3252 from alechenninger/avg-ignore-null
Ignore nulls unless 'null as zero' for series.stats.avg
2015-11-20 08:27:45 +01:00
Torkel Ödegaard
5ec448861d Merge pull request #3284 from mtanda/prometheus_null_point
(prometheus) support null point mode
2015-11-20 08:27:36 +01:00
Torkel Ödegaard
7d4326a397 Merge pull request #3292 from mtanda/cloudwatch_null_pointmode
(cloudwatch) fix null point mode
2015-11-20 08:24:51 +01:00
Torkel Ödegaard
cc43b94864 Merge pull request #3293 from jmcfarlane/prometheus-fix-step-calibration
[prometheus] Fix step calibration
2015-11-20 08:24:35 +01:00
Torkel Ödegaard
57a31828f8 fix(templating): very minor markup/css change 2015-11-20 08:18:03 +01:00
Torkel Ödegaard
0c7fccdcbe Merge pull request #2812 from utkarshcmu/master
Duplicate button for template variables
2015-11-20 08:14:20 +01:00
Mitsuhiro Tanda
a1fcd3c5b6 import fix step calibration 2015-11-20 15:37:31 +09:00
John McFarlane
c4048f8f22 [prometheus] Fix step calibration
The step interval is (correctly) being converted to seconds, but the
unit of measure suffixed onto the end is preventing the subsequent
step calibration. Because the query upstream defaults to seconds as
the unit of measure, the suffix can simply be removed and everything
works as intended.

patchset 01: Also fix the spec.
2015-11-19 21:20:07 -08:00
Mitsuhiro Tanda
15dc30edf6 if there isn't enough datapoint, add null data point 2015-11-20 13:24:49 +09:00
Mitsuhiro Tanda
4254aa5f5a (cloudwatch) fix null point mode 2015-11-20 13:06:35 +09:00
Nick Christus
09ff042986 updated-list-views: added new table layout to additional pages 2015-11-19 21:02:38 -06:00
Torkel Ödegaard
924ecce2c3 Merge pull request #3288 from replay/fix_time_format
specify date format in elastic search query
2015-11-19 18:53:59 +01:00
Mauro Stettler
a6e8d61e8e specify date format in elastic search query 2015-11-20 02:11:48 +09:00
Torkel Ödegaard
be5e6d55fc Merge pull request #3285 from ckrybus/patch-1
Fix recommended graphite version for better performance
2015-11-19 14:01:07 +01:00
Christoph Krybus
b0c01369c5 Fix recommended graphite version for better performance
There is no 0.9.13 graphite release, only 0.9.14.
2015-11-19 13:44:46 +01:00
Torkel Ödegaard
730d4857ba feat(elasticsearch): added caret arrow for metric / group by collapse/expand options 2015-11-19 11:04:21 +01:00
Torkel Ödegaard
0e1c12e65f Merge pull request #3279 from mtanda/override_null
override null point mode
2015-11-19 08:42:01 +01:00
Mitsuhiro Tanda
37b125ca98 override null point mode 2015-11-19 15:20:34 +09:00
Torkel Ödegaard
2e9303cb6c dependency(aws): updated aws go lib dependency 2015-11-18 17:26:35 +01:00
Torkel Ödegaard
7db38c80fc Merge branch 'aws-sdk-go' of https://github.com/mischief/grafana into mischief-aws-sdk-go 2015-11-18 17:16:42 +01:00
Torkel Ödegaard
aad824a562 feat(tablepanel): completed work on time series aggregations table transform, #3219 2015-11-18 17:05:21 +01:00
Torkel Ödegaard
f0087c93b8 fix(admin settings view): always censor provider_config for system info view, fixes #3268 2015-11-18 15:55:45 +01:00
Mitsuhiro Tanda
ae7e7e9656 remove getDimensions() 2015-11-17 22:49:46 +09:00
Torkel Ödegaard
e008473e47 Merge pull request #3249 from mtanda/cloudwatch_null_pointmode
(cloudwatch) support null point mode
2015-11-17 14:03:48 +01:00
Torkel Ödegaard
79313aa268 Merge pull request #3253 from mtanda/cloudwatch_ecs
(cloudwatch) support ECS suggestion
2015-11-17 14:00:31 +01:00
toni-moreno
1c35d4b26b removed autoupdate on variabe refresh, don't needed if working interactive and fix #2722 2015-11-16 21:55:23 +01:00
Torkel Ödegaard
a56f657fb1 Merge pull request #3254 from utkarshcmu/user-id
/api/admin/users returns user ID in JSON response
2015-11-16 17:47:11 +01:00
utkarshcmu
f5db9950f8 fixed gofmt tests 2015-11-16 07:28:38 -08:00
utkarshcmu
e5931e264b Updated http_api docs 2015-11-16 06:56:46 -08:00
utkarshcmu
9485e8cfee /api/admin/users returns user ID 2015-11-16 06:55:02 -08:00
Mitsuhiro Tanda
22b139d9f8 (cloudwatch) support ECS suggestion 2015-11-16 23:21:43 +09:00
Alec Henninger
2a600b25e7 Ignore nulls unless 'null as zero' for series.stats.avg 2015-11-16 08:43:41 -05:00
Mitsuhiro Tanda
393891d6ea (cloudwatch) fill null if the datapoint missing 2015-11-16 18:23:12 +09:00
Mitsuhiro Tanda
1bbd056797 fix templating 2015-11-16 16:51:03 +09:00
Mitsuhiro Tanda
add5bb47d5 add dimensions() to CloudWatch templating query 2015-11-16 16:50:53 +09:00
Torkel Ödegaard
305f8d6982 Merge pull request #3234 from utkarshcmu/opentsdb-ui
Made opentsdb query editor consistent and tags are editable now
2015-11-16 06:29:02 +01:00
Torkel Ödegaard
93851a9a0f feat(tablepanel): added time series aggregations transform mode, #3219 2015-11-13 17:36:11 +01:00
utkarshcmu
8a184e9d30 Made tags editable in opentsdb query 2015-11-13 04:01:55 -08:00
utkarshcmu
3a66d7c453 Made opentsdb query editor consistent 2015-11-13 03:08:48 -08:00
Torkel Ödegaard
00a479de6f feat(tablepanel): renamed some table panel schema things 2015-11-13 10:32:01 +01:00
Torkel Ödegaard
32f9f8fcce fix(http api): correct return status code for /api/datasources/:id so it eturns 404 when not found, fixes #3217 2015-11-13 09:43:25 +01:00
Torkel Ödegaard
e4208441b3 fix(inspector): added close button to inspector modal, fixes #3213 2015-11-13 09:36:52 +01:00
Torkel Ödegaard
ee5ebe2e81 Merge pull request #3224 from mattttt/patch-6
Updating singlestat article to include troubleshooting
2015-11-13 09:32:42 +01:00
Torkel Ödegaard
a71b681e80 fix(docs): minor docs fix 2015-11-13 09:32:16 +01:00
Torkel Ödegaard
2cbc62d6c0 fix(elasticsearch): fixed elasticsearch issue, ghost docs series, fixes #3223 2015-11-13 09:30:28 +01:00
Torkel Ödegaard
cc125f5fd7 change(shortcuts): changed CTRL+F search shortcut to just F 2015-11-13 09:25:31 +01:00
utkarshcmu
7f9c8a1935 Added hours, days units and tests for all 2015-11-12 23:56:24 -08:00
utkarshcmu
3c54d14460 Added UI for time units, minute scalability 2015-11-12 20:26:59 -08:00
utkarshcmu
c5b39a5100 Added currency units 2015-11-12 09:56:46 -08:00
Matt Toback
7888c1c614 Update singlestat.md 2015-11-12 12:35:45 -05:00
Matt Toback
b43315c36d Update singlestat.md 2015-11-12 12:07:09 -05:00
Matt Toback
fbd6417c8a Updating singlestat article to include troubleshooting 2015-11-12 12:03:33 -05:00
Torkel Ödegaard
850ad1c0fb fix(tablepanel): fixed width fix for page2+, #3219 2015-11-12 16:30:15 +01:00
Torkel Ödegaard
dbe35bfed1 change(elasticsearch): changed min_doc_count to zero in query to get buckets for missing values, fixes #3131 2015-11-12 16:06:10 +01:00
Torkel Ödegaard
f1caae126e feat(build): fixed build issues 2015-11-12 14:28:35 +01:00
Torkel Ödegaard
bad6a40a12 updated readme 2015-11-12 13:50:57 +01:00
Torkel Ödegaard
5bc194e1f0 Merge branch 'master' of github.com:grafana/grafana 2015-11-12 13:47:25 +01:00
Torkel Ödegaard
d315ff2b96 changelog: updated master version to 2.6 and merged develop branch 2015-11-12 13:47:12 +01:00
Torkel Ödegaard
163b45cf37 feat(tablepanel): minor fix 2015-11-12 13:41:53 +01:00
Torkel Ödegaard
87c718f549 feat(tablepanel): worked on issues and improving defaults 2015-11-12 12:39:16 +01:00
Torkel Ödegaard
e04678f33c feat(dasbboard): fix to issues when setting fullscreen/edit state for panel that have yet to get a scope 2015-11-12 12:01:44 +01:00
Torkel Ödegaard
bbdf75bdfa feat(dashboard): Automatically go into panel edit mode after adding a new panel to the dashboard 2015-11-12 11:49:07 +01:00
Torkel Ödegaard
5c0cf9f5d7 feat(tablepanel): worked on more display options for table panel 2015-11-12 11:36:16 +01:00
Torkel Ödegaard
8a61ec4b4e feat(tablepanel): paging is starting to work 2015-11-11 16:40:36 +01:00
Torkel Ödegaard
874a6e86fd Merge pull request #3113 from utkarshcmu/custom
Added All Value support for custom type templating
2015-11-11 12:29:04 +01:00
Mitsuhiro Tanda
ef4bec1c6d fix CloudWatch dimension value suggestion 2015-11-11 20:15:30 +09:00
Torkel Ödegaard
4da99d14e6 Merge pull request #3204 from mtanda/cloudwatch_ebs_fix
CloudWatch EBS Templating fix
2015-11-11 12:09:15 +01:00
Mitsuhiro Tanda
cc80191cd1 CloudWatch ebs templating fix 2015-11-11 19:48:10 +09:00
Torkel Ödegaard
3cdb3f0d54 Merge pull request #3196 from utkarshcmu/custom-time
Timepicker display fixed for now-*
2015-11-11 11:28:55 +01:00
utkarshcmu
9f17e4ee2c Added unit tests to verify time range fix 2015-11-11 01:34:53 -08:00
utkarshcmu
509c3dc715 Fixed time range when using NOW from and to 2015-11-11 01:34:24 -08:00
Torkel Ödegaard
4051f04e66 Merge pull request #3202 from mtanda/cloudwatch_sort
sort CloudWatch suggestion region and namspace
2015-11-11 09:42:51 +01:00
Mitsuhiro Tanda
02a37d670c sort namespaces by alphabetical order 2015-11-11 14:23:43 +09:00
Mitsuhiro Tanda
a7deca1df5 sort regions by alphabetical order 2015-11-11 14:23:38 +09:00
Torkel Ödegaard
1bec6c2aae feat(tablepanel): made annotations transform work 2015-11-10 16:15:23 +01:00
Torkel Ödegaard
a66825c71f Merge branch 'master' into develop 2015-11-10 15:26:02 +01:00
Torkel Ödegaard
0fb110a4af Merge pull request #3190 from utkarshcmu/three-hours
added now-3h option
2015-11-10 15:02:34 +01:00
utkarshcmu
167c02d773 Timepicker display fixed for now-* 2015-11-10 04:53:42 -08:00
utkarshcmu
0c50a7437b missed an S at the end 2015-11-10 02:29:02 -08:00
utkarshcmu
5339ec66b7 added now-3h option 2015-11-10 02:22:15 -08:00
Torkel Ödegaard
2371dcf694 Merge pull request #3188 from mtanda/template_error_report
Fix templating error dialog for Prometheus
2015-11-10 11:21:26 +01:00
Mitsuhiro Tanda
65bc194c42 fix templating error dialog for Prometheus 2015-11-10 19:01:37 +09:00
Torkel Ödegaard
99ee38cea3 feat(tablepanel): minor change 2015-11-10 08:38:34 +01:00
Torkel Ödegaard
5d8a51c307 Merge pull request #3129 from utkarshcmu/validity
Default AWS Region selection from dropdown
2015-11-09 22:25:30 +01:00
utkarshcmu
6325635fce Corrected the frontend filename 2015-11-09 12:31:35 -08:00
utkarshcmu
7612e47aee Select AWS region from dropdown 2015-11-09 12:08:40 -08:00
Torkel Ödegaard
d06b9401ea feat(tablepanel): added support for column sorting 2015-11-09 17:58:02 +01:00
Torkel Ödegaard
ecb1552c10 Merge pull request #3172 from utkarshcmu/aws-bug
Test specified AWS Region and not hardcoded us-east-1
2015-11-09 13:54:20 +01:00
utkarshcmu
9f066d01b1 Check specified AWS Region and not us-east-1 2015-11-09 03:35:51 -08:00
Torkel Ödegaard
673ae1edc0 Merge branch 'tablepanel2' into develop 2015-11-09 11:26:06 +01:00
Torkel Ödegaard
b25b31e4a0 Merge branch 'master' into develop 2015-11-09 11:25:22 +01:00
Torkel Ödegaard
f65fde8bb7 Merge pull request #3148 from utkarshcmu/units
Added throughput units.
2015-11-09 10:14:02 +01:00
Torkel Ödegaard
0a04b135ca Merge pull request #3158 from dthapa/master
use [auth.github] -> api_url property; supports git enterprise
2015-11-09 10:12:57 +01:00
Torkel Ödegaard
88d27fe649 log(color): disabled console log formating by default 2015-11-09 09:48:02 +01:00
Torkel Ödegaard
5d166dc8cb feat(tablepanel): added new renderer spec 2015-11-09 09:46:49 +01:00
Torkel Ödegaard
8f81c97aaa Merge pull request #3170 from utkarshcmu/opentsdb-ctrl
Fixed opentsdb queryctrl uncheck Rate UI bug
2015-11-09 09:43:25 +01:00
Torkel Ödegaard
b5f35261a0 Merge pull request #3171 from utkarshcmu/aws-regions
Added missing AWS Regions
2015-11-09 09:41:57 +01:00
utkarshcmu
f55e24cd12 Added missing AWS Regions 2015-11-09 00:28:16 -08:00
utkarshcmu
efc0b60d41 Fixed opentsdb queryctrl uncheck Rate UI bug 2015-11-08 21:44:58 -08:00
Nick Owens
60e797ccc4 pkg/setting: integrate syslog logger settings 2015-11-07 18:35:57 -08:00
Nick Owens
20b553461b conf: add syslog logging defaults 2015-11-07 18:35:52 -08:00
Nick Owens
0bff097afb pkg/log: implement syslog logger 2015-11-07 18:34:55 -08:00
Nick Owens
b0cb6d6d4c pkg/api/cloudwatch: fix api client construction against aws-sdk-go v0.10.2 2015-11-07 18:17:05 -08:00
Don Thapa
b345c7cf46 gofmt happiness 2015-11-07 11:32:06 -06:00
Torkel Ödegaard
56e2082205 Merge pull request #3157 from utkarshcmu/postgres
Fixed user deletion in Postgres SQL
2015-11-07 15:37:28 +01:00
utkarshcmu
fe2d8f1ea0 Used dialect for postgres 2015-11-07 05:21:22 -08:00
Don
e16bbc660d use [auth.github] -> api_url property; supports git enterprise 2015-11-07 00:06:15 -06:00
utkarshcmu
2676f24e0a Fixed user deletion in Postgres SQL 2015-11-06 20:17:27 -08:00
Torkel Ödegaard
b8e6fcfeae feat(tablepanel): worked on cell / value threshold coloring 2015-11-06 13:16:17 +01:00
utkarshcmu
b5f18561ab Added unit tests to verify units 2015-11-05 23:13:53 -08:00
utkarshcmu
22c3ec2d63 Made the units more readable 2015-11-05 22:57:05 -08:00
utkarshcmu
b678daa744 Added throughput units. 2015-11-05 22:31:33 -08:00
Torkel Ödegaard
e1433ebb41 feat(tablepanel) more refactoring 2015-11-05 12:42:47 -05:00
Torkel Ödegaard
1b83742e3e feat(tablepanel): began refactorin out table row html generation to write unit tests for it 2015-11-05 15:55:42 +01:00
Torkel Ödegaard
90cca93951 feat(tablepanel): lots of work on table panel 2015-11-05 13:13:13 +01:00
Torkel Ödegaard
4e37290a7f feat(tablepanel/elasticsearch): extended elasticsearch data source and query editor to support document queries 2015-11-05 09:56:19 +01:00
Torkel Ödegaard
7d3146ed8d feat(tablepanel): fixed header, and pagination styling 2015-11-05 08:36:51 +01:00
Torkel Ödegaard
0a1af65a4c feat(tablepanel): more column style rules 2015-11-05 07:43:06 +01:00
utkarshcmu
4de9ac133a Set default AWS region from dropdown now 2015-11-04 17:21:34 -08:00
Torkel Ödegaard
60c7bfe9a7 feat(tablepanel): work on table panel options 2015-11-04 22:44:08 +01:00
Torkel Ödegaard
7387f2e490 feat(tablepanel): fixed header, and pagination styling 2015-11-04 17:23:16 +01:00
Torkel Ödegaard
93b4f3fac8 feat(tablepanel): minor progress on table panel 2015-11-04 12:56:53 +01:00
utkarshcmu
fdeeb73587 AWS Region as a mandatory field 2015-11-04 02:27:35 -08:00
Torkel Ödegaard
6062930f9a feat(tablepanel): added more unit tests for table transforms 2015-11-04 09:41:03 +01:00
Torkel Ödegaard
867b838053 feat(tablepanel): work on table panel 2015-11-03 16:19:51 +01:00
Nick Christus
58dc282ca0 updated-list-views: updating table layout for org users 2015-11-03 08:24:10 -06:00
Torkel Ödegaard
d7ee7cb88f Merge pull request #3119 from utkarshcmu/docs
Dashboard JSON Docs
2015-11-03 13:33:15 +01:00
Torkel Ödegaard
da9c792ca2 feat(tablepanel): minor progress 2015-11-03 08:18:35 +01:00
Torkel Ödegaard
8171cd51c4 feat(tablepanel): minor progress on table panel 2015-11-02 20:51:49 +01:00
Utkarsh Bhatnagar
74b10a42ee Fixed broken links in the doc page 2015-11-02 10:29:42 -08:00
Utkarsh Bhatnagar
f14ef22bb6 Fixed doc links 2015-11-02 10:25:33 -08:00
Utkarsh Bhatnagar
e8c9b0806a Added templating, timepicker, panel docs 2015-11-02 10:15:40 -08:00
Torkel Ödegaard
36c4d01ef8 feat(tablepanel) began work on new table panel 2015-11-02 17:00:47 +01:00
Torkel Ödegaard
e51d403420 rename: moved test file 2015-11-02 15:14:35 +01:00
Utkarsh Bhatnagar
f54615ed46 Included rows JSON and TODO headers 2015-11-02 00:21:59 -08:00
utkarshcmu
4c1b6f3059 Fixed a typo 2015-11-01 23:55:03 -08:00
Utkarsh Bhatnagar
ada641090f Explained basic JSON fields 2015-11-01 23:53:24 -08:00
Torkel Ödegaard
eb6c8a3521 Merge pull request #3118 from utkarshcmu/angular-native
Removed unnecessary components.
2015-11-02 08:23:02 +01:00
utkarshcmu
3a021a87a1 Added JSON of new dashboard 2015-11-01 23:17:23 -08:00
utkarshcmu
5100339604 Initialized dashboard JSON doc. 2015-11-01 22:58:14 -08:00
utkarshcmu
8448e3970b Removed unnecessary components. 2015-11-01 09:48:27 -08:00
Torkel Ödegaard
dbed679904 Merge pull request #3116 from vitaliyf/patch-1
Fixed typo in OpenTSDB's "metasync" documentation
2015-11-01 14:47:28 +01:00
Vitaliy Fuks
acb5340ffb Fixed typo in OpenTSDB's "metasync" documentation 2015-10-31 18:07:24 -04:00
Torkel Ödegaard
6a01cd56ca Merge pull request #3085 from mtanda/cloudwatch_template_doc
Add templating explanation to CloudWatch docs.
2015-10-31 11:37:01 +01:00
Torkel Ödegaard
ae38705bed Merge pull request #3112 from utkarshcmu/docs
Update opentsdb.md
2015-10-31 11:35:30 +01:00
Torkel Ödegaard
db083c43dd Merge pull request #3111 from felixbuenemann/fix-npm-3-phantomjs-build-failure
Fix npm 3 build failure in phantomjs task
2015-10-31 11:35:17 +01:00
utkarshcmu
c5435596ad Added All Value support for custom type templating 2015-10-30 22:34:40 -07:00
Utkarsh Bhatnagar
03130e1217 Update opentsdb.md
As we merged, changes with auto suggestions in the master branch. Update docs respectively.
2015-10-30 22:11:17 -07:00
Felix Bünemann
2ca6acc1e9 Fix npm 3 build failure in phantomjs task
npm v3.0+ by default dedupes node modules and stores them in a flat
tree, which means the hardcoded path to the location.js will no longer
be nested under the karma-phantomjs-launcher module.

This fixes issue #2999.
2015-10-31 05:36:35 +01:00
Torkel Ödegaard
d8f68eb118 refactoring: moving and renaming things 2015-10-30 16:06:29 +01:00
Torkel Ödegaard
97697b93ed refactoring: moving and renaming things 2015-10-30 15:58:20 +01:00
Torkel Ödegaard
152b484eb5 refactoring: moved app/controllers -> app/core/controllers 2015-10-30 15:16:05 +01:00
Torkel Ödegaard
97de8c1cc2 refactoring: move moving stuff around 2015-10-30 15:04:27 +01:00
Torkel Ödegaard
6cf46b1635 refactoring: more moving stuff around 2015-10-30 14:44:40 +01:00
Torkel Ödegaard
1665cb4282 refactoring: moving components -> core 2015-10-30 14:24:04 +01:00
Torkel Ödegaard
1113081aab refactoring: moving components -> core 2015-10-30 14:19:02 +01:00
Torkel Ödegaard
39bc3cb532 refactoring: moving stuff around 2015-10-30 14:04:25 +01:00
Torkel Ödegaard
0a0a0776e4 Merge pull request #3088 from utkarshcmu/suggest-opentsdb
Tag suggestions fixed for v2.1.1 & above by using Suggest Api.
2015-10-30 10:34:44 +01:00
Torkel Ödegaard
59d199a148 Merge pull request #3098 from utkarshcmu/typos
Fixed some more typos in docs
2015-10-30 10:32:51 +01:00
utkarshcmu
8b9d13491f Fixed some more typos in docs 2015-10-30 02:07:08 -07:00
Torkel Ödegaard
4dcd2ceb01 fix(graph): fixed for color picker layout issue when right side legend was used, fixes #3093 2015-10-30 09:31:04 +01:00
Torkel Ödegaard
6004ba6554 Merge pull request #3095 from itsmrwave/correct_object_key_typo
Correct object key typo
2015-10-30 09:15:27 +01:00
King'ori Maina
f1847d4501 Correct object key typo
Should be ‘message’ not ‘messsage’.
2015-10-30 09:52:33 +02:00
Nick Christus
e63ff167a7 updated-list-views: added filter controls, updated tables on API Keys and Data Sources 2015-10-29 21:09:21 -05:00
utkarshcmu
a27186e34f Cleaned the codebase :D 2015-10-29 11:28:38 -07:00
utkarshcmu
2a8904f844 Fixed queryCtrl to use suggest API 2015-10-29 11:13:38 -07:00
Torkel Ödegaard
59fc72d37e Merge branch 'prometheus-fix_step_calculation' of https://github.com/dan-cleinmark/grafana into dan-cleinmark-prometheus-fix_step_calculation 2015-10-29 16:50:18 +01:00
Torkel Ödegaard
34e3683ded fix(cloudwatch): fixed limiting of cloudwatch period so it works for long time ranges in all cases, fixes #3086 2015-10-29 16:46:58 +01:00
Torkel Ödegaard
13760b1bdd fix(influxdb): fixed handling of relative time ranges like last x years, or last x months, fixes #3067 2015-10-29 15:53:15 +01:00
Mitsuhiro Tanda
8a252774ce Update cloudwatch.md, add explanation about templating 2015-10-29 23:51:27 +09:00
Dan Cleinmark
963f9fdf40 Ensure Promtheus step interval is always < 11000
Using a 2 week window (1209600 seconds) and a 60s step, Math.floor()
recalculates a step of 109 and results in 11097 data points in the
Prometheus query (> the 11000 max set by Prometheus). Math.ceil()
returns a step of 110 and 10996 data points.
2015-10-29 07:24:42 -07:00
Torkel Ödegaard
299a2457cd Merge branch 'master' of github.com:grafana/grafana 2015-10-29 14:05:19 +01:00
Torkel Ödegaard
3de4707c98 feat(elasticsearch): Annotation queries now use the daily index patterns defined in data source options, for old annotations that have an index property that will be used, so will not break existing dashboard/annotation configs, closes #3061 2015-10-29 14:05:05 +01:00
Torkel Ödegaard
603ec65e91 Merge pull request #3059 from mtanda/cloudwatch_template_fix
fix panel repeat for cloudwatch
2015-10-29 12:38:22 +01:00
Torkel Ödegaard
cdcffcd31e fix(css): restored tooltip background to dark for white theme #3079 2015-10-29 12:32:32 +01:00
Torkel Ödegaard
f8a1c7c8a1 docs(cloudwatch): updated docs with info about #3080 2015-10-29 12:22:01 +01:00
Torkel Ödegaard
5d64568f3e refactoring: some minor refactoring and changes to AWS profile PR #3053 2015-10-29 11:44:34 +01:00
Torkel Ödegaard
374fbad06d Merge branch 'master' into peekeri-support_aws_profiles 2015-10-29 11:19:15 +01:00
Torkel Ödegaard
aa31336c64 Merge pull request #3072 from utkarshcmu/tooltip
Added tooltip for cloudwatch datasource
2015-10-29 11:18:24 +01:00
Torkel Ödegaard
135ba68ff5 Merge branch 'support_aws_profiles' of https://github.com/peekeri/grafana into peekeri-support_aws_profiles 2015-10-29 11:14:04 +01:00
utkarshcmu
d3ee81bb5a Added tooltip for cloudwatch datasource 2015-10-28 10:18:44 -07:00
Torkel Ödegaard
4729bea1a2 fix(dashboard): fix for collapse row by clicking on row title, fixes #3065 2015-10-28 15:04:58 +01:00
Torkel Ödegaard
e1393f9780 changelog: updated and marked 2.5 as released 2015-10-28 14:46:47 +01:00
Torkel Ödegaard
ad7e66cdae Merge pull request #3062 from utkarshcmu/templates
Fixed typos in 2.5v doc
2015-10-28 13:00:22 +01:00
utkarshcmu
2f09ef2970 Fixed typos in 2.5v doc 2015-10-28 04:27:51 -07:00
Torkel Ödegaard
6ea2c08ecb docs(): minor docs fix 2015-10-28 11:50:22 +01:00
Mitsuhiro Tanda
b82f1edd63 fix panel repeat for cloudwatch 2015-10-28 19:36:49 +09:00
Torkel Ödegaard
1685e7cea4 docs(): update to install docs and whats new in 2.5 2015-10-28 11:08:12 +01:00
Torkel Ödegaard
287d8ca367 fix(changelog): minor spelling fix to changelog 2015-10-28 09:43:28 +01:00
Torkel Ödegaard
6397b8c1ef docs(): updated version to 2.5.0 2015-10-28 09:31:57 +01:00
Torkel Ödegaard
89eedd59a8 Merge pull request #3008 from mtanda/prometheus_link
Revert prometheus graph view link
2015-10-28 09:29:07 +01:00
Nick Christus
aa9093bcf6 updated-list-views: added filter-table less component, updating styles for data sources table 2015-10-27 23:26:11 -05:00
Jari Sukanen
23599814a3 cloudwatch: add support for defining AWS profile for CloudWatch datasource
Add support for defining AWS profile for CloudWatch datasource to support
pulling information from multiple different AWS accounts to single dashboard.

With this change, it is possible to define multiple AWS credentials in
~/.aws/credentials file and connect different data sources to different
AWS accounts.
2015-10-27 17:00:02 +02:00
Torkel Ödegaard
89ce1a5159 fix(dashlist): minor fix to dashlist panel, and some minor html markup fixes 2015-10-27 13:17:28 +01:00
Torkel Ödegaard
22a4ef42fc version change to 2.5-pre2 2015-10-27 12:22:30 +01:00
Torkel Ödegaard
358ba395ac fix(invite): minor fix to invite partials markup 2015-10-27 12:09:14 +01:00
Torkel Ödegaard
e7d5ea8a6c fix(build): revert some build script changes to make building on go 1.4 work again 2015-10-27 12:08:56 +01:00
Torkel Ödegaard
a36711e640 fix(changelog): fixed link in changelog 2015-10-27 10:26:44 +01:00
Torkel Ödegaard
6fabff4769 Merge pull request #3049 from utkarshcmu/docs
Fixed typos in cloudwatch docs
2015-10-27 07:14:41 +01:00
ubhatnagar
6af86152e6 Removed typos in cloudwatch docs 2015-10-26 21:02:04 -07:00
Torkel Ödegaard
06d97c78c8 Merge pull request #3044 from mattttt/patch-5
Updates to timepicker docs for 2.5 release
2015-10-26 21:22:22 +01:00
Matt
da31fffb16 Update timerange.md 2015-10-26 14:05:44 -04:00
Matt
c97e3ec7ae Updates to timepicker docs for 2.5 release
Updated images to be in separate PR.
2015-10-26 14:03:26 -04:00
Torkel Ödegaard
09b3433e32 change(dashboards): made home dashboard and json file dashboards editable unless otherwise specified in json file, closes #2567 2015-10-26 18:54:32 +01:00
Torkel Ödegaard
323e84375b refactoring: minor refactoring and handling of known data source plugins 2015-10-26 16:37:45 +01:00
Torkel Ödegaard
3b67a6a222 changelog: updated changelog with details of #2928 2015-10-26 16:23:29 +01:00
Torkel Ödegaard
da3d5375b8 feat(ldap): refactoring of PR #2928 updated docs 2015-10-26 16:21:03 +01:00
Torkel Ödegaard
38bd0d1aec Merge branch 'ldap-improvements' of https://github.com/abligh/grafana into abligh-ldap-improvements 2015-10-26 15:56:21 +01:00
Torkel Ödegaard
59bd029e46 docs(cloudwatch): minor cloudwatch fix 2015-10-26 15:51:34 +01:00
Torkel Ödegaard
1dbf0ad976 docs(cloudwatch): initial cloudwatch docs, closes #2900 2015-10-26 15:44:14 +01:00
Torkel Ödegaard
3b4c095b49 Merge branch 'time-independent-prometheus-metrics' of https://github.com/arthurdandrea/grafana into arthurdandrea-time-independent-prometheus-metrics 2015-10-26 14:35:17 +01:00
Torkel Ödegaard
2e155bdeda fix(dashboard): minor function name fixes, removed insert row above/below because it did not work, #2909 2015-10-26 14:33:55 +01:00
Torkel Ödegaard
8305fd0451 Merge branch 'master' of github.com:grafana/grafana 2015-10-26 14:21:49 +01:00
Torkel Ödegaard
7477667df1 docs(elasticsearch): initial elasticsearch docs, closes #2862 2015-10-26 14:21:38 +01:00
Torkel Ödegaard
a066d7ddcb Merge pull request #2909 from utkarshcmu/title
Added move row to top and bottom and insert row capability.
2015-10-26 14:15:42 +01:00
Torkel Ödegaard
a5c742cee5 docs(elasticsearch): began work on elasticsearch docs #2862 2015-10-26 13:36:00 +01:00
Torkel Ödegaard
cac142b134 Merge pull request #2937 from mtanda/scripted_datemath
Use dateMath in ScriptedDashboard
2015-10-26 12:51:09 +01:00
Torkel Ödegaard
da7ae2b0ab fix(build/aws): updated aws dependency and fixed minor build issue, fixes #3026 2015-10-26 12:48:30 +01:00
Torkel Ödegaard
052c9aca15 Merge branch 'master' of github.com:grafana/grafana 2015-10-26 12:16:13 +01:00
Torkel Ödegaard
b0e975bfce Merge pull request #3025 from utkarshcmu/docs
Added OpenSuse documentation
2015-10-26 08:22:52 +01:00
Torkel Ödegaard
d4664507f1 Merge pull request #3029 from mlbarrow/patch-1
Fix small typo in docs for InfluxDB (influxdb.md)
2015-10-26 08:22:34 +01:00
mlbarrow
21f3f859b9 Update influxdb.md
Typo fix
2015-10-24 20:58:19 -07:00
Utkarsh Bhatnagar
54be4c5e2c Made installation doc consistent 2015-10-23 23:21:00 -07:00
ubhatnagar
45cdbe0a18 Rearranged installation docs for OpenSuse 2015-10-23 23:03:58 -07:00
ubhatnagar
6c76e9728e Added OpenSuse installation command 2015-10-23 22:53:30 -07:00
Arthur D'Andréa Alemar
0a6a3f9ab7 fix(prometheus): use time independent API to list metrics and labels names
Using the "/api/v1/query" endpoint to extract information about metrics
and labels are limited to the metrics available at the time parameter
(that is set to current time), this can lead to labels not showing
because they have no value in the current time even when the dashboard
is displaying historic data.

On the other hand "/api/v1/series" returns results including every
metric and label known to Prometheus, independent of time and value.
2015-10-23 17:18:34 -02:00
Torkel Ödegaard
bad8ab232d Merge pull request #3020 from utkarshcmu/docs
Fixed 3014
2015-10-23 17:40:29 +02:00
Torkel Ödegaard
91a814d295 Merge pull request #3017 from volter/master
"No limit" was not effective for ES terms aggregation
2015-10-23 17:39:44 +02:00
ubhatnagar
7205cf8ce1 Fixed 3014 2015-10-23 07:29:15 -07:00
Torkel Ödegaard
6fe6a33da3 Merge pull request #3019 from sathieu/patch-2
Fix LimitNOFILE in RPM systemd unit
2015-10-23 16:17:45 +02:00
Torkel Ödegaard
9b59fc1c94 Merge pull request #3018 from sathieu/patch-1
Fix LimitNOFILE in Debian systemd unit
2015-10-23 16:17:28 +02:00
Mathieu Parent
e7834b885a Fix LimitNOFILE in RPM systemd unit 2015-10-23 15:13:41 +02:00
Mathieu Parent
4ec6691ea9 Fix LimitNOFILE in Debian systemd unit 2015-10-23 15:12:52 +02:00
Volker Fröhlich
6b9b08da30 Remove declaration of unused variable size 2015-10-23 12:00:20 +02:00
Volker Fröhlich
184307816f "No limit" was not effective for ES terms aggregation
This may belong to #2827
2015-10-23 11:40:40 +02:00
Mitsuhiro Tanda
c1d592b72c fix, call linkToPrometheus() directly 2015-10-23 09:58:42 +09:00
Torkel Ödegaard
2d23251da9 spelling: fixed selling in influxdb annotation partial, fixes #3012 2015-10-22 18:24:43 -04:00
Torkel Ödegaard
5b01e9ec97 fix(elasticsearch): minor fix to elasticsearch unit tests so that they work in any timezone, fixes #3010 2015-10-22 16:58:31 -04:00
Torkel Ödegaard
8526230b59 fix(influxdb_08): fixed influxdb 08 query editor issue, fixes #3009 2015-10-22 16:53:34 -04:00
Torkel Ödegaard
9d04a4c4f0 Merge branch 'master' of github.com:grafana/grafana 2015-10-22 16:23:31 -04:00
Torkel Ödegaard
ae93f2b936 fix(elasticsearch): fixed proper json escaping for lucene query, fixes #2981 2015-10-22 16:23:21 -04:00
Mitsuhiro Tanda
fcaecf4782 revert prometheus link 2015-10-23 01:35:34 +09:00
Torkel Ödegaard
0b28db7fae Merge pull request #3005 from felixbarny/elastic_ds_min_interval
Ability to set a low limit for Elasticsearch date histogram interval
2015-10-22 12:35:23 -04:00
Torkel Ödegaard
87715d6231 fix(solo panel): fixed solo panel view gray bottom for rendered image and embedd iframe scenarios, fixes #3004 2015-10-22 12:33:42 -04:00
Torkel Ödegaard
3228c4f41a changelog: updated with info about new units PR #2955 2015-10-22 12:27:22 -04:00
Torkel Ödegaard
ce5b4089b5 Merge branch 'new-units' of https://github.com/counsyl/grafana into counsyl-new-units 2015-10-22 12:19:21 -04:00
Torkel Ödegaard
0903aa4596 Merge pull request #2980 from tdyas/linkSrv_fix_query_string_appending
fix appending query strings in linkSrv
2015-10-22 12:18:27 -04:00
Torkel Ödegaard
26a45c8dc0 Merge pull request #3000 from utkarshcmu/docs
Fixed docs typos
2015-10-22 12:14:06 -04:00
Torkel Ödegaard
62c908a905 fix(build): fixed partials so they are included in optimized js file, fixes #2997 2015-10-22 10:02:29 -04:00
Felix Barnsteiner
eb8c2d9053 Ability to set a low limit for Elasticsearch date histogram interval
closes #2901
2015-10-22 13:16:36 +02:00
ubhatnagar
1e2e4ba3ad Fixed other docs typos 2015-10-21 22:09:07 -07:00
ubhatnagar
aaf4b1a399 Fixed typos in guides. 2015-10-21 21:13:39 -07:00
ubhatnagar
a5e3d7a94b Fixed datasources docs typos 2015-10-21 20:58:04 -07:00
Torkel Ödegaard
58497ed596 feat(panel): performance improvement for loading panels, closes #2994 2015-10-21 11:22:53 -04:00
Torkel Ödegaard
ae81fbdffe Merge branch 'master' of github.com:grafana/grafana 2015-10-21 10:59:17 -04:00
Torkel Ödegaard
fc0705e87c fix(elasticsearch): fix for daily pattern when getting index for today, is now using utc, fixes #2913 2015-10-21 10:59:02 -04:00
Torkel Ödegaard
c489d806dc Merge pull request #2995 from peekeri/fix_influxdb_annotation_query
influxdb: fix influxdb annotation query
2015-10-21 10:34:29 -04:00
Jari Sukanen
f3ecdc5af4 influxdb: fix influxdb annotation query 2015-10-21 14:31:06 +03:00
Torkel Ödegaard
6fecb4bf3e fix(http route): fixed dashboard-solo route to not return 404, fixes #2979 2015-10-20 10:02:56 -04:00
Tom Dyas
867ac5df67 fix appending query strings in linkSrv
When linkSrv appends parameters to a URL's query string, it would blindly
add a ? to the URL even if the URL already contained a ? or the string
to add was empty. This change fixes that behavior and some other edge cases.

Includes a unit test to verify expected behavior.
2015-10-19 13:11:34 -04:00
Torkel Ödegaard
1ff3c3be84 Merge pull request #2952 from Krinkle/fixup
docs(): Fix changelog link in whats-new-in-v2-1
2015-10-17 12:44:09 -04:00
Torkel Ödegaard
1cb9de07a6 Merge pull request #2962 from damm/docs_spellcheck
I noticed a typo when I was reviewing the docs ...
2015-10-17 12:41:25 -04:00
Torkel Ödegaard
43ca50ebbe fix(build): anonther minor build script fix 2015-10-16 13:03:20 -04:00
Torkel Ödegaard
b70b730cb9 fix(build): minor fix for build script to make latest copy for rpm when version is pre release version 2015-10-16 12:26:30 -04:00
Torkel Ödegaard
dbc1a9cf82 fix(influxdb_0.8.x): fixed issue with new timepicker ranges like The day so far, fixes #2936 2015-10-16 12:07:35 -04:00
Torkel Ödegaard
c95a991cb3 fix(panel/common): fix for query letters when importing old dashboards, fixes #2943 2015-10-16 11:58:44 -04:00
Torkel Ödegaard
c320e9d583 fix(annotations): fixed graphite annotations, broken by recent time handling changes, fixes #2947 2015-10-16 11:07:15 -04:00
Torkel Ödegaard
e507afc3d5 fix(panel): fix for firefox and placing cursor in text inputs when in panel fullscreen edit mode, fixes #2957 2015-10-16 10:10:39 -04:00
Scott M. Likens
bd77fd92bb I'm not sure what a dashboard is ... 2015-10-15 19:45:40 -07:00
Greg Look
882a988143 Add currency units from #1910. 2015-10-15 12:48:48 -07:00
Greg Look
0b3e33e226 Shorten percent unit labels. 2015-10-14 15:40:04 -07:00
Greg Look
524f5d45ec Add test for unit menu structure. 2015-10-14 14:51:42 -07:00
Greg Look
85887ad1cf Add mile, fix menu values. 2015-10-14 14:51:33 -07:00
Greg Look
d94b6635af Add temperature and pressure units, split submenus. 2015-10-14 14:50:45 -07:00
Greg Look
3c7a483f5c Add length and volume units. 2015-10-14 14:14:25 -07:00
Greg Look
dc5c3a3939 Implement decibel and percentunit units.
Add tests to exercise new units as well.
2015-10-14 14:02:29 -07:00
ubhatnagar
9185c94a2d Added insert row option in the row menu. 2015-10-14 13:45:47 -07:00
Greg Look
70269c8196 Reformat unit menu definition. 2015-10-14 13:34:48 -07:00
Greg Look
51a589f5a6 Change formatFuncCreator to scaledUnits builder.
Abstract out both decimal and binary SI prefixes into builders using
scaledUnits.
2015-10-14 13:18:02 -07:00
Greg Look
7d24c5fda2 Add fixedUnit format builder. 2015-10-14 13:07:34 -07:00
Greg Look
0bc85d27f8 Group rounding and fixed number functions. 2015-10-14 12:56:31 -07:00
Greg Look
43c2ca2d7d Group value formats by type. 2015-10-14 12:51:59 -07:00
Greg Look
e3e21a251f Group helper functions in kbn.js. 2015-10-14 12:41:20 -07:00
Timo Tijhof
267417d6a8 docs(): Fix changelog link in whats-new-in-v2-1
The quotes turn the value into a title attribute rather than href attribute,
thus on http://docs.grafana.org/guides/whats-new-in-v2-1/ this link was
rendered as <a href title="https://github.com/...">CHANGELOG.md</a>
which when clicked goes back to itself (not to GitHub).
2015-10-14 14:00:25 -04:00
Alex Bligh
e8256f0ad7 Add support for POSIX LDAP schema
In the POSIX LDAP schema, there is no 'memberOf' attribute returned
in relation to which groups a person is a member of. Rather, it is
necessary to query the group objects which have the people as members.
This commit adds an additional filter, which if specified explicitly
searches for groups, rather than relying on the 'memberOf' attribute.
This enables Grafana to work with LDAP POSIX schema (e.g. OpenLDAP
etc.)

Signed-off-by: Alex Bligh <alex@alex.org.uk>
2015-10-13 19:51:59 +01:00
Alex Bligh
458e6da700 Allow user specified CA certs
Signed-off-by: Alex Bligh <alex@alex.org.uk>
2015-10-13 19:47:24 +01:00
Alex Bligh
a906fa178a Support multiple space-separated LDAP hosts
Signed-off-by: Alex Bligh <alex@alex.org.uk>
2015-10-13 19:46:53 +01:00
Mitsuhiro Tanda
4588c5a19a pass dateMath to ScriptedDashboard script 2015-10-13 12:29:42 +09:00
Nick Christus
23404decea added global alerts list stub and styles 2015-10-11 16:59:40 -04:00
Torkel Ödegaard
e873574e8c fix(logging): fixed so that router_logging = true actually logs all http requests, fixes #2902 2015-10-10 17:55:15 -04:00
Torkel Ödegaard
d09bff9039 fixed failing jshint 2015-10-10 15:11:58 -04:00
Torkel Ödegaard
c0da52aac8 fix(share): fixed share panel image url, did not generate correct url when domain name contained word dashboards, fixes #2916 2015-10-10 14:38:22 -04:00
Nick Christus
7e2f653bc7 added alerting tab stub and styles 2015-10-10 14:17:07 -04:00
Torkel Ödegaard
c831369974 fix(influxdb): influxdb data source did not use right http abstraction for metric queries, fixes #2919 2015-10-10 11:46:00 -04:00
Torkel Ödegaard
f0a13b2a0d Merge pull request #2914 from anryko/master
Fixed configuration example for Github OAuth team_ids.
2015-10-10 16:26:28 +02:00
anryko
b68987dcde Fixed configuration example for Github OAuth team_ids. 2015-10-09 17:05:46 +02:00
Torkel Ödegaard
c1f77eeaea Merge branch 'master' of github.com:grafana/grafana 2015-10-09 08:28:48 +02:00
Torkel Ödegaard
9b5a0a54cf minor docs fix 2015-10-09 08:28:33 +02:00
ubhatnagar
14f3a68215 Added move row to top and bottom. 2015-10-09 03:06:52 +05:30
Torkel Ödegaard
bf25b9f443 Merge pull request #2896 from garymcleanhall/fix-grafana-on-microsoft-edge
Removed spurious `</div>`, fixes Edge on Windows
2015-10-08 21:29:23 +02:00
Torkel Ödegaard
3435aaea45 fixed failing unit test 2015-10-08 17:39:06 +02:00
Torkel Ödegaard
9fc91b7aa1 fixed gofmt issue 2015-10-08 17:30:13 +02:00
Torkel Ödegaard
04eefb8480 fix(timepicker): fixed issue with timepicker and auto refresh and entering manual time when timepicker dropdown is open, fixes #2897 2015-10-08 15:35:17 +02:00
Torkel Ödegaard
8789be7671 Merge branch 'master' of github.com:grafana/grafana 2015-10-08 13:30:11 +02:00
Torkel Ödegaard
fe46410c31 Merge branch 'cloudwatch' 2015-10-08 13:09:27 +02:00
Torkel Ödegaard
aef644bd21 feat(cloudwatch): final polish to the cloudwatch editor, closes #684 2015-10-08 13:09:15 +02:00
Torkel Ödegaard
df4d5ea8a6 Merge pull request #2898 from zachm/ztm_patch_missing_downsample
Fix missing downsampling name in kairos plugin
2015-10-08 12:05:20 +02:00
Torkel Ödegaard
c34c3ac845 feat(cloudwatch): refactoring and editor improvements, #684 2015-10-08 11:37:47 +02:00
Zachary Musgrave
a94720878b Fix missing downsampling name in kairos plugin 2015-10-07 16:43:40 -07:00
Gary McLean Hall
59a2042a31 Removed spurious </div>, fixes Edge on Windows 2015-10-08 00:14:00 +01:00
Torkel Ödegaard
f680c15283 fix(snapshot): fixed issue with timepicker / time display when viewing snapshot, fixes #2892 2015-10-07 13:07:30 +02:00
Torkel Ödegaard
f11785001c docs: minor fix 2015-10-06 17:51:22 +02:00
Torkel Ödegaard
106fe4854f Merge branch 'prometheus' of github.com:grafana/grafana
Conflicts:
	pkg/models/datasource.go
	public/app/plugins/datasource/prometheus/datasource.js
	public/app/plugins/datasource/prometheus/partials/query.editor.html
	public/app/plugins/datasource/prometheus/queryCtrl.js
2015-10-06 16:36:58 +02:00
Torkel Ödegaard
44d377b810 feat(prometheus): refactoring and polish of the prometheus editor removing unused/uneeded code 2015-10-06 14:34:44 +02:00
Torkel Ödegaard
a2bf3e056d Merge branch 'prometheus' 2015-10-06 14:10:06 +02:00
Torkel Ödegaard
2d722e26de fix(cloudwatch): fixed test datasource, broken due to some function name changes 2015-10-06 14:08:48 +02:00
Torkel Ödegaard
182e079d25 Merge branch 'master' into cloudwatch 2015-10-06 14:08:10 +02:00
Torkel Ödegaard
318af9b418 small docs structure change, moved build from source into install section and CLA into project, and about into project 2015-10-06 10:38:19 +02:00
Torkel Ödegaard
0bcda4a2eb minor elasticsearch fix 2015-10-05 11:17:53 +02:00
Ivan Babrou
0dc0e03c4c Add suggest_tagk() and suggest_tagv() to OpenTSDB datasource (#2840) 2015-10-04 12:54:43 +01:00
Torkel Ödegaard
0db2d07a1a Merge pull request #2871 from mtanda/prometheus_time_fix
fix prometheus time conversion
2015-10-03 12:50:05 +02:00
Torkel Ödegaard
3d52095765 feat(cloudwatch): restored dimension keys lookup 2015-10-02 20:25:28 +02:00
Torkel Ödegaard
0912cec0e5 feat(cloudwatch): contining work on moving hard coded stuff to backend and cleaning up code, #684 2015-10-02 11:54:35 +02:00
Torkel Ödegaard
180ba33ac8 feat(cloudwatch): refactoring and cleanup of backend code, started moving hard coded stuff in the frontend to the backend, changed name of metricFind queries region() -> regions() , and namespace() -> namespaces() to be more consistent with the others, #684 2015-10-02 11:10:21 +02:00
Torkel Ödegaard
04f4454974 feat(cloudwatch): lots of code refactoring and cleanup, #684, dimension values lookup works but seems to return all dimension values no matter what dimension key you select, removed strange formating of template dimension values query, should not return key value pair but only the dimension value 2015-10-02 09:04:46 +02:00
Torkel Ödegaard
91285baea5 feat(cloudwatch): fixed failing unit tests 2015-10-02 07:32:21 +02:00
Torkel Ödegaard
875d80aa72 feat(cloudwatch): refactoring cloudwatch datasource code, #684 2015-10-02 07:25:54 +02:00
Mitsuhiro Tanda
5e19fdb492 fix prometheus time conversion 2015-10-02 12:10:11 +09:00
Torkel Ödegaard
bddcc6491a Merge pull request #2870 from ctdk/strip-format
Add config option to strip (most) colors from console logs
2015-10-01 22:31:21 +02:00
ctdk
d37e18fdcf Add config option to strip (most) colors from console logs 2015-10-01 13:16:23 -07:00
Torkel Ödegaard
af8d12124a feat(cloudwatch): code refactoring and cleanup, trying to rewrite so the code uses promises instead of ccallbacks, #684 2015-10-01 21:20:52 +02:00
Torkel Ödegaard
5e34823e7c Merge pull request #2858 from mtanda/prometheus
fix unmatched tag
2015-10-01 18:47:21 +02:00
Torkel Ödegaard
f44eaae88a Merge pull request #2867 from juliusv/fix-prometheus-link
Fix "Link to Prometheus" button for proxied Prometheus sources.
2015-10-01 18:43:19 +02:00
Julius Volz
3cc69112c1 Fix "Link to Prometheus" button for proxied Prometheus sources. 2015-10-01 18:23:29 +02:00
Torkel Ödegaard
205d4232b9 feat(cloudwatch): only support proxy mode, can remove frontend aws-sdk lib 2015-10-01 17:38:55 +02:00
Torkel Ödegaard
3c6a06a327 feat(cloudwatch): moved specs into plugins dir 2015-10-01 17:00:41 +02:00
Torkel Ödegaard
057b533a39 Merge branch 'master' into cloudwatch 2015-10-01 16:46:59 +02:00
Torkel Ödegaard
b774b91b92 Merge branch 'cloudwatch' of https://github.com/mtanda/grafana into cloudwatch
Conflicts:
	public/test/specs/cloudwatch-datasource-specs.js
2015-10-01 16:41:54 +02:00
Torkel Ödegaard
7dc923a292 fix(timepicker): fixed displaying of customk time ranges, #2861 2015-10-01 16:36:38 +02:00
Torkel Ödegaard
ef2094f817 feat(influxdb): minor progress on #2802 2015-10-01 15:48:45 +02:00
Torkel Ödegaard
f0f791d226 Merge pull request #2859 from tmonk42/graphite_multiplySeries
add graphite multiplySeries function
2015-10-01 08:39:22 +02:00
Haneysmith, Nathan
7ca261e33e add graphite multiplySeries function 2015-09-30 11:18:47 -07:00
Torkel Ödegaard
228aac2d48 Merge pull request #2857 from jimmidyson/prometheus-datasource
Prometheus fixes
2015-09-30 18:33:55 +02:00
Mitsuhiro Tanda
d0b744387b fix unmatched tag 2015-10-01 01:33:13 +09:00
Jimmi Dyson
7cadb9012a Switch to png image 2015-09-30 16:45:38 +01:00
Jimmi Dyson
0c222c4e8c Fix Prometheus test connection 2015-09-30 16:44:24 +01:00
Torkel Ödegaard
83052352dc feat(influxdb editor): lots of work on new editor, #2856 2015-09-30 17:16:34 +02:00
Torkel Ödegaard
73516c0c97 Merge pull request #2856 from jimmidyson/prometheus-datasource
Further Prometheus unit tests
2015-09-30 16:59:45 +02:00
Jimmi Dyson
b90e4057ba Convert prometheus specs to typescript 2015-09-30 15:52:15 +01:00
Jimmi Dyson
67f253830f Add Prometheus metricFindQuery unit tests 2015-09-30 14:20:58 +01:00
Jimmi Dyson
59dbe45784 Fix typo in docs 2015-09-30 14:20:58 +01:00
Torkel Ödegaard
f053b41645 feat(influxdb editor): more progress 2015-09-30 14:37:27 +02:00
Torkel Ödegaard
84615ad299 Merge pull request #2855 from jimmidyson/prometheus-datasource
Prometheus docs & better template queries
2015-09-30 13:50:11 +02:00
Jimmi Dyson
2e291d73aa jshint fixes 2015-09-30 12:46:44 +01:00
Jimmi Dyson
055efa3904 Add prometheus docs 2015-09-30 12:42:48 +01:00
Jimmi Dyson
daee7970f3 Add label_values query to get labels on a particular metric 2015-09-30 12:29:53 +01:00
Torkel Ödegaard
2dc8fcd3be poc(influxdb v3 editor): more testing of new influxdb editor approach 2015-09-29 21:32:15 +02:00
Torkel Ödegaard
2f55c18d56 Merge pull request #2847 from jimmidyson/prometheus-datasource
Add Prometheus datasource
2015-09-29 18:23:12 +02:00
Jimmi Dyson
cf0748895e Prometheus template params fixes 2015-09-29 17:07:11 +01:00
Jimmi Dyson
6e66b8a0fa Add prometheus datasource 2015-09-29 17:07:11 +01:00
Torkel Ödegaard
e70b99a706 poc: influxdb editor v3 test 2015-09-29 18:00:15 +02:00
Torkel Ödegaard
42e7a70d99 Merge pull request #2845 from sbouchex/master
Missing carriage return in init script when displaying usage
2015-09-29 16:43:52 +02:00
Torkel Ödegaard
ee85eb2779 Updated prometheus config 2015-09-29 16:22:41 +02:00
sbouchex
6c1adf9187 Missing carriage return when displaying usage 2015-09-29 16:06:40 +02:00
Torkel Ödegaard
81a660eae4 fix(phantomjs binary): fixed PR #2832 so that it works on linux, now phantomjs based server side rendershould work on mac and linux, maybe even windows 2015-09-29 15:36:13 +02:00
Torkel Ödegaard
8b029388a5 Merge branch 'phantomjs' of https://github.com/fg2it/grafana into fg2it-phantomjs 2015-09-29 15:22:37 +02:00
Torkel Ödegaard
c816ed2527 feat(usage stats): added data source count stats 2015-09-29 13:47:56 +02:00
Torkel Ödegaard
866ea7f942 added prometheus docker block 2015-09-29 08:16:26 +02:00
Torkel Ödegaard
d9d3da4def Merge branch 'prometheus-datasource' of https://github.com/jimmidyson/grafana into prometheus 2015-09-29 08:06:16 +02:00
Mitsuhiro Tanda
be6cb24e10 fix cloudwatch test 2015-09-29 13:26:04 +09:00
Mitsuhiro Tanda
1a63d9eb3b reactivate cloudwatch test 2015-09-29 12:37:34 +09:00
Jimmi Dyson
bf98cfeadc Add prometheus datasource 2015-09-28 23:15:33 +01:00
Torkel Ödegaard
8a39b32b5c refactor: moved elasticsearch specs to plugin folder and to typescript 2015-09-28 16:28:19 +02:00
Torkel Ödegaard
7d2646f60a refactor: moved influxdb specs to plugins folder 2015-09-28 15:51:02 +02:00
Torkel Ödegaard
1a8bc1dd60 fix(dashbard save as): fixed wonky behavior in save as modal, cursor to the end randomly, fixes #2837 2015-09-28 15:26:56 +02:00
Torkel Ödegaard
7e434fc019 refactor: moved graphite specs into plugins directory 2015-09-28 15:23:53 +02:00
Torkel Ödegaard
49d57cf596 feat(cloudwatch): uncommented tests, but they do not seemt to execute 2015-09-28 14:16:03 +02:00
Torkel Ödegaard
8dfbc55851 Merge branch 'cloudwatch' of https://github.com/mtanda/grafana into cloudwatch 2015-09-28 13:44:30 +02:00
Torkel Ödegaard
3cbfe21b2c Merge branch 'master' into cloudwatch 2015-09-28 13:43:03 +02:00
Torkel Ödegaard
cb7424ce5e fix(playlist ui): minor polish / fix to playlist ui, fixes #2831, other minor css / markup fixes 2015-09-28 11:53:49 +02:00
Torkel Ödegaard
a567939f78 fix(elasticsearch): quote variable value in lucene variable mulit format, fixes #2828 2015-09-28 11:28:08 +02:00
Torkel Ödegaard
a3748d4b97 fix(singestat): fixed missing sparklines, caused by recent changes to time range handling, fixes #2815 2015-09-28 09:28:43 +09:00
Torkel Ödegaard
b80dc92ca5 Merge branch 'master' of github.com:grafana/grafana 2015-09-27 12:09:45 +02:00
Torkel Ödegaard
57dee76c88 feat(elasticsearch): templating terms query should have size set to zero to return all terms, fixes #2827 2015-09-27 12:09:12 +02:00
fg2it
2513639499 fix for relative path 2015-09-26 11:38:37 +02:00
Mitsuhiro Tanda
9ae6ac25f5 refactor dataproxy_cloudwatch 2015-09-26 02:33:53 +09:00
Mitsuhiro Tanda
01ec8d0bcb fix cloudwatch time error 2015-09-26 02:33:52 +09:00
Mitsuhiro Tanda
d09e8a12b4 fix jshint error 2015-09-26 02:33:52 +09:00
Mitsuhiro Tanda
5dd64b97d2 refactor 2015-09-26 02:33:52 +09:00
Mitsuhiro Tanda
9600b1f103 add ebs_volume_ids() for templating 2015-09-26 02:33:51 +09:00
Mitsuhiro Tanda
ca9861e749 fix cloudwatch config editor 2015-09-26 02:33:51 +09:00
Torkel Ödegaard
38bf07f4da Merge pull request #2817 from utkarshcmu/axes
Single stat panel throws warning on multiple series result.
2015-09-25 10:23:10 +02:00
fg2it
189796891e adding phantomjs task to default and build 2015-09-25 01:19:49 +02:00
fg2it
8134905a86 new grunt task for setting phantomjs binary 2015-09-25 00:56:56 +02:00
fg2it
45fb760a35 removing default phantomjs binary 2015-09-25 00:51:54 +02:00
ubhatnagar
026fffa19f Singlestat Panel Error in InspectCtrl. 2015-09-24 23:13:05 +05:30
Torkel Ödegaard
9da5ef3cbf fix(singestat): fixed missing sparklines, caused by recent changes to time range handling, fixes #2815 2015-09-24 11:36:25 +02:00
ubhatnagar
ea7fe0c761 Single stat panel throws warning on multiple series result. 2015-09-24 09:40:09 +05:30
Torkel Ödegaard
25d7b8d08d feat(cloudwatch): resumed work on cloudwath datasource, #684, #2445 2015-09-23 21:13:19 +02:00
Torkel Ödegaard
f467cb8cd2 Merge branch 'master' of github.com:grafana/grafana 2015-09-23 20:57:50 +02:00
Torkel Ödegaard
f4e3c0a2e4 Revert "poc: some tests for new influxdb editor"
This reverts commit 075d01820c.
2015-09-23 20:57:29 +02:00
ubhatnagar
9cdf0601eb Removed unnecessary statement. 2015-09-23 22:20:38 +05:30
ubhatnagar
28ef972c9f Added duplicate feature for variable. 2015-09-23 22:13:38 +05:30
Torkel Ödegaard
c66476f6b6 Merge pull request #2810 from tuxinaut/master
Fix changelog formatting
2015-09-23 11:42:29 +02:00
Denny Schäfer
3f90220208 Fix changelog formatting 2015-09-23 11:35:20 +02:00
Torkel Ödegaard
81ebaae12e feat(opentsdb): templating, added default match format option, now new variables with opentsdb datasource will automatically use the new pipe format, #2808 2015-09-23 11:18:56 +02:00
Torkel Ödegaard
71984b25e9 Merge pull request #2808 from utkarshcmu/master
Implemented Opentsdb MultiSelect Templating.
2015-09-23 11:16:01 +02:00
Torkel Ödegaard
4100c9881a fix(panel): fixed selecting text in fullscreen edit mode 2015-09-23 10:03:30 +02:00
Torkel Ödegaard
075d01820c poc: some tests for new influxdb editor 2015-09-23 09:58:36 +02:00
ubhatnagar
6f43cbf665 Added unit tests for all and multi format options. 2015-09-23 13:24:59 +05:30
ubhatnagar
866f48f92d Added pipe in All Format list. 2015-09-23 13:12:35 +05:30
Torkel Ödegaard
2790e4e819 change(influxdb): removed derivative functions from aggregator list 2015-09-23 09:40:53 +02:00
Torkel Ödegaard
8bb2b5e290 fix(influxdb): have alias field visible when using raw query mode, fixes #2803 2015-09-23 09:34:05 +02:00
Torkel Ödegaard
63290d0f5d changelog: added OpenTSDB enhancement fetch aggregators from OpenTSDB to the changelog, #1646 2015-09-23 09:19:21 +02:00
Torkel Ödegaard
5c55617585 refactor: polishing OpenTSDB related PR #1646, added caching of aggregators request so only one call is made 2015-09-23 09:17:37 +02:00
Torkel Ödegaard
0e0caabf7d Merge branch 'master' of https://github.com/mxk1235/grafana into mxk1235-master 2015-09-23 09:08:29 +02:00
ubhatnagar
024a319512 Implemented Opentsdb MultiSelect Templating. 2015-09-23 11:22:57 +05:30
Torkel Ödegaard
f632b3b029 feat(elasticsearch): added new templating all format and muli format named , also added automatic setting of correct all and multi format depending on data source, closes #2696 2015-09-22 14:29:41 +02:00
Torkel Ödegaard
b37f9a7db0 fix(graphite): minor fix to query editor when using summarize function with no metric segments, only series ref, fixes #2788 2015-09-22 13:32:28 +02:00
Torkel Ödegaard
6f9c306260 fix(singlestat): fixed usage of template variable in drilldown link for singlestat, fixes #2787 2015-09-22 12:48:03 +02:00
Torkel Ödegaard
c369350ca7 Merge branch 'master' of github.com:grafana/grafana 2015-09-22 09:32:13 +02:00
Torkel Ödegaard
bc3c394210 feat(elasticsearch): worked on elasticsearch templating support, #2696 2015-09-22 09:31:58 +02:00
Torkel Ödegaard
e9df31b650 Merge pull request #2799 from utkarshcmu/master
Fixed #2798. Removed unused components.
2015-09-22 08:50:00 +02:00
ubhatnagar
e49bb1ccc0 Fixed #2798. Removed unused components. 2015-09-22 12:16:10 +05:30
Torkel Ödegaard
0ef8e086a2 fixed(share modal): fixed issue with sharemodal introduced with recent change to time handling, #2791 2015-09-22 07:32:47 +02:00
Torkel Ödegaard
63665dccae fix(grafana datasource): fixed the built in test data source, fixes #2795 2015-09-22 07:12:36 +02:00
Torkel Ödegaard
792b194d0e feat(elasticsearch): finished work on adding support for filters aggregate, you can now split series by query using group by filters, closes #2785 2015-09-21 20:29:05 +02:00
Torkel Ödegaard
e694a74c9d feat(elasticsearch): work on supporting filters aggregate, #2785 2015-09-21 19:23:18 +02:00
Torkel Ödegaard
9de016bfe3 feat(elasticseach): alias and lucene query fields are now visible/usable when using raw json query, #1034 2015-09-21 12:07:03 +02:00
Torkel Ödegaard
3eddfc028e Merge branch 'patch-2' of https://github.com/cagdascirit/grafana 2015-09-21 11:32:36 +02:00
Torkel Ödegaard
2125648798 Merge branch 'master' of https://github.com/tuxinaut/grafana 2015-09-21 11:31:25 +02:00
Torkel Ödegaard
24bff6e04d ui(dashboard cog icon): minor change to PR #2772 that adds tooltip to cog icon 2015-09-21 11:29:14 +02:00
Torkel Ödegaard
cdab0d208e Merge branch 'master' of https://github.com/utkarshcmu/grafana into utkarshcmu-master 2015-09-21 11:16:50 +02:00
Torkel Ödegaard
20f04ab352 fix(build): fixed requirejs optimized build 2015-09-21 09:36:17 +02:00
Torkel Ödegaard
7f1af24318 fix(ldap): fixed ldap org roles sync, did only add one new role per login, now all roles are added, fixes #2766 2015-09-21 09:19:50 +02:00
Torkel Ödegaard
8d87db58c6 docs(): added link to external install tutorial / article 2015-09-21 09:04:59 +02:00
Torkel Ödegaard
feae4c6c8b fix(ldap): fixed syncing of email and name from ldap, fixes #2765 2015-09-21 09:02:52 +02:00
ubhatnagar
00c89b8354 Removed .jshintrc statement, implemented manage dashboard tooltip. 2015-09-21 08:09:34 +05:30
Torkel Ödegaard
fb767f5680 change: removed drilldown links from extended panel menu 2015-09-19 15:59:29 +02:00
Torkel Ödegaard
c7d22aafd2 feat(drilldown link): better access to drilldown links directly by clicking the external link icon in panel header, fixes #1575 2015-09-19 15:53:48 +02:00
Torkel Ödegaard
b5f237a69b fix(graph): minor fix for hover tooltip when combined with a single series using stepped lines, fixes #2754 2015-09-19 15:20:53 +02:00
Torkel Ödegaard
b4093ccf59 fix(graphite): minor fix to editor, add function dropdrop extended above page, fixes #1152 2015-09-19 14:58:52 +02:00
Torkel Ödegaard
a23217cc6f fix(influxdb): clear existing Authorization header when proxying request to InfluxDB, fixes #2778 2015-09-19 12:32:35 +02:00
Torkel Ödegaard
f4f7f47901 changelog: updated changelog with info about timepicker feature 2015-09-19 12:26:21 +02:00
Torkel Ödegaard
2f68687de9 feat(timepicker2): temporarily removed the the option to define custom quick range, will have to be part of future issue, closes #2761 2015-09-19 12:20:24 +02:00
Torkel Ödegaard
86f4907cc4 feat(panel fullscreen): completly changed how the fullscreen view/edit works, no longer uses css fixed position, yay, closes #2779 2015-09-19 11:40:51 +02:00
Torkel Ödegaard
bffb795d7a feat(timepicker): small style change for timepicker 2015-09-19 10:54:17 +02:00
Torkel Ödegaard
c21cffa6d4 fix(timepicker): UTC now works in all scenarios I can think of, manual edit, date picker edit, #2761 2015-09-18 21:01:13 +02:00
Torkel Ödegaard
96b0e70ddd feat(timepickerv2): fixed lots of minor issues and updated kairosdb and opentsdb data sources to work with the new date formats 2015-09-18 15:06:08 +02:00
Torkel Ödegaard
febe56b062 feat(timepicker): fixed zoomout 2015-09-18 14:04:53 +02:00
Torkel Ödegaard
f5e6722826 feat(timepickerv2): more work on new timepicker, now absolute time mixed with relative time works, yesterday, this day last week, etc now work 2015-09-18 13:54:31 +02:00
Torkel Ödegaard
cea13b1823 feat(timepicker2): moved to controllerAs and bindToController for timepicker component 2015-09-18 12:41:32 +02:00
Torkel Ödegaard
3d85e85f29 fix(events): fixed handling of onAppEvents when used from rootScope, must supply localscope, can now be used in isolate scope scenarios 2015-09-18 12:15:06 +02:00
ubhatnagar
923df8244b Search Dashboard Panel will hide if 'Manage Dashboards' is clicked. 2015-09-18 02:30:02 -07:00
Torkel Ödegaard
69db9e0d45 feat(timepickerV2): absolute time / calendar picker works, #2761 2015-09-18 11:01:37 +02:00
Denny Schäfer
d1534d4dcf Fix changelog (double issue entry and missing version headline) 2015-09-18 10:46:04 +02:00
Torkel Ödegaard
a8a94ef87b Merge branch 'master' into timepicker2 2015-09-18 10:36:50 +02:00
Torkel Ödegaard
f93215f4ec feat(timepicker2): more progress 2015-09-18 10:36:47 +02:00
ubhatnagar
9af460600d Fixed Indentation and Grunt run. 2015-09-18 00:50:55 -07:00
ubhatnagar
e1576b7131 Added Manage Dashboard Tooltip, hides when clicked. 2015-09-18 00:45:29 -07:00
Torkel Ödegaard
5d05de8bda Merge branch 'master' of github.com:grafana/grafana 2015-09-18 08:39:06 +02:00
Torkel Ödegaard
5e949b0564 fix(quota): fixed failing quota unit tests 2015-09-18 08:36:58 +02:00
Torkel Ödegaard
2a52d9bdf6 feat(timepicker2): more work on new timepicker 2015-09-18 08:17:19 +02:00
Torkel Ödegaard
ebf49d0668 Merge pull request #2771 from decbis/maxSeries-func-for-groupByNode
Added maxSeries option for groupByNode Graphite function
2015-09-18 08:16:46 +02:00
Torkel Ödegaard
f42955ab99 Merge pull request #2755 from Dieken/patch-1
fix duplicate tag value suggestions and relax limit on /api/search/lookup
2015-09-18 08:08:18 +02:00
Mike Kobyakov
5d516592d9 Merge remote-tracking branch 'upstream/master' 2015-09-17 23:07:52 -07:00
Eugen Dinca
7dc2b36413 Added maxSeries option for groupByNode function 2015-09-17 16:50:09 -04:00
Torkel Ödegaard
5eefa36111 feat(timepickerv2): big progress on new design of new timepicker, #2761 2015-09-17 22:44:59 +02:00
Cagdas Cirit
c26209d579 Update debian.md 2015-09-17 18:25:36 +02:00
Torkel Ödegaard
a30f73fe36 feat(timepicker): more work on getting new time formats to work in all data sources 2015-09-17 12:40:04 +02:00
Torkel Ödegaard
1a9c52e17f feat(timepicker): lots of big changes, moving to datemath from kbn.parseDateMath, moving to moment dates instead of native javascript dates 2015-09-17 11:21:38 +02:00
Torkel Ödegaard
5ad38ee9f8 feat(timepicker2): fixed timesrv specs 2015-09-17 09:57:59 +02:00
Torkel Ödegaard
4c79591403 fix(graphite): removed debug comment 2015-09-17 09:45:53 +02:00
Torkel Ödegaard
a9812167d7 feat(timepicker2): worked on more rich time range support 2015-09-17 09:44:51 +02:00
raj dutt
a7cc36f741 Update CHANGELOG.md 2015-09-16 23:50:02 -04:00
raj dutt
4c0262cbd0 Update CHANGELOG.md 2015-09-16 23:46:48 -04:00
Torkel Ödegaard
a0a98cb035 feat(timepicker2): working on richer timepicker options 2015-09-16 19:49:05 +02:00
Liu Yubao
4fccfbf543 increase limit to lookup unique metric tag values
Default limit is 25 which is too small. Considering currently the
/api/search/lookup query isn't narrowed down by selected tag
keys and values (see https://github.com/grafana/grafana/pull/1433), 
the limit is set to 3000, this should be fine because people rarely
create new graph panel.
2015-09-17 01:36:36 +08:00
Liu Yubao
62ae80eeae deduplicate tag value suggestions for OpenTSDB 2015-09-17 01:25:28 +08:00
Torkel Ödegaard
d705108be5 feat(timepicker2): added date math tests 2015-09-16 18:48:41 +02:00
Torkel Ödegaard
3912eb7b26 Merge branch 'master' into timepicker2 2015-09-16 17:03:08 +02:00
Torkel Ödegaard
1f959272c5 feat(migration): added back support to import old dashboard from from Elasticsearch 2015-09-16 16:28:41 +02:00
Torkel Ödegaard
f9cd942363 Merge branch 'master' of github.com:grafana/grafana 2015-09-16 15:17:57 +02:00
Torkel Ödegaard
1282da52eb Merge pull request #2750 from elliot/patch-1
Fixed 404 error for robots.txt
2015-09-16 12:09:56 +02:00
Elliot Anderson
10c099a52d Fixed 404 error for robots.txt 2015-09-16 11:02:50 +10:00
Torkel Ödegaard
fa0542ca8b Merge branch 'quotas' of https://github.com/raintank/grafana into raintank-quotas 2015-09-15 14:33:21 +02:00
woodsaj
3926226417 fix getting default quota as map[string]int64 2015-09-15 20:31:58 +08:00
Torkel Ödegaard
6a30511fc4 Merge branch 'quotas' of https://github.com/raintank/grafana into raintank-quotas 2015-09-15 14:23:13 +02:00
Torkel Ödegaard
4ffa26cf2c feat() started work on more feature rich time picker 2015-09-15 13:23:36 +02:00
woodsaj
1ad10914ce add quota middleware unittests 2015-09-15 18:19:47 +08:00
woodsaj
86ed85aa6e move toMap function to be a method on the quota structs 2015-09-15 17:18:26 +08:00
woodsaj
b7de847236 add unittests for quota sqltore methods. 2015-09-15 17:10:46 +08:00
woodsaj
3cf2cd4684 be sure to pass result obj by reference to xorm. 2015-09-15 17:10:16 +08:00
Torkel Ödegaard
2b95cd5081 refactor: moving routes into core, improved bundle loader 2015-09-15 08:53:06 +02:00
Torkel Ödegaard
8f45324bce fix(): fixed requirejs build 2015-09-15 08:20:26 +02:00
Torkel Ödegaard
5545cdbf4d refactor: improving structure, moving things into a core module 2015-09-15 08:17:40 +02:00
Torkel Ödegaard
9dec50832d refactor: refactoring app boot up and core structure 2015-09-14 22:54:00 +02:00
Torkel Ödegaard
64973f1d57 fix(settings): another attempt at fixing, #2736 2015-09-14 15:54:35 +02:00
Torkel Ödegaard
d7bfb727b0 fix(settings): reverted prev settings fix for detecting public_gen folder in dev, caused issue for prod build 2015-09-14 13:42:23 +02:00
Torkel Ödegaard
37ad58c69e fix(gofmt): somehow api.go did not pass gofmt test 2015-09-14 12:30:30 +02:00
Torkel Ödegaard
de753bf330 fix(build): fixed issue with ngAnnotate not including files in core/*, fixes #2733 2015-09-14 09:33:26 +02:00
Torkel Ödegaard
d17f8538b2 fix(backend): made public_gen detection more bullet proof, #2731 2015-09-13 15:23:23 +02:00
Torkel Ödegaard
1900c81d9f tech(nodejs upgrade): upgraded to nodejs 4.0 for grunt build 2015-09-13 12:13:47 +02:00
Torkel Ödegaard
9db6f82628 refactor: finished timepicker to typescript and directive refactor 2015-09-12 12:52:50 +02:00
Torkel Ödegaard
d96a6a59ee refactor: moved timepicker from a simple panel to component, removed simple panel directive 2015-09-12 11:49:27 +02:00
Torkel Ödegaard
7535677ed4 fix(build): fixed failing tslint test 2015-09-11 21:04:02 +02:00
woodsaj
6488324cf1 enhance quota support.
now includes:
- perOrg (users, dashboards, datasources, api_keys)
- perUser (orgs)
- global (users, orgs, dashboards, datasources, api_keys, sessions)
2015-09-11 23:17:10 +08:00
Torkel Ödegaard
812e4c7cd4 refactor: moved array join directive to typecrtipt 2015-09-11 10:54:56 +02:00
Torkel Ödegaard
85baae1ebd feat(influxdb): added back fill option to editor, forgot to add it in the new updated query editor 2015-09-11 10:42:50 +02:00
Torkel Ödegaard
8174b9f041 fix(tests): fixed failling backend test 2015-09-11 08:58:45 +02:00
Torkel Ödegaard
fb9e91e486 Merge branch 'master' of github.com:grafana/grafana 2015-09-11 08:13:49 +02:00
Torkel Ödegaard
1429737a60 tech(typescript): added not about typescript to changelog 2015-09-11 08:11:17 +02:00
Torkel Ödegaard
286b4c0e46 Merge branch 'typescript' 2015-09-11 08:01:11 +02:00
Torkel Ödegaard
166a3c4d64 tech(typescript): added tslint to default task 2015-09-11 08:00:13 +02:00
Torkel Ödegaard
d4a701aad0 tech(typescript): more work on typescript 2015-09-11 07:47:00 +02:00
woodsaj
47bf1bd21a return 404 when quotas not enabled. 2015-09-11 01:51:12 +08:00
woodsaj
852f9bd277 refactor quota settings 2015-09-11 01:47:33 +08:00
woodsaj
555cbeffa5 allow all users to retrieve org and quota data. 2015-09-11 01:18:36 +08:00
woodsaj
3d4d822528 implement updateQuota function 2015-09-11 01:04:29 +08:00
woodsaj
c238130842 quote table names passed by arguments 2015-09-11 01:04:22 +08:00
woodsaj
76e9ebde36 always return after errors. 2015-09-11 01:03:58 +08:00
woodsaj
0688050552 add quota middleware to enforce quotas. issue #321
Conflicts:
	pkg/api/api.go
2015-09-11 01:03:47 +08:00
woodsaj
9023171940 inital backend suport for quotas. issue #321
Conflicts:
	conf/defaults.ini
	main.go
	pkg/services/sqlstore/migrations/migrations.go
2015-09-11 01:01:36 +08:00
Torkel Ödegaard
84371f03b5 tech(typescript): more testing and migration 2015-09-10 16:47:38 +02:00
Torkel Ödegaard
20407bca89 tech(typescript): converted signup controller to typescript 2015-09-10 16:21:57 +02:00
Torkel Ödegaard
96af2debfc Merge pull request #2717 from sileht/sileht/dataproxy_test-bug
Fix dataproxy_test.go tests
2015-09-10 15:51:02 +02:00
Mehdi Abaakouk
6e532231dc Fix dataproxy_test.go tests
This change fix dataproxy_test.go tests that was failing with:

pkg/api/dataproxy_test.go:17: not enough arguments in call to NewReverseProxy
pkg/api/dataproxy_test.go:39: not enough arguments in call to NewReverseProxy
FAIL	_/home/ubuntu/grafana/pkg/api [build failed]
2015-09-10 13:27:49 +00:00
Torkel Ödegaard
dceec44671 removed tsconfig 2015-09-10 14:36:05 +02:00
Torkel Ödegaard
0b5f40e66c tech(): made config system check for generated css or javascript files and panic if there are none, also if there is a public_gen directory it will use that, even if static root is set to public 2015-09-10 13:34:32 +02:00
Torkel Ödegaard
da832368f0 dev building and optimized builds work 2015-09-10 12:42:24 +02:00
Torkel Ödegaard
abac8bccc6 tech(typescript): its looking good 2015-09-10 11:26:40 +02:00
Torkel Ödegaard
82061c7c3b experiments 2015-09-09 20:37:27 +02:00
Torkel Ödegaard
ec3a684be2 Merge pull request #2711 from jsternberg/master
Update docstring for postgres session provider
2015-09-09 20:17:23 +02:00
Jonathan A. Sternberg
5572588c54 Update docstring for postgres session provider
The postgres provider is named postgres and not postgresql. For somebody
configuring the server from the config file example, it is very easy to
write an invalid value into the file and accidentally use the "memory"
provider instead because of a typo.
2015-09-09 14:06:14 -04:00
Torkel Ödegaard
a8197df1c1 more experiments for mixing javascript and typescript 2015-09-09 19:34:24 +02:00
Torkel Ödegaard
005e1e002b more playing around with typescript 2015-09-09 17:40:52 +02:00
Torkel Ödegaard
9603dce469 feat(dataproxy): added whitelist setting and feature for data proxies, closes #2626 2015-09-09 17:21:25 +02:00
Torkel Ödegaard
13190f6f15 fixed version in changelog 2015-09-09 14:25:59 +02:00
Torkel Ödegaard
eaa061d90c cleanup(): removed old influxdb editor partial 2015-09-09 14:18:56 +02:00
Torkel Ödegaard
1ab374008f feat(influxdb): completed work on new influxdb query editor, now supports #2708, #2647, #2599 2015-09-09 14:17:55 +02:00
Torkel Ödegaard
8c9d551826 feat(influxdb): you can now change group by time iunterval on per query basis, #2647 2015-09-09 13:48:57 +02:00
Torkel Ödegaard
c4c3f9dc1f feat(influxdb): more work on changing the influxdb editor to support better aliasing and interval options, #2647, #2599 2015-09-09 13:39:45 +02:00
Torkel Ödegaard
5b722deb39 feat(influxdb): began work on changing the influxdb editor to support better aliasing and interval options, #2647, #2599 2015-09-09 11:59:02 +02:00
Torkel Ödegaard
b61b7f0c3b fix(influxdb): fixed editor bug introduced in recent commit 2015-09-09 11:05:49 +02:00
Torkel Ödegaard
3c40310e9b tech(typescript): testing for how to migrate to typescript 2015-09-09 09:57:06 +02:00
Torkel Ödegaard
7a167742d0 Merge pull request #2701 from brunoqc/patch-1
Fix typo
2015-09-08 22:31:58 +02:00
Bruno Bigras
d2a798eb3d Fix typo 2015-09-08 16:16:51 -04:00
Torkel Ödegaard
3e9e34afb8 feat(annotations): polished up the annotation editor tabs, similar to recent commits for templating editor 2015-09-08 16:59:39 +02:00
Torkel Ödegaard
26d8a041ef fix(templating): updated templating UI and tab behavior, now edit tab is not visible when not actually editing an existing var, fixes #2679 2015-09-08 15:54:08 +02:00
Torkel Ödegaard
7c25edc7b2 docs(): fixed iframe snapshot links in docs, fixes #2682 2015-09-08 15:24:20 +02:00
Torkel Ödegaard
2725053c82 docs(): fixed iframe snapshot links in docs, fixes #2682 2015-09-08 15:19:59 +02:00
Torkel Ödegaard
e2cb66f8d7 fix(logging): removed temp dev logging code 2015-09-08 14:32:25 +02:00
Torkel Ödegaard
fad1d4cf98 feat(organization): added update org address to http api and to org details settings view, closes #2672 2015-09-08 14:22:44 +02:00
Torkel Ödegaard
daf64421f2 fix(api): Added error handling to create and update org http apis and sql update handlers, now checks for org name taken scenarios and returns correct http error code and message, fixes #2686 2015-09-08 13:06:18 +02:00
Torkel Ödegaard
fa3329271d fix(email notifications): added error handling to email template parsing, fixes #2690 2015-09-08 10:57:47 +02:00
Torkel Ödegaard
fdcb4473af fix(api auth): return 401 for authentication errors and 403 for access denied errors, fixes #2693 2015-09-08 10:46:31 +02:00
Torkel Ödegaard
41154d6d11 fix(elasticsearch): fixed series naming & aliasing when using field for extended_stats and percentiles 2015-09-08 10:08:27 +02:00
Torkel Ödegaard
9904d01958 fix(unit test): trying to fix failing unit test due to timezone different on build server 2015-09-08 09:50:50 +02:00
Torkel Ödegaard
171ed497f9 version bumped to 2.5 2015-09-08 09:20:46 +02:00
Torkel Ödegaard
212a8ad6a6 Merge branch 'master' of github.com:grafana/grafana 2015-09-08 09:18:03 +02:00
Torkel Ödegaard
f2c518ba24 Merge branch 'elastic_ds'
Conflicts:
	public/app/plugins/datasource/influxdb/queryCtrl.js
2015-09-08 09:17:34 +02:00
Torkel Ödegaard
f8b61a4ebe changelog(): added #1034 to changelog 2015-09-08 09:16:41 +02:00
Torkel Ödegaard
35cc3837a0 feat(elasticsearch): more work on alias pattern, #1034 2015-09-08 09:10:26 +02:00
Torkel Ödegaard
572a80d1d1 feat(elasticsearch): metric response handling and processsing now supports alias patterns, {{term field name}} and {{metric}} now works, #1034 2015-09-07 23:15:49 +02:00
Torkel Ödegaard
2aa695fb66 feat(elasticsearch): refactoring elasticsearch response handling to support series alias patterns 2015-09-07 20:00:27 +02:00
Torkel Ödegaard
f361f324da feat(elasticsearch): more polish to editor, made interval configurable per query, #1034 2015-09-07 16:35:40 +02:00
Torkel Ödegaard
3999a3caa2 feat(elasticsearch): extended stats like std deviation now works, and sigma option as well, added unique count (cardinality as well, #1034 2015-09-07 13:13:27 +02:00
Torkel Ödegaard
efc3def7f2 feat(elasticsearch): small refactoring and polish 2015-09-07 09:36:56 +02:00
Torkel Ödegaard
6c304924f7 feat(elastic_ds): moving time field name to dataasource option, it is no longer specified for each query and date_histogram 2015-09-07 08:57:46 +02:00
Torkel Ödegaard
f90714f8fe feat(elasticsearch): changed default sort to asc 2015-09-06 16:11:01 +02:00
Torkel Ödegaard
0960360b35 feat(elasticsearch): added support for index time based patterns, #1034 2015-09-06 16:09:42 +02:00
Torkel Ödegaard
8db47e335f fixed jshint errors 2015-09-06 14:45:12 +02:00
Torkel Ödegaard
c609f67e16 fixed broken editors because of some recent refactorings 2015-09-06 14:29:28 +02:00
Torkel Ödegaard
14cb2b0143 began work on support index time patterns 2015-09-06 12:58:53 +02:00
Torkel Ödegaard
b24c539206 feat(elasticsearch): began work on supporting extended stats metric agg, it gives you standard deviation and more 2015-09-05 20:22:54 +02:00
Torkel Ödegaard
52eeefa6d9 feat(elasticsearch): fields are fetch from mapping instead of docs, you can enter custom value in field options, other fixes, #1034 2015-09-05 19:55:58 +02:00
Torkel Ödegaard
f942ec952e feat(elasticsearch): worked on percentiles metric aggregator in editor and in elasticsearch response processing 2015-09-05 18:31:42 +02:00
Torkel Ödegaard
3e9aca3ed4 feat(elasticsearch): terms aggregation options are working, things are starting to come together, #1034 2015-09-05 15:41:04 +02:00
Torkel Ödegaard
2d832e10b0 feat(elasticsearch): term metric filters are starting to work! like terms aggregation with top 5, order by metric 1 desc, where metric 1 is maybe average of @load 2015-09-05 12:51:05 +02:00
Torkel Ödegaard
f1e995ec79 feat(elasticsearch): added bucket agg id concept 2015-09-05 12:24:14 +02:00
Torkel Ödegaard
756ec8ccd7 feat(elasticsearch): close to getting group by term options ui working 2015-09-05 10:48:11 +02:00
Torkel Ödegaard
c48f24d269 feat(editor): more work on editor components, extracing things and making reusable directives 2015-09-05 10:14:21 +02:00
Torkel Ödegaard
0d2e13549a feat(editor): thing are starting to work again 2015-09-05 09:05:09 +02:00
Torkel Ödegaard
f9ce9bdcec feat(editor): refactoring and making new editor abstractions 2015-09-05 08:07:40 +02:00
Torkel Ödegaard
e339dbf473 feat(elasticsearch): so much work on new editor, its pretty broken right now, but when it is done it is going to be amazing 2015-09-04 22:10:56 +02:00
Torkel Ödegaard
f27f028d44 Merge pull request #2676 from jd/master
doc: fix link to basic concepts
2015-09-04 17:34:01 +02:00
Julien Danjou
697aaf7e70 partials: fix closing markup in datasourceHttpConfig 2015-09-04 17:14:27 +02:00
Torkel Ödegaard
cc1e3d0101 feat(elasticsearch): groundwork for a much more sophisticated elasticsearch query editor 2015-09-04 16:05:47 +02:00
Torkel Ödegaard
9daa3997e9 feat(elasticsearch): time field selector now works, #1034 2015-09-04 11:17:52 +02:00
Torkel Ödegaard
83930ec9d1 feat(elasticsearch): raw queries work, more unit tests and polish, #1034 2015-09-04 09:41:23 +02:00
Torkel Ödegaard
b83a1bf4cc Merge pull request #2673 from rodo/add_doc_app_mode
Add comments on app_mode with possible values
2015-09-04 09:25:44 +02:00
Rodolphe Quiédeville
668b47cc6e Add comments on app_mode with possible values 2015-09-04 09:01:06 +02:00
Torkel Ödegaard
f29471521c Merge pull request #2663 from victorhooi/patch-1
Add note about phantomjs binary and PNG rendering.
2015-09-04 08:40:53 +02:00
Torkel Ödegaard
5beced458c fix(): fixed problems in last commit 2015-09-04 07:41:50 +02:00
Torkel Ödegaard
ead451a979 feat(influxdb): More alias options, can now use syntax to reference part of a measurement name (seperated by dots), closes #2599 2015-09-04 07:40:28 +02:00
Torkel Ödegaard
97d42991a7 fix(graph tooltip): fixed graph tooltip when stacking and one series is not stacked (via override), fixes #2670 2015-09-03 21:18:05 +02:00
Torkel Ödegaard
cd6bdc1a78 fix(influxdb): fixed influxdb template var filter suggestion, fixes #2666 2015-09-03 20:53:20 +02:00
Torkel Ödegaard
977f538420 feat(elasticsearch): lots of work on elasticsearch metrics query editor, #1034 2015-09-03 16:35:11 +02:00
Torkel Ödegaard
590b155c6c feat(elasticsearch): lots of work on elasticsearch metrics query editor, #1034 2015-09-03 15:56:41 +02:00
Torkel Ödegaard
7e9f11ea1c feat(elasticsearch): lots of work on elasticsearch metrics query editor, #1034 2015-09-03 14:55:48 +02:00
Torkel Ödegaard
64cee145e0 Merge pull request #2669 from jd/master
doc: fix link to basic concepts
2015-09-03 14:03:04 +02:00
Torkel Ödegaard
525724cc1f feat(elasticsearch): lots of work on elasticsearch metrics query editor, #1034 2015-09-03 14:02:31 +02:00
Julien Danjou
63f9dc826f doc: fix link to basic concepts 2015-09-03 14:01:32 +02:00
Torkel Ödegaard
b3bda02063 feat(elasticsearch): lots of work on elasticsearch metrics processing, handling grouped responses, etc, #1034 2015-09-03 12:35:21 +02:00
Torkel Ödegaard
df1d56e7b1 feat(elasticsearch): lots of work on elasticsearch metrics processing, handling grouped responses, etc, #1034 2015-09-03 11:14:25 +02:00
Victor Hooi
b80adcc00a Add note about phantomjs libraries. 2015-09-03 16:35:49 +10:00
Victor Hooi
68a7c3fa3b Add note about phantomjs binary and PNG rendering. 2015-09-03 16:29:58 +10:00
Torkel Ödegaard
d099d8950f feat(elasticsearch): lots of work on elasticsearch metrics queries, #1034 2015-09-03 08:18:00 +02:00
Torkel Ödegaard
ae7093e0bb feat(elasticsearch): small progress on new elasticsearch metric query capability 2015-09-02 17:45:41 +02:00
Torkel Ödegaard
d932a877e8 fix(influxdb): removed limit of 20 for influxdb field dropdown, fixes #2655 2015-09-02 16:10:58 +02:00
Torkel Ödegaard
745a7b4461 fix(influxdb): Fixed issue when using the eye to disable queries in the query editor and when applying aliases, #2651 2015-09-02 16:06:28 +02:00
Torkel Ödegaard
9e1a9723c2 Merge branch 'cloudwatch' into elastic_ds 2015-09-02 15:25:45 +02:00
Torkel Ödegaard
e48754c73c feat(elasticsearch): began work on elasticsearch datasource, based on work in pr #2293, will need a lot more work 2015-09-02 15:25:40 +02:00
Torkel Ödegaard
096a554bfb Merge branch 'elasticsearch-datasource' of https://github.com/aheinz-sg/grafana into elastic_ds 2015-09-02 15:12:26 +02:00
Torkel Ödegaard
74da5a610c fix(spelling): capitalize text 2015-09-02 13:20:19 +02:00
Torkel Ödegaard
ea7c6edcd0 feat(cloudwatch): lots of refactoring and polish work on cloudwatch query editor 2015-09-02 12:40:08 +02:00
Torkel Ödegaard
27d5f02329 refactoring(query editors): broke out metric segment 2015-09-02 11:33:32 +02:00
Torkel Ödegaard
d56e7787f2 Merge pull request #2637 from matschaffer/env-var-output-fix
Iterate over the right env override list variable
2015-09-02 11:32:49 +02:00
Torkel Ödegaard
6d7c8431be fix(inspector): lots of improvements and fixes for the error inspector, now shows you request details and responses in many more cases, fixes #2646 2015-09-02 10:35:15 +02:00
Torkel Ödegaard
3ed63d09d9 Merge branch 'master' into cloudwatch 2015-09-01 17:25:36 +02:00
Torkel Ödegaard
4b4299604b fix(import): fixed nav link in header, fixes #2633 2015-09-01 17:14:48 +02:00
Torkel Ödegaard
3842bcb921 fix(influxdb): quote field name, fixes #2629 2015-09-01 14:49:42 +02:00
Torkel Ödegaard
f9b98767e7 docs(reference): removed link to non existant text panel page, fixes #2632 2015-09-01 13:07:42 +02:00
Torkel Ödegaard
6989c6332d fix(influxdb): fixed issue in last commit 2015-09-01 13:04:59 +02:00
Torkel Ödegaard
209e9ebda7 fix(influxdb): fixes and refactorings of influxdb 0.9 editor, no longer shows template vars in keys dropdown and group by dropdownm, fixes #2636 2015-09-01 13:01:21 +02:00
Torkel Ödegaard
e27e9dc2aa Merge pull request #2642 from bfontaine/patch-1
influxdb doc: typo fixed
2015-09-01 12:59:19 +02:00
Baptiste Fontaine
28474d9340 influxdb doc: typo fixed 2015-09-01 12:47:26 +02:00
Torkel Ödegaard
aa89416bca fix(invite): fixes to org invite stuff, #2630 2015-09-01 12:35:06 +02:00
Mat Schaffer
6c04ee1abd Iterate over the right env override list variable 2015-09-01 11:11:54 +09:00
Torkel Ödegaard
e93fba206d fix(kariosdb): fixed how kairosdb makes datasource requests, fixes #2628 2015-08-31 19:13:45 +02:00
Torkel Ödegaard
822a689b82 datasource(cloudwatch): began on polishing cloudwatch datasource, #684, #2445 2015-08-31 19:07:25 +02:00
Torkel Ödegaard
ffbf70af25 Merge branch 'cloudwatch' of https://github.com/mtanda/grafana into cloudwatch 2015-08-31 15:45:49 +02:00
Adam Heinz
d3e307b102 Refactor post-rebase to configure directives. 2015-08-31 09:32:47 -04:00
Adam Heinz
a3e4abfd5e Added group by to Elasticsearch data source. 2015-08-31 09:04:56 -04:00
Adam Heinz
56d1411253 Replace deprecated facets with aggregations. 2015-08-31 08:53:49 -04:00
Adam Heinz
7bccd17bbe Enable 'Test Connection' for Elasticsearch datasource. 2015-08-31 08:53:49 -04:00
Adam Heinz
c193208cd2 Rewrite query builder to allow for multiple time series. 2015-08-31 08:53:49 -04:00
Joseph Jones
923f9345a7 add elasticsearch query fields to the es query editor 2015-08-31 08:53:49 -04:00
Adam Heinz
d618526037 Copy/paste influxdb query editor. 2015-08-31 08:53:49 -04:00
Adam Heinz
a1dcd5f069 Initial prototype returning time series from (partially hardcoded) Elasticsearch data source. 2015-08-31 08:53:29 -04:00
Torkel Ödegaard
3dfa28570f ui(timepicker): slight polish to the time picker options view 2015-08-31 14:12:10 +02:00
Torkel Ödegaard
d78c9fa2d2 feat(signup): updated changelog with details about #2353 2015-08-31 11:47:02 +02:00
Torkel Ödegaard
6fac241404 Merge branch 'signup_remake' 2015-08-31 11:42:30 +02:00
Torkel Ödegaard
99bb9d4fcf feat(signup): added back the welcome on signup completed email 2015-08-31 11:42:12 +02:00
Torkel Ödegaard
d19e101e6b feat(signup): almost done with new sign up flow, #2353 2015-08-31 11:35:07 +02:00
Torkel Ödegaard
13c70ac02c feat(signup): selecting org after invite now works 2015-08-31 09:37:14 +02:00
Mitsuhiro Tanda
3405e44a1d add legend format tip 2015-08-31 16:03:39 +09:00
Mitsuhiro Tanda
8c5a28c0b8 improve dollar escape 2015-08-31 16:03:28 +09:00
Mitsuhiro Tanda
7229c59b8e escape {} in tip 2015-08-31 16:02:59 +09:00
Torkel Ödegaard
14884d5a2b feat(signup): progress on new signup flow, #2353 2015-08-30 18:56:53 +02:00
Torkel Ödegaard
688ed405df fix(graph tooltip): multi series tooltip did no highlight correct point when stacking was enabled and series were of different resolution, fixes #2620 2015-08-30 11:11:43 +02:00
Torkel Ödegaard
d3c79c9b49 fix(datasource query editors): fixed issue with duplicate query and the query letter (refId) 2015-08-30 10:24:15 +02:00
Torkel Ödegaard
de0f04ec3c feat(signup): progress on new sign up and email verification flow, #2353 2015-08-28 15:14:24 +02:00
Torkel Ödegaard
c61b22cefb feat(signup): progress on new sign up and email verification flow, #2353 2015-08-28 13:45:16 +02:00
Torkel Ödegaard
24dfa55465 feat(signup): progress on new sign up and email verification flow, #2353 2015-08-28 09:24:30 +02:00
Torkel Ödegaard
d25624a8ad feat(signup): began work on new / alternate signup flow that includes email verification, #2353 2015-08-27 13:59:58 +02:00
Torkel Ödegaard
41f1e5f7c9 Merge pull request #2605 from matschaffer/patch-1
Use SPDB license id
2015-08-27 08:49:52 +02:00
Mat Schaffer
be3f657073 Use SPDB license id
This avoids an npm warning telling you to use such an id.
2015-08-27 07:35:59 +09:00
Torkel Ödegaard
7e44a8ed1d feat(tagmanager): added Name variable to datalayer 2015-08-26 15:07:39 +02:00
Torkel Ödegaard
874503ca68 fix(ngblur): removed ngblur directive as it conflicted with angular naitive directive, fixes #2429 2015-08-26 13:05:33 +02:00
Torkel Ödegaard
29c833d623 Merge pull request #2598 from robison/master
updating the limit of returned tagvs in a MetricKeyLookup
2015-08-26 09:48:44 +02:00
robbie
46c0215b1f updating the limit of returned tagvs in a MetricKeyLookup since opentsdb supports limit in 2.1.0 2015-08-25 12:46:54 -07:00
Mitsuhiro Tanda
7c6e49ec65 fix too much CloudWatch query 2015-08-26 00:31:59 +09:00
Torkel Ödegaard
9a142cea7a fix(panel links): fixed panel height issue when using panel links, could cause strange panel flow, fixes #2576 2015-08-25 15:49:46 +02:00
Torkel Ödegaard
7b911aea46 fix(shutdown flow): improved shutdown flow and log closing, listing to kill and and SIGTERM as well, closes #2516 2015-08-25 15:22:24 +02:00
Torkel Ödegaard
af95daadf4 fix(jscs): fixed failing js style check 2015-08-25 09:39:42 +02:00
Torkel Ödegaard
6e8d5cd873 fix(opentsdb): blur event triggered twice for metric selector, caused double query to opentsdb after metric name change 2015-08-25 09:34:43 +02:00
Torkel Ödegaard
48686cf9f7 fix(influxdb_09): fix for handling empty series object in response from influxdb, fixes #2413 2015-08-25 09:11:39 +02:00
Mitsuhiro Tanda
b7fc3059b6 add legend format 2015-08-25 15:51:32 +09:00
Mitsuhiro Tanda
64ce5e0fad add tooltip 2015-08-25 13:35:27 +09:00
Mitsuhiro Tanda
d815d06c1c ignore empty custom metrics setting 2015-08-24 22:13:53 +09:00
Mitsuhiro Tanda
c0c8465ec2 add loading custom metrics definitions 2015-08-24 22:06:45 +09:00
Torkel Ödegaard
364d9de751 feat(ui viewer): prevent viewers from creating new dashboard or importing dashboard, closes #2590 2015-08-24 14:24:10 +02:00
Torkel Ödegaard
ca5e8c73d7 logging(ldap): added more logging to bind failures, #2588 2015-08-24 11:47:22 +02:00
Torkel Ödegaard
3e0c66edab docs(): updated download links in docs 2015-08-24 10:05:05 +02:00
Mitsuhiro Tanda
e74be5887c don't require cloudwatch dimension 2015-08-24 11:38:52 +09:00
Mitsuhiro Tanda
253d0c834c change required option by access mode 2015-08-23 14:54:08 +09:00
Mitsuhiro Tanda
feb19adb8f fix checkbox 2015-08-23 14:53:58 +09:00
Torkel Ödegaard
5d69c69b7c Merge pull request #2586 from thuck/link_typo
Fix small typo "dashbord" to dashboard
2015-08-22 14:48:13 +02:00
Denis Doria
06077faa6a Fix small typo "dashbord" to dashboard 2015-08-22 11:48:35 +02:00
Torkel Ödegaard
fcc369e854 Merge branch 'v2.1.x'
Conflicts:
	CHANGELOG.md
2015-08-22 08:44:47 +02:00
Torkel Ödegaard
ea187961da fix(templating): Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url), fixes #2564
Conflicts:
	CHANGELOG.md
2015-08-22 08:43:43 +02:00
Torkel Ödegaard
c9b2cec5ff fix(packaging): deb & rpm package did not mark ldap.toml config file as a configuration file, 2.1.1 & 2.1.2 upgrade overwrote it :(, fixes #2580 2015-08-22 08:34:20 +02:00
Torkel Ödegaard
960a4f71a3 fix(jsc): fixed javascript indenting 2015-08-21 16:28:01 +02:00
Torkel Ödegaard
292db86c9e feat(relative time override): You can now use the new relative time option in panel time override, closes #2575 2015-08-21 16:19:51 +02:00
Torkel Ödegaard
6d3b36d61b fix(annotations): removed accidental test code from annotationsSrv 2015-08-21 15:32:20 +02:00
Torkel Ödegaard
c876aa537a feat(panel resize): Resize handles in panel bottom right corners for easy width and height change, closes #2577
Conflicts:
	public/app/features/panel/panelDirective.js
2015-08-21 14:59:15 +02:00
Mitsuhiro Tanda
f1e5238e16 escape dimension if it is variable name 2015-08-21 20:15:45 +09:00
Mitsuhiro Tanda
d75d4a5c08 filter empty dimension 2015-08-21 18:29:38 +09:00
Mitsuhiro Tanda
266fe876ba pass dimension in metricFindQuery 2015-08-21 18:29:32 +09:00
Torkel Ödegaard
ca53ae4fc5 fix(templating): Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url), fixes #2564 2015-08-21 10:40:14 +02:00
Torkel Ödegaard
8f35683ccb fix(annotations): Fixed issue when html sanitizer failes for title to annotation body, now fallbacks to html escaping title and text, fixes #2563 2015-08-21 10:17:02 +02:00
Torkel Ödegaard
30cd782e92 fix(snapshot): Fix for snapshot with expire 7 days option, 7 days option not correct, was 7 hours, fixes #2574 2015-08-21 09:51:16 +02:00
Torkel Ödegaard
3d37c9c9a3 feat(tagmanager): support to add google tagmanager id, closes #2569 2015-08-21 09:30:39 +02:00
Torkel Ödegaard
7072af7c14 fix(auth proxy): Fix for server side rendering of panel when using auth proxy, fixes #2568 2015-08-21 07:49:49 +02:00
Mitsuhiro Tanda
c72bddf15e add cloudwatch datasource test 2015-08-21 12:55:18 +09:00
Haneysmith, Nathan
74ea266157 add login hint to defaults.ini 2015-08-20 11:20:40 -07:00
Haneysmith, Nathan
235bbc9c7e custom login hints via config file 2015-08-20 11:15:36 -07:00
Torkel Ödegaard
abd7c15ba8 fix(TimePicker): Fix for when you applied custom time range it did not refreh dashboard, fixes #2565 2015-08-20 17:57:15 +02:00
Torkel Ödegaard
a4ff1e4f33 docs(): updated version in install docs 2015-08-20 15:20:26 +02:00
Torkel Ödegaard
5b9ea96dc5 Merge branch 'v2.1.x'
Conflicts:
	CHANGELOG.md
	package.json
2015-08-20 11:16:05 +02:00
Torkel Ödegaard
e86c87f7bc updated version to v2.1.2 2015-08-20 11:12:32 +02:00
Torkel Ödegaard
e5794ed1c1 fix(dragdrop): Fix for broken drag drop behavior, fixes #2558 2015-08-20 11:12:06 +02:00
Mitsuhiro Tanda
269441fe3d add aws-sdk 2015-08-20 00:51:30 +09:00
Mitsuhiro Tanda
00f76ecaf6 CloudWatch proxy support 2015-08-20 00:51:23 +09:00
Torkel Ödegaard
952a69abca docs(tutorial): updated hubot tutorial 2015-08-19 13:53:15 +02:00
Torkel Ödegaard
aa21bfd8a8 fix(timepicker): fixed for viewing auto refresh submenu when timpicker selection is set to Today, fixes #2552 2015-08-19 13:25:36 +02:00
Torkel Ödegaard
5768762591 Merge pull request #2539 from papylhomme/master
Disable load timeout in requirejs
2015-08-18 20:23:47 +02:00
Torkel Ödegaard
ca9c11486e config(github oauth): removed allowed_domains options, closes #1986 2015-08-18 20:19:37 +02:00
Torkel Ödegaard
01d9849e44 fix(user create): fixed for creating multiple users with empty email when auto assign org is set to false, fixes #2011 2015-08-18 20:15:24 +02:00
Torkel Ödegaard
0339026674 fix(default datasource): minor fix for handling of default datasource 2015-08-18 20:04:24 +02:00
Trent White
2f5115ca45 Update hubot_howto.md 2015-08-18 09:37:51 -04:00
Trent White
66878a9b48 Update hubot_howto.md 2015-08-18 09:34:31 -04:00
Trent White
333952a231 Update hubot_howto.md 2015-08-18 09:34:08 -04:00
Torkel Ödegaard
50281f12b9 fix(default datasource): minor fix for handling of default datasource 2015-08-18 14:54:56 +02:00
Torkel Ödegaard
4f8cea6e2d docs(): added tutorial article on hubot and grafana integration 2015-08-18 14:39:04 +02:00
Mitsuhiro Tanda
1c6b7203cc add template variable to drop down list 2015-08-18 20:35:12 +09:00
Torkel Ödegaard
bf5081ec8c fix(type ahead): fixed highlight, and made the highlight more distinct, also rolled back #2436 due to its issues on linux and windows, fixes #2527 2015-08-18 10:09:12 +02:00
Torkel Ödegaard
4f7587492d docs(changelog): fixed minor wording issue in changelog 2015-08-18 08:24:08 +02:00
Torkel Ödegaard
9a9c9b2b12 Merge branch 'query-editor-breakout'
Conflicts:
	CHANGELOG.md
2015-08-18 08:23:13 +02:00
Torkel Ödegaard
d4432ddd64 feat(mixed datasources): feature ready to merge to master, closes #436 2015-08-18 08:22:00 +02:00
Torkel Ödegaard
5c2d49f7ce feat(mixed datasource): updated Elasticsearch so it uses new way to define annotations editor 2015-08-18 07:58:44 +02:00
Mitsuhiro Tanda
28ccd63255 fix templating 2015-08-18 13:29:17 +09:00
Michaël Lhomme
0dc263d919 Disable load timeout in requirejs 2015-08-17 21:41:04 +02:00
Torkel Ödegaard
3de041a411 feat(mixed datasource): added datasource name to left of query hamburger #436 2015-08-17 21:25:08 +02:00
Torkel Ödegaard
00f5f8b2a0 feat(mixed datasource): polishing feature, making sure everything works, #436 2015-08-17 21:20:09 +02:00
Torkel Ödegaard
95f1343a59 feat(mixed datasource): fixing varios issues with the query editor changes, updated kariosdb data source editor to work with the new model, #436 2015-08-17 20:53:40 +02:00
Torkel Ödegaard
b4115b0362 feat(query editor): updated influxdb 0.8.x data source query editors to new abstraction 2015-08-17 17:28:03 +02:00
Torkel Ödegaard
6ee0f2c6a7 feat(mixed data source queries): lots of minor polish to new mixed data source and all the changes it has required, #436 2015-08-17 17:07:33 +02:00
Torkel Ödegaard
af39e4de3e Merge branch 'v2.1.x'
Conflicts:
	CHANGELOG.md
2015-08-17 15:02:47 +02:00
Torkel Ödegaard
0d8303cf5d fix(templating): fix for setting template variable value via url and having repeated panels or rows, fixes #2534 2015-08-17 15:01:16 +02:00
Torkel Ödegaard
4a6b5274bc feat(invite): fixes for org invite enhancement story, #2353 2015-08-17 10:55:52 +02:00
Torkel Ödegaard
f7ea420a3f Merge pull request #2521 from mattttt/email-tweaks
Email tweaks
2015-08-17 10:33:06 +02:00
Torkel Ödegaard
56d5b0b12a feat(mixed datasource): minor progress 2015-08-17 10:31:54 +02:00
Torkel Ödegaard
bb81248eaa Merge pull request #2528 from thuck/permissions
Fixing permission issues for new installations
2015-08-17 10:13:12 +02:00
Torkel Ödegaard
0e18eafcfb Merge pull request #2535 from raintank/logging
close all existing loggers before re-initializing loggers. fixes #2533
2015-08-17 10:05:42 +02:00
woodsaj
c138f390ac close all existing loggers before re-initilizing loggers. fixes #2533 2015-08-17 15:59:40 +08:00
Torkel Ödegaard
a16c63a43e feat(mixed datasources): continued work on editor design change 2015-08-16 20:52:30 +02:00
Denis Doria
9e21a089d2 Fixing permission issues for new installations
The umask as 0027 will generates permissions like:
0640 - for files
0750 - for directories

This should solve the grafana.db being accesable by any user for new installations.
2015-08-16 18:43:27 +02:00
Torkel Ödegaard
3105215425 feat(mixed datasource): changed how query ref ids (letters) are assigned, now they are persisted 2015-08-16 11:24:32 +02:00
Torkel Ödegaard
e916f93787 feat(mixed datasource): fixed failing unit tests 2015-08-16 10:06:36 +02:00
Torkel Ödegaard
1332ddbc93 feat(mixed datasources): updated OpenTSDB data source query editor to new model, #436 2015-08-16 09:52:45 +02:00
Torkel Ödegaard
b30dfcf28a feat(datasource): added new mixed data source 2015-08-16 01:34:09 +02:00
Torkel Ödegaard
b9cfade18c feat(mutli db query): major changes to query editor and data source handling, looks promising 2015-08-15 23:11:37 +02:00
Torkel Ödegaard
ad1fa110ff change(search): opening search dropdown should not take you out of fullscreen view or edit 2015-08-15 21:57:41 +02:00
Torkel Ödegaard
ed49962120 refactor(): began work on big design change for how data source query editors are loaded 2015-08-15 21:49:30 +02:00
Matt Toback
0651f134e4 Backed our new styles out of ink.css and moved them into style.css. Small margin tweaks, looking good to go. 2015-08-14 15:21:51 -04:00
Torkel Ödegaard
16fa5c4df3 fix(mysql): fix for migration in newly added temp_user table, fixes #2509 2015-08-14 09:41:07 +02:00
Torkel Ödegaard
2f849be9d8 docs(): minor tweaks to new tutorial 2015-08-14 09:41:07 +02:00
Torkel Ödegaard
6c49ab932e Merge pull request #2511 from pivotal-cloudops/patch
fix test connection bug
2015-08-14 09:23:14 +02:00
Pivotal
dc9c2773cb fix test connection bug 2015-08-14 13:50:01 +08:00
Matt Toback
60eec49e95 Added in additional text and incorporated the bulletproof button that was built for raintank. Needs some help from Torkel or Nick to make sure it's cleaner 2015-08-13 17:57:58 -04:00
Torkel Ödegaard
f784551e20 docs(): Began work on stack install and config tutorial 2015-08-13 08:52:50 +02:00
Torkel Ödegaard
a3d9169930 docs(): minor update 2015-08-13 08:00:27 +02:00
Torkel Ödegaard
e87502285b Merge pull request #2502 from mtanda/stack_override_tooltip
don't calculate cumulative value if series overrides stack to false
2015-08-13 07:44:27 +02:00
Mitsuhiro Tanda
caccacf52b don't calculate cumulative value if series overrides stack to false 2015-08-13 13:40:16 +09:00
Torkel Ödegaard
5aee981590 Merge branch 'master' of https://github.com/mattttt/grafana
Conflicts:
	docs/sources/installation/debian.md
	docs/sources/installation/ldap.md
2015-08-12 16:30:48 +02:00
Torkel Ödegaard
e6d09b3266 fix(db): remove stars and tags when removing user or dashboard, fixes #2016 2015-08-12 09:23:46 +02:00
Torkel Ödegaard
8fcaa4997d feat(admin): Deleting org from orgs list now works, will permanently delete dashboards, data sources, etc, closes #2457 2015-08-12 08:59:39 +02:00
Torkel Ödegaard
7370af9a82 Merge pull request #2492 from agilgur5/master
docs(ldap): Clarify LDAP features and sample config
2015-08-12 08:20:00 +02:00
Anton Gilgur
e4baef94bc docs(ldap): Clarify LDAP features and sample config
* Clarify certain features, such as multiple mappings
* Fixup ldap.toml sample config
* Fixup docs README's port number
* Fixup bad link from LDAP docs to Configuration docs
* Fixup some spelling, grammar, and line endings
2015-08-11 19:57:53 -07:00
Trent White
8c05f9cf3e Merge pull request #11 from mattttt/trent-update
add text panel page
2015-08-11 16:52:15 -04:00
Trent White
ebcab0f26e add text panel page 2015-08-11 16:50:33 -04:00
Matt
14996bd08b Update templating.md 2015-08-11 15:57:36 -04:00
Matt
dd9fb9d526 Update templating.md 2015-08-11 15:56:18 -04:00
Matt
f3ffed673b Merge pull request #10 from mattttt/build-patch-1
Reformatted a confusing part of of the running grafana locally section
2015-08-11 15:47:38 -04:00
Matt Toback
5b342dd229 Reformatted a confusing part of of the running grafana locally section 2015-08-11 15:41:12 -04:00
Trent White
e626bbf415 Merge pull request #9 from mattttt/trent-update
formatting tweak, added datasource overview blurb.
2015-08-11 15:31:03 -04:00
Trent White
04795709a3 formatting tweak, added datasource overview blurb. 2015-08-11 15:30:13 -04:00
Torkel Ödegaard
745162c589 fix(graphite): Import dashboard from graphite is working again, fixes #2490 2015-08-11 21:20:20 +02:00
Torkel Ödegaard
089508db8e fix(influxdb 0.9): removed function from function dropdown as this function does not exist in InfluxDB 0.9 2015-08-11 21:10:15 +02:00
Matt
a912323d01 Merge pull request #8 from mattttt/ct-additions
Added patch from ct
2015-08-11 14:59:59 -04:00
Matt Toback
511a592ec8 Added patch from ct 2015-08-11 14:58:30 -04:00
Matt
d132d3abfd Merge pull request #7 from mattttt/mattttt-docs
Mattttt docs
2015-08-11 14:41:21 -04:00
Trent White
fb9b5e4274 Merge pull request #6 from mattttt/trent-docs-update
openTSDB image was broken
2015-08-11 14:32:03 -04:00
Trent White
8dcc5645db openTSDB image was broken 2015-08-11 14:26:32 -04:00
raj dutt
c6798892b8 Update screencasts.md
tweaks
2015-08-11 14:26:04 -04:00
Matt Toback
eea5b026ce Sanity checked and verified for merge 2015-08-11 14:17:56 -04:00
raj dutt
331e858ed3 Update gettingstarted.md 2015-08-11 14:04:14 -04:00
Trent White
3d602a2558 Merge remote-tracking branch 'origin/master' 2015-08-11 14:01:03 -04:00
Trent White
14386a2f62 Merge branch 'trent-docs'
Conflicts:
	docs/sources/reference/templating.md
2015-08-11 13:59:46 -04:00
raj dutt
4e91e05470 Update gettingstarted.md 2015-08-11 13:58:44 -04:00
Trent White
ea0bec8bf2 update content on annotations 2015-08-11 13:51:27 -04:00
raj dutt
c2e8a1d733 Update singlestat.md 2015-08-11 13:49:24 -04:00
raj dutt
c6155723a1 Update playlist.md 2015-08-11 13:43:36 -04:00
Trent White
8c64afc81e update to templating doc 2015-08-11 13:42:40 -04:00
raj dutt
eafd1d8c53 Update templating.md 2015-08-11 13:33:01 -04:00
raj dutt
df0673f907 Update templating.md 2015-08-11 13:31:49 -04:00
Torkel Ödegaard
fe093c6385 feat(timepicker): added new relative time option , will set time range to midnight to now, closes #1186 2015-08-11 19:16:45 +02:00
raj dutt
ed536f18ee Update basic_concepts.md 2015-08-11 13:07:30 -04:00
Matt Toback
96590f591c Updates to dashlist article 2015-08-11 12:34:18 -04:00
Trent White
32bfcbcdbc updates to screencasts page, with descriptions of each 2015-08-11 11:43:45 -04:00
Torkel Ödegaard
82feeff3aa tech(emails): small update to grunt watch for emails 2015-08-11 16:40:23 +02:00
Trent White
3af451caeb Merge pull request #4 from mattttt/trent-docs
added pages, touched up some images and content  in data-sources section
2015-08-11 10:15:59 -04:00
Trent White
ae953f2420 added pages, touched up some images and content in data-sources section 2015-08-11 10:12:58 -04:00
Torkel Ödegaard
43ef9f909a feat(admin): admin page for all grafana organizations (list / edit view), #2457 2015-08-11 15:20:50 +02:00
Mitsuhiro Tanda
4f42dae3cb change behavior of dimension value suggestion 2015-08-11 20:43:34 +09:00
Torkel Ödegaard
e01c68dcea fix(logging): removed db connection string from being printed in logs at app startup, fixes #2488 2015-08-11 11:26:28 +02:00
Torkel Ödegaard
234d1291f9 Merge branch 'invite'
Conflicts:
	public/css/less/gfbox.less
	public/emails/reset_password.html
	public/emails/welcome_on_signup.html
2015-08-11 11:22:43 +02:00
Torkel Ödegaard
90169d6a05 Bumped master version to 2.2.0-pre1 2015-08-11 11:18:39 +02:00
Torkel Ödegaard
809562c874 updated install docs for 2.1.1 release 2015-08-11 11:12:06 +02:00
Torkel Ödegaard
be7a3a0940 pumped version to 2.1.1 2015-08-11 10:48:19 +02:00
Torkel Ödegaard
1ea0b5371a feat(invite): new user invites are now also added to correct org after sign up is completed, #2353 2015-08-11 10:45:03 +02:00
Torkel Ödegaard
74932e6311 feat(invite): added specific email for invites to existing grafana users 2015-08-11 10:35:10 +02:00
Torkel Ödegaard
b0b96aa410 Moved reset password and welcome on sign up email to new email build template system 2015-08-11 10:01:52 +02:00
Mitsuhiro Tanda
deedd166db add metricFindQuery 2015-08-11 14:26:24 +09:00
Mitsuhiro Tanda
6ffab821b2 refactor suggest function 2015-08-11 14:26:18 +09:00
Matt
e53c8100a3 Merge pull request #3 from mattttt/mattttt-docs
Adding additional pages and updates from google doc
2015-08-10 23:20:15 -04:00
Mitsuhiro Tanda
6705902a94 fix style 2015-08-11 10:17:48 +09:00
Mitsuhiro Tanda
fd96e30c9c add region field in query editor 2015-08-11 10:17:39 +09:00
Matt Toback
2b59724ea8 Adding additional pages and updates from google doc 2015-08-10 18:19:56 -04:00
Trent White
aa4d6d6f0f Merge pull request #2 from mattttt/trent-docs
add basic concpets doc
2015-08-10 17:52:38 -04:00
Trent White
ca65671553 add basic concpets doc 2015-08-10 17:51:29 -04:00
Trent White
2d99308219 Merge pull request #1 from mattttt/trent-docs
adding Raj edits/additions as well as initial round of pages I created
2015-08-10 17:46:51 -04:00
Trent White
a4be75f410 adding Raj edits/additions as well as initial round of pages I created 2015-08-10 17:44:08 -04:00
Torkel Ödegaard
4439545428 feat(invite): lots of work completing the new email template system and css inlineing, converted new_user_invite.html to new system, #2353 2015-08-10 21:24:47 +02:00
Torkel Ödegaard
835fd383ad fix(postgres): Dashboard search is now case insensitive when using Postgres, fixes #1896 2015-08-10 20:25:01 +02:00
Torkel Ödegaard
dfd1bff389 feat(invite): began work on email template build system, and css inlining 2015-08-10 17:50:02 +02:00
Torkel Ödegaard
e53c1e39d3 feat(invite): can now add org user with sername again, #2353 2015-08-10 14:03:08 +02:00
Torkel Ödegaard
775e044e69 feat(invite): progress on invite feature, #2353 2015-08-10 13:47:06 +02:00
Torkel Ödegaard
4f3a3329e2 added postgres test docker fig block 2015-08-10 12:56:11 +02:00
Torkel Ödegaard
19f64bc561 fix(data source proxy): clear proxies request from cookies, fixes #2470 2015-08-10 12:11:18 +02:00
Torkel Ödegaard
1b2c1270a8 updated changelog with info about merged PR #2483 2015-08-10 09:14:45 +02:00
Torkel Ödegaard
4b849243ce Merge branch 'influxdb_alias_columns' of https://github.com/thuck/grafana into thuck-influxdb_alias_columns 2015-08-10 09:11:19 +02:00
Torkel Ödegaard
e7fc72067a refactor(influxdb): refactoring of PR 2477, added unit tests, #2477 2015-08-10 09:09:39 +02:00
Torkel Ödegaard
ca6476a57f Merge branch 'influxdb_operators' of https://github.com/thuck/grafana into thuck-influxdb_operators 2015-08-10 08:45:16 +02:00
Denis Doria
696f8451cf Update documentation with $col alias 2015-08-10 08:12:12 +02:00
Torkel Ödegaard
fb9f954882 fix(graphite): Fix for bug when using series ref (#A-Z) and referenced series is hidden in query editor. fixes #2484 2015-08-10 07:59:27 +02:00
Denis Doria
a4af30675d Included $col as an option for the "Alias pattern" on influxdb
With this patch it's possible to use the column name as an identifier.
This can help when we have multiple columns as it would be possible to automatic generate a legend.
Also this would help in future when grafana could support the "AS" in the query builder.
2015-08-10 00:22:08 +02:00
Torkel Ödegaard
95cad91bae Merge pull request #2482 from thuck/influxdb_fill
Including more special options for fill
2015-08-09 20:18:45 +02:00
Denis Doria
166a56828d Including more special options for fill
Included "previous" and "none" as possible values for fill.
From the influxdb documentation:
 - "previous" means the values of the previous window is used.
 - "none" means that all null values are removed.
2015-08-09 19:17:01 +02:00
Torkel Ödegaard
3d197abf83 Merge pull request #2480 from swoop-inc/mb_log_file_permissions
Change log file permissions from 0660 to 0644
2015-08-09 12:05:40 +02:00
Denis Doria
950d4bb24e Code style correction 2015-08-09 12:03:51 +02:00
Denis Doria
42caf02fb0 Include context for operators
Depending of the value a different set of operators will show up.
If regex only =~ and !~ will show up.
Any other value will show =, <>, <, >.
2015-08-09 11:58:08 +02:00
Mark Bell
e59ea1aa02 Change log file permissions from 0660 to 0644 2015-08-08 23:06:30 -04:00
Denis Doria
4bd83443f8 Fixing issue for missing tag.operator
This should fix the case from when we migrate from a version that doesn't contain tag.operator.
2015-08-08 17:00:10 +02:00
Denis Doria
5a906e0f3e Fix small corner case 2015-08-08 14:40:55 +02:00
Denis Doria
a51d900da0 Fix code style and included missed semicolon 2015-08-08 13:54:01 +02:00
Denis Doria
9a9902cea9 Includes more comparators/operators for influxdb
This patch includes some missing comparators/operators for influxdb:
>  - greater than
<  - less than
<> - not equal
!~ - doesn't match against

It includes the tag.operator fields to facilitate identification of the operators on the segments.
It keeps the logic for the regex syntax; although it changes the way to validate it, to handle the new operators use case.
2015-08-08 13:31:37 +02:00
Torkel Ödegaard
81790aed13 tech(lib upgrade): updated angularjs to 1.4.3 2015-08-08 12:31:37 +02:00
Torkel Ödegaard
1f7ac11baa feat(ldap): added support for compound search filter expressions, closes #2472 2015-08-08 10:00:49 +02:00
Torkel Ödegaard
0ed4744a33 influxdb(auth): fixed issue with using basic auth and influxdb, fixes #2455 2015-08-07 11:02:32 +02:00
Torkel Ödegaard
776ca86a60 Merge pull request #2458 from thuck/influxdb_non_negative_derivative
Including non_negative_derivative function on influxdb
2015-08-07 10:20:25 +02:00
Torkel Ödegaard
90400bf5ad fix(ldap): fix for ldap users with empty email address, fixes #2461 2015-08-07 10:14:36 +02:00
Torkel Ödegaard
c43f2bbb86 fix(singlestat): fix to handle series with no data points, fixes #2460 2015-08-07 10:03:21 +02:00
Denis Doria
f3fc128ec3 Including non_negative_derivative function on influxdb
Since version 0.9.0 of influxdb the non_negative_derivative function
is merged, this small patch includes it as an option on grafana.
2015-08-06 10:23:52 +02:00
Mitsuhiro Tanda
6a697eed2f fix deactivating statistics bug 2015-08-06 14:41:06 +09:00
Torkel Ödegaard
862e52a830 Merge pull request #2453 from blt04/read-only-default-role
Allow default org role to be Read Only Editor
2015-08-06 06:59:17 +02:00
Brandon Turner
1e2e65b40d Allow default org role to be Read Only Editor 2015-08-05 23:04:47 -05:00
Torkel Ödegaard
41e13fab55 fix(influxdb): fix for using template variables in side alias field in influxdb 0.9 editor, fixes #2446 2015-08-05 18:42:03 +02:00
Mitsuhiro Tanda
134819cb45 fix jshint error 2015-08-06 00:05:40 +09:00
Torkel Ödegaard
f62d743407 fix(ldap): another fix for ldap and empty bindDN and bind password, #2339 2015-08-05 16:34:39 +02:00
Torkel Ödegaard
9dc779927b fix(ldap): updated to ldap bindDN and bindPassword is allowed to be empty, fixes #2439 2015-08-05 16:28:15 +02:00
Mitsuhiro Tanda
88ce05976e add CloudWatch datasource 2015-08-05 23:10:32 +09:00
Torkel Ödegaard
956a2bbdbe Updated version 2015-08-05 16:06:55 +02:00
Torkel Ödegaard
bd7ae3dc71 fix(templating): fix for text panel when using template variables in text and repeating panel, fixes #2442 2015-08-05 14:23:09 +02:00
Torkel Ödegaard
91e5fcf8f1 fix(templating): fixed issue with repeat row, was pretty buggy due to very recent change, fixes #2443 2015-08-05 14:19:09 +02:00
Torkel Ödegaard
93b8287d23 Merge pull request #2437 from mattttt/invite
Added some small styling tweaks to the modals, and adjusted some copy.
2015-08-05 10:10:57 +02:00
Torkel Ödegaard
5c7f34fee2 Merge pull request #2436 from mattttt/patch-4
Changed font weight to look less muddy
2015-08-05 10:03:32 +02:00
Matt Toback
b64c550989 Added some small styling tweaks to the modals, and adjusted some copy. 2015-08-04 15:58:07 -04:00
Matt
997bd030f9 Changed font weight to look less muddy
Changed the font weight to look a bit less muddy. Any reason why we cant use a number instead of bold?
2015-08-04 15:38:45 -04:00
Torkel Ödegaard
bf6444c585 docs(): docker upgrade notice 2015-08-04 20:42:14 +02:00
Torkel Ödegaard
9d25d2674b fix(invite): fixed link in email 2015-08-04 20:23:04 +02:00
Torkel Ödegaard
8c1b145358 docs(): updated install docs with 2.1 download links 2015-08-04 17:00:26 +02:00
Torkel Ödegaard
925ead51b6 Updated version to 2.1 2015-08-04 16:19:48 +02:00
Torkel Ödegaard
85b606b7d2 fix(test): fix for failing unit test 2015-08-04 15:44:29 +02:00
Torkel Ödegaard
5302d3bb31 Added pidfile arg to systemd service spec, fixes #2360 2015-08-04 15:44:07 +02:00
Torkel Ödegaard
775a805959 fix(): fix for cleaning up repeat panel scope for orphaned panels or duplicated panels 2015-08-04 15:15:03 +02:00
Torkel Ödegaard
0586dbaf8b feat(save_as): imporved save as dialog, now auto focus, and enter key to submit works, closes #2426 2015-08-03 10:46:07 +02:00
Torkel Ödegaard
dd6a34e60a fix(influxdb): very minor fix to editor, blur event for alias input caused two refresh events, fixes #2429 2015-08-03 10:18:18 +02:00
Torkel Ödegaard
49c1d1a4ca docs(ldap): fixed minor issue in ldap example config doc 2015-08-01 10:32:36 +02:00
Torkel Ödegaard
6d6c9b782b fix(ldap): minor fixes, should not have any real impact, #2421 2015-08-01 10:30:05 +02:00
Torkel Ödegaard
733332a8f4 Merge pull request #2422 from grafana/grafana-light-tweak
updated some hard-coded colors to variables
2015-08-01 08:34:43 +02:00
Trent White
6b6b102079 updated some hard-coded colors to variables so they work well on light theme 2015-07-31 15:51:31 -04:00
Torkel Ödegaard
50895c7e37 fix(ldap): fixed issue with ldap group to grafana org role syncing, #1450 2015-07-31 16:38:41 +02:00
Torkel Ödegaard
66a37aa945 docs(): updated whats new in 2.1 doc 2015-07-31 15:22:13 +02:00
Torkel Ödegaard
0fd4c3bd00 fix(templating): small fixed toplaceholder text in template variable editor 2015-07-31 14:56:51 +02:00
Torkel Ödegaard
0d3321c686 docs(): updated whats new in grafana 2.1 article 2015-07-31 10:09:32 +02:00
Torkel Ödegaard
7cae087eb9 fix(influxdb_09): fixed handling of regex measurement in explore queries, fixes #2415 2015-07-31 09:17:14 +02:00
Torkel Ödegaard
4c7e52288d fix(templating): another fix for panel links and repeat panels, #2410 2015-07-30 12:04:52 +02:00
Torkel Ödegaard
e108fecdd6 fix(influxdb): fixed issue with empty influxdb result object, fixes #2294 2015-07-30 10:51:26 +02:00
Torkel Ödegaard
be6ddb08bd docs(): added auth proxy to config docs 2015-07-30 10:37:49 +02:00
Torkel Ödegaard
2cc53f328b fix(templating): fixed scoped vars issue when generating urls for panel links, fixes #2410 2015-07-30 08:28:42 +02:00
Torkel Ödegaard
aaae23e9f0 fix(templating): fixed issue with templated dashboard and error selectOptionsForCurrentValue undefined 2015-07-30 08:08:08 +02:00
Torkel Ödegaard
fb81a4f318 Merge pull request #2299 from espenfjo/kairosdb-sensible-tag-values
KairosDB: Update the template functionality to cohere with the very closely related OpenTSDB plugin
2015-07-29 20:46:13 +02:00
Torkel Ödegaard
95bce39751 docs(): updates to whats new arcticle, and changed one screenshot 2015-07-29 15:36:04 +02:00
Torkel Ödegaard
a34af84176 docs(): fixed wording in sentence in whats new in 2.1 article 2015-07-29 15:00:53 +02:00
Torkel Ödegaard
610a94ea03 docs(): lots of updates to whats new in 2.1 doc article 2015-07-29 10:24:46 +02:00
Torkel Ödegaard
5a160f426e feat(invite): trying to get username to work as well 2015-07-29 09:30:23 +02:00
Torkel Ödegaard
0367edf898 Merge pull request #2380 from stesie/graphite-function-removeEmptySeries
New graphite function: removeEmptySeries
2015-07-29 08:24:35 +02:00
Torkel Ödegaard
4279c0b77f Merge pull request #2402 from wKich/patch-1
Fix font awesome icons in OS X Safari
2015-07-27 11:04:08 +02:00
wKich
cae06f6cf4 Fix font awesome in OS X Safari 2015-07-27 12:41:11 +05:00
Torkel Ödegaard
07b5995e85 Merge pull request #2395 from Dieterbe/fix-unknown-config-key-message
fix unknown key error formatting
2015-07-24 13:24:55 +02:00
Dieter Plaetinck
0f7a77f45b fix unknown key error formatting 2015-07-24 12:29:03 +02:00
Torkel Ödegaard
cd832ea77c Merge pull request #2384 from sbengo/improved_http_api_doc
Added docs to http_api
2015-07-23 09:43:02 +02:00
sbengo
82c7764679 Added docs to http_api
Finished the documentation of API HTTP

Notes: 
· Data source proxy calls not documented - Too long request/response text.
· Dashboard from JSON file no do documented - Need more study to underestand how it works.
2015-07-22 13:11:55 +02:00
Torkel Ödegaard
a386733c04 docs(): updates to docs 2015-07-21 16:19:49 +02:00
Trent White
381e9dac34 new images for regex color and negative y transform 2015-07-21 16:19:49 +02:00
Trent White
736aadb186 formatting and some hr to make it more organized 2015-07-21 16:19:49 +02:00
Torkel Ödegaard
6d6af09296 feat(invite): handling of existing org user case when inviting, #2353 2015-07-21 12:18:11 +02:00
Torkel Ödegaard
4acfa1faef Merge pull request #2378 from sbengo/improved_http_api_doc
Added details on api documentation
2015-07-21 12:15:22 +02:00
sbengo
b9085c1522 Added details on api documentation
Added example request and example response of sections: Dashboard, Datasources, Organisation.

If you agree, I will complete the documentation following the same formatting.
2015-07-21 12:01:04 +02:00
Torkel Ödegaard
55af0ce297 fix(ui): fix scrolling issue when editors are open, fixes #2371 2015-07-21 11:05:25 +02:00
Torkel Ödegaard
6b25453f11 templating(influxdb): regex escape values when multi or all format is enabled, closes #2373 2015-07-21 11:00:09 +02:00
Torkel Ödegaard
06a32ce7be emails(): updated email templates 2015-07-21 09:59:40 +02:00
Torkel Ödegaard
75c1c1bccc Merge pull request #2377 from grafana/nopzor1200-patch-1
Update templating.md
2015-07-21 09:34:15 +02:00
raj dutt
0c25c1f0ca Update templating.md 2015-07-20 20:05:56 -04:00
Torkel Ödegaard
1b12e3fbf9 Merge pull request #2376 from mattttt/patch-3
Relative link for What's New in v2 was incorrect
2015-07-20 23:13:22 +02:00
Matt
a7a2a166e6 Relative link for What's New in v2 was incorrect 2015-07-20 17:09:58 -04:00
Torkel Ödegaard
0d7aba2502 Merge pull request #2372 from jshafton/influxdb-0.9.x-order-by-removal
Fixes "no datapoints" from InfluxDB 0.9.1 data source
2015-07-20 18:27:34 +02:00
Jacob Shafton
ce45879efb Fixes "no datapoints" from InfluxDB 0.9.1 data source
This removes usage of the `ORDER BY` clause for InfluxDB 0.9.x data
sources, which is currently not supported in the latest InfluxDB master
(0.9.1).
2015-07-20 11:13:57 -05:00
Torkel Ödegaard
ab54971763 feat(invite): more progress on completing invite form and actually creating a real user, #2353 2015-07-20 17:46:48 +02:00
Torkel Ödegaard
d75f96fdd5 feat(invite): more progress on invited / sigup view, #2353 2015-07-20 15:52:49 +02:00
Torkel Ödegaard
024c112944 feat(invite): redesign for pending invite list, added revoke button and link, copy invite also works now, #2353 2015-07-20 14:26:49 +02:00
Torkel Ödegaard
3242354a4b feat(invite): worked on pending invitations list, revoke invite now works, #2353 2015-07-20 10:57:39 +02:00
Torkel Ödegaard
f465a79924 Merge pull request #2366 from raventools/remove-hubot-script
Remove grafana.coffee
2015-07-20 08:28:13 +02:00
Stephen Yeargin
fc28423141 Remove grafana.coffee
Proposing the deprecation of this in favor of the [hubot-grafana](https://www.npmjs.com/package/hubot-grafana) package, which offers cross-adapter support, dashboard browsing and can be augmented with Amazon S3 credentials.

Fixes #2206
2015-07-20 01:02:45 -05:00
Torkel Ödegaard
4ac652b127 feat(invite): began work on invited signup view, also added backdrop to login view, #2353 2015-07-19 12:34:03 +02:00
Torkel Ödegaard
6088f83408 feat(invite): inital pass on sending new user invite email, #2353 2015-07-18 17:39:12 +02:00
Torkel Ödegaard
e92f2ecea1 feat(invite): existing grafana users now result in new org user directly, no temp user is created, #2353 2015-07-18 11:43:34 +02:00
Torkel Ödegaard
a82aa8203b Merge branch 'master' into invite 2015-07-18 10:14:45 +02:00
Torkel Ödegaard
0a59d6ab2f fix(repeat panel): fix for unwanted unsaved changes warnings when using repeat panel, #2364 2015-07-18 10:14:21 +02:00
Torkel Ödegaard
9bf9bb0273 fix(ldap): fixed issue with ldap group mappings to org roles sync, #1450 2015-07-18 09:55:21 +02:00
Torkel Ödegaard
5744c70399 fix(ldap conf): fixed another issue in ldap conf example 2015-07-18 09:31:53 +02:00
Torkel Ödegaard
f3342ae7d3 fix(ldap conf): updated ldap conf example to use literal string syntax for bind_password 2015-07-18 09:30:09 +02:00
Torkel Ödegaard
2c7e807738 feat(invite): small style change 2015-07-17 17:22:42 +02:00
Torkel Ödegaard
39430e1cee Merge pull request #2361 from mtanda/fix_link
Fix dashboard/panel link in Scripted Dashboard
2015-07-17 15:49:08 +02:00
Torkel Ödegaard
142a323efd fix(save as): fixed issue with save as and overwriting a dashboard with the same name 2015-07-17 15:34:15 +02:00
Torkel Ödegaard
ea198fea6e fix(graph): fixed legend color when having many many series, fixes #2359 2015-07-17 15:20:12 +02:00
Torkel Ödegaard
666013b010 fix(panel repeat): fixed issue with repeat panels in combination with setting variable values from URL, fixes #2351 2015-07-17 14:59:05 +02:00
Torkel Ödegaard
c7dafd4b42 fix(repeat panel): fixed issue with snapshoting a dashboard with repeated panels, Fixes #2352 2015-07-17 14:45:49 +02:00
Torkel Ödegaard
2724cf5db8 feat(invite): small progress 2015-07-17 14:42:49 +02:00
Stefan Siegl
ea623cb5a1 New graphite function removeEmptySeries 2015-07-17 12:32:29 +02:00
Torkel Ödegaard
0ffcce1b5d feat(invite): more work on invite, basic creation works, added new tab directive from angular-ui and made new tab style, #2353 2015-07-17 09:51:34 +02:00
Mitsuhiro Tanda
8ac47b4e24 fix dashboard/panel link in scripted dashboard 2015-07-17 15:40:52 +09:00
Torkel Ödegaard
444807c35b feat(invite): worked on db & domain model for temp users, #2353 2015-07-16 17:59:11 +02:00
Torkel Ödegaard
5b0585ac7f feat(ldap): removed ssl_server_name and added some validation to ldap config, #1450 2015-07-16 12:58:30 +02:00
Torkel Ödegaard
c3a5822a40 feat(user invite): progress on user invite feature, #2353 2015-07-16 12:38:55 +02:00
Torkel Ödegaard
2f4d3be303 fix(panellinks): fixed open in new tab issue for dashboard links, Fixes #2354 2015-07-16 12:06:49 +02:00
Torkel Ödegaard
9afdea8d2a feat(ldap): added config options for ssl skip verify, and ssl server name, #1450 2015-07-16 11:59:26 +02:00
Torkel Ödegaard
57c78bc603 feat(invite): began work on invite users dialog, #2353 2015-07-16 10:44:55 +02:00
raj dutt
a065f93777 Update singlestat.md 2015-07-15 14:27:55 -07:00
Mike Kobyakov
f76374cd8f fix testDatasource, which was calling performSuggestQuery instead of _performSuggestQuery 2015-07-15 12:03:22 -07:00
Torkel Ödegaard
e1345337ac docs(): small fix to time range doc page 2015-07-15 17:41:45 +02:00
Torkel Ödegaard
494810393a feat(templating): new template variable selection dropdown now supports accepting custom values that are not an actual selectable value, Fixes #2344 2015-07-15 17:21:54 +02:00
Torkel Ödegaard
8daec7bde2 fix(packaging): fixed issue with restart on upgrade in debian package, Fixes #2320 2015-07-15 14:57:52 +02:00
Torkel Ödegaard
b891677ba5 docs(ldap): added ldap integration docs and config examples, #1450 2015-07-15 14:48:39 +02:00
Torkel Ödegaard
d63a77d8b1 fix(build): fixed ldap config file sample issue and build script 2015-07-15 13:52:30 +02:00
Torkel Ödegaard
2cbe6cdfd8 fix(build): fixed issue in build.go 2015-07-15 11:06:29 +02:00
Torkel Ödegaard
ddd20e5376 Merge branch 'ldap' 2015-07-15 10:14:36 +02:00
Torkel Ödegaard
08cd949d6f feat(ldap): added ldap go dependencies 2015-07-15 10:12:11 +02:00
Torkel Ödegaard
0b5ba55131 feat(ldap): work on reading ldap config from toml file, #1450 2015-07-15 10:08:23 +02:00
Torkel Ödegaard
262a09bb2d feat(ldap): added support for binding initially with an admin user searching for user and then binding again to authenticate current user, #1450 2015-07-14 17:35:11 +02:00
Torkel Ödegaard
0320baeb5b feat(ldap): user org role sync working 2015-07-14 16:42:55 +02:00
Torkel Ödegaard
42670c27d5 feat(ldap): more work on org role sync 2015-07-14 15:46:11 +02:00
Torkel Ödegaard
fe41a4e6fa feat(ldap): began work on org role sync 2015-07-14 14:29:07 +02:00
Torkel Ödegaard
a7b1df34c5 feat(ldap): more unit tests for ldap to grafana user sync 2015-07-14 10:20:30 +02:00
Torkel Ödegaard
567a42a756 docs(http_api): updated http docs and mentioned basic auth option 2015-07-13 22:20:58 +02:00
Torkel Ödegaard
2fa9311eee Progress on ldap support, #1450 2015-07-13 16:45:47 +02:00
Torkel Ödegaard
14f439f8ba refactor(ldap): refactoring ldap code, #1450 2015-07-13 15:37:25 +02:00
Torkel Ödegaard
bfe7b77313 More work on ldap auth, got memberOf working in the docker ldap test server, playing with config options and structures, #1450 2015-07-13 14:23:59 +02:00
Torkel Ödegaard
a69086a718 Merge branch 'master' into ldap 2015-07-13 09:50:19 +02:00
Torkel Ödegaard
3e3980dbac made snapshot post url http scheme agnostic, #1725 2015-07-13 09:37:10 +02:00
Torkel Ödegaard
9f6c9cd6ff fix(graphite): raw query mode (disable graphite query editor mode) is now persisted property on the query, Fixes #2328, Fixes #2307 2015-07-12 18:18:04 +02:00
Mike Kobyakov
eb88a53223 fix for a change in datasource object 2015-07-10 16:09:08 -07:00
Mike Kobyakov
76c18e50a4 Merge remote-tracking branch 'upstream/master'
Conflicts:
	public/app/plugins/datasource/opentsdb/datasource.js
2015-07-10 15:55:45 -07:00
Torkel Ödegaard
ca1cd89190 Updated singlestat docs again 2015-07-10 15:53:39 +02:00
Torkel Ödegaard
9f8eae908d Updated singlestat docs 2015-07-10 15:34:54 +02:00
Torkel Ödegaard
db1847bc1d More work on ldap, gotten ldap search (read attributes) to work 2015-07-10 15:29:34 +02:00
Torkel Ödegaard
d9936f2dff Merge pull request #2329 from mattttt/patch-2
Update Singlestat Docs
2015-07-10 15:24:48 +02:00
Matt
3774114a4c Update Singlestat Docs
Need some additional clarification in Spark Line and Value to text before this is merged.
2015-07-10 09:13:45 -04:00
Torkel Ödegaard
4bc5b37c09 Merge pull request #2321 from gmembre/master
Adding GOPATH
2015-07-10 13:33:15 +02:00
Torkel Ödegaard
ac1ca639d6 Merge pull request #2327 from languitar/hide-in-legend-override
Series override option to hide legend entries
2015-07-10 13:32:35 +02:00
Torkel Ödegaard
83b67c0693 Fixed link to CLA, Fixes #2326 2015-07-10 13:31:21 +02:00
Johannes Wienke
244e682c2b Series override option to hide legend entries
Adds a series override option to hide certain series from the legend.

* public/app/components/timeSeries.js: boolean flag to decide display in
  legend and application of override option for this flag
* public/app/panels/graph/legend.js: exclude hidden series
* public/app/panels/graph/seriesOverridesCtrl.js: add legend override
  option

This fixes #2324
2015-07-10 12:16:41 +02:00
Torkel Ödegaard
0ef7271326 Started work on LDAP again, #1450 2015-07-10 11:10:48 +02:00
gmembre
3fa228dcbb Correcting typo 2015-07-09 22:49:27 +02:00
gmembre
14e25100d4 Adding GOPATH 2015-07-09 22:17:12 +02:00
Torkel Ödegaard
b2c0eefbbc Merge pull request #2313 from alaverty/master
Fixed invalid json example, added missing comma
2015-07-09 22:05:35 +02:00
Torkel Ödegaard
8eee0d574c Merge branch 'influxdb-09-fields-enhancements' 2015-07-09 17:17:46 +02:00
Torkel Ödegaard
150fd869a0 Finalized work on influxdb field and func selection, #2311 2015-07-09 17:17:17 +02:00
Torkel Ödegaard
50795adcf0 feat(influxdb 0.9): field lookup and other enhancements, #2311 2015-07-09 16:36:47 +02:00
Torkel Ödegaard
3f2c1de28d fix(OpenTSDB): fixed issue with OpenTSDB and query to result matching, Fixes #2315 2015-07-09 09:00:13 +02:00
Alex Laverty
dca45f177f Fixed invalid json example, added missing comma 2015-07-09 11:50:29 +10:00
Torkel Ödegaard
597c1f6a41 feat(influxdb): began work on some influxdb enhancements, #2311 2015-07-08 17:41:36 +02:00
Torkel Ödegaard
c2da54139a Merge pull request #2306 from davewongillies/grafana_ini_perms
set mode 0640 and group grafana on /etc/grafana/grafana.ini
2015-07-08 14:49:17 +02:00
Torkel Ödegaard
afcc388d8a Merge pull request #2308 from davewongillies/config_mysql_sessions
Minor update to documentation for MySQL sessions
2015-07-08 14:48:17 +02:00
David Gillies
5cd1c5c522 Minor update to documentation for MySQL sessions 2015-07-08 22:40:43 +10:00
Torkel Ödegaard
63f8800007 fix(docs): Updated getting started docs, Closes #2273, Closes #1978 2015-07-08 14:17:53 +02:00
David Gillies
a958965e64 set mode 0640 and group grafana on /etc/grafana/grafana.ini 2015-07-08 22:13:38 +10:00
Torkel Ödegaard
5d7c485991 fix(templating): can now use template vars in collapsed row title, Fixes #2305 2015-07-08 13:44:36 +02:00
Espen Fjellvær Olsen
d97f24cfc3 Update QueryController to conform with OpenTSDB Controller 2015-07-08 13:00:22 +02:00
Torkel Ödegaard
3b39c43193 Updated docs version and version fragment 2015-07-08 11:06:11 +02:00
Torkel Ödegaard
b746b63036 Updated config docs with info about [dashboards.json] section , #2302 2015-07-08 10:53:06 +02:00
Torkel Ödegaard
7c707017e7 Merge pull request #2303 from Dieterbe/datasource-docs
fix datasource api docs
2015-07-08 10:51:24 +02:00
Torkel Ödegaard
ce155a1d87 Merge pull request #2301 from tmonk42/influxdb_quoting
add double quotes around tags
2015-07-08 09:42:14 +02:00
Espen Fjellvær Olsen
48975e6533 Keep QueryController up to date as well 2015-07-08 09:36:25 +02:00
Espen Fjellvær Olsen
3e05eb23fd Update documentation based on templating changes 2015-07-08 09:29:54 +02:00
Espen Fjellvær Olsen
3980d25c23 Remove superfluous whitespaces 2015-07-08 09:29:54 +02:00
Espen Fjellvær Olsen
2255cb53f0 Close the gap between the key and the value in the js objects 2015-07-08 09:29:54 +02:00
Espen Fjellvær Olsen
521072daea KairosDB: Streamline the Templating with the very related OpenTSDB plugin
Since KairosDB is a fork of OpenTSDB it makes sense to (At least for the time being) keep their code bases similar-ish.
2015-07-08 09:29:54 +02:00
Dieter Plaetinck
66ba19b7ba clearer errors
"Not found" should only be for http path/method not found (404)
if it's about specific resources, we should be explicit for clarity
2015-07-08 08:55:31 +02:00
Dieter Plaetinck
ac37b54ddb fix datasource api docs
reflects change in e2f6633d57
2015-07-07 16:07:56 -07:00
Haneysmith, Nathan
56c332a3dd more test updates for influxdb quoting 2015-07-07 13:19:15 -07:00
Haneysmith, Nathan
cd53c78449 updating tests for influxdb quoting 2015-07-07 13:15:51 -07:00
Haneysmith, Nathan
f237cac074 add double quotes around tags 2015-07-07 11:56:36 -07:00
Torkel Ödegaard
f6ad386ba7 Fixed capital G in Graphite in docs, reviewed annotation docs, Closes #2275 2015-07-07 17:14:57 +02:00
Torkel Ödegaard
24e527ae02 Updated Graphite docs, Closes #2271 2015-07-07 14:50:23 +02:00
Torkel Ödegaard
8d7ac3862c Fixed issue with dashlinks/panellinks and sending template variable value 'All', Fixes #2297 2015-07-07 14:28:49 +02:00
Torkel Ödegaard
af50188a60 Removed alpha notice for influxdb 0.9 data source config 2015-07-06 17:16:17 +02:00
Torkel Ödegaard
572d35506b Removed some editor srv properites from data sources, they are not needed there anymore, they are in plugin.json, Closes #2283 2015-07-06 15:58:16 +02:00
Torkel Ödegaard
a4b8a88ae5 Worked on templating docs, Closes #2274 2015-07-06 15:01:57 +02:00
Torkel Ödegaard
dbd46a523f Work arround for slower template variable dropdown when variable has many thousand values, now only top 1000 values are rendered to html, you can still search all values, Closes #2246 2015-07-06 13:43:08 +02:00
Torkel Ödegaard
3c86c9908c Added docs for dashboard list panel, #2276 2015-07-06 12:24:11 +02:00
Torkel Ödegaard
37d75905ed Added min span option for panel repeater, #1888 2015-07-06 09:57:00 +02:00
Torkel Ödegaard
19812feb62 Increased max metric suggest count to 1000 for OpenTSDB, Closes #2281 2015-07-06 09:22:34 +02:00
Torkel Ödegaard
90ecb4c544 Merge pull request #2286 from awiddersheim/fix/parenthesis_typo
Fix 'paranthesis' typo to 'parenthesis'
2015-07-06 09:03:52 +02:00
Torkel Ödegaard
42926c48e5 Merge pull request #2287 from awiddersheim/feature/allow_pipe_graphite_lexer
Allow pipe ('|') in Graphite lexer
2015-07-06 09:00:43 +02:00
Andrew Widdersheim
e20e2117f7 Fix 'paranthesis' typo to 'parenthesis'
This typo is most noticable in the Graphite lexer error hint message.
2015-07-05 21:22:40 -04:00
Andrew Widdersheim
7f602feff4 Allow pipe ('|') in Graphite lexer
Allow for Graphite lexer to understand metric names like
'net|usage_average'.
2015-07-05 21:19:12 -04:00
Torkel Ödegaard
a79c1cb23f Merge pull request #2280 from Dieterbe/patch-1
fix config flag
2015-07-04 08:00:19 +02:00
Dieter Plaetinck
582ebf25f9 fix config flag 2015-07-03 17:27:55 -07:00
Torkel Ödegaard
896bbd00e1 Merge pull request #2279 from gcormier9/patch-2
Support dot (.) in metric name
2015-07-03 22:30:57 +02:00
gcormier9
4a9949e643 Support dot (.) in metric name
I'd like to update the regex in order to support dot in metric name. For example "cpu.usage.average".
2015-07-03 15:57:46 -04:00
Torkel Ödegaard
fc81cda971 Updated OpenTSDB docs, Closes #2272 2015-07-03 15:13:06 +02:00
Torkel Ödegaard
7a030e0c3b Finished improving InfluxDB docs, Closes #2270 2015-07-03 15:09:46 +02:00
Torkel Ödegaard
69c027156c Updated influxdb docs, adding docs specific to influxdb 0.9, #2270 2015-07-02 17:13:12 +02:00
Torkel Ödegaard
2ac07f12b0 Merge branch 'master' of https://github.com/williamjoy/grafana 2015-07-02 10:05:45 +02:00
Torkel Ödegaard
5494be4493 Some fixes related to OpenTSDB enhancements, #1250 2015-07-02 10:04:15 +02:00
Torkel Ödegaard
8ed0df64c7 Fixes to OpenTSDB datasource, and added support for repeat panels and repeat rows 2015-07-01 08:56:47 +02:00
Torkel Ödegaard
e8c65dc384 Updated OpenTSDB docs 2015-06-30 15:37:59 +02:00
Torkel Ödegaard
e508db994e OpenTSDB: Support for template variable values lookup queries, Closes #1250 2015-06-30 15:32:43 +02:00
Torkel Ödegaard
f7b7401a53 Updated changelog with new OpenTSDB enhancement, #1177 2015-06-30 12:34:57 +02:00
Torkel Ödegaard
f58e228d11 Merge branch 'lexh-restrict-tag-keys-values' 2015-06-30 12:28:01 +02:00
Torkel Ödegaard
97f54ac385 Small fix to PR 2119 2015-06-30 12:27:42 +02:00
Torkel Ödegaard
aedaae852b Fixed minor mistake in last commit 2015-06-30 12:14:13 +02:00
Torkel Ödegaard
ae0f8c77d1 Auth: You can now authenicate against api with username / password using basic auth, Closes #2218 2015-06-30 09:37:52 +02:00
Torkel Ödegaard
d0e7d53c69 Fixed case insensitive search for file based dashboards, Fixes #2258 2015-06-30 08:17:28 +02:00
Torkel Ödegaard
647e43dcbd Merge pull request #2259 from donnpebe/fix
Fix wrong metrics counter
2015-06-30 08:07:04 +02:00
Torkel Ödegaard
0d856cc135 Error message for missing dashboards.json config section, Closes #2256 2015-06-30 08:05:05 +02:00
Donn Pebe
df33cbc8c5 Fix wrong metrics counter 2015-06-30 12:52:55 +07:00
Torkel Ödegaard
a38a06a077 updated changelog with influxdb 0.9 support comment, Closes #1525 2015-06-29 16:07:58 +02:00
Torkel Ödegaard
15e6a4266c Fixed OR statement for influxdb 0.9 editor, #1525 2015-06-29 16:03:30 +02:00
Torkel Ödegaard
11170dd34c feat(singlestat): Added support for string values, Closes #2203 2015-06-29 09:22:08 +02:00
Torkel Ödegaard
68a45608e6 Merge branch 'master' of github.com:grafana/grafana 2015-06-29 09:02:39 +02:00
Torkel Ödegaard
0838f432ca refactor(influxdb series handling): performance and refactoring of PR #2179, also switched to InfluxDB epoch json format 2015-06-29 09:02:07 +02:00
Torkel Ödegaard
c0d2241523 Merge branch 'topic-multi-select-influxdb9' of https://github.com/randywallace/grafana into randywallace-topic-multi-select-influxdb9 2015-06-29 08:20:10 +02:00
Torkel Ödegaard
6e8505ae61 Merge pull request #2242 from sbouchex/master
Added Pressure units (mbar and hPa) and db unit
2015-06-29 08:14:40 +02:00
Torkel Ödegaard
aaeab3177e Merge pull request #2243 from craftytrickster/blank-variable-fix
Added fix for template variables when no options are available to select
2015-06-29 08:14:01 +02:00
Torkel Ödegaard
b18921cd07 Merge pull request #2244 from Dieterbe/saymynamesaymyname
clarify which handler is not found
2015-06-29 08:12:25 +02:00
Torkel Ödegaard
c776db5485 Merge pull request #2250 from jamtur01/panelmetacap
Capitalise panel menu items
2015-06-29 08:05:31 +02:00
James Turnbull
f14c6efaf8 Capitalise panel menu items
It's a minor thing but other options seem to be generally capitalised. This felt discordant.
2015-06-28 14:42:39 -04:00
Sébastien Bouchex Bellomié
9d4cce74b2 Added dB unit 2015-06-27 16:59:34 +02:00
Dieter Plaetinck
194273a643 clarify which handler is not found 2015-06-26 12:32:24 -07:00
Sébastien Bouchex Bellomié
05fc34533f Merge remote-tracking branch 'grafana/master' 2015-06-26 20:21:32 +02:00
David Raifaizen
ca42d976a7 Added fix for template variables when no options are available to select 2015-06-26 14:15:14 -04:00
Torkel Ödegaard
20d5d0eee6 Fixed issue with annotations that only occurs in optimized build, Fixes #2176, Fixes #2239 2015-06-26 19:42:33 +02:00
Torkel Ödegaard
693af182c7 Fixed broken playlist in master, Fixes #2240 2015-06-26 19:11:52 +02:00
Torkel Ödegaard
2dbb370955 Working on resize handle, drag to resize panels & rows 2015-06-26 18:45:23 +02:00
aba824a317 Added Pressure units (mbar and hPa) 2015-06-26 17:34:21 +02:00
5f513773c1 Added Pressure units (mbar and hPa) 2015-06-26 17:28:53 +02:00
999894cccb Added Pressure unit (mbar and hPa) 2015-06-26 17:22:41 +02:00
Torkel Ödegaard
906e70e599 Batch all influxdb 0.9 queries (per panel) in one request, #1525 2015-06-26 11:27:49 +02:00
Torkel Ödegaard
3f167d7fd1 more work on influxdb 0.9 query editor and query builder, can now handle regex conditions, tag operator and tag value quotes changes automatically depending if the tag value is a regex or not, #1525 2015-06-26 10:38:39 +02:00
Torkel Ödegaard
dd8d6bc705 Changed regex typeahead lookup, also works with literal regex patterns 2015-06-26 09:40:55 +02:00
Torkel Ödegaard
43afd7dff9 Fixed on blur event for influxdb 0.9 raw query editor, #1525 2015-06-26 07:54:55 +02:00
Torkel Ödegaard
9266d3789b Merge remote-tracking branch 'origin/kariosdb'
Conflicts:
	public/test/test-main.js
2015-06-25 18:34:41 +02:00
Torkel Ödegaard
dc70894d83 Merge pull request #2224 from masaori335/kairosdb-doc
Add docs of KairosDB Datasource Plugin
2015-06-25 17:08:03 +02:00
Torkel Ödegaard
24bab47f14 fix(create org): Anonymous user can no longer create org, Fixes #2227 2015-06-25 17:06:43 +02:00
Masaori Koshiba
74b8e8244a Add docs of KairosDB Datasource Plugin 2015-06-25 06:54:47 +09:00
Torkel Ödegaard
7806da11e6 Fixed issue with test data source connection and direct access, Fixes #2219 2015-06-24 21:31:16 +02:00
Torkel Ödegaard
ec98f9181b Fixed issue with new variable value select dropdown and parent -> child updates, Fixes #2215 2015-06-23 20:09:17 +02:00
Torkel Ödegaard
ba2deb5f7b Merge branch 'master' of github.com:grafana/grafana 2015-06-22 22:30:03 +02:00
Torkel Ödegaard
7794442277 Make influxdb 0.9 raw query mode still show alias input field, #1525 2015-06-22 22:29:49 +02:00
Torkel Ödegaard
fb8775124c Merge pull request #2207 from lahma/features/influxdb-scoped-vars
#1525 add support for scoped vars for InfluxDB 0.9
2015-06-22 21:26:09 +02:00
Marko Lahma
680865f13c #1525 add support for scoped vars for InfluxDB 0.9 2015-06-22 22:22:29 +03:00
Torkel Ödegaard
5373a699dd Merge branch 'master' of github.com:grafana/grafana 2015-06-22 18:56:12 +02:00
Torkel Ödegaard
177acf835c fixed mysql session example config 2015-06-22 18:47:46 +02:00
Torkel Ödegaard
f3ba491a73 Merge pull request #2205 from raventools/bug/make-status-command-return-correct-exitcode
make status command exit with proper code
2015-06-22 18:43:35 +02:00
Phil Dier
0e73c1bf78 make status command exit with proper code 2015-06-22 11:27:37 -05:00
Torkel Ödegaard
98a07eff81 Worked on enhancing panel links to support dash link features, like auto variable to url params includes, Closes #2200 2015-06-22 17:44:46 +02:00
Torkel Ödegaard
3f917b0af5 Updated org creation view, after creating new org user is switched to that org and redirect to org details view, Closes #2198 2015-06-22 08:13:21 +02:00
Torkel Ödegaard
d3c6db1ba8 Fixed tabindex for organization dropdown, Fixes #2150 2015-06-19 16:42:45 -04:00
Torkel Ödegaard
2a693f7f08 Fixed issue with multi select template variable and restoring state from url, #2196 2015-06-19 15:10:34 -04:00
Torkel Ödegaard
fb7b475457 Backend: Load dashboards with capital letters in the dashboard url slug (url id), Fixes #2163 2015-06-19 14:42:55 -04:00
Torkel Ödegaard
26e5d7a3de Graph: fixed PNG rendering of panels with legend table to the right, #2185 2015-06-19 14:13:25 -04:00
Torkel Ödegaard
cfeba99776 Merge branch 'patch-1' of https://github.com/greggsoft/grafana 2015-06-19 13:04:21 -04:00
Torkel Ödegaard
c0b4f7b6d2 updated graphite docs, Closes #2165 2015-06-19 13:03:09 -04:00
Torkel Ödegaard
021c6ac1cb fixed panel menu while in fullscreen mode 2015-06-19 12:42:10 -04:00
Torkel Ödegaard
261e5ebbd4 Fixed bug in singlestat panel and null value handling, #2189 2015-06-19 11:51:46 -04:00
Torkel Ödegaard
388fba4569 Merge pull request #2190 from zsxking/master
Update documents about environment variable override rules.
2015-06-18 16:04:37 -04:00
Shaoxuan Zhang
eda13a9626 Merge pull request #1 from zsxking/zsxking-docs-env-conf
Update configuration.md
2015-06-18 10:00:25 -10:00
Mikhail Menshenin
5da656e559 Missed double quote fix 2015-06-18 10:05:29 +05:00
Shaoxuan Zhang
d23762e06b Update configuration.md
Mention replacing `.` with `_` with environment variable override.
2015-06-17 17:57:23 -10:00
Randy D. Wallace Jr
5e6d876bd0 Support Multiple Fields in InfluxDB 0.9+
* Update influxdb 0.9 plugin to iterate thru fields returned by InfluxDB
  and output ea. field metric separately
* Updates default label to include Field Name
* Update spec to include updated label
* Add spec for multiple fields
* Do not print the field in the label when the name is value
2015-06-17 14:43:54 -04:00
Torkel Ödegaard
5e9c44b23b Merge pull request #2174 from raintank/add_robots_txt
add support for robots.txt file
2015-06-15 16:16:24 -07:00
Anthony Woods
196b7846cc add support for robots.txt file 2015-06-15 19:02:38 -04:00
Torkel Ödegaard
311946900e Fixed email subject test 2015-06-14 06:08:18 +02:00
Torkel Ödegaard
9b12350855 Added support for templated email subjects 2015-06-14 06:07:36 +02:00
Torkel Ödegaard
fd319bd5f5 Moved requirejs to bower, #677 2015-06-13 23:01:54 +02:00
Torkel Ödegaard
6f982b2791 Moved angular-bindonce to bower 2015-06-13 22:20:39 +02:00
Torkel Ödegaard
05f934548b Moved angular-native-dragdrop to bower 2015-06-13 22:17:28 +02:00
Torkel Ödegaard
f22bffe98a Upgraded angular to 1.4.0 and moved angular dependency to bower, #677 2015-06-13 21:14:08 +02:00
Torkel Ödegaard
87c65bcea0 Migrating to use bower for component dependencies 2015-06-13 20:53:08 +02:00
Torkel Ödegaard
6f4b206a99 Added bowerrc file 2015-06-13 20:43:12 +02:00
Torkel Ödegaard
2c88b418fd Fix import from elasticsearch limit, Fixes #1843, Fixes 2154 2015-06-13 20:20:48 +02:00
Torkel Ödegaard
66e60357dc Set email when creating user from auth_proxy header, Fixes #2156 2015-06-13 20:14:44 +02:00
Torkel Ödegaard
30db7e64fb Fixed example redis session config, Fixes #2157 2015-06-13 20:06:53 +02:00
Torkel Ödegaard
c6c733a40b Merge branch 'master' of github.com:grafana/grafana 2015-06-13 20:01:50 +02:00
Torkel Ödegaard
4a9bc70ca0 Added new style override, transform negative-Y, Closes #2162 2015-06-13 20:01:15 +02:00
Torkel Ödegaard
a815511aef Fixed failing unit test 2015-06-12 10:05:38 +02:00
Torkel Ödegaard
bae1e2f0c1 Fix for validating tags in dashboard json in backend, Fixes #2152 2015-06-12 09:04:10 +02:00
Torkel Ödegaard
7ce31bfaa0 changed dependency for gosimple/slug, they had finally removed the gopkgs dependency, Fixes #2153 2015-06-12 08:53:20 +02:00
Torkel Ödegaard
48175101c6 Fixes to template variable with tags and inital state after dashboard load, #2080 2015-06-11 13:38:13 +02:00
Torkel Ödegaard
e5280d55b3 Make template variables work in row titles, #1956 2015-06-11 09:35:59 +02:00
Torkel Ödegaard
3675b3fcaa Fixed issue with clearing admin permissions, Fixes #2143 2015-06-11 08:29:12 +02:00
Torkel Ödegaard
5962bcbc6c Do not show create org link when allow_org_create is set to false, Fixes #2135 2015-06-11 08:16:09 +02:00
Torkel Ödegaard
b39f8fb4de Do not show Switch to Org item for current organization in the org dropdown, Fixes #2136 2015-06-11 08:05:33 +02:00
Torkel Ödegaard
6deaa87a4c mention fpm in build from source docs, Closes #2145 2015-06-11 07:59:57 +02:00
Torkel Ödegaard
5562651ef0 Fixed spelling on graphite docs page 2015-06-10 17:03:16 +02:00
Torkel Ödegaard
2c00267d58 Changed to more strict sinon version to work around issue in latest sinon 2015-06-10 15:08:32 +02:00
Torkel Ödegaard
b5418c40ba fixed AMPQ -> AMQP typo 2015-06-10 14:44:42 +02:00
Torkel Ödegaard
2266ef6809 Minor api refactoring / code clean up 2015-06-10 10:15:42 +02:00
Torkel Ödegaard
ca67b49d67 Merge branch 'restrict-tag-keys-values' of https://github.com/lexh/grafana into lexh-restrict-tag-keys-values 2015-06-10 10:07:23 +02:00
Torkel Ödegaard
63cc794f37 Small style fix to login page 2015-06-10 08:27:38 +02:00
Torkel Ödegaard
fca6c85abb Fixed clear selections icon for new variable dropdown, #2080 2015-06-09 10:30:51 +02:00
Torkel Ödegaard
604f8a22f7 Fixed state issue with variable value dropdown, #2080 2015-06-09 10:22:43 +02:00
Torkel Ödegaard
f6845cd107 Variable value select fixes and refactorings 2015-06-09 10:06:36 +02:00
Torkel Ödegaard
c20fa85b82 Renamed variable value select to select dropdown 2015-06-09 09:45:24 +02:00
Torkel Ödegaard
bb2d810709 Final work password reset email, Closes #1456 2015-06-08 18:25:04 +02:00
Torkel Ödegaard
95fd34ed14 Merge branch 'emailing' 2015-06-08 17:57:33 +02:00
Torkel Ödegaard
db0c442eaf Added configuration options for smtp 2015-06-08 17:56:56 +02:00
Torkel Ödegaard
42fc68baa5 Simplified emailing system and combined mailer and notifications packages 2015-06-08 16:51:25 +02:00
Torkel Ödegaard
c8bc0b3bf8 Lots of work on user password reset, #1456 2015-06-08 13:39:02 +02:00
Torkel Ödegaard
aa4d60c21e Worked on reset password views, refactored out password strength to a reusable directive 2015-06-08 10:57:01 +02:00
Torkel Ödegaard
4f0a4ea7e7 Merge pull request #2104 from masaori335/kairosdb-metric-find-query
Add metricFindQuery in KairosDB Datasource Plugin
2015-06-06 08:50:02 +02:00
Torkel Ödegaard
e40b462040 began work on reset password view, and angular controller 2015-06-05 11:45:04 +02:00
Torkel Ödegaard
89418a155a More work on email and notification infra #1456 2015-06-05 11:08:19 +02:00
Torkel Ödegaard
901685bb63 Updated sample config and config docs with example for postgres session provider_config string, #2118 2015-06-05 08:18:37 +02:00
Torkel Ödegaard
c709a28f02 More work on email and notification infra #1456 2015-06-05 08:15:38 +02:00
Lex Herbert
463a750c07 OpenTSDB: Restrict typeahead tag keys and values
When selecting metric tag keys, we only are interested in keys which are
associated with this metric. Likewise, when selecting a value for a
certain key, we only want to consider values which apply to the given
key and metric.
2015-06-04 17:45:09 -07:00
Torkel Ödegaard
10abf61383 Merge pull request #2115 from tmonk42/exit_codes
fixing up init script exit codes
2015-06-04 21:06:30 +02:00
Haneysmith, Nathan
fcba62894b remove trailing exit 0 from deb init 2015-06-04 11:40:49 -07:00
Haneysmith, Nathan
77215182f7 fixing up init script exit codes 2015-06-04 11:01:04 -07:00
Torkel Ödegaard
3706d0dedd More work on email and notification infra #1456 2015-06-04 17:23:46 +02:00
Torkel Ödegaard
3f5ab189cd Began work on emailing service #1456 2015-06-04 14:29:39 +02:00
Torkel Ödegaard
eb793f7feb Initial work on ldap support, #1450 2015-06-04 09:34:42 +02:00
Masaori Koshiba
4d0c04b80d Add tag support 2015-06-04 01:23:58 +09:00
Masaori Koshiba
e9a842259a Add metricFindQuery in KairosDBDatasource 2015-06-03 22:55:31 +09:00
Torkel Ödegaard
2c7d33cdfa Merge branch 'master' into ldap 2015-06-03 14:54:48 +02:00
Torkel Ödegaard
83279604c6 updated search handler 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
c821568ac7 Merged new template dropdown 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
1c38619d8c Fixed intendation 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
8cfe89eaf9 Updated variable dropdown 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
cb63344394 Added hover tooltip for tags 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
8934c83742 Small fixes for template variable groups (tags) 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
b0451dc1b3 More refinements of tag selection and state restoration after dashboard load 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
b5a846154a Trying to make progres on persisting selection state, restoring selection state for new multi variable dropdown, proving to be really complex 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
f48d0fcb13 More work on tags in variable dropdown, now the actual tag values query is hooked up and works, #2080 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
650d3d5046 Fixed tag selection issues 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
0992b6a616 Progress on template variable dropdown, lots of unit tests for selection behavior, 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
53ed407448 Lots of refactoring of variable dropdown trying to clean up code and make it unit testable 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
d96f5598ca Breaking out logic for variable dropdown into a controller, this needs some unit tests 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
137cbe5bb4 work on variable multi select 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
6f783f5a3b Single variable select state highlight fixed, Closes #2073 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
6ed17fe62f Removed selection state for single select variables 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
7d25d6f191 progress on tag selection in variable dropdown 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
0bd50c06d7 Template varaible dropdown work 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
9a74105103 Trying out dark headers and footers for template dropdown 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
a433e0e79c More work on variable dropdown 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
483ef20527 Reworking variable value dropdown, take3 2015-06-03 14:53:52 +02:00
Torkel Ödegaard
c5d3eb53f0 Merge pull request #2098 from pborreli/typos
Fixed typos
2015-06-02 18:31:14 +02:00
Pascal Borreli
f582ac88b3 Fixed menu 2015-06-02 17:12:12 +01:00
Pascal Borreli
7b69b789b8 Fixed typos 2015-06-02 16:45:44 +01:00
Torkel Ödegaard
5c8b571c3f Fixed look of tag cloud in search 2015-06-02 13:44:46 +02:00
Torkel Ödegaard
bfe5a56a47 Updated dashboard links editor, just changed With tag > With tags 2015-06-02 12:52:31 +02:00
Torkel Ödegaard
8cfbd2f8bf Increased width of query input field in dashlist panel editor 2015-06-02 11:05:48 +02:00
Torkel Ödegaard
50a1feb90a Dashboard list panel: Now supports search by multiple tags, Closes #2096 2015-06-02 11:04:06 +02:00
Torkel Ödegaard
6df9012141 Updated dashboard links feature to support search by my multiple tags, #1944 2015-06-02 10:35:10 +02:00
Torkel Ödegaard
dc607b8e8a Dashboard search now supports filtering by multiple dashboard tags, Closes #2095 2015-06-02 10:24:20 +02:00
Torkel Ödegaard
1a71da417c Merge pull request #2092 from robertjakub/master
new role Read Only Editor - admin (small fix for #2088)
2015-06-02 09:00:47 +02:00
Torkel Ödegaard
2446168356 Sort tags in search results, Closes #2091 2015-06-02 09:00:37 +02:00
robert jakub
153ab4afaa new role Read Only Editor - admin (small fix for #2088) 2015-06-01 20:26:41 +02:00
Torkel Ödegaard
ff3843bc7f Roles: New user role that replaces the old role behavior, Closes #2088 2015-06-01 17:01:04 +02:00
Torkel Ödegaard
86f5152092 When role is viewer and edit URL is loaded view mode will be loaded instead, Closes #2089 2015-06-01 16:49:14 +02:00
Torkel Ödegaard
83e7c48767 User role 'Viewer' are now prohibited from entering edit mode (and doing other transient dashboard edits). A new role will replace the old Viewer behavior 2015-06-01 16:39:01 +02:00
Torkel Ödegaard
85c3a0aa14 Panel menu now hides edit actions for users with role Viewer, Closes #1826 2015-06-01 16:36:15 +02:00
Torkel Ödegaard
50645cc36b Added test connection action to all data sources, Closes #1997 2015-06-01 14:16:59 +02:00
Torkel Ödegaard
afede880e6 added url validation when adding data source, Fixes #2043 2015-06-01 14:02:24 +02:00
Torkel Ödegaard
e2f6633d57 Began work on data source test / validation, #1997 & #2043 2015-06-01 12:15:49 +02:00
Torkel Ödegaard
fc43ce657c allow data source proxy to proxy requests over self signed https connections, Closes #2069 2015-06-01 11:00:29 +02:00
Torkel Ödegaard
8f9eeb5ebd Merge pull request #2067 from bcandrea/private-email-in-github-oauth
Use private email address in GitHub OAUTH
2015-05-29 08:22:46 +02:00
Torkel Ödegaard
db2ff68fef Merge pull request #2072 from yinchuan/patch-1
update docs for configration: allowed_domains
2015-05-29 08:22:09 +02:00
yinchuan
ed974a808b Update configuration.md 2015-05-29 11:39:59 +08:00
yinchuan
0108dfa803 Update configuration.md 2015-05-29 11:23:14 +08:00
Andrea Bernardo Ciddio
cf147cdeaf GitHub users without a public email should be authenticated using their primary private email address 2015-05-28 16:47:20 +01:00
Torkel Ödegaard
57fac6b9aa Removed invalid scripted dashboard example, Closes #2063 2015-05-28 11:41:32 +02:00
Torkel Ödegaard
aeb8bc8755 Share link should always have absolute time range, Closes #2060 2015-05-28 09:35:55 +02:00
Torkel Ödegaard
96bd66e811 Made the annotation tags support more cross datasource compatible 2015-05-27 14:30:23 +02:00
Torkel Ödegaard
494ede5bbf Big refactoring/rewrite for how annotation tooltips are shown, also work on #1474 2015-05-27 13:11:32 +02:00
Torkel Ödegaard
e76d2ec9c2 worked on tags in search and dashboards 2015-05-27 11:16:15 +02:00
Torkel Ödegaard
7b972926ae Merge branch 'variable-tags' 2015-05-27 10:43:02 +02:00
Torkel Ödegaard
4a71a79cb8 Slight refactoring in annotationSrv 2015-05-27 10:42:52 +02:00
Torkel Ödegaard
99556057d1 Fixed bug with auto refresh option being reset when changing relative time when now delay is configured, Fixes #2042 2015-05-26 16:48:23 +02:00
Torkel Ödegaard
921d672a2c Worked on alias pattern for InfluxDB 0.9 editor, #1525 2015-05-26 11:01:28 +02:00
Torkel Ödegaard
5f004f95f2 Fixed issue with json dashboard index, json file dashboards turned up when filtering on 'is starred' 2015-05-25 15:42:59 +02:00
Torkel Ödegaard
284106b8a8 Updated changelog 2015-05-25 15:26:22 +02:00
Torkel Ödegaard
abed438d7d Merge pull request #2052 from indrekj/org-support
Add github organizations support
2015-05-25 14:16:46 +02:00
Torkel Ödegaard
0047ce067d SingleStatPanel: fix for color thresholds and value to text mapping combo, Fixes #2044 2015-05-25 10:24:01 +02:00
Torkel Ödegaard
58f743f87d Merge pull request #2053 from blt04/phantomjs-tls
Render panel images with any SSL protocol
2015-05-25 09:57:56 +02:00
Torkel Ödegaard
867e333464 Merge pull request #2045 from robertjakub/master
add pps (packets per second) format
2015-05-25 09:56:41 +02:00
Torkel Ödegaard
de683b3360 Merge pull request #2030 from felixrabe/patch-1
Fix latest.json
2015-05-25 09:56:10 +02:00
Brandon Turner
b2a0ae0f83 Render panel images with any SSL protocol
This uses any available SSL protocol (instead the phantomjs default:
SSLv3) to render panels to PNGs.  This is useful when reverse proxing
grafana and SSLv3 is disabled due to security vulnerabilities or other
reasons.
2015-05-25 01:52:49 -05:00
Indrek Juhkam
b55d9350e7 Add github organizations support 2015-05-23 17:06:51 +03:00
robert jakub
3dc2a114fa add pps (packet per second) format 2015-05-22 17:08:10 +02:00
Torkel Ödegaard
57bacb339c Merge pull request #2038 from craftytrickster/annotation-fix
Fixed variable name for annotations
2015-05-22 06:34:17 +02:00
David Raifaizen
2191921690 Fixed variable name for annotations 2015-05-21 14:36:35 -04:00
Torkel Ödegaard
23fcf85500 Merge pull request #2034 from kkoehler/api-doc
Api doc
2015-05-21 13:28:08 +02:00
Kristian Köhler
3c6b647398 added admin stuff 2015-05-21 12:56:30 +02:00
Kristian Köhler
1821809e69 listing of all none admin methods 2015-05-21 12:46:26 +02:00
Torkel Ödegaard
ba3f6f9d3e InfluxDB 09 fix, do not treat empty results as an error 2015-05-21 10:20:49 +02:00
Felix Rabe
9bedd83f39 Fix latest.json
The trailing comma trips up both Python and JS JSON parsers.
2015-05-21 00:13:50 +02:00
Torkel Ödegaard
592330b5a7 Expose data source extended properties (jsonData), to the frontend, Closes #2023 2015-05-20 18:29:20 +02:00
Torkel Ödegaard
2202094e41 work on tags in variables 2015-05-20 18:18:03 +02:00
Torkel Ödegaard
3354365177 Api handler refactoring using the wrap and response func/type, fixed small issue in influxdb 0.9 response handling 2015-05-20 14:59:38 +02:00
Torkel Ödegaard
a39e484aaa Merge pull request #2007 from anryko/master
Update latest.json
2015-05-20 09:04:06 +02:00
Torkel Ödegaard
51af06edf7 Merge pull request #2022 from dalmatinerdb/proxy-url-fix
fixing #2021
2015-05-20 09:03:23 +02:00
Heinz N. Gies
7cf9046dc5 fixing #2021
This tiny commit fixes a routing problem for proxy requests. The
router was expecting a url path behind the proxy id, if none was
given (read: the service proxied was at the root of a host) then
the router would return a 404. With this patch it now propperly
uses ProxyDataSourceRequest.
2015-05-19 23:49:30 +02:00
Torkel Ödegaard
25c6b64fed Began working on exploring variable value groups/tags 2015-05-19 16:54:08 +02:00
Torkel Ödegaard
a8aab0cb2b Worked on user administration page, a grafana server admin can now add and edit organization roles for any user, #2014 2015-05-19 11:47:14 +02:00
Torkel Ödegaard
788e7fd36d Lots of api refactoring for org routes, #2014 2015-05-19 10:16:32 +02:00
Torkel Ödegaard
74bf1f23fb Small progress on #2014 2015-05-19 09:09:21 +02:00
Torkel Ödegaard
bf9e51928d Fix to signed in user when user <-> org link is gone 2015-05-19 09:02:37 +02:00
Torkel Ödegaard
bfe6d5434e Fixed placeholder text in templating editor 2015-05-19 08:46:45 +02:00
Torkel Ödegaard
f81bde5643 Refactoring some api handlers to use the new Response return object 2015-05-18 21:23:40 +02:00
Torkel Ödegaard
fbc6bb2112 More refactoring of user http api, trying to reuse handlers for sign in user and admin operations 2015-05-18 19:06:19 +02:00
Torkel Ödegaard
62e8841e8c Fixed spelling of Peta unit Quadr 2015-05-18 17:54:12 +02:00
Torkel Ödegaard
5270c4bc74 refactorin api code for user routes, preparation for admin improvements, #2014 2015-05-18 17:52:23 +02:00
Torkel Ödegaard
a993bc1331 Merge pull request #2012 from Dieterbe/publish-no-recurse
no unbound recursion in publish()
2015-05-18 16:16:08 +02:00
Dieter Plaetinck
e9f38b9fc0 no unbound recursion in publish()
unbound recursion approach can blow up call stack,
and - I think - allocate memory unboundedly as well.

We can simply loop until err != nil

I didn't actually test this live, though tests succeed
2015-05-18 10:04:01 -04:00
Torkel Ödegaard
5896903bd3 Began work on alias support and alias patterns for InfluxDB 0.9, #1525 2015-05-18 15:01:05 +02:00
Torkel Ödegaard
f41c314329 Minor layout fix to templating editor, making input fields a little less wide 2015-05-18 11:47:12 +02:00
Torkel Ödegaard
84618a6076 Style and layout changes for InfluxDB 0.9 editor #1525 2015-05-18 11:40:50 +02:00
Torkel Ödegaard
b61f30de94 Lots of progress on InfluxDB Query Editor, show tag keys, values and measurement dropdowns now take into account already selected tags or measurements, so you can start with tag keys and the select measurement, #1525 2015-05-18 10:49:45 +02:00
Torkel Ödegaard
afe76ba260 Merge pull request #2009 from fffw/patch-1
fix mac.md for incorrect href
2015-05-18 09:10:04 +02:00
fffw
1302630abe fix mac.md for incorrect href 2015-05-18 14:47:00 +08:00
anryko
a6d993ecdc Update latest.json 2015-05-17 18:12:20 +02:00
Torkel Ödegaard
65796d7477 Merge pull request #2005 from masaori335/kairosdb-template
Templated dashboards support in KairosDB Plugin
2015-05-17 15:56:52 +02:00
Masaori Koshiba
41a73a7adb Expand variables in KairosDB Plugin 2015-05-17 21:46:14 +09:00
Torkel Ödegaard
05d725d0b8 Slight refactoring, #1525 2015-05-16 22:37:08 +02:00
Torkel Ödegaard
7a41ecb63f Minor work on InfluxDB 0.9 query editor, #1525, hit a roadblock with the new InfluxDB query language, does not support tag and tag values discovery queries with filters, not sure if I need to move to SHOW SERIES style queries 2015-05-16 22:37:08 +02:00
Torkel Ödegaard
6fd37779b8 More work on new influxdb query editor, #1525 2015-05-16 22:37:08 +02:00
Torkel Ödegaard
d14b570d76 Merge pull request #2003 from tmonk42/redact_provider_config
Addresses #1853, redact session provider secrets
2015-05-16 06:44:29 +02:00
Haneysmith, Nathan
2af28b90c2 whitespace update per gofmt 2015-05-15 13:25:41 -07:00
Haneysmith, Nathan
09b0e6e388 Addresses #1853, redact session provider secrets
In cases where a database is used for session storage, redact the
session_provider config value. I assumed "@" as the marker for a
database vs file/memory.
2015-05-15 11:11:02 -07:00
Torkel Ödegaard
5ca8d590bd Working on new query editor for influxdb 0.9, looking good! #1525 2015-05-15 15:58:07 +02:00
Torkel Ödegaard
11c8e80ea9 Working on new query editor for influxdb 0.9, #1525 2015-05-15 11:38:37 +02:00
Torkel Ödegaard
ea993b6404 Some inital ldap work 2015-05-15 09:37:16 +02:00
Torkel Ödegaard
a258e2e608 Added log base 2 graph y axis scale 2015-05-14 17:39:31 +02:00
Torkel Ödegaard
e69bacaee1 Merge branch 'color_by_regex' 2015-05-14 12:34:37 +02:00
Torkel Ödegaard
8c0e1060e6 Graph: Define series color using regex rule, Closes #590 2015-05-14 12:34:30 +02:00
Torkel Ödegaard
fb56bc59f2 Create pid file before runtime init, Fixes #1990 2015-05-14 10:33:56 +02:00
Torkel Ödegaard
1c9993f185 Backend configuration: When unknown key was detected the log error was not visible in log file due to logging was initialized after user config file is loaded, logging is now initialized twice, once based on defaults and command line arguments, then then again after user config file, Fixes #1992 2015-05-14 10:15:46 +02:00
Torkel Ödegaard
2dac975805 began work on color by regex / series override for color, #590 2015-05-13 21:34:50 +02:00
Torkel Ödegaard
2bb63835b6 Merge pull request #1991 from jeffbmartinez/fixGrammarInDocs
Fix minor grammar issue in docs index page
2015-05-13 19:30:29 +02:00
jeff martinez
bfafb83604 Fix minor grammar issue in docs index page 2015-05-13 09:58:58 -07:00
Torkel Ödegaard
ea54f6923b Added graphite function changed, closes #1961 2015-05-13 16:32:33 +02:00
Torkel Ödegaard
1a401780ba Updated changelog with completed story, JSON dashboards added to search, Closes #960 2015-05-13 14:01:48 +02:00
Torkel Ödegaard
448a8b8d1c Major refactorings around searching, moved to seperate package, trying to move stuff out of models package, extend search support searching different types of entities and different types of dashboards, #960 2015-05-13 13:36:24 +02:00
Torkel Ödegaard
c8146e759f Fixed json index unit test 2015-05-13 10:57:18 +02:00
Torkel Ödegaard
bb7d79e6d2 Refactoring search to support more than just db dashboards 2015-05-13 10:45:53 +02:00
Torkel Ödegaard
fb35f7210c Fixed failing golang test 2015-05-13 10:06:06 +02:00
Torkel Ödegaard
187834b17c Trying to get dashboard loading and dashboard meta flags like canSave, canStar more managable 2015-05-13 09:58:45 +02:00
Torkel Ödegaard
4822d02787 Things are starting to work again 2015-05-12 19:24:11 +02:00
Torkel Ödegaard
35cc0a1cc0 Rewriting anb abstracting how dashboards are loaded, unifying db, json files, and script dashboards, #960 2015-05-12 17:39:56 +02:00
Torkel Ödegaard
b3be51f17f Lots of work on search and dashboard loading, trying to generalize concepts and code, #960 2015-05-12 14:11:30 +02:00
Torkel Ödegaard
a40299b4dc Progress on json file index and search #960 2015-05-12 12:20:03 +02:00
Torkel Ödegaard
1a9f630710 Began work on adding json files to search, #960 2015-05-11 20:04:03 +02:00
Torkel Ödegaard
48ddd7213f Removed unused keybinding, ctrl+l, Fixes #1962 2015-05-11 10:03:34 +02:00
Torkel Ödegaard
582e680b12 Singlestat: threshold checks are now done on a rounded value, rounded to same number of decimals as is shown, Fixes #1983 2015-05-11 09:57:10 +02:00
Torkel Ödegaard
a04a403539 Merge branch 'docs-2.0'
Conflicts:
	docs/sources/installation/configuration.md
	docs/sources/installation/docker.md
2015-05-11 08:02:28 +02:00
Torkel Ödegaard
564162442d Merge pull request #1982 from jamtur01/conflink
Fixed a series of installation doc issues
2015-05-11 07:57:10 +02:00
James Turnbull
7da3ee72c7 More docs fixes
1. Set paragraph size to 80 chars for better readability.
2. More formatting fixes.
3. More spelling and grammar fixes.
2015-05-10 16:52:40 -04:00
James Turnbull
823294f055 Fixed a series of installation doc issues
1. Fixed several spelling and grammar issues.
2. Fixed 404s for the Configuration page.
3. Fixed several Markdown formatting issues.
4. Tidy up of several pages.
2015-05-10 16:15:34 -04:00
Torkel Ödegaard
5aa495d0e4 Restored some previous grafana modifications to jquery flot 2015-05-09 19:47:21 +02:00
Torkel Ödegaard
2e6c2347eb Did some jquery flot performance tuning, added text size cache that reduced render times by 25% (for single serie graphs) 2015-05-09 19:31:00 +02:00
Torkel Ödegaard
a256d71ef9 Merge pull request #1954 from jtyr/patch-1
Adding Ansible role for Grafana v2.x
2015-05-09 15:34:01 +02:00
Torkel Ödegaard
b7a8551290 Merge pull request #1946 from DaveBlooman/theme_colour
Add Android 5 theme colour
2015-05-09 15:33:38 +02:00
Torkel Ödegaard
8f0269de76 Merge branch 'subsecond-influx' of https://github.com/abrander/grafana 2015-05-09 15:28:39 +02:00
Torkel Ödegaard
1a6e6f405d Merge branch 'master' of github.com:grafana/grafana 2015-05-09 15:28:23 +02:00
Torkel Ödegaard
59a2109cab Investigating some performance optimizations, added more perf instrumentation & measurements 2015-05-09 15:26:39 +02:00
Torkel Ödegaard
31781bc509 Fixed the link to the config page 2015-05-08 11:23:48 +02:00
Torkel Ödegaard
db2161c7e6 Merge pull request #1967 from benheilman/detach_stderr_in_init_script
Detach stderr in rpm initscript
2015-05-08 11:19:14 +02:00
Torkel Ödegaard
7d4e676cfd Dashboard: Custom Navigation links & dynamic links to related dashboards, #1944 2015-05-08 11:04:46 +02:00
Torkel Ödegaard
2dcb98a8ed Merge branch 'master' of github.com:grafana/grafana 2015-05-08 11:03:25 +02:00
Torkel Ödegaard
0bde1bb857 Dashboard and nav links are near completion now, #1944 2015-05-08 10:56:54 +02:00
Anders Brander
d685644dee Updated test to match e906a19 2015-05-07 22:48:44 +02:00
Anders Brander
e906a1979c Allow kbn.secondsToHms() to return result in milliseconds. 2015-05-07 22:33:32 +02:00
Ben Heilman
0a9958c74e Detach stderr in rpm initscript 2015-05-07 14:47:27 -05:00
Torkel Ödegaard
04f9a6fdd0 Merge pull request #1963 from abrander/measurement-in-quotes
Quote 'measurement' properly in getAltSegments() (influxdb 0.9)
2015-05-07 20:29:34 +02:00
Anders Brander
5eee33a11f Quote 'measurement' properly in getAltSegments() to allow for dots in measurement identifier (Influxdb 0.9). 2015-05-07 19:51:55 +02:00
Torkel Ödegaard
4b4f398e83 more work on dashboard links, this feature is taking forever, dam angular, and dam complexity 2015-05-07 19:14:13 +02:00
Torkel Ödegaard
9655014041 More polish and refinements to dashboard links 2015-05-07 13:10:04 +02:00
Torkel Ödegaard
85e65e61d3 More work on dashboards links 2015-05-07 09:35:39 +02:00
Jiri Tyr
e32259ccc9 Adding Ansible role for Grafana v2.x
Adding Ansible role which is compatible with Grafana v2.x.
2015-05-07 00:31:20 +01:00
Torkel Ödegaard
15137422a6 More work on dashboards links 2015-05-06 16:40:43 +02:00
Torkel Ödegaard
1f48c07395 more work on dash links 2015-05-06 10:57:32 +02:00
Torkel Ödegaard
26c9a19392 A lot of css style polish for submenu (template var selection, and new dashlinks) 2015-05-06 09:41:30 +02:00
Torkel Ödegaard
2c3aad785d Fix in unsaved changes service 2015-05-06 07:59:47 +02:00
Torkel Ödegaard
ece9e8dcea Merge branch 'master' into dashlinks 2015-05-06 07:48:45 +02:00
DaveBlooman
85c45c2b8f Add Android 5 theme colour 2015-05-05 22:42:56 +01:00
Torkel Ödegaard
8149381f01 minor refactoring and markup fix 2015-05-05 20:59:21 +02:00
Torkel Ödegaard
e221cbfbd6 Merge branch 'sidemenu_default_open' of https://github.com/raintank/grafana 2015-05-05 20:34:07 +02:00
Torkel Ödegaard
0c56ae4f32 more work on, #1944 2015-05-05 20:33:06 +02:00
Torkel Ödegaard
1e196fc091 Began work on dashboard links and nav feature, #1944 2015-05-05 19:56:49 +02:00
Torkel Ödegaard
d4c0d5d81b changed position for checkbox label 2015-05-05 13:37:20 +02:00
Torkel Ödegaard
12c770f0ab markup update for templating editor 2015-05-05 13:33:57 +02:00
Torkel Ödegaard
637988b104 UI Polish, refactoring markup 2015-05-05 13:26:42 +02:00
Torkel Ödegaard
71edd9ff1b DashEditView: fix for small ui bug, when having dashboard settings view open and entering panel edit mode, did not cause dash edit view to be closed 2015-05-05 12:38:18 +02:00
Torkel Ödegaard
34539c0c13 Enforce domain, host header validation against domain setting, Refactoring of PR #1866, Closes #1732 2015-05-05 11:21:06 +02:00
Torkel Ödegaard
1f2cba81eb Merge branch 'enforce-domain' of https://github.com/gummiboll/grafana 2015-05-05 11:15:09 +02:00
Torkel Ödegaard
e458ca7b47 Templating: updated template editor view & form 2015-05-05 10:56:39 +02:00
Anthony Woods
b915bb98fb fix whitespace 2015-05-05 16:21:37 +08:00
Anthony Woods
8aef297e71 show sidemenu when admin user logs in the first time. #54 2015-05-05 10:43:19 +08:00
Torkel Ödegaard
c628ac9bbb Upgraded to JQuery 2.1.1 -> 2.1.3, and spectrum.js, Fixes #1932 2015-05-04 14:36:46 +02:00
Torkel Ödegaard
385048b620 Templating: You can now select multiple template variables values at the same time. Closes #1922 2015-05-04 10:13:12 +02:00
Torkel Ödegaard
e5c1169120 HTTP API: GET /api/dashboards/db/:slug response changed property to to match the POST request nameing, Fixes #1928 2015-05-04 08:36:44 +02:00
Torkel Ödegaard
38d851eb98 Another HTTP API fix 2015-05-04 08:19:29 +02:00
Torkel Ödegaard
73ee8a5985 HTTP API: fix for POST /api/dashboards/db returned 200 ok when dashboard was not found, Fixes #1929 2015-05-04 07:46:53 +02:00
Torkel Ödegaard
020d7d4939 Update docs, fixed mistake in http api docs, Fixes #1928 2015-05-03 16:46:46 +02:00
Torkel Ödegaard
697529d0e8 Worked on golang code coverage via coveralls 2015-05-02 17:24:03 +02:00
Torkel Ödegaard
b51a8f11d5 Added back coveralls coverage 2015-05-02 17:05:07 +02:00
Torkel Ödegaard
a4a8cd05d2 Updated to nodejs packages and karma code coverage setup 2015-05-02 16:59:19 +02:00
Torkel Ödegaard
38fc85d619 Final tweaks to auth proxy feature 2015-05-02 12:30:53 +02:00
Torkel Ödegaard
be589d81c7 Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER), Closes #1921 2015-05-02 12:06:58 +02:00
Torkel Ödegaard
ba883d25fe More middleware unit tests cover all current auth mechanisms 2015-05-02 09:24:56 +02:00
Torkel Ödegaard
f416e2d1ac More middleware unit test, starting to look really good 2015-05-01 22:26:16 +02:00
Torkel Ödegaard
cb8110cd48 Refactoring, worked on middleware unit tests, and began thinking about api unit tests, #1921 2015-05-01 16:23:36 +02:00
Torkel Ödegaard
8d081a081d Fixed configuration docs issue, Fixes #1924 2015-05-01 15:01:31 +02:00
Torkel Ödegaard
d1e9b6d6ae Began work on auth_proxy feature (#1932), and began work on testing http api, and auth middleware 2015-05-01 11:55:59 +02:00
Torkel Ödegaard
e7ac367392 SignUp: password strength meter updated, Fixes #1892 2015-05-01 11:22:21 +02:00
Anthony Woods
7a8851c5ab default sidemenu to open #54 2015-05-01 17:02:00 +08:00
Torkel Ödegaard
e771d8e944 Organization: You can now update the organization user role directly (without removing and readding the organization user). Closes #1899 2015-05-01 09:48:07 +02:00
Torkel Ödegaard
b48b11e902 Added new graphite function spec, weightedAverage, Closes #1879 2015-05-01 08:52:34 +02:00
Torkel Ödegaard
5de9230924 Security: New config option to disable the use of gravatar for profile images, Closes #1891 2015-05-01 08:40:13 +02:00
Torkel Ödegaard
59fcd3914d corrected master version 2015-05-01 08:08:31 +02:00
Torkel Ödegaard
95fcddcd95 More work on panel & row repeats, #1888, updated changelog 2015-05-01 08:07:57 +02:00
Torkel Ödegaard
293d0c3093 Merge branch 'panel_repeat' 2015-04-30 11:15:40 +02:00
Torkel Ödegaard
e17f56b4ad Final polish on panel & row repeats, #1888, still some missing places where scopedVars needs to be used 2015-04-30 11:15:26 +02:00
Torkel Ödegaard
32e0ce1beb Minor code refinements to panel repeat code, #1888 2015-04-30 10:50:23 +02:00
Torkel Ödegaard
25ef49494b Final polish on repeat panel variable selection, #1888 2015-04-30 10:47:51 +02:00
Torkel Ödegaard
3ee1ea28e1 Templating: Support for search filtering and keyboard up/down filtering in the new multi variable value selector dropdown, #1144 2015-04-30 09:09:12 +02:00
Torkel Ödegaard
0a8cf26731 Merge branch 'docs-2.0' 2015-04-30 08:12:37 +02:00
Morton Fox
a8b2074fc6 Fix the first CLA link. 2015-04-30 08:05:13 +02:00
Torkel Ödegaard
14be5e3ff7 Merge branch 'master' of github.com:grafana/grafana 2015-04-30 08:04:32 +02:00
Torkel Ödegaard
6de071a5f3 Merge pull request #1911 from mortonfox/patch-1
Fix the first CLA link.
2015-04-30 08:03:05 +02:00
Morton Fox
0a2b03ba52 Fix the first CLA link. 2015-04-29 16:35:03 -04:00
Torkel Ödegaard
dbc64c21a7 Merge branch 'master' into panel_repeat 2015-04-29 18:03:26 +02:00
Torkel Ödegaard
294f7f1f0d Merge branch 'v2.0.x'
Conflicts:
	conf/sample.ini
2015-04-29 15:59:35 +02:00
Torkel Ödegaard
fe0bf876d9 Style changes and polish to multi variable value selection, #1144 2015-04-29 15:34:14 +02:00
Torkel Ödegaard
c5be95e46c Began polish and tweaks of new template variable multi select dropdown 2015-04-29 14:23:29 +02:00
Torkel Ödegaard
b762f56aee Merge branch 'master' into panel_repeat
Conflicts:
	public/app/features/templating/templateValuesSrv.js
2015-04-29 13:59:15 +02:00
Torkel Ödegaard
74a8fa61f2 Merged with 2.0 docs and restored some stuff 2015-04-29 10:33:37 +02:00
Torkel Ödegaard
964d47d517 Merge branch 'docs-2.0' 2015-04-29 10:30:29 +02:00
Torkel Ödegaard
a38da2f7c6 Merge pull request #1901 from davidak/master
add gigabytes as unit
2015-04-29 10:29:49 +02:00
Torkel Ödegaard
593f2e0851 Merge pull request #1903 from craftytrickster/influx09-annotation-bugfix
Fixed Annotations with InfluxDb9 Datasources
2015-04-29 10:29:29 +02:00
Torkel Ödegaard
9ae3d66da7 Fixed docs, screencasts page 2015-04-29 10:18:11 +02:00
Torkel Ödegaard
4c7545e909 Updated docs Makefile 2015-04-29 10:14:02 +02:00
Torkel Ödegaard
a0e80e5869 Updated 2.0 docs, merged with master and removed docs for features in 2.1, doc updates related to 2.0 needs to be done to this branch 2015-04-29 10:13:00 +02:00
Torkel Ödegaard
75e6947c5a Merge branch 'master' into docs-2.0 2015-04-29 10:11:52 +02:00
Torkel Ödegaard
3007add4ca small docs fix 2015-04-29 10:10:44 +02:00
Torkel Ödegaard
67fbb173e3 Merge pull request #1904 from iandanforth/patch-1
Correct deb version number in install guide.
2015-04-29 10:09:25 +02:00
Torkel Ödegaard
0d3fbb8659 Added message alerts when login failed due to github team membership or email domain requirement, #1731, #1660 2015-04-29 10:08:01 +02:00
Torkel Ödegaard
32fa8180fa Github OAuth: You can now configure a Github team membership requirement, Closes #1731 2015-04-29 09:52:52 +02:00
Torkel Ödegaard
884dc53f8a smalĺ refactorings 2015-04-29 09:49:33 +02:00
Torkel Ödegaard
d812414621 Merge branch 'github-team-membership-requirement' of https://github.com/dewski/grafana into dewski-github-team-membership-requirement 2015-04-29 09:44:23 +02:00
Torkel Ödegaard
52c32d8b10 Merge branch 'issue1862' of https://github.com/raintank/grafana into raintank-issue1862 2015-04-29 09:31:21 +02:00
Torkel Ödegaard
eace358f1c Merge branch 'issue1907' of https://github.com/raintank/grafana
Conflicts:
	pkg/api/dtos/models.go
2015-04-29 09:29:55 +02:00
Torkel Ödegaard
e7c43bf614 expose org id to frontend, Closes #1907 2015-04-29 09:26:12 +02:00
Anthony Woods
81636d8634 expose orgId via currentUser object. fixes #1907 2015-04-29 15:14:49 +08:00
Anthony Woods
b72eba1ef2 refactor handling of refresh when urlValues being used for templates. fixes #1862 2015-04-29 14:55:39 +08:00
Garrett Bjerkhoel
1fdc5277ae Update documentation for team_ids option 2015-04-28 20:38:05 -07:00
Garrett Bjerkhoel
1d7f945268 Handle special error case if connect.UserInfo returns an error 2015-04-28 20:22:45 -07:00
Garrett Bjerkhoel
eb37fc089b Check for active team membership when fetching s.UserInfo 2015-04-28 20:22:21 -07:00
Garrett Bjerkhoel
979d0ca70f Add new error type for team membership permissions 2015-04-28 20:21:44 -07:00
Garrett Bjerkhoel
7ea579bb71 Add team_ids configuration option 2015-04-28 20:19:48 -07:00
Ian Danforth
a0dad38974 Correct deb version number in install guide. 2015-04-28 15:09:22 -07:00
David Raifaizen
e5844afb0f Corrected missing annotations: true tag from the influxdb9 plugin json and fixed influxseries data handling 2015-04-28 16:29:32 -04:00
davidak
c4ac3d61b1 add gigabytes as unit 2015-04-28 20:53:29 +02:00
Torkel Ödegaard
8c14e565a7 Restored the variable color for the label/name in the submenu 2015-04-28 17:54:22 +02:00
Torkel Ödegaard
53cb0feda9 Various fixes, restored search auto focus 2015-04-28 17:28:34 +02:00
Torkel Ödegaard
14e8c15a3a Lots of new unit tests for unsaved changes service 2015-04-28 16:42:40 +02:00
Torkel Ödegaard
aaea80e053 A lot of refactoring opf unsaved changes service so it can be unit tested better 2015-04-28 13:45:59 +02:00
Torkel Ödegaard
f6a61c1ec5 Changes to unsaved changes service to ignore repeated panels and rows, #1888 2015-04-28 12:02:39 +02:00
Torkel Ödegaard
5768f10769 More optimizations and unit tests for panel repeats #1888 2015-04-28 10:23:35 +02:00
Torkel Ödegaard
bcb80eb38f more tweaks 2015-04-28 09:26:20 +02:00
Torkel Ödegaard
9590f485f1 More optimizations and unit tests for panel repeats, this time reuse rows, #1888 2015-04-28 09:24:07 +02:00
Torkel Ödegaard
32fe723da6 More optimizations and unit tests for panel repeats, #1888 2015-04-28 08:44:48 +02:00
Torkel Ödegaard
48b25bc327 Fix to graph panel, and width=0 bug 2015-04-28 06:40:23 +02:00
Torkel Ödegaard
3044a74a52 Optimization and unit tests for panel and row repeat feature, #1888 2015-04-27 18:37:39 +02:00
Torkel Ödegaard
ca7aa294e0 Began wriing unit test for new panel repeat features, #1888 2015-04-27 17:20:32 +02:00
Torkel Ödegaard
158b77d54e small update to panel repeats 2015-04-27 15:01:27 +02:00
Torkel Ödegaard
f8f302faad Merge branch 'panel_repeat' of github.com:grafana/grafana into panel_repeat
Conflicts:
	public/app/partials/submenu.html
	public/css/less/submenu.less
	src/app/partials/roweditor.html
2015-04-27 14:01:55 +02:00
Torkel Ödegaard
3f97bd8212 Added files removed in merge 2015-04-27 13:59:20 +02:00
Torkel Ödegaard
bf6f0f1a65 Merge branch 'master' into panel_repeat
Conflicts:
	public/app/features/dashboard/dashboardCtrl.js
	public/app/partials/submenu.html
	public/css/less/submenu.less
	public/test/specs/templateSrv-specs.js
	src/app/partials/roweditor.html
2015-04-27 13:53:02 +02:00
Torkel Ödegaard
8a986ec340 Using CTRL+S should not work when dashboardMeta.canSave is false, #1834 2015-04-27 10:59:14 +02:00
Torkel Ödegaard
b0ef659add Stop users from entering panel edit mode when dashboard editable is false, #1834 2015-04-27 10:56:36 +02:00
Torkel Ödegaard
29a7490af2 Small fix for unsaved changes when using save as feature 2015-04-27 10:29:04 +02:00
Torkel Ödegaard
ad45b63e79 Merge branch 'editable_false' of github.com:grafana/grafana into editable_false 2015-04-27 10:10:01 +02:00
Torkel Ödegaard
16fc6e8b42 More work on editable false dashboards, #1834 2015-04-27 10:09:32 +02:00
Torkel Ödegaard
af277f560f Merge branch 'master' into editable_false 2015-04-27 08:53:57 +02:00
Torkel Ödegaard
ff403efe09 Merge pull request #1887 from masaori335/kairosdb-tag-suggention
Refactoring of tag suggestion in KairosDB Plugin
2015-04-26 20:31:29 +02:00
Masaori Koshiba
df9403809b Fix conflict 2015-04-26 23:14:50 +09:00
Masaori Koshiba
d86814b6c5 Refactoring of tag suggestion 2015-04-26 23:02:40 +09:00
Torkel Ödegaard
87007994af Merge branch 'kairosdb-metric-text' of https://github.com/masaori335/grafana into kariosdb 2015-04-26 15:22:21 +02:00
Torkel Ödegaard
03e4afa736 Merge pull request #1882 from masaori335/kairosdb-appearance
Fix query editor appearance of KairosDB Plugin
2015-04-26 15:15:05 +02:00
Torkel Ödegaard
f120e96eeb Merge pull request #1883 from masaori335/kairosdb-merge-master
Merge branch 'master' into kariosdb
2015-04-26 15:14:28 +02:00
Masaori Koshiba
7e044e29ba Change metric form input text
Preparation of templated dashboard support.
2015-04-26 19:31:04 +09:00
Masaori Koshiba
63ac6640e6 Merge branch 'master' into kairosdb-merge-master 2015-04-26 11:04:02 +09:00
Masaori Koshiba
5d57931060 Fix appearance of query editor 2015-04-26 00:24:04 +09:00
Torkel Ödegaard
a0cbca4cad more work on editable false, #1834 2015-04-25 11:10:28 +02:00
Masaori Koshiba
0a23a996bc Remove targetLetters 2015-04-25 16:12:10 +09:00
Torkel Ödegaard
fd274592d4 Merge branch 'master' into editable_false 2015-04-24 12:06:50 +02:00
Emil Thelin
2d7c7871b0 Fixes broken test 2015-04-23 20:56:31 +02:00
Emil Thelin
9fd3ef2687 Enforce domain 2015-04-23 20:38:00 +02:00
Anthony Woods
b6a4db3f8a always refresh varible options if refresh=true 2015-04-24 00:19:16 +08:00
Torkel Ödegaard
236c4e65f8 Began work on dashboard: editable flag, that actually stops users from changing anything, #1834 2015-04-23 15:26:48 +02:00
Mike Kobyakov
0b05f88543 Merge remote-tracking branch 'mychanges/master' 2015-04-22 12:06:32 -07:00
Mike Kobyakov
e395211654 Instead of hard-coding the OpenTsdb aggregators list, pull the supported
aggregators from the datasource directly.
2015-04-22 12:02:45 -07:00
Torkel Ödegaard
c3f0ef4126 Merge pull request #1821 from masaori335/kairosdb-refactoring
Clean up KairosDB Plugin
2015-04-20 21:08:53 +02:00
Masaori Koshiba
c762ad8db2 Refactoring KairosDB Plugin 2015-04-21 00:48:25 +09:00
Torkel Ödegaard
0d8e024c18 Merge pull request #1814 from masaori335/kairosdb-test
Add a basic test of KairosDBDatasource
2015-04-19 18:23:00 +02:00
Masaori Koshiba
cc9d2fc139 Suppress LOG in test 2015-04-20 00:51:43 +09:00
Masaori Koshiba
dbc07827cf Add a basic test of KairosDBDatasource 2015-04-19 23:47:29 +09:00
Masaori Koshiba
05c27d8340 Rename 'KairosDBTargetCtrl' to 'KairosDBQueryCtrl' 2015-04-19 21:52:04 +09:00
Masaori Koshiba
d57ffad5e1 Add a space between arguments 2015-04-19 21:43:18 +09:00
William Wei
76b517b361 allow graphite metrics name contain '~'
when a metrics name contains '~', id does not impact graph display.
but you can not use grafana UI to edit metrics with realtime
graphite query.
2015-04-17 15:51:26 +08:00
Torkel Ödegaard
32275cb009 Merge pull request #1760 from masaori335/kairosdb-cleanup
Cleanup KairosDB Plugin
2015-04-12 15:37:45 +02:00
Torkel Ödegaard
ff353197fb Merge pull request #1759 from masaori335/kairosdb-fix-icon
Fix class names in query.editor.html of KairosDB plugin
2015-04-12 15:37:33 +02:00
Masaori Koshiba
88bf0cdb9e Fix styles around 'function' 2015-04-12 20:51:35 +09:00
Masaori Koshiba
a2b751976e Add space before and after binary operators 2015-04-12 20:41:09 +09:00
Masaori Koshiba
ddb4b928a0 Delete MetricListToObject which never used 2015-04-12 20:28:01 +09:00
Masaori Koshiba
2b5fa599fb Fix styles warned by jshint 2015-04-12 20:28:00 +09:00
Masaori Koshiba
ae2201ef6f Add space after keywords 2015-04-12 20:28:00 +09:00
Masaori Koshiba
ed69ddedbf Fix styles warned by jscs 2015-04-12 20:27:51 +09:00
Masaori Koshiba
66f5411402 Fix class names in query.editor.html of KairosDB Plugin 2015-04-12 18:39:41 +09:00
Mike Kobyakov
b01b121a4b Instead of hard-coding the OpenTsdb aggregators list, pull the supported
aggregators from the datasource directly.
2015-03-30 10:17:45 -07:00
Torkel Ödegaard
795cee13c8 KairosDB data source plugin is messy, needs a lot of clean up & refactoring, please help 2015-03-29 20:30:59 +02:00
Torkel Ödegaard
15188c4a88 Moved kairosdb data source to correct folder 2015-03-29 20:13:32 +02:00
Torkel Ödegaard
48f5a77e24 Merge branch 'master' into kariosdb 2015-03-29 20:08:28 +02:00
Torkel Ödegaard
607b273b28 Merge branch 'master' into kariosdb 2015-03-29 13:42:00 +02:00
Torkel Ödegaard
618d4f0a9d Testing kariosdb datasource, hm.. needs a lot of work 2015-03-27 13:49:05 +01:00
Torkel Ödegaard
2933b89a82 Merge branch 'develop' into panel_repeat
Conflicts:
	src/app/partials/roweditor.html
	src/app/partials/submenu.html
	src/css/less/submenu.less
	src/test/specs/templateSrv-specs.js
2015-03-27 10:47:45 +01:00
Torkel Ödegaard
c658189c85 Updated 2015-03-21 18:39:43 -04:00
Torkel Ödegaard
a8d9ec426b Merge branch 'develop' into panel_repeat 2015-03-21 15:57:53 -04:00
Torkel Ödegaard
0aab51a73f Added row repeats 2015-03-20 15:06:23 -04:00
Torkel Ödegaard
86e9d2cf07 more work on repeating row 2015-03-20 13:33:52 -04:00
Torkel Ödegaard
c7b4041879 Merge branch 'template_var_multi_select' into panel_repeat
Conflicts:
	src/app/features/dashboard/submenuCtrl.js
	src/test/specs/templateSrv-specs.js
2015-03-20 13:00:15 -04:00
Torkel Ödegaard
6e0947c0d9 Multi selecting starting to work 2015-03-20 12:55:55 -04:00
Torkel Ödegaard
3334e36397 Updated 2015-03-20 10:24:42 -04:00
Torkel Ödegaard
35888c814c more work on multi select 2015-03-19 23:09:50 -04:00
Torkel Ödegaard
0d817e0664 Merge branch 'develop' into template_var_multi_select 2015-03-19 15:17:00 -04:00
Torkel Ödegaard
01148ac1b9 Merge branch 'develop' into template_var_multi_select 2015-03-19 10:17:34 -04:00
Torkel Ödegaard
d08144e730 Began work on template multi select feature 2015-03-18 11:15:21 -04:00
Torkel Ödegaard
741a1736a4 can handle updates 2015-03-17 16:04:08 -04:00
Torkel Ödegaard
9f729900f2 work on scoped variable values 2015-03-17 13:33:58 -04:00
Torkel Ödegaard
5de499c7f6 Working on panel repeat 2015-03-17 12:30:42 -04:00
Torkel Ödegaard
1b59fb5be9 POC for repeating panels based on template variable options 2015-03-14 16:13:25 -04:00
1773 changed files with 184045 additions and 16973 deletions

3
.bowerrc Normal file
View File

@@ -0,0 +1,3 @@
{
"directory": "public/vendor/"
}

View File

@@ -9,7 +9,7 @@ watch_dirs = [
"$WORKDIR/public/views",
"$WORKDIR/conf",
]
watch_exts = [".go", ".ini"]
watch_exts = [".go", ".ini", ".toml", ".html"]
build_delay = 1500
cmds = [
["go", "build", "-o", "./bin/grafana-server"],

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

8
.gitignore vendored
View File

@@ -1,9 +1,13 @@
node_modules
npm-debug.log
coverage/
.aws-config.json
awsconfig
/dist
/emails/dist
/public_gen
/tmp
vendor/phantomjs/phantomjs
docs/AWS_S3_BUCKET
docs/GIT_BRANCH
@@ -26,4 +30,6 @@ public/css/*.min.css
conf/custom.ini
fig.yml
profile.cov
grafana
.notouch

View File

@@ -23,7 +23,7 @@
"laxcomma": true,
"sub": true,
"unused": true,
"maxdepth": 5,
"maxdepth": 6,
"maxlen": 140,
"globals": {
@@ -32,4 +32,4 @@
"Chromath": false,
"setImmediate": true
}
}
}

View File

@@ -1,4 +1,171 @@
# 2.0.3 (unreleased)
# 2.6.0 (2015-12-04)
### New Features
* **Elasticsearch**: Support for pipeline aggregations Moving average and derivative, closes [#2715](https://github.com/grafana/grafana/issues/2715)
* **Elasticsearch**: Support for inline script and missing options for metrics, closes [#3500](https://github.com/grafana/grafana/issues/3500)
* **Syslog**: Support for syslog logging, closes [#3161](https://github.com/grafana/grafana/pull/3161)
* **Timepicker**: Always show refresh button even with refresh rate, closes [#3498](https://github.com/grafana/grafana/pull/3498)
* **Login**: Make it possible to change the login hint on the login page, closes [#2571](https://github.com/grafana/grafana/pull/2571)
### Bug Fixes
* **metric editors**: Fix for clicking typeahead auto dropdown option, fixes [#3428](https://github.com/grafana/grafana/issues/3428)
* **influxdb**: Fixed issue showing Group By label only on first query, fixes [#3453](https://github.com/grafana/grafana/issues/3453)
* **logging**: Add more verbose info logging for http reqeusts, closes [#3405](https://github.com/grafana/grafana/pull/3405)
# 2.6.0-Beta1 (2015-12-04)
### New Table Panel
* **table**: New powerful and flexible table panel, closes [#215](https://github.com/grafana/grafana/issues/215)
### Enhancements
* **CloudWatch**: Support for multiple AWS Credentials, closes [#3053](https://github.com/grafana/grafana/issues/3053), [#3080](https://github.com/grafana/grafana/issues/3080)
* **Elasticsearch**: Support for dynamic daily indices for annotations, closes [#3061](https://github.com/grafana/grafana/issues/3061)
* **Elasticsearch**: Support for setting min_doc_count for date histogram, closes [#3416](https://github.com/grafana/grafana/issues/3416)
* **Graph Panel**: Option to hide series with all zeroes from legend and tooltip, closes [#1381](https://github.com/grafana/grafana/issues/1381), [#3336](https://github.com/grafana/grafana/issues/3336)
### Bug Fixes
* **cloudwatch**: fix for handling of period for long time ranges, fixes [#3086](https://github.com/grafana/grafana/issues/3086)
* **dashboard**: fix for collapse row by clicking on row title, fixes [#3065](https://github.com/grafana/grafana/issues/3065)
* **influxdb**: fix for relative time ranges `last x months` and `last x years`, fixes [#3067](https://github.com/grafana/grafana/issues/3067)
* **graph**: layout fix for color picker when right side legend was enabled, fixes [#3093](https://github.com/grafana/grafana/issues/3093)
* **elasticsearch**: disabling elastic query (via eye) caused error, fixes [#3300](https://github.com/grafana/grafana/issues/3300)
### Breaking changes
* **elasticsearch**: Manual json edited queries are not supported any more (They very barely worked in 2.5)
# 2.5 (2015-10-28)
**New Feature: Mix data sources**
- A built in data source is now available named `-- Mixed --`, When picked in the metrics tab,
it allows you to add queries of differnet data source types & instances to the same graph/panel!
[Issue #436](https://github.com/grafana/grafana/issues/436)
**New Feature: Elasticsearch Metrics Query Editor and Viz Support**
- Feature rich query editor and processing features enables you to issues all kind of metric queries to Elasticsearch
- See [Issue #1034](https://github.com/grafana/grafana/issues/1034) for more info.
**New Feature: New and much improved time picker**
- Support for quick ranges like `Today`, `This day last week`, `This week`, `The day so far`, etc.
- Improved UI and improved support for UTC, [Issue #2761](https://github.com/grafana/grafana/issues/2761) for more info.
**User Onboarding**
- Org admin can now send email invites (or invite links) to people who are not yet Grafana users
- Sign up flow now supports email verification (if enabled)
- See [Issue #2353](https://github.com/grafana/grafana/issues/2353) for more info.
**Other new Features && Enhancements**
- [Pull #2720](https://github.com/grafana/grafana/pull/2720). Admin: Initial basic quota support (per Org)
- [Issue #2577](https://github.com/grafana/grafana/issues/2577). Panel: Resize handles in panel bottom right corners for easy width and height change
- [Issue #2457](https://github.com/grafana/grafana/issues/2457). Admin: admin page for all grafana organizations (list / edit view)
- [Issue #1186](https://github.com/grafana/grafana/issues/1186). Time Picker: New option `today`, will set time range from midnight to now
- [Issue #2647](https://github.com/grafana/grafana/issues/2647). InfluxDB: You can now set group by time interval on each query
- [Issue #2599](https://github.com/grafana/grafana/issues/2599). InfluxDB: Improved alias support, you can now use the `AS` clause for each select statement
- [Issue #2708](https://github.com/grafana/grafana/issues/2708). InfluxDB: You can now set math expression for select clauses.
- [Issue #1575](https://github.com/grafana/grafana/issues/1575). Drilldown link: now you can click on the external link icon in the panel header to access drilldown links!
- [Issue #1646](https://github.com/grafana/grafana/issues/1646). OpenTSDB: Fetch list of aggregators from OpenTSDB
- [Issue #2955](https://github.com/grafana/grafana/issues/2955). Graph: More axis units (Length, Volume, Temperature, Pressure, etc), thanks @greglook
- [Issue #2928](https://github.com/grafana/grafana/issues/2928). LDAP: Support for searching for groups memberships, i.e. POSIX (no memberOf) schemas, also multiple ldap servers, and root ca cert, thanks @abligh
**Fixes**
- [Issue #2413](https://github.com/grafana/grafana/issues/2413). InfluxDB 0.9: Fix for handling empty series object in response from influxdb
- [Issue #2574](https://github.com/grafana/grafana/issues/2574). Snapshot: Fix for snapshot with expire 7 days option, 7 days option not correct, was 7 hours
- [Issue #2568](https://github.com/grafana/grafana/issues/2568). AuthProxy: Fix for server side rendering of panel when using auth proxy
- [Issue #2490](https://github.com/grafana/grafana/issues/2490). Graphite: Dashboard import was broken in 2.1 and 2.1.1, working now
- [Issue #2565](https://github.com/grafana/grafana/issues/2565). TimePicker: Fix for when you applied custom time range it did not refreh dashboard
- [Issue #2563](https://github.com/grafana/grafana/issues/2563). Annotations: Fixed issue when html sanitizer failes for title to annotation body, now fallbacks to html escaping title and text
- [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url)
- [Issue #2620](https://github.com/grafana/grafana/issues/2620). Graph: multi series tooltip did no highlight correct point when stacking was enabled and series were of different resolution
- [Issue #2636](https://github.com/grafana/grafana/issues/2636). InfluxDB: Do no show template vars in dropdown for tag keys and group by keys
- [Issue #2604](https://github.com/grafana/grafana/issues/2604). InfluxDB: More alias options, can now use `$[0-9]` syntax to reference part of a measurement name (seperated by dots)
**Breaking Changes**
- Notice to makers/users of custom data sources, there is a minor breaking change in 2.2 that
require an update to custom data sources for them to work in 2.2. [Read this doc](https://github.com/grafana/grafana/tree/master/docs/sources/datasources/plugin_api.md) for more on the
data source api change.
- Data source api changes, [PLUGIN_CHANGES.md](https://github.com/grafana/grafana/blob/master/public/app/plugins/PLUGIN_CHANGES.md)
- The duplicate query function used in data source editors is changed, and moveMetricQuery function was renamed
**Tech (Note for devs)**
Started using Typescript (transpiled to ES5), uncompiled typescript files and less files are in public folder (in source tree)
This folder is never modified by build steps. Compiled css and javascript files are put in public_gen, all other files
that do not undergo transformation are just copied from public to public_gen, it is public_gen that is used by grafana-server
if it is found.
Grunt & Watch tasks:
- `grunt` : default task, will remove public_gen, copy over all files from public, do less & typescript compilation
- `grunt watch`: will watch for changes to less, and typescript files and compile them to public_gen, and for other files it will just copy them to public_gen
# 2.1.3 (2015-08-24)
**Fixes**
- [Issue #2580](https://github.com/grafana/grafana/issues/2580). Packaging: ldap.toml was not marked as config file and could be overwritten in upgrade
- [Issue #2564](https://github.com/grafana/grafana/issues/2564). Templating: Another atempt at fixing #2534 (Init multi value template var used in repeat panel from url)
# 2.1.2 (2015-08-20)
**Fixes**
- [Issue #2558](https://github.com/grafana/grafana/issues/2558). DragDrop: Fix for broken drag drop behavior
- [Issue #2534](https://github.com/grafana/grafana/issues/2534). Templating: fix for setting template variable value via url and having repeated panels or rows
# 2.1.1 (2015-08-11)
**Fixes**
- [Issue #2443](https://github.com/grafana/grafana/issues/2443). Templating: Fix for buggy repeat row behavior when combined with with repeat panel due to recent change before 2.1 release
- [Issue #2442](https://github.com/grafana/grafana/issues/2442). Templating: Fix text panel when using template variables in text in in repeated panel
- [Issue #2446](https://github.com/grafana/grafana/issues/2446). InfluxDB: Fix for using template vars inside alias field (InfluxDB 0.9)
- [Issue #2460](https://github.com/grafana/grafana/issues/2460). SinglestatPanel: Fix to handle series with no data points
- [Issue #2461](https://github.com/grafana/grafana/issues/2461). LDAP: Fix for ldap users with empty email address
- [Issue #2484](https://github.com/grafana/grafana/issues/2484). Graphite: Fix bug when using series ref (#A-Z) and referenced series is hidden in query editor.
- [Issue #1896](https://github.com/grafana/grafana/issues/1896). Postgres: Dashboard search is now case insensitive when using Postgres
**Enhancements**
- [Issue #2477](https://github.com/grafana/grafana/issues/2477). InfluxDB(0.9): Added more condition operators (`<`, `>`, `<>`, `!~`), thx @thuck
- [Issue #2483](https://github.com/grafana/grafana/issues/2484). InfluxDB(0.9): Use $col as option in alias patterns, thx @thuck
# 2.1.0 (2015-08-04)
**Data sources**
- [Issue #1525](https://github.com/grafana/grafana/issues/1525). InfluxDB: Full support for InfluxDB 0.9 with new adapted query editor
- [Issue #2191](https://github.com/grafana/grafana/issues/2191). KariosDB: Grafana now ships with a KariosDB data source plugin, thx @masaori335
- [Issue #1177](https://github.com/grafana/grafana/issues/1177). OpenTSDB: Limit tags by metric, OpenTSDB config option tsd.core.meta.enable_realtime_ts must enabled for OpenTSDB lookup api
- [Issue #1250](https://github.com/grafana/grafana/issues/1250). OpenTSDB: Support for template variable values lookup queries
**New dashboard features**
- [Issue #1144](https://github.com/grafana/grafana/issues/1144). Templating: You can now select multiple template variables values at the same time.
- [Issue #1922](https://github.com/grafana/grafana/issues/1922). Templating: Specify multiple variable values via URL params.
- [Issue #1888](https://github.com/grafana/grafana/issues/1144). Templating: Repeat panel or row for each selected template variable value
- [Issue #1888](https://github.com/grafana/grafana/issues/1944). Dashboard: Custom Navigation links & dynamic links to related dashboards
- [Issue #590](https://github.com/grafana/grafana/issues/590). Graph: Define series color using regex rule
- [Issue #2162](https://github.com/grafana/grafana/issues/2162). Graph: New series style override, negative-y transform and stack groups
- [Issue #2096](https://github.com/grafana/grafana/issues/2096). Dashboard list panel: Now supports search by multiple tags
- [Issue #2203](https://github.com/grafana/grafana/issues/2203). Singlestat: Now support string values
**User or Organization admin**
- [Issue #1899](https://github.com/grafana/grafana/issues/1899). Organization: You can now update the organization user role directly (without removing and readding the organization user).
- [Issue #2088](https://github.com/grafana/grafana/issues/2088). Roles: New user role `Read Only Editor` that replaces the old `Viewer` role behavior
**Backend**
- [Issue #2218](https://github.com/grafana/grafana/issues/2218). Auth: You can now authenicate against api with username / password using basic auth
- [Issue #2095](https://github.com/grafana/grafana/issues/2095). Search: Search now supports filtering by multiple dashboard tags
- [Issue #1905](https://github.com/grafana/grafana/issues/1905). Github OAuth: You can now configure a Github team membership requirement, thx @dewski
- [Issue #2052](https://github.com/grafana/grafana/issues/2052). Github OAuth: You can now configure a Github organization requirement, thx @indrekj
- [Issue #1891](https://github.com/grafana/grafana/issues/1891). Security: New config option to disable the use of gravatar for profile images
- [Issue #1921](https://github.com/grafana/grafana/issues/1921). Auth: Support for user authentication via reverse proxy header (like X-Authenticated-User, or X-WEBAUTH-USER)
- [Issue #960](https://github.com/grafana/grafana/issues/960). Search: Backend can now index a folder with json files, will be available in search (saving back to folder is not supported, this feature is meant for static generated json dashboards)
**Breaking changes**
- [Issue #1826](https://github.com/grafana/grafana/issues/1826). User role 'Viewer' are now prohibited from entering edit mode (and doing other transient dashboard edits). A new role `Read Only Editor` will replace the old Viewer behavior
- [Issue #1928](https://github.com/grafana/grafana/issues/1928). HTTP API: GET /api/dashboards/db/:slug response changed property `model` to `dashboard` to match the POST request nameing
- Backend render URL changed from `/render/dashboard/solo` `render/dashboard-solo/` (in order to have consistent dashboard url `/dashboard/:type/:slug`)
- Search HTTP API response has changed (simplified), tags list moved to seperate HTTP resource URI
- Datasource HTTP api breaking change, ADD datasource is now POST /api/datasources/, update is now PUT /api/datasources/:id
**Fixes**
- [Issue #2185](https://github.com/grafana/grafana/issues/2185). Graph: fixed PNG rendering of panels with legend table to the right
- [Issue #2163](https://github.com/grafana/grafana/issues/2163). Backend: Load dashboards with capital letters in the dashboard url slug (url id)
# 2.0.3 (unreleased - 2.0.x branch)
**Fixes**
- [Issue #1872](https://github.com/grafana/grafana/issues/1872). Firefox/IE issue, invisible text in dashboard search fixed
@@ -61,6 +228,10 @@
# 2.0.0-Beta1 (2015-03-30)
**Important Note**
Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated backend server. Please read the [Documentation](http://docs.grafana.org) for more detailed about this SIGNIFCANT change to Grafana
**New features**
- [Issue #1623](https://github.com/grafana/grafana/issues/1623). Share Dashboard: Dashboard snapshot sharing (dash and data snapshot), save to local or save to public snapshot dashboard snapshots.raintank.io site
- [Issue #1622](https://github.com/grafana/grafana/issues/1622). Share Panel: The share modal now has an embed option, gives you an iframe that you can use to embedd a single graph on another web site

93
Godeps/Godeps.json generated
View File

@@ -1,10 +1,15 @@
{
"ImportPath": "github.com/grafana/grafana",
"GoVersion": "go1.3",
"GoVersion": "go1.5",
"Packages": [
"./pkg/..."
],
"Deps": [
{
"ImportPath": "github.com/BurntSushi/toml",
"Comment": "v0.1.0-21-g056c9bc",
"Rev": "056c9bc7be7190eaa7715723883caffa5f8fa3e4"
},
{
"ImportPath": "github.com/Unknwon/com",
"Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df"
@@ -14,12 +19,68 @@
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
},
{
"ImportPath": "github.com/dalu/slug",
"Rev": "6dbd13912e9be466e2c1de349a2c7d1466c97e07"
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/dalu/unidecode",
"Rev": "339814d47f3e32a6f7036a0a4c56ed9b373dd755"
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatch",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v0-48-g060d7da",
"Rev": "060d7da055ba6ec5ea7a31f116332fe5efa04ce0"
},
{
"ImportPath": "github.com/go-ldap/ldap",
"Comment": "v1-19-g83e6542",
"Rev": "83e65426fd1c06626e88aa8a085e5bfed0208e29"
},
{
"ImportPath": "github.com/go-sql-driver/mysql",
@@ -35,6 +96,15 @@
"Comment": "v0.4.2-58-ge2889e5",
"Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe"
},
{
"ImportPath": "github.com/gosimple/slug",
"Rev": "8d258463b4459f161f51d6a357edacd3eef9d663"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2",
"Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9"
},
{
"ImportPath": "github.com/jtolds/gls",
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
@@ -56,6 +126,10 @@
"ImportPath": "github.com/mattn/go-sqlite3",
"Rev": "e28cd440fabdd39b9520344bc26829f61db40ece"
},
{
"ImportPath": "github.com/rainycape/unidecode",
"Rev": "836ef0a715aedf08a12d595ed73ec8ed5b288cac"
},
{
"ImportPath": "github.com/smartystreets/goconvey/convey",
"Comment": "1.5.0-356-gfbc0a1c",
@@ -73,6 +147,11 @@
"ImportPath": "golang.org/x/oauth2",
"Rev": "c58fcf0ffc1c772aa2e1ee4894bc19f2649263b2"
},
{
"ImportPath": "gopkg.in/asn1-ber.v1",
"Comment": "v1",
"Rev": "9eae18c3681ae3d3c677ac2b80a8fe57de45fc09"
},
{
"ImportPath": "gopkg.in/bufio.v1",
"Comment": "v1",
@@ -87,10 +166,6 @@
"ImportPath": "gopkg.in/redis.v2",
"Comment": "v2.3.2",
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
},
{
"ImportPath": "gopkgs.com/pool.v1",
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
}
]
}

View File

@@ -0,0 +1,5 @@
TAGS
tags
.*.swp
tomlcheck/tomlcheck
toml.test

View File

@@ -0,0 +1,12 @@
language: go
go:
- 1.1
- 1.2
- tip
install:
- go install ./...
- go get github.com/BurntSushi/toml-test
script:
- export PATH="$PATH:$HOME/gopath/bin"
- make test

View File

@@ -0,0 +1,3 @@
Compatible with TOML version
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)

View 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.

View File

@@ -0,0 +1,19 @@
install:
go install ./...
test: install
go test -v
toml-test toml-test-decoder
toml-test -encoder toml-test-encoder
fmt:
gofmt -w *.go */*.go
colcheck *.go */*.go
tags:
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
push:
git push origin master
git push github master

View File

@@ -0,0 +1,220 @@
## TOML parser and encoder for Go with reflection
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml`
packages. This package also supports the `encoding.TextUnmarshaler` and
`encoding.TextMarshaler` interfaces so that you can define custom data
representations. (There is an example of this below.)
Spec: https://github.com/mojombo/toml
Compatible with TOML version
[v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md)
Documentation: http://godoc.org/github.com/BurntSushi/toml
Installation:
```bash
go get github.com/BurntSushi/toml
```
Try the toml validator:
```bash
go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml
```
[![Build status](https://api.travis-ci.org/BurntSushi/toml.png)](https://travis-ci.org/BurntSushi/toml)
### Testing
This package passes all tests in
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
and the encoder.
### Examples
This package works similarly to how the Go standard library handles `XML`
and `JSON`. Namely, data is loaded into Go values via reflection.
For the simplest example, consider some TOML file as just a list of keys
and values:
```toml
Age = 25
Cats = [ "Cauchy", "Plato" ]
Pi = 3.14
Perfection = [ 6, 28, 496, 8128 ]
DOB = 1987-07-05T05:45:00Z
```
Which could be defined in Go as:
```go
type Config struct {
Age int
Cats []string
Pi float64
Perfection []int
DOB time.Time // requires `import time`
}
```
And then decoded with:
```go
var conf Config
if _, err := toml.Decode(tomlData, &conf); err != nil {
// handle error
}
```
You can also use struct tags if your struct field name doesn't map to a TOML
key value directly:
```toml
some_key_NAME = "wat"
```
```go
type TOML struct {
ObscureKey string `toml:"some_key_NAME"`
}
```
### Using the `encoding.TextUnmarshaler` interface
Here's an example that automatically parses duration strings into
`time.Duration` values:
```toml
[[song]]
name = "Thunder Road"
duration = "4m49s"
[[song]]
name = "Stairway to Heaven"
duration = "8m03s"
```
Which can be decoded with:
```go
type song struct {
Name string
Duration duration
}
type songs struct {
Song []song
}
var favorites songs
if _, err := toml.Decode(blob, &favorites); err != nil {
log.Fatal(err)
}
for _, s := range favorites.Song {
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
}
```
And you'll also need a `duration` type that satisfies the
`encoding.TextUnmarshaler` interface:
```go
type duration struct {
time.Duration
}
func (d *duration) UnmarshalText(text []byte) error {
var err error
d.Duration, err = time.ParseDuration(string(text))
return err
}
```
### More complex usage
Here's an example of how to load the example from the official spec page:
```toml
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]
```
And the corresponding Go types are:
```go
type tomlConfig struct {
Title string
Owner ownerInfo
DB database `toml:"database"`
Servers map[string]server
Clients clients
}
type ownerInfo struct {
Name string
Org string `toml:"organization"`
Bio string
DOB time.Time
}
type database struct {
Server string
Ports []int
ConnMax int `toml:"connection_max"`
Enabled bool
}
type server struct {
IP string
DC string
}
type clients struct {
Data [][]interface{}
Hosts []string
}
```
Note that a case insensitive match will be tried if an exact match can't be
found.
A working example of the above can be found in `_examples/example.{go,toml}`.

View 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.

View File

@@ -0,0 +1,14 @@
# Implements the TOML test suite interface
This is an implementation of the interface expected by
[toml-test](https://github.com/BurntSushi/toml-test) for my
[toml parser written in Go](https://github.com/BurntSushi/toml).
In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
Compatible with TOML version
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)
Compatible with `toml-test` version
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)

View File

@@ -0,0 +1,90 @@
// Command toml-test-decoder satisfies the toml-test interface for testing
// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"path"
"time"
"github.com/BurntSushi/toml"
)
func init() {
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
}
func usage() {
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
flag.PrintDefaults()
os.Exit(1)
}
func main() {
if flag.NArg() != 0 {
flag.Usage()
}
var tmp interface{}
if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
log.Fatalf("Error decoding TOML: %s", err)
}
typedTmp := translate(tmp)
if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
log.Fatalf("Error encoding JSON: %s", err)
}
}
func translate(tomlData interface{}) interface{} {
switch orig := tomlData.(type) {
case map[string]interface{}:
typed := make(map[string]interface{}, len(orig))
for k, v := range orig {
typed[k] = translate(v)
}
return typed
case []map[string]interface{}:
typed := make([]map[string]interface{}, len(orig))
for i, v := range orig {
typed[i] = translate(v).(map[string]interface{})
}
return typed
case []interface{}:
typed := make([]interface{}, len(orig))
for i, v := range orig {
typed[i] = translate(v)
}
// We don't really need to tag arrays, but let's be future proof.
// (If TOML ever supports tuples, we'll need this.)
return tag("array", typed)
case time.Time:
return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
case bool:
return tag("bool", fmt.Sprintf("%v", orig))
case int64:
return tag("integer", fmt.Sprintf("%d", orig))
case float64:
return tag("float", fmt.Sprintf("%v", orig))
case string:
return tag("string", orig)
}
panic(fmt.Sprintf("Unknown type: %T", tomlData))
}
func tag(typeName string, data interface{}) map[string]interface{} {
return map[string]interface{}{
"type": typeName,
"value": data,
}
}

View 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.

View File

@@ -0,0 +1,14 @@
# Implements the TOML test suite interface for TOML encoders
This is an implementation of the interface expected by
[toml-test](https://github.com/BurntSushi/toml-test) for the
[TOML encoder](https://github.com/BurntSushi/toml).
In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
Compatible with TOML version
[v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md)
Compatible with `toml-test` version
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)

View File

@@ -0,0 +1,131 @@
// Command toml-test-encoder satisfies the toml-test interface for testing
// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
package main
import (
"encoding/json"
"flag"
"log"
"os"
"path"
"strconv"
"time"
"github.com/BurntSushi/toml"
)
func init() {
log.SetFlags(0)
flag.Usage = usage
flag.Parse()
}
func usage() {
log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
flag.PrintDefaults()
os.Exit(1)
}
func main() {
if flag.NArg() != 0 {
flag.Usage()
}
var tmp interface{}
if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
log.Fatalf("Error decoding JSON: %s", err)
}
tomlData := translate(tmp)
if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
log.Fatalf("Error encoding TOML: %s", err)
}
}
func translate(typedJson interface{}) interface{} {
switch v := typedJson.(type) {
case map[string]interface{}:
if len(v) == 2 && in("type", v) && in("value", v) {
return untag(v)
}
m := make(map[string]interface{}, len(v))
for k, v2 := range v {
m[k] = translate(v2)
}
return m
case []interface{}:
tabArray := make([]map[string]interface{}, len(v))
for i := range v {
if m, ok := translate(v[i]).(map[string]interface{}); ok {
tabArray[i] = m
} else {
log.Fatalf("JSON arrays may only contain objects. This " +
"corresponds to only tables being allowed in " +
"TOML table arrays.")
}
}
return tabArray
}
log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
panic("unreachable")
}
func untag(typed map[string]interface{}) interface{} {
t := typed["type"].(string)
v := typed["value"]
switch t {
case "string":
return v.(string)
case "integer":
v := v.(string)
n, err := strconv.Atoi(v)
if err != nil {
log.Fatalf("Could not parse '%s' as integer: %s", v, err)
}
return n
case "float":
v := v.(string)
f, err := strconv.ParseFloat(v, 64)
if err != nil {
log.Fatalf("Could not parse '%s' as float64: %s", v, err)
}
return f
case "datetime":
v := v.(string)
t, err := time.Parse("2006-01-02T15:04:05Z", v)
if err != nil {
log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
}
return t
case "bool":
v := v.(string)
switch v {
case "true":
return true
case "false":
return false
}
log.Fatalf("Could not parse '%s' as a boolean.", v)
case "array":
v := v.([]interface{})
array := make([]interface{}, len(v))
for i := range v {
if m, ok := v[i].(map[string]interface{}); ok {
array[i] = untag(m)
} else {
log.Fatalf("Arrays may only contain other arrays or "+
"primitive values, but found a '%T'.", m)
}
}
return array
}
log.Fatalf("Unrecognized tag type '%s'.", t)
panic("unreachable")
}
func in(key string, m map[string]interface{}) bool {
_, ok := m[key]
return ok
}

View 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.

View File

@@ -0,0 +1,22 @@
# TOML Validator
If Go is installed, it's simple to try it out:
```bash
go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml
```
You can see the types of every key in a TOML file with:
```bash
tomlv -types some-toml-file.toml
```
At the moment, only one error message is reported at a time. Error messages
include line numbers. No output means that the files given are valid TOML, or
there is a bug in `tomlv`.
Compatible with TOML version
[v0.1.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.1.0.md)

View File

@@ -0,0 +1,61 @@
// Command tomlv validates TOML documents and prints each key's type.
package main
import (
"flag"
"fmt"
"log"
"os"
"path"
"strings"
"text/tabwriter"
"github.com/BurntSushi/toml"
)
var (
flagTypes = false
)
func init() {
log.SetFlags(0)
flag.BoolVar(&flagTypes, "types", flagTypes,
"When set, the types of every defined key will be shown.")
flag.Usage = usage
flag.Parse()
}
func usage() {
log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
path.Base(os.Args[0]))
flag.PrintDefaults()
os.Exit(1)
}
func main() {
if flag.NArg() < 1 {
flag.Usage()
}
for _, f := range flag.Args() {
var tmp interface{}
md, err := toml.DecodeFile(f, &tmp)
if err != nil {
log.Fatalf("Error in '%s': %s", f, err)
}
if flagTypes {
printTypes(md)
}
}
}
func printTypes(md toml.MetaData) {
tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
for _, key := range md.Keys() {
fmt.Fprintf(tabw, "%s%s\t%s\n",
strings.Repeat(" ", len(key)-1), key, md.Type(key...))
}
tabw.Flush()
}

View File

@@ -0,0 +1,492 @@
package toml
import (
"fmt"
"io"
"io/ioutil"
"math"
"reflect"
"strings"
"time"
)
var e = fmt.Errorf
// Unmarshaler is the interface implemented by objects that can unmarshal a
// TOML description of themselves.
type Unmarshaler interface {
UnmarshalTOML(interface{}) error
}
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
func Unmarshal(p []byte, v interface{}) error {
_, err := Decode(string(p), v)
return err
}
// Primitive is a TOML value that hasn't been decoded into a Go value.
// When using the various `Decode*` functions, the type `Primitive` may
// be given to any value, and its decoding will be delayed.
//
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
//
// The underlying representation of a `Primitive` value is subject to change.
// Do not rely on it.
//
// N.B. Primitive values are still parsed, so using them will only avoid
// the overhead of reflection. They can be useful when you don't know the
// exact type of TOML data until run time.
type Primitive struct {
undecoded interface{}
context Key
}
// DEPRECATED!
//
// Use MetaData.PrimitiveDecode instead.
func PrimitiveDecode(primValue Primitive, v interface{}) error {
md := MetaData{decoded: make(map[string]bool)}
return md.unify(primValue.undecoded, rvalue(v))
}
// PrimitiveDecode is just like the other `Decode*` functions, except it
// decodes a TOML value that has already been parsed. Valid primitive values
// can *only* be obtained from values filled by the decoder functions,
// including this method. (i.e., `v` may contain more `Primitive`
// values.)
//
// Meta data for primitive values is included in the meta data returned by
// the `Decode*` functions with one exception: keys returned by the Undecoded
// method will only reflect keys that were decoded. Namely, any keys hidden
// behind a Primitive will be considered undecoded. Executing this method will
// update the undecoded keys in the meta data. (See the example.)
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
md.context = primValue.context
defer func() { md.context = nil }()
return md.unify(primValue.undecoded, rvalue(v))
}
// Decode will decode the contents of `data` in TOML format into a pointer
// `v`.
//
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
// used interchangeably.)
//
// TOML arrays of tables correspond to either a slice of structs or a slice
// of maps.
//
// TOML datetimes correspond to Go `time.Time` values.
//
// All other TOML types (float, string, int, bool and array) correspond
// to the obvious Go types.
//
// An exception to the above rules is if a type implements the
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
// (floats, strings, integers, booleans and datetimes) will be converted to
// a byte string and given to the value's UnmarshalText method. See the
// Unmarshaler example for a demonstration with time duration strings.
//
// Key mapping
//
// TOML keys can map to either keys in a Go map or field names in a Go
// struct. The special `toml` struct tag may be used to map TOML keys to
// struct fields that don't match the key name exactly. (See the example.)
// A case insensitive match to struct names will be tried if an exact match
// can't be found.
//
// The mapping between TOML values and Go values is loose. That is, there
// may exist TOML values that cannot be placed into your representation, and
// there may be parts of your representation that do not correspond to
// TOML values. This loose mapping can be made stricter by using the IsDefined
// and/or Undecoded methods on the MetaData returned.
//
// This decoder will not handle cyclic types. If a cyclic type is passed,
// `Decode` will not terminate.
func Decode(data string, v interface{}) (MetaData, error) {
p, err := parse(data)
if err != nil {
return MetaData{}, err
}
md := MetaData{
p.mapping, p.types, p.ordered,
make(map[string]bool, len(p.ordered)), nil,
}
return md, md.unify(p.mapping, rvalue(v))
}
// DecodeFile is just like Decode, except it will automatically read the
// contents of the file at `fpath` and decode it for you.
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
bs, err := ioutil.ReadFile(fpath)
if err != nil {
return MetaData{}, err
}
return Decode(string(bs), v)
}
// DecodeReader is just like Decode, except it will consume all bytes
// from the reader and decode it for you.
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
bs, err := ioutil.ReadAll(r)
if err != nil {
return MetaData{}, err
}
return Decode(string(bs), v)
}
// unify performs a sort of type unification based on the structure of `rv`,
// which is the client representation.
//
// Any type mismatch produces an error. Finding a type that we don't know
// how to handle produces an unsupported type error.
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
// Special case. Look for a `Primitive` value.
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
// Save the undecoded data and the key context into the primitive
// value.
context := make(Key, len(md.context))
copy(context, md.context)
rv.Set(reflect.ValueOf(Primitive{
undecoded: data,
context: context,
}))
return nil
}
// Special case. Unmarshaler Interface support.
if rv.CanAddr() {
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
return v.UnmarshalTOML(data)
}
}
// Special case. Handle time.Time values specifically.
// TODO: Remove this code when we decide to drop support for Go 1.1.
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
// interfaces.
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
return md.unifyDatetime(data, rv)
}
// Special case. Look for a value satisfying the TextUnmarshaler interface.
if v, ok := rv.Interface().(TextUnmarshaler); ok {
return md.unifyText(data, v)
}
// BUG(burntsushi)
// The behavior here is incorrect whenever a Go type satisfies the
// encoding.TextUnmarshaler interface but also corresponds to a TOML
// hash or array. In particular, the unmarshaler should only be applied
// to primitive TOML values. But at this point, it will be applied to
// all kinds of values and produce an incorrect error whenever those values
// are hashes or arrays (including arrays of tables).
k := rv.Kind()
// laziness
if k >= reflect.Int && k <= reflect.Uint64 {
return md.unifyInt(data, rv)
}
switch k {
case reflect.Ptr:
elem := reflect.New(rv.Type().Elem())
err := md.unify(data, reflect.Indirect(elem))
if err != nil {
return err
}
rv.Set(elem)
return nil
case reflect.Struct:
return md.unifyStruct(data, rv)
case reflect.Map:
return md.unifyMap(data, rv)
case reflect.Array:
return md.unifyArray(data, rv)
case reflect.Slice:
return md.unifySlice(data, rv)
case reflect.String:
return md.unifyString(data, rv)
case reflect.Bool:
return md.unifyBool(data, rv)
case reflect.Interface:
// we only support empty interfaces.
if rv.NumMethod() > 0 {
return e("Unsupported type '%s'.", rv.Kind())
}
return md.unifyAnything(data, rv)
case reflect.Float32:
fallthrough
case reflect.Float64:
return md.unifyFloat64(data, rv)
}
return e("Unsupported type '%s'.", rv.Kind())
}
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
tmap, ok := mapping.(map[string]interface{})
if !ok {
return mismatch(rv, "map", mapping)
}
for key, datum := range tmap {
var f *field
fields := cachedTypeFields(rv.Type())
for i := range fields {
ff := &fields[i]
if ff.name == key {
f = ff
break
}
if f == nil && strings.EqualFold(ff.name, key) {
f = ff
}
}
if f != nil {
subv := rv
for _, i := range f.index {
subv = indirect(subv.Field(i))
}
if isUnifiable(subv) {
md.decoded[md.context.add(key).String()] = true
md.context = append(md.context, key)
if err := md.unify(datum, subv); err != nil {
return e("Type mismatch for '%s.%s': %s",
rv.Type().String(), f.name, err)
}
md.context = md.context[0 : len(md.context)-1]
} else if f.name != "" {
// Bad user! No soup for you!
return e("Field '%s.%s' is unexported, and therefore cannot "+
"be loaded with reflection.", rv.Type().String(), f.name)
}
}
}
return nil
}
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
tmap, ok := mapping.(map[string]interface{})
if !ok {
return badtype("map", mapping)
}
if rv.IsNil() {
rv.Set(reflect.MakeMap(rv.Type()))
}
for k, v := range tmap {
md.decoded[md.context.add(k).String()] = true
md.context = append(md.context, k)
rvkey := indirect(reflect.New(rv.Type().Key()))
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
if err := md.unify(v, rvval); err != nil {
return err
}
md.context = md.context[0 : len(md.context)-1]
rvkey.SetString(k)
rv.SetMapIndex(rvkey, rvval)
}
return nil
}
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
datav := reflect.ValueOf(data)
if datav.Kind() != reflect.Slice {
return badtype("slice", data)
}
sliceLen := datav.Len()
if sliceLen != rv.Len() {
return e("expected array length %d; got TOML array of length %d",
rv.Len(), sliceLen)
}
return md.unifySliceArray(datav, rv)
}
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
datav := reflect.ValueOf(data)
if datav.Kind() != reflect.Slice {
return badtype("slice", data)
}
sliceLen := datav.Len()
if rv.IsNil() {
rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen))
}
return md.unifySliceArray(datav, rv)
}
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
sliceLen := data.Len()
for i := 0; i < sliceLen; i++ {
v := data.Index(i).Interface()
sliceval := indirect(rv.Index(i))
if err := md.unify(v, sliceval); err != nil {
return err
}
}
return nil
}
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
if _, ok := data.(time.Time); ok {
rv.Set(reflect.ValueOf(data))
return nil
}
return badtype("time.Time", data)
}
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
if s, ok := data.(string); ok {
rv.SetString(s)
return nil
}
return badtype("string", data)
}
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
if num, ok := data.(float64); ok {
switch rv.Kind() {
case reflect.Float32:
fallthrough
case reflect.Float64:
rv.SetFloat(num)
default:
panic("bug")
}
return nil
}
return badtype("float", data)
}
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
if num, ok := data.(int64); ok {
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
switch rv.Kind() {
case reflect.Int, reflect.Int64:
// No bounds checking necessary.
case reflect.Int8:
if num < math.MinInt8 || num > math.MaxInt8 {
return e("Value '%d' is out of range for int8.", num)
}
case reflect.Int16:
if num < math.MinInt16 || num > math.MaxInt16 {
return e("Value '%d' is out of range for int16.", num)
}
case reflect.Int32:
if num < math.MinInt32 || num > math.MaxInt32 {
return e("Value '%d' is out of range for int32.", num)
}
}
rv.SetInt(num)
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
unum := uint64(num)
switch rv.Kind() {
case reflect.Uint, reflect.Uint64:
// No bounds checking necessary.
case reflect.Uint8:
if num < 0 || unum > math.MaxUint8 {
return e("Value '%d' is out of range for uint8.", num)
}
case reflect.Uint16:
if num < 0 || unum > math.MaxUint16 {
return e("Value '%d' is out of range for uint16.", num)
}
case reflect.Uint32:
if num < 0 || unum > math.MaxUint32 {
return e("Value '%d' is out of range for uint32.", num)
}
}
rv.SetUint(unum)
} else {
panic("unreachable")
}
return nil
}
return badtype("integer", data)
}
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
if b, ok := data.(bool); ok {
rv.SetBool(b)
return nil
}
return badtype("boolean", data)
}
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
rv.Set(reflect.ValueOf(data))
return nil
}
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
var s string
switch sdata := data.(type) {
case TextMarshaler:
text, err := sdata.MarshalText()
if err != nil {
return err
}
s = string(text)
case fmt.Stringer:
s = sdata.String()
case string:
s = sdata
case bool:
s = fmt.Sprintf("%v", sdata)
case int64:
s = fmt.Sprintf("%d", sdata)
case float64:
s = fmt.Sprintf("%f", sdata)
default:
return badtype("primitive (string-like)", data)
}
if err := v.UnmarshalText([]byte(s)); err != nil {
return err
}
return nil
}
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
func rvalue(v interface{}) reflect.Value {
return indirect(reflect.ValueOf(v))
}
// indirect returns the value pointed to by a pointer.
// Pointers are followed until the value is not a pointer.
// New values are allocated for each nil pointer.
//
// An exception to this rule is if the value satisfies an interface of
// interest to us (like encoding.TextUnmarshaler).
func indirect(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Ptr {
if v.CanAddr() {
pv := v.Addr()
if _, ok := pv.Interface().(TextUnmarshaler); ok {
return pv
}
}
return v
}
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
return indirect(reflect.Indirect(v))
}
func isUnifiable(rv reflect.Value) bool {
if rv.CanSet() {
return true
}
if _, ok := rv.Interface().(TextUnmarshaler); ok {
return true
}
return false
}
func badtype(expected string, data interface{}) error {
return e("Expected %s but found '%T'.", expected, data)
}
func mismatch(user reflect.Value, expected string, data interface{}) error {
return e("Type mismatch for %s. Expected %s but found '%T'.",
user.Type().String(), expected, data)
}

View File

@@ -0,0 +1,122 @@
package toml
import "strings"
// MetaData allows access to meta information about TOML data that may not
// be inferrable via reflection. In particular, whether a key has been defined
// and the TOML type of a key.
type MetaData struct {
mapping map[string]interface{}
types map[string]tomlType
keys []Key
decoded map[string]bool
context Key // Used only during decoding.
}
// IsDefined returns true if the key given exists in the TOML data. The key
// should be specified hierarchially. e.g.,
//
// // access the TOML key 'a.b.c'
// IsDefined("a", "b", "c")
//
// IsDefined will return false if an empty key given. Keys are case sensitive.
func (md *MetaData) IsDefined(key ...string) bool {
if len(key) == 0 {
return false
}
var hash map[string]interface{}
var ok bool
var hashOrVal interface{} = md.mapping
for _, k := range key {
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
return false
}
if hashOrVal, ok = hash[k]; !ok {
return false
}
}
return true
}
// Type returns a string representation of the type of the key specified.
//
// Type will return the empty string if given an empty key or a key that
// does not exist. Keys are case sensitive.
func (md *MetaData) Type(key ...string) string {
fullkey := strings.Join(key, ".")
if typ, ok := md.types[fullkey]; ok {
return typ.typeString()
}
return ""
}
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
// to get values of this type.
type Key []string
func (k Key) String() string {
return strings.Join(k, ".")
}
func (k Key) maybeQuotedAll() string {
var ss []string
for i := range k {
ss = append(ss, k.maybeQuoted(i))
}
return strings.Join(ss, ".")
}
func (k Key) maybeQuoted(i int) string {
quote := false
for _, c := range k[i] {
if !isBareKeyChar(c) {
quote = true
break
}
}
if quote {
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
} else {
return k[i]
}
}
func (k Key) add(piece string) Key {
newKey := make(Key, len(k)+1)
copy(newKey, k)
newKey[len(k)] = piece
return newKey
}
// Keys returns a slice of every key in the TOML data, including key groups.
// Each key is itself a slice, where the first element is the top of the
// hierarchy and the last is the most specific.
//
// The list will have the same order as the keys appeared in the TOML data.
//
// All keys returned are non-empty.
func (md *MetaData) Keys() []Key {
return md.keys
}
// Undecoded returns all keys that have not been decoded in the order in which
// they appear in the original TOML document.
//
// This includes keys that haven't been decoded because of a Primitive value.
// Once the Primitive value is decoded, the keys will be considered decoded.
//
// Also note that decoding into an empty interface will result in no decoding,
// and so no keys will be considered decoded.
//
// In this sense, the Undecoded keys correspond to keys in the TOML document
// that do not have a concrete type in your representation.
func (md *MetaData) Undecoded() []Key {
undecoded := make([]Key, 0, len(md.keys))
for _, key := range md.keys {
if !md.decoded[key.String()] {
undecoded = append(undecoded, key)
}
}
return undecoded
}

View File

@@ -0,0 +1,950 @@
package toml
import (
"fmt"
"log"
"reflect"
"testing"
"time"
)
func init() {
log.SetFlags(0)
}
func TestDecodeSimple(t *testing.T) {
var testSimple = `
age = 250
andrew = "gallant"
kait = "brady"
now = 1987-07-05T05:45:00Z
yesOrNo = true
pi = 3.14
colors = [
["red", "green", "blue"],
["cyan", "magenta", "yellow", "black"],
]
[My.Cats]
plato = "cat 1"
cauchy = "cat 2"
`
type cats struct {
Plato string
Cauchy string
}
type simple struct {
Age int
Colors [][]string
Pi float64
YesOrNo bool
Now time.Time
Andrew string
Kait string
My map[string]cats
}
var val simple
_, err := Decode(testSimple, &val)
if err != nil {
t.Fatal(err)
}
now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00")
if err != nil {
panic(err)
}
var answer = simple{
Age: 250,
Andrew: "gallant",
Kait: "brady",
Now: now,
YesOrNo: true,
Pi: 3.14,
Colors: [][]string{
{"red", "green", "blue"},
{"cyan", "magenta", "yellow", "black"},
},
My: map[string]cats{
"Cats": cats{Plato: "cat 1", Cauchy: "cat 2"},
},
}
if !reflect.DeepEqual(val, answer) {
t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n",
answer, val)
}
}
func TestDecodeEmbedded(t *testing.T) {
type Dog struct{ Name string }
type Age int
tests := map[string]struct {
input string
decodeInto interface{}
wantDecoded interface{}
}{
"embedded struct": {
input: `Name = "milton"`,
decodeInto: &struct{ Dog }{},
wantDecoded: &struct{ Dog }{Dog{"milton"}},
},
"embedded non-nil pointer to struct": {
input: `Name = "milton"`,
decodeInto: &struct{ *Dog }{},
wantDecoded: &struct{ *Dog }{&Dog{"milton"}},
},
"embedded nil pointer to struct": {
input: ``,
decodeInto: &struct{ *Dog }{},
wantDecoded: &struct{ *Dog }{nil},
},
"embedded int": {
input: `Age = -5`,
decodeInto: &struct{ Age }{},
wantDecoded: &struct{ Age }{-5},
},
}
for label, test := range tests {
_, err := Decode(test.input, test.decodeInto)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) {
t.Errorf("%s: want decoded == %+v, got %+v",
label, test.wantDecoded, test.decodeInto)
}
}
}
func TestTableArrays(t *testing.T) {
var tomlTableArrays = `
[[albums]]
name = "Born to Run"
[[albums.songs]]
name = "Jungleland"
[[albums.songs]]
name = "Meeting Across the River"
[[albums]]
name = "Born in the USA"
[[albums.songs]]
name = "Glory Days"
[[albums.songs]]
name = "Dancing in the Dark"
`
type Song struct {
Name string
}
type Album struct {
Name string
Songs []Song
}
type Music struct {
Albums []Album
}
expected := Music{[]Album{
{"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}},
{"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}},
}}
var got Music
if _, err := Decode(tomlTableArrays, &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, got) {
t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
}
}
// Case insensitive matching tests.
// A bit more comprehensive than needed given the current implementation,
// but implementations change.
// Probably still missing demonstrations of some ugly corner cases regarding
// case insensitive matching and multiple fields.
func TestCase(t *testing.T) {
var caseToml = `
tOpString = "string"
tOpInt = 1
tOpFloat = 1.1
tOpBool = true
tOpdate = 2006-01-02T15:04:05Z
tOparray = [ "array" ]
Match = "i should be in Match only"
MatcH = "i should be in MatcH only"
once = "just once"
[nEst.eD]
nEstedString = "another string"
`
type InsensitiveEd struct {
NestedString string
}
type InsensitiveNest struct {
Ed InsensitiveEd
}
type Insensitive struct {
TopString string
TopInt int
TopFloat float64
TopBool bool
TopDate time.Time
TopArray []string
Match string
MatcH string
Once string
OncE string
Nest InsensitiveNest
}
tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5])
if err != nil {
panic(err)
}
expected := Insensitive{
TopString: "string",
TopInt: 1,
TopFloat: 1.1,
TopBool: true,
TopDate: tme,
TopArray: []string{"array"},
MatcH: "i should be in MatcH only",
Match: "i should be in Match only",
Once: "just once",
OncE: "",
Nest: InsensitiveNest{
Ed: InsensitiveEd{NestedString: "another string"},
},
}
var got Insensitive
if _, err := Decode(caseToml, &got); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expected, got) {
t.Fatalf("\n%#v\n!=\n%#v\n", expected, got)
}
}
func TestPointers(t *testing.T) {
type Object struct {
Type string
Description string
}
type Dict struct {
NamedObject map[string]*Object
BaseObject *Object
Strptr *string
Strptrs []*string
}
s1, s2, s3 := "blah", "abc", "def"
expected := &Dict{
Strptr: &s1,
Strptrs: []*string{&s2, &s3},
NamedObject: map[string]*Object{
"foo": {"FOO", "fooooo!!!"},
"bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"},
},
BaseObject: &Object{"BASE", "da base"},
}
ex1 := `
Strptr = "blah"
Strptrs = ["abc", "def"]
[NamedObject.foo]
Type = "FOO"
Description = "fooooo!!!"
[NamedObject.bar]
Type = "BAR"
Description = "ba-ba-ba-ba-barrrr!!!"
[BaseObject]
Type = "BASE"
Description = "da base"
`
dict := new(Dict)
_, err := Decode(ex1, dict)
if err != nil {
t.Errorf("Decode error: %v", err)
}
if !reflect.DeepEqual(expected, dict) {
t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict)
}
}
type sphere struct {
Center [3]float64
Radius float64
}
func TestDecodeSimpleArray(t *testing.T) {
var s1 sphere
if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil {
t.Fatal(err)
}
}
func TestDecodeArrayWrongSize(t *testing.T) {
var s1 sphere
if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil {
t.Fatal("Expected array type mismatch error")
}
}
func TestDecodeLargeIntoSmallInt(t *testing.T) {
type table struct {
Value int8
}
var tab table
if _, err := Decode(`value = 500`, &tab); err == nil {
t.Fatal("Expected integer out-of-bounds error.")
}
}
func TestDecodeSizedInts(t *testing.T) {
type table struct {
U8 uint8
U16 uint16
U32 uint32
U64 uint64
U uint
I8 int8
I16 int16
I32 int32
I64 int64
I int
}
answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1}
toml := `
u8 = 1
u16 = 1
u32 = 1
u64 = 1
u = 1
i8 = -1
i16 = -1
i32 = -1
i64 = -1
i = -1
`
var tab table
if _, err := Decode(toml, &tab); err != nil {
t.Fatal(err.Error())
}
if answer != tab {
t.Fatalf("Expected %#v but got %#v", answer, tab)
}
}
func TestUnmarshaler(t *testing.T) {
var tomlBlob = `
[dishes.hamboogie]
name = "Hamboogie with fries"
price = 10.99
[[dishes.hamboogie.ingredients]]
name = "Bread Bun"
[[dishes.hamboogie.ingredients]]
name = "Lettuce"
[[dishes.hamboogie.ingredients]]
name = "Real Beef Patty"
[[dishes.hamboogie.ingredients]]
name = "Tomato"
[dishes.eggsalad]
name = "Egg Salad with rice"
price = 3.99
[[dishes.eggsalad.ingredients]]
name = "Egg"
[[dishes.eggsalad.ingredients]]
name = "Mayo"
[[dishes.eggsalad.ingredients]]
name = "Rice"
`
m := &menu{}
if _, err := Decode(tomlBlob, m); err != nil {
log.Fatal(err)
}
if len(m.Dishes) != 2 {
t.Log("two dishes should be loaded with UnmarshalTOML()")
t.Errorf("expected %d but got %d", 2, len(m.Dishes))
}
eggSalad := m.Dishes["eggsalad"]
if _, ok := interface{}(eggSalad).(dish); !ok {
t.Errorf("expected a dish")
}
if eggSalad.Name != "Egg Salad with rice" {
t.Errorf("expected the dish to be named 'Egg Salad with rice'")
}
if len(eggSalad.Ingredients) != 3 {
t.Log("dish should be loaded with UnmarshalTOML()")
t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients))
}
found := false
for _, i := range eggSalad.Ingredients {
if i.Name == "Rice" {
found = true
break
}
}
if !found {
t.Error("Rice was not loaded in UnmarshalTOML()")
}
// test on a value - must be passed as *
o := menu{}
if _, err := Decode(tomlBlob, &o); err != nil {
log.Fatal(err)
}
}
type menu struct {
Dishes map[string]dish
}
func (m *menu) UnmarshalTOML(p interface{}) error {
m.Dishes = make(map[string]dish)
data, _ := p.(map[string]interface{})
dishes := data["dishes"].(map[string]interface{})
for n, v := range dishes {
if d, ok := v.(map[string]interface{}); ok {
nd := dish{}
nd.UnmarshalTOML(d)
m.Dishes[n] = nd
} else {
return fmt.Errorf("not a dish")
}
}
return nil
}
type dish struct {
Name string
Price float32
Ingredients []ingredient
}
func (d *dish) UnmarshalTOML(p interface{}) error {
data, _ := p.(map[string]interface{})
d.Name, _ = data["name"].(string)
d.Price, _ = data["price"].(float32)
ingredients, _ := data["ingredients"].([]map[string]interface{})
for _, e := range ingredients {
n, _ := interface{}(e).(map[string]interface{})
name, _ := n["name"].(string)
i := ingredient{name}
d.Ingredients = append(d.Ingredients, i)
}
return nil
}
type ingredient struct {
Name string
}
func ExampleMetaData_PrimitiveDecode() {
var md MetaData
var err error
var tomlBlob = `
ranking = ["Springsteen", "J Geils"]
[bands.Springsteen]
started = 1973
albums = ["Greetings", "WIESS", "Born to Run", "Darkness"]
[bands."J Geils"]
started = 1970
albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"]
`
type band struct {
Started int
Albums []string
}
type classics struct {
Ranking []string
Bands map[string]Primitive
}
// Do the initial decode. Reflection is delayed on Primitive values.
var music classics
if md, err = Decode(tomlBlob, &music); err != nil {
log.Fatal(err)
}
// MetaData still includes information on Primitive values.
fmt.Printf("Is `bands.Springsteen` defined? %v\n",
md.IsDefined("bands", "Springsteen"))
// Decode primitive data into Go values.
for _, artist := range music.Ranking {
// A band is a primitive value, so we need to decode it to get a
// real `band` value.
primValue := music.Bands[artist]
var aBand band
if err = md.PrimitiveDecode(primValue, &aBand); err != nil {
log.Fatal(err)
}
fmt.Printf("%s started in %d.\n", artist, aBand.Started)
}
// Check to see if there were any fields left undecoded.
// Note that this won't be empty before decoding the Primitive value!
fmt.Printf("Undecoded: %q\n", md.Undecoded())
// Output:
// Is `bands.Springsteen` defined? true
// Springsteen started in 1973.
// J Geils started in 1970.
// Undecoded: []
}
func ExampleDecode() {
var tomlBlob = `
# Some comments.
[alpha]
ip = "10.0.0.1"
[alpha.config]
Ports = [ 8001, 8002 ]
Location = "Toronto"
Created = 1987-07-05T05:45:00Z
[beta]
ip = "10.0.0.2"
[beta.config]
Ports = [ 9001, 9002 ]
Location = "New Jersey"
Created = 1887-01-05T05:55:00Z
`
type serverConfig struct {
Ports []int
Location string
Created time.Time
}
type server struct {
IP string `toml:"ip"`
Config serverConfig `toml:"config"`
}
type servers map[string]server
var config servers
if _, err := Decode(tomlBlob, &config); err != nil {
log.Fatal(err)
}
for _, name := range []string{"alpha", "beta"} {
s := config[name]
fmt.Printf("Server: %s (ip: %s) in %s created on %s\n",
name, s.IP, s.Config.Location,
s.Config.Created.Format("2006-01-02"))
fmt.Printf("Ports: %v\n", s.Config.Ports)
}
// Output:
// Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05
// Ports: [8001 8002]
// Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05
// Ports: [9001 9002]
}
type duration struct {
time.Duration
}
func (d *duration) UnmarshalText(text []byte) error {
var err error
d.Duration, err = time.ParseDuration(string(text))
return err
}
// Example Unmarshaler shows how to decode TOML strings into your own
// custom data type.
func Example_unmarshaler() {
blob := `
[[song]]
name = "Thunder Road"
duration = "4m49s"
[[song]]
name = "Stairway to Heaven"
duration = "8m03s"
`
type song struct {
Name string
Duration duration
}
type songs struct {
Song []song
}
var favorites songs
if _, err := Decode(blob, &favorites); err != nil {
log.Fatal(err)
}
// Code to implement the TextUnmarshaler interface for `duration`:
//
// type duration struct {
// time.Duration
// }
//
// func (d *duration) UnmarshalText(text []byte) error {
// var err error
// d.Duration, err = time.ParseDuration(string(text))
// return err
// }
for _, s := range favorites.Song {
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
}
// Output:
// Thunder Road (4m49s)
// Stairway to Heaven (8m3s)
}
// Example StrictDecoding shows how to detect whether there are keys in the
// TOML document that weren't decoded into the value given. This is useful
// for returning an error to the user if they've included extraneous fields
// in their configuration.
func Example_strictDecoding() {
var blob = `
key1 = "value1"
key2 = "value2"
key3 = "value3"
`
type config struct {
Key1 string
Key3 string
}
var conf config
md, err := Decode(blob, &conf)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Undecoded keys: %q\n", md.Undecoded())
// Output:
// Undecoded keys: ["key2"]
}
// Example UnmarshalTOML shows how to implement a struct type that knows how to
// unmarshal itself. The struct must take full responsibility for mapping the
// values passed into the struct. The method may be used with interfaces in a
// struct in cases where the actual type is not known until the data is
// examined.
func Example_unmarshalTOML() {
var blob = `
[[parts]]
type = "valve"
id = "valve-1"
size = 1.2
rating = 4
[[parts]]
type = "valve"
id = "valve-2"
size = 2.1
rating = 5
[[parts]]
type = "pipe"
id = "pipe-1"
length = 2.1
diameter = 12
[[parts]]
type = "cable"
id = "cable-1"
length = 12
rating = 3.1
`
o := &order{}
err := Unmarshal([]byte(blob), o)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(o.parts))
for _, part := range o.parts {
fmt.Println(part.Name())
}
// Code to implement UmarshalJSON.
// type order struct {
// // NOTE `order.parts` is a private slice of type `part` which is an
// // interface and may only be loaded from toml using the
// // UnmarshalTOML() method of the Umarshaler interface.
// parts parts
// }
// func (o *order) UnmarshalTOML(data interface{}) error {
// // NOTE the example below contains detailed type casting to show how
// // the 'data' is retrieved. In operational use, a type cast wrapper
// // may be prefered e.g.
// //
// // func AsMap(v interface{}) (map[string]interface{}, error) {
// // return v.(map[string]interface{})
// // }
// //
// // resulting in:
// // d, _ := AsMap(data)
// //
// d, _ := data.(map[string]interface{})
// parts, _ := d["parts"].([]map[string]interface{})
// for _, p := range parts {
// typ, _ := p["type"].(string)
// id, _ := p["id"].(string)
// // detect the type of part and handle each case
// switch p["type"] {
// case "valve":
// size := float32(p["size"].(float64))
// rating := int(p["rating"].(int64))
// valve := &valve{
// Type: typ,
// ID: id,
// Size: size,
// Rating: rating,
// }
// o.parts = append(o.parts, valve)
// case "pipe":
// length := float32(p["length"].(float64))
// diameter := int(p["diameter"].(int64))
// pipe := &pipe{
// Type: typ,
// ID: id,
// Length: length,
// Diameter: diameter,
// }
// o.parts = append(o.parts, pipe)
// case "cable":
// length := int(p["length"].(int64))
// rating := float32(p["rating"].(float64))
// cable := &cable{
// Type: typ,
// ID: id,
// Length: length,
// Rating: rating,
// }
// o.parts = append(o.parts, cable)
// }
// }
// return nil
// }
// type parts []part
// type part interface {
// Name() string
// }
// type valve struct {
// Type string
// ID string
// Size float32
// Rating int
// }
// func (v *valve) Name() string {
// return fmt.Sprintf("VALVE: %s", v.ID)
// }
// type pipe struct {
// Type string
// ID string
// Length float32
// Diameter int
// }
// func (p *pipe) Name() string {
// return fmt.Sprintf("PIPE: %s", p.ID)
// }
// type cable struct {
// Type string
// ID string
// Length int
// Rating float32
// }
// func (c *cable) Name() string {
// return fmt.Sprintf("CABLE: %s", c.ID)
// }
// Output:
// 4
// VALVE: valve-1
// VALVE: valve-2
// PIPE: pipe-1
// CABLE: cable-1
}
type order struct {
// NOTE `order.parts` is a private slice of type `part` which is an
// interface and may only be loaded from toml using the UnmarshalTOML()
// method of the Umarshaler interface.
parts parts
}
func (o *order) UnmarshalTOML(data interface{}) error {
// NOTE the example below contains detailed type casting to show how
// the 'data' is retrieved. In operational use, a type cast wrapper
// may be prefered e.g.
//
// func AsMap(v interface{}) (map[string]interface{}, error) {
// return v.(map[string]interface{})
// }
//
// resulting in:
// d, _ := AsMap(data)
//
d, _ := data.(map[string]interface{})
parts, _ := d["parts"].([]map[string]interface{})
for _, p := range parts {
typ, _ := p["type"].(string)
id, _ := p["id"].(string)
// detect the type of part and handle each case
switch p["type"] {
case "valve":
size := float32(p["size"].(float64))
rating := int(p["rating"].(int64))
valve := &valve{
Type: typ,
ID: id,
Size: size,
Rating: rating,
}
o.parts = append(o.parts, valve)
case "pipe":
length := float32(p["length"].(float64))
diameter := int(p["diameter"].(int64))
pipe := &pipe{
Type: typ,
ID: id,
Length: length,
Diameter: diameter,
}
o.parts = append(o.parts, pipe)
case "cable":
length := int(p["length"].(int64))
rating := float32(p["rating"].(float64))
cable := &cable{
Type: typ,
ID: id,
Length: length,
Rating: rating,
}
o.parts = append(o.parts, cable)
}
}
return nil
}
type parts []part
type part interface {
Name() string
}
type valve struct {
Type string
ID string
Size float32
Rating int
}
func (v *valve) Name() string {
return fmt.Sprintf("VALVE: %s", v.ID)
}
type pipe struct {
Type string
ID string
Length float32
Diameter int
}
func (p *pipe) Name() string {
return fmt.Sprintf("PIPE: %s", p.ID)
}
type cable struct {
Type string
ID string
Length int
Rating float32
}
func (c *cable) Name() string {
return fmt.Sprintf("CABLE: %s", c.ID)
}

View File

@@ -0,0 +1,27 @@
/*
Package toml provides facilities for decoding and encoding TOML configuration
files via reflection. There is also support for delaying decoding with
the Primitive type, and querying the set of keys in a TOML document with the
MetaData type.
The specification implemented: https://github.com/mojombo/toml
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
whether a file is a valid TOML document. It can also be used to print the
type of each key in a TOML document.
Testing
There are two important types of tests used for this package. The first is
contained inside '*_test.go' files and uses the standard Go unit testing
framework. These tests are primarily devoted to holistically testing the
decoder and encoder.
The second type of testing is used to verify the implementation's adherence
to the TOML specification. These tests have been factored into their own
project: https://github.com/BurntSushi/toml-test
The reason the tests are in a separate project is so that they can be used by
any implementation of TOML. Namely, it is language agnostic.
*/
package toml

View File

@@ -0,0 +1,551 @@
package toml
import (
"bufio"
"errors"
"fmt"
"io"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
type tomlEncodeError struct{ error }
var (
errArrayMixedElementTypes = errors.New(
"can't encode array with mixed element types")
errArrayNilElement = errors.New(
"can't encode array with nil element")
errNonString = errors.New(
"can't encode a map with non-string key type")
errAnonNonStruct = errors.New(
"can't encode an anonymous field that is not a struct")
errArrayNoTable = errors.New(
"TOML array element can't contain a table")
errNoKey = errors.New(
"top-level values must be a Go map or struct")
errAnything = errors.New("") // used in testing
)
var quotedReplacer = strings.NewReplacer(
"\t", "\\t",
"\n", "\\n",
"\r", "\\r",
"\"", "\\\"",
"\\", "\\\\",
)
// Encoder controls the encoding of Go values to a TOML document to some
// io.Writer.
//
// The indentation level can be controlled with the Indent field.
type Encoder struct {
// A single indentation level. By default it is two spaces.
Indent string
// hasWritten is whether we have written any output to w yet.
hasWritten bool
w *bufio.Writer
}
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
// given. By default, a single indentation level is 2 spaces.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
w: bufio.NewWriter(w),
Indent: " ",
}
}
// Encode writes a TOML representation of the Go value to the underlying
// io.Writer. If the value given cannot be encoded to a valid TOML document,
// then an error is returned.
//
// The mapping between Go values and TOML values should be precisely the same
// as for the Decode* functions. Similarly, the TextMarshaler interface is
// supported by encoding the resulting bytes as strings. (If you want to write
// arbitrary binary data then you will need to use something like base64 since
// TOML does not have any binary types.)
//
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
// sub-hashes are encoded first.
//
// If a Go map is encoded, then its keys are sorted alphabetically for
// deterministic output. More control over this behavior may be provided if
// there is demand for it.
//
// Encoding Go values without a corresponding TOML representation---like map
// types with non-string keys---will cause an error to be returned. Similarly
// for mixed arrays/slices, arrays/slices with nil elements, embedded
// non-struct types and nested slices containing maps or structs.
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
// and so is []map[string][]string.)
func (enc *Encoder) Encode(v interface{}) error {
rv := eindirect(reflect.ValueOf(v))
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
return err
}
return enc.w.Flush()
}
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
defer func() {
if r := recover(); r != nil {
if terr, ok := r.(tomlEncodeError); ok {
err = terr.error
return
}
panic(r)
}
}()
enc.encode(key, rv)
return nil
}
func (enc *Encoder) encode(key Key, rv reflect.Value) {
// Special case. Time needs to be in ISO8601 format.
// Special case. If we can marshal the type to text, then we used that.
// Basically, this prevents the encoder for handling these types as
// generic structs (or whatever the underlying type of a TextMarshaler is).
switch rv.Interface().(type) {
case time.Time, TextMarshaler:
enc.keyEqElement(key, rv)
return
}
k := rv.Kind()
switch k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64,
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
enc.keyEqElement(key, rv)
case reflect.Array, reflect.Slice:
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
enc.eArrayOfTables(key, rv)
} else {
enc.keyEqElement(key, rv)
}
case reflect.Interface:
if rv.IsNil() {
return
}
enc.encode(key, rv.Elem())
case reflect.Map:
if rv.IsNil() {
return
}
enc.eTable(key, rv)
case reflect.Ptr:
if rv.IsNil() {
return
}
enc.encode(key, rv.Elem())
case reflect.Struct:
enc.eTable(key, rv)
default:
panic(e("Unsupported type for key '%s': %s", key, k))
}
}
// eElement encodes any value that can be an array element (primitives and
// arrays).
func (enc *Encoder) eElement(rv reflect.Value) {
switch v := rv.Interface().(type) {
case time.Time:
// Special case time.Time as a primitive. Has to come before
// TextMarshaler below because time.Time implements
// encoding.TextMarshaler, but we need to always use UTC.
enc.wf(v.In(time.FixedZone("UTC", 0)).Format("2006-01-02T15:04:05Z"))
return
case TextMarshaler:
// Special case. Use text marshaler if it's available for this value.
if s, err := v.MarshalText(); err != nil {
encPanic(err)
} else {
enc.writeQuoted(string(s))
}
return
}
switch rv.Kind() {
case reflect.Bool:
enc.wf(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
enc.wf(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64:
enc.wf(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32:
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
case reflect.Float64:
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv)
case reflect.Interface:
enc.eElement(rv.Elem())
case reflect.String:
enc.writeQuoted(rv.String())
default:
panic(e("Unexpected primitive type: %s", rv.Kind()))
}
}
// By the TOML spec, all floats must have a decimal with at least one
// number on either side.
func floatAddDecimal(fstr string) string {
if !strings.Contains(fstr, ".") {
return fstr + ".0"
}
return fstr
}
func (enc *Encoder) writeQuoted(s string) {
enc.wf("\"%s\"", quotedReplacer.Replace(s))
}
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
length := rv.Len()
enc.wf("[")
for i := 0; i < length; i++ {
elem := rv.Index(i)
enc.eElement(elem)
if i != length-1 {
enc.wf(", ")
}
}
enc.wf("]")
}
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
if len(key) == 0 {
encPanic(errNoKey)
}
for i := 0; i < rv.Len(); i++ {
trv := rv.Index(i)
if isNil(trv) {
continue
}
panicIfInvalidKey(key)
enc.newline()
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
enc.newline()
enc.eMapOrStruct(key, trv)
}
}
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
panicIfInvalidKey(key)
if len(key) == 1 {
// Output an extra new line between top-level tables.
// (The newline isn't written if nothing else has been written though.)
enc.newline()
}
if len(key) > 0 {
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
enc.newline()
}
enc.eMapOrStruct(key, rv)
}
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
switch rv := eindirect(rv); rv.Kind() {
case reflect.Map:
enc.eMap(key, rv)
case reflect.Struct:
enc.eStruct(key, rv)
default:
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
}
}
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
rt := rv.Type()
if rt.Key().Kind() != reflect.String {
encPanic(errNonString)
}
// Sort keys so that we have deterministic output. And write keys directly
// underneath this key first, before writing sub-structs or sub-maps.
var mapKeysDirect, mapKeysSub []string
for _, mapKey := range rv.MapKeys() {
k := mapKey.String()
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
mapKeysSub = append(mapKeysSub, k)
} else {
mapKeysDirect = append(mapKeysDirect, k)
}
}
var writeMapKeys = func(mapKeys []string) {
sort.Strings(mapKeys)
for _, mapKey := range mapKeys {
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
if isNil(mrv) {
// Don't write anything for nil fields.
continue
}
enc.encode(key.add(mapKey), mrv)
}
}
writeMapKeys(mapKeysDirect)
writeMapKeys(mapKeysSub)
}
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
// Write keys for fields directly under this key first, because if we write
// a field that creates a new table, then all keys under it will be in that
// table (not the one we're writing here).
rt := rv.Type()
var fieldsDirect, fieldsSub [][]int
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
// skip unexporded fields
if f.PkgPath != "" {
continue
}
frv := rv.Field(i)
if f.Anonymous {
frv := eindirect(frv)
t := frv.Type()
if t.Kind() != reflect.Struct {
encPanic(errAnonNonStruct)
}
addFields(t, frv, f.Index)
} else if typeIsHash(tomlTypeOfGo(frv)) {
fieldsSub = append(fieldsSub, append(start, f.Index...))
} else {
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
}
}
}
addFields(rt, rv, nil)
var writeFields = func(fields [][]int) {
for _, fieldIndex := range fields {
sft := rt.FieldByIndex(fieldIndex)
sf := rv.FieldByIndex(fieldIndex)
if isNil(sf) {
// Don't write anything for nil fields.
continue
}
keyName := sft.Tag.Get("toml")
if keyName == "-" {
continue
}
if keyName == "" {
keyName = sft.Name
}
keyName, opts := getOptions(keyName)
if _, ok := opts["omitempty"]; ok && isEmpty(sf) {
continue
} else if _, ok := opts["omitzero"]; ok && isZero(sf) {
continue
}
enc.encode(key.add(keyName), sf)
}
}
writeFields(fieldsDirect)
writeFields(fieldsSub)
}
// tomlTypeName returns the TOML type name of the Go value's type. It is
// used to determine whether the types of array elements are mixed (which is
// forbidden). If the Go value is nil, then it is illegal for it to be an array
// element, and valueIsNil is returned as true.
// Returns the TOML type of a Go value. The type may be `nil`, which means
// no concrete TOML type could be found.
func tomlTypeOfGo(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() {
return nil
}
switch rv.Kind() {
case reflect.Bool:
return tomlBool
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
return tomlInteger
case reflect.Float32, reflect.Float64:
return tomlFloat
case reflect.Array, reflect.Slice:
if typeEqual(tomlHash, tomlArrayType(rv)) {
return tomlArrayHash
} else {
return tomlArray
}
case reflect.Ptr, reflect.Interface:
return tomlTypeOfGo(rv.Elem())
case reflect.String:
return tomlString
case reflect.Map:
return tomlHash
case reflect.Struct:
switch rv.Interface().(type) {
case time.Time:
return tomlDatetime
case TextMarshaler:
return tomlString
default:
return tomlHash
}
default:
panic("unexpected reflect.Kind: " + rv.Kind().String())
}
}
// tomlArrayType returns the element type of a TOML array. The type returned
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
// slize). This function may also panic if it finds a type that cannot be
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
// nested arrays of tables).
func tomlArrayType(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
return nil
}
firstType := tomlTypeOfGo(rv.Index(0))
if firstType == nil {
encPanic(errArrayNilElement)
}
rvlen := rv.Len()
for i := 1; i < rvlen; i++ {
elem := rv.Index(i)
switch elemType := tomlTypeOfGo(elem); {
case elemType == nil:
encPanic(errArrayNilElement)
case !typeEqual(firstType, elemType):
encPanic(errArrayMixedElementTypes)
}
}
// If we have a nested array, then we must make sure that the nested
// array contains ONLY primitives.
// This checks arbitrarily nested arrays.
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
nest := tomlArrayType(eindirect(rv.Index(0)))
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
encPanic(errArrayNoTable)
}
}
return firstType
}
func getOptions(keyName string) (string, map[string]struct{}) {
opts := make(map[string]struct{})
ss := strings.Split(keyName, ",")
name := ss[0]
if len(ss) > 1 {
for _, opt := range ss {
opts[opt] = struct{}{}
}
}
return name, opts
}
func isZero(rv reflect.Value) bool {
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if rv.Int() == 0 {
return true
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if rv.Uint() == 0 {
return true
}
case reflect.Float32, reflect.Float64:
if rv.Float() == 0.0 {
return true
}
}
return false
}
func isEmpty(rv reflect.Value) bool {
switch rv.Kind() {
case reflect.String:
if len(strings.TrimSpace(rv.String())) == 0 {
return true
}
case reflect.Array, reflect.Slice, reflect.Map:
if rv.Len() == 0 {
return true
}
}
return false
}
func (enc *Encoder) newline() {
if enc.hasWritten {
enc.wf("\n")
}
}
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
if len(key) == 0 {
encPanic(errNoKey)
}
panicIfInvalidKey(key)
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.eElement(val)
enc.newline()
}
func (enc *Encoder) wf(format string, v ...interface{}) {
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
encPanic(err)
}
enc.hasWritten = true
}
func (enc *Encoder) indentStr(key Key) string {
return strings.Repeat(enc.Indent, len(key)-1)
}
func encPanic(err error) {
panic(tomlEncodeError{err})
}
func eindirect(v reflect.Value) reflect.Value {
switch v.Kind() {
case reflect.Ptr, reflect.Interface:
return eindirect(v.Elem())
default:
return v
}
}
func isNil(rv reflect.Value) bool {
switch rv.Kind() {
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return rv.IsNil()
default:
return false
}
}
func panicIfInvalidKey(key Key) {
for _, k := range key {
if len(k) == 0 {
encPanic(e("Key '%s' is not a valid table name. Key names "+
"cannot be empty.", key.maybeQuotedAll()))
}
}
}
func isValidKeyName(s string) bool {
return len(s) != 0
}

View File

@@ -0,0 +1,542 @@
package toml
import (
"bytes"
"fmt"
"log"
"net"
"testing"
"time"
)
func TestEncodeRoundTrip(t *testing.T) {
type Config struct {
Age int
Cats []string
Pi float64
Perfection []int
DOB time.Time
Ipaddress net.IP
}
var inputs = Config{
13,
[]string{"one", "two", "three"},
3.145,
[]int{11, 2, 3, 4},
time.Now(),
net.ParseIP("192.168.59.254"),
}
var firstBuffer bytes.Buffer
e := NewEncoder(&firstBuffer)
err := e.Encode(inputs)
if err != nil {
t.Fatal(err)
}
var outputs Config
if _, err := Decode(firstBuffer.String(), &outputs); err != nil {
log.Printf("Could not decode:\n-----\n%s\n-----\n",
firstBuffer.String())
t.Fatal(err)
}
// could test each value individually, but I'm lazy
var secondBuffer bytes.Buffer
e2 := NewEncoder(&secondBuffer)
err = e2.Encode(outputs)
if err != nil {
t.Fatal(err)
}
if firstBuffer.String() != secondBuffer.String() {
t.Error(
firstBuffer.String(),
"\n\n is not identical to\n\n",
secondBuffer.String())
}
}
// XXX(burntsushi)
// I think these tests probably should be removed. They are good, but they
// ought to be obsolete by toml-test.
func TestEncode(t *testing.T) {
type Embedded struct {
Int int `toml:"_int"`
}
type NonStruct int
date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600))
dateStr := "2014-05-11T19:30:40Z"
tests := map[string]struct {
input interface{}
wantOutput string
wantError error
}{
"bool field": {
input: struct {
BoolTrue bool
BoolFalse bool
}{true, false},
wantOutput: "BoolTrue = true\nBoolFalse = false\n",
},
"int fields": {
input: struct {
Int int
Int8 int8
Int16 int16
Int32 int32
Int64 int64
}{1, 2, 3, 4, 5},
wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
},
"uint fields": {
input: struct {
Uint uint
Uint8 uint8
Uint16 uint16
Uint32 uint32
Uint64 uint64
}{1, 2, 3, 4, 5},
wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
"\nUint64 = 5\n",
},
"float fields": {
input: struct {
Float32 float32
Float64 float64
}{1.5, 2.5},
wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
},
"string field": {
input: struct{ String string }{"foo"},
wantOutput: "String = \"foo\"\n",
},
"string field and unexported field": {
input: struct {
String string
unexported int
}{"foo", 0},
wantOutput: "String = \"foo\"\n",
},
"datetime field in UTC": {
input: struct{ Date time.Time }{date},
wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
},
"datetime field as primitive": {
// Using a map here to fail if isStructOrMap() returns true for
// time.Time.
input: map[string]interface{}{
"Date": date,
"Int": 1,
},
wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
},
"array fields": {
input: struct {
IntArray0 [0]int
IntArray3 [3]int
}{[0]int{}, [3]int{1, 2, 3}},
wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
},
"slice fields": {
input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
nil, []int{}, []int{1, 2, 3},
},
wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
},
"datetime slices": {
input: struct{ DatetimeSlice []time.Time }{
[]time.Time{date, date},
},
wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
dateStr, dateStr),
},
"nested arrays and slices": {
input: struct {
SliceOfArrays [][2]int
ArrayOfSlices [2][]int
SliceOfArraysOfSlices [][2][]int
ArrayOfSlicesOfArrays [2][][2]int
SliceOfMixedArrays [][2]interface{}
ArrayOfMixedSlices [2][]interface{}
}{
[][2]int{{1, 2}, {3, 4}},
[2][]int{{1, 2}, {3, 4}},
[][2][]int{
{
{1, 2}, {3, 4},
},
{
{5, 6}, {7, 8},
},
},
[2][][2]int{
{
{1, 2}, {3, 4},
},
{
{5, 6}, {7, 8},
},
},
[][2]interface{}{
{1, 2}, {"a", "b"},
},
[2][]interface{}{
{1, 2}, {"a", "b"},
},
},
wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
ArrayOfSlices = [[1, 2], [3, 4]]
SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
SliceOfMixedArrays = [[1, 2], ["a", "b"]]
ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
`,
},
"empty slice": {
input: struct{ Empty []interface{} }{[]interface{}{}},
wantOutput: "Empty = []\n",
},
"(error) slice with element type mismatch (string and integer)": {
input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
wantError: errArrayMixedElementTypes,
},
"(error) slice with element type mismatch (integer and float)": {
input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
wantError: errArrayMixedElementTypes,
},
"slice with elems of differing Go types, same TOML types": {
input: struct {
MixedInts []interface{}
MixedFloats []interface{}
}{
[]interface{}{
int(1), int8(2), int16(3), int32(4), int64(5),
uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
},
[]interface{}{float32(1.5), float64(2.5)},
},
wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
"MixedFloats = [1.5, 2.5]\n",
},
"(error) slice w/ element type mismatch (one is nested array)": {
input: struct{ Mixed []interface{} }{
[]interface{}{1, []interface{}{2}},
},
wantError: errArrayMixedElementTypes,
},
"(error) slice with 1 nil element": {
input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
wantError: errArrayNilElement,
},
"(error) slice with 1 nil element (and other non-nil elements)": {
input: struct{ NilElement []interface{} }{
[]interface{}{1, nil},
},
wantError: errArrayNilElement,
},
"simple map": {
input: map[string]int{"a": 1, "b": 2},
wantOutput: "a = 1\nb = 2\n",
},
"map with interface{} value type": {
input: map[string]interface{}{"a": 1, "b": "c"},
wantOutput: "a = 1\nb = \"c\"\n",
},
"map with interface{} value type, some of which are structs": {
input: map[string]interface{}{
"a": struct{ Int int }{2},
"b": 1,
},
wantOutput: "b = 1\n\n[a]\n Int = 2\n",
},
"nested map": {
input: map[string]map[string]int{
"a": {"b": 1},
"c": {"d": 2},
},
wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
},
"nested struct": {
input: struct{ Struct struct{ Int int } }{
struct{ Int int }{1},
},
wantOutput: "[Struct]\n Int = 1\n",
},
"nested struct and non-struct field": {
input: struct {
Struct struct{ Int int }
Bool bool
}{struct{ Int int }{1}, true},
wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
},
"2 nested structs": {
input: struct{ Struct1, Struct2 struct{ Int int } }{
struct{ Int int }{1}, struct{ Int int }{2},
},
wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
},
"deeply nested structs": {
input: struct {
Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
}{
struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
struct{ Struct3 *struct{ Int int } }{nil},
},
wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
"\n\n[Struct2]\n",
},
"nested struct with nil struct elem": {
input: struct {
Struct struct{ Inner *struct{ Int int } }
}{
struct{ Inner *struct{ Int int } }{nil},
},
wantOutput: "[Struct]\n",
},
"nested struct with no fields": {
input: struct {
Struct struct{ Inner struct{} }
}{
struct{ Inner struct{} }{struct{}{}},
},
wantOutput: "[Struct]\n [Struct.Inner]\n",
},
"struct with tags": {
input: struct {
Struct struct {
Int int `toml:"_int"`
} `toml:"_struct"`
Bool bool `toml:"_bool"`
}{
struct {
Int int `toml:"_int"`
}{1}, true,
},
wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
},
"embedded struct": {
input: struct{ Embedded }{Embedded{1}},
wantOutput: "_int = 1\n",
},
"embedded *struct": {
input: struct{ *Embedded }{&Embedded{1}},
wantOutput: "_int = 1\n",
},
"nested embedded struct": {
input: struct {
Struct struct{ Embedded } `toml:"_struct"`
}{struct{ Embedded }{Embedded{1}}},
wantOutput: "[_struct]\n _int = 1\n",
},
"nested embedded *struct": {
input: struct {
Struct struct{ *Embedded } `toml:"_struct"`
}{struct{ *Embedded }{&Embedded{1}}},
wantOutput: "[_struct]\n _int = 1\n",
},
"array of tables": {
input: struct {
Structs []*struct{ Int int } `toml:"struct"`
}{
[]*struct{ Int int }{{1}, {3}},
},
wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
},
"array of tables order": {
input: map[string]interface{}{
"map": map[string]interface{}{
"zero": 5,
"arr": []map[string]int{
map[string]int{
"friend": 5,
},
},
},
},
wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
},
"(error) top-level slice": {
input: []struct{ Int int }{{1}, {2}, {3}},
wantError: errNoKey,
},
"(error) slice of slice": {
input: struct {
Slices [][]struct{ Int int }
}{
[][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
},
wantError: errArrayNoTable,
},
"(error) map no string key": {
input: map[int]string{1: ""},
wantError: errNonString,
},
"(error) anonymous non-struct": {
input: struct{ NonStruct }{5},
wantError: errAnonNonStruct,
},
"(error) empty key name": {
input: map[string]int{"": 1},
wantError: errAnything,
},
"(error) empty map name": {
input: map[string]interface{}{
"": map[string]int{"v": 1},
},
wantError: errAnything,
},
}
for label, test := range tests {
encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
}
}
func TestEncodeNestedTableArrays(t *testing.T) {
type song struct {
Name string `toml:"name"`
}
type album struct {
Name string `toml:"name"`
Songs []song `toml:"songs"`
}
type springsteen struct {
Albums []album `toml:"albums"`
}
value := springsteen{
[]album{
{"Born to Run",
[]song{{"Jungleland"}, {"Meeting Across the River"}}},
{"Born in the USA",
[]song{{"Glory Days"}, {"Dancing in the Dark"}}},
},
}
expected := `[[albums]]
name = "Born to Run"
[[albums.songs]]
name = "Jungleland"
[[albums.songs]]
name = "Meeting Across the River"
[[albums]]
name = "Born in the USA"
[[albums.songs]]
name = "Glory Days"
[[albums.songs]]
name = "Dancing in the Dark"
`
encodeExpected(t, "nested table arrays", value, expected, nil)
}
func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
type Alpha struct {
V int
}
type Beta struct {
V int
}
type Conf struct {
V int
A Alpha
B []Beta
}
val := Conf{
V: 1,
A: Alpha{2},
B: []Beta{{3}},
}
expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
encodeExpected(t, "array hash with normal hash order", val, expected, nil)
}
func TestEncodeWithOmitEmpty(t *testing.T) {
type simple struct {
User string `toml:"user"`
Pass string `toml:"password,omitempty"`
}
value := simple{"Testing", ""}
expected := fmt.Sprintf("user = %q\n", value.User)
encodeExpected(t, "simple with omitempty, is empty", value, expected, nil)
value.Pass = "some password"
expected = fmt.Sprintf("user = %q\npassword = %q\n", value.User, value.Pass)
encodeExpected(t, "simple with omitempty, not empty", value, expected, nil)
}
func TestEncodeWithOmitZero(t *testing.T) {
type simple struct {
Number int `toml:"number,omitzero"`
Real float64 `toml:"real,omitzero"`
Unsigned uint `toml:"unsigned,omitzero"`
}
value := simple{0, 0.0, uint(0)}
expected := ""
encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
value.Number = 10
value.Real = 20
value.Unsigned = 5
expected = `number = 10
real = 20.0
unsigned = 5
`
encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
}
func encodeExpected(
t *testing.T, label string, val interface{}, wantStr string, wantErr error,
) {
var buf bytes.Buffer
enc := NewEncoder(&buf)
err := enc.Encode(val)
if err != wantErr {
if wantErr != nil {
if wantErr == errAnything && err != nil {
return
}
t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err)
} else {
t.Errorf("%s: Encode failed: %s", label, err)
}
}
if err != nil {
return
}
if got := buf.String(); wantStr != got {
t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n",
label, wantStr, got)
}
}
func ExampleEncoder_Encode() {
date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC")
var config = map[string]interface{}{
"date": date,
"counts": []int{1, 1, 2, 3, 5, 8},
"hash": map[string]string{
"key1": "val1",
"key2": "val2",
},
}
buf := new(bytes.Buffer)
if err := NewEncoder(buf).Encode(config); err != nil {
log.Fatal(err)
}
fmt.Println(buf.String())
// Output:
// counts = [1, 1, 2, 3, 5, 8]
// date = 2010-03-14T18:00:00Z
//
// [hash]
// key1 = "val1"
// key2 = "val2"
}

View File

@@ -0,0 +1,19 @@
// +build go1.2
package toml
// In order to support Go 1.1, we define our own TextMarshaler and
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
// standard library interfaces.
import (
"encoding"
)
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
// so that Go 1.1 can be supported.
type TextMarshaler encoding.TextMarshaler
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
// here so that Go 1.1 can be supported.
type TextUnmarshaler encoding.TextUnmarshaler

View File

@@ -0,0 +1,18 @@
// +build !go1.2
package toml
// These interfaces were introduced in Go 1.2, so we add them manually when
// compiling for Go 1.1.
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
// so that Go 1.1 can be supported.
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
// here so that Go 1.1 can be supported.
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}

874
Godeps/_workspace/src/github.com/BurntSushi/toml/lex.go generated vendored Normal file
View File

@@ -0,0 +1,874 @@
package toml
import (
"fmt"
"strings"
"unicode/utf8"
)
type itemType int
const (
itemError itemType = iota
itemNIL // used in the parser to indicate no type
itemEOF
itemText
itemString
itemRawString
itemMultilineString
itemRawMultilineString
itemBool
itemInteger
itemFloat
itemDatetime
itemArray // the start of an array
itemArrayEnd
itemTableStart
itemTableEnd
itemArrayTableStart
itemArrayTableEnd
itemKeyStart
itemCommentStart
)
const (
eof = 0
tableStart = '['
tableEnd = ']'
arrayTableStart = '['
arrayTableEnd = ']'
tableSep = '.'
keySep = '='
arrayStart = '['
arrayEnd = ']'
arrayValTerm = ','
commentStart = '#'
stringStart = '"'
stringEnd = '"'
rawStringStart = '\''
rawStringEnd = '\''
)
type stateFn func(lx *lexer) stateFn
type lexer struct {
input string
start int
pos int
width int
line int
state stateFn
items chan item
// A stack of state functions used to maintain context.
// The idea is to reuse parts of the state machine in various places.
// For example, values can appear at the top level or within arbitrarily
// nested arrays. The last state on the stack is used after a value has
// been lexed. Similarly for comments.
stack []stateFn
}
type item struct {
typ itemType
val string
line int
}
func (lx *lexer) nextItem() item {
for {
select {
case item := <-lx.items:
return item
default:
lx.state = lx.state(lx)
}
}
}
func lex(input string) *lexer {
lx := &lexer{
input: input + "\n",
state: lexTop,
line: 1,
items: make(chan item, 10),
stack: make([]stateFn, 0, 10),
}
return lx
}
func (lx *lexer) push(state stateFn) {
lx.stack = append(lx.stack, state)
}
func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 {
return lx.errorf("BUG in lexer: no states to pop.")
}
last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1]
return last
}
func (lx *lexer) current() string {
return lx.input[lx.start:lx.pos]
}
func (lx *lexer) emit(typ itemType) {
lx.items <- item{typ, lx.current(), lx.line}
lx.start = lx.pos
}
func (lx *lexer) emitTrim(typ itemType) {
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
lx.start = lx.pos
}
func (lx *lexer) next() (r rune) {
if lx.pos >= len(lx.input) {
lx.width = 0
return eof
}
if lx.input[lx.pos] == '\n' {
lx.line++
}
r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:])
lx.pos += lx.width
return r
}
// ignore skips over the pending input before this point.
func (lx *lexer) ignore() {
lx.start = lx.pos
}
// backup steps back one rune. Can be called only once per call of next.
func (lx *lexer) backup() {
lx.pos -= lx.width
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
lx.line--
}
}
// accept consumes the next rune if it's equal to `valid`.
func (lx *lexer) accept(valid rune) bool {
if lx.next() == valid {
return true
}
lx.backup()
return false
}
// peek returns but does not consume the next rune in the input.
func (lx *lexer) peek() rune {
r := lx.next()
lx.backup()
return r
}
// errorf stops all lexing by emitting an error and returning `nil`.
// Note that any value that is a character is escaped if it's a special
// character (new lines, tabs, etc.).
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
lx.items <- item{
itemError,
fmt.Sprintf(format, values...),
lx.line,
}
return nil
}
// lexTop consumes elements at the top level of TOML data.
func lexTop(lx *lexer) stateFn {
r := lx.next()
if isWhitespace(r) || isNL(r) {
return lexSkip(lx, lexTop)
}
switch r {
case commentStart:
lx.push(lexTop)
return lexCommentStart
case tableStart:
return lexTableStart
case eof:
if lx.pos > lx.start {
return lx.errorf("Unexpected EOF.")
}
lx.emit(itemEOF)
return nil
}
// At this point, the only valid item can be a key, so we back up
// and let the key lexer do the rest.
lx.backup()
lx.push(lexTopEnd)
return lexKeyStart
}
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
// or a table.) It must see only whitespace, and will turn back to lexTop
// upon a new line. If it sees EOF, it will quit the lexer successfully.
func lexTopEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case r == commentStart:
// a comment will read to a new line for us.
lx.push(lexTop)
return lexCommentStart
case isWhitespace(r):
return lexTopEnd
case isNL(r):
lx.ignore()
return lexTop
case r == eof:
lx.ignore()
return lexTop
}
return lx.errorf("Expected a top-level item to end with a new line, "+
"comment or EOF, but got %q instead.", r)
}
// lexTable lexes the beginning of a table. Namely, it makes sure that
// it starts with a character other than '.' and ']'.
// It assumes that '[' has already been consumed.
// It also handles the case that this is an item in an array of tables.
// e.g., '[[name]]'.
func lexTableStart(lx *lexer) stateFn {
if lx.peek() == arrayTableStart {
lx.next()
lx.emit(itemArrayTableStart)
lx.push(lexArrayTableEnd)
} else {
lx.emit(itemTableStart)
lx.push(lexTableEnd)
}
return lexTableNameStart
}
func lexTableEnd(lx *lexer) stateFn {
lx.emit(itemTableEnd)
return lexTopEnd
}
func lexArrayTableEnd(lx *lexer) stateFn {
if r := lx.next(); r != arrayTableEnd {
return lx.errorf("Expected end of table array name delimiter %q, "+
"but got %q instead.", arrayTableEnd, r)
}
lx.emit(itemArrayTableEnd)
return lexTopEnd
}
func lexTableNameStart(lx *lexer) stateFn {
switch r := lx.peek(); {
case r == tableEnd || r == eof:
return lx.errorf("Unexpected end of table name. (Table names cannot " +
"be empty.)")
case r == tableSep:
return lx.errorf("Unexpected table separator. (Table names cannot " +
"be empty.)")
case r == stringStart || r == rawStringStart:
lx.ignore()
lx.push(lexTableNameEnd)
return lexValue // reuse string lexing
case isWhitespace(r):
return lexTableNameStart
default:
return lexBareTableName
}
}
// lexTableName lexes the name of a table. It assumes that at least one
// valid character for the table has already been read.
func lexBareTableName(lx *lexer) stateFn {
switch r := lx.next(); {
case isBareKeyChar(r):
return lexBareTableName
case r == tableSep || r == tableEnd:
lx.backup()
lx.emitTrim(itemText)
return lexTableNameEnd
default:
return lx.errorf("Bare keys cannot contain %q.", r)
}
}
// lexTableNameEnd reads the end of a piece of a table name, optionally
// consuming whitespace.
func lexTableNameEnd(lx *lexer) stateFn {
switch r := lx.next(); {
case isWhitespace(r):
return lexTableNameEnd
case r == tableSep:
lx.ignore()
return lexTableNameStart
case r == tableEnd:
return lx.pop()
default:
return lx.errorf("Expected '.' or ']' to end table name, but got %q "+
"instead.", r)
}
}
// lexKeyStart consumes a key name up until the first non-whitespace character.
// lexKeyStart will ignore whitespace.
func lexKeyStart(lx *lexer) stateFn {
r := lx.peek()
switch {
case r == keySep:
return lx.errorf("Unexpected key separator %q.", keySep)
case isWhitespace(r) || isNL(r):
lx.next()
return lexSkip(lx, lexKeyStart)
case r == stringStart || r == rawStringStart:
lx.ignore()
lx.emit(itemKeyStart)
lx.push(lexKeyEnd)
return lexValue // reuse string lexing
default:
lx.ignore()
lx.emit(itemKeyStart)
return lexBareKey
}
}
// lexBareKey consumes the text of a bare key. Assumes that the first character
// (which is not whitespace) has not yet been consumed.
func lexBareKey(lx *lexer) stateFn {
switch r := lx.next(); {
case isBareKeyChar(r):
return lexBareKey
case isWhitespace(r):
lx.emitTrim(itemText)
return lexKeyEnd
case r == keySep:
lx.backup()
lx.emitTrim(itemText)
return lexKeyEnd
default:
return lx.errorf("Bare keys cannot contain %q.", r)
}
}
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
// separator).
func lexKeyEnd(lx *lexer) stateFn {
switch r := lx.next(); {
case r == keySep:
return lexSkip(lx, lexValue)
case isWhitespace(r):
return lexSkip(lx, lexKeyEnd)
default:
return lx.errorf("Expected key separator %q, but got %q instead.",
keySep, r)
}
}
// lexValue starts the consumption of a value anywhere a value is expected.
// lexValue will ignore whitespace.
// After a value is lexed, the last state on the next is popped and returned.
func lexValue(lx *lexer) stateFn {
// We allow whitespace to precede a value, but NOT new lines.
// In array syntax, the array states are responsible for ignoring new
// lines.
r := lx.next()
if isWhitespace(r) {
return lexSkip(lx, lexValue)
}
switch {
case r == arrayStart:
lx.ignore()
lx.emit(itemArray)
return lexArrayValue
case r == stringStart:
if lx.accept(stringStart) {
if lx.accept(stringStart) {
lx.ignore() // Ignore """
return lexMultilineString
}
lx.backup()
}
lx.ignore() // ignore the '"'
return lexString
case r == rawStringStart:
if lx.accept(rawStringStart) {
if lx.accept(rawStringStart) {
lx.ignore() // Ignore """
return lexMultilineRawString
}
lx.backup()
}
lx.ignore() // ignore the "'"
return lexRawString
case r == 't':
return lexTrue
case r == 'f':
return lexFalse
case r == '-':
return lexNumberStart
case isDigit(r):
lx.backup() // avoid an extra state and use the same as above
return lexNumberOrDateStart
case r == '.': // special error case, be kind to users
return lx.errorf("Floats must start with a digit, not '.'.")
}
return lx.errorf("Expected value but found %q instead.", r)
}
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
// have already been consumed. All whitespace and new lines are ignored.
func lexArrayValue(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r) || isNL(r):
return lexSkip(lx, lexArrayValue)
case r == commentStart:
lx.push(lexArrayValue)
return lexCommentStart
case r == arrayValTerm:
return lx.errorf("Unexpected array value terminator %q.",
arrayValTerm)
case r == arrayEnd:
return lexArrayEnd
}
lx.backup()
lx.push(lexArrayValueEnd)
return lexValue
}
// lexArrayValueEnd consumes the cruft between values of an array. Namely,
// it ignores whitespace and expects either a ',' or a ']'.
func lexArrayValueEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r) || isNL(r):
return lexSkip(lx, lexArrayValueEnd)
case r == commentStart:
lx.push(lexArrayValueEnd)
return lexCommentStart
case r == arrayValTerm:
lx.ignore()
return lexArrayValue // move on to the next value
case r == arrayEnd:
return lexArrayEnd
}
return lx.errorf("Expected an array value terminator %q or an array "+
"terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r)
}
// lexArrayEnd finishes the lexing of an array. It assumes that a ']' has
// just been consumed.
func lexArrayEnd(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemArrayEnd)
return lx.pop()
}
// lexString consumes the inner contents of a string. It assumes that the
// beginning '"' has already been consumed and ignored.
func lexString(lx *lexer) stateFn {
r := lx.next()
switch {
case isNL(r):
return lx.errorf("Strings cannot contain new lines.")
case r == '\\':
lx.push(lexString)
return lexStringEscape
case r == stringEnd:
lx.backup()
lx.emit(itemString)
lx.next()
lx.ignore()
return lx.pop()
}
return lexString
}
// lexMultilineString consumes the inner contents of a string. It assumes that
// the beginning '"""' has already been consumed and ignored.
func lexMultilineString(lx *lexer) stateFn {
r := lx.next()
switch {
case r == '\\':
return lexMultilineStringEscape
case r == stringEnd:
if lx.accept(stringEnd) {
if lx.accept(stringEnd) {
lx.backup()
lx.backup()
lx.backup()
lx.emit(itemMultilineString)
lx.next()
lx.next()
lx.next()
lx.ignore()
return lx.pop()
}
lx.backup()
}
}
return lexMultilineString
}
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
// It assumes that the beginning "'" has already been consumed and ignored.
func lexRawString(lx *lexer) stateFn {
r := lx.next()
switch {
case isNL(r):
return lx.errorf("Strings cannot contain new lines.")
case r == rawStringEnd:
lx.backup()
lx.emit(itemRawString)
lx.next()
lx.ignore()
return lx.pop()
}
return lexRawString
}
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
// a string. It assumes that the beginning "'" has already been consumed and
// ignored.
func lexMultilineRawString(lx *lexer) stateFn {
r := lx.next()
switch {
case r == rawStringEnd:
if lx.accept(rawStringEnd) {
if lx.accept(rawStringEnd) {
lx.backup()
lx.backup()
lx.backup()
lx.emit(itemRawMultilineString)
lx.next()
lx.next()
lx.next()
lx.ignore()
return lx.pop()
}
lx.backup()
}
}
return lexMultilineRawString
}
// lexMultilineStringEscape consumes an escaped character. It assumes that the
// preceding '\\' has already been consumed.
func lexMultilineStringEscape(lx *lexer) stateFn {
// Handle the special case first:
if isNL(lx.next()) {
lx.next()
return lexMultilineString
} else {
lx.backup()
lx.push(lexMultilineString)
return lexStringEscape(lx)
}
}
func lexStringEscape(lx *lexer) stateFn {
r := lx.next()
switch r {
case 'b':
fallthrough
case 't':
fallthrough
case 'n':
fallthrough
case 'f':
fallthrough
case 'r':
fallthrough
case '"':
fallthrough
case '\\':
return lx.pop()
case 'u':
return lexShortUnicodeEscape
case 'U':
return lexLongUnicodeEscape
}
return lx.errorf("Invalid escape character %q. Only the following "+
"escape characters are allowed: "+
"\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+
"\\uXXXX and \\UXXXXXXXX.", r)
}
func lexShortUnicodeEscape(lx *lexer) stateFn {
var r rune
for i := 0; i < 4; i++ {
r = lx.next()
if !isHexadecimal(r) {
return lx.errorf("Expected four hexadecimal digits after '\\u', "+
"but got '%s' instead.", lx.current())
}
}
return lx.pop()
}
func lexLongUnicodeEscape(lx *lexer) stateFn {
var r rune
for i := 0; i < 8; i++ {
r = lx.next()
if !isHexadecimal(r) {
return lx.errorf("Expected eight hexadecimal digits after '\\U', "+
"but got '%s' instead.", lx.current())
}
}
return lx.pop()
}
// lexNumberOrDateStart consumes either a (positive) integer, float or
// datetime. It assumes that NO negative sign has been consumed.
func lexNumberOrDateStart(lx *lexer) stateFn {
r := lx.next()
if !isDigit(r) {
if r == '.' {
return lx.errorf("Floats must start with a digit, not '.'.")
} else {
return lx.errorf("Expected a digit but got %q.", r)
}
}
return lexNumberOrDate
}
// lexNumberOrDate consumes either a (positive) integer, float or datetime.
func lexNumberOrDate(lx *lexer) stateFn {
r := lx.next()
switch {
case r == '-':
if lx.pos-lx.start != 5 {
return lx.errorf("All ISO8601 dates must be in full Zulu form.")
}
return lexDateAfterYear
case isDigit(r):
return lexNumberOrDate
case r == '.':
return lexFloatStart
}
lx.backup()
lx.emit(itemInteger)
return lx.pop()
}
// lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format.
// It assumes that "YYYY-" has already been consumed.
func lexDateAfterYear(lx *lexer) stateFn {
formats := []rune{
// digits are '0'.
// everything else is direct equality.
'0', '0', '-', '0', '0',
'T',
'0', '0', ':', '0', '0', ':', '0', '0',
'Z',
}
for _, f := range formats {
r := lx.next()
if f == '0' {
if !isDigit(r) {
return lx.errorf("Expected digit in ISO8601 datetime, "+
"but found %q instead.", r)
}
} else if f != r {
return lx.errorf("Expected %q in ISO8601 datetime, "+
"but found %q instead.", f, r)
}
}
lx.emit(itemDatetime)
return lx.pop()
}
// lexNumberStart consumes either an integer or a float. It assumes that
// a negative sign has already been read, but that *no* digits have been
// consumed. lexNumberStart will move to the appropriate integer or float
// states.
func lexNumberStart(lx *lexer) stateFn {
// we MUST see a digit. Even floats have to start with a digit.
r := lx.next()
if !isDigit(r) {
if r == '.' {
return lx.errorf("Floats must start with a digit, not '.'.")
} else {
return lx.errorf("Expected a digit but got %q.", r)
}
}
return lexNumber
}
// lexNumber consumes an integer or a float after seeing the first digit.
func lexNumber(lx *lexer) stateFn {
r := lx.next()
switch {
case isDigit(r):
return lexNumber
case r == '.':
return lexFloatStart
}
lx.backup()
lx.emit(itemInteger)
return lx.pop()
}
// lexFloatStart starts the consumption of digits of a float after a '.'.
// Namely, at least one digit is required.
func lexFloatStart(lx *lexer) stateFn {
r := lx.next()
if !isDigit(r) {
return lx.errorf("Floats must have a digit after the '.', but got "+
"%q instead.", r)
}
return lexFloat
}
// lexFloat consumes the digits of a float after a '.'.
// Assumes that one digit has been consumed after a '.' already.
func lexFloat(lx *lexer) stateFn {
r := lx.next()
if isDigit(r) {
return lexFloat
}
lx.backup()
lx.emit(itemFloat)
return lx.pop()
}
// lexConst consumes the s[1:] in s. It assumes that s[0] has already been
// consumed.
func lexConst(lx *lexer, s string) stateFn {
for i := range s[1:] {
if r := lx.next(); r != rune(s[i+1]) {
return lx.errorf("Expected %q, but found %q instead.", s[:i+1],
s[:i]+string(r))
}
}
return nil
}
// lexTrue consumes the "rue" in "true". It assumes that 't' has already
// been consumed.
func lexTrue(lx *lexer) stateFn {
if fn := lexConst(lx, "true"); fn != nil {
return fn
}
lx.emit(itemBool)
return lx.pop()
}
// lexFalse consumes the "alse" in "false". It assumes that 'f' has already
// been consumed.
func lexFalse(lx *lexer) stateFn {
if fn := lexConst(lx, "false"); fn != nil {
return fn
}
lx.emit(itemBool)
return lx.pop()
}
// lexCommentStart begins the lexing of a comment. It will emit
// itemCommentStart and consume no characters, passing control to lexComment.
func lexCommentStart(lx *lexer) stateFn {
lx.ignore()
lx.emit(itemCommentStart)
return lexComment
}
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
// It will consume *up to* the first new line character, and pass control
// back to the last state on the stack.
func lexComment(lx *lexer) stateFn {
r := lx.peek()
if isNL(r) || r == eof {
lx.emit(itemText)
return lx.pop()
}
lx.next()
return lexComment
}
// lexSkip ignores all slurped input and moves on to the next state.
func lexSkip(lx *lexer, nextState stateFn) stateFn {
return func(lx *lexer) stateFn {
lx.ignore()
return nextState
}
}
// isWhitespace returns true if `r` is a whitespace character according
// to the spec.
func isWhitespace(r rune) bool {
return r == '\t' || r == ' '
}
func isNL(r rune) bool {
return r == '\n' || r == '\r'
}
func isDigit(r rune) bool {
return r >= '0' && r <= '9'
}
func isHexadecimal(r rune) bool {
return (r >= '0' && r <= '9') ||
(r >= 'a' && r <= 'f') ||
(r >= 'A' && r <= 'F')
}
func isBareKeyChar(r rune) bool {
return (r >= 'A' && r <= 'Z') ||
(r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') ||
r == '_' ||
r == '-'
}
func (itype itemType) String() string {
switch itype {
case itemError:
return "Error"
case itemNIL:
return "NIL"
case itemEOF:
return "EOF"
case itemText:
return "Text"
case itemString:
return "String"
case itemRawString:
return "String"
case itemMultilineString:
return "String"
case itemRawMultilineString:
return "String"
case itemBool:
return "Bool"
case itemInteger:
return "Integer"
case itemFloat:
return "Float"
case itemDatetime:
return "DateTime"
case itemTableStart:
return "TableStart"
case itemTableEnd:
return "TableEnd"
case itemKeyStart:
return "KeyStart"
case itemArray:
return "Array"
case itemArrayEnd:
return "ArrayEnd"
case itemCommentStart:
return "CommentStart"
}
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
}
func (item item) String() string {
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
}

View File

@@ -0,0 +1,498 @@
package toml
import (
"fmt"
"log"
"strconv"
"strings"
"time"
"unicode"
"unicode/utf8"
)
type parser struct {
mapping map[string]interface{}
types map[string]tomlType
lx *lexer
// A list of keys in the order that they appear in the TOML data.
ordered []Key
// the full key for the current hash in scope
context Key
// the base key name for everything except hashes
currentKey string
// rough approximation of line number
approxLine int
// A map of 'key.group.names' to whether they were created implicitly.
implicits map[string]bool
}
type parseError string
func (pe parseError) Error() string {
return string(pe)
}
func parse(data string) (p *parser, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
if err, ok = r.(parseError); ok {
return
}
panic(r)
}
}()
p = &parser{
mapping: make(map[string]interface{}),
types: make(map[string]tomlType),
lx: lex(data),
ordered: make([]Key, 0),
implicits: make(map[string]bool),
}
for {
item := p.next()
if item.typ == itemEOF {
break
}
p.topLevel(item)
}
return p, nil
}
func (p *parser) panicf(format string, v ...interface{}) {
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
p.approxLine, p.current(), fmt.Sprintf(format, v...))
panic(parseError(msg))
}
func (p *parser) next() item {
it := p.lx.nextItem()
if it.typ == itemError {
p.panicf("%s", it.val)
}
return it
}
func (p *parser) bug(format string, v ...interface{}) {
log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...))
}
func (p *parser) expect(typ itemType) item {
it := p.next()
p.assertEqual(typ, it.typ)
return it
}
func (p *parser) assertEqual(expected, got itemType) {
if expected != got {
p.bug("Expected '%s' but got '%s'.", expected, got)
}
}
func (p *parser) topLevel(item item) {
switch item.typ {
case itemCommentStart:
p.approxLine = item.line
p.expect(itemText)
case itemTableStart:
kg := p.next()
p.approxLine = kg.line
var key Key
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
key = append(key, p.keyString(kg))
}
p.assertEqual(itemTableEnd, kg.typ)
p.establishContext(key, false)
p.setType("", tomlHash)
p.ordered = append(p.ordered, key)
case itemArrayTableStart:
kg := p.next()
p.approxLine = kg.line
var key Key
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
key = append(key, p.keyString(kg))
}
p.assertEqual(itemArrayTableEnd, kg.typ)
p.establishContext(key, true)
p.setType("", tomlArrayHash)
p.ordered = append(p.ordered, key)
case itemKeyStart:
kname := p.next()
p.approxLine = kname.line
p.currentKey = p.keyString(kname)
val, typ := p.value(p.next())
p.setValue(p.currentKey, val)
p.setType(p.currentKey, typ)
p.ordered = append(p.ordered, p.context.add(p.currentKey))
p.currentKey = ""
default:
p.bug("Unexpected type at top level: %s", item.typ)
}
}
// Gets a string for a key (or part of a key in a table name).
func (p *parser) keyString(it item) string {
switch it.typ {
case itemText:
return it.val
case itemString, itemMultilineString,
itemRawString, itemRawMultilineString:
s, _ := p.value(it)
return s.(string)
default:
p.bug("Unexpected key type: %s", it.typ)
panic("unreachable")
}
}
// value translates an expected value from the lexer into a Go value wrapped
// as an empty interface.
func (p *parser) value(it item) (interface{}, tomlType) {
switch it.typ {
case itemString:
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
case itemMultilineString:
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
case itemRawString:
return it.val, p.typeOfPrimitive(it)
case itemRawMultilineString:
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
case itemBool:
switch it.val {
case "true":
return true, p.typeOfPrimitive(it)
case "false":
return false, p.typeOfPrimitive(it)
}
p.bug("Expected boolean value, but got '%s'.", it.val)
case itemInteger:
num, err := strconv.ParseInt(it.val, 10, 64)
if err != nil {
// See comment below for floats describing why we make a
// distinction between a bug and a user error.
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
p.panicf("Integer '%s' is out of the range of 64-bit "+
"signed integers.", it.val)
} else {
p.bug("Expected integer value, but got '%s'.", it.val)
}
}
return num, p.typeOfPrimitive(it)
case itemFloat:
num, err := strconv.ParseFloat(it.val, 64)
if err != nil {
// Distinguish float values. Normally, it'd be a bug if the lexer
// provides an invalid float, but it's possible that the float is
// out of range of valid values (which the lexer cannot determine).
// So mark the former as a bug but the latter as a legitimate user
// error.
//
// This is also true for integers.
if e, ok := err.(*strconv.NumError); ok &&
e.Err == strconv.ErrRange {
p.panicf("Float '%s' is out of the range of 64-bit "+
"IEEE-754 floating-point numbers.", it.val)
} else {
p.bug("Expected float value, but got '%s'.", it.val)
}
}
return num, p.typeOfPrimitive(it)
case itemDatetime:
t, err := time.Parse("2006-01-02T15:04:05Z", it.val)
if err != nil {
p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val)
}
return t, p.typeOfPrimitive(it)
case itemArray:
array := make([]interface{}, 0)
types := make([]tomlType, 0)
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
if it.typ == itemCommentStart {
p.expect(itemText)
continue
}
val, typ := p.value(it)
array = append(array, val)
types = append(types, typ)
}
return array, p.typeOfArray(types)
}
p.bug("Unexpected value type: %s", it.typ)
panic("unreachable")
}
// establishContext sets the current context of the parser,
// where the context is either a hash or an array of hashes. Which one is
// set depends on the value of the `array` parameter.
//
// Establishing the context also makes sure that the key isn't a duplicate, and
// will create implicit hashes automatically.
func (p *parser) establishContext(key Key, array bool) {
var ok bool
// Always start at the top level and drill down for our context.
hashContext := p.mapping
keyContext := make(Key, 0)
// We only need implicit hashes for key[0:-1]
for _, k := range key[0 : len(key)-1] {
_, ok = hashContext[k]
keyContext = append(keyContext, k)
// No key? Make an implicit hash and move on.
if !ok {
p.addImplicit(keyContext)
hashContext[k] = make(map[string]interface{})
}
// If the hash context is actually an array of tables, then set
// the hash context to the last element in that array.
//
// Otherwise, it better be a table, since this MUST be a key group (by
// virtue of it not being the last element in a key).
switch t := hashContext[k].(type) {
case []map[string]interface{}:
hashContext = t[len(t)-1]
case map[string]interface{}:
hashContext = t
default:
p.panicf("Key '%s' was already created as a hash.", keyContext)
}
}
p.context = keyContext
if array {
// If this is the first element for this array, then allocate a new
// list of tables for it.
k := key[len(key)-1]
if _, ok := hashContext[k]; !ok {
hashContext[k] = make([]map[string]interface{}, 0, 5)
}
// Add a new table. But make sure the key hasn't already been used
// for something else.
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
hashContext[k] = append(hash, make(map[string]interface{}))
} else {
p.panicf("Key '%s' was already created and cannot be used as "+
"an array.", keyContext)
}
} else {
p.setValue(key[len(key)-1], make(map[string]interface{}))
}
p.context = append(p.context, key[len(key)-1])
}
// setValue sets the given key to the given value in the current context.
// It will make sure that the key hasn't already been defined, account for
// implicit key groups.
func (p *parser) setValue(key string, value interface{}) {
var tmpHash interface{}
var ok bool
hash := p.mapping
keyContext := make(Key, 0)
for _, k := range p.context {
keyContext = append(keyContext, k)
if tmpHash, ok = hash[k]; !ok {
p.bug("Context for key '%s' has not been established.", keyContext)
}
switch t := tmpHash.(type) {
case []map[string]interface{}:
// The context is a table of hashes. Pick the most recent table
// defined as the current hash.
hash = t[len(t)-1]
case map[string]interface{}:
hash = t
default:
p.bug("Expected hash to have type 'map[string]interface{}', but "+
"it has '%T' instead.", tmpHash)
}
}
keyContext = append(keyContext, key)
if _, ok := hash[key]; ok {
// Typically, if the given key has already been set, then we have
// to raise an error since duplicate keys are disallowed. However,
// it's possible that a key was previously defined implicitly. In this
// case, it is allowed to be redefined concretely. (See the
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
//
// But we have to make sure to stop marking it as an implicit. (So that
// another redefinition provokes an error.)
//
// Note that since it has already been defined (as a hash), we don't
// want to overwrite it. So our business is done.
if p.isImplicit(keyContext) {
p.removeImplicit(keyContext)
return
}
// Otherwise, we have a concrete key trying to override a previous
// key, which is *always* wrong.
p.panicf("Key '%s' has already been defined.", keyContext)
}
hash[key] = value
}
// setType sets the type of a particular value at a given key.
// It should be called immediately AFTER setValue.
//
// Note that if `key` is empty, then the type given will be applied to the
// current context (which is either a table or an array of tables).
func (p *parser) setType(key string, typ tomlType) {
keyContext := make(Key, 0, len(p.context)+1)
for _, k := range p.context {
keyContext = append(keyContext, k)
}
if len(key) > 0 { // allow type setting for hashes
keyContext = append(keyContext, key)
}
p.types[keyContext.String()] = typ
}
// addImplicit sets the given Key as having been created implicitly.
func (p *parser) addImplicit(key Key) {
p.implicits[key.String()] = true
}
// removeImplicit stops tagging the given key as having been implicitly
// created.
func (p *parser) removeImplicit(key Key) {
p.implicits[key.String()] = false
}
// isImplicit returns true if the key group pointed to by the key was created
// implicitly.
func (p *parser) isImplicit(key Key) bool {
return p.implicits[key.String()]
}
// current returns the full key name of the current context.
func (p *parser) current() string {
if len(p.currentKey) == 0 {
return p.context.String()
}
if len(p.context) == 0 {
return p.currentKey
}
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
}
func stripFirstNewline(s string) string {
if len(s) == 0 || s[0] != '\n' {
return s
}
return s[1:len(s)]
}
func stripEscapedWhitespace(s string) string {
esc := strings.Split(s, "\\\n")
if len(esc) > 1 {
for i := 1; i < len(esc); i++ {
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
}
}
return strings.Join(esc, "")
}
func (p *parser) replaceEscapes(str string) string {
var replaced []rune
s := []byte(str)
r := 0
for r < len(s) {
if s[r] != '\\' {
c, size := utf8.DecodeRune(s[r:])
r += size
replaced = append(replaced, c)
continue
}
r += 1
if r >= len(s) {
p.bug("Escape sequence at end of string.")
return ""
}
switch s[r] {
default:
p.bug("Expected valid escape code after \\, but got %q.", s[r])
return ""
case 'b':
replaced = append(replaced, rune(0x0008))
r += 1
case 't':
replaced = append(replaced, rune(0x0009))
r += 1
case 'n':
replaced = append(replaced, rune(0x000A))
r += 1
case 'f':
replaced = append(replaced, rune(0x000C))
r += 1
case 'r':
replaced = append(replaced, rune(0x000D))
r += 1
case '"':
replaced = append(replaced, rune(0x0022))
r += 1
case '\\':
replaced = append(replaced, rune(0x005C))
r += 1
case 'u':
// At this point, we know we have a Unicode escape of the form
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
// for us.)
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
replaced = append(replaced, escaped)
r += 5
case 'U':
// At this point, we know we have a Unicode escape of the form
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
// for us.)
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
replaced = append(replaced, escaped)
r += 9
}
}
return string(replaced)
}
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
s := string(bs)
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
if err != nil {
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
"lexer claims it's OK: %s", s, err)
}
// BUG(burntsushi)
// I honestly don't understand how this works. I can't seem
// to find a way to make this fail. I figured this would fail on invalid
// UTF-8 characters like U+DCFF, but it doesn't.
if !utf8.ValidString(string(rune(hex))) {
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
}
return rune(hex)
}
func isStringType(ty itemType) bool {
return ty == itemString || ty == itemMultilineString ||
ty == itemRawString || ty == itemRawMultilineString
}

View File

@@ -0,0 +1 @@
au BufWritePost *.go silent!make tags > /dev/null 2>&1

View File

@@ -0,0 +1,91 @@
package toml
// tomlType represents any Go type that corresponds to a TOML type.
// While the first draft of the TOML spec has a simplistic type system that
// probably doesn't need this level of sophistication, we seem to be militating
// toward adding real composite types.
type tomlType interface {
typeString() string
}
// typeEqual accepts any two types and returns true if they are equal.
func typeEqual(t1, t2 tomlType) bool {
if t1 == nil || t2 == nil {
return false
}
return t1.typeString() == t2.typeString()
}
func typeIsHash(t tomlType) bool {
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
}
type tomlBaseType string
func (btype tomlBaseType) typeString() string {
return string(btype)
}
func (btype tomlBaseType) String() string {
return btype.typeString()
}
var (
tomlInteger tomlBaseType = "Integer"
tomlFloat tomlBaseType = "Float"
tomlDatetime tomlBaseType = "Datetime"
tomlString tomlBaseType = "String"
tomlBool tomlBaseType = "Bool"
tomlArray tomlBaseType = "Array"
tomlHash tomlBaseType = "Hash"
tomlArrayHash tomlBaseType = "ArrayHash"
)
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
// Primitive values are: Integer, Float, Datetime, String and Bool.
//
// Passing a lexer item other than the following will cause a BUG message
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
switch lexItem.typ {
case itemInteger:
return tomlInteger
case itemFloat:
return tomlFloat
case itemDatetime:
return tomlDatetime
case itemString:
return tomlString
case itemMultilineString:
return tomlString
case itemRawString:
return tomlString
case itemRawMultilineString:
return tomlString
case itemBool:
return tomlBool
}
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
panic("unreachable")
}
// typeOfArray returns a tomlType for an array given a list of types of its
// values.
//
// In the current spec, if an array is homogeneous, then its type is always
// "Array". If the array is not homogeneous, an error is generated.
func (p *parser) typeOfArray(types []tomlType) tomlType {
// Empty arrays are cool.
if len(types) == 0 {
return tomlArray
}
theType := types[0]
for _, t := range types[1:] {
if !typeEqual(theType, t) {
p.panicf("Array contains values of type '%s' and '%s', but "+
"arrays must be homogeneous.", theType, t)
}
}
return tomlArray
}

View File

@@ -0,0 +1,241 @@
package toml
// Struct field handling is adapted from code in encoding/json:
//
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the Go distribution.
import (
"reflect"
"sort"
"sync"
)
// A field represents a single field found in a struct.
type field struct {
name string // the name of the field (`toml` tag included)
tag bool // whether field has a `toml` tag
index []int // represents the depth of an anonymous field
typ reflect.Type // the type of the field
}
// byName sorts field by name, breaking ties with depth,
// then breaking ties with "name came from toml tag", then
// breaking ties with index sequence.
type byName []field
func (x byName) Len() int { return len(x) }
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byName) Less(i, j int) bool {
if x[i].name != x[j].name {
return x[i].name < x[j].name
}
if len(x[i].index) != len(x[j].index) {
return len(x[i].index) < len(x[j].index)
}
if x[i].tag != x[j].tag {
return x[i].tag
}
return byIndex(x).Less(i, j)
}
// byIndex sorts field by index sequence.
type byIndex []field
func (x byIndex) Len() int { return len(x) }
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
func (x byIndex) Less(i, j int) bool {
for k, xik := range x[i].index {
if k >= len(x[j].index) {
return false
}
if xik != x[j].index[k] {
return xik < x[j].index[k]
}
}
return len(x[i].index) < len(x[j].index)
}
// typeFields returns a list of fields that TOML should recognize for the given
// type. The algorithm is breadth-first search over the set of structs to
// include - the top struct and then any reachable anonymous structs.
func typeFields(t reflect.Type) []field {
// Anonymous fields to explore at the current level and the next.
current := []field{}
next := []field{{typ: t}}
// Count of queued names for current level and the next.
count := map[reflect.Type]int{}
nextCount := map[reflect.Type]int{}
// Types already visited at an earlier level.
visited := map[reflect.Type]bool{}
// Fields found.
var fields []field
for len(next) > 0 {
current, next = next, current[:0]
count, nextCount = nextCount, map[reflect.Type]int{}
for _, f := range current {
if visited[f.typ] {
continue
}
visited[f.typ] = true
// Scan f.typ for fields to include.
for i := 0; i < f.typ.NumField(); i++ {
sf := f.typ.Field(i)
if sf.PkgPath != "" { // unexported
continue
}
name := sf.Tag.Get("toml")
if name == "-" {
continue
}
index := make([]int, len(f.index)+1)
copy(index, f.index)
index[len(f.index)] = i
ft := sf.Type
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
// Follow pointer.
ft = ft.Elem()
}
// Record found field and index sequence.
if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
tagged := name != ""
if name == "" {
name = sf.Name
}
fields = append(fields, field{name, tagged, index, ft})
if count[f.typ] > 1 {
// If there were multiple instances, add a second,
// so that the annihilation code will see a duplicate.
// It only cares about the distinction between 1 or 2,
// so don't bother generating any more copies.
fields = append(fields, fields[len(fields)-1])
}
continue
}
// Record new anonymous struct to explore in next round.
nextCount[ft]++
if nextCount[ft] == 1 {
f := field{name: ft.Name(), index: index, typ: ft}
next = append(next, f)
}
}
}
}
sort.Sort(byName(fields))
// Delete all fields that are hidden by the Go rules for embedded fields,
// except that fields with TOML tags are promoted.
// The fields are sorted in primary order of name, secondary order
// of field index length. Loop over names; for each name, delete
// hidden fields by choosing the one dominant field that survives.
out := fields[:0]
for advance, i := 0, 0; i < len(fields); i += advance {
// One iteration per name.
// Find the sequence of fields with the name of this first field.
fi := fields[i]
name := fi.name
for advance = 1; i+advance < len(fields); advance++ {
fj := fields[i+advance]
if fj.name != name {
break
}
}
if advance == 1 { // Only one field with this name
out = append(out, fi)
continue
}
dominant, ok := dominantField(fields[i : i+advance])
if ok {
out = append(out, dominant)
}
}
fields = out
sort.Sort(byIndex(fields))
return fields
}
// dominantField looks through the fields, all of which are known to
// have the same name, to find the single field that dominates the
// others using Go's embedding rules, modified by the presence of
// TOML tags. If there are multiple top-level fields, the boolean
// will be false: This condition is an error in Go and we skip all
// the fields.
func dominantField(fields []field) (field, bool) {
// The fields are sorted in increasing index-length order. The winner
// must therefore be one with the shortest index length. Drop all
// longer entries, which is easy: just truncate the slice.
length := len(fields[0].index)
tagged := -1 // Index of first tagged field.
for i, f := range fields {
if len(f.index) > length {
fields = fields[:i]
break
}
if f.tag {
if tagged >= 0 {
// Multiple tagged fields at the same level: conflict.
// Return no field.
return field{}, false
}
tagged = i
}
}
if tagged >= 0 {
return fields[tagged], true
}
// All remaining fields have the same length. If there's more than one,
// we have a conflict (two fields named "X" at the same level) and we
// return no field.
if len(fields) > 1 {
return field{}, false
}
return fields[0], true
}
var fieldCache struct {
sync.RWMutex
m map[reflect.Type][]field
}
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
func cachedTypeFields(t reflect.Type) []field {
fieldCache.RLock()
f := fieldCache.m[t]
fieldCache.RUnlock()
if f != nil {
return f
}
// Compute fields without lock.
// Might duplicate effort but won't hold other computations back.
f = typeFields(t)
if f == nil {
f = []field{}
}
fieldCache.Lock()
if fieldCache.m == nil {
fieldCache.m = map[reflect.Type][]field{}
}
fieldCache.m[t] = f
fieldCache.Unlock()
return f
}

View File

@@ -0,0 +1,105 @@
// Package awserr represents API error interface accessors for the SDK.
package awserr
// An Error wraps lower level errors with code, message and an original error.
// The underlying concrete error type may also satisfy other interfaces which
// can be to used to obtain more specific information about the error.
//
// Calling Error() or String() will always include the full information about
// an error based on its underlying type.
//
// Example:
//
// output, err := s3manage.Upload(svc, input, opts)
// if err != nil {
// if awsErr, ok := err.(awserr.Error); ok {
// // Get error details
// log.Println("Error:", err.Code(), err.Message())
//
// // Prints out full error message, including original error if there was one.
// log.Println("Error:", err.Error())
//
// // Get original error
// if origErr := err.Err(); origErr != nil {
// // operate on original error.
// }
// } else {
// fmt.Println(err.Error())
// }
// }
//
type Error interface {
// Satisfy the generic error interface.
error
// Returns the short phrase depicting the classification of the error.
Code() string
// Returns the error details message.
Message() string
// Returns the original error if one was set. Nil is returned if not set.
OrigErr() error
}
// New returns an Error object described by the code, message, and origErr.
//
// If origErr satisfies the Error interface it will not be wrapped within a new
// Error object and will instead be returned.
func New(code, message string, origErr error) Error {
if e, ok := origErr.(Error); ok && e != nil {
return e
}
return newBaseError(code, message, origErr)
}
// A RequestFailure is an interface to extract request failure information from
// an Error such as the request ID of the failed request returned by a service.
// RequestFailures may not always have a requestID value if the request failed
// prior to reaching the service such as a connection error.
//
// Example:
//
// output, err := s3manage.Upload(svc, input, opts)
// if err != nil {
// if reqerr, ok := err.(RequestFailure); ok {
// log.Printf("Request failed", reqerr.Code(), reqerr.Message(), reqerr.RequestID())
// } else {
// log.Printf("Error:", err.Error()
// }
// }
//
// Combined with awserr.Error:
//
// output, err := s3manage.Upload(svc, input, opts)
// if err != nil {
// if awsErr, ok := err.(awserr.Error); ok {
// // Generic AWS Error with Code, Message, and original error (if any)
// fmt.Println(awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
//
// if reqErr, ok := err.(awserr.RequestFailure); ok {
// // A service error occurred
// fmt.Println(reqErr.StatusCode(), reqErr.RequestID())
// }
// } else {
// fmt.Println(err.Error())
// }
// }
//
type RequestFailure interface {
Error
// The status code of the HTTP response.
StatusCode() int
// The request ID returned by the service for a request failure. This will
// be empty if no request ID is available such as the request failed due
// to a connection error.
RequestID() string
}
// NewRequestFailure returns a new request error wrapper for the given Error
// provided.
func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure {
return newRequestError(err, statusCode, reqID)
}

View File

@@ -0,0 +1,135 @@
package awserr
import "fmt"
// SprintError returns a string of the formatted error code.
//
// Both extra and origErr are optional. If they are included their lines
// will be added, but if they are not included their lines will be ignored.
func SprintError(code, message, extra string, origErr error) string {
msg := fmt.Sprintf("%s: %s", code, message)
if extra != "" {
msg = fmt.Sprintf("%s\n\t%s", msg, extra)
}
if origErr != nil {
msg = fmt.Sprintf("%s\ncaused by: %s", msg, origErr.Error())
}
return msg
}
// A baseError wraps the code and message which defines an error. It also
// can be used to wrap an original error object.
//
// Should be used as the root for errors satisfying the awserr.Error. Also
// for any error which does not fit into a specific error wrapper type.
type baseError struct {
// Classification of error
code string
// Detailed information about error
message string
// Optional original error this error is based off of. Allows building
// chained errors.
origErr error
}
// newBaseError returns an error object for the code, message, and err.
//
// code is a short no whitespace phrase depicting the classification of
// the error that is being created.
//
// message is the free flow string containing detailed information about the error.
//
// origErr is the error object which will be nested under the new error to be returned.
func newBaseError(code, message string, origErr error) *baseError {
return &baseError{
code: code,
message: message,
origErr: origErr,
}
}
// Error returns the string representation of the error.
//
// See ErrorWithExtra for formatting.
//
// Satisfies the error interface.
func (b baseError) Error() string {
return SprintError(b.code, b.message, "", b.origErr)
}
// String returns the string representation of the error.
// Alias for Error to satisfy the stringer interface.
func (b baseError) String() string {
return b.Error()
}
// Code returns the short phrase depicting the classification of the error.
func (b baseError) Code() string {
return b.code
}
// Message returns the error details message.
func (b baseError) Message() string {
return b.message
}
// OrigErr returns the original error if one was set. Nil is returned if no error
// was set.
func (b baseError) OrigErr() error {
return b.origErr
}
// So that the Error interface type can be included as an anonymous field
// in the requestError struct and not conflict with the error.Error() method.
type awsError Error
// A requestError wraps a request or service error.
//
// Composed of baseError for code, message, and original error.
type requestError struct {
awsError
statusCode int
requestID string
}
// newRequestError returns a wrapped error with additional information for request
// status code, and service requestID.
//
// Should be used to wrap all request which involve service requests. Even if
// the request failed without a service response, but had an HTTP status code
// that may be meaningful.
//
// Also wraps original errors via the baseError.
func newRequestError(err Error, statusCode int, requestID string) *requestError {
return &requestError{
awsError: err,
statusCode: statusCode,
requestID: requestID,
}
}
// Error returns the string representation of the error.
// Satisfies the error interface.
func (r requestError) Error() string {
extra := fmt.Sprintf("status code: %d, request id: %s",
r.statusCode, r.requestID)
return SprintError(r.Code(), r.Message(), extra, r.OrigErr())
}
// String returns the string representation of the error.
// Alias for Error to satisfy the stringer interface.
func (r requestError) String() string {
return r.Error()
}
// StatusCode returns the wrapped status code for the error
func (r requestError) StatusCode() int {
return r.statusCode
}
// RequestID returns the wrapped requestID
func (r requestError) RequestID() string {
return r.requestID
}

View File

@@ -0,0 +1,100 @@
package awsutil
import (
"io"
"reflect"
)
// Copy deeply copies a src structure to dst. Useful for copying request and
// response structures.
//
// Can copy between structs of different type, but will only copy fields which
// are assignable, and exist in both structs. Fields which are not assignable,
// or do not exist in both structs are ignored.
func Copy(dst, src interface{}) {
dstval := reflect.ValueOf(dst)
if !dstval.IsValid() {
panic("Copy dst cannot be nil")
}
rcopy(dstval, reflect.ValueOf(src), true)
}
// CopyOf returns a copy of src while also allocating the memory for dst.
// src must be a pointer type or this operation will fail.
func CopyOf(src interface{}) (dst interface{}) {
dsti := reflect.New(reflect.TypeOf(src).Elem())
dst = dsti.Interface()
rcopy(dsti, reflect.ValueOf(src), true)
return
}
// rcopy performs a recursive copy of values from the source to destination.
//
// root is used to skip certain aspects of the copy which are not valid
// for the root node of a object.
func rcopy(dst, src reflect.Value, root bool) {
if !src.IsValid() {
return
}
switch src.Kind() {
case reflect.Ptr:
if _, ok := src.Interface().(io.Reader); ok {
if dst.Kind() == reflect.Ptr && dst.Elem().CanSet() {
dst.Elem().Set(src)
} else if dst.CanSet() {
dst.Set(src)
}
} else {
e := src.Type().Elem()
if dst.CanSet() && !src.IsNil() {
dst.Set(reflect.New(e))
}
if src.Elem().IsValid() {
// Keep the current root state since the depth hasn't changed
rcopy(dst.Elem(), src.Elem(), root)
}
}
case reflect.Struct:
t := dst.Type()
for i := 0; i < t.NumField(); i++ {
name := t.Field(i).Name
srcVal := src.FieldByName(name)
dstVal := dst.FieldByName(name)
if srcVal.IsValid() && dstVal.CanSet() {
rcopy(dstVal, srcVal, false)
}
}
case reflect.Slice:
if src.IsNil() {
break
}
s := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
dst.Set(s)
for i := 0; i < src.Len(); i++ {
rcopy(dst.Index(i), src.Index(i), false)
}
case reflect.Map:
if src.IsNil() {
break
}
s := reflect.MakeMap(src.Type())
dst.Set(s)
for _, k := range src.MapKeys() {
v := src.MapIndex(k)
v2 := reflect.New(v.Type()).Elem()
rcopy(v2, v, false)
dst.SetMapIndex(k, v2)
}
default:
// Assign the value if possible. If its not assignable, the value would
// need to be converted and the impact of that may be unexpected, or is
// not compatible with the dst type.
if src.Type().AssignableTo(dst.Type()) {
dst.Set(src)
}
}
}

View File

@@ -0,0 +1,233 @@
package awsutil_test
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"testing"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/stretchr/testify/assert"
)
func ExampleCopy() {
type Foo struct {
A int
B []*string
}
// Create the initial value
str1 := "hello"
str2 := "bye bye"
f1 := &Foo{A: 1, B: []*string{&str1, &str2}}
// Do the copy
var f2 Foo
awsutil.Copy(&f2, f1)
// Print the result
fmt.Println(awsutil.Prettify(f2))
// Output:
// {
// A: 1,
// B: ["hello","bye bye"]
// }
}
func TestCopy(t *testing.T) {
type Foo struct {
A int
B []*string
C map[string]*int
}
// Create the initial value
str1 := "hello"
str2 := "bye bye"
int1 := 1
int2 := 2
f1 := &Foo{
A: 1,
B: []*string{&str1, &str2},
C: map[string]*int{
"A": &int1,
"B": &int2,
},
}
// Do the copy
var f2 Foo
awsutil.Copy(&f2, f1)
// Values are equal
assert.Equal(t, f2.A, f1.A)
assert.Equal(t, f2.B, f1.B)
assert.Equal(t, f2.C, f1.C)
// But pointers are not!
str3 := "nothello"
int3 := 57
f2.A = 100
f2.B[0] = &str3
f2.C["B"] = &int3
assert.NotEqual(t, f2.A, f1.A)
assert.NotEqual(t, f2.B, f1.B)
assert.NotEqual(t, f2.C, f1.C)
}
func TestCopyNestedWithUnexported(t *testing.T) {
type Bar struct {
a int
B int
}
type Foo struct {
A string
B Bar
}
f1 := &Foo{A: "string", B: Bar{a: 1, B: 2}}
var f2 Foo
awsutil.Copy(&f2, f1)
// Values match
assert.Equal(t, f2.A, f1.A)
assert.NotEqual(t, f2.B, f1.B)
assert.NotEqual(t, f2.B.a, f1.B.a)
assert.Equal(t, f2.B.B, f2.B.B)
}
func TestCopyIgnoreNilMembers(t *testing.T) {
type Foo struct {
A *string
B []string
C map[string]string
}
f := &Foo{}
assert.Nil(t, f.A)
assert.Nil(t, f.B)
assert.Nil(t, f.C)
var f2 Foo
awsutil.Copy(&f2, f)
assert.Nil(t, f2.A)
assert.Nil(t, f2.B)
assert.Nil(t, f2.C)
fcopy := awsutil.CopyOf(f)
f3 := fcopy.(*Foo)
assert.Nil(t, f3.A)
assert.Nil(t, f3.B)
assert.Nil(t, f3.C)
}
func TestCopyPrimitive(t *testing.T) {
str := "hello"
var s string
awsutil.Copy(&s, &str)
assert.Equal(t, "hello", s)
}
func TestCopyNil(t *testing.T) {
var s string
awsutil.Copy(&s, nil)
assert.Equal(t, "", s)
}
func TestCopyReader(t *testing.T) {
var buf io.Reader = bytes.NewReader([]byte("hello world"))
var r io.Reader
awsutil.Copy(&r, buf)
b, err := ioutil.ReadAll(r)
assert.NoError(t, err)
assert.Equal(t, []byte("hello world"), b)
// empty bytes because this is not a deep copy
b, err = ioutil.ReadAll(buf)
assert.NoError(t, err)
assert.Equal(t, []byte(""), b)
}
func TestCopyDifferentStructs(t *testing.T) {
type SrcFoo struct {
A int
B []*string
C map[string]*int
SrcUnique string
SameNameDiffType int
unexportedPtr *int
ExportedPtr *int
}
type DstFoo struct {
A int
B []*string
C map[string]*int
DstUnique int
SameNameDiffType string
unexportedPtr *int
ExportedPtr *int
}
// Create the initial value
str1 := "hello"
str2 := "bye bye"
int1 := 1
int2 := 2
f1 := &SrcFoo{
A: 1,
B: []*string{&str1, &str2},
C: map[string]*int{
"A": &int1,
"B": &int2,
},
SrcUnique: "unique",
SameNameDiffType: 1,
unexportedPtr: &int1,
ExportedPtr: &int2,
}
// Do the copy
var f2 DstFoo
awsutil.Copy(&f2, f1)
// Values are equal
assert.Equal(t, f2.A, f1.A)
assert.Equal(t, f2.B, f1.B)
assert.Equal(t, f2.C, f1.C)
assert.Equal(t, "unique", f1.SrcUnique)
assert.Equal(t, 1, f1.SameNameDiffType)
assert.Equal(t, 0, f2.DstUnique)
assert.Equal(t, "", f2.SameNameDiffType)
assert.Equal(t, int1, *f1.unexportedPtr)
assert.Nil(t, f2.unexportedPtr)
assert.Equal(t, int2, *f1.ExportedPtr)
assert.Equal(t, int2, *f2.ExportedPtr)
}
func ExampleCopyOf() {
type Foo struct {
A int
B []*string
}
// Create the initial value
str1 := "hello"
str2 := "bye bye"
f1 := &Foo{A: 1, B: []*string{&str1, &str2}}
// Do the copy
v := awsutil.CopyOf(f1)
var f2 *Foo = v.(*Foo)
// Print the result
fmt.Println(awsutil.Prettify(f2))
// Output:
// {
// A: 1,
// B: ["hello","bye bye"]
// }
}

View File

@@ -0,0 +1,27 @@
package awsutil
import (
"reflect"
)
// DeepEqual returns if the two values are deeply equal like reflect.DeepEqual.
// In addition to this, this method will also dereference the input values if
// possible so the DeepEqual performed will not fail if one parameter is a
// pointer and the other is not.
//
// DeepEqual will not perform indirection of nested values of the input parameters.
func DeepEqual(a, b interface{}) bool {
ra := reflect.Indirect(reflect.ValueOf(a))
rb := reflect.Indirect(reflect.ValueOf(b))
if raValid, rbValid := ra.IsValid(), rb.IsValid(); !raValid && !rbValid {
// If the elements are both nil, and of the same type the are equal
// If they are of different types they are not equal
return reflect.TypeOf(a) == reflect.TypeOf(b)
} else if raValid != rbValid {
// Both values must be valid to be equal
return false
}
return reflect.DeepEqual(ra.Interface(), rb.Interface())
}

View File

@@ -0,0 +1,29 @@
package awsutil_test
import (
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/stretchr/testify/assert"
)
func TestDeepEqual(t *testing.T) {
cases := []struct {
a, b interface{}
equal bool
}{
{"a", "a", true},
{"a", "b", false},
{"a", aws.String(""), false},
{"a", nil, false},
{"a", aws.String("a"), true},
{(*bool)(nil), (*bool)(nil), true},
{(*bool)(nil), (*string)(nil), false},
{nil, nil, true},
}
for i, c := range cases {
assert.Equal(t, c.equal, awsutil.DeepEqual(c.a, c.b), "%d, a:%v b:%v, %t", i, c.a, c.b, c.equal)
}
}

View File

@@ -0,0 +1,222 @@
package awsutil
import (
"reflect"
"regexp"
"strconv"
"strings"
"github.com/jmespath/go-jmespath"
)
var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`)
// rValuesAtPath returns a slice of values found in value v. The values
// in v are explored recursively so all nested values are collected.
func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTerm bool) []reflect.Value {
pathparts := strings.Split(path, "||")
if len(pathparts) > 1 {
for _, pathpart := range pathparts {
vals := rValuesAtPath(v, pathpart, createPath, caseSensitive, nilTerm)
if len(vals) > 0 {
return vals
}
}
return nil
}
values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))}
components := strings.Split(path, ".")
for len(values) > 0 && len(components) > 0 {
var index *int64
var indexStar bool
c := strings.TrimSpace(components[0])
if c == "" { // no actual component, illegal syntax
return nil
} else if caseSensitive && c != "*" && strings.ToLower(c[0:1]) == c[0:1] {
// TODO normalize case for user
return nil // don't support unexported fields
}
// parse this component
if m := indexRe.FindStringSubmatch(c); m != nil {
c = m[1]
if m[2] == "" {
index = nil
indexStar = true
} else {
i, _ := strconv.ParseInt(m[2], 10, 32)
index = &i
indexStar = false
}
}
nextvals := []reflect.Value{}
for _, value := range values {
// pull component name out of struct member
if value.Kind() != reflect.Struct {
continue
}
if c == "*" { // pull all members
for i := 0; i < value.NumField(); i++ {
if f := reflect.Indirect(value.Field(i)); f.IsValid() {
nextvals = append(nextvals, f)
}
}
continue
}
value = value.FieldByNameFunc(func(name string) bool {
if c == name {
return true
} else if !caseSensitive && strings.ToLower(name) == strings.ToLower(c) {
return true
}
return false
})
if nilTerm && value.Kind() == reflect.Ptr && len(components[1:]) == 0 {
if !value.IsNil() {
value.Set(reflect.Zero(value.Type()))
}
return []reflect.Value{value}
}
if createPath && value.Kind() == reflect.Ptr && value.IsNil() {
// TODO if the value is the terminus it should not be created
// if the value to be set to its position is nil.
value.Set(reflect.New(value.Type().Elem()))
value = value.Elem()
} else {
value = reflect.Indirect(value)
}
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
if !createPath && value.IsNil() {
value = reflect.ValueOf(nil)
}
}
if value.IsValid() {
nextvals = append(nextvals, value)
}
}
values = nextvals
if indexStar || index != nil {
nextvals = []reflect.Value{}
for _, value := range values {
value := reflect.Indirect(value)
if value.Kind() != reflect.Slice {
continue
}
if indexStar { // grab all indices
for i := 0; i < value.Len(); i++ {
idx := reflect.Indirect(value.Index(i))
if idx.IsValid() {
nextvals = append(nextvals, idx)
}
}
continue
}
// pull out index
i := int(*index)
if i >= value.Len() { // check out of bounds
if createPath {
// TODO resize slice
} else {
continue
}
} else if i < 0 { // support negative indexing
i = value.Len() + i
}
value = reflect.Indirect(value.Index(i))
if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
if !createPath && value.IsNil() {
value = reflect.ValueOf(nil)
}
}
if value.IsValid() {
nextvals = append(nextvals, value)
}
}
values = nextvals
}
components = components[1:]
}
return values
}
// ValuesAtPath returns a list of values at the case insensitive lexical
// path inside of a structure.
func ValuesAtPath(i interface{}, path string) ([]interface{}, error) {
result, err := jmespath.Search(path, i)
if err != nil {
return nil, err
}
v := reflect.ValueOf(result)
if !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) {
return nil, nil
}
if s, ok := result.([]interface{}); ok {
return s, err
}
if v.Kind() == reflect.Map && v.Len() == 0 {
return nil, nil
}
if v.Kind() == reflect.Slice {
out := make([]interface{}, v.Len())
for i := 0; i < v.Len(); i++ {
out[i] = v.Index(i).Interface()
}
return out, nil
}
return []interface{}{result}, nil
}
// SetValueAtPath sets a value at the case insensitive lexical path inside
// of a structure.
func SetValueAtPath(i interface{}, path string, v interface{}) {
if rvals := rValuesAtPath(i, path, true, false, v == nil); rvals != nil {
for _, rval := range rvals {
if rval.Kind() == reflect.Ptr && rval.IsNil() {
continue
}
setValue(rval, v)
}
}
}
func setValue(dstVal reflect.Value, src interface{}) {
if dstVal.Kind() == reflect.Ptr {
dstVal = reflect.Indirect(dstVal)
}
srcVal := reflect.ValueOf(src)
if !srcVal.IsValid() { // src is literal nil
if dstVal.CanAddr() {
// Convert to pointer so that pointer's value can be nil'ed
// dstVal = dstVal.Addr()
}
dstVal.Set(reflect.Zero(dstVal.Type()))
} else if srcVal.Kind() == reflect.Ptr {
if srcVal.IsNil() {
srcVal = reflect.Zero(dstVal.Type())
} else {
srcVal = reflect.ValueOf(src).Elem()
}
dstVal.Set(srcVal)
} else {
dstVal.Set(srcVal)
}
}

View File

@@ -0,0 +1,142 @@
package awsutil_test
import (
"testing"
"github.com/aws/aws-sdk-go/aws/awsutil"
"github.com/stretchr/testify/assert"
)
type Struct struct {
A []Struct
z []Struct
B *Struct
D *Struct
C string
E map[string]string
}
var data = Struct{
A: []Struct{{C: "value1"}, {C: "value2"}, {C: "value3"}},
z: []Struct{{C: "value1"}, {C: "value2"}, {C: "value3"}},
B: &Struct{B: &Struct{C: "terminal"}, D: &Struct{C: "terminal2"}},
C: "initial",
}
var data2 = Struct{A: []Struct{
{A: []Struct{{C: "1"}, {C: "1"}, {C: "1"}, {C: "1"}, {C: "1"}}},
{A: []Struct{{C: "2"}, {C: "2"}, {C: "2"}, {C: "2"}, {C: "2"}}},
}}
func TestValueAtPathSuccess(t *testing.T) {
var testCases = []struct {
expect []interface{}
data interface{}
path string
}{
{[]interface{}{"initial"}, data, "C"},
{[]interface{}{"value1"}, data, "A[0].C"},
{[]interface{}{"value2"}, data, "A[1].C"},
{[]interface{}{"value3"}, data, "A[2].C"},
{[]interface{}{"value3"}, data, "a[2].c"},
{[]interface{}{"value3"}, data, "A[-1].C"},
{[]interface{}{"value1", "value2", "value3"}, data, "A[].C"},
{[]interface{}{"terminal"}, data, "B . B . C"},
{[]interface{}{"initial"}, data, "A.D.X || C"},
{[]interface{}{"initial"}, data, "A[0].B || C"},
{[]interface{}{
Struct{A: []Struct{{C: "1"}, {C: "1"}, {C: "1"}, {C: "1"}, {C: "1"}}},
Struct{A: []Struct{{C: "2"}, {C: "2"}, {C: "2"}, {C: "2"}, {C: "2"}}},
}, data2, "A"},
}
for i, c := range testCases {
v, err := awsutil.ValuesAtPath(c.data, c.path)
assert.NoError(t, err, "case %d, expected no error, %s", i, c.path)
assert.Equal(t, c.expect, v, "case %d, %s", i, c.path)
}
}
func TestValueAtPathFailure(t *testing.T) {
var testCases = []struct {
expect []interface{}
errContains string
data interface{}
path string
}{
{nil, "", data, "C.x"},
{nil, "SyntaxError: Invalid token: tDot", data, ".x"},
{nil, "", data, "X.Y.Z"},
{nil, "", data, "A[100].C"},
{nil, "", data, "A[3].C"},
{nil, "", data, "B.B.C.Z"},
{nil, "", data, "z[-1].C"},
{nil, "", nil, "A.B.C"},
{[]interface{}{}, "", Struct{}, "A"},
{nil, "", data, "A[0].B.C"},
{nil, "", data, "D"},
}
for i, c := range testCases {
v, err := awsutil.ValuesAtPath(c.data, c.path)
if c.errContains != "" {
assert.Contains(t, err.Error(), c.errContains, "case %d, expected error, %s", i, c.path)
continue
} else {
assert.NoError(t, err, "case %d, expected no error, %s", i, c.path)
}
assert.Equal(t, c.expect, v, "case %d, %s", i, c.path)
}
}
func TestSetValueAtPathSuccess(t *testing.T) {
var s Struct
awsutil.SetValueAtPath(&s, "C", "test1")
awsutil.SetValueAtPath(&s, "B.B.C", "test2")
awsutil.SetValueAtPath(&s, "B.D.C", "test3")
assert.Equal(t, "test1", s.C)
assert.Equal(t, "test2", s.B.B.C)
assert.Equal(t, "test3", s.B.D.C)
awsutil.SetValueAtPath(&s, "B.*.C", "test0")
assert.Equal(t, "test0", s.B.B.C)
assert.Equal(t, "test0", s.B.D.C)
var s2 Struct
awsutil.SetValueAtPath(&s2, "b.b.c", "test0")
assert.Equal(t, "test0", s2.B.B.C)
awsutil.SetValueAtPath(&s2, "A", []Struct{{}})
assert.Equal(t, []Struct{{}}, s2.A)
str := "foo"
s3 := Struct{}
awsutil.SetValueAtPath(&s3, "b.b.c", str)
assert.Equal(t, "foo", s3.B.B.C)
s3 = Struct{B: &Struct{B: &Struct{C: str}}}
awsutil.SetValueAtPath(&s3, "b.b.c", nil)
assert.Equal(t, "", s3.B.B.C)
s3 = Struct{}
awsutil.SetValueAtPath(&s3, "b.b.c", nil)
assert.Equal(t, "", s3.B.B.C)
s3 = Struct{}
awsutil.SetValueAtPath(&s3, "b.b.c", &str)
assert.Equal(t, "foo", s3.B.B.C)
var s4 struct{ Name *string }
awsutil.SetValueAtPath(&s4, "Name", str)
assert.Equal(t, str, *s4.Name)
s4 = struct{ Name *string }{}
awsutil.SetValueAtPath(&s4, "Name", nil)
assert.Equal(t, (*string)(nil), s4.Name)
s4 = struct{ Name *string }{Name: &str}
awsutil.SetValueAtPath(&s4, "Name", nil)
assert.Equal(t, (*string)(nil), s4.Name)
s4 = struct{ Name *string }{}
awsutil.SetValueAtPath(&s4, "Name", &str)
assert.Equal(t, str, *s4.Name)
}

View File

@@ -0,0 +1,103 @@
package awsutil
import (
"bytes"
"fmt"
"io"
"reflect"
"strings"
)
// Prettify returns the string representation of a value.
func Prettify(i interface{}) string {
var buf bytes.Buffer
prettify(reflect.ValueOf(i), 0, &buf)
return buf.String()
}
// prettify will recursively walk value v to build a textual
// representation of the value.
func prettify(v reflect.Value, indent int, buf *bytes.Buffer) {
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
strtype := v.Type().String()
if strtype == "time.Time" {
fmt.Fprintf(buf, "%s", v.Interface())
break
} else if strings.HasPrefix(strtype, "io.") {
buf.WriteString("<buffer>")
break
}
buf.WriteString("{\n")
names := []string{}
for i := 0; i < v.Type().NumField(); i++ {
name := v.Type().Field(i).Name
f := v.Field(i)
if name[0:1] == strings.ToLower(name[0:1]) {
continue // ignore unexported fields
}
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice || f.Kind() == reflect.Map) && f.IsNil() {
continue // ignore unset fields
}
names = append(names, name)
}
for i, n := range names {
val := v.FieldByName(n)
buf.WriteString(strings.Repeat(" ", indent+2))
buf.WriteString(n + ": ")
prettify(val, indent+2, buf)
if i < len(names)-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
case reflect.Slice:
nl, id, id2 := "", "", ""
if v.Len() > 3 {
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
}
buf.WriteString("[" + nl)
for i := 0; i < v.Len(); i++ {
buf.WriteString(id2)
prettify(v.Index(i), indent+2, buf)
if i < v.Len()-1 {
buf.WriteString("," + nl)
}
}
buf.WriteString(nl + id + "]")
case reflect.Map:
buf.WriteString("{\n")
for i, k := range v.MapKeys() {
buf.WriteString(strings.Repeat(" ", indent+2))
buf.WriteString(k.String() + ": ")
prettify(v.MapIndex(k), indent+2, buf)
if i < v.Len()-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
default:
format := "%v"
switch v.Interface().(type) {
case string:
format = "%q"
case io.ReadSeeker, io.Reader:
format = "buffer(%p)"
}
fmt.Fprintf(buf, format, v.Interface())
}
}

View File

@@ -0,0 +1,89 @@
package awsutil
import (
"bytes"
"fmt"
"reflect"
"strings"
)
// StringValue returns the string representation of a value.
func StringValue(i interface{}) string {
var buf bytes.Buffer
stringValue(reflect.ValueOf(i), 0, &buf)
return buf.String()
}
func stringValue(v reflect.Value, indent int, buf *bytes.Buffer) {
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
switch v.Kind() {
case reflect.Struct:
buf.WriteString("{\n")
names := []string{}
for i := 0; i < v.Type().NumField(); i++ {
name := v.Type().Field(i).Name
f := v.Field(i)
if name[0:1] == strings.ToLower(name[0:1]) {
continue // ignore unexported fields
}
if (f.Kind() == reflect.Ptr || f.Kind() == reflect.Slice) && f.IsNil() {
continue // ignore unset fields
}
names = append(names, name)
}
for i, n := range names {
val := v.FieldByName(n)
buf.WriteString(strings.Repeat(" ", indent+2))
buf.WriteString(n + ": ")
stringValue(val, indent+2, buf)
if i < len(names)-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
case reflect.Slice:
nl, id, id2 := "", "", ""
if v.Len() > 3 {
nl, id, id2 = "\n", strings.Repeat(" ", indent), strings.Repeat(" ", indent+2)
}
buf.WriteString("[" + nl)
for i := 0; i < v.Len(); i++ {
buf.WriteString(id2)
stringValue(v.Index(i), indent+2, buf)
if i < v.Len()-1 {
buf.WriteString("," + nl)
}
}
buf.WriteString(nl + id + "]")
case reflect.Map:
buf.WriteString("{\n")
for i, k := range v.MapKeys() {
buf.WriteString(strings.Repeat(" ", indent+2))
buf.WriteString(k.String() + ": ")
stringValue(v.MapIndex(k), indent+2, buf)
if i < v.Len()-1 {
buf.WriteString(",\n")
}
}
buf.WriteString("\n" + strings.Repeat(" ", indent) + "}")
default:
format := "%v"
switch v.Interface().(type) {
case string:
format = "%q"
}
fmt.Fprintf(buf, format, v.Interface())
}
}

View File

@@ -0,0 +1,120 @@
package client
import (
"fmt"
"io/ioutil"
"net/http/httputil"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
)
// A Config provides configuration to a service client instance.
type Config struct {
Config *aws.Config
Handlers request.Handlers
Endpoint, SigningRegion string
}
// ConfigProvider provides a generic way for a service client to receive
// the ClientConfig without circular dependencies.
type ConfigProvider interface {
ClientConfig(serviceName string, cfgs ...*aws.Config) Config
}
// A Client implements the base client request and response handling
// used by all service clients.
type Client struct {
request.Retryer
metadata.ClientInfo
Config aws.Config
Handlers request.Handlers
}
// New will return a pointer to a new initialized service client.
func New(cfg aws.Config, info metadata.ClientInfo, handlers request.Handlers, options ...func(*Client)) *Client {
svc := &Client{
Config: cfg,
ClientInfo: info,
Handlers: handlers,
}
switch retryer, ok := cfg.Retryer.(request.Retryer); {
case ok:
svc.Retryer = retryer
case cfg.Retryer != nil && cfg.Logger != nil:
s := fmt.Sprintf("WARNING: %T does not implement request.Retryer; using DefaultRetryer instead", cfg.Retryer)
cfg.Logger.Log(s)
fallthrough
default:
maxRetries := aws.IntValue(cfg.MaxRetries)
if cfg.MaxRetries == nil || maxRetries == aws.UseServiceDefaultRetries {
maxRetries = 3
}
svc.Retryer = DefaultRetryer{NumMaxRetries: maxRetries}
}
svc.AddDebugHandlers()
for _, option := range options {
option(svc)
}
return svc
}
// NewRequest returns a new Request pointer for the service API
// operation and parameters.
func (c *Client) NewRequest(operation *request.Operation, params interface{}, data interface{}) *request.Request {
return request.New(c.Config, c.ClientInfo, c.Handlers, c.Retryer, operation, params, data)
}
// AddDebugHandlers injects debug logging handlers into the service to log request
// debug information.
func (c *Client) AddDebugHandlers() {
if !c.Config.LogLevel.AtLeast(aws.LogDebug) {
return
}
c.Handlers.Send.PushFront(logRequest)
c.Handlers.Send.PushBack(logResponse)
}
const logReqMsg = `DEBUG: Request %s/%s Details:
---[ REQUEST POST-SIGN ]-----------------------------
%s
-----------------------------------------------------`
func logRequest(r *request.Request) {
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpRequestOut(r.HTTPRequest, logBody)
if logBody {
// Reset the request body because dumpRequest will re-wrap the r.HTTPRequest's
// Body as a NoOpCloser and will not be reset after read by the HTTP
// client reader.
r.Body.Seek(r.BodyStart, 0)
r.HTTPRequest.Body = ioutil.NopCloser(r.Body)
}
r.Config.Logger.Log(fmt.Sprintf(logReqMsg, r.ClientInfo.ServiceName, r.Operation.Name, string(dumpedBody)))
}
const logRespMsg = `DEBUG: Response %s/%s Details:
---[ RESPONSE ]--------------------------------------
%s
-----------------------------------------------------`
func logResponse(r *request.Request) {
var msg = "no reponse data"
if r.HTTPResponse != nil {
logBody := r.Config.LogLevel.Matches(aws.LogDebugWithHTTPBody)
dumpedBody, _ := httputil.DumpResponse(r.HTTPResponse, logBody)
msg = string(dumpedBody)
} else if r.Error != nil {
msg = r.Error.Error()
}
r.Config.Logger.Log(fmt.Sprintf(logRespMsg, r.ClientInfo.ServiceName, r.Operation.Name, msg))
}

View File

@@ -0,0 +1,45 @@
package client
import (
"math"
"math/rand"
"time"
"github.com/aws/aws-sdk-go/aws/request"
)
// DefaultRetryer implements basic retry logic using exponential backoff for
// most services. If you want to implement custom retry logic, implement the
// request.Retryer interface or create a structure type that composes this
// struct and override the specific methods. For example, to override only
// the MaxRetries method:
//
// type retryer struct {
// service.DefaultRetryer
// }
//
// // This implementation always has 100 max retries
// func (d retryer) MaxRetries() uint { return 100 }
type DefaultRetryer struct {
NumMaxRetries int
}
// MaxRetries returns the number of maximum returns the service will use to make
// an individual API request.
func (d DefaultRetryer) MaxRetries() int {
return d.NumMaxRetries
}
// RetryRules returns the delay duration before retrying this request again
func (d DefaultRetryer) RetryRules(r *request.Request) time.Duration {
delay := int(math.Pow(2, float64(r.RetryCount))) * (rand.Intn(30) + 30)
return time.Duration(delay) * time.Millisecond
}
// ShouldRetry returns if the request should be retried.
func (d DefaultRetryer) ShouldRetry(r *request.Request) bool {
if r.HTTPResponse.StatusCode >= 500 {
return true
}
return r.IsErrorRetryable()
}

View File

@@ -0,0 +1,12 @@
package metadata
// ClientInfo wraps immutable data from the client.Client structure.
type ClientInfo struct {
ServiceName string
APIVersion string
Endpoint string
SigningName string
SigningRegion string
JSONVersion string
TargetPrefix string
}

View File

@@ -0,0 +1,270 @@
package aws
import (
"net/http"
"time"
"github.com/aws/aws-sdk-go/aws/credentials"
)
// UseServiceDefaultRetries instructs the config to use the service's own default
// number of retries. This will be the default action if Config.MaxRetries
// is nil also.
const UseServiceDefaultRetries = -1
// RequestRetryer is an alias for a type that implements the request.Retryer interface.
type RequestRetryer interface{}
// A Config provides service configuration for service clients. By default,
// all clients will use the {defaults.DefaultConfig} structure.
type Config struct {
// The credentials object to use when signing requests. Defaults to
// a chain of credential providers to search for credentials in environment
// variables, shared credential file, and EC2 Instance Roles.
Credentials *credentials.Credentials
// An optional endpoint URL (hostname only or fully qualified URI)
// that overrides the default generated endpoint for a client. Set this
// to `""` to use the default generated endpoint.
//
// @note You must still provide a `Region` value when specifying an
// endpoint for a client.
Endpoint *string
// The region to send requests to. This parameter is required and must
// be configured globally or on a per-client basis unless otherwise
// noted. A full list of regions is found in the "Regions and Endpoints"
// document.
//
// @see http://docs.aws.amazon.com/general/latest/gr/rande.html
// AWS Regions and Endpoints
Region *string
// Set this to `true` to disable SSL when sending requests. Defaults
// to `false`.
DisableSSL *bool
// The HTTP client to use when sending requests. Defaults to
// `http.DefaultClient`.
HTTPClient *http.Client
// An integer value representing the logging level. The default log level
// is zero (LogOff), which represents no logging. To enable logging set
// to a LogLevel Value.
LogLevel *LogLevelType
// The logger writer interface to write logging messages to. Defaults to
// standard out.
Logger Logger
// The maximum number of times that a request will be retried for failures.
// Defaults to -1, which defers the max retry setting to the service specific
// configuration.
MaxRetries *int
// Retryer guides how HTTP requests should be retried in case of recoverable failures.
//
// When nil or the value does not implement the request.Retryer interface,
// the request.DefaultRetryer will be used.
//
// When both Retryer and MaxRetries are non-nil, the former is used and
// the latter ignored.
//
// To set the Retryer field in a type-safe manner and with chaining, use
// the request.WithRetryer helper function:
//
// cfg := request.WithRetryer(aws.NewConfig(), myRetryer)
//
Retryer RequestRetryer
// Disables semantic parameter validation, which validates input for missing
// required fields and/or other semantic request input errors.
DisableParamValidation *bool
// Disables the computation of request and response checksums, e.g.,
// CRC32 checksums in Amazon DynamoDB.
DisableComputeChecksums *bool
// Set this to `true` to force the request to use path-style addressing,
// i.e., `http://s3.amazonaws.com/BUCKET/KEY`. By default, the S3 client will
// use virtual hosted bucket addressing when possible
// (`http://BUCKET.s3.amazonaws.com/KEY`).
//
// @note This configuration option is specific to the Amazon S3 service.
// @see http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html
// Amazon S3: Virtual Hosting of Buckets
S3ForcePathStyle *bool
SleepDelay func(time.Duration)
}
// NewConfig returns a new Config pointer that can be chained with builder methods to
// set multiple configuration values inline without using pointers.
//
// svc := s3.New(aws.NewConfig().WithRegion("us-west-2").WithMaxRetries(10))
//
func NewConfig() *Config {
return &Config{}
}
// WithCredentials sets a config Credentials value returning a Config pointer
// for chaining.
func (c *Config) WithCredentials(creds *credentials.Credentials) *Config {
c.Credentials = creds
return c
}
// WithEndpoint sets a config Endpoint value returning a Config pointer for
// chaining.
func (c *Config) WithEndpoint(endpoint string) *Config {
c.Endpoint = &endpoint
return c
}
// WithRegion sets a config Region value returning a Config pointer for
// chaining.
func (c *Config) WithRegion(region string) *Config {
c.Region = &region
return c
}
// WithDisableSSL sets a config DisableSSL value returning a Config pointer
// for chaining.
func (c *Config) WithDisableSSL(disable bool) *Config {
c.DisableSSL = &disable
return c
}
// WithHTTPClient sets a config HTTPClient value returning a Config pointer
// for chaining.
func (c *Config) WithHTTPClient(client *http.Client) *Config {
c.HTTPClient = client
return c
}
// WithMaxRetries sets a config MaxRetries value returning a Config pointer
// for chaining.
func (c *Config) WithMaxRetries(max int) *Config {
c.MaxRetries = &max
return c
}
// WithDisableParamValidation sets a config DisableParamValidation value
// returning a Config pointer for chaining.
func (c *Config) WithDisableParamValidation(disable bool) *Config {
c.DisableParamValidation = &disable
return c
}
// WithDisableComputeChecksums sets a config DisableComputeChecksums value
// returning a Config pointer for chaining.
func (c *Config) WithDisableComputeChecksums(disable bool) *Config {
c.DisableComputeChecksums = &disable
return c
}
// WithLogLevel sets a config LogLevel value returning a Config pointer for
// chaining.
func (c *Config) WithLogLevel(level LogLevelType) *Config {
c.LogLevel = &level
return c
}
// WithLogger sets a config Logger value returning a Config pointer for
// chaining.
func (c *Config) WithLogger(logger Logger) *Config {
c.Logger = logger
return c
}
// WithS3ForcePathStyle sets a config S3ForcePathStyle value returning a Config
// pointer for chaining.
func (c *Config) WithS3ForcePathStyle(force bool) *Config {
c.S3ForcePathStyle = &force
return c
}
// WithSleepDelay overrides the function used to sleep while waiting for the
// next retry. Defaults to time.Sleep.
func (c *Config) WithSleepDelay(fn func(time.Duration)) *Config {
c.SleepDelay = fn
return c
}
// MergeIn merges the passed in configs into the existing config object.
func (c *Config) MergeIn(cfgs ...*Config) {
for _, other := range cfgs {
mergeInConfig(c, other)
}
}
func mergeInConfig(dst *Config, other *Config) {
if other == nil {
return
}
if other.Credentials != nil {
dst.Credentials = other.Credentials
}
if other.Endpoint != nil {
dst.Endpoint = other.Endpoint
}
if other.Region != nil {
dst.Region = other.Region
}
if other.DisableSSL != nil {
dst.DisableSSL = other.DisableSSL
}
if other.HTTPClient != nil {
dst.HTTPClient = other.HTTPClient
}
if other.LogLevel != nil {
dst.LogLevel = other.LogLevel
}
if other.Logger != nil {
dst.Logger = other.Logger
}
if other.MaxRetries != nil {
dst.MaxRetries = other.MaxRetries
}
if other.Retryer != nil {
dst.Retryer = other.Retryer
}
if other.DisableParamValidation != nil {
dst.DisableParamValidation = other.DisableParamValidation
}
if other.DisableComputeChecksums != nil {
dst.DisableComputeChecksums = other.DisableComputeChecksums
}
if other.S3ForcePathStyle != nil {
dst.S3ForcePathStyle = other.S3ForcePathStyle
}
if other.SleepDelay != nil {
dst.SleepDelay = other.SleepDelay
}
}
// Copy will return a shallow copy of the Config object. If any additional
// configurations are provided they will be merged into the new config returned.
func (c *Config) Copy(cfgs ...*Config) *Config {
dst := &Config{}
dst.MergeIn(c)
for _, cfg := range cfgs {
dst.MergeIn(cfg)
}
return dst
}

View File

@@ -0,0 +1,86 @@
package aws
import (
"net/http"
"reflect"
"testing"
"github.com/aws/aws-sdk-go/aws/credentials"
)
var testCredentials = credentials.NewStaticCredentials("AKID", "SECRET", "SESSION")
var copyTestConfig = Config{
Credentials: testCredentials,
Endpoint: String("CopyTestEndpoint"),
Region: String("COPY_TEST_AWS_REGION"),
DisableSSL: Bool(true),
HTTPClient: http.DefaultClient,
LogLevel: LogLevel(LogDebug),
Logger: NewDefaultLogger(),
MaxRetries: Int(3),
DisableParamValidation: Bool(true),
DisableComputeChecksums: Bool(true),
S3ForcePathStyle: Bool(true),
}
func TestCopy(t *testing.T) {
want := copyTestConfig
got := copyTestConfig.Copy()
if !reflect.DeepEqual(*got, want) {
t.Errorf("Copy() = %+v", got)
t.Errorf(" want %+v", want)
}
got.Region = String("other")
if got.Region == want.Region {
t.Errorf("Expect setting copy values not not reflect in source")
}
}
func TestCopyReturnsNewInstance(t *testing.T) {
want := copyTestConfig
got := copyTestConfig.Copy()
if got == &want {
t.Errorf("Copy() = %p; want different instance as source %p", got, &want)
}
}
var mergeTestZeroValueConfig = Config{}
var mergeTestConfig = Config{
Credentials: testCredentials,
Endpoint: String("MergeTestEndpoint"),
Region: String("MERGE_TEST_AWS_REGION"),
DisableSSL: Bool(true),
HTTPClient: http.DefaultClient,
LogLevel: LogLevel(LogDebug),
Logger: NewDefaultLogger(),
MaxRetries: Int(10),
DisableParamValidation: Bool(true),
DisableComputeChecksums: Bool(true),
S3ForcePathStyle: Bool(true),
}
var mergeTests = []struct {
cfg *Config
in *Config
want *Config
}{
{&Config{}, nil, &Config{}},
{&Config{}, &mergeTestZeroValueConfig, &Config{}},
{&Config{}, &mergeTestConfig, &mergeTestConfig},
}
func TestMerge(t *testing.T) {
for i, tt := range mergeTests {
got := tt.cfg.Copy()
got.MergeIn(tt.in)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Config %d %+v", i, tt.cfg)
t.Errorf(" Merge(%+v)", tt.in)
t.Errorf(" got %+v", got)
t.Errorf(" want %+v", tt.want)
}
}
}

View File

@@ -0,0 +1,357 @@
package aws
import "time"
// String returns a pointer to of the string value passed in.
func String(v string) *string {
return &v
}
// StringValue returns the value of the string pointer passed in or
// "" if the pointer is nil.
func StringValue(v *string) string {
if v != nil {
return *v
}
return ""
}
// StringSlice converts a slice of string values into a slice of
// string pointers
func StringSlice(src []string) []*string {
dst := make([]*string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// StringValueSlice converts a slice of string pointers into a slice of
// string values
func StringValueSlice(src []*string) []string {
dst := make([]string, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// StringMap converts a string map of string values into a string
// map of string pointers
func StringMap(src map[string]string) map[string]*string {
dst := make(map[string]*string)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// StringValueMap converts a string map of string pointers into a string
// map of string values
func StringValueMap(src map[string]*string) map[string]string {
dst := make(map[string]string)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Bool returns a pointer to of the bool value passed in.
func Bool(v bool) *bool {
return &v
}
// BoolValue returns the value of the bool pointer passed in or
// false if the pointer is nil.
func BoolValue(v *bool) bool {
if v != nil {
return *v
}
return false
}
// BoolSlice converts a slice of bool values into a slice of
// bool pointers
func BoolSlice(src []bool) []*bool {
dst := make([]*bool, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// BoolValueSlice converts a slice of bool pointers into a slice of
// bool values
func BoolValueSlice(src []*bool) []bool {
dst := make([]bool, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// BoolMap converts a string map of bool values into a string
// map of bool pointers
func BoolMap(src map[string]bool) map[string]*bool {
dst := make(map[string]*bool)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// BoolValueMap converts a string map of bool pointers into a string
// map of bool values
func BoolValueMap(src map[string]*bool) map[string]bool {
dst := make(map[string]bool)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int returns a pointer to of the int value passed in.
func Int(v int) *int {
return &v
}
// IntValue returns the value of the int pointer passed in or
// 0 if the pointer is nil.
func IntValue(v *int) int {
if v != nil {
return *v
}
return 0
}
// IntSlice converts a slice of int values into a slice of
// int pointers
func IntSlice(src []int) []*int {
dst := make([]*int, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// IntValueSlice converts a slice of int pointers into a slice of
// int values
func IntValueSlice(src []*int) []int {
dst := make([]int, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// IntMap converts a string map of int values into a string
// map of int pointers
func IntMap(src map[string]int) map[string]*int {
dst := make(map[string]*int)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// IntValueMap converts a string map of int pointers into a string
// map of int values
func IntValueMap(src map[string]*int) map[string]int {
dst := make(map[string]int)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int64 returns a pointer to of the int64 value passed in.
func Int64(v int64) *int64 {
return &v
}
// Int64Value returns the value of the int64 pointer passed in or
// 0 if the pointer is nil.
func Int64Value(v *int64) int64 {
if v != nil {
return *v
}
return 0
}
// Int64Slice converts a slice of int64 values into a slice of
// int64 pointers
func Int64Slice(src []int64) []*int64 {
dst := make([]*int64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int64ValueSlice converts a slice of int64 pointers into a slice of
// int64 values
func Int64ValueSlice(src []*int64) []int64 {
dst := make([]int64, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Int64Map converts a string map of int64 values into a string
// map of int64 pointers
func Int64Map(src map[string]int64) map[string]*int64 {
dst := make(map[string]*int64)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Int64ValueMap converts a string map of int64 pointers into a string
// map of int64 values
func Int64ValueMap(src map[string]*int64) map[string]int64 {
dst := make(map[string]int64)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Float64 returns a pointer to of the float64 value passed in.
func Float64(v float64) *float64 {
return &v
}
// Float64Value returns the value of the float64 pointer passed in or
// 0 if the pointer is nil.
func Float64Value(v *float64) float64 {
if v != nil {
return *v
}
return 0
}
// Float64Slice converts a slice of float64 values into a slice of
// float64 pointers
func Float64Slice(src []float64) []*float64 {
dst := make([]*float64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float64ValueSlice converts a slice of float64 pointers into a slice of
// float64 values
func Float64ValueSlice(src []*float64) []float64 {
dst := make([]float64, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Float64Map converts a string map of float64 values into a string
// map of float64 pointers
func Float64Map(src map[string]float64) map[string]*float64 {
dst := make(map[string]*float64)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Float64ValueMap converts a string map of float64 pointers into a string
// map of float64 values
func Float64ValueMap(src map[string]*float64) map[string]float64 {
dst := make(map[string]float64)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Time returns a pointer to of the time.Time value passed in.
func Time(v time.Time) *time.Time {
return &v
}
// TimeValue returns the value of the time.Time pointer passed in or
// time.Time{} if the pointer is nil.
func TimeValue(v *time.Time) time.Time {
if v != nil {
return *v
}
return time.Time{}
}
// TimeSlice converts a slice of time.Time values into a slice of
// time.Time pointers
func TimeSlice(src []time.Time) []*time.Time {
dst := make([]*time.Time, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// TimeValueSlice converts a slice of time.Time pointers into a slice of
// time.Time values
func TimeValueSlice(src []*time.Time) []time.Time {
dst := make([]time.Time, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// TimeMap converts a string map of time.Time values into a string
// map of time.Time pointers
func TimeMap(src map[string]time.Time) map[string]*time.Time {
dst := make(map[string]*time.Time)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// TimeValueMap converts a string map of time.Time pointers into a string
// map of time.Time values
func TimeValueMap(src map[string]*time.Time) map[string]time.Time {
dst := make(map[string]time.Time)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}

View File

@@ -0,0 +1,437 @@
package aws
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
var testCasesStringSlice = [][]string{
{"a", "b", "c", "d", "e"},
{"a", "b", "", "", "e"},
}
func TestStringSlice(t *testing.T) {
for idx, in := range testCasesStringSlice {
if in == nil {
continue
}
out := StringSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := StringValueSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesStringValueSlice = [][]*string{
{String("a"), String("b"), nil, String("c")},
}
func TestStringValueSlice(t *testing.T) {
for idx, in := range testCasesStringValueSlice {
if in == nil {
continue
}
out := StringValueSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
if in[i] == nil {
assert.Empty(t, out[i], "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx)
}
}
out2 := StringSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
for i := range out2 {
if in[i] == nil {
assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx)
}
}
}
}
var testCasesStringMap = []map[string]string{
{"a": "1", "b": "2", "c": "3"},
}
func TestStringMap(t *testing.T) {
for idx, in := range testCasesStringMap {
if in == nil {
continue
}
out := StringMap(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := StringValueMap(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesBoolSlice = [][]bool{
{true, true, false, false},
}
func TestBoolSlice(t *testing.T) {
for idx, in := range testCasesBoolSlice {
if in == nil {
continue
}
out := BoolSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := BoolValueSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesBoolValueSlice = [][]*bool{}
func TestBoolValueSlice(t *testing.T) {
for idx, in := range testCasesBoolValueSlice {
if in == nil {
continue
}
out := BoolValueSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
if in[i] == nil {
assert.Empty(t, out[i], "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx)
}
}
out2 := BoolSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
for i := range out2 {
if in[i] == nil {
assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx)
}
}
}
}
var testCasesBoolMap = []map[string]bool{
{"a": true, "b": false, "c": true},
}
func TestBoolMap(t *testing.T) {
for idx, in := range testCasesBoolMap {
if in == nil {
continue
}
out := BoolMap(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := BoolValueMap(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesIntSlice = [][]int{
{1, 2, 3, 4},
}
func TestIntSlice(t *testing.T) {
for idx, in := range testCasesIntSlice {
if in == nil {
continue
}
out := IntSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := IntValueSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesIntValueSlice = [][]*int{}
func TestIntValueSlice(t *testing.T) {
for idx, in := range testCasesIntValueSlice {
if in == nil {
continue
}
out := IntValueSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
if in[i] == nil {
assert.Empty(t, out[i], "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx)
}
}
out2 := IntSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
for i := range out2 {
if in[i] == nil {
assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx)
}
}
}
}
var testCasesIntMap = []map[string]int{
{"a": 3, "b": 2, "c": 1},
}
func TestIntMap(t *testing.T) {
for idx, in := range testCasesIntMap {
if in == nil {
continue
}
out := IntMap(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := IntValueMap(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesInt64Slice = [][]int64{
{1, 2, 3, 4},
}
func TestInt64Slice(t *testing.T) {
for idx, in := range testCasesInt64Slice {
if in == nil {
continue
}
out := Int64Slice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := Int64ValueSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesInt64ValueSlice = [][]*int64{}
func TestInt64ValueSlice(t *testing.T) {
for idx, in := range testCasesInt64ValueSlice {
if in == nil {
continue
}
out := Int64ValueSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
if in[i] == nil {
assert.Empty(t, out[i], "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx)
}
}
out2 := Int64Slice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
for i := range out2 {
if in[i] == nil {
assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx)
}
}
}
}
var testCasesInt64Map = []map[string]int64{
{"a": 3, "b": 2, "c": 1},
}
func TestInt64Map(t *testing.T) {
for idx, in := range testCasesInt64Map {
if in == nil {
continue
}
out := Int64Map(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := Int64ValueMap(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesFloat64Slice = [][]float64{
{1, 2, 3, 4},
}
func TestFloat64Slice(t *testing.T) {
for idx, in := range testCasesFloat64Slice {
if in == nil {
continue
}
out := Float64Slice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := Float64ValueSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesFloat64ValueSlice = [][]*float64{}
func TestFloat64ValueSlice(t *testing.T) {
for idx, in := range testCasesFloat64ValueSlice {
if in == nil {
continue
}
out := Float64ValueSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
if in[i] == nil {
assert.Empty(t, out[i], "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx)
}
}
out2 := Float64Slice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
for i := range out2 {
if in[i] == nil {
assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx)
}
}
}
}
var testCasesFloat64Map = []map[string]float64{
{"a": 3, "b": 2, "c": 1},
}
func TestFloat64Map(t *testing.T) {
for idx, in := range testCasesFloat64Map {
if in == nil {
continue
}
out := Float64Map(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := Float64ValueMap(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesTimeSlice = [][]time.Time{
{time.Now(), time.Now().AddDate(100, 0, 0)},
}
func TestTimeSlice(t *testing.T) {
for idx, in := range testCasesTimeSlice {
if in == nil {
continue
}
out := TimeSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := TimeValueSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}
var testCasesTimeValueSlice = [][]*time.Time{}
func TestTimeValueSlice(t *testing.T) {
for idx, in := range testCasesTimeValueSlice {
if in == nil {
continue
}
out := TimeValueSlice(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
if in[i] == nil {
assert.Empty(t, out[i], "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, *(in[i]), out[i], "Unexpected value at idx %d", idx)
}
}
out2 := TimeSlice(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
for i := range out2 {
if in[i] == nil {
assert.Empty(t, *(out2[i]), "Unexpected value at idx %d", idx)
} else {
assert.Equal(t, in[i], out2[i], "Unexpected value at idx %d", idx)
}
}
}
}
var testCasesTimeMap = []map[string]time.Time{
{"a": time.Now().AddDate(-100, 0, 0), "b": time.Now()},
}
func TestTimeMap(t *testing.T) {
for idx, in := range testCasesTimeMap {
if in == nil {
continue
}
out := TimeMap(in)
assert.Len(t, out, len(in), "Unexpected len at idx %d", idx)
for i := range out {
assert.Equal(t, in[i], *(out[i]), "Unexpected value at idx %d", idx)
}
out2 := TimeValueMap(out)
assert.Len(t, out2, len(in), "Unexpected len at idx %d", idx)
assert.Equal(t, in, out2, "Unexpected value at idx %d", idx)
}
}

View File

@@ -0,0 +1,139 @@
package corehandlers
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"runtime"
"strconv"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
// Interface for matching types which also have a Len method.
type lener interface {
Len() int
}
// BuildContentLengthHandler builds the content length of a request based on the body,
// or will use the HTTPRequest.Header's "Content-Length" if defined. If unable
// to determine request body length and no "Content-Length" was specified it will panic.
var BuildContentLengthHandler = request.NamedHandler{Name: "core.BuildContentLengthHandler", Fn: func(r *request.Request) {
if slength := r.HTTPRequest.Header.Get("Content-Length"); slength != "" {
length, _ := strconv.ParseInt(slength, 10, 64)
r.HTTPRequest.ContentLength = length
return
}
var length int64
switch body := r.Body.(type) {
case nil:
length = 0
case lener:
length = int64(body.Len())
case io.Seeker:
r.BodyStart, _ = body.Seek(0, 1)
end, _ := body.Seek(0, 2)
body.Seek(r.BodyStart, 0) // make sure to seek back to original location
length = end - r.BodyStart
default:
panic("Cannot get length of body, must provide `ContentLength`")
}
r.HTTPRequest.ContentLength = length
r.HTTPRequest.Header.Set("Content-Length", fmt.Sprintf("%d", length))
}}
// SDKVersionUserAgentHandler is a request handler for adding the SDK Version to the user agent.
var SDKVersionUserAgentHandler = request.NamedHandler{
Name: "core.SDKVersionUserAgentHandler",
Fn: request.MakeAddToUserAgentHandler(aws.SDKName, aws.SDKVersion,
runtime.Version(), runtime.GOOS, runtime.GOARCH),
}
var reStatusCode = regexp.MustCompile(`^(\d{3})`)
// SendHandler is a request handler to send service request using HTTP client.
var SendHandler = request.NamedHandler{Name: "core.SendHandler", Fn: func(r *request.Request) {
var err error
r.HTTPResponse, err = r.Config.HTTPClient.Do(r.HTTPRequest)
if err != nil {
// Capture the case where url.Error is returned for error processing
// response. e.g. 301 without location header comes back as string
// error and r.HTTPResponse is nil. Other url redirect errors will
// comeback in a similar method.
if e, ok := err.(*url.Error); ok && e.Err != nil {
if s := reStatusCode.FindStringSubmatch(e.Err.Error()); s != nil {
code, _ := strconv.ParseInt(s[1], 10, 64)
r.HTTPResponse = &http.Response{
StatusCode: int(code),
Status: http.StatusText(int(code)),
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
}
return
}
}
if r.HTTPResponse == nil {
// Add a dummy request response object to ensure the HTTPResponse
// value is consistent.
r.HTTPResponse = &http.Response{
StatusCode: int(0),
Status: http.StatusText(int(0)),
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
}
}
// Catch all other request errors.
r.Error = awserr.New("RequestError", "send request failed", err)
r.Retryable = aws.Bool(true) // network errors are retryable
}
}}
// ValidateResponseHandler is a request handler to validate service response.
var ValidateResponseHandler = request.NamedHandler{Name: "core.ValidateResponseHandler", Fn: func(r *request.Request) {
if r.HTTPResponse.StatusCode == 0 || r.HTTPResponse.StatusCode >= 300 {
// this may be replaced by an UnmarshalError handler
r.Error = awserr.New("UnknownError", "unknown error", nil)
}
}}
// AfterRetryHandler performs final checks to determine if the request should
// be retried and how long to delay.
var AfterRetryHandler = request.NamedHandler{Name: "core.AfterRetryHandler", Fn: func(r *request.Request) {
// If one of the other handlers already set the retry state
// we don't want to override it based on the service's state
if r.Retryable == nil {
r.Retryable = aws.Bool(r.ShouldRetry(r))
}
if r.WillRetry() {
r.RetryDelay = r.RetryRules(r)
r.Config.SleepDelay(r.RetryDelay)
// when the expired token exception occurs the credentials
// need to be expired locally so that the next request to
// get credentials will trigger a credentials refresh.
if r.IsErrorExpired() {
r.Config.Credentials.Expire()
}
r.RetryCount++
r.Error = nil
}
}}
// ValidateEndpointHandler is a request handler to validate a request had the
// appropriate Region and Endpoint set. Will set r.Error if the endpoint or
// region is not valid.
var ValidateEndpointHandler = request.NamedHandler{Name: "core.ValidateEndpointHandler", Fn: func(r *request.Request) {
if r.ClientInfo.SigningRegion == "" && aws.StringValue(r.Config.Region) == "" {
r.Error = aws.ErrMissingRegion
} else if r.ClientInfo.Endpoint == "" {
r.Error = aws.ErrMissingEndpoint
}
}}

View File

@@ -0,0 +1,113 @@
package corehandlers_test
import (
"fmt"
"net/http"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/awstesting"
)
func TestValidateEndpointHandler(t *testing.T) {
os.Clearenv()
svc := awstesting.NewClient(aws.NewConfig().WithRegion("us-west-2"))
svc.Handlers.Clear()
svc.Handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
req := svc.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
err := req.Build()
assert.NoError(t, err)
}
func TestValidateEndpointHandlerErrorRegion(t *testing.T) {
os.Clearenv()
svc := awstesting.NewClient()
svc.Handlers.Clear()
svc.Handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
req := svc.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
err := req.Build()
assert.Error(t, err)
assert.Equal(t, aws.ErrMissingRegion, err)
}
type mockCredsProvider struct {
expired bool
retrieveCalled bool
}
func (m *mockCredsProvider) Retrieve() (credentials.Value, error) {
m.retrieveCalled = true
return credentials.Value{}, nil
}
func (m *mockCredsProvider) IsExpired() bool {
return m.expired
}
func TestAfterRetryRefreshCreds(t *testing.T) {
os.Clearenv()
credProvider := &mockCredsProvider{}
svc := awstesting.NewClient(&aws.Config{
Credentials: credentials.NewCredentials(credProvider),
MaxRetries: aws.Int(1),
})
svc.Handlers.Clear()
svc.Handlers.ValidateResponse.PushBack(func(r *request.Request) {
r.Error = awserr.New("UnknownError", "", nil)
r.HTTPResponse = &http.Response{StatusCode: 400}
})
svc.Handlers.UnmarshalError.PushBack(func(r *request.Request) {
r.Error = awserr.New("ExpiredTokenException", "", nil)
})
svc.Handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler)
assert.True(t, svc.Config.Credentials.IsExpired(), "Expect to start out expired")
assert.False(t, credProvider.retrieveCalled)
req := svc.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
req.Send()
assert.True(t, svc.Config.Credentials.IsExpired())
assert.False(t, credProvider.retrieveCalled)
_, err := svc.Config.Credentials.Get()
assert.NoError(t, err)
assert.True(t, credProvider.retrieveCalled)
}
type testSendHandlerTransport struct{}
func (t *testSendHandlerTransport) RoundTrip(r *http.Request) (*http.Response, error) {
return nil, fmt.Errorf("mock error")
}
func TestSendHandlerError(t *testing.T) {
svc := awstesting.NewClient(&aws.Config{
HTTPClient: &http.Client{
Transport: &testSendHandlerTransport{},
},
})
svc.Handlers.Clear()
svc.Handlers.Send.PushBackNamed(corehandlers.SendHandler)
r := svc.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
r.Send()
assert.Error(t, r.Error)
assert.NotNil(t, r.HTTPResponse)
}

View File

@@ -0,0 +1,144 @@
package corehandlers
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/request"
)
// ValidateParametersHandler is a request handler to validate the input parameters.
// Validating parameters only has meaning if done prior to the request being sent.
var ValidateParametersHandler = request.NamedHandler{Name: "core.ValidateParametersHandler", Fn: func(r *request.Request) {
if r.ParamsFilled() {
v := validator{errors: []string{}}
v.validateAny(reflect.ValueOf(r.Params), "")
if count := len(v.errors); count > 0 {
format := "%d validation errors:\n- %s"
msg := fmt.Sprintf(format, count, strings.Join(v.errors, "\n- "))
r.Error = awserr.New("InvalidParameter", msg, nil)
}
}
}}
// A validator validates values. Collects validations errors which occurs.
type validator struct {
errors []string
}
// validateAny will validate any struct, slice or map type. All validations
// are also performed recursively for nested types.
func (v *validator) validateAny(value reflect.Value, path string) {
value = reflect.Indirect(value)
if !value.IsValid() {
return
}
switch value.Kind() {
case reflect.Struct:
v.validateStruct(value, path)
case reflect.Slice:
for i := 0; i < value.Len(); i++ {
v.validateAny(value.Index(i), path+fmt.Sprintf("[%d]", i))
}
case reflect.Map:
for _, n := range value.MapKeys() {
v.validateAny(value.MapIndex(n), path+fmt.Sprintf("[%q]", n.String()))
}
}
}
// validateStruct will validate the struct value's fields. If the structure has
// nested types those types will be validated also.
func (v *validator) validateStruct(value reflect.Value, path string) {
prefix := "."
if path == "" {
prefix = ""
}
for i := 0; i < value.Type().NumField(); i++ {
f := value.Type().Field(i)
if strings.ToLower(f.Name[0:1]) == f.Name[0:1] {
continue
}
fvalue := value.FieldByName(f.Name)
err := validateField(f, fvalue, validateFieldRequired, validateFieldMin)
if err != nil {
v.errors = append(v.errors, fmt.Sprintf("%s: %s", err.Error(), path+prefix+f.Name))
continue
}
v.validateAny(fvalue, path+prefix+f.Name)
}
}
type validatorFunc func(f reflect.StructField, fvalue reflect.Value) error
func validateField(f reflect.StructField, fvalue reflect.Value, funcs ...validatorFunc) error {
for _, fn := range funcs {
if err := fn(f, fvalue); err != nil {
return err
}
}
return nil
}
// Validates that a field has a valid value provided for required fields.
func validateFieldRequired(f reflect.StructField, fvalue reflect.Value) error {
if f.Tag.Get("required") == "" {
return nil
}
switch fvalue.Kind() {
case reflect.Ptr, reflect.Slice, reflect.Map:
if fvalue.IsNil() {
return fmt.Errorf("missing required parameter")
}
default:
if !fvalue.IsValid() {
return fmt.Errorf("missing required parameter")
}
}
return nil
}
// Validates that if a value is provided for a field, that value must be at
// least a minimum length.
func validateFieldMin(f reflect.StructField, fvalue reflect.Value) error {
minStr := f.Tag.Get("min")
if minStr == "" {
return nil
}
min, _ := strconv.ParseInt(minStr, 10, 64)
kind := fvalue.Kind()
if kind == reflect.Ptr {
if fvalue.IsNil() {
return nil
}
fvalue = fvalue.Elem()
}
switch fvalue.Kind() {
case reflect.String:
if int64(fvalue.Len()) < min {
return fmt.Errorf("field too short, minimum length %d", min)
}
case reflect.Slice, reflect.Map:
if fvalue.IsNil() {
return nil
}
if int64(fvalue.Len()) < min {
return fmt.Errorf("field too short, minimum length %d", min)
}
// TODO min can also apply to number minimum value.
}
return nil
}

View File

@@ -0,0 +1,134 @@
package corehandlers_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/stretchr/testify/require"
)
var testSvc = func() *client.Client {
s := &client.Client{
Config: aws.Config{},
ClientInfo: metadata.ClientInfo{
ServiceName: "mock-service",
APIVersion: "2015-01-01",
},
}
return s
}()
type StructShape struct {
RequiredList []*ConditionalStructShape `required:"true"`
RequiredMap map[string]*ConditionalStructShape `required:"true"`
RequiredBool *bool `required:"true"`
OptionalStruct *ConditionalStructShape
hiddenParameter *string
metadataStructureShape
}
type metadataStructureShape struct {
SDKShapeTraits bool
}
type ConditionalStructShape struct {
Name *string `required:"true"`
SDKShapeTraits bool
}
func TestNoErrors(t *testing.T) {
input := &StructShape{
RequiredList: []*ConditionalStructShape{},
RequiredMap: map[string]*ConditionalStructShape{
"key1": {Name: aws.String("Name")},
"key2": {Name: aws.String("Name")},
},
RequiredBool: aws.Bool(true),
OptionalStruct: &ConditionalStructShape{Name: aws.String("Name")},
}
req := testSvc.NewRequest(&request.Operation{}, input, nil)
corehandlers.ValidateParametersHandler.Fn(req)
require.NoError(t, req.Error)
}
func TestMissingRequiredParameters(t *testing.T) {
input := &StructShape{}
req := testSvc.NewRequest(&request.Operation{}, input, nil)
corehandlers.ValidateParametersHandler.Fn(req)
require.Error(t, req.Error)
assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList\n- missing required parameter: RequiredMap\n- missing required parameter: RequiredBool", req.Error.(awserr.Error).Message())
}
func TestNestedMissingRequiredParameters(t *testing.T) {
input := &StructShape{
RequiredList: []*ConditionalStructShape{{}},
RequiredMap: map[string]*ConditionalStructShape{
"key1": {Name: aws.String("Name")},
"key2": {},
},
RequiredBool: aws.Bool(true),
OptionalStruct: &ConditionalStructShape{},
}
req := testSvc.NewRequest(&request.Operation{}, input, nil)
corehandlers.ValidateParametersHandler.Fn(req)
require.Error(t, req.Error)
assert.Equal(t, "InvalidParameter", req.Error.(awserr.Error).Code())
assert.Equal(t, "3 validation errors:\n- missing required parameter: RequiredList[0].Name\n- missing required parameter: RequiredMap[\"key2\"].Name\n- missing required parameter: OptionalStruct.Name", req.Error.(awserr.Error).Message())
}
type testInput struct {
StringField string `min:"5"`
PtrStrField *string `min:"2"`
ListField []string `min:"3"`
MapField map[string]string `min:"4"`
}
var testsFieldMin = []struct {
err awserr.Error
in testInput
}{
{
err: awserr.New("InvalidParameter", "1 validation errors:\n- field too short, minimum length 5: StringField", nil),
in: testInput{StringField: "abcd"},
},
{
err: awserr.New("InvalidParameter", "2 validation errors:\n- field too short, minimum length 5: StringField\n- field too short, minimum length 3: ListField", nil),
in: testInput{StringField: "abcd", ListField: []string{"a", "b"}},
},
{
err: awserr.New("InvalidParameter", "3 validation errors:\n- field too short, minimum length 5: StringField\n- field too short, minimum length 3: ListField\n- field too short, minimum length 4: MapField", nil),
in: testInput{StringField: "abcd", ListField: []string{"a", "b"}, MapField: map[string]string{"a": "a", "b": "b"}},
},
{
err: awserr.New("InvalidParameter", "1 validation errors:\n- field too short, minimum length 2: PtrStrField", nil),
in: testInput{StringField: "abcde", PtrStrField: aws.String("v")},
},
{
err: nil,
in: testInput{StringField: "abcde", PtrStrField: aws.String("value"),
ListField: []string{"a", "b", "c"}, MapField: map[string]string{"a": "a", "b": "b", "c": "c", "d": "d"}},
},
}
func TestValidateFieldMinParameter(t *testing.T) {
for i, c := range testsFieldMin {
req := testSvc.NewRequest(&request.Operation{}, &c.in, nil)
corehandlers.ValidateParametersHandler.Fn(req)
require.Equal(t, c.err, req.Error, "%d case failed", i)
}
}

View File

@@ -0,0 +1,85 @@
package credentials
import (
"github.com/aws/aws-sdk-go/aws/awserr"
)
var (
// ErrNoValidProvidersFoundInChain Is returned when there are no valid
// providers in the ChainProvider.
//
// @readonly
ErrNoValidProvidersFoundInChain = awserr.New("NoCredentialProviders", "no valid providers in chain", nil)
)
// A ChainProvider will search for a provider which returns credentials
// and cache that provider until Retrieve is called again.
//
// The ChainProvider provides a way of chaining multiple providers together
// which will pick the first available using priority order of the Providers
// in the list.
//
// If none of the Providers retrieve valid credentials Value, ChainProvider's
// Retrieve() will return the error ErrNoValidProvidersFoundInChain.
//
// If a Provider is found which returns valid credentials Value ChainProvider
// will cache that Provider for all calls to IsExpired(), until Retrieve is
// called again.
//
// Example of ChainProvider to be used with an EnvProvider and EC2RoleProvider.
// In this example EnvProvider will first check if any credentials are available
// vai the environment variables. If there are none ChainProvider will check
// the next Provider in the list, EC2RoleProvider in this case. If EC2RoleProvider
// does not return any credentials ChainProvider will return the error
// ErrNoValidProvidersFoundInChain
//
// creds := NewChainCredentials(
// []Provider{
// &EnvProvider{},
// &EC2RoleProvider{},
// })
//
// // Usage of ChainCredentials with aws.Config
// svc := ec2.New(&aws.Config{Credentials: creds})
//
type ChainProvider struct {
Providers []Provider
curr Provider
}
// NewChainCredentials returns a pointer to a new Credentials object
// wrapping a chain of providers.
func NewChainCredentials(providers []Provider) *Credentials {
return NewCredentials(&ChainProvider{
Providers: append([]Provider{}, providers...),
})
}
// Retrieve returns the credentials value or error if no provider returned
// without error.
//
// If a provider is found it will be cached and any calls to IsExpired()
// will return the expired state of the cached provider.
func (c *ChainProvider) Retrieve() (Value, error) {
for _, p := range c.Providers {
if creds, err := p.Retrieve(); err == nil {
c.curr = p
return creds, nil
}
}
c.curr = nil
// TODO better error reporting. maybe report error for each failed retrieve?
return Value{}, ErrNoValidProvidersFoundInChain
}
// IsExpired will returned the expired state of the currently cached provider
// if there is one. If there is no current provider, true will be returned.
func (c *ChainProvider) IsExpired() bool {
if c.curr != nil {
return c.curr.IsExpired()
}
return true
}

View File

@@ -0,0 +1,73 @@
package credentials
import (
"testing"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/stretchr/testify/assert"
)
func TestChainProviderGet(t *testing.T) {
p := &ChainProvider{
Providers: []Provider{
&stubProvider{err: awserr.New("FirstError", "first provider error", nil)},
&stubProvider{err: awserr.New("SecondError", "second provider error", nil)},
&stubProvider{
creds: Value{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
SessionToken: "",
},
},
},
}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match")
assert.Empty(t, creds.SessionToken, "Expect session token to be empty")
}
func TestChainProviderIsExpired(t *testing.T) {
stubProvider := &stubProvider{expired: true}
p := &ChainProvider{
Providers: []Provider{
stubProvider,
},
}
assert.True(t, p.IsExpired(), "Expect expired to be true before any Retrieve")
_, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.False(t, p.IsExpired(), "Expect not expired after retrieve")
stubProvider.expired = true
assert.True(t, p.IsExpired(), "Expect return of expired provider")
_, err = p.Retrieve()
assert.False(t, p.IsExpired(), "Expect not expired after retrieve")
}
func TestChainProviderWithNoProvider(t *testing.T) {
p := &ChainProvider{
Providers: []Provider{},
}
assert.True(t, p.IsExpired(), "Expect expired with no providers")
_, err := p.Retrieve()
assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned")
}
func TestChainProviderWithNoValidProvider(t *testing.T) {
p := &ChainProvider{
Providers: []Provider{
&stubProvider{err: awserr.New("FirstError", "first provider error", nil)},
&stubProvider{err: awserr.New("SecondError", "second provider error", nil)},
},
}
assert.True(t, p.IsExpired(), "Expect expired with no providers")
_, err := p.Retrieve()
assert.Equal(t, ErrNoValidProvidersFoundInChain, err, "Expect no providers error returned")
}

View File

@@ -0,0 +1,220 @@
// Package credentials provides credential retrieval and management
//
// The Credentials is the primary method of getting access to and managing
// credentials Values. Using dependency injection retrieval of the credential
// values is handled by a object which satisfies the Provider interface.
//
// By default the Credentials.Get() will cache the successful result of a
// Provider's Retrieve() until Provider.IsExpired() returns true. At which
// point Credentials will call Provider's Retrieve() to get new credential Value.
//
// The Provider is responsible for determining when credentials Value have expired.
// It is also important to note that Credentials will always call Retrieve the
// first time Credentials.Get() is called.
//
// Example of using the environment variable credentials.
//
// creds := NewEnvCredentials()
//
// // Retrieve the credentials value
// credValue, err := creds.Get()
// if err != nil {
// // handle error
// }
//
// Example of forcing credentials to expire and be refreshed on the next Get().
// This may be helpful to proactively expire credentials and refresh them sooner
// than they would naturally expire on their own.
//
// creds := NewCredentials(&EC2RoleProvider{})
// creds.Expire()
// credsValue, err := creds.Get()
// // New credentials will be retrieved instead of from cache.
//
//
// Custom Provider
//
// Each Provider built into this package also provides a helper method to generate
// a Credentials pointer setup with the provider. To use a custom Provider just
// create a type which satisfies the Provider interface and pass it to the
// NewCredentials method.
//
// type MyProvider struct{}
// func (m *MyProvider) Retrieve() (Value, error) {...}
// func (m *MyProvider) IsExpired() bool {...}
//
// creds := NewCredentials(&MyProvider{})
// credValue, err := creds.Get()
//
package credentials
import (
"sync"
"time"
)
// AnonymousCredentials is an empty Credential object that can be used as
// dummy placeholder credentials for requests that do not need signed.
//
// This Credentials can be used to configure a service to not sign requests
// when making service API calls. For example, when accessing public
// s3 buckets.
//
// svc := s3.New(&aws.Config{Credentials: AnonymousCredentials})
// // Access public S3 buckets.
//
// @readonly
var AnonymousCredentials = NewStaticCredentials("", "", "")
// A Value is the AWS credentials value for individual credential fields.
type Value struct {
// AWS Access key ID
AccessKeyID string
// AWS Secret Access Key
SecretAccessKey string
// AWS Session Token
SessionToken string
}
// A Provider is the interface for any component which will provide credentials
// Value. A provider is required to manage its own Expired state, and what to
// be expired means.
//
// The Provider should not need to implement its own mutexes, because
// that will be managed by Credentials.
type Provider interface {
// Refresh returns nil if it successfully retrieved the value.
// Error is returned if the value were not obtainable, or empty.
Retrieve() (Value, error)
// IsExpired returns if the credentials are no longer valid, and need
// to be retrieved.
IsExpired() bool
}
// A Expiry provides shared expiration logic to be used by credentials
// providers to implement expiry functionality.
//
// The best method to use this struct is as an anonymous field within the
// provider's struct.
//
// Example:
// type EC2RoleProvider struct {
// Expiry
// ...
// }
type Expiry struct {
// The date/time when to expire on
expiration time.Time
// If set will be used by IsExpired to determine the current time.
// Defaults to time.Now if CurrentTime is not set. Available for testing
// to be able to mock out the current time.
CurrentTime func() time.Time
}
// SetExpiration sets the expiration IsExpired will check when called.
//
// If window is greater than 0 the expiration time will be reduced by the
// window value.
//
// Using a window is helpful to trigger credentials to expire sooner than
// the expiration time given to ensure no requests are made with expired
// tokens.
func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) {
e.expiration = expiration
if window > 0 {
e.expiration = e.expiration.Add(-window)
}
}
// IsExpired returns if the credentials are expired.
func (e *Expiry) IsExpired() bool {
if e.CurrentTime == nil {
e.CurrentTime = time.Now
}
return e.expiration.Before(e.CurrentTime())
}
// A Credentials provides synchronous safe retrieval of AWS credentials Value.
// Credentials will cache the credentials value until they expire. Once the value
// expires the next Get will attempt to retrieve valid credentials.
//
// Credentials is safe to use across multiple goroutines and will manage the
// synchronous state so the Providers do not need to implement their own
// synchronization.
//
// The first Credentials.Get() will always call Provider.Retrieve() to get the
// first instance of the credentials Value. All calls to Get() after that
// will return the cached credentials Value until IsExpired() returns true.
type Credentials struct {
creds Value
forceRefresh bool
m sync.Mutex
provider Provider
}
// NewCredentials returns a pointer to a new Credentials with the provider set.
func NewCredentials(provider Provider) *Credentials {
return &Credentials{
provider: provider,
forceRefresh: true,
}
}
// Get returns the credentials value, or error if the credentials Value failed
// to be retrieved.
//
// Will return the cached credentials Value if it has not expired. If the
// credentials Value has expired the Provider's Retrieve() will be called
// to refresh the credentials.
//
// If Credentials.Expire() was called the credentials Value will be force
// expired, and the next call to Get() will cause them to be refreshed.
func (c *Credentials) Get() (Value, error) {
c.m.Lock()
defer c.m.Unlock()
if c.isExpired() {
creds, err := c.provider.Retrieve()
if err != nil {
return Value{}, err
}
c.creds = creds
c.forceRefresh = false
}
return c.creds, nil
}
// Expire expires the credentials and forces them to be retrieved on the
// next call to Get().
//
// This will override the Provider's expired state, and force Credentials
// to call the Provider's Retrieve().
func (c *Credentials) Expire() {
c.m.Lock()
defer c.m.Unlock()
c.forceRefresh = true
}
// IsExpired returns if the credentials are no longer valid, and need
// to be retrieved.
//
// If the Credentials were forced to be expired with Expire() this will
// reflect that override.
func (c *Credentials) IsExpired() bool {
c.m.Lock()
defer c.m.Unlock()
return c.isExpired()
}
// isExpired helper method wrapping the definition of expired credentials.
func (c *Credentials) isExpired() bool {
return c.forceRefresh || c.provider.IsExpired()
}

View File

@@ -0,0 +1,62 @@
package credentials
import (
"testing"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/stretchr/testify/assert"
)
type stubProvider struct {
creds Value
expired bool
err error
}
func (s *stubProvider) Retrieve() (Value, error) {
s.expired = false
return s.creds, s.err
}
func (s *stubProvider) IsExpired() bool {
return s.expired
}
func TestCredentialsGet(t *testing.T) {
c := NewCredentials(&stubProvider{
creds: Value{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
SessionToken: "",
},
expired: true,
})
creds, err := c.Get()
assert.Nil(t, err, "Expected no error")
assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match")
assert.Empty(t, creds.SessionToken, "Expect session token to be empty")
}
func TestCredentialsGetWithError(t *testing.T) {
c := NewCredentials(&stubProvider{err: awserr.New("provider error", "", nil), expired: true})
_, err := c.Get()
assert.Equal(t, "provider error", err.(awserr.Error).Code(), "Expected provider error")
}
func TestCredentialsExpire(t *testing.T) {
stub := &stubProvider{}
c := NewCredentials(stub)
stub.expired = false
assert.True(t, c.IsExpired(), "Expected to start out expired")
c.Expire()
assert.True(t, c.IsExpired(), "Expected to be expired")
c.forceRefresh = false
assert.False(t, c.IsExpired(), "Expected not to be expired")
stub.expired = true
assert.True(t, c.IsExpired(), "Expected to be expired")
}

View File

@@ -0,0 +1,173 @@
package ec2rolecreds
import (
"bufio"
"encoding/json"
"fmt"
"path"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
)
// A EC2RoleProvider retrieves credentials from the EC2 service, and keeps track if
// those credentials are expired.
//
// Example how to configure the EC2RoleProvider with custom http Client, Endpoint
// or ExpiryWindow
//
// p := &ec2rolecreds.EC2RoleProvider{
// // Pass in a custom timeout to be used when requesting
// // IAM EC2 Role credentials.
// Client: &http.Client{
// Timeout: 10 * time.Second,
// },
// // Do not use early expiry of credentials. If a non zero value is
// // specified the credentials will be expired early
// ExpiryWindow: 0,
// }
type EC2RoleProvider struct {
credentials.Expiry
// Required EC2Metadata client to use when connecting to EC2 metadata service.
Client *ec2metadata.EC2Metadata
// ExpiryWindow will allow the credentials to trigger refreshing prior to
// the credentials actually expiring. This is beneficial so race conditions
// with expiring credentials do not cause request to fail unexpectedly
// due to ExpiredTokenException exceptions.
//
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
// 10 seconds before the credentials are actually expired.
//
// If ExpiryWindow is 0 or less it will be ignored.
ExpiryWindow time.Duration
}
// NewCredentials returns a pointer to a new Credentials object wrapping
// the EC2RoleProvider. Takes a ConfigProvider to create a EC2Metadata client.
// The ConfigProvider is satisfied by the session.Session type.
func NewCredentials(c client.ConfigProvider, options ...func(*EC2RoleProvider)) *credentials.Credentials {
p := &EC2RoleProvider{
Client: ec2metadata.New(c),
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping
// the EC2RoleProvider. Takes a EC2Metadata client to use when connecting to EC2
// metadata service.
func NewCredentialsWithClient(client *ec2metadata.EC2Metadata, options ...func(*EC2RoleProvider)) *credentials.Credentials {
p := &EC2RoleProvider{
Client: client,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// Retrieve retrieves credentials from the EC2 service.
// Error will be returned if the request fails, or unable to extract
// the desired credentials.
func (m *EC2RoleProvider) Retrieve() (credentials.Value, error) {
credsList, err := requestCredList(m.Client)
if err != nil {
return credentials.Value{}, err
}
if len(credsList) == 0 {
return credentials.Value{}, awserr.New("EmptyEC2RoleList", "empty EC2 Role list", nil)
}
credsName := credsList[0]
roleCreds, err := requestCred(m.Client, credsName)
if err != nil {
return credentials.Value{}, err
}
m.SetExpiration(roleCreds.Expiration, m.ExpiryWindow)
return credentials.Value{
AccessKeyID: roleCreds.AccessKeyID,
SecretAccessKey: roleCreds.SecretAccessKey,
SessionToken: roleCreds.Token,
}, nil
}
// A ec2RoleCredRespBody provides the shape for unmarshalling credential
// request responses.
type ec2RoleCredRespBody struct {
// Success State
Expiration time.Time
AccessKeyID string
SecretAccessKey string
Token string
// Error state
Code string
Message string
}
const iamSecurityCredsPath = "/iam/security-credentials"
// requestCredList requests a list of credentials from the EC2 service.
// If there are no credentials, or there is an error making or receiving the request
func requestCredList(client *ec2metadata.EC2Metadata) ([]string, error) {
resp, err := client.GetMetadata(iamSecurityCredsPath)
if err != nil {
return nil, awserr.New("EC2RoleRequestError", "failed to list EC2 Roles", err)
}
credsList := []string{}
s := bufio.NewScanner(strings.NewReader(resp))
for s.Scan() {
credsList = append(credsList, s.Text())
}
if err := s.Err(); err != nil {
return nil, awserr.New("SerializationError", "failed to read list of EC2 Roles", err)
}
return credsList, nil
}
// requestCred requests the credentials for a specific credentials from the EC2 service.
//
// If the credentials cannot be found, or there is an error reading the response
// and error will be returned.
func requestCred(client *ec2metadata.EC2Metadata, credsName string) (ec2RoleCredRespBody, error) {
resp, err := client.GetMetadata(path.Join(iamSecurityCredsPath, credsName))
if err != nil {
return ec2RoleCredRespBody{},
awserr.New("EC2RoleRequestError",
fmt.Sprintf("failed to get %s EC2 Role credentials", credsName),
err)
}
respCreds := ec2RoleCredRespBody{}
if err := json.NewDecoder(strings.NewReader(resp)).Decode(&respCreds); err != nil {
return ec2RoleCredRespBody{},
awserr.New("SerializationError",
fmt.Sprintf("failed to decode %s EC2 Role credentials", credsName),
err)
}
if respCreds.Code != "Success" {
// If an error code was returned something failed requesting the role.
return ec2RoleCredRespBody{}, awserr.New(respCreds.Code, respCreds.Message, nil)
}
return respCreds, nil
}

View File

@@ -0,0 +1,159 @@
package ec2rolecreds_test
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
)
const credsRespTmpl = `{
"Code": "Success",
"Type": "AWS-HMAC",
"AccessKeyId" : "accessKey",
"SecretAccessKey" : "secret",
"Token" : "token",
"Expiration" : "%s",
"LastUpdated" : "2009-11-23T0:00:00Z"
}`
const credsFailRespTmpl = `{
"Code": "ErrorCode",
"Message": "ErrorMsg",
"LastUpdated": "2009-11-23T0:00:00Z"
}`
func initTestServer(expireOn string, failAssume bool) *httptest.Server {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/latest/meta-data/iam/security-credentials" {
fmt.Fprintln(w, "RoleName")
} else if r.URL.Path == "/latest/meta-data/iam/security-credentials/RoleName" {
if failAssume {
fmt.Fprintf(w, credsFailRespTmpl)
} else {
fmt.Fprintf(w, credsRespTmpl, expireOn)
}
} else {
http.Error(w, "bad request", http.StatusBadRequest)
}
}))
return server
}
func TestEC2RoleProvider(t *testing.T) {
server := initTestServer("2014-12-16T01:51:37Z", false)
defer server.Close()
p := &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")}),
}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error, %v", err)
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
}
func TestEC2RoleProviderFailAssume(t *testing.T) {
server := initTestServer("2014-12-16T01:51:37Z", true)
defer server.Close()
p := &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")}),
}
creds, err := p.Retrieve()
assert.Error(t, err, "Expect error")
e := err.(awserr.Error)
assert.Equal(t, "ErrorCode", e.Code())
assert.Equal(t, "ErrorMsg", e.Message())
assert.Nil(t, e.OrigErr())
assert.Equal(t, "", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, "", creds.SessionToken, "Expect session token to match")
}
func TestEC2RoleProviderIsExpired(t *testing.T) {
server := initTestServer("2014-12-16T01:51:37Z", false)
defer server.Close()
p := &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")}),
}
p.CurrentTime = func() time.Time {
return time.Date(2014, 12, 15, 21, 26, 0, 0, time.UTC)
}
assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve.")
_, err := p.Retrieve()
assert.Nil(t, err, "Expect no error, %v", err)
assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve.")
p.CurrentTime = func() time.Time {
return time.Date(3014, 12, 15, 21, 26, 0, 0, time.UTC)
}
assert.True(t, p.IsExpired(), "Expect creds to be expired.")
}
func TestEC2RoleProviderExpiryWindowIsExpired(t *testing.T) {
server := initTestServer("2014-12-16T01:51:37Z", false)
defer server.Close()
p := &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")}),
ExpiryWindow: time.Hour * 1,
}
p.CurrentTime = func() time.Time {
return time.Date(2014, 12, 15, 0, 51, 37, 0, time.UTC)
}
assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve.")
_, err := p.Retrieve()
assert.Nil(t, err, "Expect no error, %v", err)
assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve.")
p.CurrentTime = func() time.Time {
return time.Date(2014, 12, 16, 0, 55, 37, 0, time.UTC)
}
assert.True(t, p.IsExpired(), "Expect creds to be expired.")
}
func BenchmarkEC3RoleProvider(b *testing.B) {
server := initTestServer("2014-12-16T01:51:37Z", false)
defer server.Close()
p := &ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")}),
}
_, err := p.Retrieve()
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := p.Retrieve(); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -0,0 +1,73 @@
package credentials
import (
"os"
"github.com/aws/aws-sdk-go/aws/awserr"
)
var (
// ErrAccessKeyIDNotFound is returned when the AWS Access Key ID can't be
// found in the process's environment.
//
// @readonly
ErrAccessKeyIDNotFound = awserr.New("EnvAccessKeyNotFound", "AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment", nil)
// ErrSecretAccessKeyNotFound is returned when the AWS Secret Access Key
// can't be found in the process's environment.
//
// @readonly
ErrSecretAccessKeyNotFound = awserr.New("EnvSecretNotFound", "AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment", nil)
)
// A EnvProvider retrieves credentials from the environment variables of the
// running process. Environment credentials never expire.
//
// Environment variables used:
//
// * Access Key ID: AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY
// * Secret Access Key: AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY
type EnvProvider struct {
retrieved bool
}
// NewEnvCredentials returns a pointer to a new Credentials object
// wrapping the environment variable provider.
func NewEnvCredentials() *Credentials {
return NewCredentials(&EnvProvider{})
}
// Retrieve retrieves the keys from the environment.
func (e *EnvProvider) Retrieve() (Value, error) {
e.retrieved = false
id := os.Getenv("AWS_ACCESS_KEY_ID")
if id == "" {
id = os.Getenv("AWS_ACCESS_KEY")
}
secret := os.Getenv("AWS_SECRET_ACCESS_KEY")
if secret == "" {
secret = os.Getenv("AWS_SECRET_KEY")
}
if id == "" {
return Value{}, ErrAccessKeyIDNotFound
}
if secret == "" {
return Value{}, ErrSecretAccessKeyNotFound
}
e.retrieved = true
return Value{
AccessKeyID: id,
SecretAccessKey: secret,
SessionToken: os.Getenv("AWS_SESSION_TOKEN"),
}, nil
}
// IsExpired returns if the credentials have been retrieved.
func (e *EnvProvider) IsExpired() bool {
return !e.retrieved
}

View File

@@ -0,0 +1,70 @@
package credentials
import (
"github.com/stretchr/testify/assert"
"os"
"testing"
)
func TestEnvProviderRetrieve(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_ACCESS_KEY_ID", "access")
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
os.Setenv("AWS_SESSION_TOKEN", "token")
e := EnvProvider{}
creds, err := e.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "access", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
}
func TestEnvProviderIsExpired(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_ACCESS_KEY_ID", "access")
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
os.Setenv("AWS_SESSION_TOKEN", "token")
e := EnvProvider{}
assert.True(t, e.IsExpired(), "Expect creds to be expired before retrieve.")
_, err := e.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.False(t, e.IsExpired(), "Expect creds to not be expired after retrieve.")
}
func TestEnvProviderNoAccessKeyID(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_SECRET_ACCESS_KEY", "secret")
e := EnvProvider{}
creds, err := e.Retrieve()
assert.Equal(t, ErrAccessKeyIDNotFound, err, "ErrAccessKeyIDNotFound expected, but was %#v error: %#v", creds, err)
}
func TestEnvProviderNoSecretAccessKey(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_ACCESS_KEY_ID", "access")
e := EnvProvider{}
creds, err := e.Retrieve()
assert.Equal(t, ErrSecretAccessKeyNotFound, err, "ErrSecretAccessKeyNotFound expected, but was %#v error: %#v", creds, err)
}
func TestEnvProviderAlternateNames(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_ACCESS_KEY", "access")
os.Setenv("AWS_SECRET_KEY", "secret")
e := EnvProvider{}
creds, err := e.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "access", creds.AccessKeyID, "Expected access key ID")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expected secret access key")
assert.Empty(t, creds.SessionToken, "Expected no token")
}

View File

@@ -0,0 +1,12 @@
[default]
aws_access_key_id = accessKey
aws_secret_access_key = secret
aws_session_token = token
[no_token]
aws_access_key_id = accessKey
aws_secret_access_key = secret
[with_colon]
aws_access_key_id: accessKey
aws_secret_access_key: secret

View File

@@ -0,0 +1,147 @@
package credentials
import (
"fmt"
"os"
"path/filepath"
"github.com/go-ini/ini"
"github.com/aws/aws-sdk-go/aws/awserr"
)
var (
// ErrSharedCredentialsHomeNotFound is emitted when the user directory cannot be found.
//
// @readonly
ErrSharedCredentialsHomeNotFound = awserr.New("UserHomeNotFound", "user home directory not found.", nil)
)
// A SharedCredentialsProvider retrieves credentials from the current user's home
// directory, and keeps track if those credentials are expired.
//
// Profile ini file example: $HOME/.aws/credentials
type SharedCredentialsProvider struct {
// Path to the shared credentials file.
//
// If empty will look for "AWS_SHARED_CREDENTIALS_FILE" env variable. If the
// env value is empty will default to current user's home directory.
// Linux/OSX: "$HOME/.aws/credentials"
// Windows: "%USERPROFILE%\.aws\credentials"
Filename string
// AWS Profile to extract credentials from the shared credentials file. If empty
// will default to environment variable "AWS_PROFILE" or "default" if
// environment variable is also not set.
Profile string
// retrieved states if the credentials have been successfully retrieved.
retrieved bool
}
// NewSharedCredentials returns a pointer to a new Credentials object
// wrapping the Profile file provider.
func NewSharedCredentials(filename, profile string) *Credentials {
return NewCredentials(&SharedCredentialsProvider{
Filename: filename,
Profile: profile,
})
}
// Retrieve reads and extracts the shared credentials from the current
// users home directory.
func (p *SharedCredentialsProvider) Retrieve() (Value, error) {
p.retrieved = false
filename, err := p.filename()
if err != nil {
return Value{}, err
}
creds, err := loadProfile(filename, p.profile())
if err != nil {
return Value{}, err
}
p.retrieved = true
return creds, nil
}
// IsExpired returns if the shared credentials have expired.
func (p *SharedCredentialsProvider) IsExpired() bool {
return !p.retrieved
}
// loadProfiles loads from the file pointed to by shared credentials filename for profile.
// The credentials retrieved from the profile will be returned or error. Error will be
// returned if it fails to read from the file, or the data is invalid.
func loadProfile(filename, profile string) (Value, error) {
config, err := ini.Load(filename)
if err != nil {
return Value{}, awserr.New("SharedCredsLoad", "failed to load shared credentials file", err)
}
iniProfile, err := config.GetSection(profile)
if err != nil {
return Value{}, awserr.New("SharedCredsLoad", "failed to get profile", err)
}
id, err := iniProfile.GetKey("aws_access_key_id")
if err != nil {
return Value{}, awserr.New("SharedCredsAccessKey",
fmt.Sprintf("shared credentials %s in %s did not contain aws_access_key_id", profile, filename),
err)
}
secret, err := iniProfile.GetKey("aws_secret_access_key")
if err != nil {
return Value{}, awserr.New("SharedCredsSecret",
fmt.Sprintf("shared credentials %s in %s did not contain aws_secret_access_key", profile, filename),
nil)
}
// Default to empty string if not found
token := iniProfile.Key("aws_session_token")
return Value{
AccessKeyID: id.String(),
SecretAccessKey: secret.String(),
SessionToken: token.String(),
}, nil
}
// filename returns the filename to use to read AWS shared credentials.
//
// Will return an error if the user's home directory path cannot be found.
func (p *SharedCredentialsProvider) filename() (string, error) {
if p.Filename == "" {
if p.Filename = os.Getenv("AWS_SHARED_CREDENTIALS_FILE"); p.Filename != "" {
return p.Filename, nil
}
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" { // Windows
homeDir = os.Getenv("USERPROFILE")
}
if homeDir == "" {
return "", ErrSharedCredentialsHomeNotFound
}
p.Filename = filepath.Join(homeDir, ".aws", "credentials")
}
return p.Filename, nil
}
// profile returns the AWS shared credentials profile. If empty will read
// environment variable "AWS_PROFILE". If that is not set profile will
// return "default".
func (p *SharedCredentialsProvider) profile() string {
if p.Profile == "" {
p.Profile = os.Getenv("AWS_PROFILE")
}
if p.Profile == "" {
p.Profile = "default"
}
return p.Profile
}

View File

@@ -0,0 +1,100 @@
package credentials
import (
"github.com/stretchr/testify/assert"
"os"
"testing"
)
func TestSharedCredentialsProvider(t *testing.T) {
os.Clearenv()
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
}
func TestSharedCredentialsProviderIsExpired(t *testing.T) {
os.Clearenv()
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
assert.True(t, p.IsExpired(), "Expect creds to be expired before retrieve")
_, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.False(t, p.IsExpired(), "Expect creds to not be expired after retrieve")
}
func TestSharedCredentialsProviderWithAWS_SHARED_CREDENTIALS_FILE(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "example.ini")
p := SharedCredentialsProvider{}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, "token", creds.SessionToken, "Expect session token to match")
}
func TestSharedCredentialsProviderWithAWS_PROFILE(t *testing.T) {
os.Clearenv()
os.Setenv("AWS_PROFILE", "no_token")
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Empty(t, creds.SessionToken, "Expect no token")
}
func TestSharedCredentialsProviderWithoutTokenFromProfile(t *testing.T) {
os.Clearenv()
p := SharedCredentialsProvider{Filename: "example.ini", Profile: "no_token"}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Empty(t, creds.SessionToken, "Expect no token")
}
func TestSharedCredentialsProviderColonInCredFile(t *testing.T) {
os.Clearenv()
p := SharedCredentialsProvider{Filename: "example.ini", Profile: "with_colon"}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "accessKey", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "secret", creds.SecretAccessKey, "Expect secret access key to match")
assert.Empty(t, creds.SessionToken, "Expect no token")
}
func BenchmarkSharedCredentialsProvider(b *testing.B) {
os.Clearenv()
p := SharedCredentialsProvider{Filename: "example.ini", Profile: ""}
_, err := p.Retrieve()
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := p.Retrieve()
if err != nil {
b.Fatal(err)
}
}
}

View File

@@ -0,0 +1,44 @@
package credentials
import (
"github.com/aws/aws-sdk-go/aws/awserr"
)
var (
// ErrStaticCredentialsEmpty is emitted when static credentials are empty.
//
// @readonly
ErrStaticCredentialsEmpty = awserr.New("EmptyStaticCreds", "static credentials are empty", nil)
)
// A StaticProvider is a set of credentials which are set pragmatically,
// and will never expire.
type StaticProvider struct {
Value
}
// NewStaticCredentials returns a pointer to a new Credentials object
// wrapping a static credentials value provider.
func NewStaticCredentials(id, secret, token string) *Credentials {
return NewCredentials(&StaticProvider{Value: Value{
AccessKeyID: id,
SecretAccessKey: secret,
SessionToken: token,
}})
}
// Retrieve returns the credentials or error if the credentials are invalid.
func (s *StaticProvider) Retrieve() (Value, error) {
if s.AccessKeyID == "" || s.SecretAccessKey == "" {
return Value{}, ErrStaticCredentialsEmpty
}
return s.Value, nil
}
// IsExpired returns if the credentials are expired.
//
// For StaticProvider, the credentials never expired.
func (s *StaticProvider) IsExpired() bool {
return false
}

View File

@@ -0,0 +1,34 @@
package credentials
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestStaticProviderGet(t *testing.T) {
s := StaticProvider{
Value: Value{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
SessionToken: "",
},
}
creds, err := s.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "AKID", creds.AccessKeyID, "Expect access key ID to match")
assert.Equal(t, "SECRET", creds.SecretAccessKey, "Expect secret access key to match")
assert.Empty(t, creds.SessionToken, "Expect no session token")
}
func TestStaticProviderIsExpired(t *testing.T) {
s := StaticProvider{
Value: Value{
AccessKeyID: "AKID",
SecretAccessKey: "SECRET",
SessionToken: "",
},
}
assert.False(t, s.IsExpired(), "Expect static credentials to never expire")
}

View File

@@ -0,0 +1,130 @@
// Package stscreds are credential Providers to retrieve STS AWS credentials.
//
// STS provides multiple ways to retrieve credentials which can be used when making
// future AWS service API operation calls.
package stscreds
import (
"fmt"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/service/sts"
)
// AssumeRoler represents the minimal subset of the STS client API used by this provider.
type AssumeRoler interface {
AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error)
}
// DefaultDuration is the default amount of time in minutes that the credentials
// will be valid for.
var DefaultDuration = time.Duration(15) * time.Minute
// AssumeRoleProvider retrieves temporary credentials from the STS service, and
// keeps track of their expiration time. This provider must be used explicitly,
// as it is not included in the credentials chain.
type AssumeRoleProvider struct {
credentials.Expiry
// STS client to make assume role request with.
Client AssumeRoler
// Role to be assumed.
RoleARN string
// Session name, if you wish to reuse the credentials elsewhere.
RoleSessionName string
// Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
Duration time.Duration
// Optional ExternalID to pass along, defaults to nil if not set.
ExternalID *string
// ExpiryWindow will allow the credentials to trigger refreshing prior to
// the credentials actually expiring. This is beneficial so race conditions
// with expiring credentials do not cause request to fail unexpectedly
// due to ExpiredTokenException exceptions.
//
// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
// 10 seconds before the credentials are actually expired.
//
// If ExpiryWindow is 0 or less it will be ignored.
ExpiryWindow time.Duration
}
// NewCredentials returns a pointer to a new Credentials object wrapping the
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation.
//
// Takes a Config provider to create the STS client. The ConfigProvider is
// satisfied by the session.Session type.
func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: sts.New(c),
RoleARN: roleARN,
Duration: DefaultDuration,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// NewCredentialsWithClient returns a pointer to a new Credentials object wrapping the
// AssumeRoleProvider. The credentials will expire every 15 minutes and the
// role will be named after a nanosecond timestamp of this operation.
//
// Takes an AssumeRoler which can be satisfiede by the STS client.
func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials {
p := &AssumeRoleProvider{
Client: svc,
RoleARN: roleARN,
Duration: DefaultDuration,
}
for _, option := range options {
option(p)
}
return credentials.NewCredentials(p)
}
// Retrieve generates a new set of temporary credentials using STS.
func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) {
// Apply defaults where parameters are not set.
if p.RoleSessionName == "" {
// Try to work out a role name that will hopefully end up unique.
p.RoleSessionName = fmt.Sprintf("%d", time.Now().UTC().UnixNano())
}
if p.Duration == 0 {
// Expire as often as AWS permits.
p.Duration = DefaultDuration
}
roleOutput, err := p.Client.AssumeRole(&sts.AssumeRoleInput{
DurationSeconds: aws.Int64(int64(p.Duration / time.Second)),
RoleArn: aws.String(p.RoleARN),
RoleSessionName: aws.String(p.RoleSessionName),
ExternalId: p.ExternalID,
})
if err != nil {
return credentials.Value{}, err
}
// We will proactively generate new credentials before they expire.
p.SetExpiration(*roleOutput.Credentials.Expiration, p.ExpiryWindow)
return credentials.Value{
AccessKeyID: *roleOutput.Credentials.AccessKeyId,
SecretAccessKey: *roleOutput.Credentials.SecretAccessKey,
SessionToken: *roleOutput.Credentials.SessionToken,
}, nil
}

View File

@@ -0,0 +1,56 @@
package stscreds
import (
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/stretchr/testify/assert"
)
type stubSTS struct {
}
func (s *stubSTS) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) {
expiry := time.Now().Add(60 * time.Minute)
return &sts.AssumeRoleOutput{
Credentials: &sts.Credentials{
// Just reflect the role arn to the provider.
AccessKeyId: input.RoleArn,
SecretAccessKey: aws.String("assumedSecretAccessKey"),
SessionToken: aws.String("assumedSessionToken"),
Expiration: &expiry,
},
}, nil
}
func TestAssumeRoleProvider(t *testing.T) {
stub := &stubSTS{}
p := &AssumeRoleProvider{
Client: stub,
RoleARN: "roleARN",
}
creds, err := p.Retrieve()
assert.Nil(t, err, "Expect no error")
assert.Equal(t, "roleARN", creds.AccessKeyID, "Expect access key ID to be reflected role ARN")
assert.Equal(t, "assumedSecretAccessKey", creds.SecretAccessKey, "Expect secret access key to match")
assert.Equal(t, "assumedSessionToken", creds.SessionToken, "Expect session token to match")
}
func BenchmarkAssumeRoleProvider(b *testing.B) {
stub := &stubSTS{}
p := &AssumeRoleProvider{
Client: stub,
RoleARN: "roleARN",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if _, err := p.Retrieve(); err != nil {
b.Fatal(err)
}
}
}

View File

@@ -0,0 +1,76 @@
// Package defaults is a collection of helpers to retrieve the SDK's default
// configuration and handlers.
package defaults
import (
"net/http"
"os"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// A Defaults provides a collection of default values for SDK clients.
type Defaults struct {
Config *aws.Config
Handlers request.Handlers
}
// Get returns the SDK's default values with Config and handlers pre-configured.
func Get() Defaults {
cfg := Config()
handlers := Handlers()
cfg.Credentials = CredChain(cfg, handlers)
return Defaults{
Config: cfg,
Handlers: handlers,
}
}
// Config returns the default configuration.
func Config() *aws.Config {
return aws.NewConfig().
WithCredentials(credentials.AnonymousCredentials).
WithRegion(os.Getenv("AWS_REGION")).
WithHTTPClient(http.DefaultClient).
WithMaxRetries(aws.UseServiceDefaultRetries).
WithLogger(aws.NewDefaultLogger()).
WithLogLevel(aws.LogOff).
WithSleepDelay(time.Sleep)
}
// Handlers returns the default request handlers.
func Handlers() request.Handlers {
var handlers request.Handlers
handlers.Validate.PushBackNamed(corehandlers.ValidateEndpointHandler)
handlers.Build.PushBackNamed(corehandlers.SDKVersionUserAgentHandler)
handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
handlers.Send.PushBackNamed(corehandlers.SendHandler)
handlers.AfterRetry.PushBackNamed(corehandlers.AfterRetryHandler)
handlers.ValidateResponse.PushBackNamed(corehandlers.ValidateResponseHandler)
return handlers
}
// CredChain returns the default credential chain.
func CredChain(cfg *aws.Config, handlers request.Handlers) *credentials.Credentials {
endpoint, signingRegion := endpoints.EndpointForRegion(ec2metadata.ServiceName, *cfg.Region, true)
return credentials.NewChainCredentials(
[]credentials.Provider{
&credentials.EnvProvider{},
&credentials.SharedCredentialsProvider{Filename: "", Profile: ""},
&ec2rolecreds.EC2RoleProvider{
Client: ec2metadata.NewClient(*cfg, handlers, endpoint, signingRegion),
ExpiryWindow: 5 * time.Minute,
},
})
}

View File

@@ -0,0 +1,43 @@
package ec2metadata
import (
"path"
"github.com/aws/aws-sdk-go/aws/request"
)
// GetMetadata uses the path provided to request
func (c *EC2Metadata) GetMetadata(p string) (string, error) {
op := &request.Operation{
Name: "GetMetadata",
HTTPMethod: "GET",
HTTPPath: path.Join("/", "meta-data", p),
}
output := &metadataOutput{}
req := c.NewRequest(op, nil, output)
return output.Content, req.Send()
}
// Region returns the region the instance is running in.
func (c *EC2Metadata) Region() (string, error) {
resp, err := c.GetMetadata("placement/availability-zone")
if err != nil {
return "", err
}
// returns region without the suffix. Eg: us-west-2a becomes us-west-2
return resp[:len(resp)-1], nil
}
// Available returns if the application has access to the EC2 Metadata service.
// Can be used to determine if application is running within an EC2 Instance and
// the metadata service is available.
func (c *EC2Metadata) Available() bool {
if _, err := c.GetMetadata("instance-id"); err != nil {
return false
}
return true
}

View File

@@ -0,0 +1,101 @@
package ec2metadata_test
import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
)
func initTestServer(path string, resp string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI != path {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Write([]byte(resp))
}))
}
func TestEndpoint(t *testing.T) {
c := ec2metadata.New(session.New())
op := &request.Operation{
Name: "GetMetadata",
HTTPMethod: "GET",
HTTPPath: path.Join("/", "meta-data", "testpath"),
}
req := c.NewRequest(op, nil, nil)
assert.Equal(t, "http://169.254.169.254/latest", req.ClientInfo.Endpoint)
assert.Equal(t, "http://169.254.169.254/latest/meta-data/testpath", req.HTTPRequest.URL.String())
}
func TestGetMetadata(t *testing.T) {
server := initTestServer(
"/latest/meta-data/some/path",
"success", // real response includes suffix
)
defer server.Close()
c := ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")})
resp, err := c.GetMetadata("some/path")
assert.NoError(t, err)
assert.Equal(t, "success", resp)
}
func TestGetRegion(t *testing.T) {
server := initTestServer(
"/latest/meta-data/placement/availability-zone",
"us-west-2a", // real response includes suffix
)
defer server.Close()
c := ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")})
region, err := c.Region()
assert.NoError(t, err)
assert.Equal(t, "us-west-2", region)
}
func TestMetadataAvailable(t *testing.T) {
server := initTestServer(
"/latest/meta-data/instance-id",
"instance-id",
)
defer server.Close()
c := ec2metadata.New(session.New(), &aws.Config{Endpoint: aws.String(server.URL + "/latest")})
available := c.Available()
assert.True(t, available)
}
func TestMetadataNotAvailable(t *testing.T) {
c := ec2metadata.New(session.New())
c.Handlers.Send.Clear()
c.Handlers.Send.PushBack(func(r *request.Request) {
r.HTTPResponse = &http.Response{
StatusCode: int(0),
Status: http.StatusText(int(0)),
Body: ioutil.NopCloser(bytes.NewReader([]byte{})),
}
r.Error = awserr.New("RequestError", "send request failed", nil)
r.Retryable = aws.Bool(true) // network errors are retryable
})
available := c.Available()
assert.False(t, available)
}

View File

@@ -0,0 +1,116 @@
// Package ec2metadata provides the client for making API calls to the
// EC2 Metadata service.
package ec2metadata
import (
"io/ioutil"
"net"
"net/http"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/client/metadata"
"github.com/aws/aws-sdk-go/aws/request"
)
// ServiceName is the name of the service.
const ServiceName = "ec2metadata"
// A EC2Metadata is an EC2 Metadata service Client.
type EC2Metadata struct {
*client.Client
}
// New creates a new instance of the EC2Metadata client with a session.
// This client is safe to use across multiple goroutines.
//
// Example:
// // Create a EC2Metadata client from just a session.
// svc := ec2metadata.New(mySession)
//
// // Create a EC2Metadata client with additional configuration
// svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody))
func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata {
c := p.ClientConfig(ServiceName, cfgs...)
return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion)
}
// NewClient returns a new EC2Metadata client. Should be used to create
// a client when not using a session. Generally using just New with a session
// is preferred.
func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata {
// If the default http client is provided, replace it with a custom
// client using default timeouts.
if cfg.HTTPClient == http.DefaultClient {
cfg.HTTPClient = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
// use a shorter timeout than default because the metadata
// service is local if it is running, and to fail faster
// if not running on an ec2 instance.
Timeout: 5 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
},
}
}
svc := &EC2Metadata{
Client: client.New(
cfg,
metadata.ClientInfo{
ServiceName: ServiceName,
Endpoint: endpoint,
APIVersion: "latest",
},
handlers,
),
}
svc.Handlers.Unmarshal.PushBack(unmarshalHandler)
svc.Handlers.UnmarshalError.PushBack(unmarshalError)
svc.Handlers.Validate.Clear()
svc.Handlers.Validate.PushBack(validateEndpointHandler)
// Add additional options to the service config
for _, option := range opts {
option(svc.Client)
}
return svc
}
type metadataOutput struct {
Content string
}
func unmarshalHandler(r *request.Request) {
defer r.HTTPResponse.Body.Close()
b, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata respose", err)
}
data := r.Data.(*metadataOutput)
data.Content = string(b)
}
func unmarshalError(r *request.Request) {
defer r.HTTPResponse.Body.Close()
_, err := ioutil.ReadAll(r.HTTPResponse.Body)
if err != nil {
r.Error = awserr.New("SerializationError", "unable to unmarshal EC2 metadata error respose", err)
}
// TODO extract the error...
}
func validateEndpointHandler(r *request.Request) {
if r.ClientInfo.Endpoint == "" {
r.Error = aws.ErrMissingEndpoint
}
}

View File

@@ -0,0 +1,17 @@
package aws
import "github.com/aws/aws-sdk-go/aws/awserr"
var (
// ErrMissingRegion is an error that is returned if region configuration is
// not found.
//
// @readonly
ErrMissingRegion = awserr.New("MissingRegion", "could not find region configuration", nil)
// ErrMissingEndpoint is an error that is returned if an endpoint cannot be
// resolved for a service.
//
// @readonly
ErrMissingEndpoint = awserr.New("MissingEndpoint", "'Endpoint' configuration is required for this service", nil)
)

View File

@@ -0,0 +1,98 @@
package aws
import (
"log"
"os"
)
// A LogLevelType defines the level logging should be performed at. Used to instruct
// the SDK which statements should be logged.
type LogLevelType uint
// LogLevel returns the pointer to a LogLevel. Should be used to workaround
// not being able to take the address of a non-composite literal.
func LogLevel(l LogLevelType) *LogLevelType {
return &l
}
// Value returns the LogLevel value or the default value LogOff if the LogLevel
// is nil. Safe to use on nil value LogLevelTypes.
func (l *LogLevelType) Value() LogLevelType {
if l != nil {
return *l
}
return LogOff
}
// Matches returns true if the v LogLevel is enabled by this LogLevel. Should be
// used with logging sub levels. Is safe to use on nil value LogLevelTypes. If
// LogLevel is nill, will default to LogOff comparison.
func (l *LogLevelType) Matches(v LogLevelType) bool {
c := l.Value()
return c&v == v
}
// AtLeast returns true if this LogLevel is at least high enough to satisfies v.
// Is safe to use on nil value LogLevelTypes. If LogLevel is nill, will default
// to LogOff comparison.
func (l *LogLevelType) AtLeast(v LogLevelType) bool {
c := l.Value()
return c >= v
}
const (
// LogOff states that no logging should be performed by the SDK. This is the
// default state of the SDK, and should be use to disable all logging.
LogOff LogLevelType = iota * 0x1000
// LogDebug state that debug output should be logged by the SDK. This should
// be used to inspect request made and responses received.
LogDebug
)
// Debug Logging Sub Levels
const (
// LogDebugWithSigning states that the SDK should log request signing and
// presigning events. This should be used to log the signing details of
// requests for debugging. Will also enable LogDebug.
LogDebugWithSigning LogLevelType = LogDebug | (1 << iota)
// LogDebugWithHTTPBody states the SDK should log HTTP request and response
// HTTP bodys in addition to the headers and path. This should be used to
// see the body content of requests and responses made while using the SDK
// Will also enable LogDebug.
LogDebugWithHTTPBody
// LogDebugWithRequestRetries states the SDK should log when service requests will
// be retried. This should be used to log when you want to log when service
// requests are being retried. Will also enable LogDebug.
LogDebugWithRequestRetries
// LogDebugWithRequestErrors states the SDK should log when service requests fail
// to build, send, validate, or unmarshal.
LogDebugWithRequestErrors
)
// A Logger is a minimalistic interface for the SDK to log messages to. Should
// be used to provide custom logging writers for the SDK to use.
type Logger interface {
Log(...interface{})
}
// NewDefaultLogger returns a Logger which will write log messages to stdout, and
// use same formatting runes as the stdlib log.Logger
func NewDefaultLogger() Logger {
return &defaultLogger{
logger: log.New(os.Stdout, "", log.LstdFlags),
}
}
// A defaultLogger provides a minimalistic logger satisfying the Logger interface.
type defaultLogger struct {
logger *log.Logger
}
// Log logs the parameters to the stdlib logger. See log.Println.
func (l defaultLogger) Log(args ...interface{}) {
l.logger.Println(args...)
}

View File

@@ -0,0 +1,140 @@
package request
import (
"fmt"
"strings"
)
// A Handlers provides a collection of request handlers for various
// stages of handling requests.
type Handlers struct {
Validate HandlerList
Build HandlerList
Sign HandlerList
Send HandlerList
ValidateResponse HandlerList
Unmarshal HandlerList
UnmarshalMeta HandlerList
UnmarshalError HandlerList
Retry HandlerList
AfterRetry HandlerList
}
// Copy returns of this handler's lists.
func (h *Handlers) Copy() Handlers {
return Handlers{
Validate: h.Validate.copy(),
Build: h.Build.copy(),
Sign: h.Sign.copy(),
Send: h.Send.copy(),
ValidateResponse: h.ValidateResponse.copy(),
Unmarshal: h.Unmarshal.copy(),
UnmarshalError: h.UnmarshalError.copy(),
UnmarshalMeta: h.UnmarshalMeta.copy(),
Retry: h.Retry.copy(),
AfterRetry: h.AfterRetry.copy(),
}
}
// Clear removes callback functions for all handlers
func (h *Handlers) Clear() {
h.Validate.Clear()
h.Build.Clear()
h.Send.Clear()
h.Sign.Clear()
h.Unmarshal.Clear()
h.UnmarshalMeta.Clear()
h.UnmarshalError.Clear()
h.ValidateResponse.Clear()
h.Retry.Clear()
h.AfterRetry.Clear()
}
// A HandlerList manages zero or more handlers in a list.
type HandlerList struct {
list []NamedHandler
}
// A NamedHandler is a struct that contains a name and function callback.
type NamedHandler struct {
Name string
Fn func(*Request)
}
// copy creates a copy of the handler list.
func (l *HandlerList) copy() HandlerList {
var n HandlerList
n.list = append([]NamedHandler{}, l.list...)
return n
}
// Clear clears the handler list.
func (l *HandlerList) Clear() {
l.list = []NamedHandler{}
}
// Len returns the number of handlers in the list.
func (l *HandlerList) Len() int {
return len(l.list)
}
// PushBack pushes handler f to the back of the handler list.
func (l *HandlerList) PushBack(f func(*Request)) {
l.list = append(l.list, NamedHandler{"__anonymous", f})
}
// PushFront pushes handler f to the front of the handler list.
func (l *HandlerList) PushFront(f func(*Request)) {
l.list = append([]NamedHandler{{"__anonymous", f}}, l.list...)
}
// PushBackNamed pushes named handler f to the back of the handler list.
func (l *HandlerList) PushBackNamed(n NamedHandler) {
l.list = append(l.list, n)
}
// PushFrontNamed pushes named handler f to the front of the handler list.
func (l *HandlerList) PushFrontNamed(n NamedHandler) {
l.list = append([]NamedHandler{n}, l.list...)
}
// Remove removes a NamedHandler n
func (l *HandlerList) Remove(n NamedHandler) {
newlist := []NamedHandler{}
for _, m := range l.list {
if m.Name != n.Name {
newlist = append(newlist, m)
}
}
l.list = newlist
}
// Run executes all handlers in the list with a given request object.
func (l *HandlerList) Run(r *Request) {
for _, f := range l.list {
f.Fn(r)
}
}
// MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request
// header. If the extra parameters are provided they will be added as metadata to the
// name/version pair resulting in the following format.
// "name/version (extra0; extra1; ...)"
// The user agent part will be concatenated with this current request's user agent string.
func MakeAddToUserAgentHandler(name, version string, extra ...string) func(*Request) {
ua := fmt.Sprintf("%s/%s", name, version)
if len(extra) > 0 {
ua += fmt.Sprintf(" (%s)", strings.Join(extra, "; "))
}
return func(r *Request) {
AddToUserAgent(r, ua)
}
}
// MakeAddToUserAgentFreeFormHandler adds the input to the User-Agent request header.
// The input string will be concatenated with the current request's user agent string.
func MakeAddToUserAgentFreeFormHandler(s string) func(*Request) {
return func(r *Request) {
AddToUserAgent(r, s)
}
}

View File

@@ -0,0 +1,47 @@
package request_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
)
func TestHandlerList(t *testing.T) {
s := ""
r := &request.Request{}
l := request.HandlerList{}
l.PushBack(func(r *request.Request) {
s += "a"
r.Data = s
})
l.Run(r)
assert.Equal(t, "a", s)
assert.Equal(t, "a", r.Data)
}
func TestMultipleHandlers(t *testing.T) {
r := &request.Request{}
l := request.HandlerList{}
l.PushBack(func(r *request.Request) { r.Data = nil })
l.PushFront(func(r *request.Request) { r.Data = aws.Bool(true) })
l.Run(r)
if r.Data != nil {
t.Error("Expected handler to execute")
}
}
func TestNamedHandlers(t *testing.T) {
l := request.HandlerList{}
named := request.NamedHandler{Name: "Name", Fn: func(r *request.Request) {}}
named2 := request.NamedHandler{Name: "NotName", Fn: func(r *request.Request) {}}
l.PushBackNamed(named)
l.PushBackNamed(named)
l.PushBackNamed(named2)
l.PushBack(func(r *request.Request) {})
assert.Equal(t, 4, l.Len())
l.Remove(named)
assert.Equal(t, 2, l.Len())
}

View File

@@ -0,0 +1,279 @@
package request
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client/metadata"
)
// A Request is the service request to be made.
type Request struct {
Config aws.Config
ClientInfo metadata.ClientInfo
Handlers Handlers
Retryer
Time time.Time
ExpireTime time.Duration
Operation *Operation
HTTPRequest *http.Request
HTTPResponse *http.Response
Body io.ReadSeeker
BodyStart int64 // offset from beginning of Body that the request body starts
Params interface{}
Error error
Data interface{}
RequestID string
RetryCount int
Retryable *bool
RetryDelay time.Duration
built bool
}
// An Operation is the service API operation to be made.
type Operation struct {
Name string
HTTPMethod string
HTTPPath string
*Paginator
}
// Paginator keeps track of pagination configuration for an API operation.
type Paginator struct {
InputTokens []string
OutputTokens []string
LimitToken string
TruncationToken string
}
// New returns a new Request pointer for the service API
// operation and parameters.
//
// Params is any value of input parameters to be the request payload.
// Data is pointer value to an object which the request's response
// payload will be deserialized to.
func New(cfg aws.Config, clientInfo metadata.ClientInfo, handlers Handlers,
retryer Retryer, operation *Operation, params interface{}, data interface{}) *Request {
method := operation.HTTPMethod
if method == "" {
method = "POST"
}
p := operation.HTTPPath
if p == "" {
p = "/"
}
httpReq, _ := http.NewRequest(method, "", nil)
httpReq.URL, _ = url.Parse(clientInfo.Endpoint + p)
r := &Request{
Config: cfg,
ClientInfo: clientInfo,
Handlers: handlers.Copy(),
Retryer: retryer,
Time: time.Now(),
ExpireTime: 0,
Operation: operation,
HTTPRequest: httpReq,
Body: nil,
Params: params,
Error: nil,
Data: data,
}
r.SetBufferBody([]byte{})
return r
}
// WillRetry returns if the request's can be retried.
func (r *Request) WillRetry() bool {
return r.Error != nil && aws.BoolValue(r.Retryable) && r.RetryCount < r.MaxRetries()
}
// ParamsFilled returns if the request's parameters have been populated
// and the parameters are valid. False is returned if no parameters are
// provided or invalid.
func (r *Request) ParamsFilled() bool {
return r.Params != nil && reflect.ValueOf(r.Params).Elem().IsValid()
}
// DataFilled returns true if the request's data for response deserialization
// target has been set and is a valid. False is returned if data is not
// set, or is invalid.
func (r *Request) DataFilled() bool {
return r.Data != nil && reflect.ValueOf(r.Data).Elem().IsValid()
}
// SetBufferBody will set the request's body bytes that will be sent to
// the service API.
func (r *Request) SetBufferBody(buf []byte) {
r.SetReaderBody(bytes.NewReader(buf))
}
// SetStringBody sets the body of the request to be backed by a string.
func (r *Request) SetStringBody(s string) {
r.SetReaderBody(strings.NewReader(s))
}
// SetReaderBody will set the request's body reader.
func (r *Request) SetReaderBody(reader io.ReadSeeker) {
r.HTTPRequest.Body = ioutil.NopCloser(reader)
r.Body = reader
}
// Presign returns the request's signed URL. Error will be returned
// if the signing fails.
func (r *Request) Presign(expireTime time.Duration) (string, error) {
r.ExpireTime = expireTime
r.Sign()
if r.Error != nil {
return "", r.Error
}
return r.HTTPRequest.URL.String(), nil
}
func debugLogReqError(r *Request, stage string, retrying bool, err error) {
if !r.Config.LogLevel.Matches(aws.LogDebugWithRequestErrors) {
return
}
retryStr := "not retrying"
if retrying {
retryStr = "will retry"
}
r.Config.Logger.Log(fmt.Sprintf("DEBUG: %s %s/%s failed, %s, error %v",
stage, r.ClientInfo.ServiceName, r.Operation.Name, retryStr, err))
}
// Build will build the request's object so it can be signed and sent
// to the service. Build will also validate all the request's parameters.
// Anny additional build Handlers set on this request will be run
// in the order they were set.
//
// The request will only be built once. Multiple calls to build will have
// no effect.
//
// If any Validate or Build errors occur the build will stop and the error
// which occurred will be returned.
func (r *Request) Build() error {
if !r.built {
r.Error = nil
r.Handlers.Validate.Run(r)
if r.Error != nil {
debugLogReqError(r, "Validate Request", false, r.Error)
return r.Error
}
r.Handlers.Build.Run(r)
r.built = true
}
return r.Error
}
// Sign will sign the request retuning error if errors are encountered.
//
// Send will build the request prior to signing. All Sign Handlers will
// be executed in the order they were set.
func (r *Request) Sign() error {
r.Build()
if r.Error != nil {
debugLogReqError(r, "Build Request", false, r.Error)
return r.Error
}
r.Handlers.Sign.Run(r)
return r.Error
}
// Send will send the request returning error if errors are encountered.
//
// Send will sign the request prior to sending. All Send Handlers will
// be executed in the order they were set.
func (r *Request) Send() error {
for {
r.Sign()
if r.Error != nil {
return r.Error
}
if aws.BoolValue(r.Retryable) {
if r.Config.LogLevel.Matches(aws.LogDebugWithRequestRetries) {
r.Config.Logger.Log(fmt.Sprintf("DEBUG: Retrying Request %s/%s, attempt %d",
r.ClientInfo.ServiceName, r.Operation.Name, r.RetryCount))
}
// Re-seek the body back to the original point in for a retry so that
// send will send the body's contents again in the upcoming request.
r.Body.Seek(r.BodyStart, 0)
r.HTTPRequest.Body = ioutil.NopCloser(r.Body)
}
r.Retryable = nil
r.Handlers.Send.Run(r)
if r.Error != nil {
err := r.Error
r.Handlers.Retry.Run(r)
r.Handlers.AfterRetry.Run(r)
if r.Error != nil {
debugLogReqError(r, "Send Request", false, r.Error)
return r.Error
}
debugLogReqError(r, "Send Request", true, err)
continue
}
r.Handlers.UnmarshalMeta.Run(r)
r.Handlers.ValidateResponse.Run(r)
if r.Error != nil {
err := r.Error
r.Handlers.UnmarshalError.Run(r)
r.Handlers.Retry.Run(r)
r.Handlers.AfterRetry.Run(r)
if r.Error != nil {
debugLogReqError(r, "Validate Response", false, r.Error)
return r.Error
}
debugLogReqError(r, "Validate Response", true, err)
continue
}
r.Handlers.Unmarshal.Run(r)
if r.Error != nil {
err := r.Error
r.Handlers.Retry.Run(r)
r.Handlers.AfterRetry.Run(r)
if r.Error != nil {
debugLogReqError(r, "Unmarshal Response", false, r.Error)
return r.Error
}
debugLogReqError(r, "Unmarshal Response", true, err)
continue
}
break
}
return nil
}
// AddToUserAgent adds the string to the end of the request's current user agent.
func AddToUserAgent(r *Request, s string) {
curUA := r.HTTPRequest.Header.Get("User-Agent")
if len(curUA) > 0 {
s = curUA + " " + s
}
r.HTTPRequest.Header.Set("User-Agent", s)
}

View File

@@ -0,0 +1,104 @@
package request
import (
"reflect"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
)
//type Paginater interface {
// HasNextPage() bool
// NextPage() *Request
// EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error
//}
// HasNextPage returns true if this request has more pages of data available.
func (r *Request) HasNextPage() bool {
return len(r.nextPageTokens()) > 0
}
// nextPageTokens returns the tokens to use when asking for the next page of
// data.
func (r *Request) nextPageTokens() []interface{} {
if r.Operation.Paginator == nil {
return nil
}
if r.Operation.TruncationToken != "" {
tr, _ := awsutil.ValuesAtPath(r.Data, r.Operation.TruncationToken)
if len(tr) == 0 {
return nil
}
switch v := tr[0].(type) {
case *bool:
if !aws.BoolValue(v) {
return nil
}
case bool:
if v == false {
return nil
}
}
}
tokens := []interface{}{}
tokenAdded := false
for _, outToken := range r.Operation.OutputTokens {
v, _ := awsutil.ValuesAtPath(r.Data, outToken)
if len(v) > 0 {
tokens = append(tokens, v[0])
tokenAdded = true
} else {
tokens = append(tokens, nil)
}
}
if !tokenAdded {
return nil
}
return tokens
}
// NextPage returns a new Request that can be executed to return the next
// page of result data. Call .Send() on this request to execute it.
func (r *Request) NextPage() *Request {
tokens := r.nextPageTokens()
if len(tokens) == 0 {
return nil
}
data := reflect.New(reflect.TypeOf(r.Data).Elem()).Interface()
nr := New(r.Config, r.ClientInfo, r.Handlers, r.Retryer, r.Operation, awsutil.CopyOf(r.Params), data)
for i, intok := range nr.Operation.InputTokens {
awsutil.SetValueAtPath(nr.Params, intok, tokens[i])
}
return nr
}
// EachPage iterates over each page of a paginated request object. The fn
// parameter should be a function with the following sample signature:
//
// func(page *T, lastPage bool) bool {
// return true // return false to stop iterating
// }
//
// Where "T" is the structure type matching the output structure of the given
// operation. For example, a request object generated by
// DynamoDB.ListTablesRequest() would expect to see dynamodb.ListTablesOutput
// as the structure "T". The lastPage value represents whether the page is
// the last page of data or not. The return value of this function should
// return true to keep iterating or false to stop.
func (r *Request) EachPage(fn func(data interface{}, isLastPage bool) (shouldContinue bool)) error {
for page := r; page != nil; page = page.NextPage() {
if err := page.Send(); err != nil {
return err
}
if getNextPage := fn(page.Data, !page.HasNextPage()); !getNextPage {
return page.Error
}
}
return nil
}

View File

@@ -0,0 +1,455 @@
package request_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/awstesting/unit"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/s3"
)
// Use DynamoDB methods for simplicity
func TestPaginationQueryPage(t *testing.T) {
db := dynamodb.New(unit.Session)
tokens, pages, numPages, gotToEnd := []map[string]*dynamodb.AttributeValue{}, []map[string]*dynamodb.AttributeValue{}, 0, false
reqNum := 0
resps := []*dynamodb.QueryOutput{
{
LastEvaluatedKey: map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key1")}},
Count: aws.Int64(1),
Items: []map[string]*dynamodb.AttributeValue{
map[string]*dynamodb.AttributeValue{
"key": {S: aws.String("key1")},
},
},
},
{
LastEvaluatedKey: map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key2")}},
Count: aws.Int64(1),
Items: []map[string]*dynamodb.AttributeValue{
map[string]*dynamodb.AttributeValue{
"key": {S: aws.String("key2")},
},
},
},
{
LastEvaluatedKey: map[string]*dynamodb.AttributeValue{},
Count: aws.Int64(1),
Items: []map[string]*dynamodb.AttributeValue{
map[string]*dynamodb.AttributeValue{
"key": {S: aws.String("key3")},
},
},
},
}
db.Handlers.Send.Clear() // mock sending
db.Handlers.Unmarshal.Clear()
db.Handlers.UnmarshalMeta.Clear()
db.Handlers.ValidateResponse.Clear()
db.Handlers.Build.PushBack(func(r *request.Request) {
in := r.Params.(*dynamodb.QueryInput)
if in == nil {
tokens = append(tokens, nil)
} else if len(in.ExclusiveStartKey) != 0 {
tokens = append(tokens, in.ExclusiveStartKey)
}
})
db.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = resps[reqNum]
reqNum++
})
params := &dynamodb.QueryInput{
Limit: aws.Int64(2),
TableName: aws.String("tablename"),
}
err := db.QueryPages(params, func(p *dynamodb.QueryOutput, last bool) bool {
numPages++
for _, item := range p.Items {
pages = append(pages, item)
}
if last {
if gotToEnd {
assert.Fail(t, "last=true happened twice")
}
gotToEnd = true
}
return true
})
assert.Nil(t, err)
assert.Equal(t,
[]map[string]*dynamodb.AttributeValue{
map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key1")}},
map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key2")}},
}, tokens)
assert.Equal(t,
[]map[string]*dynamodb.AttributeValue{
map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key1")}},
map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key2")}},
map[string]*dynamodb.AttributeValue{"key": {S: aws.String("key3")}},
}, pages)
assert.Equal(t, 3, numPages)
assert.True(t, gotToEnd)
assert.Nil(t, params.ExclusiveStartKey)
}
// Use DynamoDB methods for simplicity
func TestPagination(t *testing.T) {
db := dynamodb.New(unit.Session)
tokens, pages, numPages, gotToEnd := []string{}, []string{}, 0, false
reqNum := 0
resps := []*dynamodb.ListTablesOutput{
{TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")},
{TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")},
{TableNames: []*string{aws.String("Table5")}},
}
db.Handlers.Send.Clear() // mock sending
db.Handlers.Unmarshal.Clear()
db.Handlers.UnmarshalMeta.Clear()
db.Handlers.ValidateResponse.Clear()
db.Handlers.Build.PushBack(func(r *request.Request) {
in := r.Params.(*dynamodb.ListTablesInput)
if in == nil {
tokens = append(tokens, "")
} else if in.ExclusiveStartTableName != nil {
tokens = append(tokens, *in.ExclusiveStartTableName)
}
})
db.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = resps[reqNum]
reqNum++
})
params := &dynamodb.ListTablesInput{Limit: aws.Int64(2)}
err := db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, last bool) bool {
numPages++
for _, t := range p.TableNames {
pages = append(pages, *t)
}
if last {
if gotToEnd {
assert.Fail(t, "last=true happened twice")
}
gotToEnd = true
}
return true
})
assert.Equal(t, []string{"Table2", "Table4"}, tokens)
assert.Equal(t, []string{"Table1", "Table2", "Table3", "Table4", "Table5"}, pages)
assert.Equal(t, 3, numPages)
assert.True(t, gotToEnd)
assert.Nil(t, err)
assert.Nil(t, params.ExclusiveStartTableName)
}
// Use DynamoDB methods for simplicity
func TestPaginationEachPage(t *testing.T) {
db := dynamodb.New(unit.Session)
tokens, pages, numPages, gotToEnd := []string{}, []string{}, 0, false
reqNum := 0
resps := []*dynamodb.ListTablesOutput{
{TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")},
{TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")},
{TableNames: []*string{aws.String("Table5")}},
}
db.Handlers.Send.Clear() // mock sending
db.Handlers.Unmarshal.Clear()
db.Handlers.UnmarshalMeta.Clear()
db.Handlers.ValidateResponse.Clear()
db.Handlers.Build.PushBack(func(r *request.Request) {
in := r.Params.(*dynamodb.ListTablesInput)
if in == nil {
tokens = append(tokens, "")
} else if in.ExclusiveStartTableName != nil {
tokens = append(tokens, *in.ExclusiveStartTableName)
}
})
db.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = resps[reqNum]
reqNum++
})
params := &dynamodb.ListTablesInput{Limit: aws.Int64(2)}
req, _ := db.ListTablesRequest(params)
err := req.EachPage(func(p interface{}, last bool) bool {
numPages++
for _, t := range p.(*dynamodb.ListTablesOutput).TableNames {
pages = append(pages, *t)
}
if last {
if gotToEnd {
assert.Fail(t, "last=true happened twice")
}
gotToEnd = true
}
return true
})
assert.Equal(t, []string{"Table2", "Table4"}, tokens)
assert.Equal(t, []string{"Table1", "Table2", "Table3", "Table4", "Table5"}, pages)
assert.Equal(t, 3, numPages)
assert.True(t, gotToEnd)
assert.Nil(t, err)
}
// Use DynamoDB methods for simplicity
func TestPaginationEarlyExit(t *testing.T) {
db := dynamodb.New(unit.Session)
numPages, gotToEnd := 0, false
reqNum := 0
resps := []*dynamodb.ListTablesOutput{
{TableNames: []*string{aws.String("Table1"), aws.String("Table2")}, LastEvaluatedTableName: aws.String("Table2")},
{TableNames: []*string{aws.String("Table3"), aws.String("Table4")}, LastEvaluatedTableName: aws.String("Table4")},
{TableNames: []*string{aws.String("Table5")}},
}
db.Handlers.Send.Clear() // mock sending
db.Handlers.Unmarshal.Clear()
db.Handlers.UnmarshalMeta.Clear()
db.Handlers.ValidateResponse.Clear()
db.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = resps[reqNum]
reqNum++
})
params := &dynamodb.ListTablesInput{Limit: aws.Int64(2)}
err := db.ListTablesPages(params, func(p *dynamodb.ListTablesOutput, last bool) bool {
numPages++
if numPages == 2 {
return false
}
if last {
if gotToEnd {
assert.Fail(t, "last=true happened twice")
}
gotToEnd = true
}
return true
})
assert.Equal(t, 2, numPages)
assert.False(t, gotToEnd)
assert.Nil(t, err)
}
func TestSkipPagination(t *testing.T) {
client := s3.New(unit.Session)
client.Handlers.Send.Clear() // mock sending
client.Handlers.Unmarshal.Clear()
client.Handlers.UnmarshalMeta.Clear()
client.Handlers.ValidateResponse.Clear()
client.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = &s3.HeadBucketOutput{}
})
req, _ := client.HeadBucketRequest(&s3.HeadBucketInput{Bucket: aws.String("bucket")})
numPages, gotToEnd := 0, false
req.EachPage(func(p interface{}, last bool) bool {
numPages++
if last {
gotToEnd = true
}
return true
})
assert.Equal(t, 1, numPages)
assert.True(t, gotToEnd)
}
// Use S3 for simplicity
func TestPaginationTruncation(t *testing.T) {
client := s3.New(unit.Session)
reqNum := 0
resps := []*s3.ListObjectsOutput{
{IsTruncated: aws.Bool(true), Contents: []*s3.Object{{Key: aws.String("Key1")}}},
{IsTruncated: aws.Bool(true), Contents: []*s3.Object{{Key: aws.String("Key2")}}},
{IsTruncated: aws.Bool(false), Contents: []*s3.Object{{Key: aws.String("Key3")}}},
{IsTruncated: aws.Bool(true), Contents: []*s3.Object{{Key: aws.String("Key4")}}},
}
client.Handlers.Send.Clear() // mock sending
client.Handlers.Unmarshal.Clear()
client.Handlers.UnmarshalMeta.Clear()
client.Handlers.ValidateResponse.Clear()
client.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = resps[reqNum]
reqNum++
})
params := &s3.ListObjectsInput{Bucket: aws.String("bucket")}
results := []string{}
err := client.ListObjectsPages(params, func(p *s3.ListObjectsOutput, last bool) bool {
results = append(results, *p.Contents[0].Key)
return true
})
assert.Equal(t, []string{"Key1", "Key2", "Key3"}, results)
assert.Nil(t, err)
// Try again without truncation token at all
reqNum = 0
resps[1].IsTruncated = nil
resps[2].IsTruncated = aws.Bool(true)
results = []string{}
err = client.ListObjectsPages(params, func(p *s3.ListObjectsOutput, last bool) bool {
results = append(results, *p.Contents[0].Key)
return true
})
assert.Equal(t, []string{"Key1", "Key2"}, results)
assert.Nil(t, err)
}
func TestPaginationNilToken(t *testing.T) {
client := route53.New(unit.Session)
reqNum := 0
resps := []*route53.ListResourceRecordSetsOutput{
{
ResourceRecordSets: []*route53.ResourceRecordSet{
{Name: aws.String("first.example.com.")},
},
IsTruncated: aws.Bool(true),
NextRecordName: aws.String("second.example.com."),
NextRecordType: aws.String("MX"),
NextRecordIdentifier: aws.String("second"),
MaxItems: aws.String("1"),
},
{
ResourceRecordSets: []*route53.ResourceRecordSet{
{Name: aws.String("second.example.com.")},
},
IsTruncated: aws.Bool(true),
NextRecordName: aws.String("third.example.com."),
NextRecordType: aws.String("MX"),
MaxItems: aws.String("1"),
},
{
ResourceRecordSets: []*route53.ResourceRecordSet{
{Name: aws.String("third.example.com.")},
},
IsTruncated: aws.Bool(false),
MaxItems: aws.String("1"),
},
}
client.Handlers.Send.Clear() // mock sending
client.Handlers.Unmarshal.Clear()
client.Handlers.UnmarshalMeta.Clear()
client.Handlers.ValidateResponse.Clear()
idents := []string{}
client.Handlers.Build.PushBack(func(r *request.Request) {
p := r.Params.(*route53.ListResourceRecordSetsInput)
idents = append(idents, aws.StringValue(p.StartRecordIdentifier))
})
client.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = resps[reqNum]
reqNum++
})
params := &route53.ListResourceRecordSetsInput{
HostedZoneId: aws.String("id-zone"),
}
results := []string{}
err := client.ListResourceRecordSetsPages(params, func(p *route53.ListResourceRecordSetsOutput, last bool) bool {
results = append(results, *p.ResourceRecordSets[0].Name)
return true
})
assert.NoError(t, err)
assert.Equal(t, []string{"", "second", ""}, idents)
assert.Equal(t, []string{"first.example.com.", "second.example.com.", "third.example.com."}, results)
}
// Benchmarks
var benchResps = []*dynamodb.ListTablesOutput{
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE"), aws.String("NXT")}, LastEvaluatedTableName: aws.String("NXT")},
{TableNames: []*string{aws.String("TABLE")}},
}
var benchDb = func() *dynamodb.DynamoDB {
db := dynamodb.New(unit.Session)
db.Handlers.Send.Clear() // mock sending
db.Handlers.Unmarshal.Clear()
db.Handlers.UnmarshalMeta.Clear()
db.Handlers.ValidateResponse.Clear()
return db
}
func BenchmarkCodegenIterator(b *testing.B) {
reqNum := 0
db := benchDb()
db.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = benchResps[reqNum]
reqNum++
})
input := &dynamodb.ListTablesInput{Limit: aws.Int64(2)}
iter := func(fn func(*dynamodb.ListTablesOutput, bool) bool) error {
page, _ := db.ListTablesRequest(input)
for ; page != nil; page = page.NextPage() {
page.Send()
out := page.Data.(*dynamodb.ListTablesOutput)
if result := fn(out, !page.HasNextPage()); page.Error != nil || !result {
return page.Error
}
}
return nil
}
for i := 0; i < b.N; i++ {
reqNum = 0
iter(func(p *dynamodb.ListTablesOutput, last bool) bool {
return true
})
}
}
func BenchmarkEachPageIterator(b *testing.B) {
reqNum := 0
db := benchDb()
db.Handlers.Unmarshal.PushBack(func(r *request.Request) {
r.Data = benchResps[reqNum]
reqNum++
})
input := &dynamodb.ListTablesInput{Limit: aws.Int64(2)}
for i := 0; i < b.N; i++ {
reqNum = 0
req, _ := db.ListTablesRequest(input)
req.EachPage(func(p interface{}, last bool) bool {
return true
})
}
}

View File

@@ -0,0 +1,261 @@
package request_test
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"runtime"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/awstesting"
)
type testData struct {
Data string
}
func body(str string) io.ReadCloser {
return ioutil.NopCloser(bytes.NewReader([]byte(str)))
}
func unmarshal(req *request.Request) {
defer req.HTTPResponse.Body.Close()
if req.Data != nil {
json.NewDecoder(req.HTTPResponse.Body).Decode(req.Data)
}
return
}
func unmarshalError(req *request.Request) {
bodyBytes, err := ioutil.ReadAll(req.HTTPResponse.Body)
if err != nil {
req.Error = awserr.New("UnmarshaleError", req.HTTPResponse.Status, err)
return
}
if len(bodyBytes) == 0 {
req.Error = awserr.NewRequestFailure(
awserr.New("UnmarshaleError", req.HTTPResponse.Status, fmt.Errorf("empty body")),
req.HTTPResponse.StatusCode,
"",
)
return
}
var jsonErr jsonErrorResponse
if err := json.Unmarshal(bodyBytes, &jsonErr); err != nil {
req.Error = awserr.New("UnmarshaleError", "JSON unmarshal", err)
return
}
req.Error = awserr.NewRequestFailure(
awserr.New(jsonErr.Code, jsonErr.Message, nil),
req.HTTPResponse.StatusCode,
"",
)
}
type jsonErrorResponse struct {
Code string `json:"__type"`
Message string `json:"message"`
}
// test that retries occur for 5xx status codes
func TestRequestRecoverRetry5xx(t *testing.T) {
reqNum := 0
reqs := []http.Response{
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
{StatusCode: 501, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
{StatusCode: 200, Body: body(`{"data":"valid"}`)},
}
s := awstesting.NewClient(aws.NewConfig().WithMaxRetries(10))
s.Handlers.Validate.Clear()
s.Handlers.Unmarshal.PushBack(unmarshal)
s.Handlers.UnmarshalError.PushBack(unmarshalError)
s.Handlers.Send.Clear() // mock sending
s.Handlers.Send.PushBack(func(r *request.Request) {
r.HTTPResponse = &reqs[reqNum]
reqNum++
})
out := &testData{}
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
err := r.Send()
assert.Nil(t, err)
assert.Equal(t, 2, int(r.RetryCount))
assert.Equal(t, "valid", out.Data)
}
// test that retries occur for 4xx status codes with a response type that can be retried - see `shouldRetry`
func TestRequestRecoverRetry4xxRetryable(t *testing.T) {
reqNum := 0
reqs := []http.Response{
{StatusCode: 400, Body: body(`{"__type":"Throttling","message":"Rate exceeded."}`)},
{StatusCode: 429, Body: body(`{"__type":"ProvisionedThroughputExceededException","message":"Rate exceeded."}`)},
{StatusCode: 200, Body: body(`{"data":"valid"}`)},
}
s := awstesting.NewClient(aws.NewConfig().WithMaxRetries(10))
s.Handlers.Validate.Clear()
s.Handlers.Unmarshal.PushBack(unmarshal)
s.Handlers.UnmarshalError.PushBack(unmarshalError)
s.Handlers.Send.Clear() // mock sending
s.Handlers.Send.PushBack(func(r *request.Request) {
r.HTTPResponse = &reqs[reqNum]
reqNum++
})
out := &testData{}
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
err := r.Send()
assert.Nil(t, err)
assert.Equal(t, 2, int(r.RetryCount))
assert.Equal(t, "valid", out.Data)
}
// test that retries don't occur for 4xx status codes with a response type that can't be retried
func TestRequest4xxUnretryable(t *testing.T) {
s := awstesting.NewClient(aws.NewConfig().WithMaxRetries(10))
s.Handlers.Validate.Clear()
s.Handlers.Unmarshal.PushBack(unmarshal)
s.Handlers.UnmarshalError.PushBack(unmarshalError)
s.Handlers.Send.Clear() // mock sending
s.Handlers.Send.PushBack(func(r *request.Request) {
r.HTTPResponse = &http.Response{StatusCode: 401, Body: body(`{"__type":"SignatureDoesNotMatch","message":"Signature does not match."}`)}
})
out := &testData{}
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
err := r.Send()
assert.NotNil(t, err)
if e, ok := err.(awserr.RequestFailure); ok {
assert.Equal(t, 401, e.StatusCode())
} else {
assert.Fail(t, "Expected error to be a service failure")
}
assert.Equal(t, "SignatureDoesNotMatch", err.(awserr.Error).Code())
assert.Equal(t, "Signature does not match.", err.(awserr.Error).Message())
assert.Equal(t, 0, int(r.RetryCount))
}
func TestRequestExhaustRetries(t *testing.T) {
delays := []time.Duration{}
sleepDelay := func(delay time.Duration) {
delays = append(delays, delay)
}
reqNum := 0
reqs := []http.Response{
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
{StatusCode: 500, Body: body(`{"__type":"UnknownError","message":"An error occurred."}`)},
}
s := awstesting.NewClient(aws.NewConfig().WithSleepDelay(sleepDelay))
s.Handlers.Validate.Clear()
s.Handlers.Unmarshal.PushBack(unmarshal)
s.Handlers.UnmarshalError.PushBack(unmarshalError)
s.Handlers.Send.Clear() // mock sending
s.Handlers.Send.PushBack(func(r *request.Request) {
r.HTTPResponse = &reqs[reqNum]
reqNum++
})
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, nil)
err := r.Send()
assert.NotNil(t, err)
if e, ok := err.(awserr.RequestFailure); ok {
assert.Equal(t, 500, e.StatusCode())
} else {
assert.Fail(t, "Expected error to be a service failure")
}
assert.Equal(t, "UnknownError", err.(awserr.Error).Code())
assert.Equal(t, "An error occurred.", err.(awserr.Error).Message())
assert.Equal(t, 3, int(r.RetryCount))
expectDelays := []struct{ min, max time.Duration }{{30, 59}, {60, 118}, {120, 236}}
for i, v := range delays {
min := expectDelays[i].min * time.Millisecond
max := expectDelays[i].max * time.Millisecond
assert.True(t, min <= v && v <= max,
"Expect delay to be within range, i:%d, v:%s, min:%s, max:%s", i, v, min, max)
}
}
// test that the request is retried after the credentials are expired.
func TestRequestRecoverExpiredCreds(t *testing.T) {
reqNum := 0
reqs := []http.Response{
{StatusCode: 400, Body: body(`{"__type":"ExpiredTokenException","message":"expired token"}`)},
{StatusCode: 200, Body: body(`{"data":"valid"}`)},
}
s := awstesting.NewClient(&aws.Config{MaxRetries: aws.Int(10), Credentials: credentials.NewStaticCredentials("AKID", "SECRET", "")})
s.Handlers.Validate.Clear()
s.Handlers.Unmarshal.PushBack(unmarshal)
s.Handlers.UnmarshalError.PushBack(unmarshalError)
credExpiredBeforeRetry := false
credExpiredAfterRetry := false
s.Handlers.AfterRetry.PushBack(func(r *request.Request) {
credExpiredAfterRetry = r.Config.Credentials.IsExpired()
})
s.Handlers.Sign.Clear()
s.Handlers.Sign.PushBack(func(r *request.Request) {
r.Config.Credentials.Get()
})
s.Handlers.Send.Clear() // mock sending
s.Handlers.Send.PushBack(func(r *request.Request) {
r.HTTPResponse = &reqs[reqNum]
reqNum++
})
out := &testData{}
r := s.NewRequest(&request.Operation{Name: "Operation"}, nil, out)
err := r.Send()
assert.Nil(t, err)
assert.False(t, credExpiredBeforeRetry, "Expect valid creds before retry check")
assert.True(t, credExpiredAfterRetry, "Expect expired creds after retry check")
assert.False(t, s.Config.Credentials.IsExpired(), "Expect valid creds after cred expired recovery")
assert.Equal(t, 1, int(r.RetryCount))
assert.Equal(t, "valid", out.Data)
}
func TestMakeAddtoUserAgentHandler(t *testing.T) {
fn := request.MakeAddToUserAgentHandler("name", "version", "extra1", "extra2")
r := &request.Request{HTTPRequest: &http.Request{Header: http.Header{}}}
r.HTTPRequest.Header.Set("User-Agent", "foo/bar")
fn(r)
assert.Equal(t, "foo/bar name/version (extra1; extra2)", r.HTTPRequest.Header.Get("User-Agent"))
}
func TestMakeAddtoUserAgentFreeFormHandler(t *testing.T) {
fn := request.MakeAddToUserAgentFreeFormHandler("name/version (extra1; extra2)")
r := &request.Request{HTTPRequest: &http.Request{Header: http.Header{}}}
r.HTTPRequest.Header.Set("User-Agent", "foo/bar")
fn(r)
assert.Equal(t, "foo/bar name/version (extra1; extra2)", r.HTTPRequest.Header.Get("User-Agent"))
}
func TestRequestUserAgent(t *testing.T) {
s := awstesting.NewClient(&aws.Config{Region: aws.String("us-east-1")})
// s.Handlers.Validate.Clear()
req := s.NewRequest(&request.Operation{Name: "Operation"}, nil, &testData{})
req.HTTPRequest.Header.Set("User-Agent", "foo/bar")
assert.NoError(t, req.Build())
expectUA := fmt.Sprintf("foo/bar %s/%s (%s; %s; %s)",
aws.SDKName, aws.SDKVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH)
assert.Equal(t, expectUA, req.HTTPRequest.Header.Get("User-Agent"))
}

View File

@@ -0,0 +1,82 @@
package request
import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
)
// Retryer is an interface to control retry logic for a given service.
// The default implementation used by most services is the service.DefaultRetryer
// structure, which contains basic retry logic using exponential backoff.
type Retryer interface {
RetryRules(*Request) time.Duration
ShouldRetry(*Request) bool
MaxRetries() int
}
// WithRetryer sets a config Retryer value to the given Config returning it
// for chaining.
func WithRetryer(cfg *aws.Config, retryer Retryer) *aws.Config {
cfg.Retryer = retryer
return cfg
}
// retryableCodes is a collection of service response codes which are retry-able
// without any further action.
var retryableCodes = map[string]struct{}{
"RequestError": {},
"RequestTimeout": {},
"ProvisionedThroughputExceededException": {},
"Throttling": {},
"ThrottlingException": {},
"RequestLimitExceeded": {},
"RequestThrottled": {},
"LimitExceededException": {}, // Deleting 10+ DynamoDb tables at once
"TooManyRequestsException": {}, // Lambda functions
}
// credsExpiredCodes is a collection of error codes which signify the credentials
// need to be refreshed. Expired tokens require refreshing of credentials, and
// resigning before the request can be retried.
var credsExpiredCodes = map[string]struct{}{
"ExpiredToken": {},
"ExpiredTokenException": {},
"RequestExpired": {}, // EC2 Only
}
func isCodeRetryable(code string) bool {
if _, ok := retryableCodes[code]; ok {
return true
}
return isCodeExpiredCreds(code)
}
func isCodeExpiredCreds(code string) bool {
_, ok := credsExpiredCodes[code]
return ok
}
// IsErrorRetryable returns whether the error is retryable, based on its Code.
// Returns false if the request has no Error set.
func (r *Request) IsErrorRetryable() bool {
if r.Error != nil {
if err, ok := r.Error.(awserr.Error); ok {
return isCodeRetryable(err.Code())
}
}
return false
}
// IsErrorExpired returns whether the error code is a credential expiry error.
// Returns false if the request has no Error set.
func (r *Request) IsErrorExpired() bool {
if r.Error != nil {
if err, ok := r.Error.(awserr.Error); ok {
return isCodeExpiredCreds(err.Code())
}
}
return false
}

View File

@@ -0,0 +1,105 @@
// Package session provides a way to create service clients with shared configuration
// and handlers.
package session
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/client"
"github.com/aws/aws-sdk-go/aws/corehandlers"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/private/endpoints"
)
// A Session provides a central location to create service clients from and
// store configurations and request handlers for those services.
//
// Sessions are safe to create service clients concurrently, but it is not safe
// to mutate the session concurrently.
type Session struct {
Config *aws.Config
Handlers request.Handlers
}
// New creates a new instance of the handlers merging in the provided Configs
// on top of the SDK's default configurations. Once the session is created it
// can be mutated to modify Configs or Handlers. The session is safe to be read
// concurrently, but it should not be written to concurrently.
//
// Example:
// // Create a session with the default config and request handlers.
// sess := session.New()
//
// // Create a session with a custom region
// sess := session.New(&aws.Config{Region: aws.String("us-east-1")})
//
// // Create a session, and add additional handlers for all service
// // clients created with the session to inherit. Adds logging handler.
// sess := session.New()
// sess.Handlers.Send.PushFront(func(r *request.Request) {
// // Log every request made and its payload
// logger.Println("Request: %s/%s, Payload: %s", r.ClientInfo.ServiceName, r.Operation, r.Params)
// })
//
// // Create a S3 client instance from a session
// sess := session.New()
// svc := s3.New(sess)
func New(cfgs ...*aws.Config) *Session {
def := defaults.Get()
s := &Session{
Config: def.Config,
Handlers: def.Handlers,
}
s.Config.MergeIn(cfgs...)
initHandlers(s)
return s
}
func initHandlers(s *Session) {
// Add the Validate parameter handler if it is not disabled.
s.Handlers.Validate.Remove(corehandlers.ValidateParametersHandler)
if !aws.BoolValue(s.Config.DisableParamValidation) {
s.Handlers.Validate.PushBackNamed(corehandlers.ValidateParametersHandler)
}
}
// Copy creates and returns a copy of the current session, coping the config
// and handlers. If any additional configs are provided they will be merged
// on top of the session's copied config.
//
// Example:
// // Create a copy of the current session, configured for the us-west-2 region.
// sess.Copy(&aws.Config{Region: aws.String("us-west-2"})
func (s *Session) Copy(cfgs ...*aws.Config) *Session {
newSession := &Session{
Config: s.Config.Copy(cfgs...),
Handlers: s.Handlers.Copy(),
}
initHandlers(newSession)
return newSession
}
// ClientConfig satisfies the client.ConfigProvider interface and is used to
// configure the service client instances. Passing the Session to the service
// client's constructor (New) will use this method to configure the client.
//
// Example:
// sess := session.New()
// s3.New(sess)
func (s *Session) ClientConfig(serviceName string, cfgs ...*aws.Config) client.Config {
s = s.Copy(cfgs...)
endpoint, signingRegion := endpoints.NormalizeEndpoint(
aws.StringValue(s.Config.Endpoint), serviceName,
aws.StringValue(s.Config.Region), aws.BoolValue(s.Config.DisableSSL))
return client.Config{
Config: s.Config,
Handlers: s.Handlers,
Endpoint: endpoint,
SigningRegion: signingRegion,
}
}

View File

@@ -0,0 +1,20 @@
package session_test
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
)
func TestNewDefaultSession(t *testing.T) {
s := session.New(&aws.Config{Region: aws.String("region")})
assert.Equal(t, "region", *s.Config.Region)
assert.Equal(t, http.DefaultClient, s.Config.HTTPClient)
assert.NotNil(t, s.Config.Logger)
assert.Equal(t, aws.LogOff, *s.Config.LogLevel)
}

View File

@@ -0,0 +1,88 @@
package aws
import (
"io"
"sync"
)
// ReadSeekCloser wraps a io.Reader returning a ReaderSeekerCloser
func ReadSeekCloser(r io.Reader) ReaderSeekerCloser {
return ReaderSeekerCloser{r}
}
// ReaderSeekerCloser represents a reader that can also delegate io.Seeker and
// io.Closer interfaces to the underlying object if they are available.
type ReaderSeekerCloser struct {
r io.Reader
}
// Read reads from the reader up to size of p. The number of bytes read, and
// error if it occurred will be returned.
//
// If the reader is not an io.Reader zero bytes read, and nil error will be returned.
//
// Performs the same functionality as io.Reader Read
func (r ReaderSeekerCloser) Read(p []byte) (int, error) {
switch t := r.r.(type) {
case io.Reader:
return t.Read(p)
}
return 0, nil
}
// Seek sets the offset for the next Read to offset, interpreted according to
// whence: 0 means relative to the origin of the file, 1 means relative to the
// current offset, and 2 means relative to the end. Seek returns the new offset
// and an error, if any.
//
// If the ReaderSeekerCloser is not an io.Seeker nothing will be done.
func (r ReaderSeekerCloser) Seek(offset int64, whence int) (int64, error) {
switch t := r.r.(type) {
case io.Seeker:
return t.Seek(offset, whence)
}
return int64(0), nil
}
// Close closes the ReaderSeekerCloser.
//
// If the ReaderSeekerCloser is not an io.Closer nothing will be done.
func (r ReaderSeekerCloser) Close() error {
switch t := r.r.(type) {
case io.Closer:
return t.Close()
}
return nil
}
// A WriteAtBuffer provides a in memory buffer supporting the io.WriterAt interface
// Can be used with the s3manager.Downloader to download content to a buffer
// in memory. Safe to use concurrently.
type WriteAtBuffer struct {
buf []byte
m sync.Mutex
}
// WriteAt writes a slice of bytes to a buffer starting at the position provided
// The number of bytes written will be returned, or error. Can overwrite previous
// written slices if the write ats overlap.
func (b *WriteAtBuffer) WriteAt(p []byte, pos int64) (n int, err error) {
b.m.Lock()
defer b.m.Unlock()
expLen := pos + int64(len(p))
if int64(len(b.buf)) < expLen {
newBuf := make([]byte, expLen)
copy(newBuf, b.buf)
b.buf = newBuf
}
copy(b.buf[pos:], p)
return len(p), nil
}
// Bytes returns a slice of bytes written to the buffer.
func (b *WriteAtBuffer) Bytes() []byte {
b.m.Lock()
defer b.m.Unlock()
return b.buf[:len(b.buf):len(b.buf)]
}

View File

@@ -0,0 +1,56 @@
package aws
import (
"math/rand"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWriteAtBuffer(t *testing.T) {
b := &WriteAtBuffer{}
n, err := b.WriteAt([]byte{1}, 0)
assert.NoError(t, err)
assert.Equal(t, 1, n)
n, err = b.WriteAt([]byte{1, 1, 1}, 5)
assert.NoError(t, err)
assert.Equal(t, 3, n)
n, err = b.WriteAt([]byte{2}, 1)
assert.NoError(t, err)
assert.Equal(t, 1, n)
n, err = b.WriteAt([]byte{3}, 2)
assert.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, []byte{1, 2, 3, 0, 0, 1, 1, 1}, b.Bytes())
}
func BenchmarkWriteAtBuffer(b *testing.B) {
buf := &WriteAtBuffer{}
r := rand.New(rand.NewSource(1))
b.ResetTimer()
for i := 0; i < b.N; i++ {
to := r.Intn(10) * 4096
bs := make([]byte, to)
buf.WriteAt(bs, r.Int63n(10)*4096)
}
}
func BenchmarkWriteAtBufferParallel(b *testing.B) {
buf := &WriteAtBuffer{}
r := rand.New(rand.NewSource(1))
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
to := r.Intn(10) * 4096
bs := make([]byte, to)
buf.WriteAt(bs, r.Int63n(10)*4096)
}
})
}

View File

@@ -0,0 +1,8 @@
// Package aws provides core functionality for making requests to AWS services.
package aws
// SDKName is the name of this AWS SDK
const SDKName = "aws-sdk-go"
// SDKVersion is the version of this SDK
const SDKVersion = "1.0.0"

View File

@@ -0,0 +1,31 @@
// Package endpoints validates regional endpoints for services.
package endpoints
//go:generate go run ../model/cli/gen-endpoints/main.go endpoints.json endpoints_map.go
//go:generate gofmt -s -w endpoints_map.go
import "strings"
// EndpointForRegion returns an endpoint and its signing region for a service and region.
// if the service and region pair are not found endpoint and signingRegion will be empty.
func EndpointForRegion(svcName, region string) (endpoint, signingRegion string) {
derivedKeys := []string{
region + "/" + svcName,
region + "/*",
"*/" + svcName,
"*/*",
}
for _, key := range derivedKeys {
if val, ok := endpointsMap.Endpoints[key]; ok {
ep := val.Endpoint
ep = strings.Replace(ep, "{region}", region, -1)
ep = strings.Replace(ep, "{service}", svcName, -1)
endpoint = ep
signingRegion = val.SigningRegion
return
}
}
return
}

View File

@@ -0,0 +1,77 @@
{
"version": 2,
"endpoints": {
"*/*": {
"endpoint": "{service}.{region}.amazonaws.com"
},
"cn-north-1/*": {
"endpoint": "{service}.{region}.amazonaws.com.cn",
"signatureVersion": "v4"
},
"us-gov-west-1/iam": {
"endpoint": "iam.us-gov.amazonaws.com"
},
"us-gov-west-1/sts": {
"endpoint": "sts.us-gov-west-1.amazonaws.com"
},
"us-gov-west-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"*/cloudfront": {
"endpoint": "cloudfront.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/cloudsearchdomain": {
"endpoint": "",
"signingRegion": "us-east-1"
},
"*/iam": {
"endpoint": "iam.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/importexport": {
"endpoint": "importexport.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/route53": {
"endpoint": "route53.amazonaws.com",
"signingRegion": "us-east-1"
},
"*/sts": {
"endpoint": "sts.amazonaws.com",
"signingRegion": "us-east-1"
},
"us-east-1/sdb": {
"endpoint": "sdb.amazonaws.com",
"signingRegion": "us-east-1"
},
"us-east-1/s3": {
"endpoint": "s3.amazonaws.com"
},
"us-west-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"us-west-2/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"eu-west-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-southeast-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-southeast-2/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"ap-northeast-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"sa-east-1/s3": {
"endpoint": "s3-{region}.amazonaws.com"
},
"eu-central-1/s3": {
"endpoint": "{service}.{region}.amazonaws.com",
"signatureVersion": "v4"
}
}
}

View File

@@ -0,0 +1,89 @@
package endpoints
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
type endpointStruct struct {
Version int
Endpoints map[string]endpointEntry
}
type endpointEntry struct {
Endpoint string
SigningRegion string
}
var endpointsMap = endpointStruct{
Version: 2,
Endpoints: map[string]endpointEntry{
"*/*": {
Endpoint: "{service}.{region}.amazonaws.com",
},
"*/cloudfront": {
Endpoint: "cloudfront.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/cloudsearchdomain": {
Endpoint: "",
SigningRegion: "us-east-1",
},
"*/iam": {
Endpoint: "iam.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/importexport": {
Endpoint: "importexport.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/route53": {
Endpoint: "route53.amazonaws.com",
SigningRegion: "us-east-1",
},
"*/sts": {
Endpoint: "sts.amazonaws.com",
SigningRegion: "us-east-1",
},
"ap-northeast-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"ap-southeast-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"ap-southeast-2/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"cn-north-1/*": {
Endpoint: "{service}.{region}.amazonaws.com.cn",
},
"eu-central-1/s3": {
Endpoint: "{service}.{region}.amazonaws.com",
},
"eu-west-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"sa-east-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"us-east-1/s3": {
Endpoint: "s3.amazonaws.com",
},
"us-east-1/sdb": {
Endpoint: "sdb.amazonaws.com",
SigningRegion: "us-east-1",
},
"us-gov-west-1/iam": {
Endpoint: "iam.us-gov.amazonaws.com",
},
"us-gov-west-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"us-gov-west-1/sts": {
Endpoint: "sts.us-gov-west-1.amazonaws.com",
},
"us-west-1/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
"us-west-2/s3": {
Endpoint: "s3-{region}.amazonaws.com",
},
},
}

View File

@@ -0,0 +1,28 @@
package endpoints
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGlobalEndpoints(t *testing.T) {
region := "mock-region-1"
svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts"}
for _, name := range svcs {
ep, sr := EndpointForRegion(name, region)
assert.Equal(t, name+".amazonaws.com", ep)
assert.Equal(t, "us-east-1", sr)
}
}
func TestServicesInCN(t *testing.T) {
region := "cn-north-1"
svcs := []string{"cloudfront", "iam", "importexport", "route53", "sts", "s3"}
for _, name := range svcs {
ep, _ := EndpointForRegion(name, region)
assert.Equal(t, name+"."+region+".amazonaws.com.cn", ep)
}
}

View File

@@ -0,0 +1,32 @@
// Package ec2query provides serialisation of AWS EC2 requests and responses.
package ec2query
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/ec2.json build_test.go
import (
"net/url"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/internal/protocol/query/queryutil"
)
// Build builds a request for the EC2 protocol.
func Build(r *aws.Request) {
body := url.Values{
"Action": {r.Operation.Name},
"Version": {r.Service.APIVersion},
}
if err := queryutil.Parse(body, r.Params, true); err != nil {
r.Error = awserr.New("SerializationError", "failed encoding EC2 Query request", err)
}
if r.ExpireTime == 0 {
r.HTTPRequest.Method = "POST"
r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
r.SetBufferBody([]byte(body.Encode()))
} else { // This is a pre-signed request
r.HTTPRequest.Method = "GET"
r.HTTPRequest.URL.RawQuery = body.Encode()
}
}

View File

@@ -0,0 +1,860 @@
package ec2query_test
import (
"bytes"
"encoding/json"
"encoding/xml"
"io"
"io/ioutil"
"net/http"
"net/url"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/internal/protocol/ec2query"
"github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil"
"github.com/aws/aws-sdk-go/internal/signer/v4"
"github.com/aws/aws-sdk-go/internal/util"
"github.com/stretchr/testify/assert"
)
var _ bytes.Buffer // always import bytes
var _ http.Request
var _ json.Marshaler
var _ time.Time
var _ xmlutil.XMLNode
var _ xml.Attr
var _ = ioutil.Discard
var _ = util.Trim("")
var _ = url.Values{}
var _ = io.EOF
type InputService1ProtocolTest struct {
*aws.Service
}
// New returns a new InputService1ProtocolTest client.
func NewInputService1ProtocolTest(config *aws.Config) *InputService1ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice1protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService1ProtocolTest{service}
}
// newRequest creates a new request for a InputService1ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService1ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService1TestCaseOperation1 = "OperationName"
// InputService1TestCaseOperation1Request generates a request for the InputService1TestCaseOperation1 operation.
func (c *InputService1ProtocolTest) InputService1TestCaseOperation1Request(input *InputService1TestShapeInputShape) (req *aws.Request, output *InputService1TestShapeInputService1TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService1TestCaseOperation1,
}
if input == nil {
input = &InputService1TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService1TestShapeInputService1TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService1ProtocolTest) InputService1TestCaseOperation1(input *InputService1TestShapeInputShape) (*InputService1TestShapeInputService1TestCaseOperation1Output, error) {
req, out := c.InputService1TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService1TestShapeInputService1TestCaseOperation1Output struct {
metadataInputService1TestShapeInputService1TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService1TestShapeInputService1TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService1TestShapeInputShape struct {
Bar *string `type:"string"`
Foo *string `type:"string"`
metadataInputService1TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService1TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService2ProtocolTest struct {
*aws.Service
}
// New returns a new InputService2ProtocolTest client.
func NewInputService2ProtocolTest(config *aws.Config) *InputService2ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice2protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService2ProtocolTest{service}
}
// newRequest creates a new request for a InputService2ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService2ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService2TestCaseOperation1 = "OperationName"
// InputService2TestCaseOperation1Request generates a request for the InputService2TestCaseOperation1 operation.
func (c *InputService2ProtocolTest) InputService2TestCaseOperation1Request(input *InputService2TestShapeInputShape) (req *aws.Request, output *InputService2TestShapeInputService2TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService2TestCaseOperation1,
}
if input == nil {
input = &InputService2TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService2TestShapeInputService2TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService2ProtocolTest) InputService2TestCaseOperation1(input *InputService2TestShapeInputShape) (*InputService2TestShapeInputService2TestCaseOperation1Output, error) {
req, out := c.InputService2TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService2TestShapeInputService2TestCaseOperation1Output struct {
metadataInputService2TestShapeInputService2TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService2TestShapeInputService2TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService2TestShapeInputShape struct {
Bar *string `locationName:"barLocationName" type:"string"`
Foo *string `type:"string"`
Yuck *string `locationName:"yuckLocationName" queryName:"yuckQueryName" type:"string"`
metadataInputService2TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService2TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService3ProtocolTest struct {
*aws.Service
}
// New returns a new InputService3ProtocolTest client.
func NewInputService3ProtocolTest(config *aws.Config) *InputService3ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice3protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService3ProtocolTest{service}
}
// newRequest creates a new request for a InputService3ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService3ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService3TestCaseOperation1 = "OperationName"
// InputService3TestCaseOperation1Request generates a request for the InputService3TestCaseOperation1 operation.
func (c *InputService3ProtocolTest) InputService3TestCaseOperation1Request(input *InputService3TestShapeInputShape) (req *aws.Request, output *InputService3TestShapeInputService3TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService3TestCaseOperation1,
}
if input == nil {
input = &InputService3TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService3TestShapeInputService3TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService3ProtocolTest) InputService3TestCaseOperation1(input *InputService3TestShapeInputShape) (*InputService3TestShapeInputService3TestCaseOperation1Output, error) {
req, out := c.InputService3TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService3TestShapeInputService3TestCaseOperation1Output struct {
metadataInputService3TestShapeInputService3TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService3TestShapeInputService3TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService3TestShapeInputShape struct {
StructArg *InputService3TestShapeStructType `locationName:"Struct" type:"structure"`
metadataInputService3TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService3TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService3TestShapeStructType struct {
ScalarArg *string `locationName:"Scalar" type:"string"`
metadataInputService3TestShapeStructType `json:"-" xml:"-"`
}
type metadataInputService3TestShapeStructType struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService4ProtocolTest struct {
*aws.Service
}
// New returns a new InputService4ProtocolTest client.
func NewInputService4ProtocolTest(config *aws.Config) *InputService4ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice4protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService4ProtocolTest{service}
}
// newRequest creates a new request for a InputService4ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService4ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService4TestCaseOperation1 = "OperationName"
// InputService4TestCaseOperation1Request generates a request for the InputService4TestCaseOperation1 operation.
func (c *InputService4ProtocolTest) InputService4TestCaseOperation1Request(input *InputService4TestShapeInputShape) (req *aws.Request, output *InputService4TestShapeInputService4TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService4TestCaseOperation1,
}
if input == nil {
input = &InputService4TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService4TestShapeInputService4TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService4ProtocolTest) InputService4TestCaseOperation1(input *InputService4TestShapeInputShape) (*InputService4TestShapeInputService4TestCaseOperation1Output, error) {
req, out := c.InputService4TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService4TestShapeInputService4TestCaseOperation1Output struct {
metadataInputService4TestShapeInputService4TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService4TestShapeInputService4TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService4TestShapeInputShape struct {
ListArg []*string `type:"list"`
metadataInputService4TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService4TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService5ProtocolTest struct {
*aws.Service
}
// New returns a new InputService5ProtocolTest client.
func NewInputService5ProtocolTest(config *aws.Config) *InputService5ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice5protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService5ProtocolTest{service}
}
// newRequest creates a new request for a InputService5ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService5ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService5TestCaseOperation1 = "OperationName"
// InputService5TestCaseOperation1Request generates a request for the InputService5TestCaseOperation1 operation.
func (c *InputService5ProtocolTest) InputService5TestCaseOperation1Request(input *InputService5TestShapeInputShape) (req *aws.Request, output *InputService5TestShapeInputService5TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService5TestCaseOperation1,
}
if input == nil {
input = &InputService5TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService5TestShapeInputService5TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService5ProtocolTest) InputService5TestCaseOperation1(input *InputService5TestShapeInputShape) (*InputService5TestShapeInputService5TestCaseOperation1Output, error) {
req, out := c.InputService5TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService5TestShapeInputService5TestCaseOperation1Output struct {
metadataInputService5TestShapeInputService5TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService5TestShapeInputService5TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService5TestShapeInputShape struct {
ListArg []*string `locationName:"ListMemberName" locationNameList:"item" type:"list"`
metadataInputService5TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService5TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService6ProtocolTest struct {
*aws.Service
}
// New returns a new InputService6ProtocolTest client.
func NewInputService6ProtocolTest(config *aws.Config) *InputService6ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice6protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService6ProtocolTest{service}
}
// newRequest creates a new request for a InputService6ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService6ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService6TestCaseOperation1 = "OperationName"
// InputService6TestCaseOperation1Request generates a request for the InputService6TestCaseOperation1 operation.
func (c *InputService6ProtocolTest) InputService6TestCaseOperation1Request(input *InputService6TestShapeInputShape) (req *aws.Request, output *InputService6TestShapeInputService6TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService6TestCaseOperation1,
}
if input == nil {
input = &InputService6TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService6TestShapeInputService6TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService6ProtocolTest) InputService6TestCaseOperation1(input *InputService6TestShapeInputShape) (*InputService6TestShapeInputService6TestCaseOperation1Output, error) {
req, out := c.InputService6TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService6TestShapeInputService6TestCaseOperation1Output struct {
metadataInputService6TestShapeInputService6TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService6TestShapeInputService6TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService6TestShapeInputShape struct {
ListArg []*string `locationName:"ListMemberName" queryName:"ListQueryName" locationNameList:"item" type:"list"`
metadataInputService6TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService6TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService7ProtocolTest struct {
*aws.Service
}
// New returns a new InputService7ProtocolTest client.
func NewInputService7ProtocolTest(config *aws.Config) *InputService7ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice7protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService7ProtocolTest{service}
}
// newRequest creates a new request for a InputService7ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService7ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService7TestCaseOperation1 = "OperationName"
// InputService7TestCaseOperation1Request generates a request for the InputService7TestCaseOperation1 operation.
func (c *InputService7ProtocolTest) InputService7TestCaseOperation1Request(input *InputService7TestShapeInputShape) (req *aws.Request, output *InputService7TestShapeInputService7TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService7TestCaseOperation1,
}
if input == nil {
input = &InputService7TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService7TestShapeInputService7TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService7ProtocolTest) InputService7TestCaseOperation1(input *InputService7TestShapeInputShape) (*InputService7TestShapeInputService7TestCaseOperation1Output, error) {
req, out := c.InputService7TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService7TestShapeInputService7TestCaseOperation1Output struct {
metadataInputService7TestShapeInputService7TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService7TestShapeInputService7TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService7TestShapeInputShape struct {
BlobArg []byte `type:"blob"`
metadataInputService7TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService7TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService8ProtocolTest struct {
*aws.Service
}
// New returns a new InputService8ProtocolTest client.
func NewInputService8ProtocolTest(config *aws.Config) *InputService8ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "inputservice8protocoltest",
APIVersion: "2014-01-01",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &InputService8ProtocolTest{service}
}
// newRequest creates a new request for a InputService8ProtocolTest operation and runs any
// custom request initialization.
func (c *InputService8ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opInputService8TestCaseOperation1 = "OperationName"
// InputService8TestCaseOperation1Request generates a request for the InputService8TestCaseOperation1 operation.
func (c *InputService8ProtocolTest) InputService8TestCaseOperation1Request(input *InputService8TestShapeInputShape) (req *aws.Request, output *InputService8TestShapeInputService8TestCaseOperation1Output) {
op := &aws.Operation{
Name: opInputService8TestCaseOperation1,
}
if input == nil {
input = &InputService8TestShapeInputShape{}
}
req = c.newRequest(op, input, output)
output = &InputService8TestShapeInputService8TestCaseOperation1Output{}
req.Data = output
return
}
func (c *InputService8ProtocolTest) InputService8TestCaseOperation1(input *InputService8TestShapeInputShape) (*InputService8TestShapeInputService8TestCaseOperation1Output, error) {
req, out := c.InputService8TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type InputService8TestShapeInputService8TestCaseOperation1Output struct {
metadataInputService8TestShapeInputService8TestCaseOperation1Output `json:"-" xml:"-"`
}
type metadataInputService8TestShapeInputService8TestCaseOperation1Output struct {
SDKShapeTraits bool `type:"structure"`
}
type InputService8TestShapeInputShape struct {
TimeArg *time.Time `type:"timestamp" timestampFormat:"iso8601"`
metadataInputService8TestShapeInputShape `json:"-" xml:"-"`
}
type metadataInputService8TestShapeInputShape struct {
SDKShapeTraits bool `type:"structure"`
}
//
// Tests begin here
//
func TestInputService1ProtocolTestScalarMembersCase1(t *testing.T) {
svc := NewInputService1ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService1TestShapeInputShape{
Bar: aws.String("val2"),
Foo: aws.String("val1"),
}
req, _ := svc.InputService1TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&Bar=val2&Foo=val1&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService2ProtocolTestStructureWithLocationNameAndQueryNameAppliedToMembersCase1(t *testing.T) {
svc := NewInputService2ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService2TestShapeInputShape{
Bar: aws.String("val2"),
Foo: aws.String("val1"),
Yuck: aws.String("val3"),
}
req, _ := svc.InputService2TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&BarLocationName=val2&Foo=val1&Version=2014-01-01&yuckQueryName=val3`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService3ProtocolTestNestedStructureMembersCase1(t *testing.T) {
svc := NewInputService3ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService3TestShapeInputShape{
StructArg: &InputService3TestShapeStructType{
ScalarArg: aws.String("foo"),
},
}
req, _ := svc.InputService3TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&Struct.Scalar=foo&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService4ProtocolTestListTypesCase1(t *testing.T) {
svc := NewInputService4ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService4TestShapeInputShape{
ListArg: []*string{
aws.String("foo"),
aws.String("bar"),
aws.String("baz"),
},
}
req, _ := svc.InputService4TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&ListArg.1=foo&ListArg.2=bar&ListArg.3=baz&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService5ProtocolTestListWithLocationNameAppliedToMemberCase1(t *testing.T) {
svc := NewInputService5ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService5TestShapeInputShape{
ListArg: []*string{
aws.String("a"),
aws.String("b"),
aws.String("c"),
},
}
req, _ := svc.InputService5TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&ListMemberName.1=a&ListMemberName.2=b&ListMemberName.3=c&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService6ProtocolTestListWithLocationNameAndQueryNameCase1(t *testing.T) {
svc := NewInputService6ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService6TestShapeInputShape{
ListArg: []*string{
aws.String("a"),
aws.String("b"),
aws.String("c"),
},
}
req, _ := svc.InputService6TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&ListQueryName.1=a&ListQueryName.2=b&ListQueryName.3=c&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService7ProtocolTestBase64EncodedBlobsCase1(t *testing.T) {
svc := NewInputService7ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService7TestShapeInputShape{
BlobArg: []byte("foo"),
}
req, _ := svc.InputService7TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&BlobArg=Zm9v&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}
func TestInputService8ProtocolTestTimestampValuesCase1(t *testing.T) {
svc := NewInputService8ProtocolTest(nil)
svc.Endpoint = "https://test"
input := &InputService8TestShapeInputShape{
TimeArg: aws.Time(time.Unix(1422172800, 0)),
}
req, _ := svc.InputService8TestCaseOperation1Request(input)
r := req.HTTPRequest
// build request
ec2query.Build(req)
assert.NoError(t, req.Error)
// assert body
assert.NotNil(t, r.Body)
body, _ := ioutil.ReadAll(r.Body)
assert.Equal(t, util.Trim(`Action=OperationName&TimeArg=2015-01-25T08%3A00%3A00Z&Version=2014-01-01`), util.Trim(string(body)))
// assert URL
assert.Equal(t, "https://test/", r.URL.String())
// assert headers
}

View File

@@ -0,0 +1,54 @@
package ec2query
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/ec2.json unmarshal_test.go
import (
"encoding/xml"
"io"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil"
)
// Unmarshal unmarshals a response body for the EC2 protocol.
func Unmarshal(r *aws.Request) {
defer r.HTTPResponse.Body.Close()
if r.DataFilled() {
decoder := xml.NewDecoder(r.HTTPResponse.Body)
err := xmlutil.UnmarshalXML(r.Data, decoder, "")
if err != nil {
r.Error = awserr.New("SerializationError", "failed decoding EC2 Query response", err)
return
}
}
}
// UnmarshalMeta unmarshals response headers for the EC2 protocol.
func UnmarshalMeta(r *aws.Request) {
// TODO implement unmarshaling of request IDs
}
type xmlErrorResponse struct {
XMLName xml.Name `xml:"Response"`
Code string `xml:"Errors>Error>Code"`
Message string `xml:"Errors>Error>Message"`
RequestID string `xml:"RequestId"`
}
// UnmarshalError unmarshals a response error for the EC2 protocol.
func UnmarshalError(r *aws.Request) {
defer r.HTTPResponse.Body.Close()
resp := &xmlErrorResponse{}
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp)
if err != nil && err != io.EOF {
r.Error = awserr.New("SerializationError", "failed decoding EC2 Query error response", err)
} else {
r.Error = awserr.NewRequestFailure(
awserr.New(resp.Code, resp.Message, nil),
r.HTTPResponse.StatusCode,
resp.RequestID,
)
}
}

View File

@@ -0,0 +1,816 @@
package ec2query_test
import (
"bytes"
"encoding/json"
"encoding/xml"
"io"
"io/ioutil"
"net/http"
"net/url"
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/internal/protocol/ec2query"
"github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil"
"github.com/aws/aws-sdk-go/internal/signer/v4"
"github.com/aws/aws-sdk-go/internal/util"
"github.com/stretchr/testify/assert"
)
var _ bytes.Buffer // always import bytes
var _ http.Request
var _ json.Marshaler
var _ time.Time
var _ xmlutil.XMLNode
var _ xml.Attr
var _ = ioutil.Discard
var _ = util.Trim("")
var _ = url.Values{}
var _ = io.EOF
type OutputService1ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService1ProtocolTest client.
func NewOutputService1ProtocolTest(config *aws.Config) *OutputService1ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice1protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService1ProtocolTest{service}
}
// newRequest creates a new request for a OutputService1ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService1ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService1TestCaseOperation1 = "OperationName"
// OutputService1TestCaseOperation1Request generates a request for the OutputService1TestCaseOperation1 operation.
func (c *OutputService1ProtocolTest) OutputService1TestCaseOperation1Request(input *OutputService1TestShapeOutputService1TestCaseOperation1Input) (req *aws.Request, output *OutputService1TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService1TestCaseOperation1,
}
if input == nil {
input = &OutputService1TestShapeOutputService1TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService1TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService1ProtocolTest) OutputService1TestCaseOperation1(input *OutputService1TestShapeOutputService1TestCaseOperation1Input) (*OutputService1TestShapeOutputShape, error) {
req, out := c.OutputService1TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService1TestShapeOutputService1TestCaseOperation1Input struct {
metadataOutputService1TestShapeOutputService1TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService1TestShapeOutputService1TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService1TestShapeOutputShape struct {
Char *string `type:"character"`
Double *float64 `type:"double"`
FalseBool *bool `type:"boolean"`
Float *float64 `type:"float"`
Long *int64 `type:"long"`
Num *int64 `locationName:"FooNum" type:"integer"`
Str *string `type:"string"`
TrueBool *bool `type:"boolean"`
metadataOutputService1TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService1TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService2ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService2ProtocolTest client.
func NewOutputService2ProtocolTest(config *aws.Config) *OutputService2ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice2protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService2ProtocolTest{service}
}
// newRequest creates a new request for a OutputService2ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService2ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService2TestCaseOperation1 = "OperationName"
// OutputService2TestCaseOperation1Request generates a request for the OutputService2TestCaseOperation1 operation.
func (c *OutputService2ProtocolTest) OutputService2TestCaseOperation1Request(input *OutputService2TestShapeOutputService2TestCaseOperation1Input) (req *aws.Request, output *OutputService2TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService2TestCaseOperation1,
}
if input == nil {
input = &OutputService2TestShapeOutputService2TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService2TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService2ProtocolTest) OutputService2TestCaseOperation1(input *OutputService2TestShapeOutputService2TestCaseOperation1Input) (*OutputService2TestShapeOutputShape, error) {
req, out := c.OutputService2TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService2TestShapeOutputService2TestCaseOperation1Input struct {
metadataOutputService2TestShapeOutputService2TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService2TestShapeOutputService2TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService2TestShapeOutputShape struct {
Blob []byte `type:"blob"`
metadataOutputService2TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService2TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService3ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService3ProtocolTest client.
func NewOutputService3ProtocolTest(config *aws.Config) *OutputService3ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice3protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService3ProtocolTest{service}
}
// newRequest creates a new request for a OutputService3ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService3ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService3TestCaseOperation1 = "OperationName"
// OutputService3TestCaseOperation1Request generates a request for the OutputService3TestCaseOperation1 operation.
func (c *OutputService3ProtocolTest) OutputService3TestCaseOperation1Request(input *OutputService3TestShapeOutputService3TestCaseOperation1Input) (req *aws.Request, output *OutputService3TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService3TestCaseOperation1,
}
if input == nil {
input = &OutputService3TestShapeOutputService3TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService3TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService3ProtocolTest) OutputService3TestCaseOperation1(input *OutputService3TestShapeOutputService3TestCaseOperation1Input) (*OutputService3TestShapeOutputShape, error) {
req, out := c.OutputService3TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService3TestShapeOutputService3TestCaseOperation1Input struct {
metadataOutputService3TestShapeOutputService3TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService3TestShapeOutputService3TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService3TestShapeOutputShape struct {
ListMember []*string `type:"list"`
metadataOutputService3TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService3TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService4ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService4ProtocolTest client.
func NewOutputService4ProtocolTest(config *aws.Config) *OutputService4ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice4protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService4ProtocolTest{service}
}
// newRequest creates a new request for a OutputService4ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService4ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService4TestCaseOperation1 = "OperationName"
// OutputService4TestCaseOperation1Request generates a request for the OutputService4TestCaseOperation1 operation.
func (c *OutputService4ProtocolTest) OutputService4TestCaseOperation1Request(input *OutputService4TestShapeOutputService4TestCaseOperation1Input) (req *aws.Request, output *OutputService4TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService4TestCaseOperation1,
}
if input == nil {
input = &OutputService4TestShapeOutputService4TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService4TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService4ProtocolTest) OutputService4TestCaseOperation1(input *OutputService4TestShapeOutputService4TestCaseOperation1Input) (*OutputService4TestShapeOutputShape, error) {
req, out := c.OutputService4TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService4TestShapeOutputService4TestCaseOperation1Input struct {
metadataOutputService4TestShapeOutputService4TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService4TestShapeOutputService4TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService4TestShapeOutputShape struct {
ListMember []*string `locationNameList:"item" type:"list"`
metadataOutputService4TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService4TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService5ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService5ProtocolTest client.
func NewOutputService5ProtocolTest(config *aws.Config) *OutputService5ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice5protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService5ProtocolTest{service}
}
// newRequest creates a new request for a OutputService5ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService5ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService5TestCaseOperation1 = "OperationName"
// OutputService5TestCaseOperation1Request generates a request for the OutputService5TestCaseOperation1 operation.
func (c *OutputService5ProtocolTest) OutputService5TestCaseOperation1Request(input *OutputService5TestShapeOutputService5TestCaseOperation1Input) (req *aws.Request, output *OutputService5TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService5TestCaseOperation1,
}
if input == nil {
input = &OutputService5TestShapeOutputService5TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService5TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService5ProtocolTest) OutputService5TestCaseOperation1(input *OutputService5TestShapeOutputService5TestCaseOperation1Input) (*OutputService5TestShapeOutputShape, error) {
req, out := c.OutputService5TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService5TestShapeOutputService5TestCaseOperation1Input struct {
metadataOutputService5TestShapeOutputService5TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService5TestShapeOutputService5TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService5TestShapeOutputShape struct {
ListMember []*string `type:"list" flattened:"true"`
metadataOutputService5TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService5TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService6ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService6ProtocolTest client.
func NewOutputService6ProtocolTest(config *aws.Config) *OutputService6ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice6protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService6ProtocolTest{service}
}
// newRequest creates a new request for a OutputService6ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService6ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService6TestCaseOperation1 = "OperationName"
// OutputService6TestCaseOperation1Request generates a request for the OutputService6TestCaseOperation1 operation.
func (c *OutputService6ProtocolTest) OutputService6TestCaseOperation1Request(input *OutputService6TestShapeOutputService6TestCaseOperation1Input) (req *aws.Request, output *OutputService6TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService6TestCaseOperation1,
}
if input == nil {
input = &OutputService6TestShapeOutputService6TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService6TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService6ProtocolTest) OutputService6TestCaseOperation1(input *OutputService6TestShapeOutputService6TestCaseOperation1Input) (*OutputService6TestShapeOutputShape, error) {
req, out := c.OutputService6TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService6TestShapeOutputService6TestCaseOperation1Input struct {
metadataOutputService6TestShapeOutputService6TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService6TestShapeOutputService6TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService6TestShapeOutputShape struct {
Map map[string]*OutputService6TestShapeStructureType `type:"map"`
metadataOutputService6TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService6TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService6TestShapeStructureType struct {
Foo *string `locationName:"foo" type:"string"`
metadataOutputService6TestShapeStructureType `json:"-" xml:"-"`
}
type metadataOutputService6TestShapeStructureType struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService7ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService7ProtocolTest client.
func NewOutputService7ProtocolTest(config *aws.Config) *OutputService7ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice7protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService7ProtocolTest{service}
}
// newRequest creates a new request for a OutputService7ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService7ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService7TestCaseOperation1 = "OperationName"
// OutputService7TestCaseOperation1Request generates a request for the OutputService7TestCaseOperation1 operation.
func (c *OutputService7ProtocolTest) OutputService7TestCaseOperation1Request(input *OutputService7TestShapeOutputService7TestCaseOperation1Input) (req *aws.Request, output *OutputService7TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService7TestCaseOperation1,
}
if input == nil {
input = &OutputService7TestShapeOutputService7TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService7TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService7ProtocolTest) OutputService7TestCaseOperation1(input *OutputService7TestShapeOutputService7TestCaseOperation1Input) (*OutputService7TestShapeOutputShape, error) {
req, out := c.OutputService7TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService7TestShapeOutputService7TestCaseOperation1Input struct {
metadataOutputService7TestShapeOutputService7TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService7TestShapeOutputService7TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService7TestShapeOutputShape struct {
Map map[string]*string `type:"map" flattened:"true"`
metadataOutputService7TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService7TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService8ProtocolTest struct {
*aws.Service
}
// New returns a new OutputService8ProtocolTest client.
func NewOutputService8ProtocolTest(config *aws.Config) *OutputService8ProtocolTest {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "outputservice8protocoltest",
APIVersion: "",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
return &OutputService8ProtocolTest{service}
}
// newRequest creates a new request for a OutputService8ProtocolTest operation and runs any
// custom request initialization.
func (c *OutputService8ProtocolTest) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
return req
}
const opOutputService8TestCaseOperation1 = "OperationName"
// OutputService8TestCaseOperation1Request generates a request for the OutputService8TestCaseOperation1 operation.
func (c *OutputService8ProtocolTest) OutputService8TestCaseOperation1Request(input *OutputService8TestShapeOutputService8TestCaseOperation1Input) (req *aws.Request, output *OutputService8TestShapeOutputShape) {
op := &aws.Operation{
Name: opOutputService8TestCaseOperation1,
}
if input == nil {
input = &OutputService8TestShapeOutputService8TestCaseOperation1Input{}
}
req = c.newRequest(op, input, output)
output = &OutputService8TestShapeOutputShape{}
req.Data = output
return
}
func (c *OutputService8ProtocolTest) OutputService8TestCaseOperation1(input *OutputService8TestShapeOutputService8TestCaseOperation1Input) (*OutputService8TestShapeOutputShape, error) {
req, out := c.OutputService8TestCaseOperation1Request(input)
err := req.Send()
return out, err
}
type OutputService8TestShapeOutputService8TestCaseOperation1Input struct {
metadataOutputService8TestShapeOutputService8TestCaseOperation1Input `json:"-" xml:"-"`
}
type metadataOutputService8TestShapeOutputService8TestCaseOperation1Input struct {
SDKShapeTraits bool `type:"structure"`
}
type OutputService8TestShapeOutputShape struct {
Map map[string]*string `locationNameKey:"foo" locationNameValue:"bar" type:"map" flattened:"true"`
metadataOutputService8TestShapeOutputShape `json:"-" xml:"-"`
}
type metadataOutputService8TestShapeOutputShape struct {
SDKShapeTraits bool `type:"structure"`
}
//
// Tests begin here
//
func TestOutputService1ProtocolTestScalarMembersCase1(t *testing.T) {
svc := NewOutputService1ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><Str>myname</Str><FooNum>123</FooNum><FalseBool>false</FalseBool><TrueBool>true</TrueBool><Float>1.2</Float><Double>1.3</Double><Long>200</Long><Char>a</Char><RequestId>request-id</RequestId></OperationNameResponse>"))
req, out := svc.OutputService1TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "a", *out.Char)
assert.Equal(t, 1.3, *out.Double)
assert.Equal(t, false, *out.FalseBool)
assert.Equal(t, 1.2, *out.Float)
assert.Equal(t, int64(200), *out.Long)
assert.Equal(t, int64(123), *out.Num)
assert.Equal(t, "myname", *out.Str)
assert.Equal(t, true, *out.TrueBool)
}
func TestOutputService2ProtocolTestBlobCase1(t *testing.T) {
svc := NewOutputService2ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><Blob>dmFsdWU=</Blob><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService2TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "value", string(out.Blob))
}
func TestOutputService3ProtocolTestListsCase1(t *testing.T) {
svc := NewOutputService3ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><ListMember><member>abc</member><member>123</member></ListMember><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService3TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "abc", *out.ListMember[0])
assert.Equal(t, "123", *out.ListMember[1])
}
func TestOutputService4ProtocolTestListWithCustomMemberNameCase1(t *testing.T) {
svc := NewOutputService4ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><ListMember><item>abc</item><item>123</item></ListMember><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService4TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "abc", *out.ListMember[0])
assert.Equal(t, "123", *out.ListMember[1])
}
func TestOutputService5ProtocolTestFlattenedListCase1(t *testing.T) {
svc := NewOutputService5ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><ListMember>abc</ListMember><ListMember>123</ListMember><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService5TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "abc", *out.ListMember[0])
assert.Equal(t, "123", *out.ListMember[1])
}
func TestOutputService6ProtocolTestNormalMapCase1(t *testing.T) {
svc := NewOutputService6ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><Map><entry><key>qux</key><value><foo>bar</foo></value></entry><entry><key>baz</key><value><foo>bam</foo></value></entry></Map><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService6TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "bam", *out.Map["baz"].Foo)
assert.Equal(t, "bar", *out.Map["qux"].Foo)
}
func TestOutputService7ProtocolTestFlattenedMapCase1(t *testing.T) {
svc := NewOutputService7ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><Map><key>qux</key><value>bar</value></Map><Map><key>baz</key><value>bam</value></Map><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService7TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "bam", *out.Map["baz"])
assert.Equal(t, "bar", *out.Map["qux"])
}
func TestOutputService8ProtocolTestNamedMapCase1(t *testing.T) {
svc := NewOutputService8ProtocolTest(nil)
buf := bytes.NewReader([]byte("<OperationNameResponse><Map><foo>qux</foo><bar>bar</bar></Map><Map><foo>baz</foo><bar>bam</bar></Map><RequestId>requestid</RequestId></OperationNameResponse>"))
req, out := svc.OutputService8TestCaseOperation1Request(nil)
req.HTTPResponse = &http.Response{StatusCode: 200, Body: ioutil.NopCloser(buf), Header: http.Header{}}
// set headers
// unmarshal response
ec2query.UnmarshalMeta(req)
ec2query.Unmarshal(req)
assert.NoError(t, req.Error)
// assert response
assert.NotNil(t, out) // ensure out variable is used
assert.Equal(t, "bam", *out.Map["baz"])
assert.Equal(t, "bar", *out.Map["qux"])
}

View File

@@ -0,0 +1,33 @@
// Package query provides serialisation of AWS query requests, and responses.
package query
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/query.json build_test.go
import (
"net/url"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/internal/protocol/query/queryutil"
)
// Build builds a request for an AWS Query service.
func Build(r *aws.Request) {
body := url.Values{
"Action": {r.Operation.Name},
"Version": {r.Service.APIVersion},
}
if err := queryutil.Parse(body, r.Params, false); err != nil {
r.Error = awserr.New("SerializationError", "failed encoding Query request", err)
return
}
if r.ExpireTime == 0 {
r.HTTPRequest.Method = "POST"
r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
r.SetBufferBody([]byte(body.Encode()))
} else { // This is a pre-signed request
r.HTTPRequest.Method = "GET"
r.HTTPRequest.URL.RawQuery = body.Encode()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
package queryutil
import (
"encoding/base64"
"fmt"
"net/url"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
// Parse parses an object i and fills a url.Values object. The isEC2 flag
// indicates if this is the EC2 Query sub-protocol.
func Parse(body url.Values, i interface{}, isEC2 bool) error {
q := queryParser{isEC2: isEC2}
return q.parseValue(body, reflect.ValueOf(i), "", "")
}
func elemOf(value reflect.Value) reflect.Value {
for value.Kind() == reflect.Ptr {
value = value.Elem()
}
return value
}
type queryParser struct {
isEC2 bool
}
func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
value = elemOf(value)
// no need to handle zero values
if !value.IsValid() {
return nil
}
t := tag.Get("type")
if t == "" {
switch value.Kind() {
case reflect.Struct:
t = "structure"
case reflect.Slice:
t = "list"
case reflect.Map:
t = "map"
}
}
switch t {
case "structure":
return q.parseStruct(v, value, prefix)
case "list":
return q.parseList(v, value, prefix, tag)
case "map":
return q.parseMap(v, value, prefix, tag)
default:
return q.parseScalar(v, value, prefix, tag)
}
}
func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error {
if !value.IsValid() {
return nil
}
t := value.Type()
for i := 0; i < value.NumField(); i++ {
if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c {
continue // ignore unexported fields
}
value := elemOf(value.Field(i))
field := t.Field(i)
var name string
if q.isEC2 {
name = field.Tag.Get("queryName")
}
if name == "" {
if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
name = field.Tag.Get("locationNameList")
} else if locName := field.Tag.Get("locationName"); locName != "" {
name = locName
}
if name != "" && q.isEC2 {
name = strings.ToUpper(name[0:1]) + name[1:]
}
}
if name == "" {
name = field.Name
}
if prefix != "" {
name = prefix + "." + name
}
if err := q.parseValue(v, value, name, field.Tag); err != nil {
return err
}
}
return nil
}
func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
// If it's empty, generate an empty value
if !value.IsNil() && value.Len() == 0 {
v.Set(prefix, "")
return nil
}
// check for unflattened list member
if !q.isEC2 && tag.Get("flattened") == "" {
prefix += ".member"
}
for i := 0; i < value.Len(); i++ {
slicePrefix := prefix
if slicePrefix == "" {
slicePrefix = strconv.Itoa(i + 1)
} else {
slicePrefix = slicePrefix + "." + strconv.Itoa(i+1)
}
if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil {
return err
}
}
return nil
}
func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
// If it's empty, generate an empty value
if !value.IsNil() && value.Len() == 0 {
v.Set(prefix, "")
return nil
}
// check for unflattened list member
if !q.isEC2 && tag.Get("flattened") == "" {
prefix += ".entry"
}
// sort keys for improved serialization consistency.
// this is not strictly necessary for protocol support.
mapKeyValues := value.MapKeys()
mapKeys := map[string]reflect.Value{}
mapKeyNames := make([]string, len(mapKeyValues))
for i, mapKey := range mapKeyValues {
name := mapKey.String()
mapKeys[name] = mapKey
mapKeyNames[i] = name
}
sort.Strings(mapKeyNames)
for i, mapKeyName := range mapKeyNames {
mapKey := mapKeys[mapKeyName]
mapValue := value.MapIndex(mapKey)
kname := tag.Get("locationNameKey")
if kname == "" {
kname = "key"
}
vname := tag.Get("locationNameValue")
if vname == "" {
vname = "value"
}
// serialize key
var keyName string
if prefix == "" {
keyName = strconv.Itoa(i+1) + "." + kname
} else {
keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname
}
if err := q.parseValue(v, mapKey, keyName, ""); err != nil {
return err
}
// serialize value
var valueName string
if prefix == "" {
valueName = strconv.Itoa(i+1) + "." + vname
} else {
valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname
}
if err := q.parseValue(v, mapValue, valueName, ""); err != nil {
return err
}
}
return nil
}
func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error {
switch value := r.Interface().(type) {
case string:
v.Set(name, value)
case []byte:
if !r.IsNil() {
v.Set(name, base64.StdEncoding.EncodeToString(value))
}
case bool:
v.Set(name, strconv.FormatBool(value))
case int64:
v.Set(name, strconv.FormatInt(value, 10))
case int:
v.Set(name, strconv.Itoa(value))
case float64:
v.Set(name, strconv.FormatFloat(value, 'f', -1, 64))
case float32:
v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32))
case time.Time:
const ISO8601UTC = "2006-01-02T15:04:05Z"
v.Set(name, value.UTC().Format(ISO8601UTC))
default:
return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name())
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More