* use collector interface

* mysql can work fine

* add basecollector

* add prober & monapi.plugins

* enable mysql plugins work

* rename collector -> manager

* add white list access check for rdb

* add cache module for authConfig & session

* rollback n9e_rdb_3.3.0.sql

* add sql ddl document

* add white_list, pwd, login access control

* add email code for login & reset password

* use sessionUsername instead of cookieUsername

* remove cookie name and data from session

* rename userName to username

* add remote_addr with session connection

* add get user by sid with cache

* enable cookie life time could be zero

* go mod tidy

* Rdb with session & monapi with telegraf (#456)

* use collector interface

* mysql can work fine

* add basecollector

* add prober & monapi.plugins

* enable mysql plugins work

* rename collector -> manager

* add white list access check for rdb

* add cache module for authConfig & session

* rollback n9e_rdb_3.3.0.sql

* add sql ddl document

* add white_list, pwd, login access control

* add email code for login & reset password

* use sessionUsername instead of cookieUsername

* remove cookie name and data from session

* rename userName to username

* add remote_addr with session connection

* add get user by sid with cache

* enable cookie life time could be zero

* go mod tidy

* add plugins config for prober

* add prober plugin expression parse

* update transfer default config for m3

* Rdb (#458)

* bugfix: session gc

* use flag for pwdMustInclude

* change user login function

* delete invite token after use

* bugfix: login response

* add sessionStart middle ware

* add auth module

* add i18n for rdb

* add i18n.zh for rdb.auth

* add mon plugins(redis, mongodb)

* update config

* add sub struct into definitions

* clean up sid cache after session destory

* bugfix: get user return nil when not found

* update i18n

* bugfix: ignore cache nologin user

* add user for callback output

* add password change api

* update default configfile & sql patch

* merge mon http middleware from rdb

* remove sso logout, sso already supporte one time auth
This commit is contained in:
yubo 2021-01-01 10:41:30 +08:00 committed by GitHub
parent bad43090ff
commit d45ea02562
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
119 changed files with 8299 additions and 1480 deletions

1
.gitignore vendored
View File

@ -31,6 +31,7 @@ _test
/build
/dist
/etc/*.local.yml
/etc/plugins/*.local.yml
/etc/log/log.test.json
/data*
/tarball

16
docs/account-access.md Normal file
View File

@ -0,0 +1,16 @@
## 登陆相关
#### 来源地址限制
IP地址的获取顺序
- http header "X-Forwarded-For"
- http header "X-Real-Ip"
- http request RemoteAddr
nginx 代理配置客户端地址
```
# https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
```

View File

@ -42,5 +42,11 @@ judge:
addresses:
- 127.0.0.1
prober:
http: 0.0.0.0:8023
rpc: 0.0.0.0:8024
addresses:
- 127.0.0.1
agent:
http: 0.0.0.0:2080

View File

@ -6,7 +6,7 @@ logger:
http:
mode: release
cookieDomain: ""
cookieName: ecmc-user
cookieName: ecmc-sid
tokens:
- ams-builtin-token

View File

@ -57,6 +57,101 @@
"node is managed by other system": "租户正在被系统系统使用",
"resources not found by %s": "通过 %s 没有找到资源",
"cannot delete root user": "root用户不能删除",
"user not found": "用户未找到"
"user not found": "用户未找到",
"Databases": "数据库",
"if the list is empty, then metrics are gathered from all database tables": "如果列表为空,则收集所有数据库表",
"Process List": "进程列表",
"gather thread state counts from INFORMATION_SCHEMA.PROCESSLIST": "从 INFORMATION_SCHEMA.PROCESSLIST 收集线程状态信息",
"User Statistics": "User Statistics",
"gather user statistics from INFORMATION_SCHEMA.USER_STATISTICS": "从 INFORMATION_SCHEMA.USER_STATISTICS 收集用户状态信息",
"Auto Increment": "Auto Increment",
"gather auto_increment columns and max values from information schema": "采集 auto_increment 和 max values 信息",
"Innodb Metrics": "Innodb Metrics",
"gather metrics from INFORMATION_SCHEMA.INNODB_METRICS": "采集 INFORMATION_SCHEMA.INNODB_METRICS 信息",
"Slave Status": "Slave Status",
"gather metrics from SHOW SLAVE STATUS command output": "采集 metrics from SHOW SLAVE STATUS command output",
"Binary Logs": "Binary Logs",
"gather metrics from SHOW BINARY LOGS command output": "采集 metrics from SHOW BINARY LOGS command output",
"Table IO Waits": "Table IO Waits",
"gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE": "采集 from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE",
"Table Lock Waits": "Table Lock Waits",
"gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS": "采集 from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS",
"Index IO Waits": "Index IO Waits",
"gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE": "采集 from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE",
"Event Waits": "Event Waits",
"gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS": "采集 from PERFORMANCE_SCHEMA.EVENT_WAITS",
"Tables": "Tables",
"gather metrics from INFORMATION_SCHEMA.TABLES for databases provided above list": "采集 from INFORMATION_SCHEMA.TABLES for databases provided above list",
"File Events Stats": "File Events Stats",
"gather metrics from PERFORMANCE_SCHEMA.FILE_SUMMARY_BY_EVENT_NAME": "采集 from PERFORMANCE_SCHEMA.FILE_SUMMARY_BY_EVENT_NAME",
"Perf Events Statements": "Perf Events Statements",
"gather metrics from PERFORMANCE_SCHEMA.EVENTS_STATEMENTS_SUMMARY_BY_DIGEST": "采集 from PERFORMANCE_SCHEMA.EVENTS_STATEMENTS_SUMMARY_BY_DIGEST",
"Interval Slow": "Interval Slow",
"Repositories": "Repositories",
"List of repositories to monitor": "List of repositories to monitor",
"Access token": "Access token",
"Github API access token. Unauthenticated requests are limited to 60 per hour": "Github API access token. Unauthenticated requests are limited to 60 per hour",
"Enterprise base url": "Enterprise base url",
"Github API enterprise url. Github Enterprise accounts must specify their base url": "Github API enterprise url. Github Enterprise accounts must specify their base url",
"HTTP timeout": "HTTP timeout",
"Timeout for HTTP requests": "Timeout for HTTP requests",
"Unable to get captcha": "无法获得验证码",
"Invalid captcha answer": "错误的验证码",
"Username %s is invalid": "用户名 %s 不符合规范",
"Username %s too long > 64": "用户名 %s 太长(64)",
"Unable to get login arguments": "无法获得登陆参数",
"Deny Access from %s with whitelist control": "来自 %s 的访问被白名单规则拒绝",
"Invalid login type %s": "不支持的登陆类型 %s",
"Unable to get type, sms-code | email-code": "无法获得验证码类型",
"Unable to get code arg": "无法获得验证码类型",
"sms/email sender is disabled": "无法发送 短信/邮件 验证码",
"Invalid code type %s": "不支持的验证码类型 %s",
"Cannot find the user by %s": "无法用 %s 找到相关用户",
"Unable to get password": "无法获取密码",
"Invalid code": "不符合规范的验证码",
"The code is incorrect": "无效的验证码",
"The code has expired": "失效的验证码",
"Invalid arguments %s": "不合法的参数 %s",
"Login fail, check your username and password": "登陆失败,请检查用户名/密码",
"User dose not exist": "用户不存在",
"Username %s already exists": "用户名 %s 已存在",
"Upper char": "大写字母",
"Lower char": "小写字母",
"Number": "数字",
"Special char": "特殊字符",
"Must include %s": "必须包含 %s",
"Invalid Password, %s": "密码不符合规范, %s",
"character: %s not supported": "不支持的字符 %s",
"Incorrect login/password %s times, you still have %s chances": "登陆失败%d次你还有%d次机会",
"The limited sessions %d": "会话数量限制,最多%d个会话",
"Password has been expired": "密码已过期,请重置密码",
"User is inactive": "用户已禁用",
"User is locked": "用户已锁定",
"User is frozen": "用户已休眠",
"User is writen off": "用户已注销",
"Minimum password length %d": "密码最小长度 %d",
"Password too short (min:%d) %s": "密码太短 (最小 %d) %s",
"%s format error":"%s 所填内容不符合规范",
"%s %s format error":"%s %s 所填内容不符合规范",
"username too long (max:%d)": "用户名太长 (最长:%d)",
"dispname too long (max:%d)": "昵称太长 (最长:%d)",
"email %s or phone %s is exists": "邮箱 %s 或者 手机号 %s 已存在",
"Password is not set": "密码未设置",
"Incorrect old password": "密码错误",
"The password is the same as the old password": "密码与历史密码重复",
"phone": "手机号",
"email": "邮箱",
"username": "用户名",
"dispname": "昵称",
"Temporary user has expired": "临时账户,已过有效期",
"Invalid user status %d": "异常的用户状态 %d",
"Password expired, please change the password in time": "密码过期,请及时修改密码",
"First Login, please change the password in time": "初始登陆,请及时修改密码",
"EOF": ""
}
}
}

View File

@ -3,7 +3,7 @@ ip:
specify: ""
shell: ifconfig `route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1|awk -F':' '{print $NF}'
# MON、JOB的客户端拿来做本机标识
# MON、JOB, judge, prober 的客户端拿来做本机标识
ident:
specify: ""
shell: ifconfig `route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1|awk -F':' '{print $NF}'
shell: ifconfig `route|grep '^default'|awk '{print $NF}'`|grep inet|awk '{print $2}'|head -n 1|awk -F':' '{print $NF}'

View File

@ -6,7 +6,7 @@ logger:
http:
mode: release
cookieDomain: ""
cookieName: ecmc-user
cookieName: ecmc-sid
output:
# database | remote

View File

@ -1 +1 @@
您好,您的登录验证码为 {{.Code}}
您好,您的验证码为 {{.Code}}

View File

@ -1 +1 @@
您好,您的登录验证码为 {{.Code}}
您好,您的验证码为 {{.Code}}

View File

@ -25,6 +25,8 @@ redis:
# conn: 500
# read: 3000
# write: 3000
i18n:
lang: zh
notify:
p1: ["voice", "sms", "mail", "im"]
@ -36,3 +38,8 @@ link:
stra: http://n9e.com/mon/strategy/%v
event: http://n9e.com/mon/history/his/%v
claim: http://n9e.com/mon/history/cur/%v
http:
mode: release
cookieDomain: ""
cookieName: ecmc-sid

856
etc/plugins/mysql.yml Normal file
View File

@ -0,0 +1,856 @@
metrics:
- name: mysql_queries
type: COUNTER
- name: mysql_transactions
type: COUNTER
expr: mysql_com_commit + mysql_com_rollback
- name: mysql_threads_running
type: GAUGE
comment: "并发数"
- name: mysql_threads_connected
type: GAUGE
comment: "当前连接数"
- name: mysql_variables_max_connections
type: GAUGE
comment: "最大连接数"
- name: mysql_connections_threshold
type: GAUGE
expr: mysql_threads_connected / mysql_variables_max_connections
comment: "连接数阈值 < 0.8"
- name: mysql_innodb_buffer_pool_read_requests
type: COUNTER
comment: "innodb缓冲池查询总数"
- name: mysql_innodb_buffer_pool_reads
type: COUNTER
comment: "innodb从磁盘查询数"
- name: mysql_innodb_buffer_read_threshold
type: COUNTER
expr: (mysql_innodb_buffer_pool_read_requests - mysql_innodb_buffer_pool_reads) / mysql_innodb_buffer_pool_read_requests
comment: "磁盘查询报警阈值 < 0.95"
- name: mysql_binary_files_count
type: COUNTER
- name: mysql_binary_size_bytes
type: COUNTER
- name: mysql_binlog_bytes_written
type: COUNTER
- name: mysql_binlog_cache_disk_use
type: COUNTER
- name: mysql_binlog_cache_use
type: COUNTER
- name: mysql_binlog_commits
type: COUNTER
- name: mysql_com_begin
type: COUNTER
- name: mysql_com_binlog
type: COUNTER
- name: mysql_com_commit
type: COUNTER
- name: mysql_com_create_table
type: COUNTER
- name: mysql_com_delete
type: COUNTER
- name: mysql_com_delete_multi
type: COUNTER
- name: mysql_com_drop_table
type: COUNTER
- name: mysql_com_empty_query
type: COUNTER
- name: mysql_com_execute_sql
type: COUNTER
- name: mysql_com_flush
type: COUNTER
- name: mysql_com_insert
type: COUNTER
- name: mysql_com_lock_tables
type: COUNTER
- name: mysql_com_rollback
type: COUNTER
- name: mysql_com_stmt_close
type: COUNTER
- name: mysql_com_stmt_execute
type: COUNTER
- name: mysql_com_stmt_prepare
type: COUNTER
- name: mysql_com_stmt_reprepare
type: COUNTER
- name: mysql_com_stmt_reset
type: COUNTER
- name: mysql_com_stmt_fetch
type: COUNTER
- name: mysql_com_update
type: COUNTER
- name: mysql_com_update_multi
type: COUNTER
- name: mysql_compression
type: COUNTER
- name: mysql_connections
type: GAUGE
- name: mysql_max_used_connections
type: COUNTER
- name: mysql_open_files
type: GAUGE
- name: mysql_open_streams
type: GAUGE
- name: mysql_open_tables
type: GAUGE
- name: mysql_opened_files
type: COUNTER
- name: mysql_opened_table_definitions
type: COUNTER
- name: mysql_opened_tables
type: COUNTER
- name: mysql_opened_views
type: COUNTER
- name: mysql_rows_read
type: COUNTER
- name: mysql_rows_sent
type: COUNTER
- name: mysql_sort_rows
type: COUNTER
trash:
- name: mysql_aborted_clients
type: COUNTER
comment: "mysql aborted clients number"
- name: mysql_aborted_connects
type: COUNTER
- name: mysql_access_denied_errors
type: COUNTER
- name: mysql_aria_pagecache_blocks_not_flushed
type: COUNTER
- name: mysql_aria_pagecache_blocks_unused
type: COUNTER
- name: mysql_aria_pagecache_blocks_used
type: COUNTER
- name: mysql_aria_pagecache_read_requests
type: COUNTER
- name: mysql_aria_pagecache_reads
type: COUNTER
- name: mysql_aria_pagecache_write_requests
type: COUNTER
- name: mysql_aria_pagecache_writes
type: COUNTER
- name: mysql_aria_transaction_log_syncs
type: COUNTER
- name: mysql_binlog_group_commits
- name: mysql_binlog_snapshot_position
- name: mysql_binlog_stmt_cache_disk_use
- name: mysql_binlog_stmt_cache_use
- name: mysql_busy_time
- name: mysql_bytes_received
- name: mysql_bytes_sent
- name: mysql_com_admin_commands
- name: mysql_com_alter_db
- name: mysql_com_alter_db_upgrade
- name: mysql_com_alter_event
- name: mysql_com_alter_function
- name: mysql_com_alter_procedure
- name: mysql_com_alter_server
- name: mysql_com_alter_table
- name: mysql_com_alter_tablespace
- name: mysql_com_analyze
- name: mysql_com_assign_to_keycache
- name: mysql_com_call_procedure
- name: mysql_com_change_db
- name: mysql_com_change_master
- name: mysql_com_check
- name: mysql_com_checksum
- name: mysql_com_create_db
- name: mysql_com_create_event
- name: mysql_com_create_function
- name: mysql_com_create_index
- name: mysql_com_create_procedure
- name: mysql_com_create_server
- name: mysql_com_create_trigger
- name: mysql_com_create_udf
- name: mysql_com_create_user
- name: mysql_com_create_view
- name: mysql_com_dealloc_sql
- name: mysql_com_do
- name: mysql_com_drop_db
- name: mysql_com_drop_event
- name: mysql_com_drop_function
- name: mysql_com_drop_index
- name: mysql_com_drop_procedure
- name: mysql_com_drop_server
- name: mysql_com_drop_trigger
- name: mysql_com_drop_user
- name: mysql_com_drop_view
- name: mysql_com_grant
- name: mysql_com_ha_close
- name: mysql_com_ha_open
- name: mysql_com_ha_read
- name: mysql_com_help
- name: mysql_com_insert_select
- name: mysql_com_install_plugin
- name: mysql_com_kill
- name: mysql_com_load
- name: mysql_com_optimize
- name: mysql_com_preload_keys
- name: mysql_com_prepare_sql
- name: mysql_com_purge
- name: mysql_com_purge_before_date
- name: mysql_com_release_savepoint
- name: mysql_com_rename_table
- name: mysql_com_rename_user
- name: mysql_com_repair
- name: mysql_com_replace
- name: mysql_com_replace_select
- name: mysql_com_reset
- name: mysql_com_resignal
- name: mysql_com_revoke
- name: mysql_com_revoke_all
- name: mysql_com_rollback_to_savepoint
- name: mysql_com_savepoint
- name: mysql_com_select
- name: mysql_com_set_option
- name: mysql_com_show_authors
- name: mysql_com_show_binlog_events
- name: mysql_com_show_binlogs
- name: mysql_com_show_charsets
- name: mysql_com_show_client_statistics
- name: mysql_com_show_collations
- name: mysql_com_show_contributors
- name: mysql_com_show_create_db
- name: mysql_com_show_create_event
- name: mysql_com_show_create_func
- name: mysql_com_show_create_proc
- name: mysql_com_show_create_table
- name: mysql_com_show_create_trigger
- name: mysql_com_show_databases
- name: mysql_com_show_engine_logs
- name: mysql_com_show_engine_mutex
- name: mysql_com_show_engine_status
- name: mysql_com_show_errors
- name: mysql_com_show_events
- name: mysql_com_show_fields
- name: mysql_com_show_function_status
- name: mysql_com_show_grants
- name: mysql_com_show_index_statistics
- name: mysql_com_show_keys
- name: mysql_com_show_master_status
- name: mysql_com_show_open_tables
- name: mysql_com_show_plugins
- name: mysql_com_show_privileges
- name: mysql_com_show_procedure_status
- name: mysql_com_show_processlist
- name: mysql_com_show_profile
- name: mysql_com_show_profiles
- name: mysql_com_show_relaylog_events
- name: mysql_com_show_slave_hosts
- name: mysql_com_show_slave_status
- name: mysql_com_show_status
- name: mysql_com_show_storage_engines
- name: mysql_com_show_table_statistics
- name: mysql_com_show_table_status
- name: mysql_com_show_tables
- name: mysql_com_show_triggers
- name: mysql_com_show_user_statistics
- name: mysql_com_show_variables
- name: mysql_com_show_warnings
- name: mysql_com_signal
- name: mysql_com_slave_start
- name: mysql_com_slave_stop
- name: mysql_com_stmt_send_long_data
- name: mysql_com_truncate
- name: mysql_com_uninstall_plugin
- name: mysql_com_unlock_tables
- name: mysql_com_xa_commit
- name: mysql_com_xa_end
- name: mysql_com_xa_prepare
- name: mysql_com_xa_recover
- name: mysql_com_xa_rollback
- name: mysql_com_xa_start
- name: mysql_cpu_time
- name: mysql_created_tmp_disk_tables
- name: mysql_created_tmp_files
- name: mysql_created_tmp_tables
- name: mysql_delayed_errors
- name: mysql_delayed_insert_threads
- name: mysql_delayed_writes
- name: mysql_empty_queries
- name: mysql_executed_events
- name: mysql_executed_triggers
- name: mysql_feature_dynamic_columns
- name: mysql_feature_fulltext
- name: mysql_feature_gis
- name: mysql_feature_locale
- name: mysql_feature_subquery
- name: mysql_feature_timezone
- name: mysql_feature_trigger
- name: mysql_feature_xml
- name: mysql_flush_commands
- name: mysql_handler_commit
- name: mysql_handler_delete
- name: mysql_handler_discover
- name: mysql_handler_icp_attempts
- name: mysql_handler_icp_match
- name: mysql_handler_mrr_init
- name: mysql_handler_mrr_key_refills
- name: mysql_handler_mrr_rowid_refills
- name: mysql_handler_prepare
- name: mysql_handler_read_first
- name: mysql_handler_read_key
- name: mysql_handler_read_last
- name: mysql_handler_read_next
- name: mysql_handler_read_prev
- name: mysql_handler_read_rnd
- name: mysql_handler_read_rnd_deleted
- name: mysql_handler_read_rnd_next
- name: mysql_handler_rollback
- name: mysql_handler_savepoint
- name: mysql_handler_savepoint_rollback
- name: mysql_handler_tmp_update
- name: mysql_handler_tmp_write
- name: mysql_handler_update
- name: mysql_handler_write
- name: mysql_innodb_adaptive_hash_cells
- name: mysql_innodb_adaptive_hash_hash_searches
- name: mysql_innodb_adaptive_hash_heap_buffers
- name: mysql_innodb_adaptive_hash_non_hash_searches
- name: mysql_innodb_background_log_sync
- name: mysql_innodb_buffer_pool_bytes_data
- name: mysql_innodb_buffer_pool_bytes_dirty
- name: mysql_innodb_buffer_pool_pages_data
- name: mysql_innodb_buffer_pool_pages_dirty
- name: mysql_innodb_buffer_pool_pages_flushed
- name: mysql_innodb_buffer_pool_pages_free
- name: mysql_innodb_buffer_pool_pages_lru_flushed
- name: mysql_innodb_buffer_pool_pages_made_not_young
- name: mysql_innodb_buffer_pool_pages_made_young
- name: mysql_innodb_buffer_pool_pages_misc
- name: mysql_innodb_buffer_pool_pages_old
- name: mysql_innodb_buffer_pool_pages_total
- name: mysql_innodb_buffer_pool_read_ahead
- name: mysql_innodb_buffer_pool_read_ahead_evicted
- name: mysql_innodb_buffer_pool_read_ahead_rnd
- name: mysql_innodb_buffer_pool_wait_free
- name: mysql_innodb_buffer_pool_write_requests
- name: mysql_innodb_checkpoint_age
- name: mysql_innodb_checkpoint_max_age
- name: mysql_innodb_checkpoint_target_age
- name: mysql_innodb_current_row_locks
- name: mysql_innodb_data_fsyncs
- name: mysql_innodb_data_pending_fsyncs
- name: mysql_innodb_data_pending_reads
- name: mysql_innodb_data_pending_writes
- name: mysql_innodb_data_read
- name: mysql_innodb_data_reads
- name: mysql_innodb_data_writes
- name: mysql_innodb_data_written
- name: mysql_innodb_dblwr_pages_written
- name: mysql_innodb_dblwr_writes
- name: mysql_innodb_deadlocks
- name: mysql_innodb_descriptors_memory
- name: mysql_innodb_dict_tables
- name: mysql_innodb_have_atomic_builtins
- name: mysql_innodb_history_list_length
- name: mysql_innodb_ibuf_discarded_delete_marks
- name: mysql_innodb_ibuf_discarded_deletes
- name: mysql_innodb_ibuf_discarded_inserts
- name: mysql_innodb_ibuf_free_list
- name: mysql_innodb_ibuf_merged_delete_marks
- name: mysql_innodb_ibuf_merged_deletes
- name: mysql_innodb_ibuf_merged_inserts
- name: mysql_innodb_ibuf_merges
- name: mysql_innodb_ibuf_segment_size
- name: mysql_innodb_ibuf_size
- name: mysql_innodb_log_waits
- name: mysql_innodb_log_write_requests
- name: mysql_innodb_log_writes
- name: mysql_innodb_lsn_current
- name: mysql_innodb_lsn_flushed
- name: mysql_innodb_lsn_last_checkpoint
- name: mysql_innodb_master_thread_10_second_loops
- name: mysql_innodb_master_thread_1_second_loops
- name: mysql_innodb_master_thread_background_loops
- name: mysql_innodb_master_thread_main_flush_loops
- name: mysql_innodb_master_thread_sleeps
- name: mysql_innodb_max_trx_id
- name: mysql_innodb_mem_adaptive_hash
- name: mysql_innodb_mem_dictionary
- name: mysql_innodb_mem_total
- name: mysql_innodb_mutex_os_waits
- name: mysql_innodb_mutex_spin_rounds
- name: mysql_innodb_mutex_spin_waits
- name: mysql_innodb_oldest_view_low_limit_trx_id
- name: mysql_innodb_os_log_fsyncs
- name: mysql_innodb_os_log_pending_fsyncs
- name: mysql_innodb_os_log_pending_writes
- name: mysql_innodb_os_log_written
- name: mysql_innodb_page_size
- name: mysql_innodb_pages_created
- name: mysql_innodb_pages_read
- name: mysql_innodb_pages_written
- name: mysql_innodb_purge_trx_id
- name: mysql_innodb_purge_undo_no
- name: mysql_innodb_read_views_memory
- name: mysql_innodb_row_lock_current_waits
- name: mysql_innodb_row_lock_time
- name: mysql_innodb_row_lock_time_avg
- name: mysql_innodb_row_lock_time_max
- name: mysql_innodb_row_lock_waits
- name: mysql_innodb_rows_deleted
- name: mysql_innodb_rows_inserted
- name: mysql_innodb_rows_read
- name: mysql_innodb_rows_updated
- name: mysql_innodb_s_lock_os_waits
- name: mysql_innodb_s_lock_spin_rounds
- name: mysql_innodb_s_lock_spin_waits
- name: mysql_innodb_truncated_status_writes
- name: mysql_innodb_x_lock_os_waits
- name: mysql_innodb_x_lock_spin_rounds
- name: mysql_innodb_x_lock_spin_waits
- name: mysql_key_blocks_not_flushed
- name: mysql_key_blocks_unused
- name: mysql_key_blocks_used
- name: mysql_key_blocks_warm
- name: mysql_key_read_requests
- name: mysql_key_reads
- name: mysql_key_write_requests
- name: mysql_key_writes
- name: mysql_last_query_cost
- name: mysql_not_flushed_delayed_rows
- name: mysql_open_table_definitions
- name: mysql_performance_schema_cond_classes_lost
- name: mysql_performance_schema_cond_instances_lost
- name: mysql_performance_schema_file_classes_lost
- name: mysql_performance_schema_file_handles_lost
- name: mysql_performance_schema_file_instances_lost
- name: mysql_performance_schema_locker_lost
- name: mysql_performance_schema_mutex_classes_lost
- name: mysql_performance_schema_mutex_instances_lost
- name: mysql_performance_schema_rwlock_classes_lost
- name: mysql_performance_schema_rwlock_instances_lost
- name: mysql_performance_schema_table_handles_lost
- name: mysql_performance_schema_table_instances_lost
- name: mysql_performance_schema_thread_classes_lost
- name: mysql_performance_schema_thread_instances_lost
- name: mysql_prepared_stmt_count
- name: mysql_process_list_threads_after_create
- name: mysql_process_list_threads_altering_table
- name: mysql_process_list_threads_analyzing
- name: mysql_process_list_threads_checking_permissions
- name: mysql_process_list_threads_checking_table
- name: mysql_process_list_threads_cleaning_up
- name: mysql_process_list_threads_closing_tables
- name: mysql_process_list_threads_converting_heap_to_myisam
- name: mysql_process_list_threads_copying_to_tmp_table
- name: mysql_process_list_threads_creating_sort_index
- name: mysql_process_list_threads_creating_table
- name: mysql_process_list_threads_creating_tmp_table
- name: mysql_process_list_threads_deleting
- name: mysql_process_list_threads_end
- name: mysql_process_list_threads_executing
- name: mysql_process_list_threads_execution_of_init_command
- name: mysql_process_list_threads_flushing_tables
- name: mysql_process_list_threads_freeing_items
- name: mysql_process_list_threads_fulltext_initialization
- name: mysql_process_list_threads_idle
- name: mysql_process_list_threads_init
- name: mysql_process_list_threads_killed
- name: mysql_process_list_threads_logging_slow_query
- name: mysql_process_list_threads_login
- name: mysql_process_list_threads_manage_keys
- name: mysql_process_list_threads_opening_tables
- name: mysql_process_list_threads_optimizing
- name: mysql_process_list_threads_other
- name: mysql_process_list_threads_preparing
- name: mysql_process_list_threads_reading_from_net
- name: mysql_process_list_threads_removing_duplicates
- name: mysql_process_list_threads_removing_tmp_table
- name: mysql_process_list_threads_reopen_tables
- name: mysql_process_list_threads_repair_by_sorting
- name: mysql_process_list_threads_repair_done
- name: mysql_process_list_threads_repair_with_keycache
- name: mysql_process_list_threads_replication_master
- name: mysql_process_list_threads_rolling_back
- name: mysql_process_list_threads_searching_rows_for_update
- name: mysql_process_list_threads_sending_data
- name: mysql_process_list_threads_sorting_for_group
- name: mysql_process_list_threads_sorting_for_order
- name: mysql_process_list_threads_sorting_index
- name: mysql_process_list_threads_sorting_result
- name: mysql_process_list_threads_statistics
- name: mysql_process_list_threads_updating
- name: mysql_process_list_threads_waiting_for_lock
- name: mysql_process_list_threads_waiting_for_table_flush
- name: mysql_process_list_threads_waiting_for_tables
- name: mysql_process_list_threads_waiting_on_cond
- name: mysql_process_list_threads_writing_to_net
- name: mysql_qcache_free_blocks
- name: mysql_qcache_free_memory
- name: mysql_qcache_hits
- name: mysql_qcache_inserts
- name: mysql_qcache_lowmem_prunes
- name: mysql_qcache_not_cached
- name: mysql_qcache_queries_in_cache
- name: mysql_qcache_total_blocks
- name: mysql_questions
- name: mysql_rows_tmp_read
- name: mysql_select_full_join
- name: mysql_select_full_range_join
- name: mysql_select_range
- name: mysql_select_range_check
- name: mysql_select_scan
- name: mysql_slave_heartbeat_period
- name: mysql_slave_open_temp_tables
- name: mysql_slave_received_heartbeats
- name: mysql_slave_retried_transactions
- name: mysql_slave_running
- name: mysql_slow_launch_threads
- name: mysql_slow_queries
- name: mysql_sort_merge_passes
- name: mysql_sort_range
- name: mysql_sort_scan
- name: mysql_ssl_accept_renegotiates
- name: mysql_ssl_accepts
- name: mysql_ssl_callback_cache_hits
- name: mysql_ssl_client_connects
- name: mysql_ssl_connect_renegotiates
- name: mysql_ssl_ctx_verify_depth
- name: mysql_ssl_ctx_verify_mode
- name: mysql_ssl_default_timeout
- name: mysql_ssl_finished_accepts
- name: mysql_ssl_finished_connects
- name: mysql_ssl_session_cache_hits
- name: mysql_ssl_session_cache_misses
- name: mysql_ssl_session_cache_overflows
- name: mysql_ssl_session_cache_size
- name: mysql_ssl_session_cache_timeouts
- name: mysql_ssl_sessions_reused
- name: mysql_ssl_used_session_cache_entries
- name: mysql_ssl_verify_depth
- name: mysql_ssl_verify_mode
- name: mysql_subquery_cache_hit
- name: mysql_subquery_cache_miss
- name: mysql_syncs
- name: mysql_table_locks_immediate
- name: mysql_table_locks_waited
- name: mysql_tc_log_max_pages_used
- name: mysql_tc_log_page_size
- name: mysql_tc_log_page_waits
- name: mysql_threadpool_idle_threads
- name: mysql_threadpool_threads
- name: mysql_threads_cached
- name: mysql_threads_created
- name: mysql_uptime
- name: mysql_uptime_since_flush_status
- name: mysql_users_connections
- name: mysql_variables_aria_block_size
- name: mysql_variables_aria_checkpoint_interval
- name: mysql_variables_aria_checkpoint_log_activity
- name: mysql_variables_aria_force_start_after_recovery_failures
- name: mysql_variables_aria_group_commit_interval
- name: mysql_variables_aria_log_file_size
- name: mysql_variables_aria_max_sort_file_size
- name: mysql_variables_aria_page_checksum
- name: mysql_variables_aria_pagecache_age_threshold
- name: mysql_variables_aria_pagecache_buffer_size
- name: mysql_variables_aria_pagecache_division_limit
- name: mysql_variables_aria_repair_threads
- name: mysql_variables_aria_sort_buffer_size
- name: mysql_variables_aria_used_for_temp_tables
- name: mysql_variables_auto_increment_increment
- name: mysql_variables_auto_increment_offset
- name: mysql_variables_autocommit
- name: mysql_variables_automatic_sp_privileges
- name: mysql_variables_back_log
- name: mysql_variables_big_tables
- name: mysql_variables_binlog_annotate_row_events
- name: mysql_variables_binlog_cache_size
- name: mysql_variables_binlog_direct_non_transactional_updates
- name: mysql_variables_binlog_optimize_thread_scheduling
- name: mysql_variables_binlog_stmt_cache_size
- name: mysql_variables_bulk_insert_buffer_size
- name: mysql_variables_connect_timeout
- name: mysql_variables_deadlock_search_depth_long
- name: mysql_variables_deadlock_search_depth_short
- name: mysql_variables_deadlock_timeout_long
- name: mysql_variables_deadlock_timeout_short
- name: mysql_variables_debug_no_thread_alarm
- name: mysql_variables_default_week_format
- name: mysql_variables_delay_key_write
- name: mysql_variables_delayed_insert_limit
- name: mysql_variables_delayed_insert_timeout
- name: mysql_variables_delayed_queue_size
- name: mysql_variables_div_precision_increment
- name: mysql_variables_engine_condition_pushdown
- name: mysql_variables_event_scheduler
- name: mysql_variables_expensive_subquery_limit
- name: mysql_variables_expire_logs_days
- name: mysql_variables_extra_max_connections
- name: mysql_variables_extra_port
- name: mysql_variables_flush
- name: mysql_variables_flush_time
- name: mysql_variables_foreign_key_checks
- name: mysql_variables_ft_max_word_len
- name: mysql_variables_ft_min_word_len
- name: mysql_variables_ft_query_expansion_limit
- name: mysql_variables_general_log
- name: mysql_variables_group_concat_max_len
- name: mysql_variables_have_compress
- name: mysql_variables_have_crypt
- name: mysql_variables_have_csv
- name: mysql_variables_have_dynamic_loading
- name: mysql_variables_have_geometry
- name: mysql_variables_have_innodb
- name: mysql_variables_have_ndbcluster
- name: mysql_variables_have_partitioning
- name: mysql_variables_have_profiling
- name: mysql_variables_have_query_cache
- name: mysql_variables_have_rtree_keys
- name: mysql_variables_ignore_builtin_innodb
- name: mysql_variables_innodb_adaptive_flushing
- name: mysql_variables_innodb_adaptive_hash_index
- name: mysql_variables_innodb_adaptive_hash_index_partitions
- name: mysql_variables_innodb_additional_mem_pool_size
- name: mysql_variables_innodb_autoextend_increment
- name: mysql_variables_innodb_autoinc_lock_mode
- name: mysql_variables_innodb_blocking_buffer_pool_restore
- name: mysql_variables_innodb_buffer_pool_instances
- name: mysql_variables_innodb_buffer_pool_populate
- name: mysql_variables_innodb_buffer_pool_restore_at_startup
- name: mysql_variables_innodb_buffer_pool_shm_checksum
- name: mysql_variables_innodb_buffer_pool_shm_key
- name: mysql_variables_innodb_buffer_pool_size
- name: mysql_variables_innodb_checkpoint_age_target
- name: mysql_variables_innodb_checksums
- name: mysql_variables_innodb_commit_concurrency
- name: mysql_variables_innodb_concurrency_tickets
- name: mysql_variables_innodb_dict_size_limit
- name: mysql_variables_innodb_doublewrite
- name: mysql_variables_innodb_fake_changes
- name: mysql_variables_innodb_fast_checksum
- name: mysql_variables_innodb_fast_shutdown
- name: mysql_variables_innodb_file_format_check
- name: mysql_variables_innodb_file_per_table
- name: mysql_variables_innodb_flush_log_at_trx_commit
- name: mysql_variables_innodb_force_load_corrupted
- name: mysql_variables_innodb_force_recovery
- name: mysql_variables_innodb_ibuf_accel_rate
- name: mysql_variables_innodb_ibuf_active_contract
- name: mysql_variables_innodb_ibuf_max_size
- name: mysql_variables_innodb_import_table_from_xtrabackup
- name: mysql_variables_innodb_io_capacity
- name: mysql_variables_innodb_kill_idle_transaction
- name: mysql_variables_innodb_large_prefix
- name: mysql_variables_innodb_lazy_drop_table
- name: mysql_variables_innodb_lock_wait_timeout
- name: mysql_variables_innodb_locking_fake_changes
- name: mysql_variables_innodb_locks_unsafe_for_binlog
- name: mysql_variables_innodb_log_block_size
- name: mysql_variables_innodb_log_buffer_size
- name: mysql_variables_innodb_log_file_size
- name: mysql_variables_innodb_log_files_in_group
- name: mysql_variables_innodb_max_bitmap_file_size
- name: mysql_variables_innodb_max_changed_pages
- name: mysql_variables_innodb_max_dirty_pages_pct
- name: mysql_variables_innodb_max_purge_lag
- name: mysql_variables_innodb_merge_sort_block_size
- name: mysql_variables_innodb_mirrored_log_groups
- name: mysql_variables_innodb_old_blocks_pct
- name: mysql_variables_innodb_old_blocks_time
- name: mysql_variables_innodb_open_files
- name: mysql_variables_innodb_page_size
- name: mysql_variables_innodb_print_all_deadlocks
- name: mysql_variables_innodb_purge_batch_size
- name: mysql_variables_innodb_purge_threads
- name: mysql_variables_innodb_random_read_ahead
- name: mysql_variables_innodb_read_ahead_threshold
- name: mysql_variables_innodb_read_io_threads
- name: mysql_variables_innodb_recovery_stats
- name: mysql_variables_innodb_recovery_update_relay_log
- name: mysql_variables_innodb_replication_delay
- name: mysql_variables_innodb_rollback_on_timeout
- name: mysql_variables_innodb_rollback_segments
- name: mysql_variables_innodb_show_locks_held
- name: mysql_variables_innodb_show_verbose_locks
- name: mysql_variables_innodb_simulate_comp_failures
- name: mysql_variables_innodb_spin_wait_delay
- name: mysql_variables_innodb_stats_auto_update
- name: mysql_variables_innodb_stats_modified_counter
- name: mysql_variables_innodb_stats_on_metadata
- name: mysql_variables_innodb_stats_sample_pages
- name: mysql_variables_innodb_stats_traditional
- name: mysql_variables_innodb_stats_update_need_lock
- name: mysql_variables_innodb_strict_mode
- name: mysql_variables_innodb_support_xa
- name: mysql_variables_innodb_sync_spin_loops
- name: mysql_variables_innodb_table_locks
- name: mysql_variables_innodb_thread_concurrency
- name: mysql_variables_innodb_thread_concurrency_timer_based
- name: mysql_variables_innodb_thread_sleep_delay
- name: mysql_variables_innodb_track_changed_pages
- name: mysql_variables_innodb_use_atomic_writes
- name: mysql_variables_innodb_use_fallocate
- name: mysql_variables_innodb_use_global_flush_log_at_trx_commit
- name: mysql_variables_innodb_use_native_aio
- name: mysql_variables_innodb_use_stacktrace
- name: mysql_variables_innodb_use_sys_malloc
- name: mysql_variables_innodb_use_sys_stats_table
- name: mysql_variables_innodb_write_io_threads
- name: mysql_variables_interactive_timeout
- name: mysql_variables_join_buffer_size
- name: mysql_variables_join_buffer_space_limit
- name: mysql_variables_join_cache_level
- name: mysql_variables_keep_files_on_create
- name: mysql_variables_key_buffer_size
- name: mysql_variables_key_cache_age_threshold
- name: mysql_variables_key_cache_block_size
- name: mysql_variables_key_cache_division_limit
- name: mysql_variables_key_cache_segments
- name: mysql_variables_large_files_support
- name: mysql_variables_large_page_size
- name: mysql_variables_large_pages
- name: mysql_variables_local_infile
- name: mysql_variables_lock_wait_timeout
- name: mysql_variables_locked_in_memory
- name: mysql_variables_log
- name: mysql_variables_log_bin
- name: mysql_variables_log_bin_trust_function_creators
- name: mysql_variables_log_queries_not_using_indexes
- name: mysql_variables_log_slave_updates
- name: mysql_variables_log_slow_queries
- name: mysql_variables_log_slow_rate_limit
- name: mysql_variables_log_warnings
- name: mysql_variables_long_query_time
- name: mysql_variables_low_priority_updates
- name: mysql_variables_lower_case_file_system
- name: mysql_variables_lower_case_table_names
- name: mysql_variables_master_verify_checksum
- name: mysql_variables_max_allowed_packet
- name: mysql_variables_max_binlog_cache_size
- name: mysql_variables_max_binlog_size
- name: mysql_variables_max_binlog_stmt_cache_size
- name: mysql_variables_max_connect_errors
- name: mysql_variables_max_delayed_threads
- name: mysql_variables_max_error_count
- name: mysql_variables_max_heap_table_size
- name: mysql_variables_max_insert_delayed_threads
- name: mysql_variables_max_join_size
- name: mysql_variables_max_length_for_sort_data
- name: mysql_variables_max_long_data_size
- name: mysql_variables_max_prepared_stmt_count
- name: mysql_variables_max_relay_log_size
- name: mysql_variables_max_seeks_for_key
- name: mysql_variables_max_sort_length
- name: mysql_variables_max_sp_recursion_depth
- name: mysql_variables_max_tmp_tables
- name: mysql_variables_max_user_connections
- name: mysql_variables_max_write_lock_count
- name: mysql_variables_metadata_locks_cache_size
- name: mysql_variables_min_examined_row_limit
- name: mysql_variables_mrr_buffer_size
- name: mysql_variables_multi_range_count
- name: mysql_variables_myisam_block_size
- name: mysql_variables_myisam_data_pointer_size
- name: mysql_variables_myisam_max_sort_file_size
- name: mysql_variables_myisam_mmap_size
- name: mysql_variables_myisam_repair_threads
- name: mysql_variables_myisam_sort_buffer_size
- name: mysql_variables_myisam_use_mmap
- name: mysql_variables_net_buffer_length
- name: mysql_variables_net_read_timeout
- name: mysql_variables_net_retry_count
- name: mysql_variables_net_write_timeout
- name: mysql_variables_old
- name: mysql_variables_old_alter_table
- name: mysql_variables_old_passwords
- name: mysql_variables_open_files_limit
- name: mysql_variables_optimizer_prune_level
- name: mysql_variables_optimizer_search_depth
- name: mysql_variables_performance_schema
- name: mysql_variables_performance_schema_events_waits_history_long_size
- name: mysql_variables_performance_schema_events_waits_history_size
- name: mysql_variables_performance_schema_max_cond_classes
- name: mysql_variables_performance_schema_max_cond_instances
- name: mysql_variables_performance_schema_max_file_classes
- name: mysql_variables_performance_schema_max_file_handles
- name: mysql_variables_performance_schema_max_file_instances
- name: mysql_variables_performance_schema_max_mutex_classes
- name: mysql_variables_performance_schema_max_mutex_instances
- name: mysql_variables_performance_schema_max_rwlock_classes
- name: mysql_variables_performance_schema_max_rwlock_instances
- name: mysql_variables_performance_schema_max_table_handles
- name: mysql_variables_performance_schema_max_table_instances
- name: mysql_variables_performance_schema_max_thread_classes
- name: mysql_variables_performance_schema_max_thread_instances
- name: mysql_variables_port
- name: mysql_variables_preload_buffer_size
- name: mysql_variables_profiling
- name: mysql_variables_profiling_history_size
- name: mysql_variables_progress_report_time
- name: mysql_variables_protocol_version
- name: mysql_variables_query_alloc_block_size
- name: mysql_variables_query_cache_limit
- name: mysql_variables_query_cache_min_res_unit
- name: mysql_variables_query_cache_size
- name: mysql_variables_query_cache_strip_comments
- name: mysql_variables_query_cache_type
- name: mysql_variables_query_cache_wlock_invalidate
- name: mysql_variables_query_prealloc_size
- name: mysql_variables_range_alloc_block_size
- name: mysql_variables_read_buffer_size
- name: mysql_variables_read_only
- name: mysql_variables_read_rnd_buffer_size
- name: mysql_variables_relay_log_purge
- name: mysql_variables_relay_log_recovery
- name: mysql_variables_relay_log_space_limit
- name: mysql_variables_replicate_annotate_row_events
- name: mysql_variables_report_port
- name: mysql_variables_rowid_merge_buff_size
- name: mysql_variables_rpl_recovery_rank
- name: mysql_variables_secure_auth
- name: mysql_variables_server_id
- name: mysql_variables_skip_external_locking
- name: mysql_variables_skip_name_resolve
- name: mysql_variables_skip_networking
- name: mysql_variables_skip_show_database
- name: mysql_variables_slave_compressed_protocol
- name: mysql_variables_slave_max_allowed_packet
- name: mysql_variables_slave_net_timeout
- name: mysql_variables_slave_skip_errors
- name: mysql_variables_slave_sql_verify_checksum
- name: mysql_variables_slave_transaction_retries
- name: mysql_variables_slow_launch_time
- name: mysql_variables_slow_query_log
- name: mysql_variables_sort_buffer_size
- name: mysql_variables_sql_auto_is_null
- name: mysql_variables_sql_big_selects
- name: mysql_variables_sql_big_tables
- name: mysql_variables_sql_buffer_result
- name: mysql_variables_sql_log_bin
- name: mysql_variables_sql_log_off
- name: mysql_variables_sql_low_priority_updates
- name: mysql_variables_sql_max_join_size
- name: mysql_variables_sql_notes
- name: mysql_variables_sql_quote_show_create
- name: mysql_variables_sql_safe_updates
- name: mysql_variables_sql_select_limit
- name: mysql_variables_sql_slave_skip_counter
- name: mysql_variables_sql_warnings
- name: mysql_variables_stored_program_cache
- name: mysql_variables_sync_binlog
- name: mysql_variables_sync_frm
- name: mysql_variables_sync_master_info
- name: mysql_variables_sync_relay_log
- name: mysql_variables_sync_relay_log_info
- name: mysql_variables_table_definition_cache
- name: mysql_variables_table_open_cache
- name: mysql_variables_thread_cache_size
- name: mysql_variables_thread_concurrency
- name: mysql_variables_thread_pool_idle_timeout
- name: mysql_variables_thread_pool_max_threads
- name: mysql_variables_thread_pool_oversubscribe
- name: mysql_variables_thread_pool_size
- name: mysql_variables_thread_pool_stall_limit
- name: mysql_variables_thread_stack
- name: mysql_variables_timed_mutexes
- name: mysql_variables_tmp_table_size
- name: mysql_variables_transaction_alloc_block_size
- name: mysql_variables_transaction_prealloc_size
- name: mysql_variables_unique_checks
- name: mysql_variables_updatable_views_with_limit
- name: mysql_variables_userstat
- name: mysql_variables_wait_timeout

8
etc/prober.yml Normal file
View File

@ -0,0 +1,8 @@
region: default
workerProcesses: 5
logger:
dir: logs/prober
level: DEBUG
keepHours: 24
pluginsConfig: etc/plugins
ignoreConfig: false

View File

@ -5,8 +5,15 @@ logger:
http:
mode: release
cookieDomain: ""
cookieName: ecmc-user
session:
cookieName: ecmc-sid
domain: ""
httpOnly: true
gcInterval: 60
cookieLifetime: 86400 # 单位秒0: 与浏览器相同
i18n:
lang: zh
sso:
enable: false
@ -23,7 +30,13 @@ sso:
coverAttributes: false
stateExpiresIn: 300
captcha: false
auth:
captcha: false
extraMode:
enable: false # enable whiteList, login retry lock, userControl, ...
whiteList: false
frozenDays: 90 # frozen time (day)
writenOffDays: 365 # writenOff time (day)
tokens:
- rdb-builtin-token

8
go.mod
View File

@ -3,28 +3,28 @@ module github.com/didi/nightingale
go 1.12
require (
github.com/Shopify/sarama v1.19.0
github.com/Shopify/sarama v1.27.1
github.com/cespare/xxhash v1.1.0
github.com/codegangsta/negroni v1.0.0
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/dgryski/go-tsz v0.0.0-20180227144327-03b7d791f4fe
github.com/eapache/go-resiliency v1.2.0 // indirect
github.com/garyburd/redigo v1.6.2
github.com/gin-contrib/pprof v1.3.0
github.com/gin-gonic/gin v1.6.3
github.com/go-sql-driver/mysql v1.5.0
github.com/google/go-github/v32 v32.1.0
github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f
github.com/gorilla/mux v1.7.3
github.com/hashicorp/golang-lru v0.5.4
github.com/hpcloud/tail v1.0.0
github.com/influxdata/influxdb v1.8.0
github.com/influxdata/telegraf v1.16.2
github.com/m3db/m3 v0.15.17
github.com/mattn/go-isatty v0.0.12
github.com/mattn/go-sqlite3 v1.14.0 // indirect
github.com/mojocn/base64Captcha v1.3.1
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad
github.com/pquerna/cachecontrol v0.0.0-20200819021114-67c6ae64274f // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 // indirect
github.com/shirou/gopsutil v3.20.11+incompatible
github.com/spaolacci/murmur3 v1.1.0
@ -35,10 +35,8 @@ require (
github.com/ugorji/go/codec v1.1.7
github.com/unrolled/render v1.0.3
go.uber.org/automaxprocs v1.3.0 // indirect
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/text v0.3.3
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df

272
go.sum
View File

@ -9,25 +9,44 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM=
cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0 h1:MZQCQQaRwOrAcuKjiHWHrgKykt4fZyuwF2dtiG3fGW8=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8=
collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-amqp-common-go/v3 v3.0.0/go.mod h1:SY08giD/XbhTz07tJdpw1SoxQXHPN30+DI3Z04SYqyg=
github.com/Azure/azure-event-hubs-go/v3 v3.2.0/go.mod h1:BPIIJNH/l/fVHYq3Rm6eg4clbrULrQ3q7+icmqHyyLc=
github.com/Azure/azure-pipeline-go v0.1.8/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg=
github.com/Azure/azure-pipeline-go v0.1.9/go.mod h1:XA1kFWRVhSK+KNFiOhfv83Fv8L9achrP7OxIzeTn1Yg=
github.com/Azure/azure-sdk-for-go v37.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go v40.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-storage-blob-go v0.6.0/go.mod h1:oGfmITT1V6x//CswqY2gtAHND+xIP64/qL7a5QJix0Y=
github.com/Azure/azure-storage-queue-go v0.0.0-20181215014128-6ed74e755687/go.mod h1:K6am8mT+5iFXgingS9LUc7TmbsW6XBw3nxaRyaMyWc8=
github.com/Azure/go-amqp v0.12.6/go.mod h1:qApuH6OFTSKZFmCOxccvAv5rLizBQf4v8pRmG138DPo=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI=
github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0=
github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
@ -49,12 +68,14 @@ github.com/DataDog/datadog-go v3.7.1+incompatible h1:HmA9qHVrHIAqpSvoCYJ+c6qst0l
github.com/DataDog/datadog-go v3.7.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Mellanox/rdmamap v0.0.0-20191106181932-7c3c4763a6ee/go.mod h1:jDA6v0TUYrFEIAE5uGJ29LQOeONIgMdP4Rkqb8HUnPM=
github.com/MichaelTJones/pcg v0.0.0-20180122055547-df440c6ed7ed h1:hQC4FSwvsLH6rOLJTndsHnANARF9RwW4PbrDTjks/0A=
github.com/MichaelTJones/pcg v0.0.0-20180122055547-df440c6ed7ed/go.mod h1:NQ4UMHqyfXyYVmZopcfwPRWJa0rw2aH16eDIltReVUo=
github.com/Microsoft/ApplicationInsights-Go v0.4.2/go.mod h1:CukZ/G66zxXtI+h/VcVn3eVVDGDHfXM2zVILF7bMmsg=
github.com/Microsoft/go-winio v0.4.9/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
@ -66,36 +87,46 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/RoaringBitmap/roaring v0.4.21 h1:WJ/zIlNX4wQZ9x8Ey33O1UaD9TCTakYsdLFSBcTwH+8=
github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/sarama v1.27.1 h1:iUlzHymqWsITyttu6KxazcAz8WEj5FqcwFK/oEi7rE8=
github.com/Shopify/sarama v1.27.1/go.mod h1:g5s5osgELxgM+Md9Qni9rzo7Rbt+vvFQI4bt/Mc93II=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/aerospike/aerospike-client-go v1.27.0/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/amir/raidman v0.0.0-20170415203553-1ccc43bfb9c9/go.mod h1:eliMa/PW+RDr2QLWRmLH1R1ZA4RInpmvOzDDXtaIZkc=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
github.com/apache/arrow/go/arrow v0.0.0-20191024131854-af6fa24be0db/go.mod h1:VTxUBvSJ3s3eHAg65PNgrsn5BtqCRPdmyXh6rAfdxN0=
github.com/apex/log v1.3.0 h1:1fyfbPvUwD10nMoh3hY6MXzvZShJQn9/ck7ATgAt5pA=
github.com/apex/log v1.3.0/go.mod h1:jd8Vpsr46WAe3EZSQ/IUMs2qQD/GOycT5rPWCO1yGcs=
github.com/apex/logs v0.0.4/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
github.com/aristanetworks/glog v0.0.0-20191112221043-67e8567f59f3/go.mod h1:KASm+qXFKs/xjSoWn30NrWBBvdTTQq+UjkhjEJHfSFA=
github.com/aristanetworks/goarista v0.0.0-20190325233358-a123909ec740/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs=
github.com/armon/go-metrics v0.3.4 h1:Xqf+7f2Vhl9tsqDYmXhnXInUdcrtgpRNpIA15/uldSc=
github.com/armon/go-metrics v0.3.4/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@ -107,16 +138,20 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.29.18/go.mod h1:1KvfttTE3SPKMpo8g2c6jL3ZKfXtFvKscTgahTma5Xg=
github.com/aws/aws-sdk-go v1.34.34/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
github.com/bmatcuk/doublestar v1.3.1/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDfD5VcY4CIfh1hRXBUavxrvELjTiOE=
github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q=
@ -124,8 +159,10 @@ github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx2
github.com/briandowns/spinner v1.11.1/go.mod h1:QOuQk7x+EaDASo80FEXwlwiA+j/PPIcX3FScO+3/ZPQ=
github.com/c-bata/go-prompt v0.2.2/go.mod h1:VzqtzE2ksDBcdln8G7mk2RX9QyGjH+OVqOCSiVIqS34=
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
github.com/caio/go-tdigest v2.3.0+incompatible/go.mod h1:sHQM/ubZStBUmF1WbB8FAm8q9GjDajLC5T7ydxE3JHI=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v0.0.0-20181003080854-62661b46c409/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
@ -139,15 +176,18 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cisco-ie/nx-telemetry-proto v0.0.0-20190531143454-82441e232cf6/go.mod h1:ugEfq4B8T8ciw/h5mCkgdiDRFS4CkqqhH2dymDB4knc=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY=
github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -164,9 +204,13 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/couchbase/go-couchbase v0.0.0-20180501122049-16db1f1fe037/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20180502221210-0da75df14530/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -174,20 +218,26 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
github.com/dgryski/go-bitstream v0.0.0-20180413035011-3522498ce2c8/go.mod h1:VMaSuZ+SZcx/wljOQKvp5srsbCiKDEb6K2wC4+PiBmQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dgryski/go-tsz v0.0.0-20180227144327-03b7d791f4fe h1:VOrqop9SqFzqwZpROEOZpIufuLEUoJ3reNhdOdC9Zzw=
github.com/dgryski/go-tsz v0.0.0-20180227144327-03b7d791f4fe/go.mod h1:ft6P746mYUFQBCsH3OkFBG8FtjLx1XclLMo+9Jh1Yts=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/docker/distribution v2.6.0-rc.1.0.20170726174610-edc3ab29cdff+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/docker/libnetwork v0.8.0-dev.2.0.20181012153825-d7b61745d166/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4 h1:qk/FSDDxo05wdJH28W+p5yivv7LuLYLRXPPD8KQCtZs=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@ -208,16 +258,21 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ericchiang/k8s v1.2.0 h1:vxrMwEzY43oxu8aZyD/7b1s8tsBM+xoUoxjWECWFbPI=
github.com/ericchiang/k8s v1.2.0/go.mod h1:/OmBgSq2cd9IANnsGHGlEz27nwMZV2YxlpXuQtU3Bz4=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fortytw2/leaktest v1.2.1-0.20180901000122-b433bbd6d743 h1:QDM8xNoGxemDHdExynv+HzqkTPsFFZ8EyZdMwGElpGg=
github.com/fortytw2/leaktest v1.2.1-0.20180901000122-b433bbd6d743/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fossas/fossa-cli v1.0.30/go.mod h1:5K4/qTj0P2qaT1G3SccFidhmazoJ9dm/OexAAYT8lOI=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.10.2 h1:19ARM85nVi4xH7xPXuc5eM/udya5ieh7b/Sv+d844Tk=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garethr/kubeval v0.0.0-20180821130434-c44f5193dc94/go.mod h1:L8VwozDBY4bGI25r29I6FURZus8xlVo/B7lNOSfre2g=
@ -225,6 +280,8 @@ github.com/garyburd/redigo v1.6.2 h1:yE/pwKCrbLpLpQICzYTeZ7JsTA/C53wFTJHaEtRqniM
github.com/garyburd/redigo v1.6.2/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@ -233,6 +290,7 @@ github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/glinton/ping v0.1.4-0.20200311211934-5ac87da8cd96/go.mod h1:uY+1eqFUyotrQxF1wYFNtMeHp/swbYRsoGzfcPZ8x3o=
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
@ -243,6 +301,7 @@ github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1T
github.com/gnewton/jargo v0.0.0-20150417131352-41f5f186a805/go.mod h1:x+HLDnZexLq1FmhrdgFf4c3EWGbqhU3ITvISBFyzvRo=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@ -250,6 +309,7 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
@ -299,6 +359,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
@ -306,6 +368,10 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/goburrow/modbus v0.1.0/go.mod h1:Kx552D5rLIS8E7TyUwQ/UdHEqvX5T8tyiGBTlzMcZBg=
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gofrs/uuid v2.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -321,19 +387,22 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -356,8 +425,13 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8=
github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-jsonnet v0.16.0/go.mod h1:sOcuej3UW1vpPTZOr8L7RQimqai1a57bt5j22LzGZCw=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -365,6 +439,7 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -374,18 +449,16 @@ github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f/go.mod h1:TIyPZe4Mgq
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gopcua/opcua v0.1.12/go.mod h1:a6QH4F9XeODklCmWuvaOdL8v9H0d73CEKUHWVZLQyE8=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.8.0/go.mod h1:Kc/QKr9thLKruO/dG0szY8kRIYS+iENz0ziI0hJf76A=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YARg=
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@ -407,6 +480,9 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.14.1 h1:YuM9SXYy583fxvSOkzCDyBPCtY+/IMSHEG1dKFMLZsA=
github.com/grpc-ecosystem/grpc-gateway v1.14.1/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/harlow/kinesis-consumer v0.3.1-0.20181230152818-2f58b136fee0/go.mod h1:dk23l2BruuUzRP8wbybQbPn3J7sZga2QHICCeaEy5rQ=
github.com/hashicorp/consul v1.2.1/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/api v1.4.0/go.mod h1:xc8u05kyMa3Wjr9eEAsIAo3dg8+LywT5E/Cl7cNS5nU=
@ -428,6 +504,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v0.0.0-20160503143440-6bb64b370b90/go.mod h1:o4zcYY1e0GEZI6eSEr+43QDYmuGglw1qSO6qdHUHCgg=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@ -441,7 +518,6 @@ github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
@ -453,8 +529,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2 h1:5+RffWKwqJ71YPu9mWsF7ZOscZmwfasdA8kbdC7AO2g=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
@ -466,6 +544,7 @@ github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJ
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/flux v0.65.0/go.mod h1:BwN2XG2lMszOoquQaFdPET8FRQfrXiZsWmcMO9rkaVY=
github.com/influxdata/go-syslog/v2 v2.0.1/go.mod h1:hjvie1UTaD5E1fTnDmxaCw8RRDrT4Ve+XHr5O2dKSCo=
github.com/influxdata/influxdb v1.7.7/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY=
github.com/influxdata/influxdb v1.8.0 h1:/X+G+i3udzHVxpBMuXdPZcUbkIE0ouT+6U+CzQTsOys=
github.com/influxdata/influxdb v1.8.0/go.mod h1:SIzcnsjaHRFpmlxpJ4S3NT64qtEKYweNTUMb/vh0OMQ=
@ -474,8 +553,15 @@ github.com/influxdata/influxql v1.1.0/go.mod h1:KpVI7okXjK6PRi3Z5B+mtKZli+R1DnZg
github.com/influxdata/line-protocol v0.0.0-20180522152040-32c6aa80de5e/go.mod h1:4kt73NQhadE3daL3WhR5EJ/J2ocX0PZzwxQ0gXJ7oFE=
github.com/influxdata/promql/v2 v2.12.0/go.mod h1:fxOPu+DY0bqCTCECchSRtWfc+0X19ybifQhZoQNF5D8=
github.com/influxdata/roaring v0.4.13-0.20180809181101-fc520f41fab6/go.mod h1:bSgUQ7q5ZLSO+bKBGqJiCBGAl+9DxyW63zLTujjUlOE=
github.com/influxdata/tail v1.0.1-0.20200707181643-03a791b270e4/go.mod h1:VeiWgI3qaGdJWust2fP27a6J+koITo/1c/UhxeOxgaM=
github.com/influxdata/tdigest v0.0.0-20181121200506-bf2b5ad3c0a9/go.mod h1:Js0mqiSBE6Ffsg94weZZ2c+v/ciT8QRHFOap7EKDrR0=
github.com/influxdata/telegraf v1.16.2 h1:G988b0+CL2IVDft9V2ZUteKgnbp+eI7vtQ0liPhQKxw=
github.com/influxdata/telegraf v1.16.2/go.mod h1:LZ/6hlf60cwqGr8phfbRKf8x1HoAoqxoMpTp/iqcNXk=
github.com/influxdata/toml v0.0.0-20190415235208-270119a8ce65/go.mod h1:zApaNFpP/bTpQItGZNNUMISDMDAnTXu9UqJ4yT3ocz8=
github.com/influxdata/usage-client v0.0.0-20160829180054-6d3895376368/go.mod h1:Wbbw6tYNvwa5dlB6304Sd+82Z3f7PmVZHVKU637d4po=
github.com/influxdata/wlog v0.0.0-20160411224016-7c63b0a71ef8/go.mod h1:/2NMgWB1DHM1ti/gqhOlg+LJeBVk6FqR5aVGYY0hlwI=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
@ -493,10 +579,15 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS
github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8=
github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -511,33 +602,43 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
github.com/jwilder/encoding v0.0.0-20170811194829-b4e1701a28ef/go.mod h1:Ct9fl0F6iIOGgxJ5npU/IUOhOhqlVrGjyIZc8/MagT0=
github.com/kardianos/service v1.0.0/go.mod h1:8CzDhVuCuugtsHyZoTvsOBuvonN/UDBvl0kH+BUxvbo=
github.com/karrick/godirwalk v1.12.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.11.0 h1:wJbzvpYMVGG9iTI9VxpnNZfd4DzMPoCWze3GgSqz8yg=
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
github.com/klauspost/pgzip v1.0.2-0.20170402124221-0bf5dcad4ada/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kubernetes/apimachinery v0.0.0-20190119020841-d41becfba9ee/go.mod h1:Pe/YBTPc3vqoMkbuIWPH8CF9ehINdvNyS0dP3J6HC0s=
github.com/kylelemons/godebug v0.0.0-20160406211939-eadb3ce320cb/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leanovate/gopter v0.2.8 h1:eFPtJ3aa5zLfbxGROSNY75T9Dume60CWBAqoWQ3h/ig=
github.com/leanovate/gopter v0.2.8/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/leodido/ragel-machinery v0.0.0-20181214104525-299bdde78165/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc=
github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743 h1:143Bb8f8DuGWck/xpNUOckBVYfFbBTnLevfRZ1aVVqo=
@ -579,6 +680,7 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
github.com/magiconair/properties v1.8.2 h1:znVR8Q4g7/WlcvsxLBRWvo+vtFJUAbDn3w+Yak2xVMI=
github.com/magiconair/properties v1.8.2/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180717111219-efc7eb8984d6/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -604,11 +706,17 @@ github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4f
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mauricelam/genny v0.0.0-20180903214747-eb2c5232c885/go.mod h1:wRyVMWiOZeVj+MieWS5tIBBtJ3RtqqMbPsA5Z+t5b5U=
github.com/mdlayher/apcupsd v0.0.0-20190314144147-eb3dd99a75fe/go.mod h1:y3mw3VG+t0m20OMqpG8RQqw8cDXvShVb+L8Z8FEnebw=
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -631,19 +739,27 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/multiplay/go-ts3 v1.0.0/go.mod h1:14S6cS3fLNT3xOytrA/DkRyAFNuQLMLEqOYAsf87IbQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats-server/v2 v2.1.4/go.mod h1:Jw1Z28soD/QasIA2uWjXyM9El1jly3YwyFOuR8tH1rg=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/newrelic/newrelic-telemetry-sdk-go v0.2.0/go.mod h1:G9MqE/cHGv3Hx3qpYhfuyFUsGx2DpVcGi1iJIqTg+JQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nsqio/go-nsq v1.0.7/go.mod h1:XP5zaUs3pqf+Q71EqUJs3HYfBIqfK6G83WQMdNN+Ito=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
@ -663,6 +779,7 @@ github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad h1:GXUy5t8CYdaaEj1lRnE22CbHVY1M5h6Rv4kk0PJQc54=
github.com/open-falcon/rrdlite v0.0.0-20200214140804-bf5829f786ad/go.mod h1:pXROoG0iWVnqq4u2Ii97S0Vt9iCTVypshsl9HXsV6cs=
github.com/openconfig/gnmi v0.0.0-20180912164834-33a1865c3029/go.mod h1:t+O9It+LKzfOAhKTT5O0ehDix+MTqbtT0T9t+7zzOvc=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
@ -678,6 +795,7 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go-opentracing v0.3.4/go.mod h1:js2AbwmHW0YD9DwIw2JhQWmbfFi/UnWyYwdVhqbCDOE=
github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -698,8 +816,9 @@ github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHu
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -721,6 +840,7 @@ github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a h1:AA9vgIBDjM
github.com/prashantv/protectmem v0.0.0-20171002184600-e20412882b3a/go.mod h1:lzZQ3Noex5pfAy7mkAeCjcBDteYU85uWWnJ/y6gKU8k=
github.com/prometheus/alertmanager v0.20.0/go.mod h1:9g2i48FAyZW6BtbsnvHtMHQXl2aVtrORKwKVCQ+nbrg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -739,6 +859,7 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -748,11 +869,11 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/prometheus v1.8.2-0.20200420081721-18254838fbe2/go.mod h1:ZnfuiMn3LNsry2q7ECmRe4WcscxmJSd2dIFpOi4w3lM=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
github.com/rhysd/go-github-selfupdate v1.2.2/go.mod h1:khesvSyKcXDUxeySCedFh621iawCks0dS/QnHPcpCws=
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
@ -768,6 +889,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/rveen/ogdl v0.0.0-20200522080342-eeeda1a978e7/go.mod h1:9fqUB54wJS9u5TSXJZhRfTdh1lXVxTytDjed7t2cNdw=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -780,19 +903,19 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil v2.17.13-0.20180801053943-8048a2e9c577+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v2.20.7+incompatible h1:Ymv4OD12d6zm+2yONe39VSmp2XooJe8za7ngOLW/o/w=
github.com/shirou/gopsutil v2.20.7+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v2.20.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/gopsutil v3.20.11+incompatible h1:LJr4ZQK4mPpIV5gOa4jCOKOGb4ty4DZO54I4FGqIpto=
github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shopspring/decimal v0.0.0-20200105231215-408a2507e114/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@ -802,8 +925,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soniah/gosnmp v1.25.0/go.mod h1:8YvfZxH388NIIw2A+X5z2Oh97VcNhtmxDLt5QeUzVuQ=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -825,7 +948,6 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
@ -833,6 +955,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
github.com/streadway/amqp v0.0.0-20180528204448-e5adc2ada8b8/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
@ -840,6 +963,8 @@ github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1Sd
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25 h1:7z3LSn867ex6VSaahyKadf4WtSsJIgne6A1WLOAGM8A=
github.com/streadway/quantile v0.0.0-20150917103942-b0c588724d25/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@ -849,7 +974,11 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.2.1-0.20190917103637-de67a6614a4d h1:YN4gX82mT31qsizy2jRheOCrGLCs15VF9SV5XPuBvkQ=
github.com/subosito/gotenv v1.2.1-0.20190917103637-de67a6614a4d/go.mod h1:GVSeM7r0P1RI1gOKYyN9IuNkhMmQwKGsjVf3ulDrdzo=
github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62/go.mod h1:qUzPVlSj2UgxJkVbH0ZwuuiR46U8RBMDT5KLY78Ifpw=
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
github.com/tedsuo/ifrit v0.0.0-20191009134036-9a97d0632f00/go.mod h1:eyZnKCc955uh98WQvzOm0dgAeLnf2O0Rz0LPoC5ze+0=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
@ -887,12 +1016,20 @@ github.com/unrolled/render v1.0.3/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0ob
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmihailenco/msgpack v2.8.3+incompatible h1:76LCLwxS08gKHRpGA10PBxfWk72JfUH6mgzp2+URwYM=
github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/vjeantet/grok v1.0.0/go.mod h1:/FWYEVYekkm+2VjcFmO9PufDU5FgXHUz9oy2EGqmQBo=
github.com/vmihailenco/msgpack v2.8.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmware/govmomi v0.19.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU=
github.com/wavefronthq/wavefront-sdk-go v0.9.2/go.mod h1:hQI6y8M9OtTCtc0xdwh+dCER4osxXdEAeCpacjpDZEU=
github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
github.com/wvanbergen/kafka v0.0.0-20171203153745-e2edea948ddf/go.mod h1:nxx7XRXbR9ykhnC8lXqQyJS0rfvJGxKyKw/sT1YOttg=
github.com/wvanbergen/kazoo-go v0.0.0-20180202103751-f72d8611297a/go.mod h1:vQQATAGxVK20DC1rRubTJbZDDhhpA4QfU02pMdPxGO4=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@ -900,7 +1037,9 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20180630135845-46796da1b0b4/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
@ -914,6 +1053,8 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.starlark.net v0.0.0-20200901195727-6e684ef5eeee/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
@ -934,6 +1075,7 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -949,14 +1091,16 @@ golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -967,6 +1111,9 @@ golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm0
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
@ -979,13 +1126,16 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -1009,11 +1159,15 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -1021,8 +1175,9 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1036,14 +1191,15 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 h1:DvY3Zkh7KabQE/kfzMvYvKirSiguP9Q/veMtkYyf0o8=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -1087,22 +1243,34 @@ golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200108203644-89082a384178/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200305205014-bc073721adb6/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200317043434-63da46f3035e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM=
golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20200205215550-e35592f146e4/go.mod h1:UdS9frhv65KTfwxME1xE8+rHYoFpbm36gOud1GhBe9c=
gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
gonum.org/v1/gonum v0.6.2/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU=
gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
@ -1114,6 +1282,7 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1142,8 +1311,14 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200108215221-bd8f9a0ef82f/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200305110556-506484158171 h1:xes2Q2k+d/+YNXVw0FpZkIDJiaux4OVrRKXRAzH6A0U=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24 h1:IGPykv426z7LZSVPlaPufOyphngM4at5uZ7x5alaFvE=
google.golang.org/genproto v0.0.0-20200317114155-1f3552e48f24/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
@ -1160,18 +1335,15 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1 h1:M8spwkmx0pHrPq+uMdl22w5CvJ/Y+oAJTIs9oGoCpOE=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
@ -1182,12 +1354,15 @@ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
gopkg.in/fsnotify.v1 v1.2.1/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
@ -1198,18 +1373,26 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
gopkg.in/go-playground/validator.v9 v9.7.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1 h1:GyboHr4UqMiLUybYjd22ZjQIKEJEpgtLXtuGbR21Oho=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0 h1:a9tsXlIDD9SKxotJMK3niV7rPZAJeX2aD/0yg3qlIrg=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0 h1:QHIUxTX1ISuAv9dD2wJ9HWQVuWDX/Zc0PfeC2tjc4rU=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE=
gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/olivere/elastic.v5 v5.0.70/go.mod h1:FylZT6jQWtfHsicejzOm3jIMVPOAksa80i3o+6qtQRk=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
@ -1217,6 +1400,7 @@ gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
gopkg.in/tomb.v1 v1.0.0-20140529071818-c131134a1947/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/validator.v2 v2.0.0-20160201165114-3e4f037f12a1 h1:1IZMbdoz1SZAQ4HMRwAP0FPSyXt7ywsiJ4q7OPTEu4A=
@ -1233,16 +1417,20 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/netdb v0.0.0-20150201073656-a416d700ae39/go.mod h1:rbNo0ST5hSazCG4rGfpHrwnwvzP1QX62WbhzD+ghGzs=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.17.3/go.mod h1:YZ0OTkuw7ipbe305fMpIdf3GLXZKRigjtZaV5gzC2J0=
k8s.io/apimachinery v0.17.3 h1:f+uZV6rm4/tHE7xXgLyToprg6xWairaClGVkm2t8omg=
k8s.io/apimachinery v0.17.1/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/apimachinery v0.17.3/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
k8s.io/client-go v0.17.3/go.mod h1:cLXlTMtWHkuK4tD360KpWz2gG2KtdWEr/OT02i3emRQ=
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
@ -1252,8 +1440,16 @@ k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
modernc.org/httpfs v1.0.0/go.mod h1:BSkfoMUcahSijQD5J/Vu4UMOxzmEf5SNRwyXC4PJBEw=
modernc.org/libc v1.3.1/go.mod h1:f8sp9GAfEyGYh3lsRIKtBh/XwACdFvGznxm6GJmQvXk=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.1/go.mod h1:NSjvC08+g3MLOpcAxQbdctcThAEX4YlJ20WWHYEhvRg=
modernc.org/sqlite v1.7.4/go.mod h1:xse4RHCm8Fzw0COf5SJqAyiDrVeDwAQthAS1V/woNIA=
modernc.org/tcl v1.4.1/go.mod h1:8YCvzidU9SIwkz7RZwlCWK61mhV8X9UwfkRDRp7y5e0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

20
sql/README.md Normal file
View File

@ -0,0 +1,20 @@
## sql 的维护
- n9e_{module}.sql 完整的sql
- n9e_{module}-path.sql 增量的sql
## sql 的版本发布
在使用 git tag 之前,将特定版本的增量文件固化下来
```
module=rdb
version=v3.3.3
cat n9e_${module}-patch.sql > upgrade/n9e_${module}-${version}.sql
echo > n9e_{module}-patch.sql
# 然后提交更改后再打上版本的tag
git add .
git commit -a -m "${version} release"
git tag ${version}
git push
```

View File

@ -10,7 +10,8 @@ create table `instance` (
`identity` varchar(255) not null,
`rpc_port` varchar(16) not null,
`http_port` varchar(16) not null,
`remark` text,
`region` varchar(32) not null,
`remark` text,
`ts` int unsigned not null,
primary key (`id`),
key(`module`,`identity`,`rpc_port`,`http_port`)
@ -26,4 +27,4 @@ create table `detector` (
`ts` int unsigned not null,
primary key (`id`),
key(`ip`,`port`)
) engine=innodb default charset=utf8;
) engine=innodb default charset=utf8;

25
sql/n9e_mon-patch.sql Normal file
View File

@ -0,0 +1,25 @@
set names utf8;
use n9e_mon;
CREATE TABLE `collect_rule` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'nid',
`step` int(11) NOT NULL DEFAULT '0' COMMENT 'step',
`timeout` int(11) NOT NULL DEFAULT '0' COMMENT 'total timeout',
`collect_type` varchar(64) NOT NULL DEFAULT '' COMMENT 'collector name',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'name',
`region` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'region',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`data` blob NULL COMMENT 'data',
`tags` varchar(512) NOT NULL DEFAULT '' COMMENT 'tags',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`created` datetime NOT NULL COMMENT 'created',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
KEY `idx_collect_type` (`collect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'api collect';

View File

@ -11,7 +11,7 @@ create table `maskconf` (
`metric` varchar(255) not null,
`tags` varchar(255) not null default '',
`cause` varchar(255) not null default '',
`user` varchar(32) not null default 'operate user',
`user` varchar(64) not null default 'operate user',
`btime` bigint not null default 0 comment 'begin time',
`etime` bigint not null default 0 comment 'end time',
primary key (`id`),
@ -176,7 +176,7 @@ CREATE TABLE `stra_log` (
`sid` bigint(20) NOT NULL DEFAULT '0' COMMENT 'collect id',
`action` varchar(255) NOT NULL DEFAULT '' COMMENT '动作 update, delete',
`body` text COMMENT '修改之前采集的内容',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` timestamp NOT NULL DEFAULT '1971-01-01 00:00:00' COMMENT 'created',
PRIMARY KEY (`id`),
KEY `idx_sid` (`sid`)
@ -192,9 +192,9 @@ CREATE TABLE `port_collect` (
`step` int(11) NOT NULL DEFAULT '0' COMMENT '采集周期',
`timeout` int(11) NOT NULL DEFAULT '0' COMMENT 'connect time',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(128) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'last_updated',
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
@ -211,9 +211,9 @@ CREATE TABLE `proc_collect` (
`target` varchar(255) NOT NULL DEFAULT '' COMMENT '采集对象',
`step` int(11) NOT NULL DEFAULT '0' COMMENT '采集周期',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(128) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
@ -237,9 +237,9 @@ CREATE TABLE `log_collect` (
`unit` varchar(64) NOT NULL DEFAULT '' COMMENT 'unit',
`zero_fill` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'zero fill',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(128) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
@ -257,9 +257,9 @@ CREATE TABLE `plugin_collect` (
`stdin` text NOT NULL COMMENT 'stdin',
`env` text NOT NULL COMMENT 'env',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(128) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
@ -286,9 +286,9 @@ CREATE TABLE `api_collect` (
`unexpected_string` varchar(255) NOT NULL DEFAULT '' COMMENT 'unexpected_string',
`region` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'region',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(128) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
@ -309,9 +309,9 @@ CREATE TABLE `snmp_collect` (
`step` int(11) NOT NULL DEFAULT '0' COMMENT 'step',
`timeout` int(11) NOT NULL DEFAULT '0' COMMENT 'total timeout',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`creator` varchar(255) NOT NULL DEFAULT '' COMMENT 'creator',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`created` datetime NOT NULL COMMENT 'created',
`last_updator` varchar(128) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
@ -319,6 +319,26 @@ CREATE TABLE `snmp_collect` (
KEY `idx_collect_type` (`collect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'api collect';
CREATE TABLE `collect_rule` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'nid',
`step` int(11) NOT NULL DEFAULT '0' COMMENT 'step',
`timeout` int(11) NOT NULL DEFAULT '0' COMMENT 'total timeout',
`collect_type` varchar(64) NOT NULL DEFAULT '' COMMENT 'collector name',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'name',
`region` varchar(32) NOT NULL DEFAULT 'default' COMMENT 'region',
`comment` varchar(512) NOT NULL DEFAULT '' COMMENT 'comment',
`data` blob NULL COMMENT 'data',
`tags` varchar(512) NOT NULL DEFAULT '' COMMENT 'tags',
`creator` varchar(64) NOT NULL DEFAULT '' COMMENT 'creator',
`last_updator` varchar(64) NOT NULL DEFAULT '' COMMENT 'last_updator',
`created` datetime NOT NULL COMMENT 'created',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_nid` (`nid`),
KEY `idx_collect_type` (`collect_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'api collect';
CREATE TABLE `aggr_calc` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`nid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT 'nid',
@ -389,12 +409,3 @@ CREATE TABLE `collect_hist` (
PRIMARY KEY (`id`),
KEY `idx_cid` (`cid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT 'hist';
CREATE TABLE `api_collect_sid` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`sid` bigint(20) NOT NULL DEFAULT '0' COMMENT 'stra id',
`cid` bigint(20) NOT NULL DEFAULT '0' COMMENT 'collect id',
PRIMARY KEY (`id`),
KEY (`sid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

42
sql/n9e_rdb-patch.sql Normal file
View File

@ -0,0 +1,42 @@
set names utf8;
use n9e_rdb;
CREATE TABLE `white_list` (
`id` bigint unsigned not null AUTO_INCREMENT,
`start_ip` varchar(32) DEFAULT '0' NOT NULL,
`end_ip` varchar(32) DEFAULT '0' NOT NULL,
`start_ip_int` bigint DEFAULT '0' NOT NULL,
`end_ip_int` bigint DEFAULT '0' NOT NULL,
`start_time` bigint DEFAULT '0' NOT NULL,
`end_time` bigint DEFAULT '0' NOT NULL,
`created_at` bigint DEFAULT '0' NOT NULL,
`updated_at` bigint DEFAULT '0' NOT NULL,
`creator` varchar(64) DEFAULT '' NOT NULL,
`updater` varchar(64) DEFAULT '' NOT NULL,
PRIMARY KEY (`id`),
KEY (`start_ip_int`, `end_ip_int`),
KEY (`start_time`, `end_time`),
KEY (`created_at`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE `session` (
`sid` char(128) NOT NULL,
`username` varchar(64) DEFAULT '',
`remote_addr` varchar(32) DEFAULT '',
`created_at` integer unsigned DEFAULT '0',
`updated_at` integer unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (`sid`),
KEY (`username`),
KEY (`updated_at`)
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
alter table user add `login_err_num` int unsigned not null default 0 after leader_name;
alter table user add `active_begin` bigint not null default 0 after login_err_num;
alter table user add `active_end` bigint not null default 0 after active_begin;
alter table user add `locked_at` bigint not null default 0 after active_end;
alter table user add `updated_at` bigint not null default 0 after locked_at;
alter table user add `pwd_updated_at` bigint not null default 0 after updated_at;
alter table user add `logged_at` bigint not null default 0 after pwd_updated_at;
alter table user add `passwords` varchar(512) not null default '' after password;
alter table login_log add `err` varchar(128) not null default '' after loginout;

View File

@ -6,23 +6,31 @@ use n9e_rdb;
CREATE TABLE `user`
(
`id` int unsigned not null AUTO_INCREMENT,
`uuid` varchar(128) not null comment 'use in cookie',
`username` varchar(64) not null comment 'login name, cannot rename',
`password` varchar(128) not null default '',
`dispname` varchar(32) not null default '' comment 'display name, chinese name',
`phone` varchar(16) not null default '',
`email` varchar(64) not null default '',
`im` varchar(64) not null default '',
`portrait` varchar(2048) not null default '',
`intro` varchar(2048) not null default '',
`organization` varchar(255) not null default '',
`typ` tinyint(1) not null default 0 comment '0: long-term account; 1: temporary account',
`status` tinyint(1) not null default 0 comment '0: active; 1: inactive 2: disable',
`is_root` tinyint(1) not null,
`leader_id` int unsigned not null default 0,
`leader_name` varchar(32) not null default '',
`create_at` timestamp not null default CURRENT_TIMESTAMP,
`id` int unsigned not null AUTO_INCREMENT,
`uuid` varchar(128) not null comment 'use in cookie',
`username` varchar(64) not null comment 'login name, cannot rename',
`password` varchar(128) not null default '',
`passwords` varchar(512) not null default '',
`dispname` varchar(32) not null default '' comment 'display name, chinese name',
`phone` varchar(16) not null default '',
`email` varchar(64) not null default '',
`im` varchar(64) not null default '',
`portrait` varchar(2048) not null default '',
`intro` varchar(2048) not null default '',
`organization` varchar(255) not null default '',
`typ` tinyint(1) not null default 0 comment '0: long-term account; 1: temporary account',
`status` tinyint(1) not null default 0 comment '0: active, 1: inactive, 2: locked, 3: frozen, 5: writen-off',
`is_root` tinyint(1) not null,
`leader_id` int unsigned not null default 0,
`leader_name` varchar(32) not null default '',
`login_err_num` int unsigned not null default 0,
`active_begin` bigint not null default 0,
`active_end` bigint not null default 0,
`locked_at` bigint not null default 0,
`updated_at` bigint not null default 0,
`pwd_updated_at` bigint not null default 0,
`logged_at` bigint not null default 0,
`create_at` timestamp not null default CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY (`username`),
UNIQUE KEY (`uuid`)
@ -269,6 +277,7 @@ CREATE TABLE `login_log`
`client` varchar(128) not null comment 'client ip',
`clock` bigint not null comment 'login timestamp',
`loginout` char(3) not null comment 'in or out',
`err` varchar(128) not null comment 'err msg',
PRIMARY KEY (`id`),
KEY (`username`),
KEY (`clock`)
@ -315,3 +324,32 @@ CREATE TABLE `captcha` (
KEY (`captcha_id`, `answer`),
KEY (`created_at`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE `white_list` (
`id` bigint unsigned not null AUTO_INCREMENT,
`start_ip` varchar(32) DEFAULT '0' NOT NULL,
`end_ip` varchar(32) DEFAULT '0' NOT NULL,
`start_ip_int` bigint DEFAULT '0' NOT NULL,
`end_ip_int` bigint DEFAULT '0' NOT NULL,
`start_time` bigint DEFAULT '0' NOT NULL,
`end_time` bigint DEFAULT '0' NOT NULL,
`created_at` bigint DEFAULT '0' NOT NULL,
`updated_at` bigint DEFAULT '0' NOT NULL,
`creator` varchar(64) DEFAULT '' NOT NULL,
`updater` varchar(64) DEFAULT '' NOT NULL,
PRIMARY KEY (`id`),
KEY (`start_ip_int`, `end_ip_int`),
KEY (`start_time`, `end_time`),
KEY (`created_at`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE `session` (
`sid` char(128) NOT NULL,
`username` varchar(64) DEFAULT '',
`remote_addr` varchar(32) DEFAULT '',
`created_at` integer unsigned DEFAULT '0',
`updated_at` integer unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (`sid`),
KEY (`username`),
KEY (`updated_at`)
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;

View File

@ -30,9 +30,9 @@ type MetricValue struct {
Step int64 `json:"step"`
ValueUntyped interface{} `json:"value"`
Value float64 `json:"-"`
CounterType string `json:"counterType"`
Tags string `json:"tags"`
TagsMap map[string]string `json:"tagsMap"` //保留2种格式方便后端组件使用
CounterType string `json:"counterType"` // GAUGE | COUNTER | SUBTRACT | DERIVE
Tags string `json:"tags"` // a=1,b=2,c=3
TagsMap map[string]string `json:"tagsMap"` // {"a":1, "b"=2, "c="3} 保留2种格式方便后端组件使用
Extra string `json:"extra"`
}
@ -69,7 +69,7 @@ func (m *MetricValue) CheckValidity(now int64) (err error) {
}
if m.Nid == "" && m.Endpoint == "" {
err = fmt.Errorf("nid or endpoint should not be empty")
err = fmt.Errorf("nid and endpoint should not be empty")
return
}
@ -133,7 +133,7 @@ func (m *MetricValue) CheckValidity(now int64) (err error) {
k = filterString(k)
v = filterString(v)
if len(k) == 0 || len(v) == 0 {
err = fmt.Errorf("tag key and value should not be empty")
err = fmt.Errorf("tag key and value should not be empty key:%s value:%s", k, v)
return
}

View File

@ -110,4 +110,5 @@ type IndexByFullTagsResp struct {
Tags []string `json:"tags"`
Step int `json:"step"`
DsType string `json:"dstype"`
Count int `json:"count"`
}

View File

@ -1,9 +1,11 @@
package models
import (
"encoding/json"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/toolkits/pkg/runner"
@ -88,3 +90,103 @@ func ConfigsGets(ckeys []string) (map[string]string, error) {
return kvmap, nil
}
type AuthConfig struct {
MaxNumErr int `json:"maxNumErr"`
MaxSessionNumber int64 `json:"maxSessionNumber"`
MaxConnIdelTime int64 `json:"maxConnIdelTime" description:"minute"`
LockTime int64 `json:"lockTime" description:"minute"`
PwdHistorySize int `json:"pwdHistorySize"`
PwdMinLenght int `json:"pwdMinLenght"`
PwdExpiresIn int64 `json:"pwdExpiresIn" description:"month"`
PwdMustInclude []string `json:"pwdMustInclude" description:"upper,lower,number,specChar"`
PwdMustIncludeFlag int `json:"pwdMustIncludeFlag"`
}
func (p AuthConfig) PwdRules() []string {
s := []string{}
if p.PwdMinLenght > 0 {
s = append(s, _s("Minimum password length %d", p.PwdMinLenght))
}
if rule := p.MustInclude(); rule != "" {
s = append(s, rule)
}
return s
}
func (p AuthConfig) MustInclude() string {
s := []string{}
if p.PwdMustIncludeFlag&PWD_INCLUDE_UPPER > 0 {
s = append(s, _s("Upper char"))
}
if p.PwdMustIncludeFlag&PWD_INCLUDE_LOWER > 0 {
s = append(s, _s("Lower char"))
}
if p.PwdMustIncludeFlag&PWD_INCLUDE_NUMBER > 0 {
s = append(s, _s("Number"))
}
if p.PwdMustIncludeFlag&PWD_INCLUDE_SPEC_CHAR > 0 {
s = append(s, _s("Special char"))
}
if len(s) > 0 {
return _s("Must include %s", strings.Join(s, ","))
}
return ""
}
const (
PWD_INCLUDE_UPPER = 1 << iota
PWD_INCLUDE_LOWER
PWD_INCLUDE_NUMBER
PWD_INCLUDE_SPEC_CHAR
)
func (p *AuthConfig) Validate() error {
for _, v := range p.PwdMustInclude {
switch v {
case "upper":
p.PwdMustIncludeFlag |= PWD_INCLUDE_UPPER
case "lower":
p.PwdMustIncludeFlag |= PWD_INCLUDE_LOWER
case "number":
p.PwdMustIncludeFlag |= PWD_INCLUDE_NUMBER
case "specChar":
p.PwdMustIncludeFlag |= PWD_INCLUDE_SPEC_CHAR
default:
return fmt.Errorf("invalid pwd flags, must be 'upper', 'lower', 'number', 'specChar'")
}
}
return nil
}
var DefaultAuthConfig = AuthConfig{
MaxConnIdelTime: 30,
PwdMustInclude: []string{},
}
func AuthConfigGet() (*AuthConfig, error) {
buf, err := ConfigsGet("auth.config")
if err != nil {
return &DefaultAuthConfig, nil
}
c := &AuthConfig{}
if err := json.Unmarshal([]byte(buf), c); err != nil {
return &DefaultAuthConfig, nil
}
return c, nil
}
func AuthConfigSet(config *AuthConfig) error {
if err := config.Validate(); err != nil {
return err
}
buf, err := json.Marshal(config)
if err != nil {
return err
}
return ConfigsSet("auth.config", string(buf))
}

View File

@ -1,11 +1,21 @@
package models
import (
"fmt"
"time"
"github.com/didi/nightingale/src/toolkits/i18n"
"github.com/toolkits/pkg/cache"
)
func init() {
cache.InitMemoryCache(time.Hour)
}
func _e(format string, a ...interface{}) error {
return fmt.Errorf(i18n.Sprintf(format, a...))
}
func _s(format string, a ...interface{}) string {
return i18n.Sprintf(format, a...)
}

View File

@ -35,3 +35,8 @@ func InviteNew(token, creator string) error {
_, err := DB["rdb"].Insert(obj)
return err
}
func (i *Invite) Del() error {
_, err := DB["rdb"].Where("token=?", i.Token).Delete(i)
return err
}

View File

@ -1,6 +1,9 @@
package models
import "time"
import (
"fmt"
"time"
)
type LoginLog struct {
Id int64 `json:"id"`
@ -8,18 +11,20 @@ type LoginLog struct {
Client string `json:"client"`
Clock int64 `json:"clock"`
Loginout string `json:"loginout"`
Err string `json:"err"`
}
func LoginLogNew(username, client, inout string) error {
func LoginLogNew(username, client, inout string, err error) error {
now := time.Now().Unix()
obj := LoginLog{
Username: username,
Client: client,
Clock: now,
Loginout: inout,
Err: fmt.Sprintf("%v", err),
}
_, err := DB["rdb"].Insert(obj)
return err
_, e := DB["rdb"].Insert(obj)
return e
}
func LoginLogTotal(username string, btime, etime int64) (int64, error) {

View File

@ -185,12 +185,6 @@ type LogCollect struct {
ParseSucc bool `xorm:"-" json:"-"`
}
type ApiCollectSid struct {
Id int64 `json:"id"`
Cid int64 `json:"cid"`
Sid int64 `json:"sid"`
}
type ApiCollect struct {
Id int64 `json:"id"`
Nid int64 `json:"nid"`
@ -531,58 +525,6 @@ func (a *ApiCollect) Update() error {
return err
}
func DeleteApiCollect(id int64) error {
session := DB["mon"].NewSession()
defer session.Close()
_, err := session.Where("id = ?", id).Delete(new(ApiCollect))
if err != nil {
session.Rollback()
return err
}
var relCidSid ApiCollectSid
has, err := session.Where("cid = ?", id).Get(&relCidSid)
if err != nil {
session.Rollback()
return err
}
if has {
err = StraDel(relCidSid.Sid)
if err != nil {
session.Rollback()
return err
}
}
return session.Commit()
}
func GetSidByCid(cid int64) (int64, error) {
var cidSid ApiCollectSid
_, err := DB["mon"].Where("cid = ?", cid).Get(&cidSid)
return cidSid.Sid, err
}
func (a *ApiCollectSid) Add() error {
session := DB["mon"].NewSession()
defer session.Close()
_, err := session.Where("cid = ?", a.Cid).Delete(new(ApiCollectSid))
if err != nil {
session.Rollback()
return err
}
_, err = session.Insert(a)
if err != nil {
session.Rollback()
return err
}
return session.Commit()
}
func CreateCollect(collectType, creator string, collect interface{}) error {
session := DB["mon"].NewSession()
defer session.Close()
@ -611,126 +553,6 @@ func CreateCollect(collectType, creator string, collect interface{}) error {
return session.Commit()
}
func GetCollectByNid(collectType string, nids []int64) ([]interface{}, error) {
var res []interface{}
switch collectType {
case "port":
collects := []PortCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
res = append(res, c)
}
return res, err
case "proc":
collects := []ProcCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
res = append(res, c)
}
return res, err
case "log":
collects := []LogCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
c.Decode()
res = append(res, c)
}
return res, err
case "plugin":
collects := []PluginCollect{}
err := DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
res = append(res, c)
}
return res, err
default:
return nil, fmt.Errorf("采集类型不合法")
}
}
func GetCollectById(collectType string, cid int64) (interface{}, error) {
switch collectType {
case "port":
collect := new(PortCollect)
has, err := DB["mon"].Where("id = ?", cid).Get(collect)
if !has {
return nil, err
}
return collect, err
case "proc":
collect := new(ProcCollect)
has, err := DB["mon"].Where("id = ?", cid).Get(collect)
if !has {
return nil, err
}
return collect, err
case "log":
collect := new(LogCollect)
has, err := DB["mon"].Where("id = ?", cid).Get(collect)
if !has {
return nil, err
}
collect.Decode()
return collect, err
case "plugin":
collect := new(PluginCollect)
has, err := DB["mon"].Where("id = ?", cid).Get(collect)
if !has {
return nil, err
}
return collect, err
default:
return nil, fmt.Errorf("采集类型不合法")
}
return nil, nil
}
func GetCollectByNameAndNid(collectType string, name string, nid int64) (interface{}, error) {
switch collectType {
case "port":
collect := new(PortCollect)
has, err := DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
case "proc":
collect := new(ProcCollect)
has, err := DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
case "log":
collect := new(LogCollect)
has, err := DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
collect.Decode()
return collect, err
case "plugin":
collect := new(PluginCollect)
has, err := DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
default:
return nil, fmt.Errorf("采集类型不合法")
}
return nil, nil
}
func DeleteCollectById(collectType, creator string, cid int64) error {
session := DB["mon"].NewSession()
defer session.Close()

View File

@ -0,0 +1,104 @@
package models
import (
"encoding/json"
"fmt"
"time"
"github.com/didi/nightingale/src/common/dataobj"
)
const (
defaultStep = 10
)
type CollectRule struct {
Id int64 `json:"id"`
Nid int64 `json:"nid"`
Step int `json:"step" description:"interval"`
Timeout int `json:"timeout"`
CollectType string `json:"collect_type" description:"plugin name"`
Name string `json:"name"`
Region string `json:"region"`
Comment string `json:"comment"`
Data json.RawMessage `json:"data"`
Tags string `json:"tags" description:"k1=v1,k2=v2,k3=v3,..."`
Creator string `json:"creator" description:"just for output"`
LastUpdator string `xorm:"last_updator" json:"last_updator" description:"just for output"`
Created time.Time `xorm:"updated" json:"created" description:"just for output"`
LastUpdated time.Time `xorm:"updated" json:"last_updated" description:"just for output"`
}
type validator interface {
Validate() error
}
func (p *CollectRule) Validate(v ...interface{}) error {
if p.Name == "" {
return fmt.Errorf("invalid collectRule.name")
}
if p.Step == 0 {
p.Step = defaultStep
}
if _, err := dataobj.SplitTagsString(p.Tags); err != nil {
return err
}
if len(v) > 0 && v[0] != nil {
if err := json.Unmarshal(p.Data, v[0]); err != nil {
return err
}
if o, ok := v[0].(validator); ok {
if err := o.Validate(); err != nil {
return err
}
}
}
return nil
}
func GetCollectRules() ([]*CollectRule, error) {
rules := []*CollectRule{}
err := DB["mon"].Find(&rules)
return rules, err
}
func (p *CollectRule) Update() error {
session := DB["mon"].NewSession()
defer session.Close()
err := session.Begin()
if err != nil {
return err
}
if _, err = session.Id(p.Id).AllCols().Update(p); err != nil {
session.Rollback()
return err
}
b, err := json.Marshal(p)
if err != nil {
session.Rollback()
return err
}
if err := saveHist(p.Id, p.CollectType, "update", p.Creator, string(b), session); err != nil {
session.Rollback()
return err
}
if err = session.Commit(); err != nil {
return err
}
return err
}
func DeleteCollectRule(sid int64) error {
_, err := DB["mon"].Where("id=?", sid).Delete(new(CollectRule))
return err
}

View File

@ -10,6 +10,7 @@ type Instance struct {
HTTPPort string `json:"http_port" xorm:"http_port"`
TS int64 `json:"ts" xorm:"ts"`
Remark string `json:"remark"`
Region string `json:"region"`
Active bool `xorm:"-" json:"active"`
}

112
src/models/session.go Normal file
View File

@ -0,0 +1,112 @@
package models
import (
"fmt"
"time"
"github.com/toolkits/pkg/cache"
"github.com/toolkits/pkg/logger"
)
type Session struct {
Sid string `json:"sid"`
Username string `json:"username"`
RemoteAddr string `json:"remote_addr"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
func SessionAll() (int64, error) {
return DB["rdb"].Count(new(Session))
}
func SessionUserAll(username string) (int64, error) {
return DB["rdb"].Where("username=?", username).Count(new(Session))
}
func SessionGet(sid string) (*Session, error) {
var obj Session
has, err := DB["rdb"].Where("sid=?", sid).Get(&obj)
if err != nil {
return nil, fmt.Errorf("get session err %s", err)
}
if !has {
return nil, fmt.Errorf("not found")
}
return &obj, nil
}
func SessionInsert(in *Session) error {
_, err := DB["rdb"].Insert(in)
return err
}
func SessionDel(sid string) error {
_, err := DB["rdb"].Where("sid=?", sid).Delete(new(Session))
return err
}
func SessionUpdate(in *Session) error {
_, err := DB["rdb"].Where("sid=?", in.Sid).AllCols().Update(in)
return err
}
func SessionCleanup(ts int64) error {
n, err := DB["rdb"].Where("updated_at<?", ts).Delete(new(Session))
logger.Debugf("delete before updated_at %d session %d", ts, n)
return err
}
func SessionCleanupByCreatedAt(ts int64) error {
n, err := DB["rdb"].Where("created_at<?", ts).Delete(new(Session))
logger.Debugf("delete before created_at %d session %d", ts, n)
return err
}
func (s *Session) Update(cols ...string) error {
_, err := DB["rdb"].Where("id=?", s.Sid).Cols(cols...).Update(s)
return err
}
// SessionGetWithCache will update session.UpdatedAt
func SessionGetWithCache(sid string) (*Session, error) {
if sid == "" {
return nil, fmt.Errorf("unable to get sid")
}
sess := &Session{}
if err := cache.Get("sid."+sid, &sess); err == nil {
return sess, nil
}
var err error
if sess, err = SessionGet(sid); err != nil {
return nil, fmt.Errorf("session not found")
}
// update session
sess.UpdatedAt = time.Now().Unix()
sess.Update("updated_at")
if sess.Username != "" {
cache.Set("sid."+sid, sess, time.Second*30)
}
return sess, nil
}
func SessionGetUserWithCache(sid string) (*User, error) {
s, err := SessionGetWithCache(sid)
if err != nil {
return nil, err
}
if s.Username == "" {
return nil, fmt.Errorf("user not found")
}
return UserMustGet("username=?", s.Username)
}
func SessionCacheDelete(sid string) {
cache.Delete("sid." + sid)
}

View File

@ -10,9 +10,12 @@ import (
// CryptoPass crypto password use salt
func CryptoPass(raw string) (string, error) {
if raw == "" {
return "", _e("Password is not set")
}
salt, err := ConfigsGet("salt")
if err != nil {
return "", fmt.Errorf("query salt from mysql fail: %v", err)
return "", _e("query salt from mysql fail: %v", err)
}
return str.MD5(salt + "<-*Uk30^96eY*->" + raw), nil

View File

@ -21,17 +21,30 @@ import (
const (
LOGIN_T_SMS = "sms-code"
LOGIN_T_EMAIL = "email-code"
LOGIN_T_RST = "rst-code"
LOGIN_T_PWD = "password"
LOGIN_T_LDAP = "ldap"
LOGIN_T_RST = "rst-code"
LOGIN_T_LOGIN = "login-code"
LOGIN_EXPIRES_IN = 300
)
const (
USER_S_ACTIVE = iota
USER_S_INACTIVE
USER_S_LOCKED
USER_S_FROZEN
USER_S_WRITEN_OFF
)
const (
USER_T_NATIVE = iota
USER_T_TEMP
)
type User struct {
Id int64 `json:"id"`
UUID string `json:"uuid" xorm:"'uuid'"`
Username string `json:"username"`
Password string `json:"-"`
Passwords string `json:"-"`
Dispname string `json:"dispname"`
Phone string `json:"phone"`
Email string `json:"email"`
@ -39,14 +52,63 @@ type User struct {
Portrait string `json:"portrait"`
Intro string `json:"intro"`
Organization string `json:"organization"`
Typ int `json:"typ"`
Status int `json:"status"`
Type int `json:"type" xorm:"'typ'" description:"0: long-term account; 1: temporary account"`
Status int `json:"status" description:"0: active, 1: inactive, 2: locked, 3: frozen, 4: writen-off"`
IsRoot int `json:"is_root"`
LeaderId int64 `json:"leader_id"`
LeaderName string `json:"leader_name"`
LoginErrNum int `json:"login_err_num"`
ActiveBegin int64 `json:"active_begin" description:"for temporary account"`
ActiveEnd int64 `json:"active_end" description:"for temporary account"`
LockedAt int64 `json:"locked_at" description:"locked time"`
UpdatedAt int64 `json:"updated_at" description:"user info change time"`
PwdUpdatedAt int64 `json:"pwd_updated_at" description:"password change time"`
LoggedAt int64 `json:"logged_at" description:"last logged time"`
CreateAt time.Time `json:"create_at" xorm:"<-"`
}
func (u *User) Validate() error {
u.Username = strings.TrimSpace(u.Username)
if u.Username == "" {
return _e("username is blank")
}
if str.Dangerous(u.Username) {
return _e("%s %s format error", _s("username"), u.Username)
}
if str.Dangerous(u.Dispname) {
return _e("%s %s format error", _s("dispname"), u.Dispname)
}
if u.Phone != "" && !str.IsPhone(u.Phone) {
return _e("%s %s format error", _s("phone"), u.Phone)
}
if u.Email != "" && !str.IsMail(u.Email) {
return _e("%s %s format error", _s("email"), u.Email)
}
if len(u.Username) > 32 {
return _e("username too long (max:%d)", 32)
}
if len(u.Dispname) > 32 {
return _e("dispname too long (max:%d)", 32)
}
if strings.ContainsAny(u.Im, "%'") {
return _e("%s %s format error", "im", u.Im)
}
cnt, _ := DB["rdb"].Where("((email <> '' and email=?) or (phone <> '' and phone=?)) and username=?",
u.Email, u.Phone, u.Username).Count(u)
if cnt > 0 {
return _e("email %s or phone %s is exists", u.Email, u.Phone)
}
return nil
}
func (u *User) CopyLdapAttr(sr *ldap.SearchResult) {
attrs := config.Config.LDAP.Attributes
if attrs.Dispname != "" {
@ -95,59 +157,59 @@ func InitRooter() {
log.Println("user root init done")
}
func LdapLogin(user, pass string) (*User, error) {
sr, err := ldapReq(user, pass)
func LdapLogin(username, pass string) (*User, error) {
sr, err := ldapReq(username, pass)
if err != nil {
return nil, err
}
var u User
has, err := DB["rdb"].Where("username=?", user).Get(&u)
var user User
has, err := DB["rdb"].Where("username=?", username).Get(&user)
if err != nil {
return nil, err
}
u.CopyLdapAttr(sr)
user.CopyLdapAttr(sr)
if has {
if config.Config.LDAP.CoverAttributes {
_, err := DB["rdb"].Where("id=?", u.Id).Update(u)
return &u, err
_, err := DB["rdb"].Where("id=?", user.Id).Update(user)
return &user, err
} else {
return &u, err
return &user, err
}
}
u.Username = user
u.Password = "******"
u.UUID = GenUUIDForUser(user)
_, err = DB["rdb"].Insert(u)
return &u, nil
user.Username = username
user.Password = "******"
user.UUID = GenUUIDForUser(username)
_, err = DB["rdb"].Insert(user)
return &user, nil
}
func PassLogin(user, pass string) (*User, error) {
var u User
has, err := DB["rdb"].Where("username=?", user).Get(&u)
func PassLogin(username, pass string) (*User, error) {
var user User
has, err := DB["rdb"].Where("username=?", username).Get(&user)
if err != nil {
return nil, err
return nil, _e("Login fail, check your username and password")
}
if !has {
logger.Infof("password auth fail, no such user: %s", user)
return nil, fmt.Errorf("login fail, check your username and password")
logger.Infof("password auth fail, no such user: %s", username)
return nil, _e("Login fail, check your username and password")
}
loginPass, err := CryptoPass(pass)
if err != nil {
return nil, err
return &user, err
}
if loginPass != u.Password {
logger.Infof("password auth fail, password error, user: %s", user)
return nil, fmt.Errorf("login fail, check your username and password")
if loginPass != user.Password {
logger.Infof("password auth fail, password error, user: %s", username)
return &user, _e("Login fail, check your username and password")
}
return &u, nil
return &user, nil
}
func SmsCodeLogin(phone, code string) (*User, error) {
@ -156,15 +218,15 @@ func SmsCodeLogin(phone, code string) (*User, error) {
return nil, fmt.Errorf("phone %s dose not exist", phone)
}
lc, err := LoginCodeGet("username=? and code=? and login_type=?", user.Username, code, LOGIN_T_SMS)
lc, err := LoginCodeGet("username=? and code=? and login_type=?", user.Username, code, LOGIN_T_LOGIN)
if err != nil {
logger.Infof("sms-code auth fail, user: %s", user.Username)
return nil, fmt.Errorf("login fail, check your sms-code")
logger.Debugf("sms-code auth fail, user: %s", user.Username)
return user, _e("The code is incorrect")
}
if time.Now().Unix()-lc.CreatedAt > LOGIN_EXPIRES_IN {
logger.Infof("sms-code auth expired, user: %s", user.Username)
return nil, fmt.Errorf("login fail, the code has expired")
logger.Debugf("sms-code auth expired, user: %s", user.Username)
return user, _e("The code has expired")
}
lc.Del()
@ -178,15 +240,15 @@ func EmailCodeLogin(email, code string) (*User, error) {
return nil, fmt.Errorf("email %s dose not exist", email)
}
lc, err := LoginCodeGet("username=? and code=? and login_type=?", user.Username, code, LOGIN_T_EMAIL)
lc, err := LoginCodeGet("username=? and code=? and login_type=?", user.Username, code, LOGIN_T_LOGIN)
if err != nil {
logger.Infof("email-code auth fail, user: %s", user.Username)
return nil, fmt.Errorf("login fail, check your email-code")
logger.Debugf("email-code auth fail, user: %s", user.Username)
return user, _e("The code is incorrect")
}
if time.Now().Unix()-lc.CreatedAt > LOGIN_EXPIRES_IN {
logger.Infof("email-code auth expired, user: %s", user.Username)
return nil, fmt.Errorf("login fail, the code has expired")
logger.Debugf("email-code auth expired, user: %s", user.Username)
return user, _e("The code has expired")
}
lc.Del()
@ -208,56 +270,40 @@ func UserGet(where string, args ...interface{}) (*User, error) {
return &obj, nil
}
func UserMustGet(where string, args ...interface{}) (*User, error) {
var obj User
has, err := DB["rdb"].Where(where, args...).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, _e("User dose not exist")
}
return &obj, nil
}
func (u *User) IsRooter() bool {
return u.IsRoot == 1
}
func (u *User) CheckFields() {
u.Username = strings.TrimSpace(u.Username)
if u.Username == "" {
errors.Bomb("username is blank")
}
if str.Dangerous(u.Username) {
errors.Bomb("username is dangerous")
}
if str.Dangerous(u.Dispname) {
errors.Bomb("dispname is dangerous")
}
if u.Phone != "" && !str.IsPhone(u.Phone) {
errors.Bomb("%s format error", u.Phone)
}
if u.Email != "" && !str.IsMail(u.Email) {
errors.Bomb("%s format error", u.Email)
}
if len(u.Username) > 32 {
errors.Bomb("username too long")
}
if len(u.Dispname) > 32 {
errors.Bomb("dispname too long")
}
if strings.ContainsAny(u.Im, "%'") {
errors.Bomb("im invalid")
}
}
func (u *User) Update(cols ...string) error {
u.CheckFields()
if err := u.Validate(); err != nil {
return err
}
_, err := DB["rdb"].Where("id=?", u.Id).Cols(cols...).Update(u)
return err
}
func (u *User) Save() error {
u.CheckFields()
if err := u.Validate(); err != nil {
return err
}
if u.Id > 0 {
return fmt.Errorf("user.id[%d] not equal 0", u.Id)
return _e("user.id[%d] not equal 0", u.Id)
}
if u.UUID == "" {
@ -270,9 +316,11 @@ func (u *User) Save() error {
}
if cnt > 0 {
return fmt.Errorf("username already exists")
return _e("Username %s already exists", u.Username)
}
u.UpdatedAt = time.Now().Unix()
_, err = DB["rdb"].Insert(u)
return err
}
@ -440,7 +488,7 @@ func (u *User) HasPermGlobal(operation string) (bool, error) {
rids, err := RoleIdsHasOp(operation)
if err != nil {
return false, fmt.Errorf("[CheckPermGlobal] RoleIdsHasOp fail: %v, operation: %s", err, operation)
return false, _e("[CheckPermGlobal] RoleIdsHasOp fail: %v, operation: %s", err, operation)
}
if rids == nil || len(rids) == 0 {
@ -449,7 +497,7 @@ func (u *User) HasPermGlobal(operation string) (bool, error) {
has, err := UserHasGlobalRole(u.Id, rids)
if err != nil {
return false, fmt.Errorf("[CheckPermGlobal] UserHasGlobalRole fail: %v, username: %s", err, u.Username)
return false, _e("[CheckPermGlobal] UserHasGlobalRole fail: %v, username: %s", err, u.Username)
}
return has, nil
@ -531,7 +579,9 @@ func safeUserIds(ids []int64) ([]int64, error) {
return ret, nil
}
// Deprecated
func UsernameByUUID(uuid string) string {
logger.Warningf("UsernameByUUID is Deprectaed, use UsernameBySid instead of it")
if uuid == "" {
return ""
}

135
src/models/white_list.go Normal file
View File

@ -0,0 +1,135 @@
package models
import (
"errors"
"fmt"
"time"
)
type WhiteList struct {
Id int64 `json:"id"`
StartIp string `json:"startIp"`
StartIpInt int64 `json:"-"`
EndIp string `json:"endIp"`
EndIpInt int64 `json:"-"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updateAt"`
Creator string `json:"creator"`
Updater string `json:"updater"`
}
func WhiteListAccess(addr string) error {
ip := parseIPv4(addr)
if ip == 0 {
return fmt.Errorf("invalid remote address %s", addr)
}
now := time.Now().Unix()
count, _ := DB["rdb"].Where("start_ip_int<? and end_ip_int>? and start_time>? and end_time<?", ip, ip, now, now).Count(new(WhiteList))
if count == 0 {
return fmt.Errorf("access deny from %s", addr)
}
return nil
}
const big = 0xFFFFFF
func dtoi(s string) (n int, i int, ok bool) {
n = 0
for i = 0; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
n = n*10 + int(s[i]-'0')
if n >= big {
return big, i, false
}
}
if i == 0 {
return 0, 0, false
}
return n, i, true
}
func parseIPv4(s string) uint32 {
var p [4]uint32
for i := 0; i < 4; i++ {
if len(s) == 0 {
// Missing octets.
return 0
}
if i > 0 {
if s[0] != '.' {
return 0
}
s = s[1:]
}
n, c, ok := dtoi(s)
if !ok || n > 0xFF {
return 0
}
s = s[c:]
p[i] = uint32(n)
}
if len(s) != 0 {
return 0
}
return p[0]<<24 + p[1]<<16 + p[2]<<8 + p[3]
}
func (p *WhiteList) Validate() error {
if p.StartIpInt = int64(parseIPv4(p.StartIp)); p.StartIpInt == 0 {
return fmt.Errorf("invalid start ip %s", p.StartIp)
}
if p.EndIpInt = int64(parseIPv4(p.EndIp)); p.EndIpInt == 0 {
return fmt.Errorf("invalid end ip %s", p.EndIp)
}
return nil
}
func WhiteListTotal(query string) (int64, error) {
if query != "" {
q := "%" + query + "%"
return DB["rdb"].Where("start_ip like ? or end_ip like ?", q, q).Count(new(NodeTrash))
}
return DB["rdb"].Count(new(WhiteList))
}
func WhiteListGets(query string, limit, offset int) ([]WhiteList, error) {
session := DB["rdb"].Desc("id").Limit(limit, offset)
if query != "" {
q := "%" + query + "%"
session = session.Where("start_ip like ? or end_ip like ?", q, q)
}
var objs []WhiteList
err := session.Find(&objs)
return objs, err
}
func WhiteListGet(where string, args ...interface{}) (*WhiteList, error) {
var obj WhiteList
has, err := DB["rdb"].Where(where, args...).Get(&obj)
if err != nil {
return nil, err
}
if !has {
return nil, errors.New("whiteList not found")
}
return &obj, nil
}
func (p *WhiteList) Save() error {
_, err := DB["rdb"].Insert(p)
return err
}
func (p *WhiteList) Update(cols ...string) error {
_, err := DB["rdb"].Where("id=?", p.Id).Cols(cols...).Update(p)
return err
}
func (p *WhiteList) Del() error {
_, err := DB["rdb"].Where("id=?", p.Id).Delete(new(WhiteList))
return err
}

View File

@ -47,7 +47,7 @@ func shouldBeService() gin.HandlerFunc {
}
func mustUsername(c *gin.Context) string {
username := cookieUsername(c)
username := sessionUsername(c)
if username == "" {
username = headerUsername(c)
}
@ -59,8 +59,12 @@ func mustUsername(c *gin.Context) string {
return username
}
func cookieUsername(c *gin.Context) string {
return models.UsernameByUUID(readCookieUser(c))
func sessionUsername(c *gin.Context) string {
sess, err := models.SessionGetWithCache(readSessionId(c))
if err != nil {
return ""
}
return sess.Username
}
func headerUsername(c *gin.Context) string {
@ -84,11 +88,10 @@ func headerUsername(c *gin.Context) string {
// ------------
func readCookieUser(c *gin.Context) string {
uuid, err := c.Cookie(config.Config.HTTP.CookieName)
func readSessionId(c *gin.Context) string {
sid, err := c.Cookie(config.Config.HTTP.CookieName)
if err != nil {
return ""
}
return uuid
return sid
}

View File

@ -47,7 +47,7 @@ func shouldBeService() gin.HandlerFunc {
}
func mustUsername(c *gin.Context) string {
username := cookieUsername(c)
username := sessionUsername(c)
if username == "" {
username = headerUsername(c)
}
@ -59,8 +59,12 @@ func mustUsername(c *gin.Context) string {
return username
}
func cookieUsername(c *gin.Context) string {
return models.UsernameByUUID(readCookieUser(c))
func sessionUsername(c *gin.Context) string {
sess, err := models.SessionGetWithCache(readSessionId(c))
if err != nil {
return ""
}
return sess.Username
}
func headerUsername(c *gin.Context) string {
@ -84,11 +88,10 @@ func headerUsername(c *gin.Context) string {
// ------------
func readCookieUser(c *gin.Context) string {
uuid, err := c.Cookie(config.Config.HTTP.CookieName)
func readSessionId(c *gin.Context) string {
sid, err := c.Cookie(config.Config.HTTP.CookieName)
if err != nil {
return ""
}
return uuid
return sid
}

View File

@ -0,0 +1,167 @@
package collector
import (
"encoding/json"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/influxdata/telegraf"
)
type BaseCollector struct {
name string
category Category
newRule func() interface{}
}
func NewBaseCollector(name string, category Category, newRule func() interface{}) *BaseCollector {
return &BaseCollector{
name: name,
category: category,
newRule: newRule,
}
}
type telegrafPlugin interface {
TelegrafInput() (telegraf.Input, error)
}
func (p BaseCollector) Name() string { return p.name }
func (p BaseCollector) Category() Category { return p.category }
func (p BaseCollector) Template() (interface{}, error) { return Template(p.newRule()) }
func (p BaseCollector) TelegrafInput(rule *models.CollectRule) (telegraf.Input, error) {
r2 := p.newRule()
if err := json.Unmarshal(rule.Data, r2); err != nil {
return nil, err
}
plugin, ok := r2.(telegrafPlugin)
if !ok {
return nil, errUnsupported
}
return plugin.TelegrafInput()
}
func (p BaseCollector) Get(id int64) (interface{}, error) {
collect := &models.CollectRule{}
has, err := models.DB["mon"].Where("id = ?", id).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p BaseCollector) Gets(nids []int64) (ret []interface{}, err error) {
collects := []models.CollectRule{}
err = models.DB["mon"].Where("collect_type=?", p.name).In("nid", nids).Find(&collects)
for _, c := range collects {
ret = append(ret, c)
}
return ret, err
}
func (p BaseCollector) GetByNameAndNid(name string, nid int64) (interface{}, error) {
collect := &models.CollectRule{}
has, err := models.DB["mon"].Where("collect_type = ? and name = ? and nid = ?", p.name, name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p BaseCollector) Create(data []byte, username string) error {
collect := &models.CollectRule{CollectType: p.name}
rule := p.newRule()
if err := json.Unmarshal(data, collect); err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
if err := collect.Validate(rule); err != nil {
return err
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_create", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collect.Creator = username
collect.LastUpdator = username
old, err := p.GetByNameAndNid(collect.Name, collect.Nid)
if err != nil {
return err
}
if old != nil {
return fmt.Errorf("同节点下策略名称 %s 已存在", collect.Name)
}
return models.CreateCollect(p.name, username, collect)
}
func (p BaseCollector) Update(data []byte, username string) error {
collect := &models.CollectRule{}
rule := p.newRule()
if err := json.Unmarshal(data, collect); err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
if err := collect.Validate(rule); err != nil {
return err
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_modify", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
//校验采集是否存在
obj, err := p.Get(collect.Id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.name, collect.Id)
}
tmpId := obj.(*models.CollectRule).Id
if tmpId == 0 {
return fmt.Errorf("采集不存在 type:%s id:%d", p.name, collect.Id)
}
collect.Creator = username
collect.LastUpdator = username
old, err := p.GetByNameAndNid(collect.Name, collect.Nid)
if err != nil {
return err
}
if old != nil && tmpId != old.(*models.CollectRule).Id {
return fmt.Errorf("同节点下策略名称 %s 已存在", collect.Name)
}
return collect.Update()
}
func (p BaseCollector) Delete(id int64, username string) error {
tmp, err := p.Get(id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.name, id)
}
nid := tmp.(*models.CollectRule).Nid
can, err := models.UsernameCandoNodeOp(username, "mon_collect_delete", int64(nid))
if err != nil {
return fmt.Errorf("models.UsernameCandoNodeOp error %s", err)
}
if !can {
return fmt.Errorf("permission deny")
}
return models.DeleteCollectRule(id)
}

View File

@ -0,0 +1,77 @@
package collector
import (
"errors"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/toolkits/i18n"
"github.com/influxdata/telegraf"
"github.com/toolkits/pkg/logger"
)
var (
collectors = map[string]Collector{}
remoteCollectors = []string{}
localCollectors = []string{}
errUnsupported = errors.New("unsupported")
)
type Category string
const (
RemoteCategory Category = "remote" // used for prober
LocalCategory Category = "local" // used for agent
)
type Collector interface {
Name() string
Category() Category
Get(id int64) (interface{}, error)
Gets(nids []int64) ([]interface{}, error)
GetByNameAndNid(name string, nid int64) (interface{}, error)
Create(data []byte, username string) error
Update(data []byte, username string) error
Delete(id int64, username string) error
Template() (interface{}, error)
TelegrafInput(*models.CollectRule) (telegraf.Input, error)
}
func CollectorRegister(c Collector) error {
name := c.Name()
if _, ok := collectors[name]; ok {
return fmt.Errorf("collector %s exists", name)
}
collectors[name] = c
if c.Category() == RemoteCategory {
remoteCollectors = append(remoteCollectors, name)
}
if c.Category() == LocalCategory {
localCollectors = append(localCollectors, name)
}
return nil
}
func GetCollector(name string) (Collector, error) {
if c, ok := collectors[name]; !ok {
return nil, fmt.Errorf("collector %s does not exist", name)
} else {
return c, nil
}
}
func GetRemoteCollectors() []string {
return remoteCollectors
}
func GetLocalCollectors() []string {
return localCollectors
}
func _s(format string, a ...interface{}) string {
logger.Debugf(` "%s": "%s",`, format, format)
return i18n.Sprintf(format, a...)
}

View File

@ -0,0 +1,227 @@
package collector
import (
"encoding/json"
"fmt"
"reflect"
"strings"
"sync"
"unicode"
)
var fieldCache sync.Map // map[reflect.Type]structFields
type Field struct {
skip bool `json:"-"`
// definitions map[string][]Field `json:"-"`
Name string `json:"name,omitempty"`
Label string `json:"label,omitempty"`
Example string `json:"example,omitempty"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitempty"`
Items *Field `json:"items,omitempty" description:"arrays's items"`
Type string `json:"type,omitempty" description:"struct,boolean,integer,folat,string,array"`
Ref string `json:"$ref,omitempty" description:"name of the struct ref"`
Fields []Field `json:"fields,omitempty" description:"fields of struct type"`
Definitions map[string][]Field `json:"definitions,omitempty"`
}
func (p Field) String() string {
return prettify(p)
}
// cachedTypeContent is like typeFields but uses a cache to avoid repeated work.
func cachedTypeContent(t reflect.Type) Field {
if f, ok := fieldCache.Load(t); ok {
return f.(Field)
}
f, _ := fieldCache.LoadOrStore(t, typeContent(t))
return f.(Field)
}
func typeContent(t reflect.Type) Field {
definitions := map[string][]Field{t.String(): nil}
ret := Field{
// definitions: map[string][]Field{
// t.String(): nil,
// },
}
for i := 0; i < t.NumField(); i++ {
sf := t.Field(i)
isUnexported := sf.PkgPath != ""
if sf.Anonymous {
panic("unsupported anonymous field")
} else if isUnexported {
// Ignore unexported non-embedded fields.
continue
}
field := getTagOpt(sf)
if field.skip {
continue
}
ft := sf.Type
fieldType(ft, &field, definitions)
// Record found field and index sequence.
if field.Name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
ret.Fields = append(ret.Fields, field)
continue
}
panic("unsupported anonymous, struct field")
}
definitions[t.String()] = ret.Fields
ret.Definitions = definitions
return ret
}
// tagOptions is the string following a comma in a struct field's "json"
// tag, or the empty string. It does not include the leading comma.
type tagOptions string
// parseTag splits a struct field's json tag into its name and
// comma-separated options.
func parseTag(tag string) (string, tagOptions) {
if idx := strings.Index(tag, ","); idx != -1 {
return tag[:idx], tagOptions(tag[idx+1:])
}
return tag, tagOptions("")
}
// Contains reports whether a comma-separated list of options
// contains a particular substr flag. substr must be surrounded by a
// string boundary or commas.
func (o tagOptions) Contains(optionName string) bool {
if len(o) == 0 {
return false
}
s := string(o)
for s != "" {
var next string
i := strings.Index(s, ",")
if i >= 0 {
s, next = s[:i], s[i+1:]
}
if s == optionName {
return true
}
s = next
}
return false
}
func getTagOpt(sf reflect.StructField) (opt Field) {
if sf.Anonymous {
return
}
tag := sf.Tag.Get("json")
if tag == "-" {
opt.skip = true
return
}
name, opts := parseTag(tag)
if opts.Contains("required") {
opt.Required = true
}
opt.Name = name
opt.Label = _s(sf.Tag.Get("label"))
opt.Example = sf.Tag.Get("example")
opt.Description = _s(sf.Tag.Get("description"))
return
}
func isValidTag(s string) bool {
if s == "" {
return false
}
for _, c := range s {
switch {
case strings.ContainsRune("!#$%&()*+-./:<=>?@[]^_{|}~ ", c):
case !unicode.IsLetter(c) && !unicode.IsDigit(c):
return false
}
}
return true
}
func panicType(ft reflect.Type, args ...interface{}) {
msg := fmt.Sprintf("type field %s %s", ft.PkgPath(), ft.Name())
if len(args) > 0 {
panic(fmt.Sprint(args...) + " " + msg)
}
panic(msg)
}
func Template(v interface{}) (interface{}, error) {
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() != reflect.Struct {
return nil, fmt.Errorf("invalid argument, must be a struct")
}
content := cachedTypeContent(rv.Type())
return content, nil
}
func prettify(in interface{}) string {
b, _ := json.MarshalIndent(in, "", " ")
return string(b)
}
func fieldType(t reflect.Type, in *Field, definitions map[string][]Field) {
if t.Name() == "" && t.Kind() == reflect.Ptr {
// Follow pointer.
t = t.Elem()
}
switch t.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
in.Type = "integer"
case reflect.Float32, reflect.Float64:
in.Type = "float"
case reflect.Bool:
in.Type = "boolean"
case reflect.String:
in.Type = "string"
case reflect.Struct:
name := t.String()
if _, ok := definitions[name]; !ok {
f := cachedTypeContent(t)
for k, v := range f.Definitions {
definitions[k] = v
}
}
in.Ref = t.String()
case reflect.Slice, reflect.Array:
t2 := t.Elem()
if t2.Kind() == reflect.Ptr {
t2 = t2.Elem()
}
if k := t2.Kind(); k == reflect.Int || k == reflect.Int32 || k == reflect.Int64 ||
k == reflect.Uint || k == reflect.Uint32 || k == reflect.Uint64 ||
k == reflect.Float32 || k == reflect.Float64 ||
k == reflect.Bool || k == reflect.String || k == reflect.Struct {
in.Type = "array"
in.Items = &Field{}
fieldType(t2, in.Items, definitions)
} else {
panic(fmt.Sprintf("unspport type %s items %s", t.String(), t2.String()))
}
default:
panic(fmt.Sprintf("unspport type %s", t.String()))
// in.Type = "string"
}
}

View File

@ -3,6 +3,7 @@ package config
const Version = 1
const JudgesReplicas = 500
const ProbersReplicas = 500
const DetectorReplicas = 500
const (

View File

@ -94,7 +94,9 @@ type loggerSection struct {
}
type httpSection struct {
Listen string `yaml:"listen"`
Mode string `yaml:"mode"`
CookieName string `yaml:"cookieName"`
CookieDomain string `yaml:"cookieDomain"`
}
type proxySection struct {

View File

@ -5,7 +5,6 @@ import (
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/config"
"github.com/didi/nightingale/src/modules/monapi/tools"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/errors"
@ -15,7 +14,7 @@ import (
// GetCookieUser 从cookie中获取username
func GetCookieUser() gin.HandlerFunc {
return func(c *gin.Context) {
username := cookieUser(c)
username := sessionUsername(c)
if username == "" {
username = headerUser(c)
}
@ -29,15 +28,6 @@ func GetCookieUser() gin.HandlerFunc {
}
}
func cookieUser(c *gin.Context) string {
uuid, err := c.Cookie("ecmc-user")
if err != nil {
return ""
}
return tools.UsernameByUUID(uuid)
}
func headerUser(c *gin.Context) string {
token := c.GetHeader("X-User-Token")
if token == "" {
@ -88,3 +78,19 @@ func getUserByToken(token string) (user *models.User, err error) {
return
}
func sessionUsername(c *gin.Context) string {
sess, err := models.SessionGetWithCache(readSessionId(c))
if err != nil {
return ""
}
return sess.Username
}
func readSessionId(c *gin.Context) string {
sid, err := c.Cookie(config.Get().HTTP.CookieName)
if err != nil {
return ""
}
return sid
}

View File

@ -2,9 +2,11 @@ package http
import (
"context"
"log"
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"strings"
"time"
"github.com/didi/nightingale/src/common/address"
@ -31,7 +33,7 @@ func Start() {
loggerMid := middleware.LoggerWithConfig(middleware.LoggerConfig{SkipPaths: skipPaths})
recoveryMid := middleware.Recovery()
if c.Logger.Level != "DEBUG" {
if strings.ToLower(c.HTTP.Mode) == "release" {
gin.SetMode(gin.ReleaseMode)
middleware.DisableConsoleColor()
} else {
@ -47,9 +49,10 @@ func Start() {
srv.Handler = r
go func() {
log.Println("starting http server, listening on:", srv.Addr)
fmt.Println("http.listening:", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listening %s occur error: %s\n", srv.Addr, err)
fmt.Printf("listening %s occur error: %s\n", srv.Addr, err)
os.Exit(3)
}
}()
}
@ -59,14 +62,15 @@ func Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalln("cannot shutdown http server:", err)
fmt.Println("cannot shutdown http server:", err)
os.Exit(2)
}
// catching ctx.Done(). timeout of 5 seconds.
select {
case <-ctx.Done():
log.Println("shutdown http server timeout of 5 seconds.")
fmt.Println("shutdown http server timeout of 5 seconds.")
default:
log.Println("http server stopped")
fmt.Println("http server stopped")
}
}

View File

@ -83,20 +83,41 @@ func Config(r *gin.Engine) {
event.POST("/cur/claim", eventCurClaim)
}
// TODO: merge to collect-rule
collect := r.Group("/api/mon/collect").Use(GetCookieUser())
{
collect.POST("", collectPost)
collect.GET("/list", collectsGet)
collect.GET("", collectGet)
collect.PUT("", collectPut)
collect.DELETE("", collectsDel)
collect.POST("/check", regExpCheck)
collect.POST("", collectRulePost) // create a collect rule
collect.GET("/list", collectRulesGet) // get collect rules
collect.GET("", collectRuleGet) // get collect rule by type & id
collect.PUT("", collectRulePut) // update collect rule by type & id
collect.DELETE("", collectsRuleDel) // delete collect rules by type & ids
collect.POST("/check", regExpCheck) // check collect rule
}
// TODO: merge to collect-rules, used by agent
collects := r.Group("/api/mon/collects")
{
collects.GET("/:endpoint", collectGetByEndpoint)
collects.GET("", collectsGet)
collects.GET("/:endpoint", collectRulesGetByLocalEndpoint) // get collect rules by endpoint, for agent
collects.GET("", collectRulesGet) // get collect rules
}
collectRules := r.Group("/api/mon/collect-rules").Use(GetCookieUser())
{
collectRules.POST("", collectRulePost) // create a collect rule
collectRules.GET("/list", collectRulesGet) // get collect rules
collectRules.GET("", collectRuleGet) // get collect rule by type & id
collectRules.PUT("", collectRulePut) // update collect rule by type & id
collectRules.DELETE("", collectsRuleDel) // delete collect rules by type & ids
collectRules.POST("/check", regExpCheck) // check collect rule
collectRules.GET("/types", collectRuleTypesGet) // get collect types, category: local|remote
collectRules.GET("/types/:type/template", collectRuleTemplateGet) // get collect teplate by type
}
collectRulesAnonymous := r.Group("/api/mon/collect-rules")
{
collectRulesAnonymous.GET("/endpoints/:endpoint/remote", collectRulesGetByRemoteEndpoint) // for prober
collectRulesAnonymous.GET("/endpoints/:endpoint/local", collectRulesGetByLocalEndpoint) // for agent
}
stra := r.Group("/api/mon/stra").Use(GetCookieUser())
@ -151,6 +172,15 @@ func Config(r *gin.Engine) {
indexProxy.POST("/counter/detail", indexReq)
}
/*
v1 := r.Group("/v1/mon")
{
v1.POST("/report-detector-heartbeat", detectorHeartBeat)
v1.GET("/detectors", detectorInstanceGets)
v1.GET("/rules", collectRulesGet)
}
*/
if config.Get().Logger.Level == "DEBUG" {
pprof.Register(r, "/api/monapi/debug/pprof")
}

View File

@ -6,8 +6,7 @@ import (
"regexp"
"strings"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/config"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/didi/nightingale/src/modules/monapi/scache"
"github.com/gin-gonic/gin"
@ -16,201 +15,44 @@ import (
)
type CollectRecv struct {
Type string `json:"type"`
Data interface{} `json:"data"`
Type string `json:"type"`
Data json.RawMessage `json:"data"`
}
//此处实现需要重构
func collectPost(c *gin.Context) {
creator := loginUsername(c)
func collectRulePost(c *gin.Context) {
var recv []CollectRecv
errors.Dangerous(c.ShouldBind(&recv))
creator := loginUsername(c)
for _, obj := range recv {
switch obj.Type {
case "port":
collect := new(models.PortCollect)
cl, err := collector.GetCollector(obj.Type)
errors.Dangerous(err)
b, err := json.Marshal(obj.Data)
if err != nil {
bomb("marshal body %s err:%v", obj, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_create", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Creator = creator
collect.LastUpdator = creator
nid := collect.Nid
name := collect.Name
old, err := models.GetCollectByNameAndNid(obj.Type, name, nid)
errors.Dangerous(err)
if old != nil {
bomb("same stra name %s in node", name)
}
errors.Dangerous(models.CreateCollect(obj.Type, creator, collect))
case "proc":
collect := new(models.ProcCollect)
b, err := json.Marshal(obj.Data)
if err != nil {
bomb("marshal body %s err:%v", obj, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_create", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Creator = creator
collect.LastUpdator = creator
nid := collect.Nid
name := collect.Name
old, err := models.GetCollectByNameAndNid(obj.Type, name, nid)
errors.Dangerous(err)
if old != nil {
bomb("same stra name %s in node", name)
}
errors.Dangerous(models.CreateCollect(obj.Type, creator, collect))
case "log":
collect := new(models.LogCollect)
b, err := json.Marshal(obj.Data)
if err != nil {
bomb("marshal body %s err:%v", obj, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_create", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Encode()
collect.Creator = creator
collect.LastUpdator = creator
nid := collect.Nid
name := collect.Name
old, err := models.GetCollectByNameAndNid(obj.Type, name, nid)
errors.Dangerous(err)
if old != nil {
bomb("same stra name %s in node", name)
}
errors.Dangerous(models.CreateCollect(obj.Type, creator, collect))
case "plugin":
collect := new(models.PluginCollect)
b, err := json.Marshal(obj.Data)
if err != nil {
bomb("marshal body %s err:%v", obj, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_create", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Creator = creator
collect.LastUpdator = creator
nid := collect.Nid
name := collect.Name
old, err := models.GetCollectByNameAndNid(obj.Type, name, nid)
errors.Dangerous(err)
if old != nil {
bomb("same stra name %s in node", name)
}
errors.Dangerous(models.CreateCollect(obj.Type, creator, collect))
case "api":
collect := new(models.ApiCollect)
b, err := json.Marshal(obj.Data)
if err != nil {
bomb("marshal body %s err:%v", obj, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_create", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Encode()
collect.Creator = creator
collect.LastUpdator = creator
nid := collect.Nid
name := collect.Name
old, err := models.GetCollectByNameAndNid(obj.Type, name, nid)
errors.Dangerous(err)
if old != nil {
bomb("same stra name %s in node", name)
}
errors.Dangerous(models.CreateCollect(obj.Type, creator, collect))
default:
bomb("collect type not support")
if err := cl.Create([]byte(obj.Data), creator); err != nil {
errors.Bomb("%s add rule err %s", obj.Type, err)
}
}
renderData(c, "ok", nil)
}
func collectGetByEndpoint(c *gin.Context) {
func collectRulesGetByLocalEndpoint(c *gin.Context) {
collect := scache.CollectCache.GetBy(urlParamStr(c, "endpoint"))
renderData(c, collect, nil)
}
func collectGet(c *gin.Context) {
func collectRuleGet(c *gin.Context) {
t := mustQueryStr(c, "type")
nid := mustQueryInt64(c, "id")
collect, err := models.GetCollectById(t, nid)
id := mustQueryInt64(c, "id")
cl, err := collector.GetCollector(t)
errors.Dangerous(err)
renderData(c, collect, nil)
ret, err := cl.Get(id)
renderData(c, ret, err)
}
func collectsGet(c *gin.Context) {
func collectRulesGet(c *gin.Context) {
nid := queryInt64(c, "nid", -1)
tp := queryStr(c, "type", "")
var resp []interface{}
@ -222,275 +64,36 @@ func collectsGet(c *gin.Context) {
types = []string{tp}
}
if nid == -1 && tp != "" { //没有nid参数tp不为空
if tp == "api" {
collects, err := models.GetApiCollects()
errors.Dangerous(err)
for _, c := range collects {
c.Decode()
resp = append(resp, c)
}
nids := []int64{nid}
for _, t := range types {
cl, err := collector.GetCollector(t)
if err != nil {
logger.Warning(t, err)
continue
}
} else {
nids := []int64{nid}
for _, t := range types {
collects, err := models.GetCollectByNid(t, nids)
if err != nil {
logger.Warning(t, err)
continue
}
resp = append(resp, collects...)
ret, err := cl.Gets(nids)
if err != nil {
logger.Warning(t, err)
continue
}
resp = append(resp, ret...)
}
renderData(c, resp, nil)
}
func collectPut(c *gin.Context) {
creator := loginUsername(c)
func collectRulePut(c *gin.Context) {
var recv CollectRecv
errors.Dangerous(c.ShouldBind(&recv))
switch recv.Type {
case "port":
collect := new(models.PortCollect)
cl, err := collector.GetCollector(recv.Type)
errors.Dangerous(err)
b, err := json.Marshal(recv.Data)
if err != nil {
bomb("marshal body %s err:%v", recv, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_modify", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := models.GetCollectById(recv.Type, collect.Id) //id找不到的情况
if err != nil {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
tmpId := obj.(*models.PortCollect).Id
if tmpId == 0 {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
collect.Creator = creator
collect.LastUpdator = creator
old, err := models.GetCollectByNameAndNid(recv.Type, name, nid)
errors.Dangerous(err)
if old != nil && tmpId != old.(*models.PortCollect).Id {
bomb("same stra name %s in node", name)
}
errors.Dangerous(collect.Update())
renderData(c, "ok", nil)
return
case "proc":
collect := new(models.ProcCollect)
b, err := json.Marshal(recv.Data)
if err != nil {
bomb("marshal body %s err:%v", recv, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_modify", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := models.GetCollectById(recv.Type, collect.Id) //id找不到的情况
if err != nil {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
tmpId := obj.(*models.ProcCollect).Id
if tmpId == 0 {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
can, err = models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_modify", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Creator = creator
collect.LastUpdator = creator
old, err := models.GetCollectByNameAndNid(recv.Type, name, nid)
errors.Dangerous(err)
if old != nil && tmpId != old.(*models.ProcCollect).Id {
bomb("same stra name %s in node", name)
}
errors.Dangerous(collect.Update())
renderData(c, "ok", nil)
return
case "log":
collect := new(models.LogCollect)
b, err := json.Marshal(recv.Data)
if err != nil {
bomb("marshal body %s err:%v", recv, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_modify", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Encode()
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := models.GetCollectById(recv.Type, collect.Id) //id找不到的情况
if err != nil {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
tmpId := obj.(*models.LogCollect).Id
if tmpId == 0 {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
collect.Creator = creator
collect.LastUpdator = creator
old, err := models.GetCollectByNameAndNid(recv.Type, name, nid)
errors.Dangerous(err)
if old != nil && tmpId != old.(*models.LogCollect).Id {
bomb("same stra name %s in node", name)
}
errors.Dangerous(collect.Update())
renderData(c, "ok", nil)
return
case "plugin":
collect := new(models.PluginCollect)
b, err := json.Marshal(recv.Data)
if err != nil {
bomb("marshal body %s err:%v", recv, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_modify", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := models.GetCollectById(recv.Type, collect.Id) //id找不到的情况
if err != nil {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
tmpId := obj.(*models.PluginCollect).Id
if tmpId == 0 {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
collect.Creator = creator
collect.LastUpdator = creator
old, err := models.GetCollectByNameAndNid(recv.Type, name, nid)
errors.Dangerous(err)
if old != nil && tmpId != old.(*models.PluginCollect).Id {
bomb("same stra name %s in node", name)
}
errors.Dangerous(collect.Update())
renderData(c, "ok", nil)
return
case "api":
collect := new(models.ApiCollect)
b, err := json.Marshal(recv.Data)
if err != nil {
bomb("marshal body %s err:%v", recv, err)
}
err = json.Unmarshal(b, collect)
if err != nil {
bomb("unmarshal body %s err:%v", string(b), err)
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_modify", collect.Nid)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
collect.Encode()
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := models.GetCollectById(recv.Type, collect.Id) //id找不到的情况
if err != nil {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
tmpId := obj.(*models.ApiCollect).Id
if tmpId == 0 {
bomb("采集不存在 type:%s id:%d", recv.Type, collect.Id)
}
collect.Creator = creator
collect.LastUpdator = creator
old, err := models.GetCollectByNameAndNid(recv.Type, name, nid)
errors.Dangerous(err)
if old != nil && tmpId != old.(*models.ApiCollect).Id {
bomb("same stra name %s in node", name)
}
errors.Dangerous(collect.Update())
renderData(c, "ok", nil)
return
default:
bomb("采集类型不合法")
creator := loginUsername(c)
if err := cl.Update([]byte(recv.Data), creator); err != nil {
errors.Bomb("%s update rule err %s", recv.Type, err)
}
renderData(c, "ok", nil)
}
@ -499,89 +102,44 @@ type CollectsDelRev struct {
Ids []int64 `json:"ids"`
}
func collectsDel(c *gin.Context) {
username := loginUsername(c)
func collectsRuleDel(c *gin.Context) {
var recv []CollectsDelRev
errors.Dangerous(c.ShouldBind(&recv))
username := loginUsername(c)
for _, obj := range recv {
for i := 0; i < len(obj.Ids); i++ {
tmp, err := models.GetCollectById(obj.Type, obj.Ids[i]) //id找不到的情况
if err != nil {
bomb("采集不存在 type:%s id:%d", obj.Type, obj.Ids[i])
}
if tmp == nil {
bomb("采集不存在 type:%s id:%d", obj.Type, obj.Ids[i])
}
var nid int64
switch obj.Type {
case "log":
nid = tmp.(*models.LogCollect).Nid
case "proc":
nid = tmp.(*models.ProcCollect).Nid
case "port":
nid = tmp.(*models.PortCollect).Nid
case "plugin":
nid = tmp.(*models.PluginCollect).Nid
}
can, err := models.UsernameCandoNodeOp(loginUsername(c), "mon_collect_delete", int64(nid))
cl, err := collector.GetCollector(obj.Type)
errors.Dangerous(err)
if !can {
bomb("permission deny")
}
}
for i := 0; i < len(obj.Ids); i++ {
switch obj.Type {
case "api":
err := models.DeleteApiCollect(obj.Ids[i])
errors.Dangerous(err)
default:
err := models.DeleteCollectById(obj.Type, username, obj.Ids[i])
if err := cl.Delete(obj.Ids[i], username); err != nil {
errors.Dangerous(err)
}
}
}
renderData(c, "ok", nil)
}
type ApiStraRev struct {
Sid int64 `json:"sid"`
Cid int64 `json:"cid"`
func collectRuleTypesGet(c *gin.Context) {
category := mustQueryStr(c, "category")
switch category {
case "remote":
renderData(c, collector.GetRemoteCollectors(), nil)
case "local":
renderData(c, collector.GetLocalCollectors(), nil)
default:
renderData(c, nil, nil)
}
}
type ApiStraRes struct {
Has bool `json:"has"`
Sid int64 `json:"sid"`
}
func ApiStraGet(c *gin.Context) {
cid := mustQueryInt64(c, "cid")
sid, err := models.GetSidByCid(cid)
var res ApiStraRes
func collectRuleTemplateGet(c *gin.Context) {
t := urlParamStr(c, "type")
collector, err := collector.GetCollector(t)
errors.Dangerous(err)
if sid != 0 {
res.Has = true
res.Sid = sid
}
renderData(c, res, nil)
}
func ApiStraPost(c *gin.Context) {
recv := new(models.ApiCollectSid)
errors.Dangerous(c.ShouldBind(&recv))
errors.Dangerous(recv.Add())
renderData(c, "ok", nil)
return
}
func ApiRegionGet(c *gin.Context) {
renderData(c, config.Get().Region, nil)
tpl, err := collector.Template()
renderData(c, tpl, err)
}
type RegExpCheckDto struct {
@ -788,3 +346,9 @@ func genSubErrMsg(sign string) string {
func genIllegalCharErrMsg() string {
return fmt.Sprintf(`正则匹配成功。但是tag的key或者value包含非法字符:[:,/=\r\n\t], 请重新调整`)
}
func collectRulesGetByRemoteEndpoint(c *gin.Context) {
rules := scache.CollectRuleCache.GetBy(urlParamStr(c, "endpoint"))
renderData(c, rules, nil)
}

View File

@ -142,22 +142,13 @@ func renderData(c *gin.Context, data interface{}, err error) {
renderMessage(c, err.Error())
}
func cookieUsername(c *gin.Context) string {
uuid, err := c.Cookie("ecmc-user")
if err != nil {
return ""
}
return models.UsernameByUUID(uuid)
}
func loginUsername(c *gin.Context) string {
username1, has := c.Get("username")
if has {
return username1.(string)
}
username2 := cookieUsername(c)
username2 := sessionUsername(c)
if username2 == "" {
bomb("unauthorized")
}

View File

@ -129,7 +129,7 @@ func effectiveStrasGet(c *gin.Context) {
if queryInt(c, "all", 0) == 1 {
stras = scache.StraCache.GetAll()
} else if instance != "" {
node, err := scache.ActiveNode.GetNodeBy(instance)
node, err := scache.ActiveJudgeNode.GetNodeBy(instance)
errors.Dangerous(err)
stras = scache.StraCache.GetByNode(node)

View File

@ -18,6 +18,7 @@ import (
"github.com/didi/nightingale/src/modules/monapi/scache"
"github.com/didi/nightingale/src/toolkits/i18n"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/all"
_ "github.com/go-sql-driver/mysql"
"github.com/toolkits/pkg/cache"

1
src/modules/monapi/plugins/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
prometheus/

View File

@ -0,0 +1,16 @@
package all
import (
// remote
_ "github.com/didi/nightingale/src/modules/monapi/plugins/api"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/github"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/mysql"
// _ "github.com/didi/nightingale/src/modules/monapi/plugins/prometheus"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/redis"
// local
_ "github.com/didi/nightingale/src/modules/monapi/plugins/log"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/plugin"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/port"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/proc"
)

View File

@ -0,0 +1,147 @@
package api
import (
"encoding/json"
"errors"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
)
func init() {
collector.CollectorRegister(&ApiCollector{})
}
type ApiCollector struct{}
func (p ApiCollector) Name() string { return "api" }
func (p ApiCollector) Category() collector.Category { return collector.RemoteCategory }
func (p ApiCollector) Template() (interface{}, error) { return nil, nil }
func (p ApiCollector) TelegrafInput(*models.CollectRule) (telegraf.Input, error) {
return nil, errors.New("unsupported")
}
func (p ApiCollector) Get(id int64) (interface{}, error) {
collect := new(models.ApiCollect)
has, err := models.DB["mon"].Where("id = ?", id).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p ApiCollector) Gets(nids []int64) (ret []interface{}, err error) {
collects := []models.ApiCollect{}
err = models.DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
c.Decode()
ret = append(ret, c)
}
return ret, err
}
func (p ApiCollector) GetByNameAndNid(name string, nid int64) (interface{}, error) {
collect := new(models.ApiCollect)
has, err := models.DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p ApiCollector) Create(data []byte, username string) error {
collect := new(models.ApiCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_create", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collect.Encode()
collect.Creator = username
collect.LastUpdator = username
nid := collect.Nid
name := collect.Name
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return models.CreateCollect(p.Name(), username, collect)
}
func (p ApiCollector) Update(data []byte, username string) error {
collect := new(models.ApiCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_modify", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collect.Encode()
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := p.Get(collect.Id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
tmpId := obj.(*models.ApiCollect).Id
if tmpId == 0 {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
collect.Creator = username
collect.LastUpdator = username
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil && tmpId != old.(*models.ApiCollect).Id {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return collect.Update()
}
func (p ApiCollector) Delete(id int64, username string) error {
tmp, err := p.Get(id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), id)
}
nid := tmp.(*models.ApiCollect).Nid
can, err := models.UsernameCandoNodeOp(username, "mon_collect_delete", int64(nid))
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
return models.DeleteCollectById(p.Name(), username, id)
}

View File

@ -0,0 +1,64 @@
# GitHub Input Plugin
Gather repository information from [GitHub][] hosted repositories.
**Note:** Telegraf also contains the [webhook][] input which can be used as an
alternative method for collecting repository information.
### Configuration
```toml
[[inputs.github]]
## List of repositories to monitor
repositories = [
"influxdata/telegraf",
"influxdata/influxdb"
]
## Github API access token. Unauthenticated requests are limited to 60 per hour.
# access_token = ""
## Github API enterprise url. Github Enterprise accounts must specify their base url.
# enterprise_base_url = ""
## Timeout for HTTP requests.
# http_timeout = "5s"
```
### Metrics
- github_repository
- tags:
- name - The repository name
- owner - The owner of the repository
- language - The primary language of the repository
- license - The license set for the repository
- fields:
- forks (int)
- open_issues (int)
- networks (int)
- size (int)
- subscribers (int)
- stars (int)
- watchers (int)
When the [internal][] input is enabled:
+ internal_github
- tags:
- access_token - An obfuscated reference to the configured access token or "Unauthenticated"
- fields:
- limit - How many requests you are limited to (per hour)
- remaining - How many requests you have remaining (per hour)
- blocks - How many requests have been blocked due to rate limit
### Example Output
```
github_repository,language=Go,license=MIT\ License,name=telegraf,owner=influxdata forks=2679i,networks=2679i,open_issues=794i,size=23263i,stars=7091i,subscribers=316i,watchers=7091i 1563901372000000000
internal_github,access_token=Unauthenticated rate_limit_remaining=59i,rate_limit_limit=60i,rate_limit_blocks=0i 1552653551000000000
```
[GitHub]: https://www.github.com
[internal]: /plugins/inputs/internal
[webhook]: /plugins/inputs/webhooks/github

View File

@ -0,0 +1,55 @@
package github
import (
"fmt"
"time"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
)
func init() {
collector.CollectorRegister(NewGitHubCollector()) // for monapi
}
type GitHubCollector struct {
*collector.BaseCollector
}
func NewGitHubCollector() *GitHubCollector {
return &GitHubCollector{BaseCollector: collector.NewBaseCollector(
"github",
collector.RemoteCategory,
func() interface{} { return &GitHubRule{} },
)}
}
type GitHubRule struct {
Repositories []string `label:"Repositories" json:"repositories" description:"List of repositories to monitor"`
AccessToken string `label:"Access token" json:"access_token" description:"Github API access token. Unauthenticated requests are limited to 60 per hour"`
EnterpriseBaseURL string `label:"Enterprise base url" json:"enterprise_base_url" description:"Github API enterprise url. Github Enterprise accounts must specify their base url"`
HTTPTimeout int `label:"HTTP timeout" json:"http_timeout" description:"Timeout for HTTP requests"`
}
func (p *GitHubRule) Validate() error {
if len(p.Repositories) == 0 || p.Repositories[0] == "" {
return fmt.Errorf("github.rule.repositories must be set")
}
if p.HTTPTimeout == 0 {
p.HTTPTimeout = 5
}
return nil
}
func (p *GitHubRule) TelegrafInput() (telegraf.Input, error) {
if err := p.Validate(); err != nil {
return nil, err
}
return &GitHub{
Repositories: p.Repositories,
AccessToken: p.AccessToken,
EnterpriseBaseURL: p.EnterpriseBaseURL,
HTTPTimeout: time.Second * time.Duration(p.HTTPTimeout),
}, nil
}

View File

@ -0,0 +1,200 @@
package github
import (
"context"
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/google/go-github/v32/github"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/selfstat"
"golang.org/x/oauth2"
)
// GitHub - plugin main structure
type GitHub struct {
Repositories []string `toml:"repositories"`
AccessToken string `toml:"access_token"`
EnterpriseBaseURL string `toml:"enterprise_base_url"`
HTTPTimeout time.Duration `toml:"http_timeout"`
githubClient *github.Client
obfuscatedToken string
RateLimit selfstat.Stat
RateLimitErrors selfstat.Stat
RateRemaining selfstat.Stat
}
const sampleConfig = `
## List of repositories to monitor.
repositories = [
"influxdata/telegraf",
"influxdata/influxdb"
]
## Github API access token. Unauthenticated requests are limited to 60 per hour.
# access_token = ""
## Github API enterprise url. Github Enterprise accounts must specify their base url.
# enterprise_base_url = ""
## Timeout for HTTP requests.
# http_timeout = "5s"
`
// SampleConfig returns sample configuration for this plugin.
func (g *GitHub) SampleConfig() string {
return sampleConfig
}
// Description returns the plugin description.
func (g *GitHub) Description() string {
return "Gather repository information from GitHub hosted repositories."
}
// Create GitHub Client
func (g *GitHub) createGitHubClient(ctx context.Context) (*github.Client, error) {
httpClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
},
Timeout: g.HTTPTimeout,
}
g.obfuscatedToken = "Unauthenticated"
if g.AccessToken != "" {
tokenSource := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: g.AccessToken},
)
oauthClient := oauth2.NewClient(ctx, tokenSource)
_ = context.WithValue(ctx, oauth2.HTTPClient, oauthClient)
g.obfuscatedToken = g.AccessToken[0:4] + "..." + g.AccessToken[len(g.AccessToken)-3:]
return g.newGithubClient(oauthClient)
}
return g.newGithubClient(httpClient)
}
func (g *GitHub) newGithubClient(httpClient *http.Client) (*github.Client, error) {
if g.EnterpriseBaseURL != "" {
return github.NewEnterpriseClient(g.EnterpriseBaseURL, "", httpClient)
}
return github.NewClient(httpClient), nil
}
// Gather GitHub Metrics
func (g *GitHub) Gather(acc telegraf.Accumulator) error {
ctx := context.Background()
if g.githubClient == nil {
githubClient, err := g.createGitHubClient(ctx)
if err != nil {
return err
}
g.githubClient = githubClient
tokenTags := map[string]string{
"access_token": g.obfuscatedToken,
}
g.RateLimitErrors = selfstat.Register("github", "rate_limit_blocks", tokenTags)
g.RateLimit = selfstat.Register("github", "rate_limit_limit", tokenTags)
g.RateRemaining = selfstat.Register("github", "rate_limit_remaining", tokenTags)
}
var wg sync.WaitGroup
wg.Add(len(g.Repositories))
for _, repository := range g.Repositories {
go func(repositoryName string, acc telegraf.Accumulator) {
defer wg.Done()
owner, repository, err := splitRepositoryName(repositoryName)
if err != nil {
acc.AddError(err)
return
}
repositoryInfo, response, err := g.githubClient.Repositories.Get(ctx, owner, repository)
if _, ok := err.(*github.RateLimitError); ok {
g.RateLimitErrors.Incr(1)
}
if err != nil {
acc.AddError(err)
return
}
g.RateLimit.Set(int64(response.Rate.Limit))
g.RateRemaining.Set(int64(response.Rate.Remaining))
now := time.Now()
tags := getTags(repositoryInfo)
fields := getFields(repositoryInfo)
acc.AddFields("github_repository", fields, tags, now)
}(repository, acc)
}
wg.Wait()
return nil
}
func splitRepositoryName(repositoryName string) (string, string, error) {
splits := strings.SplitN(repositoryName, "/", 2)
if len(splits) != 2 {
return "", "", fmt.Errorf("%v is not of format 'owner/repository'", repositoryName)
}
return splits[0], splits[1], nil
}
func getLicense(rI *github.Repository) string {
if licenseName := rI.GetLicense().GetName(); licenseName != "" {
return licenseName
}
return "None"
}
func getTags(repositoryInfo *github.Repository) map[string]string {
return map[string]string{
"owner": repositoryInfo.GetOwner().GetLogin(),
"name": repositoryInfo.GetName(),
"language": repositoryInfo.GetLanguage(),
"license": getLicense(repositoryInfo),
}
}
func getFields(repositoryInfo *github.Repository) map[string]interface{} {
return map[string]interface{}{
"stars": repositoryInfo.GetStargazersCount(),
"subscribers": repositoryInfo.GetSubscribersCount(),
"watchers": repositoryInfo.GetWatchersCount(),
"networks": repositoryInfo.GetNetworkCount(),
"forks": repositoryInfo.GetForksCount(),
"open_issues": repositoryInfo.GetOpenIssuesCount(),
"size": repositoryInfo.GetSize(),
}
}
/*
func init() {
inputs.Add("github", func() telegraf.Input {
return &GitHub{
HTTPTimeout: time.Second * 5,
}
})
}
*/

View File

@ -0,0 +1,140 @@
package github
import (
"net/http"
"reflect"
"testing"
gh "github.com/google/go-github/v32/github"
"github.com/stretchr/testify/require"
)
func TestNewGithubClient(t *testing.T) {
httpClient := &http.Client{}
g := &GitHub{}
client, err := g.newGithubClient(httpClient)
require.NoError(t, err)
require.Contains(t, client.BaseURL.String(), "api.github.com")
g.EnterpriseBaseURL = "api.example.com/"
enterpriseClient, err := g.newGithubClient(httpClient)
require.NoError(t, err)
require.Contains(t, enterpriseClient.BaseURL.String(), "api.example.com")
}
func TestSplitRepositoryNameWithWorkingExample(t *testing.T) {
var validRepositoryNames = []struct {
fullName string
owner string
repository string
}{
{"influxdata/telegraf", "influxdata", "telegraf"},
{"influxdata/influxdb", "influxdata", "influxdb"},
{"rawkode/saltstack-dotfiles", "rawkode", "saltstack-dotfiles"},
}
for _, tt := range validRepositoryNames {
t.Run(tt.fullName, func(t *testing.T) {
owner, repository, _ := splitRepositoryName(tt.fullName)
require.Equal(t, tt.owner, owner)
require.Equal(t, tt.repository, repository)
})
}
}
func TestSplitRepositoryNameWithNoSlash(t *testing.T) {
var invalidRepositoryNames = []string{
"influxdata-influxdb",
}
for _, tt := range invalidRepositoryNames {
t.Run(tt, func(t *testing.T) {
_, _, err := splitRepositoryName(tt)
require.Error(t, err)
})
}
}
func TestGetLicenseWhenExists(t *testing.T) {
licenseName := "MIT"
license := gh.License{Name: &licenseName}
repository := gh.Repository{License: &license}
getLicenseReturn := getLicense(&repository)
require.Equal(t, "MIT", getLicenseReturn)
}
func TestGetLicenseWhenMissing(t *testing.T) {
repository := gh.Repository{}
getLicenseReturn := getLicense(&repository)
require.Equal(t, "None", getLicenseReturn)
}
func TestGetTags(t *testing.T) {
licenseName := "MIT"
license := gh.License{Name: &licenseName}
ownerName := "influxdata"
owner := gh.User{Login: &ownerName}
fullName := "influxdata/influxdb"
repositoryName := "influxdb"
language := "Go"
repository := gh.Repository{
FullName: &fullName,
Name: &repositoryName,
License: &license,
Owner: &owner,
Language: &language,
}
getTagsReturn := getTags(&repository)
correctTagsReturn := map[string]string{
"owner": ownerName,
"name": repositoryName,
"language": language,
"license": licenseName,
}
require.Equal(t, true, reflect.DeepEqual(getTagsReturn, correctTagsReturn))
}
func TestGetFields(t *testing.T) {
stars := 1
forks := 2
openIssues := 3
size := 4
subscribers := 5
watchers := 6
repository := gh.Repository{
StargazersCount: &stars,
ForksCount: &forks,
OpenIssuesCount: &openIssues,
Size: &size,
NetworkCount: &forks,
SubscribersCount: &subscribers,
WatchersCount: &watchers,
}
getFieldsReturn := getFields(&repository)
correctFieldReturn := make(map[string]interface{})
correctFieldReturn["stars"] = 1
correctFieldReturn["forks"] = 2
correctFieldReturn["networks"] = 2
correctFieldReturn["open_issues"] = 3
correctFieldReturn["size"] = 4
correctFieldReturn["subscribers"] = 5
correctFieldReturn["watchers"] = 6
require.Equal(t, true, reflect.DeepEqual(getFieldsReturn, correctFieldReturn))
}

View File

@ -0,0 +1,147 @@
package log
import (
"encoding/json"
"errors"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
)
func init() {
collector.CollectorRegister(&LogCollector{})
}
type LogCollector struct{}
func (p LogCollector) Name() string { return "log" }
func (p LogCollector) Category() collector.Category { return collector.LocalCategory }
func (p LogCollector) Template() (interface{}, error) { return nil, nil }
func (p LogCollector) TelegrafInput(*models.CollectRule) (telegraf.Input, error) {
return nil, errors.New("unsupported")
}
func (p LogCollector) Get(id int64) (interface{}, error) {
collect := new(models.LogCollect)
has, err := models.DB["mon"].Where("id = ?", id).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p LogCollector) Gets(nids []int64) (ret []interface{}, err error) {
collects := []models.LogCollect{}
err = models.DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
c.Decode()
ret = append(ret, c)
}
return ret, err
}
func (p LogCollector) GetByNameAndNid(name string, nid int64) (interface{}, error) {
collect := new(models.LogCollect)
has, err := models.DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p LogCollector) Create(data []byte, username string) error {
collector := new(models.LogCollect)
err := json.Unmarshal(data, collector)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_create", collector.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collector.Encode()
collector.Creator = username
collector.LastUpdator = username
nid := collector.Nid
name := collector.Name
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return models.CreateCollect(p.Name(), username, collector)
}
func (p LogCollector) Update(data []byte, username string) error {
collector := new(models.LogCollect)
err := json.Unmarshal(data, collector)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_modify", collector.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collector.Encode()
nid := collector.Nid
name := collector.Name
//校验采集是否存在
obj, err := p.Get(collector.Id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collector.Id)
}
tmpId := obj.(*models.LogCollect).Id
if tmpId == 0 {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collector.Id)
}
collector.Creator = username
collector.LastUpdator = username
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil && tmpId != old.(*models.LogCollect).Id {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return collector.Update()
}
func (p LogCollector) Delete(id int64, username string) error {
tmp, err := p.Get(id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), id)
}
nid := tmp.(*models.LogCollect).Nid
can, err := models.UsernameCandoNodeOp(username, "mon_collect_delete", int64(nid))
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
return models.DeleteCollectById(p.Name(), username, id)
}

View File

@ -0,0 +1,38 @@
package redis
import (
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs/redis"
)
func init() {
collector.CollectorRegister(NewRedisCollector()) // for monapi
}
type RedisCollector struct {
*collector.BaseCollector
}
func NewRedisCollector() *RedisCollector {
return &RedisCollector{BaseCollector: collector.NewBaseCollector(
"redis",
collector.RemoteCategory,
func() interface{} { return &RedisRule{} },
)}
}
type RedisRule struct {
}
func (p *RedisRule) Validate() error {
return nil
}
func (p *RedisRule) TelegrafInput() (telegraf.Input, error) {
if err := p.Validate(); err != nil {
return nil, err
}
return &redis.Redis{}, nil
}

View File

@ -0,0 +1,86 @@
package mysql
import (
"fmt"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs/mysql"
)
func init() {
collector.CollectorRegister(NewMysqlCollector()) // for monapi
}
type MysqlCollector struct {
*collector.BaseCollector
}
func NewMysqlCollector() *MysqlCollector {
return &MysqlCollector{BaseCollector: collector.NewBaseCollector(
"mysql",
collector.RemoteCategory,
func() interface{} { return &MysqlRule{} },
)}
}
type MysqlRule struct {
Servers []string `label:"Servers" json:"servers,required" description:"specify servers via a url matching\n[username[:password]@][protocol[(address)]]/[?tls=[true|false|skip-verify|custom]]\nsee https://github.com/go-sql-driver/mysql#dsn-data-source-name" example:"servers = ['user:passwd@tcp(127.0.0.1:3306)/?tls=false']\nservers = ["user@tcp(127.0.0.1:3306)/?tls=false"]"`
PerfEventsStatementsDigestTextLimit int64 `label:"-" json:"-"`
PerfEventsStatementsLimit int64 `label:"-" json:"-"`
PerfEventsStatementsTimeLimit int64 `label:"-" json:"-"`
TableSchemaDatabases []string `label:"Databases" json:"table_schema_databases" description:"if the list is empty, then metrics are gathered from all database tables"`
GatherProcessList bool `label:"Process List" json:"gather_process_list" description:"gather thread state counts from INFORMATION_SCHEMA.PROCESSLIST"`
GatherUserStatistics bool `label:"User Statistics" json:"gather_user_statistics" description:"gather user statistics from INFORMATION_SCHEMA.USER_STATISTICS"`
GatherInfoSchemaAutoInc bool `label:"Auto Increment" json:"gather_info_schema_auto_inc" description:"gather auto_increment columns and max values from information schema"`
GatherInnoDBMetrics bool `label:"Innodb Metrics" json:"gather_innodb_metrics" description:"gather metrics from INFORMATION_SCHEMA.INNODB_METRICS"`
GatherSlaveStatus bool `label:"Slave Status" json:"gather_slave_status" description:"gather metrics from SHOW SLAVE STATUS command output"`
GatherBinaryLogs bool `label:"Binary Logs" json:"gather_binary_logs" description:"gather metrics from SHOW BINARY LOGS command output"`
GatherTableIOWaits bool `label:"Table IO Waits" json:"gather_table_io_waits" description:"gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_TABLE"`
GatherTableLockWaits bool `label:"Table Lock Waits" json:"gather_table_lock_waits" description:"gather metrics from PERFORMANCE_SCHEMA.TABLE_LOCK_WAITS"`
GatherIndexIOWaits bool `label:"Index IO Waits" json:"gather_index_io_waits" description:"gather metrics from PERFORMANCE_SCHEMA.TABLE_IO_WAITS_SUMMARY_BY_INDEX_USAGE"`
GatherEventWaits bool `label:"Event Waits" json:"gather_event_waits" description:"gather metrics from PERFORMANCE_SCHEMA.EVENT_WAITS"`
GatherTableSchema bool `label:"Tables" json:"gather_table_schema" description:"gather metrics from INFORMATION_SCHEMA.TABLES for databases provided above list"`
GatherFileEventsStats bool `label:"File Events Stats" json:"gather_file_events_stats" description:"gather metrics from PERFORMANCE_SCHEMA.FILE_SUMMARY_BY_EVENT_NAME"`
GatherPerfEventsStatements bool `label:"Perf Events Statements" json:"gather_perf_events_statements" description:"gather metrics from PERFORMANCE_SCHEMA.EVENTS_STATEMENTS_SUMMARY_BY_DIGEST"`
GatherGlobalVars bool `label:"-" json:"-"`
IntervalSlow string `label:"Interval Slow" json:"interval_slow" desc:"Some queries we may want to run less often (such as SHOW GLOBAL VARIABLES)" example:"interval_slow = '30m'" json:"-"`
MetricVersion int `label:"-" json:"-"`
}
func (p *MysqlRule) Validate() error {
if len(p.Servers) == 0 || p.Servers[0] == "" {
return fmt.Errorf("mysql.rule.servers must be set")
}
return nil
}
func (p *MysqlRule) TelegrafInput() (telegraf.Input, error) {
if err := p.Validate(); err != nil {
return nil, err
}
return &mysql.Mysql{
Servers: p.Servers,
PerfEventsStatementsDigestTextLimit: 120,
PerfEventsStatementsLimit: 250,
PerfEventsStatementsTimeLimit: 86400,
TableSchemaDatabases: p.TableSchemaDatabases,
GatherProcessList: p.GatherProcessList,
GatherUserStatistics: p.GatherUserStatistics,
GatherInfoSchemaAutoInc: p.GatherInfoSchemaAutoInc,
GatherInnoDBMetrics: p.GatherInnoDBMetrics,
GatherSlaveStatus: p.GatherSlaveStatus,
GatherBinaryLogs: p.GatherBinaryLogs,
GatherTableIOWaits: p.GatherTableIOWaits,
GatherTableLockWaits: p.GatherTableLockWaits,
GatherIndexIOWaits: p.GatherIndexIOWaits,
GatherEventWaits: p.GatherEventWaits,
GatherTableSchema: p.GatherTableSchema,
GatherFileEventsStats: p.GatherFileEventsStats,
GatherPerfEventsStatements: p.GatherPerfEventsStatements,
GatherGlobalVars: true,
IntervalSlow: "0m",
MetricVersion: 2,
}, nil
}

View File

@ -0,0 +1,144 @@
package collector
import (
"encoding/json"
"errors"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
)
func init() {
collector.CollectorRegister(&PluginCollector{})
}
type PluginCollector struct{}
func (p PluginCollector) Name() string { return "plugin" }
func (p PluginCollector) Category() collector.Category { return collector.LocalCategory }
func (p PluginCollector) Template() (interface{}, error) { return nil, nil }
func (p PluginCollector) TelegrafInput(*models.CollectRule) (telegraf.Input, error) {
return nil, errors.New("unsupported")
}
func (p PluginCollector) Get(id int64) (interface{}, error) {
collect := new(models.PluginCollect)
has, err := models.DB["mon"].Where("id = ?", id).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p PluginCollector) Gets(nids []int64) (ret []interface{}, err error) {
collects := []models.PluginCollect{}
err = models.DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
ret = append(ret, c)
}
return ret, err
}
func (p PluginCollector) GetByNameAndNid(name string, nid int64) (interface{}, error) {
collect := new(models.PluginCollect)
has, err := models.DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p PluginCollector) Create(data []byte, username string) error {
collect := new(models.PluginCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_create", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collect.Creator = username
collect.LastUpdator = username
nid := collect.Nid
name := collect.Name
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return models.CreateCollect(p.Name(), username, collect)
}
func (p PluginCollector) Update(data []byte, username string) error {
collect := new(models.PluginCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_modify", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := p.Get(collect.Id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
tmpId := obj.(*models.PluginCollect).Id
if tmpId == 0 {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
collect.Creator = username
collect.LastUpdator = username
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil && tmpId != old.(*models.PluginCollect).Id {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return collect.Update()
}
func (p PluginCollector) Delete(id int64, username string) error {
tmp, err := p.Get(id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), id)
}
nid := tmp.(*models.PluginCollect).Nid
can, err := models.UsernameCandoNodeOp(username, "mon_collect_delete", int64(nid))
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
return models.DeleteCollectById(p.Name(), username, id)
}

View File

@ -0,0 +1,144 @@
package collector
import (
"encoding/json"
"errors"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
)
func init() {
collector.CollectorRegister(&PortCollector{})
}
type PortCollector struct{}
func (p PortCollector) Name() string { return "port" }
func (p PortCollector) Category() collector.Category { return collector.LocalCategory }
func (p PortCollector) Template() (interface{}, error) { return nil, nil }
func (p PortCollector) TelegrafInput(*models.CollectRule) (telegraf.Input, error) {
return nil, errors.New("unsupported")
}
func (p PortCollector) Get(id int64) (interface{}, error) {
collect := new(models.PortCollect)
has, err := models.DB["mon"].Where("id = ?", id).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p PortCollector) Gets(nids []int64) (ret []interface{}, err error) {
collects := []models.PortCollect{}
err = models.DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
ret = append(ret, c)
}
return ret, err
}
func (p PortCollector) GetByNameAndNid(name string, nid int64) (interface{}, error) {
collect := new(models.PortCollect)
has, err := models.DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p PortCollector) Create(data []byte, username string) error {
collect := new(models.PortCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_create", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collect.Creator = username
collect.LastUpdator = username
nid := collect.Nid
name := collect.Name
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return models.CreateCollect(p.Name(), username, collect)
}
func (p PortCollector) Update(data []byte, username string) error {
collect := new(models.PortCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_modify", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := p.Get(collect.Id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
tmpId := obj.(*models.PortCollect).Id
if tmpId == 0 {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
collect.Creator = username
collect.LastUpdator = username
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil && tmpId != old.(*models.PortCollect).Id {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return collect.Update()
}
func (p PortCollector) Delete(id int64, username string) error {
tmp, err := p.Get(id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), id)
}
nid := tmp.(*models.PortCollect).Nid
can, err := models.UsernameCandoNodeOp(username, "mon_collect_delete", int64(nid))
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
return models.DeleteCollectById(p.Name(), username, id)
}

View File

@ -0,0 +1,144 @@
package collector
import (
"encoding/json"
"errors"
"fmt"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
)
func init() {
collector.CollectorRegister(&ProcCollector{})
}
type ProcCollector struct{}
func (p ProcCollector) Name() string { return "proc" }
func (p ProcCollector) Category() collector.Category { return collector.LocalCategory }
func (p ProcCollector) Template() (interface{}, error) { return nil, nil }
func (p ProcCollector) TelegrafInput(*models.CollectRule) (telegraf.Input, error) {
return nil, errors.New("unsupported")
}
func (p ProcCollector) Get(id int64) (interface{}, error) {
collect := new(models.ProcCollect)
has, err := models.DB["mon"].Where("id = ?", id).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p ProcCollector) Gets(nids []int64) (ret []interface{}, err error) {
collects := []models.ProcCollect{}
err = models.DB["mon"].In("nid", nids).Find(&collects)
for _, c := range collects {
ret = append(ret, c)
}
return ret, err
}
func (p ProcCollector) GetByNameAndNid(name string, nid int64) (interface{}, error) {
collect := new(models.ProcCollect)
has, err := models.DB["mon"].Where("name = ? and nid = ?", name, nid).Get(collect)
if !has {
return nil, err
}
return collect, err
}
func (p ProcCollector) Create(data []byte, username string) error {
collect := new(models.ProcCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_create", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
collect.Creator = username
collect.LastUpdator = username
nid := collect.Nid
name := collect.Name
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return models.CreateCollect(p.Name(), username, collect)
}
func (p ProcCollector) Update(data []byte, username string) error {
collect := new(models.ProcCollect)
err := json.Unmarshal(data, collect)
if err != nil {
return fmt.Errorf("unmarshal body %s err:%v", string(data), err)
}
can, err := models.UsernameCandoNodeOp(username, "mon_collect_modify", collect.Nid)
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
nid := collect.Nid
name := collect.Name
//校验采集是否存在
obj, err := p.Get(collect.Id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
tmpId := obj.(*models.ProcCollect).Id
if tmpId == 0 {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), collect.Id)
}
collect.Creator = username
collect.LastUpdator = username
old, err := p.GetByNameAndNid(name, nid)
if err != nil {
return err
}
if old != nil && tmpId != old.(*models.ProcCollect).Id {
return fmt.Errorf("同节点下策略名称 %s 已存在", name)
}
return collect.Update()
}
func (p ProcCollector) Delete(id int64, username string) error {
tmp, err := p.Get(id) //id找不到的情况
if err != nil {
return fmt.Errorf("采集不存在 type:%s id:%d", p.Name(), id)
}
nid := tmp.(*models.ProcCollect).Nid
can, err := models.UsernameCandoNodeOp(username, "mon_collect_delete", int64(nid))
if err != nil {
return err
}
if !can {
return fmt.Errorf("permission deny")
}
return models.DeleteCollectById(p.Name(), username, id)
}

View File

@ -0,0 +1,63 @@
package redis
import (
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs/redis"
)
func init() {
collector.CollectorRegister(NewRedisCollector()) // for monapi
}
type RedisCollector struct {
*collector.BaseCollector
}
func NewRedisCollector() *RedisCollector {
return &RedisCollector{BaseCollector: collector.NewBaseCollector(
"redis",
collector.RemoteCategory,
func() interface{} { return &RedisRule{} },
)}
}
type RedisCommand struct {
Command []string `label:"Command" json:"command,required" description:"" `
Field string `label:"Field" json:"field,required" description:"metric name"`
Type string `label:"Type" json:"type" description:"integer|string|float"`
}
type RedisRule struct {
Servers []string `label:"Servers" json:"servers,required" description:"If no servers are specified, then localhost is used as the host." example:"tcp://localhost:6379"`
Commands []*RedisCommand `label:"Commands" json:"commands" description:"Optional. Specify redis commands to retrieve values"`
Password string `label:"Password" json:"password" description:"specify server password"`
}
func (p *RedisRule) Validate() error {
return nil
}
func (p *RedisRule) TelegrafInput() (telegraf.Input, error) {
if err := p.Validate(); err != nil {
return nil, err
}
commands := make([]*redis.RedisCommand, len(p.Commands))
for i, c := range p.Commands {
cmd := &redis.RedisCommand{
Field: c.Field,
Type: c.Type,
}
for _, v := range c.Command {
cmd.Command = append(cmd.Command, v)
}
commands[i] = cmd
}
return &redis.Redis{
Servers: p.Servers,
Commands: commands,
Password: p.Password,
}, nil
}

View File

@ -0,0 +1,210 @@
package scache
import (
"context"
"encoding/json"
"fmt"
"strconv"
"sync"
"time"
"github.com/didi/nightingale/src/common/report"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/config"
"github.com/toolkits/pkg/consistent"
"github.com/toolkits/pkg/logger"
)
type collectRuleCache struct {
sync.RWMutex
Region []string
Data map[string][]*models.CollectRule // map: node Identity
HashRing map[string]*ConsistentHashRing // map: region
}
func NewCollectRuleCache() *collectRuleCache {
return &collectRuleCache{
Region: config.Get().Region,
Data: make(map[string][]*models.CollectRule),
HashRing: make(map[string]*ConsistentHashRing),
}
}
func (p *collectRuleCache) Start(ctx context.Context) {
go func() {
p.initHashRing()
p.syncPlacement()
go p.syncPlacementLoop(ctx)
p.syncCollectRules()
go p.syncCollectRulesLoop(ctx)
}()
}
func (p *collectRuleCache) initHashRing() {
for _, region := range p.Region {
p.HashRing[region] = NewConsistentHashRing(int32(config.DetectorReplicas), []string{})
}
}
func (p *collectRuleCache) GetBy(node string) []*models.CollectRule {
p.RLock()
defer p.RUnlock()
return p.Data[node]
}
func (p *collectRuleCache) Set(node string, rules []*models.CollectRule) {
p.Lock()
defer p.Unlock()
p.Data[node] = rules
return
}
func (p *collectRuleCache) SetAll(data map[string][]*models.CollectRule) {
p.Lock()
defer p.Unlock()
p.Data = data
return
}
func (p *collectRuleCache) GetAll() []*models.CollectRule {
p.RLock()
defer p.RUnlock()
data := []*models.CollectRule{}
for nodeId, rules := range p.Data {
logger.Debugf("get nodeId %s rules %d", nodeId, len(rules))
for _, s := range rules {
data = append(data, s)
}
}
return data
}
func (p *collectRuleCache) syncCollectRulesLoop(ctx context.Context) {
t1 := time.NewTicker(time.Duration(CHECK_INTERVAL) * time.Second)
defer t1.Stop()
for {
select {
case <-ctx.Done():
return
case <-t1.C:
p.syncCollectRules()
}
}
}
func str(in interface{}) string {
b, _ := json.Marshal(in)
return string(b)
}
func (p *collectRuleCache) syncCollectRules() {
rules, err := models.GetCollectRules()
if err != nil {
logger.Warningf("get log collectRules err:%v", err)
}
logger.Debugf("get collectRules %d %s", len(rules), str(rules))
rulesMap := make(map[string][]*models.CollectRule)
for _, rule := range rules {
if _, exists := p.HashRing[rule.Region]; !exists {
logger.Warningf("get node err, hash ring do noe exists %v", rule)
continue
}
node, err := p.HashRing[rule.Region].GetNode(strconv.FormatInt(rule.Id, 10))
if err != nil {
logger.Warningf("get node err:%v %v", err, rule)
continue
}
key := node
if _, exists := rulesMap[key]; exists {
rulesMap[key] = append(rulesMap[key], rule)
} else {
rulesMap[key] = []*models.CollectRule{rule}
}
}
CollectRuleCache.SetAll(rulesMap)
}
func (p *collectRuleCache) syncPlacementLoop(ctx context.Context) {
t1 := time.NewTicker(time.Duration(CHECK_INTERVAL) * time.Second)
defer t1.Stop()
for {
select {
case <-ctx.Done():
return
case <-t1.C:
p.syncPlacement()
}
}
}
func (p *collectRuleCache) syncPlacement() error {
instances, err := report.GetAlive("prober", "rdb")
if err != nil {
logger.Warning("get prober err:", err)
return fmt.Errorf("report.GetAlive prober fail: %v", err)
}
logger.Debugf("get placement %d %s", len(instances), str(instances))
if len(instances) < 1 {
logger.Warningf("probers count is zero")
return nil
}
nodesMap := make(map[string]map[string]struct{})
for _, d := range instances {
if d.Active {
if _, exists := nodesMap[d.Region]; !exists {
nodesMap[d.Region] = make(map[string]struct{})
}
nodesMap[d.Region][d.Identity+":"+d.RPCPort] = struct{}{}
}
}
for region, nodes := range nodesMap {
rehash := false
if _, exists := p.HashRing[region]; !exists {
logger.Warningf("hash ring do not exists %v", region)
continue
}
oldNodes := p.HashRing[region].GetRing().Members()
if len(oldNodes) != len(nodes) {
rehash = true
} else {
for _, node := range oldNodes {
if _, exists := nodes[node]; !exists {
rehash = true
break
}
}
}
if rehash {
//重建 hash环
r := consistent.New()
r.NumberOfReplicas = config.DetectorReplicas
for node, _ := range nodes {
r.Add(node)
}
logger.Warningf("detector hash ring rebuild old:%v new:%v", oldNodes, r.Members())
p.HashRing[region].Set(r)
}
}
return nil
}

View File

@ -1,6 +1,7 @@
package scache
import (
"context"
"strconv"
"github.com/didi/nightingale/src/common/report"
@ -9,8 +10,9 @@ import (
"github.com/toolkits/pkg/logger"
)
var CollectRuleCache *collectRuleCache
var JudgeHashRing *ConsistentHashRing
var ActiveNode = NewNodeMap()
var ActiveJudgeNode = NewNodeMap()
const CHECK_INTERVAL = 9
@ -22,8 +24,10 @@ func Init() {
InitJudgeHashRing()
go CheckJudgeNodes()
CollectRuleCache = NewCollectRuleCache()
CollectRuleCache.Start(context.Background())
go CheckJudgeNodes()
go SyncStras()
go SyncCollects()
go CleanCollectLoop()

View File

@ -40,11 +40,11 @@ func CheckJudge() error {
}
rehash := false
if ActiveNode.Len() != len(judgeNode) { //scache.ActiveNode中的node数量和新获取的不同重新rehash
if ActiveJudgeNode.Len() != len(judgeNode) { //scache.ActiveJudgeNode中的node数量和新获取的不同重新rehash
rehash = true
} else {
for node, instance := range judgeNode {
v, exists := ActiveNode.GetInstanceBy(node)
v, exists := ActiveJudgeNode.GetInstanceBy(node)
if !exists || (exists && instance != v) {
rehash = true
break
@ -52,12 +52,12 @@ func CheckJudge() error {
}
}
if rehash {
ActiveNode.Set(judgeNode)
ActiveJudgeNode.Set(judgeNode)
//重建judge hash环
r := consistent.New()
r.NumberOfReplicas = config.JudgesReplicas
nodes := ActiveNode.GetNodes()
nodes := ActiveJudgeNode.GetNodes()
for _, node := range nodes {
r.Add(node)
}

View File

@ -46,7 +46,7 @@ func (s *StraCacheMap) GetAll() []*models.Stra {
data := []*models.Stra{}
for node, stras := range s.Data {
instance, exists := ActiveNode.GetInstanceBy(node)
instance, exists := ActiveJudgeNode.GetInstanceBy(node)
if !exists {
continue
}

18
src/modules/prober/cache/cache.go vendored Normal file
View File

@ -0,0 +1,18 @@
package cache
import (
"context"
"github.com/didi/nightingale/src/modules/prober/config"
)
var CollectRule *CollectRuleCache // collectrule.go
var MetricHistory *history // history.go
func Init(ctx context.Context) error {
CollectRule = NewCollectRuleCache(&config.Config.CollectRule)
CollectRule.start(ctx)
MetricHistory = NewHistory()
InitPluginsConfig(config.Config)
return nil
}

159
src/modules/prober/cache/collect_rule.go vendored Normal file
View File

@ -0,0 +1,159 @@
package cache
import (
"context"
"fmt"
"math/rand"
"sync"
"time"
"github.com/didi/nightingale/src/common/address"
"github.com/didi/nightingale/src/common/identity"
"github.com/didi/nightingale/src/common/report"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/prober/config"
"github.com/didi/nightingale/src/toolkits/stats"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/net/httplib"
)
type CollectRuleCache struct {
sync.RWMutex
*config.CollectRuleSection
Data map[int64]*models.CollectRule
TS map[int64]int64
C chan time.Time
timeout time.Duration
}
func NewCollectRuleCache(cf *config.CollectRuleSection) *CollectRuleCache {
return &CollectRuleCache{
CollectRuleSection: cf,
Data: make(map[int64]*models.CollectRule),
TS: make(map[int64]int64),
timeout: time.Duration(cf.Timeout) * time.Millisecond,
C: make(chan time.Time, 1),
}
}
func (p *CollectRuleCache) start(ctx context.Context) error {
go func() {
p.syncCollectRule()
p.syncCollectRuleLoop(ctx)
}()
return nil
}
func (p *CollectRuleCache) Set(id int64, rule *models.CollectRule) {
p.Lock()
defer p.Unlock()
p.Data[id] = rule
p.TS[id] = time.Now().Unix()
}
func (p *CollectRuleCache) Get(id int64) (*models.CollectRule, bool) {
p.RLock()
defer p.RUnlock()
rule, exists := p.Data[id]
return rule, exists
}
func (p *CollectRuleCache) GetAll() []*models.CollectRule {
p.RLock()
defer p.RUnlock()
var rules []*models.CollectRule
for _, rule := range p.Data {
rules = append(rules, rule)
}
return rules
}
func (p *CollectRuleCache) Clean() {
p.Lock()
defer p.Unlock()
now := time.Now().Unix()
for id, ts := range p.TS {
if now-ts > 60 {
stats.Counter.Set("collectrule.clean", 1)
delete(p.Data, id)
delete(p.TS, id)
}
}
}
func (p *CollectRuleCache) syncCollectRuleLoop(ctx context.Context) {
t1 := time.NewTicker(time.Duration(p.UpdateInterval) * time.Millisecond)
defer t1.Stop()
for {
select {
case <-ctx.Done():
return
case t := <-t1.C:
if err := p.syncCollectRule(); err != nil {
logger.Errorf("syncCollectRule err %s", err)
} else {
p.C <- t
}
}
}
}
type collectRulesResp struct {
Data []*models.CollectRule `json:"dat"`
Err string `json:"err"`
}
func (p *CollectRuleCache) syncCollectRule() error {
addrs := address.GetHTTPAddresses(p.Mod)
if len(addrs) == 0 {
return fmt.Errorf("empty config addr")
}
var resp collectRulesResp
perm := rand.Perm(len(addrs))
for i := range perm {
ident, err := identity.GetIdent()
if err != nil {
return fmt.Errorf("getIdent err %s", err)
}
url := fmt.Sprintf("http://%s/api/mon/collect-rules/endpoints/%s:%s/remote",
addrs[perm[i]], ident, report.Config.RPCPort)
err = httplib.Get(url).SetTimeout(p.timeout).ToJSON(&resp)
if err != nil {
logger.Warningf("get %s collect rule from remote failed, error:%v", url, err)
stats.Counter.Set("collectrule.get.err", 1)
continue
}
if resp.Err != "" {
logger.Warningf("get collect rule from remote failed, error:%v", resp.Err)
stats.Counter.Set("collectrule.get.err", 1)
continue
}
if len(resp.Data) > 0 {
break
}
}
collectRuleCount := len(resp.Data)
stats.Counter.Set("collectrule.count", collectRuleCount)
if collectRuleCount == 0 { //获取策略数为0不正常不更新策略缓存
return fmt.Errorf("clloect rule count is 0")
}
for _, rule := range resp.Data {
if err := rule.Validate(); err != nil {
logger.Debugf("rule.Validate err %s", err)
continue
}
stats.Counter.Set("collectrule.common", 1)
p.Set(rule.Id, rule)
}
p.Clean()
return nil
}

57
src/modules/prober/cache/history.go vendored Normal file
View File

@ -0,0 +1,57 @@
package cache
import (
"sync"
"time"
"github.com/didi/nightingale/src/common/dataobj"
)
func NewHistory() *history {
h := history{
Data: make(map[string]dataobj.MetricValue),
}
go h.Clean()
return &h
}
type history struct {
sync.RWMutex
Data map[string]dataobj.MetricValue
}
func (h *history) Set(key string, item dataobj.MetricValue) {
h.Lock()
defer h.Unlock()
h.Data[key] = item
}
func (h *history) Get(key string) (dataobj.MetricValue, bool) {
h.RLock()
defer h.RUnlock()
item, exists := h.Data[key]
return item, exists
}
func (h *history) Clean() {
ticker := time.NewTicker(10 * time.Minute)
for {
select {
case <-ticker.C:
h.clean()
}
}
}
func (h *history) clean() {
h.Lock()
defer h.Unlock()
now := time.Now().Unix()
for key, item := range h.Data {
if now-item.Timestamp > 10*item.Step {
delete(h.Data, key)
}
}
}

View File

@ -0,0 +1,118 @@
package cache
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/didi/nightingale/src/modules/prober/config"
"github.com/didi/nightingale/src/modules/prober/expr"
"github.com/influxdata/telegraf"
"github.com/toolkits/pkg/logger"
"gopkg.in/yaml.v2"
)
type MetricConfig struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Comment string `yaml:"comment"`
Expr string `yaml:"expr"`
notations expr.Notations `yaml:"-"`
}
type PluginConfig struct {
Metrics []MetricConfig `metrics`
}
var (
metricsConfig map[string]MetricConfig
metricsExpr map[string]map[string]MetricConfig
ignoreConfig bool
)
func InitPluginsConfig(cf *config.ConfYaml) {
metricsConfig = make(map[string]MetricConfig)
metricsExpr = make(map[string]map[string]MetricConfig)
ignoreConfig = cf.IgnoreConfig
plugins := collector.GetRemoteCollectors()
for _, plugin := range plugins {
metricsExpr[plugin] = make(map[string]MetricConfig)
pluginConfig := PluginConfig{}
file := filepath.Join(cf.PluginsConfig, plugin+".yml")
b, err := ioutil.ReadFile(file)
if err != nil {
logger.Debugf("readfile %s err %s", plugin, err)
continue
}
if err := yaml.Unmarshal(b, &pluginConfig); err != nil {
logger.Warningf("yaml.Unmarshal %s err %s", plugin, err)
continue
}
for _, v := range pluginConfig.Metrics {
if _, ok := metricsConfig[v.Name]; ok {
panic(fmt.Sprintf("plugin %s metrics %s is already exists", plugin, v.Name))
}
if v.Expr == "" {
// nomore
metricsConfig[v.Name] = v
} else {
err := v.parse()
if err != nil {
panic(fmt.Sprintf("plugin %s metrics %s expr %s parse err %s",
plugin, v.Name, v.Expr, err))
}
metricsExpr[plugin][v.Name] = v
}
}
logger.Infof("loaded plugin config %s", file)
}
}
func (p *MetricConfig) parse() (err error) {
p.notations, err = expr.NewNotations([]byte(p.Expr))
return
}
func (p *MetricConfig) Calc(vars map[string]float64) (float64, error) {
return p.notations.Calc(vars)
}
func Metric(metric string, typ telegraf.ValueType) (c MetricConfig, ok bool) {
c, ok = metricsConfig[metric]
if !ok && !ignoreConfig {
return
}
if c.Type == "" {
c.Type = metricType(typ)
}
return
}
func GetMetricExprs(pluginName string) (c map[string]MetricConfig, ok bool) {
c, ok = metricsExpr[pluginName]
return
}
func metricType(typ telegraf.ValueType) string {
switch typ {
case telegraf.Counter:
return "COUNTER"
case telegraf.Gauge:
return "GAUGE"
case telegraf.Untyped:
return "GAUGE"
case telegraf.Summary: // TODO
return "SUMMARY"
case telegraf.Histogram: // TODO
return "HISTOGRAM"
default:
return "GAUGE"
}
}

View File

@ -0,0 +1,94 @@
package config
import (
"bytes"
"fmt"
"strconv"
"github.com/didi/nightingale/src/common/address"
"github.com/didi/nightingale/src/common/loggeri"
"github.com/didi/nightingale/src/common/report"
// "github.com/didi/nightingale/src/modules/prober/backend/transfer"
"github.com/spf13/viper"
"github.com/toolkits/pkg/file"
)
type ConfYaml struct {
CollectRule CollectRuleSection `yaml:"collectRule"`
Logger loggeri.Config `yaml:"logger"`
Report report.ReportSection `yaml:"report"`
WorkerProcesses int `yaml:"workerProcesses"`
PluginsConfig string `yaml:"pluginsConfig"`
IgnoreConfig bool `yaml:"ignoreConfig"`
HTTP HTTPSection `yaml:"http"`
}
type CollectRuleSection struct {
Timeout int `yaml:"timeout"`
Token string `yaml:"token"`
UpdateInterval int `yaml:"updateInterval"`
IndexInterval int `yaml:"indexInterval"`
ReportInterval int `yaml:"reportInterval"`
Mod string `yaml:"mod"`
}
var (
Config *ConfYaml
)
type HTTPSection struct {
Mode string `yaml:"mode"`
CookieName string `yaml:"cookieName"`
CookieDomain string `yaml:"cookieDomain"`
}
func Parse(conf string) error {
bs, err := file.ReadBytes(conf)
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v", conf, err)
}
viper.SetConfigType("yaml")
err = viper.ReadConfig(bytes.NewBuffer(bs))
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v", conf, err)
}
viper.SetDefault("http.enabled", true)
viper.SetDefault("collectRule", map[string]interface{}{
"updateInterval": 9000,
"indexInterval": 60000,
"timeout": 5000,
"mod": "monapi",
"eventPrefix": "n9e",
})
viper.SetDefault("report", map[string]interface{}{
"mod": "prober",
"enabled": true,
"interval": 4000,
"timeout": 3000,
"api": "api/hbs/heartbeat",
"remark": "",
"region": "default",
})
viper.SetDefault("workerProcesses", 5)
viper.SetDefault("pluginsConfig", "etc/plugins")
viper.SetDefault("pushUrl", "http://127.0.0.1:2058/v1/push")
err = viper.Unmarshal(&Config)
if err != nil {
return fmt.Errorf("cannot read yml[%s]: %v\n", conf, err)
}
Config.Report.HTTPPort = strconv.Itoa(address.GetHTTPPort("prober"))
Config.Report.RPCPort = strconv.Itoa(address.GetRPCPort("prober"))
return err
}

View File

@ -0,0 +1,51 @@
package core
import (
"net/rpc"
"sync"
)
type RpcClientContainer struct {
M map[string]*rpc.Client
sync.RWMutex
}
var rpcClients *RpcClientContainer
func InitRpcClients() {
rpcClients = &RpcClientContainer{
M: make(map[string]*rpc.Client),
}
}
func (rcc *RpcClientContainer) Get(addr string) *rpc.Client {
rcc.RLock()
defer rcc.RUnlock()
client, has := rcc.M[addr]
if !has {
return nil
}
return client
}
// Put 返回的bool表示affected确实把自己塞进去了
func (rcc *RpcClientContainer) Put(addr string, client *rpc.Client) bool {
rcc.Lock()
defer rcc.Unlock()
oc, has := rcc.M[addr]
if has && oc != nil {
return false
}
rcc.M[addr] = client
return true
}
func (rcc *RpcClientContainer) Del(addr string) {
rcc.Lock()
defer rcc.Unlock()
delete(rcc.M, addr)
}

View File

@ -0,0 +1,31 @@
package core
import (
"strings"
"github.com/didi/nightingale/src/common/dataobj"
)
func NewMetricValue(metric string, val interface{}, dataType string, tags ...string) *dataobj.MetricValue {
mv := dataobj.MetricValue{
Metric: metric,
ValueUntyped: val,
CounterType: dataType,
}
size := len(tags)
if size > 0 {
mv.Tags = strings.Join(tags, ",")
}
return &mv
}
func GaugeValue(metric string, val interface{}, tags ...string) *dataobj.MetricValue {
return NewMetricValue(metric, val, "GAUGE", tags...)
}
func CounterValue(metric string, val interface{}, tags ...string) *dataobj.MetricValue {
return NewMetricValue(metric, val, "COUNTER", tags...)
}

View File

@ -0,0 +1,196 @@
package core
import (
"bufio"
"fmt"
"io"
"math/rand"
"net"
"net/rpc"
"reflect"
"time"
"github.com/toolkits/pkg/logger"
"github.com/ugorji/go/codec"
"github.com/didi/nightingale/src/common/address"
"github.com/didi/nightingale/src/common/dataobj"
"github.com/didi/nightingale/src/modules/prober/cache"
)
func Push(metricItems []*dataobj.MetricValue) {
var err error
var items []*dataobj.MetricValue
now := time.Now().Unix()
for _, item := range metricItems {
// logger.Debugf("->recv:%+v", item)
err = item.CheckValidity(now)
if err != nil {
msg := fmt.Errorf("metric:%v err:%v", item, err)
logger.Warning(msg)
// 如果数据有问题直接跳过吧比如mymon采集的到的数据其实只有一个有问题剩下的都没问题
continue
}
if item.CounterType == dataobj.COUNTER {
item = CounterToGauge(item)
if item == nil {
continue
}
}
if item.CounterType == dataobj.SUBTRACT {
item = SubtractToGauge(item)
if item == nil {
continue
}
}
// logger.Debugf("push item: %+v", item)
items = append(items, item)
}
addrs := address.GetRPCAddresses("transfer")
count := len(addrs)
retry := 0
for {
for _, i := range rand.Perm(count) {
addr := addrs[i]
reply, err := rpcCall(addr, items)
if err != nil {
logger.Error(err)
continue
} else {
if reply.Msg != "ok" {
err = fmt.Errorf("some item push err: %s", reply.Msg)
logger.Error(err)
}
return
}
}
time.Sleep(time.Millisecond * 500)
retry += 1
if retry == 3 {
break
}
}
}
func rpcCall(addr string, items []*dataobj.MetricValue) (dataobj.TransferResp, error) {
var reply dataobj.TransferResp
var err error
client := rpcClients.Get(addr)
if client == nil {
client, err = rpcClient(addr)
if err != nil {
return reply, err
}
affected := rpcClients.Put(addr, client)
if !affected {
defer func() {
// 我尝试把自己这个client塞进map失败说明已经有一个client塞进去了那我自己用完了就关闭
client.Close()
}()
}
}
timeout := time.Duration(8) * time.Second
done := make(chan error, 1)
go func() {
err := client.Call("Transfer.Push", items, &reply)
done <- err
}()
select {
case <-time.After(timeout):
logger.Warningf("rpc call timeout, transfer addr: %s\n", addr)
rpcClients.Put(addr, nil)
client.Close()
return reply, fmt.Errorf("%s rpc call timeout", addr)
case err := <-done:
if err != nil {
rpcClients.Del(addr)
client.Close()
return reply, fmt.Errorf("%s rpc call done, but fail: %v", addr, err)
}
}
return reply, nil
}
func rpcClient(addr string) (*rpc.Client, error) {
conn, err := net.DialTimeout("tcp", addr, time.Second*3)
if err != nil {
err = fmt.Errorf("dial transfer %s fail: %v", addr, err)
logger.Error(err)
return nil, err
}
var bufConn = struct {
io.Closer
*bufio.Reader
*bufio.Writer
}{conn, bufio.NewReader(conn), bufio.NewWriter(conn)}
var mh codec.MsgpackHandle
mh.MapType = reflect.TypeOf(map[string]interface{}(nil))
rpcCodec := codec.MsgpackSpecRpc.ClientCodec(bufConn, &mh)
client := rpc.NewClientWithCodec(rpcCodec)
return client, nil
}
func CounterToGauge(item *dataobj.MetricValue) *dataobj.MetricValue {
key := item.PK()
old, exists := cache.MetricHistory.Get(key)
cache.MetricHistory.Set(key, *item)
if !exists {
logger.Debugf("not found old item:%v, maybe this is the first item", item)
return nil
}
if old.Value > item.Value {
logger.Warningf("item:%v old value:%v greater than new value:%v", item, old.Value, item.Value)
return nil
}
if old.Timestamp >= item.Timestamp {
logger.Warningf("item:%v old timestamp:%v greater than new timestamp:%v", item, old.Timestamp, item.Timestamp)
return nil
}
item.ValueUntyped = (item.Value - old.Value) / float64(item.Timestamp-old.Timestamp)
item.CounterType = dataobj.GAUGE
return item
}
func SubtractToGauge(item *dataobj.MetricValue) *dataobj.MetricValue {
key := item.PK()
old, exists := cache.MetricHistory.Get(key)
cache.MetricHistory.Set(key, *item)
if !exists {
logger.Debugf("not found old item:%v, maybe this is the first item", item)
return nil
}
if old.Timestamp >= item.Timestamp {
logger.Warningf("item:%v old timestamp:%v greater than new timestamp:%v", item, old.Timestamp, item.Timestamp)
return nil
}
if old.Timestamp <= item.Timestamp-2*item.Step {
logger.Warningf("item:%v old timestamp:%v too old <= %v = (new timestamp: %v - 2 * step: %v), maybe some point lost", item, old.Timestamp, item.Timestamp-2*item.Step, item.Timestamp, item.Step)
return nil
}
item.ValueUntyped = item.Value - old.Value
item.CounterType = dataobj.GAUGE
return item
}

View File

@ -0,0 +1,174 @@
package expr
import (
"bytes"
"fmt"
"go/scanner"
"go/token"
"strconv"
"github.com/toolkits/pkg/logger"
)
type tokenType int
const (
tokenOperator tokenType = iota
tokenVar
tokenConst
)
type TokenNotation struct {
tokenType tokenType
o token.Token // operator
v string // variable
c float64 // const
}
type Notations []*TokenNotation
func (s *Notations) Push(tn *TokenNotation) { *s = append(*s, tn) }
func (s *Notations) Pop() *TokenNotation { n := (*s)[len(*s)-1]; *s = (*s)[:len(*s)-1]; return n }
func (s *Notations) Top() *TokenNotation { return (*s)[len(*s)-1] }
func (s *Notations) Len() int { return len(*s) }
func (s Notations) String() string {
out := bytes.NewBuffer(nil)
for i := 0; i < len(s); i++ {
tn := s[i]
switch tn.tokenType {
case tokenOperator:
out.WriteString(tn.o.String() + " ")
case tokenVar:
out.WriteString(tn.v + " ")
case tokenConst:
out.WriteString(fmt.Sprintf("%.0f ", tn.c))
}
}
return out.String()
}
type StackOp []token.Token
func (s *StackOp) Push(t token.Token) { *s = append(*s, t) }
func (s *StackOp) Pop() token.Token { n := (*s)[len(*s)-1]; *s = (*s)[:len(*s)-1]; return n }
func (s *StackOp) Top() token.Token { return (*s)[len(*s)-1] }
func (s *StackOp) Len() int { return len(*s) }
type StackFloat []float64
func (s *StackFloat) Push(f float64) { *s = append(*s, f) }
func (s *StackFloat) Pop() float64 { n := (*s)[len(*s)-1]; *s = (*s)[:len(*s)-1]; return n }
func (s *StackFloat) Len() int { return len(*s) }
func (rpn Notations) Calc(vars map[string]float64) (float64, error) {
var s StackFloat
for i := 0; i < rpn.Len(); i++ {
tn := rpn[i]
switch tn.tokenType {
case tokenVar:
if v, ok := vars[tn.v]; !ok {
return 0, fmt.Errorf("variable %s is not set", tn.v)
} else {
logger.Debugf("get %s %f", tn.v, v)
s.Push(v)
}
case tokenConst:
s.Push(tn.c)
case tokenOperator:
op2 := s.Pop()
op1 := s.Pop()
switch tn.o {
case token.ADD:
s.Push(op1 + op2)
case token.SUB:
s.Push(op1 - op2)
case token.MUL:
s.Push(op1 * op2)
case token.QUO:
s.Push(op1 / op2)
}
}
}
if s.Len() == 1 {
return s[0], nil
}
return 0, fmt.Errorf("invalid calc, stack len %d expect 1", s.Len())
}
// return reverse polish notation stack
func NewNotations(src []byte) (output Notations, err error) {
var scan scanner.Scanner
var s StackOp
fset := token.NewFileSet()
file := fset.AddFile("", fset.Base(), len(src))
scan.Init(file, src, errorHandler, scanner.ScanComments)
var (
pos token.Pos
tok token.Token
lit string
)
for {
pos, tok, lit = scan.Scan()
switch tok {
case token.EOF, token.SEMICOLON:
goto out
case token.INT, token.FLOAT:
c, err := strconv.ParseFloat(lit, 64)
if err != nil {
return nil, fmt.Errorf("parseFloat error %s\t%s\t%q",
fset.Position(pos), tok, lit)
}
output.Push(&TokenNotation{tokenType: tokenConst, c: c})
case token.IDENT:
output.Push(&TokenNotation{tokenType: tokenVar, v: lit})
case token.LPAREN: // (
s.Push(tok)
case token.ADD, token.SUB, token.MUL, token.QUO: // + - * /
opRetry:
if s.Len() == 0 {
s.Push(tok)
} else if op := s.Top(); op == token.LPAREN || priority(tok) > priority(op) {
s.Push(tok)
} else {
output.Push(&TokenNotation{tokenType: tokenOperator, o: s.Pop()})
goto opRetry
}
case token.RPAREN: // )
for s.Len() > 0 {
if op := s.Pop(); op == token.LPAREN {
break
} else {
output.Push(&TokenNotation{tokenType: tokenOperator, o: op})
}
}
default:
return nil, fmt.Errorf("unsupport token %s", tok)
}
}
out:
for i, l := 0, s.Len(); i < l; i++ {
output.Push(&TokenNotation{tokenType: tokenOperator, o: s.Pop()})
}
return
}
func errorHandler(pos token.Position, msg string) {
logger.Errorf("error %s\t%s\n", pos, msg)
}
func priority(tok token.Token) int {
switch tok {
case token.ADD, token.SUB:
return 1
case token.MUL, token.QUO:
return 2
default:
return 0
}
}

View File

@ -0,0 +1,67 @@
package http
import (
"context"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/didi/nightingale/src/common/address"
"github.com/didi/nightingale/src/common/middleware"
"github.com/didi/nightingale/src/modules/prober/config"
)
var srv = &http.Server{
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
func Start() {
c := config.Config
recoveryMid := middleware.Recovery()
if strings.ToLower(c.HTTP.Mode) == "release" {
gin.SetMode(gin.ReleaseMode)
middleware.DisableConsoleColor()
}
r := gin.New()
r.Use(recoveryMid)
Config(r)
srv.Addr = address.GetHTTPListen("prober")
srv.Handler = r
go func() {
fmt.Println("http.listening:", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
fmt.Printf("listening %s occur error: %s\n", srv.Addr, err)
os.Exit(3)
}
}()
}
// Shutdown http server
func Shutdown() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
fmt.Println("cannot shutdown http server:", err)
os.Exit(2)
}
// catching ctx.Done(). timeout of 5 seconds.
select {
case <-ctx.Done():
fmt.Println("shutdown http server timeout of 5 seconds.")
default:
fmt.Println("http server stopped")
}
}

View File

@ -0,0 +1,53 @@
package http
import (
"fmt"
"os"
"github.com/didi/nightingale/src/modules/prober/cache"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
)
func Config(r *gin.Engine) {
notLogin := r.Group("/api/rdb")
{
notLogin.GET("/ping", ping)
notLogin.GET("/pid", pid)
notLogin.GET("/addr", addr)
notLogin.GET("/collect-rule/:id", getCollectRule)
// notLogin.POST("/data", getData)
}
pprof.Register(r, "/api/prober/debug/pprof")
}
func ping(c *gin.Context) {
c.String(200, "pong")
}
func addr(c *gin.Context) {
c.String(200, c.Request.RemoteAddr)
}
func pid(c *gin.Context) {
c.String(200, fmt.Sprintf("%d", os.Getpid()))
}
func getCollectRule(c *gin.Context) {
rule, _ := cache.CollectRule.Get(urlParamInt64(c, "id"))
renderData(c, rule, nil)
}
/*
// TODO: get last collect data
func getData(c *gin.Context) {
var input dataobj.JudgeItem
errors.Dangerous(c.ShouldBind(&input))
pk := input.MD5()
linkedList, _ := cache.HistoryBigMap[pk[0:2]].Get(pk)
data := linkedList.HistoryData()
renderData(c, data, nil)
}
*/

View File

@ -0,0 +1,282 @@
package http
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/errors"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/toolkits/i18n"
)
func dangerous(v interface{}) {
errors.Dangerous(v)
}
func bomb(format string, a ...interface{}) {
errors.Bomb(i18n.Sprintf(format, a...))
}
func bind(c *gin.Context, ptr interface{}) {
dangerous(c.ShouldBindJSON(ptr))
}
func urlParamStr(c *gin.Context, field string) string {
val := c.Param(field)
if val == "" {
bomb("url param[%s] is blank", field)
}
return val
}
func urlParamInt64(c *gin.Context, field string) int64 {
strval := urlParamStr(c, field)
intval, err := strconv.ParseInt(strval, 10, 64)
if err != nil {
bomb("cannot convert %s to int64", strval)
}
return intval
}
func urlParamInt(c *gin.Context, field string) int {
return int(urlParamInt64(c, field))
}
func queryStr(c *gin.Context, key string, defaultVal ...string) string {
val := c.Query(key)
if val != "" {
return val
}
if len(defaultVal) == 0 {
bomb("query param[%s] is necessary", key)
}
return defaultVal[0]
}
func queryInt(c *gin.Context, key string, defaultVal ...int) int {
strv := c.Query(key)
if strv != "" {
intv, err := strconv.Atoi(strv)
if err != nil {
bomb("cannot convert [%s] to int", strv)
}
return intv
}
if len(defaultVal) == 0 {
bomb("query param[%s] is necessary", key)
}
return defaultVal[0]
}
func queryInt64(c *gin.Context, key string, defaultVal ...int64) int64 {
strv := c.Query(key)
if strv != "" {
intv, err := strconv.ParseInt(strv, 10, 64)
if err != nil {
bomb("cannot convert [%s] to int64", strv)
}
return intv
}
if len(defaultVal) == 0 {
bomb("query param[%s] is necessary", key)
}
return defaultVal[0]
}
func offset(c *gin.Context, limit int) int {
if limit <= 0 {
limit = 10
}
page := queryInt(c, "p", 1)
return (page - 1) * limit
}
func renderMessage(c *gin.Context, v interface{}) {
if v == nil {
c.JSON(200, gin.H{"err": ""})
return
}
switch t := v.(type) {
case string:
c.JSON(200, gin.H{"err": i18n.Sprintf(t)})
case error:
c.JSON(200, gin.H{"err": t.Error()})
}
}
func renderData(c *gin.Context, data interface{}, err error) {
if err == nil {
c.JSON(200, gin.H{"dat": data, "err": ""})
return
}
renderMessage(c, err.Error())
}
func renderZeroPage(c *gin.Context) {
renderData(c, gin.H{
"list": []int{},
"total": 0,
}, nil)
}
// ------------
type idsForm struct {
Ids []int64 `json:"ids"`
}
func checkPassword(passwd string) error {
indNum := [4]int{0, 0, 0, 0}
spCode := []byte{'!', '@', '#', '$', '%', '^', '&', '*', '_', '-', '~', '.', ',', '<', '>', '/', ';', ':', '|', '?', '+', '='}
if len(passwd) < 6 {
return fmt.Errorf("password too short")
}
passwdByte := []byte(passwd)
for _, i := range passwdByte {
if i >= 'A' && i <= 'Z' {
indNum[0] = 1
continue
}
if i >= 'a' && i <= 'z' {
indNum[1] = 1
continue
}
if i >= '0' && i <= '9' {
indNum[2] = 1
continue
}
has := false
for _, s := range spCode {
if i == s {
indNum[3] = 1
has = true
break
}
}
if !has {
return fmt.Errorf("character: %s not supported", string(i))
}
}
codeCount := 0
for _, i := range indNum {
codeCount += i
}
if codeCount < 4 {
return fmt.Errorf("password too simple")
}
return nil
}
// ------------
func loginUsername(c *gin.Context) string {
value, has := c.Get("username")
if !has {
bomb("unauthorized")
}
if value == nil {
bomb("unauthorized")
}
return value.(string)
}
func loginUser(c *gin.Context) *models.User {
username := loginUsername(c)
user, err := models.UserGet("username=?", username)
dangerous(err)
if user == nil {
bomb("unauthorized")
}
return user
}
func loginRoot(c *gin.Context) *models.User {
value, has := c.Get("user")
if !has {
bomb("unauthorized")
}
return value.(*models.User)
}
func User(id int64) *models.User {
user, err := models.UserGet("id=?", id)
if err != nil {
bomb("cannot retrieve user[%d]: %v", id, err)
}
if user == nil {
bomb("no such user[%d]", id)
}
return user
}
func Team(id int64) *models.Team {
team, err := models.TeamGet("id=?", id)
if err != nil {
bomb("cannot retrieve team[%d]: %v", id, err)
}
if team == nil {
bomb("no such team[%d]", id)
}
return team
}
func Role(id int64) *models.Role {
role, err := models.RoleGet("id=?", id)
if err != nil {
bomb("cannot retrieve role[%d]: %v", id, err)
}
if role == nil {
bomb("no such role[%d]", id)
}
return role
}
func Node(id int64) *models.Node {
node, err := models.NodeGet("id=?", id)
dangerous(err)
if node == nil {
bomb("no such node[%d]", id)
}
return node
}

View File

@ -0,0 +1,34 @@
package manager
type ruleSummary struct {
id int64 // collect rule id
executeAt int64
}
type ruleSummaryHeap []*ruleSummary
func (h ruleSummaryHeap) Len() int {
return len(h)
}
func (h ruleSummaryHeap) Less(i, j int) bool {
return h[i].executeAt < h[j].executeAt
}
func (h ruleSummaryHeap) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *ruleSummaryHeap) Push(x interface{}) {
*h = append(*h, x.(*ruleSummary))
}
func (h *ruleSummaryHeap) Pop() interface{} {
x := (*h)[len(*h)-1]
*h = (*h)[:len(*h)-1]
return x
}
func (h *ruleSummaryHeap) Top() *ruleSummary {
return (*h)[len(*h)-1]
}

View File

@ -0,0 +1,194 @@
package manager
import (
"container/heap"
"context"
"log"
"time"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/didi/nightingale/src/modules/prober/cache"
"github.com/didi/nightingale/src/modules/prober/config"
"github.com/didi/nightingale/src/modules/prober/core"
"github.com/influxdata/telegraf"
"github.com/toolkits/pkg/logger"
)
type manager struct {
ctx context.Context
cache *cache.CollectRuleCache
config *config.ConfYaml
heap ruleSummaryHeap
index map[int64]*ruleEntity // add at cache.C , del at executeAt check
worker []worker
tx chan *ruleEntity
}
func NewManager(cfg *config.ConfYaml, cache *cache.CollectRuleCache) *manager {
return &manager{
cache: cache,
config: cfg,
index: make(map[int64]*ruleEntity),
}
}
func (p *manager) Start(ctx context.Context) error {
workerProcesses := p.config.WorkerProcesses
p.ctx = ctx
p.tx = make(chan *ruleEntity, 1)
heap.Init(&p.heap)
p.worker = make([]worker, workerProcesses)
for i := 0; i < workerProcesses; i++ {
p.worker[i].rx = p.tx
p.worker[i].ctx = ctx
// p.worker[i].acc = p.acc
p.worker[i].loop(i)
}
p.loop()
return nil
}
// loop schedule collect job and send the metric to transfer
func (p *manager) loop() {
// main
go func() {
tick := time.NewTicker(1 * time.Second)
defer tick.Stop()
for {
select {
case <-p.ctx.Done():
return
case <-p.cache.C:
if err := p.SyncRules(); err != nil {
log.Printf("manager.SyncRules err %s", err)
}
case <-tick.C:
if err := p.schedule(); err != nil {
log.Printf("manager.schedule err %s", err)
}
}
}
}()
}
// schedule return until there are no jobs
func (p *manager) schedule() error {
for {
now := time.Now().Unix()
if p.heap.Len() == 0 {
return nil
}
if p.heap.Top().executeAt > now {
return nil
}
summary := heap.Pop(&p.heap).(*ruleSummary)
rule, ok := p.cache.Get(summary.id)
if !ok {
// drop it if not exist in cache
delete(p.index, summary.id)
continue
}
entity, ok := p.index[rule.Id]
if !ok {
// impossible
log.Printf("manager.index[%d] not exists", rule.Id)
// let's fix it
p.index[entity.rule.Id] = entity
}
// update rule
if err := entity.update(rule); err != nil {
logger.Warningf("ruleEntity update err %s", err)
}
p.tx <- entity
summary.executeAt = now + int64(rule.Step)
heap.Push(&p.heap, summary)
continue
}
}
func (p *manager) SyncRules() error {
for _, v := range p.cache.GetAll() {
if _, ok := p.index[v.Id]; !ok {
p.AddRule(v)
}
}
return nil
}
func (p *manager) AddRule(rule *models.CollectRule) error {
ruleEntity, err := newRuleEntity(rule)
if err != nil {
return err
}
p.index[rule.Id] = ruleEntity
heap.Push(&p.heap, &ruleSummary{
id: rule.Id,
executeAt: time.Now().Unix() + int64(rule.Step),
})
return nil
}
type collectRule interface {
telegraf.Input
tags() map[string]string
}
func telegrafInput(rule *models.CollectRule) (telegraf.Input, error) {
c, err := collector.GetCollector(rule.CollectType)
if err != nil {
return nil, err
}
return c.TelegrafInput(rule)
}
type worker struct {
ctx context.Context
cache *cache.CollectRuleCache
rx chan *ruleEntity
}
func (p *worker) loop(id int) {
go func() {
for {
select {
case <-p.ctx.Done():
return
case entity := <-p.rx:
if err := p.do(entity); err != nil {
log.Printf("work[%d].do err %s", id, err)
}
}
}
}()
}
func (p *worker) do(entity *ruleEntity) error {
entity.metrics = entity.metrics[:0]
// telegraf
err := entity.Input.Gather(entity)
if len(entity.metrics) == 0 {
return err
}
// eval expression metrics
entity.calc()
// send
core.Push(entity.metrics)
return err
}

View File

@ -0,0 +1,398 @@
package manager
import (
"fmt"
"hash/fnv"
"sort"
"strconv"
"time"
"github.com/influxdata/telegraf"
)
type metric struct {
name string
tags []*telegraf.Tag
fields []*telegraf.Field
tm time.Time
tp telegraf.ValueType
aggregate bool
}
func NewMetric(
name string,
tags map[string]string,
fields map[string]interface{},
tm time.Time,
tp ...telegraf.ValueType,
) (telegraf.Metric, error) {
var vtype telegraf.ValueType
if len(tp) > 0 {
vtype = tp[0]
} else {
vtype = telegraf.Untyped
}
m := &metric{
name: name,
tags: nil,
fields: nil,
tm: tm,
tp: vtype,
}
if len(tags) > 0 {
m.tags = make([]*telegraf.Tag, 0, len(tags))
for k, v := range tags {
m.tags = append(m.tags,
&telegraf.Tag{Key: k, Value: v})
}
sort.Slice(m.tags, func(i, j int) bool { return m.tags[i].Key < m.tags[j].Key })
}
if len(fields) > 0 {
m.fields = make([]*telegraf.Field, 0, len(fields))
for k, v := range fields {
v := convertField(v)
if v == nil {
continue
}
m.AddField(k, v)
}
}
return m, nil
}
// FromMetric returns a deep copy of the metric with any tracking information
// removed.
func FromMetric(other telegraf.Metric) telegraf.Metric {
m := &metric{
name: other.Name(),
tags: make([]*telegraf.Tag, len(other.TagList())),
fields: make([]*telegraf.Field, len(other.FieldList())),
tm: other.Time(),
tp: other.Type(),
aggregate: other.IsAggregate(),
}
for i, tag := range other.TagList() {
m.tags[i] = &telegraf.Tag{Key: tag.Key, Value: tag.Value}
}
for i, field := range other.FieldList() {
m.fields[i] = &telegraf.Field{Key: field.Key, Value: field.Value}
}
return m
}
func (m *metric) String() string {
return fmt.Sprintf("%s %v %v %d", m.name, m.Tags(), m.Fields(), m.tm.UnixNano())
}
func (m *metric) Name() string {
return m.name
}
func (m *metric) Tags() map[string]string {
tags := make(map[string]string, len(m.tags))
for _, tag := range m.tags {
tags[tag.Key] = tag.Value
}
return tags
}
func (m *metric) TagList() []*telegraf.Tag {
return m.tags
}
func (m *metric) Fields() map[string]interface{} {
fields := make(map[string]interface{}, len(m.fields))
for _, field := range m.fields {
fields[field.Key] = field.Value
}
return fields
}
func (m *metric) FieldList() []*telegraf.Field {
return m.fields
}
func (m *metric) Time() time.Time {
return m.tm
}
func (m *metric) Type() telegraf.ValueType {
return m.tp
}
func (m *metric) SetName(name string) {
m.name = name
}
func (m *metric) AddPrefix(prefix string) {
m.name = prefix + m.name
}
func (m *metric) AddSuffix(suffix string) {
m.name = m.name + suffix
}
func (m *metric) AddTag(key, value string) {
for i, tag := range m.tags {
if key > tag.Key {
continue
}
if key == tag.Key {
tag.Value = value
return
}
m.tags = append(m.tags, nil)
copy(m.tags[i+1:], m.tags[i:])
m.tags[i] = &telegraf.Tag{Key: key, Value: value}
return
}
m.tags = append(m.tags, &telegraf.Tag{Key: key, Value: value})
}
func (m *metric) HasTag(key string) bool {
for _, tag := range m.tags {
if tag.Key == key {
return true
}
}
return false
}
func (m *metric) GetTag(key string) (string, bool) {
for _, tag := range m.tags {
if tag.Key == key {
return tag.Value, true
}
}
return "", false
}
func (m *metric) RemoveTag(key string) {
for i, tag := range m.tags {
if tag.Key == key {
copy(m.tags[i:], m.tags[i+1:])
m.tags[len(m.tags)-1] = nil
m.tags = m.tags[:len(m.tags)-1]
return
}
}
}
func (m *metric) AddField(key string, value interface{}) {
for i, field := range m.fields {
if key == field.Key {
m.fields[i] = &telegraf.Field{Key: key, Value: convertField(value)}
return
}
}
m.fields = append(m.fields, &telegraf.Field{Key: key, Value: convertField(value)})
}
func (m *metric) HasField(key string) bool {
for _, field := range m.fields {
if field.Key == key {
return true
}
}
return false
}
func (m *metric) GetField(key string) (interface{}, bool) {
for _, field := range m.fields {
if field.Key == key {
return field.Value, true
}
}
return nil, false
}
func (m *metric) RemoveField(key string) {
for i, field := range m.fields {
if field.Key == key {
copy(m.fields[i:], m.fields[i+1:])
m.fields[len(m.fields)-1] = nil
m.fields = m.fields[:len(m.fields)-1]
return
}
}
}
func (m *metric) SetTime(t time.Time) {
m.tm = t
}
func (m *metric) Copy() telegraf.Metric {
m2 := &metric{
name: m.name,
tags: make([]*telegraf.Tag, len(m.tags)),
fields: make([]*telegraf.Field, len(m.fields)),
tm: m.tm,
tp: m.tp,
aggregate: m.aggregate,
}
for i, tag := range m.tags {
m2.tags[i] = &telegraf.Tag{Key: tag.Key, Value: tag.Value}
}
for i, field := range m.fields {
m2.fields[i] = &telegraf.Field{Key: field.Key, Value: field.Value}
}
return m2
}
func (m *metric) SetAggregate(b bool) {
m.aggregate = true
}
func (m *metric) IsAggregate() bool {
return m.aggregate
}
func (m *metric) HashID() uint64 {
h := fnv.New64a()
h.Write([]byte(m.name))
h.Write([]byte("\n"))
for _, tag := range m.tags {
h.Write([]byte(tag.Key))
h.Write([]byte("\n"))
h.Write([]byte(tag.Value))
h.Write([]byte("\n"))
}
return h.Sum64()
}
func (m *metric) Accept() {
}
func (m *metric) Reject() {
}
func (m *metric) Drop() {
}
// Convert field to a supported type or nil if unconvertible
// tranfer to float64
func convertField(v interface{}) interface{} {
switch v := v.(type) {
case float64:
return v
case int64:
return float64(v)
case string:
return atof(v)
case bool:
return btof(v)
case int:
return float64(v)
case uint:
return float64(v)
case uint64:
return float64(v)
case []byte:
return atof(string(v))
case int32:
return float64(v)
case int16:
return float64(v)
case int8:
return float64(v)
case uint32:
return float64(v)
case uint16:
return float64(v)
case uint8:
return float64(v)
case float32:
return float64(v)
case *float64:
if v != nil {
return float64(*v)
}
case *int64:
if v != nil {
return float64(*v)
}
case *string:
if v != nil {
return atof(*v)
}
case *bool:
if v != nil {
return btof(*v)
}
case *int:
if v != nil {
return float64(*v)
}
case *uint:
if v != nil {
return float64(*v)
}
case *uint64:
if v != nil {
return float64(*v)
}
case *[]byte:
if v != nil {
return atof(string(*v))
}
case *int32:
if v != nil {
return float64(*v)
}
case *int16:
if v != nil {
return float64(*v)
}
case *int8:
if v != nil {
return float64(*v)
}
case *uint32:
if v != nil {
return float64(*v)
}
case *uint16:
if v != nil {
return float64(*v)
}
case *uint8:
if v != nil {
return float64(*v)
}
case *float32:
if v != nil {
return float64(*v)
}
default:
return nil
}
return nil
}
func atof(s string) interface{} {
if f, err := strconv.ParseFloat(s, 64); err != nil {
return nil
} else {
return f
}
}
func btof(b bool) interface{} {
if b {
return float64(1)
}
return float64(0)
}

View File

@ -0,0 +1,258 @@
package manager
import (
"log"
"strconv"
"sync"
"time"
"github.com/didi/nightingale/src/common/dataobj"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/monapi/collector"
"github.com/didi/nightingale/src/modules/prober/cache"
"github.com/influxdata/telegraf"
"github.com/toolkits/pkg/logger"
)
// not thread-safe
type ruleEntity struct {
sync.RWMutex
telegraf.Input
rule *models.CollectRule
tags map[string]string
precision time.Duration
metrics []*dataobj.MetricValue
}
func newRuleEntity(rule *models.CollectRule) (*ruleEntity, error) {
c, err := collector.GetCollector(rule.CollectType)
if err != nil {
return nil, err
}
input, err := c.TelegrafInput(rule)
if err != nil {
return nil, err
}
tags, err := dataobj.SplitTagsString(rule.Tags)
if err != nil {
return nil, err
}
return &ruleEntity{
Input: input,
rule: rule,
tags: tags,
metrics: []*dataobj.MetricValue{},
precision: time.Second,
}, nil
}
// calc metrics with expression
func (p *ruleEntity) calc() error {
if len(p.metrics) == 0 {
return nil
}
sample := p.metrics[0]
configs, ok := cache.GetMetricExprs(p.rule.CollectType)
if !ok {
return nil
}
vars := map[string]float64{}
for _, v := range p.metrics {
logger.Debugf("get v[%s] %f", v.Metric, v.Value)
vars[v.Metric] = v.Value
}
// TODO: add some variable from system or rule
for _, config := range configs {
f, err := config.Calc(vars)
if err != nil {
logger.Debugf("calc err %s", err)
continue
}
p.metrics = append(p.metrics, &dataobj.MetricValue{
Nid: sample.Nid,
Metric: config.Name,
Timestamp: sample.Timestamp,
Step: sample.Step,
CounterType: config.Type,
TagsMap: sample.TagsMap,
Value: f,
ValueUntyped: f,
})
}
return nil
}
func (p *ruleEntity) update(rule *models.CollectRule) error {
if p.rule.LastUpdated == rule.LastUpdated {
return nil
}
input, err := telegrafInput(rule)
if err != nil {
// ignore error, use old config
log.Printf("telegrafInput() id %d type %s name %s err %s",
rule.Id, rule.CollectType, rule.Name, err)
}
tags, err := dataobj.SplitTagsString(rule.Tags)
if err != nil {
return err
}
p.Input = input
p.rule = rule
p.tags = tags
return nil
}
// https://docs.influxdata.com/telegraf/v1.14/data_formats/output/prometheus/
func (p *ruleEntity) MakeMetric(metric telegraf.Metric) []*dataobj.MetricValue {
tags := map[string]string{}
for _, v := range metric.TagList() {
tags[v.Key] = v.Value
}
for k, v := range p.tags {
tags[k] = v
}
nid := strconv.FormatInt(p.rule.Nid, 10)
name := metric.Name()
ts := metric.Time().Unix()
step := int64(p.rule.Step) // deprecated
fields := metric.Fields()
ms := make([]*dataobj.MetricValue, 0, len(fields))
for k, v := range fields {
f, ok := v.(float64)
if !ok {
continue
}
c, ok := cache.Metric(name+"_"+k, metric.Type())
if !ok {
continue
}
ms = append(ms, &dataobj.MetricValue{
Nid: nid,
Metric: c.Name,
Timestamp: ts,
Step: step,
CounterType: c.Type,
TagsMap: tags,
Value: f,
ValueUntyped: f,
})
}
return ms
}
func (p *ruleEntity) AddFields(
measurement string,
fields map[string]interface{},
tags map[string]string,
t ...time.Time,
) {
p.addFields(measurement, tags, fields, telegraf.Untyped, t...)
}
func (p *ruleEntity) AddGauge(
measurement string,
fields map[string]interface{},
tags map[string]string,
t ...time.Time,
) {
p.addFields(measurement, tags, fields, telegraf.Gauge, t...)
}
func (p *ruleEntity) AddCounter(
measurement string,
fields map[string]interface{},
tags map[string]string,
t ...time.Time,
) {
p.addFields(measurement, tags, fields, telegraf.Counter, t...)
}
func (p *ruleEntity) AddSummary(
measurement string,
fields map[string]interface{},
tags map[string]string,
t ...time.Time,
) {
p.addFields(measurement, tags, fields, telegraf.Summary, t...)
}
func (p *ruleEntity) AddHistogram(
measurement string,
fields map[string]interface{},
tags map[string]string,
t ...time.Time,
) {
p.addFields(measurement, tags, fields, telegraf.Histogram, t...)
}
func (p *ruleEntity) AddMetric(m telegraf.Metric) {
m.SetTime(m.Time().Round(p.precision))
if metrics := p.MakeMetric(m); m != nil {
p.pushMetrics(metrics)
}
}
func (p *ruleEntity) pushMetrics(metrics []*dataobj.MetricValue) {
p.Lock()
defer p.Unlock()
p.metrics = append(p.metrics, metrics...)
}
func (p *ruleEntity) addFields(
measurement string,
tags map[string]string,
fields map[string]interface{},
tp telegraf.ValueType,
t ...time.Time,
) {
m, err := NewMetric(measurement, tags, fields, p.getTime(t), tp)
if err != nil {
return
}
if metrics := p.MakeMetric(m); m != nil {
p.pushMetrics(metrics)
}
}
// AddError passes a runtime error to the accumulator.
// The error will be tagged with the plugin name and written to the log.
func (p *ruleEntity) AddError(err error) {
if err == nil {
return
}
log.Printf("Error in plugin: %v", err)
}
func (p *ruleEntity) SetPrecision(precision time.Duration) {
p.precision = precision
}
func (p *ruleEntity) getTime(t []time.Time) time.Time {
var timestamp time.Time
if len(t) > 0 {
timestamp = t[0]
} else {
timestamp = time.Now()
}
return timestamp.Round(p.precision)
}
func (p *ruleEntity) WithTracking(maxTracked int) telegraf.TrackingAccumulator {
return nil
}

View File

@ -0,0 +1,132 @@
package main
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/didi/nightingale/src/common/identity"
"github.com/didi/nightingale/src/common/loggeri"
"github.com/didi/nightingale/src/common/report"
"github.com/didi/nightingale/src/toolkits/stats"
"github.com/didi/nightingale/src/modules/prober/cache"
"github.com/didi/nightingale/src/modules/prober/config"
"github.com/didi/nightingale/src/modules/prober/core"
"github.com/didi/nightingale/src/modules/prober/http"
"github.com/didi/nightingale/src/modules/prober/manager"
_ "github.com/didi/nightingale/src/modules/monapi/plugins/all"
_ "github.com/go-sql-driver/mysql"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/runner"
)
var (
vers *bool
help *bool
conf *string
version = "No Version Provided"
)
func init() {
vers = flag.Bool("v", false, "display the version.")
help = flag.Bool("h", false, "print this help.")
conf = flag.String("f", "", "specify configuration file.")
flag.Parse()
if *vers {
fmt.Println("Version:", version)
os.Exit(0)
}
if *help {
flag.Usage()
os.Exit(0)
}
}
func main() {
aconf()
pconf()
start()
ctx, cancel := context.WithCancel(context.Background())
cfg := config.Config
identity.Parse()
loggeri.Init(cfg.Logger)
go stats.Init("n9e.prober")
go report.Init(cfg.Report, "rdb")
cache.Init(ctx)
if cfg.Logger.Level != "DEBUG" {
gin.SetMode(gin.ReleaseMode)
}
core.InitRpcClients()
manager.NewManager(cfg, cache.CollectRule).Start(ctx)
http.Start()
ending(cancel)
}
// auto detect configuration file
func aconf() {
if *conf != "" && file.IsExist(*conf) {
return
}
*conf = "etc/prober.local.yml"
if file.IsExist(*conf) {
return
}
*conf = "etc/prober.yml"
if file.IsExist(*conf) {
return
}
fmt.Println("no configuration file for prober")
os.Exit(1)
}
// parse configuration file
func pconf() {
if err := config.Parse(*conf); err != nil {
fmt.Println("cannot parse configuration file:", err)
os.Exit(1)
}
}
func start() {
runner.Init()
fmt.Println("prober start, use configuration file:", *conf)
fmt.Println("runner.Cwd:", runner.Cwd)
fmt.Println("runner.Hostname:", runner.Hostname)
}
func ending(cancel context.CancelFunc) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
select {
case <-c:
fmt.Printf("stop signal caught, stopping... pid=%d\n", os.Getpid())
}
cancel()
logger.Close()
http.Shutdown()
fmt.Printf("%s stopped successfully\n", os.Args[0])
}

View File

@ -0,0 +1,39 @@
package auth
import (
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/config"
)
var defaultAuth Authenticator
func Init(cf config.AuthExtraSection) {
defaultAuth = *New(cf)
}
func WhiteListAccess(remoteAddr string) error {
return defaultAuth.WhiteListAccess(remoteAddr)
}
// PostLogin check user status after login
func PostLogin(user *models.User, loginErr error) error {
return defaultAuth.PostLogin(user, loginErr)
}
func ChangePassword(user *models.User, password string) error {
return defaultAuth.ChangePassword(user, password)
}
func CheckPassword(password string) error {
return defaultAuth.CheckPassword(password)
}
// ChangePasswordRedirect check user should change password before login
// return change password redirect url
func ChangePasswordRedirect(user *models.User, redirect string) string {
return defaultAuth.ChangePasswordRedirect(user, redirect)
}
func Start() error {
return defaultAuth.Start()
}

View File

@ -0,0 +1,363 @@
package auth
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/cache"
"github.com/didi/nightingale/src/modules/rdb/config"
"github.com/didi/nightingale/src/toolkits/i18n"
"github.com/toolkits/pkg/logger"
)
const (
ChangePasswordURL = "/change-password"
)
type Authenticator struct {
extraMode bool
whiteList bool
frozenTime int64
writenOffTime int64
userExpire bool
}
// description:"enable user expire control, active -> frozen -> writen-off"
func New(cf config.AuthExtraSection) *Authenticator {
if !cf.Enable {
return &Authenticator{}
}
return &Authenticator{
extraMode: true,
whiteList: cf.WhiteList,
frozenTime: 86400 * int64(cf.FrozenDays),
writenOffTime: 86400 * int64(cf.WritenOffDays),
}
}
func (p *Authenticator) WhiteListAccess(remoteAddr string) error {
if !p.whiteList {
return nil
}
return models.WhiteListAccess(remoteAddr)
}
// ChangePasswordRedirect check user should change password before login
// return change password redirect url
func (p *Authenticator) ChangePasswordRedirect(user *models.User, redirect string) string {
if !p.extraMode {
return ""
}
cf := cache.AuthConfig()
var reason string
if user.PwdUpdatedAt == 0 {
reason = _s("First Login, please change the password in time")
} else if user.PwdUpdatedAt+cf.PwdExpiresIn*86400*30 < time.Now().Unix() {
reason = _s("Password expired, please change the password in time")
} else {
return ""
}
v := url.Values{
"redirect": {redirect},
"username": {user.Username},
"reason": {reason},
"pwdRules": cf.PwdRules(),
}
return ChangePasswordURL + "?" + v.Encode()
}
func (p *Authenticator) PostLogin(user *models.User, loginErr error) (err error) {
now := time.Now().Unix()
defer func() {
if user == nil {
return
}
if err == nil {
user.LoggedAt = now
}
user.Update("status", "login_err_num", "locked_at", "updated_at", "logged_at")
}()
if !p.extraMode || user == nil {
err = loginErr
return
}
cf := cache.AuthConfig()
if user.Type == models.USER_T_TEMP && (now < user.ActiveBegin || user.ActiveEnd < now) {
err = _e("Temporary user has expired")
return
}
status := user.Status
retry:
switch user.Status {
case models.USER_S_ACTIVE:
err = activeUserAccess(cf, user, loginErr)
case models.USER_S_INACTIVE:
err = inactiveUserAccess(cf, user, loginErr)
case models.USER_S_LOCKED:
err = lockedUserAccess(cf, user, loginErr)
case models.USER_S_FROZEN:
err = frozenUserAccess(cf, user, loginErr)
case models.USER_S_WRITEN_OFF:
err = writenOffUserAccess(cf, user, loginErr)
default:
err = _e("Invalid user status %d", user.Status)
}
// if user's status has been changed goto retry
if user.Status != status {
status = user.Status
goto retry
}
return
}
func (p *Authenticator) ChangePassword(user *models.User, password string) (err error) {
defer func() {
if err == nil {
err = user.Update("password", "passwords",
"pwd_updated_at", "updated_at")
}
}()
changePassword := func() error {
pwd, err := models.CryptoPass(password)
if err != nil {
return err
}
now := time.Now().Unix()
user.Password = pwd
user.PwdUpdatedAt = now
user.UpdatedAt = now
return nil
}
if !p.extraMode {
return changePassword()
}
// precheck
cf := cache.AuthConfig()
if err = checkPassword(cf, password); err != nil {
return
}
if err = changePassword(); err != nil {
return
}
var passwords []string
err = json.Unmarshal([]byte(user.Passwords), &passwords)
if err != nil {
// reset passwords
passwords = []string{user.Password}
b, _ := json.Marshal(passwords)
user.Passwords = string(b)
err = nil
return
}
for _, v := range passwords {
if user.Password == v {
err = _e("The password is the same as the old password")
return
}
}
passwords = append(passwords, user.Password)
if n := len(passwords) - cf.PwdHistorySize; n > 0 {
passwords = passwords[n:]
}
b, _ := json.Marshal(passwords)
user.Passwords = string(b)
return
}
func (p *Authenticator) CheckPassword(password string) error {
if !p.extraMode {
return nil
}
return checkPassword(cache.AuthConfig(), password)
}
func (p *Authenticator) Start() error {
if !p.extraMode {
return nil
}
go func() {
for {
now := time.Now().Unix()
if p.frozenTime > 0 {
// 3个月以上未登录用户自动变为休眠状态
if _, err := models.DB["rdb"].Exec("update user set status=?, updated_at=?, locked_at=? where ((logged_at > 0 and logged_at<?) or (logged_at == 0 and created_at < ?)) and status in (?,?,?)",
models.USER_S_FROZEN, now, now, now-p.frozenTime,
models.USER_S_ACTIVE, models.USER_S_INACTIVE, models.USER_S_LOCKED); err != nil {
logger.Errorf("update user status error %s", err)
}
}
if p.writenOffTime > 0 {
// 变为休眠状态后1年未激活用户自动变为已注销状态
if _, err := models.DB["rdb"].Exec("update user set status=?, updated_at=? where locked_at<? and status=?",
models.USER_S_WRITEN_OFF, now, now-p.writenOffTime, models.USER_S_FROZEN); err != nil {
logger.Errorf("update user status error %s", err)
}
}
// reset login err num before 24 hours ago
if _, err := models.DB["rdb"].Exec("update user set login_err_num=0, updated_at=? where updated_at<? and login_err_num>0", now, now-86400); err != nil {
logger.Errorf("update user login err num error %s", err)
}
time.Sleep(time.Hour)
}
}()
return nil
}
func activeUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
now := time.Now().Unix()
if loginErr != nil {
if cf.MaxNumErr > 0 {
user.UpdatedAt = now
user.LoginErrNum++
if user.LoginErrNum >= cf.MaxNumErr {
user.Status = models.USER_S_LOCKED
user.LockedAt = now
return nil
}
return _e("Incorrect login/password %s times, you still have %s chances",
user.LoginErrNum, cf.MaxNumErr-user.LoginErrNum)
} else {
return loginErr
}
}
user.LoginErrNum = 0
user.UpdatedAt = now
if cf.MaxSessionNumber > 0 {
if n, err := models.SessionUserAll(user.Username); err != nil {
return err
} else if n >= cf.MaxSessionNumber {
return _e("The limited sessions %d", cf.MaxSessionNumber)
}
}
if cf.PwdExpiresIn > 0 && user.PwdUpdatedAt > 0 {
// debug account
// TODO: remove me
if user.Username == "Demo.2022" {
if now-user.PwdUpdatedAt > cf.PwdExpiresIn*60 {
return _e("Password has been expired")
}
}
if now-user.PwdUpdatedAt > cf.PwdExpiresIn*30*86400 {
return _e("Password has been expired")
}
}
return nil
}
func inactiveUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
return _e("User is inactive")
}
func lockedUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
now := time.Now().Unix()
if now-user.LockedAt > cf.LockTime*60 {
user.Status = models.USER_S_ACTIVE
user.LoginErrNum = 0
user.UpdatedAt = now
return nil
}
return _e("User is locked")
}
func frozenUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
return _e("User is frozen")
}
func writenOffUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
return _e("User is writen off")
}
func checkPassword(cf *models.AuthConfig, passwd string) error {
indNum := [4]int{0, 0, 0, 0}
spCode := []byte{'!', '@', '#', '$', '%', '^', '&', '*', '_', '-', '~', '.', ',', '<', '>', '/', ';', ':', '|', '?', '+', '='}
if cf.PwdMinLenght > 0 && len(passwd) < cf.PwdMinLenght {
return _e("Password too short (min:%d) %s", cf.PwdMinLenght, cf.MustInclude())
}
passwdByte := []byte(passwd)
for _, i := range passwdByte {
if i >= 'A' && i <= 'Z' {
indNum[0] = 1
continue
}
if i >= 'a' && i <= 'z' {
indNum[1] = 1
continue
}
if i >= '0' && i <= '9' {
indNum[2] = 1
continue
}
has := false
for _, s := range spCode {
if i == s {
indNum[3] = 1
has = true
break
}
}
if !has {
return _e("character: %s not supported", string(i))
}
}
if cf.PwdMustIncludeFlag&models.PWD_INCLUDE_UPPER > 0 && indNum[0] == 0 {
return _e("Invalid Password, %s", cf.MustInclude())
}
if cf.PwdMustIncludeFlag&models.PWD_INCLUDE_LOWER > 0 && indNum[1] == 0 {
return _e("Invalid Password, %s", cf.MustInclude())
}
if cf.PwdMustIncludeFlag&models.PWD_INCLUDE_NUMBER > 0 && indNum[2] == 0 {
return _e("Invalid Password, %s", cf.MustInclude())
}
if cf.PwdMustIncludeFlag&models.PWD_INCLUDE_SPEC_CHAR > 0 && indNum[3] == 0 {
return _e("Invalid Password, %s", cf.MustInclude())
}
return nil
}
func _e(format string, a ...interface{}) error {
return fmt.Errorf(i18n.Sprintf(format, a...))
}
func _s(format string, a ...interface{}) string {
return i18n.Sprintf(format, a...)
}

54
src/modules/rdb/cache/cache.go vendored Normal file
View File

@ -0,0 +1,54 @@
package cache
import (
"context"
"github.com/didi/nightingale/src/models"
)
var (
DefaultCache = &Cache{
interval: 10, // Seconds
config: configCache{
authConfig: &models.DefaultAuthConfig,
},
}
)
func NewCache(interval int) *Cache {
return &Cache{
interval: interval,
}
}
func AuthConfig() *models.AuthConfig {
return DefaultCache.config.AuthConfig()
}
func Start() {
DefaultCache.Start()
}
func Stop() {
DefaultCache.Stop()
}
type Cache struct {
session sessionCache
config configCache
interval int
ctx context.Context
cancel context.CancelFunc
}
func (p *Cache) Start() {
p.ctx, p.cancel = context.WithCancel(context.Background())
p.config.loop(p.ctx, p.interval)
// p.session.loop(ctx, p.interval)
}
func (p *Cache) Stop() {
p.cancel()
}

52
src/modules/rdb/cache/config.go vendored Normal file
View File

@ -0,0 +1,52 @@
package cache
import (
"context"
"sync"
"time"
"github.com/didi/nightingale/src/models"
"github.com/toolkits/pkg/logger"
)
type configCache struct {
sync.RWMutex
authConfig *models.AuthConfig
}
func (p *configCache) AuthConfig() *models.AuthConfig {
p.RLock()
defer p.RUnlock()
return p.authConfig
}
func (p *configCache) loop(ctx context.Context, interval int) {
go func() {
t := time.NewTicker(time.Duration(interval) * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
if err := p.update(); err != nil {
logger.Errorf("configCache update err %s", err)
}
}
}
}()
}
func (p *configCache) update() error {
authConfig, err := models.AuthConfigGet()
if err != nil {
return err
}
p.Lock()
p.authConfig = authConfig
p.Unlock()
return nil
}

35
src/modules/rdb/cache/session.go vendored Normal file
View File

@ -0,0 +1,35 @@
package cache
import (
"context"
"sync"
"time"
"github.com/toolkits/pkg/logger"
)
type sessionCache struct {
sync.RWMutex
}
func (p *sessionCache) loop(ctx context.Context, interval int) {
func() {
t := time.NewTicker(time.Duration(interval) * time.Second)
defer t.Stop()
for {
select {
case <-ctx.Done():
return
case <-t.C:
if err := p.update(); err != nil {
logger.Errorf("sessionCache update err %s", err)
}
}
}
}()
}
func (p *sessionCache) update() error {
return nil
}

View File

@ -19,8 +19,20 @@ type ConfigT struct {
Sender map[string]senderSection `yaml:"sender"`
RabbitMQ rabbitmqSection `yaml:"rabbitmq"`
WeChat wechatSection `yaml:"wechat"`
Captcha bool `yaml:"captcha"`
I18n i18n.I18nSection `yaml:"i18n"`
Auth authSection `yaml:"auth"`
}
type authSection struct {
Captcha bool `yaml:"captcha"`
ExtraMode AuthExtraSection `yaml:"extraMode"`
}
type AuthExtraSection struct {
Enable bool `yaml:"enable"`
WhiteList bool `yaml:"whiteList"`
FrozenDays int `yaml:"frozenDays"`
WritenOffDays int `yaml:"writenOffDays"`
}
type wechatSection struct {
@ -47,9 +59,18 @@ type ssoSection struct {
}
type httpSection struct {
Mode string `yaml:"mode"`
CookieName string `yaml:"cookieName"`
CookieDomain string `yaml:"cookieDomain"`
Mode string `yaml:"mode"`
Session SessionSection `yaml:"session"`
}
type SessionSection struct {
CookieName string `yaml:"cookieName"`
SidLength int `yaml:"sidLength"`
HttpOnly bool `yaml:"httpOnly"`
Domain string `yaml:"domain"`
GcInterval int64 `yaml:"gcInterval"`
CookieLifetime int64 `yaml:"cookieLifetime"`
Storage string `yaml:"storage" description:"mem|db(defualt)"`
}
type ldapSection struct {
@ -129,6 +150,17 @@ func Parse() error {
return err
}
// if Config.HTTP.Session.CookieLifetime == 0 {
// Config.HTTP.Session.CookieLifetime = 24 * 3600
// }
if Config.HTTP.Session.GcInterval == 0 {
Config.HTTP.Session.GcInterval = 60
}
if Config.HTTP.Session.SidLength == 0 {
Config.HTTP.Session.SidLength = 32
}
return nil
}

View File

@ -12,17 +12,31 @@ import (
"github.com/didi/nightingale/src/common/address"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/config"
"github.com/didi/nightingale/src/modules/rdb/session"
)
func shouldStartSession() gin.HandlerFunc {
return func(c *gin.Context) {
sessionStart(c)
c.Next()
sessionUpdate(c)
}
}
func shouldBeLogin() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("username", mustUsername(c))
sessionStart(c)
username := mustUsername(c)
logger.Debugf("set username %s", username)
c.Set("username", username)
c.Next()
sessionUpdate(c)
}
}
func shouldBeRoot() gin.HandlerFunc {
return func(c *gin.Context) {
sessionStart(c)
username := mustUsername(c)
user, err := models.UserGet("username=?", username)
@ -35,6 +49,7 @@ func shouldBeRoot() gin.HandlerFunc {
c.Set("username", username)
c.Set("user", user)
c.Next()
sessionUpdate(c)
}
}
@ -73,7 +88,7 @@ func shouldBeService() gin.HandlerFunc {
}
func mustUsername(c *gin.Context) string {
username := cookieUsername(c)
username := sessionUsername(c)
if username == "" {
username = headerUsername(c)
}
@ -85,10 +100,6 @@ func mustUsername(c *gin.Context) string {
return username
}
func cookieUsername(c *gin.Context) string {
return models.UsernameByUUID(readCookieUser(c))
}
func headerUsername(c *gin.Context) string {
token := c.GetHeader("X-User-Token")
if token == "" {
@ -108,17 +119,52 @@ func headerUsername(c *gin.Context) string {
return ut.Username
}
// ------------
func readCookieUser(c *gin.Context) string {
uuid, err := c.Cookie(config.Config.HTTP.CookieName)
func sessionStart(c *gin.Context) error {
s, err := session.Start(c.Writer, c.Request)
if err != nil {
return ""
return err
}
c.Request = c.Request.WithContext(session.NewContext(c.Request.Context(), s))
return nil
}
func sessionUpdate(c *gin.Context) {
if store, ok := session.FromContext(c.Request.Context()); ok {
err := store.Update(c.Writer)
if err != nil {
logger.Errorf("session update err %s", err)
}
}
}
func sessionDestory(c *gin.Context) (sid string, err error) {
if sid, err = session.Destroy(c.Writer, c.Request); sid != "" {
models.SessionCacheDelete(sid)
}
return uuid
return
}
func writeCookieUser(c *gin.Context, uuid string) {
c.SetCookie(config.Config.HTTP.CookieName, uuid, 3600*24, "/", config.Config.HTTP.CookieDomain, false, true)
func sessionUsername(c *gin.Context) string {
s, ok := session.FromContext(c.Request.Context())
if !ok {
return ""
}
return s.Get("username")
}
func sessionLogin(c *gin.Context, username, remoteAddr string) {
s, ok := session.FromContext(c.Request.Context())
if !ok {
logger.Warningf("session.Start() err not found sessionStore")
return
}
if err := s.Set("username", username); err != nil {
logger.Warningf("session.Set() err %s", err)
return
}
if err := s.Set("remoteAddr", remoteAddr); err != nil {
logger.Warningf("session.Set() err %s", err)
return
}
}

View File

@ -10,25 +10,29 @@ func Config(r *gin.Engine) {
{
notLogin.GET("/ping", ping)
notLogin.GET("/ldap/used", ldapUsed)
notLogin.POST("/auth/login", login)
notLogin.GET("/auth/logout", logout)
notLogin.GET("/ops/global", globalOpsGet)
notLogin.GET("/ops/local", localOpsGet)
notLogin.GET("/roles/global", globalRoleGet)
notLogin.GET("/roles/local", localRoleGet)
notLogin.POST("/users/invite", userInvitePost)
notLogin.GET("/auth/v2/authorize", authAuthorizeV2)
notLogin.GET("/auth/v2/callback", authCallbackV2)
notLogin.GET("/auth/v2/logout", logoutV2)
notLogin.POST("/auth/send-login-code-by-sms", v1SendLoginCodeBySms)
notLogin.POST("/auth/send-login-code-by-email", v1SendLoginCodeByEmail)
notLogin.POST("/auth/send-rst-code-by-sms", sendRstCodeBySms)
notLogin.POST("/auth/send-login-code", sendLoginCode)
notLogin.POST("/auth/send-rst-code", sendRstCode)
notLogin.POST("/auth/rst-password", rstPassword)
notLogin.GET("/auth/captcha", captchaGet)
notLogin.GET("/v2/nodes", nodeGets)
notLogin.GET("/pwd-rules", pwdRulesGet)
}
sessionStarted := r.Group("/api/rdb").Use(shouldStartSession())
{
sessionStarted.POST("/auth/login", login)
sessionStarted.GET("/auth/logout", logout)
sessionStarted.GET("/auth/v2/authorize", authAuthorizeV2)
sessionStarted.GET("/auth/v2/callback", authCallbackV2)
sessionStarted.GET("/auth/v2/logout", logoutV2)
}
hbs := r.Group("/api/hbs")
@ -43,6 +47,14 @@ func Config(r *gin.Engine) {
rootLogin.POST("/configs/smtp/test", smtpTest)
rootLogin.PUT("/configs/smtp", smtpConfigsPut)
rootLogin.GET("/configs/auth", authConfigsGet)
rootLogin.PUT("/configs/auth", authConfigsPut)
rootLogin.POST("/auth/white-list", whiteListPost)
rootLogin.GET("/auth/white-list", whiteListsGet)
rootLogin.GET("/auth/white-list/:id", whiteListGet)
rootLogin.PUT("/auth/white-list/:id", whiteListPut)
rootLogin.DELETE("/auth/white-list/:id", whiteListDel)
rootLogin.GET("/log/login", loginLogGets)
rootLogin.GET("/log/operation", operationLogGets)
@ -83,12 +95,13 @@ func Config(r *gin.Engine) {
userLogin.GET("/self/profile", selfProfileGet)
userLogin.PUT("/self/profile", selfProfilePut)
userLogin.PUT("/self/password", selfPasswordPut)
userLogin.GET("/self/token", selfTokenGets)
userLogin.POST("/self/token", selfTokenPost)
userLogin.PUT("/self/token", selfTokenPut)
userLogin.GET("/self/perms/global", permGlobalOps)
notLogin.PUT("/self/password", selfPasswordPut)
userLogin.GET("/users", userListGet)
userLogin.GET("/users/invite", userInviteGet)
@ -167,7 +180,6 @@ func Config(r *gin.Engine) {
v1.GET("/can-do-node-ops", v1CandoNodeOps)
// 获取用户、团队相关信息
v1.GET("/get-username-by-uuid", v1UsernameGetByUUID)
v1.GET("/get-user-by-uuid", v1UserGetByUUID)
v1.GET("/get-users-by-uuids", v1UserGetByUUIDs)
v1.GET("/get-users-by-ids", v1UserGetByIds)
@ -180,12 +192,16 @@ func Config(r *gin.Engine) {
v1.GET("/users", v1UserListGet)
v1.POST("/login", v1Login)
v1.POST("/send-login-code-by-sms", v1SendLoginCodeBySms)
v1.POST("/send-login-code-by-email", v1SendLoginCodeByEmail)
v1.POST("/send-login-code", sendLoginCode)
// 第三方系统获取某个用户的所有权限点
v1.GET("/perms/global", v1PermGlobalOps)
// session
v1.GET("/sessions/:sid", v1SessionGet)
v1.GET("/sessions/:sid/user", v1SessionGetUser)
v1.DELETE("/sessions/:sid", v1SessionDelete)
// 第三方系统同步权限表的数据
v1.GET("/table/sync/role-operation", v1RoleOperationGets)
v1.GET("/table/sync/role-global-user", v1RoleGlobalUserGets)

View File

@ -14,10 +14,13 @@ import (
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"github.com/toolkits/pkg/file"
"github.com/toolkits/pkg/logger"
"github.com/toolkits/pkg/str"
"github.com/didi/nightingale/src/common/dataobj"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/auth"
"github.com/didi/nightingale/src/modules/rdb/cache"
"github.com/didi/nightingale/src/modules/rdb/config"
"github.com/didi/nightingale/src/modules/rdb/redisc"
"github.com/didi/nightingale/src/modules/rdb/ssoc"
@ -27,7 +30,6 @@ var (
loginCodeSmsTpl *template.Template
loginCodeEmailTpl *template.Template
errUnsupportCaptcha = errors.New("unsupported captcha")
errInvalidAnswer = errors.New("Invalid captcha answer")
// TODO: set false
debug = true
@ -76,49 +78,49 @@ func init() {
}
}
// login for UI
func login(c *gin.Context) {
var f loginInput
bind(c, &f)
f.validate()
var in loginInput
bind(c, &in)
in.RemoteAddr = c.ClientIP()
if config.Config.Captcha {
c, err := models.CaptchaGet("captcha_id=?", f.CaptchaId)
dangerous(err)
if strings.ToLower(c.Answer) != strings.ToLower(f.Answer) {
dangerous(errInvalidAnswer)
err := func() error {
if err := in.Validate(); err != nil {
return err
}
}
user, err := authLogin(f)
dangerous(err)
if config.Config.Auth.Captcha {
c, err := models.CaptchaGet("captcha_id=?", in.CaptchaId)
if err != nil {
return _e("Unable to get captcha")
}
if strings.ToLower(c.Answer) != strings.ToLower(in.Answer) {
return _e("Invalid captcha answer")
}
}
writeCookieUser(c, user.UUID)
user, err := authLogin(in.v1LoginInput())
if err != nil {
logger.Debugf("login error %s", err)
return err
}
renderMessage(c, "")
go models.LoginLogNew(user.Username, c.ClientIP(), "in")
sessionLogin(c, user.Username, in.RemoteAddr)
return nil
}()
renderMessage(c, err)
}
func logout(c *gin.Context) {
func() {
uuid := readCookieUser(c)
if uuid == "" {
return
}
username := models.UsernameByUUID(uuid)
username := sessionUsername(c)
if username == "" {
return
}
writeCookieUser(c, "")
go models.LoginLogNew(username, c.ClientIP(), "out")
sessionDestory(c)
models.LoginLogNew(username, c.ClientIP(), "out", nil)
}()
if config.Config.SSO.Enable {
redirect := queryStr(c, "redirect", "/")
c.Redirect(302, ssoc.LogoutLocation(redirect))
return
}
if redirect := queryStr(c, "redirect", ""); redirect != "" {
c.Redirect(302, redirect)
return
@ -128,27 +130,31 @@ func logout(c *gin.Context) {
}
type authRedirect struct {
Redirect string `json:"redirect"`
Msg string `json:"msg"`
Redirect string `json:"redirect"`
User *models.User `json:"user"`
Msg string `json:"msg"`
}
func authAuthorizeV2(c *gin.Context) {
redirect := queryStr(c, "redirect", "/")
ret := &authRedirect{Redirect: redirect}
resp, err := func() (*authRedirect, error) {
redirect := queryStr(c, "redirect", "/")
username := cookieUsername(c)
if username != "" { // alread login
renderData(c, ret, nil)
return
}
username := sessionUsername(c)
if username != "" { // alread login
return &authRedirect{Redirect: redirect}, nil
}
var err error
if config.Config.SSO.Enable {
ret.Redirect, err = ssoc.Authorize(redirect)
} else {
ret.Redirect = "/login"
}
renderData(c, ret, err)
if !config.Config.SSO.Enable {
return &authRedirect{Redirect: "/login"}, nil
}
if redirect, err := ssoc.Authorize(redirect); err != nil {
return nil, err
} else {
return &authRedirect{Redirect: redirect}, nil
}
}()
renderData(c, resp, err)
}
func authCallbackV2(c *gin.Context) {
@ -158,19 +164,28 @@ func authCallbackV2(c *gin.Context) {
ret := &authRedirect{Redirect: redirect}
if code == "" && redirect != "" {
logger.Debugf("sso.callback() can't get code and redirect is not set")
renderData(c, ret, nil)
return
}
var user *models.User
var err error
ret.Redirect, user, err = ssoc.Callback(code, state)
ret.Redirect, ret.User, err = ssoc.Callback(code, state)
if err != nil {
logger.Debugf("sso.callback() error %s", err)
renderData(c, ret, err)
return
}
writeCookieUser(c, user.UUID)
if redirect := auth.ChangePasswordRedirect(ret.User, ret.Redirect); redirect != "" {
logger.Debugf("sso.callback() redirect to changePassword %s", redirect)
ret.Redirect = redirect
renderData(c, ret, nil)
return
}
logger.Debugf("sso.callback() successfully, set username %s", ret.User.Username)
sessionLogin(c, ret.User.Username, c.ClientIP())
renderData(c, ret, nil)
}
@ -178,131 +193,197 @@ func logoutV2(c *gin.Context) {
redirect := queryStr(c, "redirect", "")
ret := &authRedirect{Redirect: redirect}
uuid := readCookieUser(c)
if uuid == "" {
renderData(c, ret, nil)
return
}
username := models.UsernameByUUID(uuid)
username := sessionUsername(c)
if username == "" {
renderData(c, ret, nil)
return
}
writeCookieUser(c, "")
sessionDestory(c)
ret.Msg = "logout successfully"
if config.Config.SSO.Enable {
if redirect == "" {
redirect = "/"
}
ret.Redirect = ssoc.LogoutLocation(redirect)
}
renderData(c, ret, nil)
go models.LoginLogNew(username, c.ClientIP(), "out")
models.LoginLogNew(username, c.ClientIP(), "out", nil)
}
type loginInput struct {
Username string `json:"username"`
Password string `json:"password"`
Phone string `json:"phone"`
Email string `json:"email"`
Code string `json:"code"`
CaptchaId string `json:"captcha_id"`
Answer string `json:"answer" description:"captcha answer"`
Type string `json:"type" description:"sms-code|email-code|password|ldap"`
RemoteAddr string `json:"remote_addr" description:"use for server account(v1)"`
IsLDAP int `json:"is_ldap" description:"deprecated"`
Username string `json:"username"`
Password string `json:"password"`
CaptchaId string `json:"captcha_id"`
Answer string `json:"answer" description:"captcha answer"`
Type string `json:"type" description:"sms-code|email-code|password|ldap"`
Args []string `json:"args" description:""`
RemoteAddr string `json:"remote_addr" description:"use for server account(v1)"`
IsLDAP int `json:"is_ldap" description:"deprecated"`
}
func (f *loginInput) validate() {
if f.IsLDAP == 1 {
f.Type = models.LOGIN_T_LDAP
func (p *loginInput) Validate() error {
if p.IsLDAP == 1 {
p.Type = models.LOGIN_T_LDAP
}
if f.Type == "" {
f.Type = models.LOGIN_T_PWD
if p.Type == "" {
p.Type = models.LOGIN_T_PWD
}
if f.Type == models.LOGIN_T_PWD {
if str.Dangerous(f.Username) {
bomb("%s invalid", f.Username)
}
if len(f.Username) > 64 {
bomb("%s too long > 64", f.Username)
if p.Type == models.LOGIN_T_PWD || p.Type == models.LOGIN_T_LDAP {
if len(p.Args) == 0 {
if str.Dangerous(p.Username) {
return _e("Username %s is invalid", p.Username)
}
if len(p.Username) > 64 {
return _e("Username %s too long > 64", p.Username)
}
p.Args = []string{p.Username, p.Password}
}
}
if len(p.Args) == 0 {
return _e("Unable to get login arguments")
}
return nil
}
func (p *loginInput) v1LoginInput() *v1LoginInput {
return &v1LoginInput{
Type: p.Type,
Args: p.Args,
RemoteAddr: p.RemoteAddr,
}
}
type v1LoginInput struct {
Type string `param:"data" json:"type"`
Args []string `param:"data" json:"args"`
RemoteAddr string `param:"data" json:"remote_addr"`
}
func (p *v1LoginInput) Validate() error {
if p.Type == "" {
p.Type = models.LOGIN_T_PWD
}
if len(p.Args) == 0 {
return _e("Unable to get login arguments")
}
return nil
}
// v1Login called by sso.rdb module
func v1Login(c *gin.Context) {
var f loginInput
bind(c, &f)
var in v1LoginInput
bind(c, &in)
user, err := authLogin(f)
renderData(c, *user, err)
go models.LoginLogNew(user.Username, f.RemoteAddr, "in")
user, err := authLogin(&in)
renderData(c, user, err)
}
// authLogin called by /v1/rdb/login, /api/rdb/auth/login
func authLogin(in loginInput) (user *models.User, err error) {
func authLogin(in *v1LoginInput) (user *models.User, err error) {
if err = in.Validate(); err != nil {
return
}
if err := auth.WhiteListAccess(in.RemoteAddr); err != nil {
return nil, _e("Deny Access from %s with whitelist control", in.RemoteAddr)
}
defer func() {
models.LoginLogNew(in.Args[0], in.RemoteAddr, "in", err)
}()
switch strings.ToLower(in.Type) {
case models.LOGIN_T_LDAP:
return models.LdapLogin(in.Username, in.Password)
user, err = models.LdapLogin(in.Args[0], in.Args[1])
case models.LOGIN_T_PWD:
return models.PassLogin(in.Username, in.Password)
user, err = models.PassLogin(in.Args[0], in.Args[1])
case models.LOGIN_T_SMS:
return models.SmsCodeLogin(in.Phone, in.Code)
user, err = models.SmsCodeLogin(in.Args[0], in.Args[1])
case models.LOGIN_T_EMAIL:
return models.EmailCodeLogin(in.Email, in.Code)
user, err = models.EmailCodeLogin(in.Args[0], in.Args[1])
default:
return nil, fmt.Errorf("invalid login type %s", in.Type)
err = _e("Invalid login type %s", in.Type)
}
if err = auth.PostLogin(user, err); err != nil {
return nil, err
}
return user, nil
}
type v1SendLoginCodeBySmsInput struct {
Phone string `json:"phone"`
type sendCodeInput struct {
Type string `json:"type" description:"sms-code, email-code"`
Arg string `json:"arg"`
}
func v1SendLoginCodeBySms(c *gin.Context) {
var f v1SendLoginCodeBySmsInput
bind(c, &f)
func (p *sendCodeInput) Validate() error {
if p.Type == "" {
return _e("Unable to get type, sms-code | email-code")
}
if p.Arg == "" {
return _e("Unable to get code arg")
}
return nil
}
func sendLoginCode(c *gin.Context) {
var in sendCodeInput
bind(c, &in)
msg, err := func() (string, error) {
if !config.Config.Redis.Enable {
return "", fmt.Errorf("sms sender is disabled")
if err := in.Validate(); err != nil {
return "", err
}
phone := f.Phone
user, _ := models.UserGet("phone=?", phone)
if user == nil {
return "", fmt.Errorf("phone %s dose not exist", phone)
if !config.Config.Redis.Enable {
return "", _e("sms/email sender is disabled")
}
if err := in.Validate(); err != nil {
return "", err
}
// general a random code and add cache
code := fmt.Sprintf("%06d", rand.Intn(1000000))
loginCode := &models.LoginCode{
Username: user.Username,
Code: code,
LoginType: models.LOGIN_T_SMS,
LoginType: models.LOGIN_T_LOGIN,
CreatedAt: time.Now().Unix(),
}
var (
user *models.User
buf bytes.Buffer
queueName string
)
switch in.Type {
case models.LOGIN_T_SMS:
user, _ = models.UserGet("phone=?", in.Arg)
if err := loginCodeSmsTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
queueName = config.SMS_QUEUE_NAME
case models.LOGIN_T_EMAIL:
user, _ = models.UserGet("email=?", in.Arg)
if err := loginCodeEmailTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
queueName = config.MAIL_QUEUE_NAME
default:
return "", _e("Invalid code type %s", in.Type)
}
if user == nil {
return "", _e("Cannot find the user by %s", in.Arg)
}
loginCode.Username = user.Username
if err := loginCode.Save(); err != nil {
return "", err
}
var buf bytes.Buffer
if err := loginCodeSmsTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
if err := redisc.Write(&dataobj.Message{
Tos: []string{phone},
Content: buf.String(),
}, config.SMS_QUEUE_NAME); err != nil {
if err := redisc.Write(&dataobj.Message{Tos: []string{in.Arg}, Content: buf.String()}, queueName); err != nil {
return "", err
}
@ -316,100 +397,65 @@ func v1SendLoginCodeBySms(c *gin.Context) {
renderData(c, msg, err)
}
type v1SendLoginCodeByEmailInput struct {
Email string `json:"email"`
}
func v1SendLoginCodeByEmail(c *gin.Context) {
var f v1SendLoginCodeByEmailInput
bind(c, &f)
func sendRstCode(c *gin.Context) {
var in sendCodeInput
bind(c, &in)
logger.Debugf("rst code input %#v", in)
msg, err := func() (string, error) {
if !config.Config.Redis.Enable {
return "", fmt.Errorf("mail sender is disabled")
if err := in.Validate(); err != nil {
return "", err
}
email := f.Email
user, _ := models.UserGet("email=?", email)
if user == nil {
return "", fmt.Errorf("email %s dose not exist", email)
if !config.Config.Redis.Enable {
return "", _e("email/sms sender is disabled")
}
if err := in.Validate(); err != nil {
return "", err
}
// general a random code and add cache
code := fmt.Sprintf("%06d", rand.Intn(1000000))
loginCode := &models.LoginCode{
Username: user.Username,
Code: code,
LoginType: models.LOGIN_T_EMAIL,
CreatedAt: time.Now().Unix(),
}
if err := loginCode.Save(); err != nil {
return "", err
}
var buf bytes.Buffer
if err := loginCodeEmailTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
if err := redisc.Write(&dataobj.Message{
Tos: []string{email},
Content: buf.String(),
}, config.SMS_QUEUE_NAME); err != nil {
return "", err
}
if debug {
return fmt.Sprintf("[debug]: %s", buf.String()), nil
}
return "successed", nil
}()
renderData(c, msg, err)
}
type sendRstCodeBySmsInput struct {
Username string `json:"username"`
Phone string `json:"phone"`
}
func sendRstCodeBySms(c *gin.Context) {
var f sendRstCodeBySmsInput
bind(c, &f)
msg, err := func() (string, error) {
if !config.Config.Redis.Enable {
return "", fmt.Errorf("sms sender is disabled")
}
phone := f.Phone
user, _ := models.UserGet("username=? and phone=?", f.Username, phone)
if user == nil {
return "", fmt.Errorf("user %s phone %s dose not exist", f.Username, phone)
}
// general a random code and add cache
code := fmt.Sprintf("%06d", rand.Intn(1000000))
loginCode := &models.LoginCode{
Username: user.Username,
Code: code,
LoginType: models.LOGIN_T_RST,
CreatedAt: time.Now().Unix(),
}
var (
user *models.User
buf bytes.Buffer
queueName string
)
switch in.Type {
case models.LOGIN_T_SMS:
user, _ = models.UserGet("phone=?", in.Arg)
if err := loginCodeSmsTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
queueName = config.SMS_QUEUE_NAME
case models.LOGIN_T_EMAIL:
user, _ = models.UserGet("email=?", in.Arg)
if err := loginCodeEmailTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
queueName = config.MAIL_QUEUE_NAME
default:
return "", _e("Invalid code type %s", in.Type)
}
if user == nil {
return "", _e("Cannot find the user by %s", in.Arg)
}
loginCode.Username = user.Username
if err := loginCode.Save(); err != nil {
return "", err
}
var buf bytes.Buffer
if err := loginCodeSmsTpl.Execute(&buf, loginCode); err != nil {
return "", err
}
if err := redisc.Write(&dataobj.Message{
Tos: []string{phone},
Content: buf.String(),
}, config.SMS_QUEUE_NAME); err != nil {
if err := redisc.Write(&dataobj.Message{Tos: []string{in.Arg}, Content: buf.String()}, queueName); err != nil {
return "", err
}
@ -418,17 +464,29 @@ func sendRstCodeBySms(c *gin.Context) {
}
return "successed", nil
}()
renderData(c, msg, err)
}
type rstPasswordInput struct {
Username string `json:"username"`
Phone string `json:"phone"`
Type string `json:"type"`
Arg string `json:"arg"`
Code string `json:"code"`
Password string `json:"password"`
Type string `json:"type"`
DryRun bool `json:"dryRun"`
}
func (p *rstPasswordInput) Validate() error {
if p.Type == "" {
return _e("Unable to get type, sms-code | email-code")
}
if p.Arg == "" {
return _e("Unable to get code arg")
}
if !p.DryRun && p.Password == "" {
return _e("Unable to get password")
}
return nil
}
func rstPassword(c *gin.Context) {
@ -436,39 +494,43 @@ func rstPassword(c *gin.Context) {
bind(c, &in)
err := func() error {
user, _ := models.UserGet("username=? and phone=?", in.Username, in.Phone)
if user == nil {
return fmt.Errorf("user's phone not exist")
if err := in.Validate(); err != nil {
return err
}
lc, err := models.LoginCodeGet("username=? and code=? and login_type=?",
user.Username, in.Code, models.LOGIN_T_RST)
var user *models.User
switch in.Type {
case models.LOGIN_T_SMS:
user, _ = models.UserGet("phone=?", in.Arg)
case models.LOGIN_T_EMAIL:
user, _ = models.UserGet("email=?", in.Arg)
default:
return fmt.Errorf("invalid type %s", in.Type)
}
if user == nil {
return _e("Cannot find the user by %s", in.Arg)
}
lc, err := models.LoginCodeGet("code=? and login_type=?", in.Code, models.LOGIN_T_RST)
if err != nil {
return fmt.Errorf("invalid code")
return _e("Invalid code")
}
if time.Now().Unix()-lc.CreatedAt > models.LOGIN_EXPIRES_IN {
return fmt.Errorf("the code has expired")
return _e("The code has expired")
}
if in.Type == "verify-code" {
if in.DryRun {
return nil
}
defer lc.Del()
// update password
if user.Password, err = models.CryptoPass(in.Password); err != nil {
if err := auth.ChangePassword(user, in.Password); err != nil {
return err
}
if err = checkPassword(in.Password); err != nil {
return err
}
if err = user.Update("password"); err != nil {
return err
}
lc.Del()
return nil
}()
@ -481,7 +543,7 @@ func rstPassword(c *gin.Context) {
func captchaGet(c *gin.Context) {
ret, err := func() (*models.Captcha, error) {
if !config.Config.Captcha {
if !config.Config.Auth.Captcha {
return nil, errUnsupportCaptcha
}
@ -516,3 +578,129 @@ func authSettings(c *gin.Context) {
Sso: config.Config.SSO.Enable,
}, nil)
}
func authConfigsGet(c *gin.Context) {
config, err := models.AuthConfigGet()
renderData(c, config, err)
}
func authConfigsPut(c *gin.Context) {
var in models.AuthConfig
bind(c, &in)
err := models.AuthConfigSet(&in)
renderData(c, "", err)
}
type createWhiteListInput struct {
StartIp string `json:"startIp"`
EndIp string `json:"endIp"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
}
func whiteListPost(c *gin.Context) {
var in createWhiteListInput
bind(c, &in)
username := loginUser(c).Username
ts := time.Now().Unix()
wl := models.WhiteList{
StartIp: in.StartIp,
EndIp: in.EndIp,
StartTime: in.StartTime,
EndTime: in.EndTime,
CreatedAt: ts,
UpdatedAt: ts,
Creator: username,
Updater: username,
}
if err := wl.Validate(); err != nil {
bomb("Invalid arguments %s", err)
}
dangerous(wl.Save())
renderData(c, gin.H{"id": wl.Id}, nil)
}
func whiteListsGet(c *gin.Context) {
limit := queryInt(c, "limit", 20)
query := queryStr(c, "query", "")
total, err := models.WhiteListTotal(query)
dangerous(err)
list, err := models.WhiteListGets(query, limit, offset(c, limit))
dangerous(err)
renderData(c, gin.H{
"list": list,
"total": total,
}, nil)
}
func whiteListGet(c *gin.Context) {
id := urlParamInt64(c, "id")
ret, err := models.WhiteListGet("id=?", id)
renderData(c, ret, err)
}
type updateWhiteListInput struct {
StartIp string `json:"startIp"`
EndIp string `json:"endIp"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
}
func whiteListPut(c *gin.Context) {
var in updateWhiteListInput
bind(c, &in)
wl, err := models.WhiteListGet("id=?", urlParamInt64(c, "id"))
if err != nil {
bomb("Cannot found white list")
}
wl.StartIp = in.StartIp
wl.EndIp = in.EndIp
wl.StartTime = in.StartTime
wl.EndTime = in.EndTime
wl.UpdatedAt = time.Now().Unix()
wl.Updater = loginUser(c).Username
if err := wl.Validate(); err != nil {
bomb("Invalid arguments %s", err)
}
renderMessage(c, wl.Update("start_ip", "end_ip", "start_time", "end_time", "updated_at", "updater"))
}
func whiteListDel(c *gin.Context) {
wl, err := models.WhiteListGet("id=?", urlParamInt64(c, "id"))
dangerous(err)
renderMessage(c, wl.Del())
}
func v1SessionGet(c *gin.Context) {
sess, err := models.SessionGetWithCache(urlParamStr(c, "sid"))
renderData(c, sess, err)
}
func v1SessionGetUser(c *gin.Context) {
user, err := models.SessionGetUserWithCache(urlParamStr(c, "sid"))
renderData(c, user, err)
}
func v1SessionDelete(c *gin.Context) {
sid := urlParamStr(c, "sid")
logger.Debugf("session del sid %s", sid)
renderMessage(c, models.SessionDel(sid))
}
// pwdRulesGet return pwd rules
func pwdRulesGet(c *gin.Context) {
cf := cache.AuthConfig()
renderData(c, cf.PwdRules(), nil)
}

View File

@ -4,11 +4,10 @@ import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/errors"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/toolkits/i18n"
"github.com/gin-gonic/gin"
"github.com/toolkits/pkg/errors"
)
func dangerous(v interface{}) {
@ -133,69 +132,10 @@ func renderZeroPage(c *gin.Context) {
}, nil)
}
// ------------
type idsForm struct {
Ids []int64 `json:"ids"`
}
func checkPassword(passwd string) error {
indNum := [4]int{0, 0, 0, 0}
spCode := []byte{'!', '@', '#', '$', '%', '^', '&', '*', '_', '-', '~', '.', ',', '<', '>', '/', ';', ':', '|', '?', '+', '='}
if len(passwd) < 6 {
return fmt.Errorf("password too short")
}
passwdByte := []byte(passwd)
for _, i := range passwdByte {
if i >= 'A' && i <= 'Z' {
indNum[0] = 1
continue
}
if i >= 'a' && i <= 'z' {
indNum[1] = 1
continue
}
if i >= '0' && i <= '9' {
indNum[2] = 1
continue
}
has := false
for _, s := range spCode {
if i == s {
indNum[3] = 1
has = true
break
}
}
if !has {
return fmt.Errorf("character: %s not supported", string(i))
}
}
codeCount := 0
for _, i := range indNum {
codeCount += i
}
if codeCount < 4 {
return fmt.Errorf("password too simple")
}
return nil
}
// ------------
func loginUsername(c *gin.Context) string {
value, has := c.Get("username")
if !has {
@ -280,3 +220,11 @@ func Node(id int64) *models.Node {
return node
}
func _e(format string, a ...interface{}) error {
return fmt.Errorf(i18n.Sprintf(format, a...))
}
func _s(format string, a ...interface{}) string {
return i18n.Sprintf(format, a...)
}

View File

@ -23,12 +23,14 @@ func heartBeat(c *gin.Context) {
Module: rev.Module,
RPCPort: rev.RPCPort,
HTTPPort: rev.HTTPPort,
Region: rev.Region,
TS: now,
}
errors.Dangerous(instance.Add())
} else {
instance.TS = now
instance.HTTPPort = rev.HTTPPort
instance.Region = rev.Region
errors.Dangerous(instance.Update())
}

View File

@ -2,6 +2,7 @@ package http
import (
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/auth"
"github.com/didi/nightingale/src/modules/rdb/config"
"github.com/gin-gonic/gin"
)
@ -35,28 +36,32 @@ func selfProfilePut(c *gin.Context) {
}
type selfPasswordForm struct {
OldPass string `json:"oldpass" binding:"required"`
NewPass string `json:"newpass" binding:"required"`
Username string `json:"username" binding:"required"`
OldPass string `json:"oldpass" binding:"required"`
NewPass string `json:"newpass" binding:"required"`
}
func selfPasswordPut(c *gin.Context) {
var f selfPasswordForm
bind(c, &f)
dangerous(checkPassword(f.NewPass))
oldpass, err := models.CryptoPass(f.OldPass)
dangerous(err)
err := func() error {
user, err := models.UserMustGet("username=?", f.Username)
if err != nil {
return err
}
oldpass, err := models.CryptoPass(f.OldPass)
if err != nil {
return err
}
if user.Password != oldpass {
return _e("Incorrect old password")
}
newpass, err := models.CryptoPass(f.NewPass)
dangerous(err)
return auth.ChangePassword(user, f.NewPass)
}()
user := loginUser(c)
if user.Password != oldpass {
bomb("old password error")
}
user.Password = newpass
renderMessage(c, user.Update("password"))
renderMessage(c, err)
}
func selfTokenGets(c *gin.Context) {

View File

@ -1,6 +1,7 @@
package http
import (
"encoding/json"
"fmt"
"strings"
"time"
@ -9,6 +10,7 @@ import (
"github.com/toolkits/pkg/str"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/auth"
)
// 通讯录,只要登录用户就可以看,超管要修改某个用户的信息,也是调用这个接口获取列表先
@ -64,21 +66,25 @@ func userAddPost(c *gin.Context) {
var f userProfileForm
bind(c, &f)
dangerous(checkPassword(f.Password))
dangerous(auth.CheckPassword(f.Password))
pass, err := models.CryptoPass(f.Password)
dangerous(err)
now := time.Now().Unix()
b, _ := json.Marshal([]string{pass})
u := models.User{
Username: f.Username,
Password: pass,
Dispname: f.Dispname,
Phone: f.Phone,
Email: f.Email,
Im: f.Im,
IsRoot: f.IsRoot,
LeaderId: f.LeaderId,
UUID: models.GenUUIDForUser(f.Username),
Username: f.Username,
Password: pass,
Passwords: string(b),
Dispname: f.Dispname,
Phone: f.Phone,
Email: f.Email,
Im: f.Im,
IsRoot: f.IsRoot,
LeaderId: f.LeaderId,
UpdatedAt: now,
UUID: models.GenUUIDForUser(f.Username),
}
if f.LeaderId != 0 {
@ -144,19 +150,17 @@ func userProfilePut(c *gin.Context) {
target.IsRoot = f.IsRoot
}
if f.Typ != target.Typ {
arr = append(arr, fmt.Sprintf("typ: %d -> %d", target.Typ, f.Typ))
target.Typ = f.Typ
if f.Typ != target.Type {
arr = append(arr, fmt.Sprintf("typ: %d -> %d", target.Type, f.Typ))
target.Type = f.Typ
}
if f.Status != target.Status {
arr = append(arr, fmt.Sprintf("typ: %d -> %d", target.Status, f.Status))
target.Status = f.Status
}
if f.Status != target.Status {
arr = append(arr, fmt.Sprintf("typ: %s -> %s", target.Status, f.Status))
target.Status = f.Status
if target.Status == models.USER_S_ACTIVE {
target.LoginErrNum = 0
}
}
if f.Organization != target.Organization {
@ -164,7 +168,9 @@ func userProfilePut(c *gin.Context) {
target.Organization = f.Organization
}
err := target.Update("dispname", "phone", "email", "im", "is_root", "leader_id", "leader_name", "typ", "status", "organization")
target.UpdatedAt = time.Now().Unix()
err := target.Update("dispname", "phone", "email", "im", "is_root", "leader_id", "leader_name", "typ", "status", "organization", "login_err_num", "updated_at")
if err == nil && len(arr) > 0 {
content := strings.Join(arr, "")
go models.OperationLogNew(root.Username, "user", target.Id, fmt.Sprintf("UserModify %s %s", target.Username, content))
@ -182,17 +188,13 @@ func userPasswordPut(c *gin.Context) {
var f userPasswordForm
bind(c, &f)
dangerous(checkPassword(f.Password))
dangerous(auth.CheckPassword(f.Password))
target := User(urlParamInt64(c, "id"))
user := User(urlParamInt64(c, "id"))
err := auth.ChangePassword(user, f.Password)
pass, err := models.CryptoPass(f.Password)
dangerous(err)
target.Password = pass
err = target.Update("password")
if err == nil {
go models.OperationLogNew(root.Username, "user", target.Id, fmt.Sprintf("UserChangePassword %s", target.Username))
go models.OperationLogNew(root.Username, "user", user.Id, fmt.Sprintf("UserChangePassword %s", user.Username))
}
renderMessage(c, err)
}
@ -221,10 +223,6 @@ func userDel(c *gin.Context) {
renderMessage(c, err)
}
func v1UsernameGetByUUID(c *gin.Context) {
renderData(c, models.UsernameByUUID(queryStr(c, "uuid")), nil)
}
func v1UserGetByUUID(c *gin.Context) {
user, err := models.UserGet("uuid=?", queryStr(c, "uuid"))
dangerous(err)
@ -302,26 +300,40 @@ type userInviteForm struct {
func userInvitePost(c *gin.Context) {
var f userInviteForm
bind(c, &f)
dangerous(checkPassword(f.Password))
inv, err := models.InviteGet("token=?", f.Token)
dangerous(err)
err := func() error {
if err := auth.CheckPassword(f.Password); err != nil {
return err
}
if inv.Expire < time.Now().Unix() {
dangerous("invite url already expired")
}
inv, err := models.InviteGet("token=?", f.Token)
if err != nil {
return err
}
u := models.User{
Username: f.Username,
Dispname: f.Dispname,
Phone: f.Phone,
Email: f.Email,
Im: f.Im,
UUID: models.GenUUIDForUser(f.Username),
}
if inv.Expire < time.Now().Unix() {
return _e("invite url already expired")
}
u.Password, err = models.CryptoPass(f.Password)
dangerous(err)
u := models.User{
Username: f.Username,
Dispname: f.Dispname,
Phone: f.Phone,
Email: f.Email,
Im: f.Im,
UUID: models.GenUUIDForUser(f.Username),
}
renderMessage(c, u.Save())
u.Password, err = models.CryptoPass(f.Password)
if err != nil {
return err
}
if err = u.Save(); err != nil {
return err
}
return inv.Del()
}()
renderMessage(c, err)
}

View File

@ -14,11 +14,14 @@ import (
"github.com/didi/nightingale/src/common/loggeri"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/auth"
"github.com/didi/nightingale/src/modules/rdb/cache"
"github.com/didi/nightingale/src/modules/rdb/config"
"github.com/didi/nightingale/src/modules/rdb/cron"
"github.com/didi/nightingale/src/modules/rdb/http"
"github.com/didi/nightingale/src/modules/rdb/rabbitmq"
"github.com/didi/nightingale/src/modules/rdb/redisc"
"github.com/didi/nightingale/src/modules/rdb/session"
"github.com/didi/nightingale/src/modules/rdb/ssoc"
"github.com/didi/nightingale/src/toolkits/i18n"
)
@ -72,6 +75,12 @@ func main() {
// 初始化 rabbitmq 处理部分异步逻辑
rabbitmq.Init()
cache.Start()
session.Init()
auth.Init(config.Config.Auth.ExtraMode)
auth.Start()
go cron.ConsumeMail()
go cron.ConsumeSms()
go cron.ConsumeVoice()
@ -102,6 +111,8 @@ func endingProc() {
http.Shutdown()
redisc.CloseRedis()
rabbitmq.Shutdown()
session.Stop()
cache.Stop()
fmt.Println("process stopped successfully")
}

View File

@ -0,0 +1,288 @@
package session
import (
"context"
"fmt"
"net/http"
"net/url"
"sync"
"time"
"github.com/didi/nightingale/src/models"
"github.com/didi/nightingale/src/modules/rdb/config"
"github.com/google/uuid"
"github.com/toolkits/pkg/logger"
)
type storage interface {
all() int
get(sid string) (*models.Session, error)
insert(*models.Session) error
del(sid string) error
update(*models.Session) error
}
var (
DefaultSession *Manager
)
func Init() {
var err error
DefaultSession, err = StartSession(&config.Config.HTTP.Session)
if err != nil {
panic(err)
}
}
func Stop() {
DefaultSession.StopGC()
}
func Start(w http.ResponseWriter, r *http.Request) (store *SessionStore, err error) {
return DefaultSession.Start(w, r)
}
func Destroy(w http.ResponseWriter, r *http.Request) (string, error) {
return DefaultSession.Destroy(w, r)
}
func Get(sid string) (*SessionStore, error) {
return DefaultSession.Get(sid)
}
func Exist(sid string) bool {
return DefaultSession.Exist(sid)
}
func All() int {
return DefaultSession.All()
}
func StartSession(cf *config.SessionSection, opts_ ...Option) (*Manager, error) {
opts := &options{}
for _, opt := range opts_ {
opt.apply(opts)
}
if opts.ctx == nil {
opts.ctx, opts.cancel = context.WithCancel(context.Background())
}
var storage storage
var err error
if cf.Storage == "mem" {
storage, err = newMemStorage(cf, opts)
} else {
storage, err = newDbStorage(cf, opts)
}
if err != nil {
return nil, err
}
return &Manager{
storage: storage,
options: opts,
config: cf,
}, nil
}
type Manager struct {
storage
*options
config *config.SessionSection
}
// SessionStart generate or read the session id from http request.
// if session id exists, return SessionStore with this id.
func (p *Manager) Start(w http.ResponseWriter, r *http.Request) (store *SessionStore, err error) {
var sid string
if sid, err = p.getSid(r); err != nil {
return
}
if sid != "" {
if store, err := p.getSessionStore(sid, false); err == nil {
return store, nil
}
}
// Generate a new session
sid = uuid.New().String()
store, err = p.getSessionStore(sid, true)
if err != nil {
return nil, err
}
cookie := &http.Cookie{
Name: p.config.CookieName,
Value: url.QueryEscape(sid),
Path: "/",
HttpOnly: p.config.HttpOnly,
Domain: p.config.Domain,
}
if p.config.CookieLifetime > 0 {
cookie.MaxAge = int(p.config.CookieLifetime)
cookie.Expires = time.Now().Add(time.Duration(p.config.CookieLifetime) * time.Second)
}
http.SetCookie(w, cookie)
r.AddCookie(cookie)
return
}
func (p *Manager) StopGC() {
if p.cancel != nil {
p.cancel()
}
}
func (p *Manager) Destroy(w http.ResponseWriter, r *http.Request) (string, error) {
cookie, err := r.Cookie(p.config.CookieName)
if err != nil || cookie.Value == "" {
return "", fmt.Errorf("Have not login yet")
}
sid, _ := url.QueryUnescape(cookie.Value)
logger.Debugf("session Destory sid %s", sid)
p.del(sid)
cookie = &http.Cookie{Name: p.config.CookieName,
Path: "/",
HttpOnly: p.config.HttpOnly,
Expires: time.Now(),
MaxAge: -1}
http.SetCookie(w, cookie)
return sid, nil
}
func (p *Manager) Get(sid string) (*SessionStore, error) {
return p.getSessionStore(sid, true)
}
func (p *Manager) Exist(sid string) bool {
_, err := p.get(sid)
return err == nil
}
// All count values in mysql session
func (p *Manager) All() int {
return p.all()
}
func (p *Manager) getSid(r *http.Request) (sid string, err error) {
var cookie *http.Cookie
cookie, err = r.Cookie(p.config.CookieName)
if err != nil || cookie.Value == "" {
return sid, nil
}
return url.QueryUnescape(cookie.Value)
}
func (p *Manager) getSessionStore(sid string, create bool) (*SessionStore, error) {
sc, err := p.get(sid)
if sc == nil && create {
ts := time.Now().Unix()
sc = &models.Session{
Sid: sid,
CreatedAt: ts,
UpdatedAt: ts,
}
err = p.insert(sc)
}
if err != nil {
return nil, err
}
return &SessionStore{manager: p, session: sc}, nil
}
// SessionStore mysql session store
type SessionStore struct {
sync.RWMutex
session *models.Session
manager *Manager
}
// Set value in mysql session.
// it is temp value in map.
func (p *SessionStore) Set(k, v string) error {
p.Lock()
defer p.Unlock()
switch k {
case "username":
p.session.Username = v
case "remoteAddr":
p.session.RemoteAddr = v
default:
fmt.Errorf("unsupported session field %s", k)
}
return nil
}
// Get value from mysql session
func (p *SessionStore) Get(k string) string {
p.RLock()
defer p.RUnlock()
switch k {
case "username":
return p.session.Username
case "remoteAddr":
return p.session.RemoteAddr
default:
return ""
}
}
func (p *SessionStore) CreatedAt() int64 {
return p.session.CreatedAt
}
// Delete value in mysql session
func (p *SessionStore) Delete(k string) error {
return p.Set(k, "")
}
// Reset clear all values in mysql session
func (p *SessionStore) Reset() error {
p.Lock()
defer p.Unlock()
p.session.Username = ""
p.session.RemoteAddr = ""
return nil
}
// Sid get session id of this mysql session store
func (p *SessionStore) Sid() string {
return p.session.Sid
}
func (p *SessionStore) Update(w http.ResponseWriter) error {
p.session.UpdatedAt = time.Now().Unix()
return p.manager.update(p.session)
}
const sessionKey = "context-session-key"
type contextKeyT string
var contextKey = contextKeyT("session")
/*
ctx := NewContext(req.Context(), p)
req = req.WithContext(ctx)
*/
// NewContext returns a copy of the parent context
// and associates it with an sessionStore.
func NewContext(ctx context.Context, s *SessionStore) context.Context {
return context.WithValue(ctx, contextKey, s)
}
// FromContext returns the Auth bound to the context, if any.
func FromContext(ctx context.Context) (s *SessionStore, ok bool) {
s, ok = ctx.Value(contextKey).(*SessionStore)
return
}

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