Compare commits

..

614 Commits

Author SHA1 Message Date
刘祥超
6a9045ba44 提交SQL 2022-07-18 09:13:16 +08:00
刘祥超
bc7ee19962 优化代码 2022-07-17 17:08:28 +08:00
刘祥超
2484cd4ae6 可以通过ID查找服务 2022-07-17 11:43:23 +08:00
刘祥超
57b8a96c6d 增加用户身份认证相关接口 2022-07-17 11:00:14 +08:00
刘祥超
6c8ab7cb94 集群DNS设置增加允许通过CNAME访问网站服务选项 2022-07-14 09:49:09 +08:00
刘祥超
b2c2bd247b 更新SQL 2022-07-12 14:22:20 +08:00
刘祥超
29d9e6db81 域名解析支持DNS.COM(商业版) 2022-07-11 11:51:49 +08:00
刘祥超
24fcb48c75 删除节点的时候同时也删除相关的运行日志 2022-07-07 20:31:12 +08:00
刘祥超
4911198fe7 安全设置中增加禁止搜索引擎、禁止爬虫、允许访问的域名等选项 2022-07-07 19:58:50 +08:00
刘祥超
c2af796992 服务看板增加峰值带宽数据 2022-07-07 17:11:49 +08:00
刘祥超
51c8572e53 用户界面设置中增加流量、带宽相关设置 2022-07-07 12:40:05 +08:00
刘祥超
c15cc6d75c 增加按用户统计带宽/修改其他相关代码 2022-07-07 09:18:08 +08:00
刘祥超
c0a99a4ba3 升级Go版本为1.18 2022-07-06 16:13:52 +08:00
刘祥超
62e26bed5a 增加服务带宽统计 2022-07-05 20:08:58 +08:00
刘祥超
9e68a2915c 增加UAM(5秒盾)集群设置 2022-07-03 22:10:46 +08:00
刘祥超
5b809fda1f 反向代理设置中增加移除回源主机名端口功能 2022-06-30 12:12:41 +08:00
刘祥超
88b782940a 提交SQL 2022-06-29 22:17:32 +08:00
刘祥超
85c596644d 实现源站端口跟随功能 2022-06-29 21:55:57 +08:00
刘祥超
ffa2d884bd 移除386、mips等不常用节点安装文件 2022-06-29 17:11:13 +08:00
刘祥超
813ed18610 优化编译脚本 2022-06-29 16:57:46 +08:00
刘祥超
564b11eae7 优化编译脚本 2022-06-29 14:52:11 +08:00
刘祥超
d2af0307b9 将DNS版本改为0.2.4 2022-06-28 20:31:59 +08:00
刘祥超
4fa6b03238 对于小内存(不大于2G),缩短服务统计导入数据库的时间 2022-06-25 20:57:03 +08:00
刘祥超
4bda78aa8c 限制节点自动升级时的速度和并发数 2022-06-25 20:36:31 +08:00
刘祥超
6002cc96d9 增加修改服务所在分组API 2022-06-25 19:21:46 +08:00
刘祥超
56c09f5be7 指标数据写入改成队列执行 2022-06-22 21:51:44 +08:00
刘祥超
e1fb8e4c74 删除某个IP时更新版本 2022-06-22 18:59:27 +08:00
刘祥超
14f055ce7c fix typo 2022-06-22 14:00:56 +08:00
刘祥超
7b7f2b0a00 修改版本号为v0.4.9 2022-06-20 16:00:27 +08:00
刘祥超
75662586fa 修改相关节点版本 2022-06-20 09:34:40 +08:00
刘祥超
c024331fa0 增加统计服务某日、某月流量API 2022-06-18 14:26:43 +08:00
刘祥超
b01ea79c5c 升级IP名单权限判断逻辑 2022-06-15 19:22:33 +08:00
刘祥超
d59f80b3ec 优化缓存任务Key状态增加执行中状态 2022-06-15 15:18:18 +08:00
刘祥超
b87e48c1f9 城市API增加省份信息 2022-06-14 17:37:42 +08:00
刘祥超
0e7a7f168f 访问日志查询增加requestPath:/hello、proto:HTTP/1.1、scheme:http等语法 2022-06-12 20:30:28 +08:00
刘祥超
2bbc09d3af 优化syslog提示、优化其他代码 2022-06-08 19:55:06 +08:00
刘祥超
97b50fab28 删除EdgePlus 2022-06-08 15:14:30 +08:00
刘祥超
83c867cb65 API节点启动失败后记录相关问题和处理建议 2022-06-08 15:13:24 +08:00
刘祥超
3c4b7ca57b 节点状态中增加时间戳字段 2022-06-06 19:39:08 +08:00
刘祥超
a2f98d2f25 可以设置用户每天执行缓存任务的额度 2022-06-05 21:15:28 +08:00
刘祥超
71677a8638 增加刷新、预热缓存任务管理 2022-06-05 17:13:56 +08:00
刘祥超
95a2187f95 优化API/修改用户集群不影响套餐服务 2022-05-25 11:44:18 +08:00
刘祥超
86bec813bf fix typo 2022-05-22 11:48:31 +08:00
刘祥超
0d7bd6f04e 增加LICENSE和README 2022-05-22 11:36:54 +08:00
刘祥超
d7117209b2 删除不过期IP时不立即删除,以等待节点完成同步 2022-05-21 22:06:04 +08:00
刘祥超
7a340ac68b 新创建WAF时增加默认选项 2022-05-21 18:58:03 +08:00
刘祥超
353b1b4ad1 WAF策略中增加验证码相关定制设置 2022-05-20 22:07:23 +08:00
刘祥超
fdac8beb40 健康检查增加是否记录访问日志选项 2022-05-19 17:14:32 +08:00
刘祥超
f098723a41 实现基础的DDoS防护 2022-05-18 21:02:53 +08:00
刘祥超
6ded627903 增加通过IP删除IP名单中IP参数 2022-05-10 15:11:48 +08:00
刘祥超
ed2d5ee5cc fix typo 2022-05-08 19:33:17 +08:00
刘祥超
c677368482 集群节点列表可以使用“未分组”筛选 2022-05-08 19:33:10 +08:00
刘祥超
51ccd614a7 忽略部分MySQL 1213错误 2022-05-08 00:24:22 +08:00
刘祥超
9be7f61b8c 阿里云DNS增加区域ID 2022-05-07 21:00:07 +08:00
刘祥超
14ad97c937 DNS服务商增加厂家筛选 2022-05-07 20:41:53 +08:00
刘祥超
94609d8ef4 EdgeUser版本升级为0.3.4 2022-05-05 18:56:35 +08:00
刘祥超
e34e38bcb2 Update sql.go 2022-05-04 20:35:38 +08:00
刘祥超
b10d9fe842 路由规则可以单独设置UAM(仅企业版可用) 2022-05-04 20:32:34 +08:00
刘祥超
992e725378 节点增加DNS解析库类型设置 2022-05-04 16:40:34 +08:00
刘祥超
346de7ca7a 版本修改为0.4.8 2022-04-25 11:11:43 +08:00
刘祥超
b212e124c2 统计看板中合并相同Key的指标数据 2022-04-24 15:24:40 +08:00
刘祥超
ee2781fe65 优化代码 2022-04-23 13:26:15 +08:00
刘祥超
89c1edc9ee 多个API节点时选择一个作为主节点/优化任务相关代码 2022-04-23 12:32:30 +08:00
刘祥超
773f3e1a7e 修复无法使用倒序分表查询日志的问题 2022-04-22 22:23:07 +08:00
刘祥超
e6f6f4dcc2 集群概要信息中增加系统服务状态 2022-04-22 22:04:29 +08:00
刘祥超
174946aa4d 修复集群健康检查无法自动上下线IP地址的Bug 2022-04-22 21:53:38 +08:00
刘祥超
93c594a785 更新SQL 2022-04-22 17:16:19 +08:00
刘祥超
2aea527dff 访问日志策略增加只记录WAF相关访问日志选项 2022-04-22 17:13:59 +08:00
刘祥超
fa30015ca5 增加WAF策略日志设置 2022-04-21 20:00:56 +08:00
刘祥超
d06c8cebf5 IP列表增加名单类型筛选 2022-04-21 15:09:18 +08:00
刘祥超
e4ef2e8253 WAF策略增加日志配置(暂未开放) /修复通过IP可能无法查询到访问日志的Bug 2022-04-21 09:41:04 +08:00
刘祥超
665aa06cc7 优化代码 2022-04-19 19:48:37 +08:00
刘祥超
88dfda82d6 Linux下自动添加端口到Firewalld 2022-04-19 19:35:50 +08:00
刘祥超
243463df83 优化代码 2022-04-19 12:58:00 +08:00
刘祥超
2dc1bfcb55 创建和修改证书的时候检查时间 2022-04-19 11:09:42 +08:00
刘祥超
e6f7cafe7e 当修改集群主域名和DNS子域名时,自动删除旧的相关域名 2022-04-18 21:00:40 +08:00
刘祥超
26fe3558f4 如果服务变更集群前后域名ID一致,则不执行删除操作 2022-04-18 18:35:57 +08:00
刘祥超
ea09ef3d91 服务切换集群后,直接删除老的域名记录 2022-04-18 18:21:29 +08:00
刘祥超
c4ca2521ee 单个服务切换集群时可以选择是否保留配置 2022-04-18 17:18:00 +08:00
刘祥超
db5cdd2957 修复没有日志数据库时无法进行分区查询的Bug 2022-04-17 16:50:38 +08:00
刘祥超
0f483b98ec 删除一处调试信息 2022-04-17 16:24:27 +08:00
刘祥超
0fe51abeb1 访问日志可以使用分表查询 2022-04-17 16:18:53 +08:00
刘祥超
db6b7f57bb 修复看板中统计数据可能不显示的问题 2022-04-16 22:22:05 +08:00
刘祥超
663ead19e4 优化节点排序 2022-04-16 14:45:54 +08:00
刘祥超
bc8adb663a 服务列表增加下行带宽 2022-04-15 12:14:59 +08:00
刘祥超
08b5dd7531 判断节点数量时增加集群状态检查 2022-04-14 22:00:47 +08:00
刘祥超
8177f3d7e4 可以通过groupId=-1查询到未分组的服务 2022-04-14 16:58:21 +08:00
刘祥超
bb5252caf6 edgeServers增加plainServerNames字段 2022-04-14 16:44:00 +08:00
刘祥超
bd4a47d2a1 服务列表中分组信息中增加UserId字段 2022-04-13 15:01:45 +08:00
刘祥超
300be4e936 预估同时间流量的时候刻度改为10分钟 2022-04-10 21:57:26 +08:00
刘祥超
c9dbfb79a7 按天统计流量接口可以预估某日同时间流量 2022-04-10 21:25:24 +08:00
刘祥超
589ae124f1 修复访问日志可能显示重复的问题 2022-04-10 18:21:52 +08:00
刘祥超
d72b440406 修改DNS版本为0.2.2 2022-04-10 16:02:21 +08:00
刘祥超
63e4e7cf9f 优化本地日志 2022-04-10 15:57:36 +08:00
刘祥超
ad416dddec 增加两个数据库相关调试命令 2022-04-08 15:09:33 +08:00
刘祥超
4e18129c6c 更新TeaGo 2022-04-08 14:57:20 +08:00
刘祥超
c03d9f1880 优化数据库相关代码 2022-04-08 14:15:45 +08:00
刘祥超
fe448e6556 增加当日统计接口 2022-04-07 19:46:50 +08:00
刘祥超
adcb33fce4 优化节点列表 2022-04-07 18:31:38 +08:00
刘祥超
e58c3774b6 优化代码 2022-04-05 19:32:35 +08:00
刘祥超
cd7e01c2f0 商业版支持L2节点 2022-04-04 12:08:08 +08:00
刘祥超
d884777a55 增加单元测试 2022-04-02 11:52:42 +08:00
刘祥超
c48aba1c99 更新TeaGo 2022-04-02 11:52:11 +08:00
刘祥超
8cc0faf1d7 集群可以单独设置WebP策略 2022-04-01 16:42:23 +08:00
刘祥超
9851b1a146 集群可以设置WebP策略 2022-04-01 16:18:54 +08:00
刘祥超
36162c603d 看板:只有指标数据不为空时才缓存 2022-04-01 10:00:10 +08:00
刘祥超
cc2782584e 自定义页面支持用户操作 2022-03-31 15:30:04 +08:00
刘祥超
1c1e82ee38 指标统计数据分表 2022-03-30 15:35:42 +08:00
刘祥超
4b3a9cedfa 可以用域名搜索DNS账号 2022-03-30 11:15:38 +08:00
刘祥超
e9497ee65d DNSPod支持国际站 2022-03-30 10:56:22 +08:00
刘祥超
29fae55dc6 IP列表可以使用级别筛选 2022-03-30 09:39:43 +08:00
刘祥超
54fdf3b762 支持DNSPod国际版(需要测试) 2022-03-30 09:12:42 +08:00
刘祥超
f609008984 商业版增加UAM功能 2022-03-29 21:24:31 +08:00
刘祥超
f9b6838dc6 写入指标统计数据时忽略MySQL 1213错误 2022-03-29 20:01:49 +08:00
刘祥超
6d2ecb9af3 对统计指标进行分表 2022-03-28 16:25:16 +08:00
刘祥超
7c4a01137b 数据库管理中列出更多可手动清理的数据表 2022-03-28 11:19:50 +08:00
刘祥超
418c15f79f 数据库管理中列出更多可手动清理的数据表 2022-03-28 11:12:49 +08:00
刘祥超
d13176b8a5 可以自行设定指标数据保留时间 2022-03-28 09:37:28 +08:00
刘祥超
b2752ddd5a 优化看板打开速度 2022-03-27 16:39:54 +08:00
刘祥超
7aea2fd48c 访问日志关键词支持完整的URL/优化Like语句 2022-03-27 12:22:47 +08:00
刘祥超
803f11659c 优化代码 2022-03-26 22:04:16 +08:00
刘祥超
073926ff67 修改用户平台版本为0.3.3 2022-03-26 22:04:10 +08:00
刘祥超
65b4832c94 增加脚本相关表 2022-03-25 14:11:17 +08:00
刘祥超
5f793f1f76 IP找不到不再提示错误 2022-03-24 21:41:14 +08:00
刘祥超
0ce1df25bc 可以修复单页或者全部服务日志 2022-03-23 17:31:26 +08:00
刘祥超
06ad9cc63b 版本号改为0.4.7 2022-03-23 14:45:37 +08:00
刘祥超
57247d0f5b 修复访问日志可能无法写入当天数据表的Bug 2022-03-23 10:58:54 +08:00
刘祥超
813267c50f 修复访问日志表可能无法自动创建的Bug 2022-03-23 10:05:42 +08:00
刘祥超
b8078e73b2 更改版本为v0.4.6 2022-03-23 10:03:57 +08:00
刘祥超
f958c57984 优化代码 2022-03-22 22:11:32 +08:00
刘祥超
30bfbdc01e 优化代码 2022-03-22 21:45:07 +08:00
刘祥超
314362c24d 优化代码 2022-03-22 19:30:30 +08:00
刘祥超
8582715be7 字段中的blob和JSON类型映射为[]byte和dbs.JSON 2022-03-21 21:39:36 +08:00
刘祥超
74e585263c 升级TeaGo 2022-03-21 19:46:14 +08:00
刘祥超
35cb10fffe 自动升级一个SQL注入规则 2022-03-20 11:54:17 +08:00
刘祥超
7c84794ac4 GRPC通讯支持gzip压缩 2022-03-20 11:28:45 +08:00
刘祥超
5859f5df91 更新相关库 2022-03-20 10:50:34 +08:00
刘祥超
e370944233 OCSP支持过期时间 2022-03-18 20:21:24 +08:00
刘祥超
a23a2ed826 获取OCSP更新列表兼容MySQL 5.7.22以下版本 2022-03-18 18:28:28 +08:00
刘祥超
3f9c250dff 动态更新OCSP,防止过期 2022-03-18 17:08:51 +08:00
刘祥超
06c9c9403b WAF统计图表中把page归为拦截,把tag归为记录 2022-03-17 16:49:46 +08:00
刘祥超
0e580a0890 源站支持自定义回源主机名 2022-03-17 15:48:40 +08:00
刘祥超
7aba622403 增加置顶集群功能 2022-03-17 11:12:46 +08:00
刘祥超
72cc8389e6 修复服务配置可能无法更新的Bug 2022-03-16 21:42:56 +08:00
刘祥超
39cf470b0c 上传SQL 2022-03-16 17:15:54 +08:00
刘祥超
6e71dda713 节点可以单独设置缓存目录 2022-03-16 15:23:58 +08:00
刘祥超
10909e28c8 修复集群配置/节点配置无法同步的Bug 2022-03-16 15:23:36 +08:00
刘祥超
6bd6e350b9 WAF全局看板中拦截类型只显示当天的统计/合并同名规则分组统计 2022-03-14 16:44:56 +08:00
刘祥超
d2ae7834ae 增加SQL子版本 2022-03-14 16:06:25 +08:00
刘祥超
c1ffd25c8b 上传SQL 2022-03-14 15:43:24 +08:00
刘祥超
931e55162b 缓存策略可以使用存储类型筛选 2022-03-14 15:42:48 +08:00
刘祥超
fa2bac6d1d 实现源站跟随功能 2022-03-14 15:42:45 +08:00
刘祥超
2f7b7240dd 增加证书OCSP错误日志管理 2022-03-11 20:27:53 +08:00
刘祥超
da67e726a2 优化代码 2022-03-11 17:39:08 +08:00
刘祥超
6cb5529c3f 证书ocsp获取失败时,报告错误 2022-03-11 13:02:53 +08:00
刘祥超
473d0d9439 修改OCSP自动更新的间隔时间和条数 2022-03-11 10:00:53 +08:00
刘祥超
bf0db231fc 上传sql 2022-03-10 21:33:08 +08:00
刘祥超
5644906b77 HTTP DNS QueryRecord动作支持返回null 2022-03-10 19:45:03 +08:00
刘祥超
3e32fe8e10 HTTP DNS请求时增加Content-Type 2022-03-10 19:13:53 +08:00
刘祥超
8459f106e9 域名操作错误时显示具体的域名、记录信息等 2022-03-10 17:42:23 +08:00
刘祥超
b768bbce5d 修改用户平台版本为0.3.2 2022-03-10 16:05:21 +08:00
刘祥超
9e7beb39c0 修复用户可能无法添加黑白名单IP的Bug 2022-03-10 15:59:00 +08:00
刘祥超
94287f5857 增加OCSP Stapling功能 2022-03-10 11:54:35 +08:00
刘祥超
60690dfd01 使用IPv6的IP搜索访问日志时自动去掉[] 2022-03-09 11:10:56 +08:00
刘祥超
fb00a7931e 支持使用小时筛选访问日志 2022-03-09 11:00:54 +08:00
刘祥超
42a6494bde 增加对访问日志自动分表配置 2022-03-09 10:01:24 +08:00
刘祥超
85a46a9827 自动对访问日志进行分表 2022-03-08 19:55:39 +08:00
刘祥超
088636553c 修复审计日志无法自动清理的Bug 2022-03-08 09:53:10 +08:00
刘祥超
95de3b12e2 缓存一些看板数据,以加快看板打开速度 2022-03-07 11:45:58 +08:00
刘祥超
b66b8d198a 更新相关库 2022-03-07 09:58:17 +08:00
刘祥超
e968a79886 更新TeaGo 2022-03-04 15:08:56 +08:00
刘祥超
4fc5d5b549 升级相关依赖库 2022-03-04 12:31:44 +08:00
刘祥超
77606709b3 增加是否同步写入压缩缓存设置 2022-02-24 20:11:53 +08:00
刘祥超
3529ceefcd 修改版本为0.4.5 2022-02-24 19:25:31 +08:00
刘祥超
7e851f07b1 修改版本为0.4.4 2022-02-23 14:49:12 +08:00
刘祥超
a62711e520 修改版本为v0.4.3 2022-02-21 18:31:47 +08:00
刘祥超
ffce574b39 更改版本为v0.4.2 2022-02-21 17:52:53 +08:00
刘祥超
4b1a9f9a45 升级时执行一次清理域名统计 2022-02-18 12:58:26 +08:00
刘祥超
7a86ecb44b 优化IPItem清理 2022-02-17 19:37:22 +08:00
刘祥超
89a113431a 更新SQL 2022-02-17 11:44:03 +08:00
刘祥超
ce62d0769b 优化过期IP清理 2022-02-14 09:00:24 +08:00
刘祥超
a72dc2e011 删除过期IP条目改成定时执行 2022-02-14 08:52:27 +08:00
刘祥超
91ca2d6b6b 删除过期IP时增加条数限制,防止超时 2022-02-10 16:07:32 +08:00
刘祥超
0de6fa5ce8 自动删除N天之前过期的IP条目 2022-01-30 11:24:31 +08:00
刘祥超
84e2628769 升级时SQL出错时提示对应的操作 2022-01-30 10:11:45 +08:00
刘祥超
fc28798c9f 支持默认价格设置 2022-01-23 20:16:06 +08:00
刘祥超
24c21c5513 实现带宽计费套餐 2022-01-23 19:16:52 +08:00
刘祥超
8445e811a5 用户版本改为0.3.1 2022-01-23 19:15:45 +08:00
刘祥超
d54621d500 修复域名统计数据无法自动清除的Bug 2022-01-20 11:40:28 +08:00
刘祥超
ef045e90f2 当服务配置变化时创建单个服务通知任务 2022-01-19 22:15:01 +08:00
刘祥超
5205136809 增加API方法调用耗时统计 2022-01-19 16:53:52 +08:00
刘祥超
179a7760fa 优化节点离线通知 2022-01-18 10:35:30 +08:00
刘祥超
640e69524c 只有连续N分钟离线的节点才会发送通知 2022-01-18 10:13:41 +08:00
刘祥超
3620ab3dc6 修改版本为v0.4.1 2022-01-17 10:53:46 +08:00
刘祥超
5789b1afb9 修改用户版本号 2022-01-16 20:38:53 +08:00
刘祥超
6f9f9dfb6f 源站支持客户端证书 2022-01-16 19:51:54 +08:00
刘祥超
171bafce6a WAF策略简要信息中增加serverId 2022-01-14 10:43:22 +08:00
刘祥超
7c7fecab26 可以使用集群搜索WAF策略、缓存策略 2022-01-11 15:46:41 +08:00
刘祥超
7afdf5a2d9 上传SQL 2022-01-11 15:29:03 +08:00
刘祥超
ee9718ac77 运行日志可以使用集群、节点筛选 2022-01-11 14:59:32 +08:00
刘祥超
15e10182da 访问日志可以使用集群和节点搜索 2022-01-11 12:03:20 +08:00
刘祥超
4341c5b738 增加读取当日拦截数API 2022-01-11 09:46:37 +08:00
刘祥超
cf9b31b8eb 自动升级SYN Flood配置 2022-01-10 20:07:26 +08:00
刘祥超
564d440cd1 实现自动SYN Flood防护 2022-01-10 19:54:37 +08:00
刘祥超
bad9231ab3 API相关HTTP请求增加User-Agent 2022-01-10 09:58:34 +08:00
刘祥超
a78333c490 上传SQL 2022-01-09 20:13:30 +08:00
刘祥超
ace44173ec WAF策略增加是否使用本地防火墙设置 2022-01-09 17:05:36 +08:00
刘祥超
5155ce97af 节点统计数据只保留两个小时 2022-01-09 11:14:05 +08:00
刘祥超
e2bf3ba1a4 节点配置增加产品信息 2022-01-09 10:44:17 +08:00
刘祥超
9abc65bd2b IP名单增加未读状态 2022-01-08 16:48:17 +08:00
刘祥超
a99156ea49 自动加入名单不需要即时更新 2022-01-08 15:24:51 +08:00
刘祥超
d35db163ae 增加城市/ISP查询接口 2022-01-06 16:25:23 +08:00
刘祥超
e7b0f0df90 部分API支持用户平台调用 2022-01-05 20:12:37 +08:00
刘祥超
eab09fa37a 用户创建服务时刻自动添加到分组 2022-01-05 15:55:02 +08:00
刘祥超
ec3f89ecf5 用户列表可以使用待审核、关键词搜索 2022-01-05 11:12:24 +08:00
刘祥超
d58106c7ca 实现用户注册/审核功能 2022-01-05 10:45:19 +08:00
刘祥超
5da924118d 更新数据库 2022-01-04 18:29:25 +08:00
刘祥超
51e37f0c52 可以在集群中配置是否自动在firewalld中开放端口 2022-01-03 16:27:50 +08:00
刘祥超
7e9e764322 节点日志可以使用标签筛选/级别可以填写多个,用逗号分隔 2022-01-03 11:04:14 +08:00
刘祥超
75e41fff4d 缩短节点运行日志保存时间 2022-01-03 10:19:17 +08:00
刘祥超
8a9842edaf 优化检查节点更新代码 2022-01-01 17:36:01 +08:00
刘祥超
4e8629d74a 实现初版边缘脚本 2021-12-31 15:21:27 +08:00
刘祥超
2e02aa556e 修改版本为0.4.0 2021-12-31 15:19:32 +08:00
刘祥超
974c95c20a 修改版本号0.3.8 2021-12-31 11:38:00 +08:00
刘祥超
3c0e6a900b 访问日志requestBody和responseBody字段从blob改为mediumblob 2021-12-22 21:08:01 +08:00
刘祥超
fb420f8fce 访问日志requestBody和responseBody字段从blob改为mediumblob 2021-12-22 20:39:15 +08:00
刘祥超
705e80f6c7 修改版本号为0.4.0 2021-12-20 20:02:07 +08:00
刘祥超
9a4be1f5a7 修复当数据库设置为lower_case_table_names=1时无法查询访问日志的Bug 2021-12-20 16:20:33 +08:00
刘祥超
7d82c3abf6 节点因阈值切换到备用IP时保持在线状态 2021-12-16 10:09:19 +08:00
刘祥超
55034efa87 优化代码 2021-12-15 20:45:51 +08:00
刘祥超
3ff45d6f49 优化ip2region查询代码 2021-12-15 20:17:01 +08:00
刘祥超
2b2c285c90 修改DNS版本为0.2.1 2021-12-15 10:47:32 +08:00
刘祥超
29ad1bc741 HTTP Header:实现请求方法、域名、状态码等限制,实现内容替换功能 2021-12-15 09:56:06 +08:00
刘祥超
a263ab5938 修改测试用例 2021-12-14 14:55:22 +08:00
刘祥超
6b34d32a8f 实现访问日志队列 2021-12-14 12:44:57 +08:00
刘祥超
3ff6ca5905 优化代码/增加edge-api goman命令 2021-12-14 10:49:29 +08:00
刘祥超
59ce71f72e WAF策略:可以修改分组代号/导入时可以根据名称合并 2021-12-12 20:24:36 +08:00
刘祥超
49da653ed8 上传SQL 2021-12-12 17:14:21 +08:00
刘祥超
12db6910dc 服务分组可以设置请求限制 2021-12-12 17:07:21 +08:00
刘祥超
3ce1aa14b5 路由规则增加专属域名设置 2021-12-12 16:38:31 +08:00
刘祥超
c67fbfa849 全局IP名单不即时通知节点更新 2021-12-12 14:38:57 +08:00
刘祥超
72b94c5035 实现请求连接数等限制 2021-12-12 11:46:23 +08:00
刘祥超
01ea13d283 IP名单添加新IP时删除老的IP内容 2021-12-10 11:12:34 +08:00
刘祥超
91378b26dd 上传SQL 2021-12-09 18:51:22 +08:00
刘祥超
46dc74195b 支持设置单节点最大线程数、单节点TCP最大连接数 2021-12-09 18:49:51 +08:00
刘祥超
63316b6b23 缩短各项统计清理时间 2021-12-08 10:30:14 +08:00
刘祥超
1b8600e04d 增加批量增加节点IP接口 2021-12-07 18:22:19 +08:00
刘祥超
11141d022f 访问日志实现记录requestBody 2021-12-07 14:57:55 +08:00
刘祥超
8fd29bd81c SSH认证支持sudo 2021-12-06 19:27:11 +08:00
刘祥超
820779e614 自动删除过期的IP 2021-12-06 11:24:18 +08:00
刘祥超
d45dc35ea6 自动删除过期的IP 2021-12-06 11:21:18 +08:00
刘祥超
b3280ee290 缩短监控数据清理时间为2天 2021-12-06 10:15:32 +08:00
刘祥超
ff0e89aa5f 自动将API节点的IP加入到白名单,防止误封 2021-12-06 10:09:37 +08:00
刘祥超
6ff5ba67e8 服务看板增加区域地图 2021-12-06 09:16:18 +08:00
刘祥超
e7474523ba 更新SQL 2021-12-05 21:14:57 +08:00
刘祥超
0b928aed14 优化代码 2021-12-05 19:53:37 +08:00
刘祥超
52dcd3da1f 非商业版不用记录每日地区统计 2021-12-05 19:43:33 +08:00
刘祥超
0260d6f735 商业版WAF看板增加地图 2021-12-05 19:38:32 +08:00
刘祥超
364636ea61 国家/地区统计时上传流量、攻击量等信息/多个统计增加自动清理程序 2021-12-05 18:58:14 +08:00
刘祥超
12f816cb5e 增加GRPC最大能接收的消息尺寸为128M 2021-12-04 19:02:12 +08:00
刘祥超
ef70d3465d 增加若干个统计数据清理程序 2021-12-04 11:31:07 +08:00
刘祥超
a3a9e726cb 更新sql 2021-12-03 15:46:21 +08:00
刘祥超
0c65774c82 首页看板显示未审核的服务数 2021-12-03 14:53:47 +08:00
刘祥超
fa31e547a2 优化指标数据清理 2021-12-03 12:16:00 +08:00
刘祥超
c406536511 优化指标数据清理 2021-12-03 12:06:13 +08:00
刘祥超
35f48e4f5d 增加统计指标自动清理 2021-12-03 10:57:40 +08:00
刘祥超
851e982ab9 修复通过IP查询IP名单时没有过滤已删除IP的Bug 2021-12-02 17:12:13 +08:00
刘祥超
956186613f WAF规则集中增加是否忽略局域网IP 2021-12-02 16:08:59 +08:00
刘祥超
aeb2a0c4f9 使用请求ID搜索访问日志时自动去除多余的空格和句号 2021-12-02 14:44:20 +08:00
刘祥超
4f05671af2 支持使用请求ID搜索访问日志 2021-12-02 11:50:16 +08:00
刘祥超
6b6170b183 当用户提交待审核域名时,给管理员发送消息 2021-12-01 17:20:09 +08:00
刘祥超
29f2aa2654 审核中服务增加提交审核时间 2021-12-01 17:06:16 +08:00
刘祥超
ed234b58bc 优化节点日志 2021-11-30 16:43:16 +08:00
刘祥超
cfcad531f4 用户账单增加多个API 2021-11-29 14:33:51 +08:00
刘祥超
12ffdfd849 健康检查不使用密钥加密 2021-11-29 09:52:47 +08:00
刘祥超
c2be7d70b8 完善套餐 2021-11-28 20:11:36 +08:00
刘祥超
efe723bc51 多个API支持查询用户查询 2021-11-28 14:28:00 +08:00
刘祥超
501559e3b8 版本号为0.3.7 2021-11-28 14:27:23 +08:00
刘祥超
de8b3c2195 节点任务查询时增加排除的任务类型 2021-11-27 17:07:01 +08:00
刘祥超
31aeb21e09 修复新启动节点时获取不到最新配置的Bug 2021-11-26 13:53:34 +08:00
刘祥超
f1cf89a78c 提交SQL 2021-11-26 10:45:02 +08:00
刘祥超
d6df5e1c48 提升全局IP名单变更通知速度 2021-11-26 10:39:50 +08:00
刘祥超
049b9b52dd 限制非商业版本从用户端登录 2021-11-24 20:06:43 +08:00
刘祥超
c53259008e 修改用户节点版本为0.2.0 2021-11-24 19:57:15 +08:00
刘祥超
6e0a0a099d 用户端实现UDP设置/优化检查端口是否已被使用API 2021-11-24 19:46:59 +08:00
刘祥超
f4313de55f 缩短统计指标数据保留时间 2021-11-24 19:45:51 +08:00
刘祥超
fd3823255c 服务增加是否合并URL中的多余分隔符选项 2021-11-24 14:49:51 +08:00
刘祥超
79823f4317 版本号改为0.3.6 2021-11-24 14:04:11 +08:00
刘祥超
99fd015066 优化API命名 2021-11-24 12:00:38 +08:00
刘祥超
f63c2d867d 修复流量控制API可能产生的JSON解析错误 2021-11-22 10:39:29 +08:00
刘祥超
eefb497c20 修复一个拼写错误:默认安装的节点从https改成http 2021-11-21 19:28:05 +08:00
刘祥超
1c8564d817 启动时增加查询是否正在启动指令 2021-11-21 19:27:27 +08:00
刘祥超
1359997f48 优化访问日志的requestId 2021-11-21 15:56:13 +08:00
刘祥超
4a52d43273 增加批量删除IP名单中IP的API 2021-11-21 09:42:57 +08:00
刘祥超
fb68de23c1 IP名单查看所有IP时不显示过期IP/节点同步IP名单时将过期Item设置为已删除 2021-11-21 08:43:26 +08:00
刘祥超
c1b7cf11c3 优化边缘节点在线状态管理 2021-11-20 18:59:35 +08:00
刘祥超
311751596c 监控数据只保留7天(先前是100天) 2021-11-20 14:35:34 +08:00
刘祥超
4132dee4bc 修复用户查询证书时返回其他证书的Bug 2021-11-18 16:44:41 +08:00
刘祥超
8e886cd410 更新SQL 2021-11-18 16:43:43 +08:00
刘祥超
d6f896298d 节点IP阈值增加节点健康检查失败 2021-11-18 14:30:53 +08:00
刘祥超
2ca8aa4b44 开源版本不运行sql.sh 2021-11-18 08:56:13 +08:00
刘祥超
783052a0b4 IP名单增加是否只显示自动拦截名单选项 2021-11-17 20:25:36 +08:00
刘祥超
5bda9e4b51 IP名单默认改为非全局 2021-11-17 20:00:34 +08:00
刘祥超
f7cbf051bd 增加全局查看、检索IP功能 2021-11-17 19:51:00 +08:00
刘祥超
4d7e82d0a2 IP名单增加是否全局 2021-11-17 16:14:55 +08:00
刘祥超
bf2ffeb15c 删除WAF策略和删除服务时同时也删除关联的IP名单 2021-11-16 17:50:52 +08:00
刘祥超
7b3290800d 更新SQL 2021-11-16 16:11:55 +08:00
刘祥超
eaebb6df5a IP名单中IP创建时保存相关节点、服务、WAF策略信息 2021-11-16 16:10:48 +08:00
刘祥超
acdddf5e12 API取消对节点时钟的检查 2021-11-16 09:02:55 +08:00
刘祥超
1224758b5b IP名单API增加IP添加时间 2021-11-15 11:31:27 +08:00
刘祥超
5b32343a2c 优化节点配置生成速度 2021-11-11 14:16:42 +08:00
刘祥超
8afd21f2f4 节点获取配置时进行压缩传输,至少减少80%的传输带宽 2021-11-11 09:06:44 +08:00
刘祥超
3e76bf6e5a 自动生成账单,自动支付账单 2021-11-11 08:30:53 +08:00
刘祥超
ed74adebac 改进流量限制 2021-11-10 14:54:27 +08:00
刘祥超
1400dff676 将带宽限制改为流量限制 2021-11-09 17:36:54 +08:00
刘祥超
8e3360b368 支持套餐相关操作 2021-11-09 15:36:25 +08:00
刘祥超
e80441639c 支持购买套餐/续费套餐/用户账户操作等 2021-11-08 20:52:15 +08:00
刘祥超
07bf994b68 增加删除/恢复DNS域名API 2021-11-06 16:23:12 +08:00
刘祥超
344ef4d597 SSH登录支持Passphrase 2021-11-06 15:31:01 +08:00
刘祥超
144b9b9519 增加多个API/规范命名 2021-11-05 17:56:35 +08:00
刘祥超
38841d6d75 规范命名 2021-11-05 15:36:45 +08:00
刘祥超
6d1603e253 增加APINodeService.CountAllEnabledAndOnAPINodes() 2021-11-05 15:35:22 +08:00
刘祥超
6ce23c9913 修复服务列表无法使用数字搜索的Bug 2021-11-04 18:48:07 +08:00
刘祥超
fd8c9adbf7 修改版本号为0.3.5 2021-11-04 18:47:53 +08:00
刘祥超
ea40dc7534 支持info指令查询PID、版本号等信息 2021-11-04 11:15:22 +08:00
刘祥超
37c8a8c955 升级到v0.3.3时删除7天以前的节点运行日志(info级别) 2021-11-01 16:03:40 +08:00
刘祥超
2a0e4bba4b 优化节点运行日志清理时间 2021-11-01 15:58:58 +08:00
刘祥超
dd252b7b87 修复db.yaml自动被还原的Bug 2021-11-01 15:31:59 +08:00
刘祥超
2de63863a7 修改版本为0.3.4 2021-11-01 10:46:00 +08:00
刘祥超
5d00d53ea5 增加ACME错误信息字段长度 2021-10-30 15:47:59 +08:00
刘祥超
dcf04d64bd 更新SQL 2021-10-29 14:06:12 +08:00
刘祥超
d36f7d01df 增加套餐相关代码 2021-10-29 14:02:40 +08:00
刘祥超
a0ff24adb6 增加在IP名单中使用ipFrom和ipTo查找IP的API 2021-10-26 09:17:33 +08:00
刘祥超
5597a0af6c 创建WAF分组时也记录代号 2021-10-25 19:02:20 +08:00
刘祥超
c196a85a59 增加为WAF分组添加规则集的API 2021-10-25 12:01:16 +08:00
刘祥超
ba638d4e1d 优化代码 2021-10-22 13:40:04 +08:00
刘祥超
7f8abccd2a 优化代码 2021-10-22 13:38:18 +08:00
刘祥超
c0f540cc2c 更新SQL 2021-10-22 12:40:30 +08:00
刘祥超
bd905ff1a9 可以在IP名单中搜索IP 2021-10-22 12:18:53 +08:00
刘祥超
3b8d1b4cd8 实现单个服务的带宽限制(商业版) 2021-10-21 17:10:53 +08:00
刘祥超
f86180b93c 将HTTP Header中Edge-改成X-Edge- 2021-10-19 19:49:06 +08:00
刘祥超
cfb1864fd2 健康检查支持UserAgent和是否基础请求设置 2021-10-19 16:31:05 +08:00
刘祥超
1d0a66a156 上传SQL 2021-10-18 09:09:32 +08:00
刘祥超
b4d4f6460e 增加PURGE某个URL缓存功能 2021-10-17 20:22:14 +08:00
刘祥超
e2de9799c0 增加清除服务缓存API 2021-10-17 17:12:30 +08:00
刘祥超
9b9c6471f7 增加支持服务CNAME选项/提供重新生成服务CNAME API 2021-10-16 12:02:42 +08:00
刘祥超
d30b10baee 只讲错误级别的节点运行日志设置为未读 2021-10-16 10:26:47 +08:00
刘祥超
55f7189a1c 运行日志显示未读的日志数量 2021-10-15 12:54:31 +08:00
刘祥超
0ff6fb002d 域名小时统计只保留7天 2021-10-15 10:16:14 +08:00
刘祥超
a5d34565c5 节点日志增加是否已读标记 2021-10-14 17:29:54 +08:00
刘祥超
3c17ba0a8b 修复华为云DNS TXT记录值不加引号无法添加的问题 2021-10-13 17:53:14 +08:00
刘祥超
a8c0c64071 更新SQL 2021-10-13 10:12:10 +08:00
刘祥超
82ed22a464 支持PROXY Protocol 2021-10-12 20:18:35 +08:00
刘祥超
b190479d44 集群API中增加时区信息 2021-10-12 14:40:24 +08:00
刘祥超
ed7b586137 可以在集群中指定节点时区 2021-10-12 11:44:24 +08:00
刘祥超
ea19635fe5 修复同属多集群下的节点无法删除线路的Bug 2021-10-12 08:40:39 +08:00
刘祥超
29cd7da6e4 版本号改为0.3.3 2021-10-12 08:38:00 +08:00
刘祥超
4dd9c6398e WAF列表中去除跟服务分组相关的策略 2021-10-10 20:18:07 +08:00
刘祥超
3aae6b6b89 TCP、TLS、UDP支持端口范围 2021-10-10 16:29:50 +08:00
刘祥超
a91f0ac206 服务分组增加特殊页面设置 2021-10-10 10:53:05 +08:00
刘祥超
f60745794c 特殊页面可以直接使用HTML 2021-10-10 10:34:56 +08:00
刘祥超
da408bfe8e WAF看板增加节点拦截排行和域名拦截排行 2021-10-09 16:01:29 +08:00
刘祥超
9a40100fd7 TCP/UDP服务看板只显示对应的指标图表 2021-10-08 14:59:51 +08:00
刘祥超
42c53b47dc 修复查看附近服务API中scope错误的问题 2021-10-08 14:38:36 +08:00
刘祥超
9855829a3c 增加查看单个服务附近服务API 2021-10-08 14:36:35 +08:00
刘祥超
5cf1f9bf33 支持更多的分组全局设置功能 2021-10-07 16:47:21 +08:00
刘祥超
7b1efe65d5 服务支持自定义访客IP地址获取方式 2021-10-06 11:40:29 +08:00
刘祥超
53c371f8d1 设定WAF策略默认模式为defend 2021-10-06 09:46:58 +08:00
刘祥超
82d30ca958 增加使用账号查询ACME用户数量的API 2021-10-03 14:43:20 +08:00
刘祥超
eeec60c543 ACME证书增加ZeroSSL支持 2021-10-03 13:09:29 +08:00
刘祥超
7b9e6fe5fb 更新SQL 2021-10-03 10:37:38 +08:00
刘祥超
b14ab9ecd8 上传SQL 2021-10-03 08:36:50 +08:00
刘祥超
2854e1cd7c 更新SQL 2021-10-02 18:09:36 +08:00
刘祥超
a11b3c0d8d 修复修改HTTP Header不会自动更新节点配置的Bug 2021-10-01 16:25:16 +08:00
刘祥超
724a8e99ee 支持自动转换图像文件为WebP 2021-10-01 16:24:56 +08:00
刘祥超
fc77a2d7ed 自建DNS改成智能DNS 2021-09-30 13:21:05 +08:00
刘祥超
97e7165dec WAF策略增加观察模式和通过模式 2021-09-30 11:30:45 +08:00
刘祥超
06a27e9bbc 升级原有gzip配置到compression配置 2021-09-29 20:12:53 +08:00
刘祥超
121605324d 支持brotli和deflate压缩 2021-09-29 19:32:25 +08:00
刘祥超
6c088c304b 看板增加离线节点数字 2021-09-27 09:23:41 +08:00
刘祥超
fc86111039 缓存条件增加最小内容尺寸配置 2021-09-26 15:02:13 +08:00
刘祥超
77fde956ee 版本改为0.3.2 2021-09-26 10:09:45 +08:00
刘祥超
6b81ffd074 边缘节点版本调整为0.3.1 2021-09-23 20:59:21 +08:00
刘祥超
eff3c77551 分组配置信息中增加分组ID 2021-09-23 14:41:32 +08:00
刘祥超
2089bac52f 可以在分组中设置一些全局配置选项 2021-09-22 19:39:43 +08:00
刘祥超
67760a53ba 域名解析任务可以使用集群ID筛选 2021-09-21 10:56:53 +08:00
刘祥超
d4d7b1fff7 可以设置集群的DNS记录TTL 2021-09-20 20:01:21 +08:00
刘祥超
d121bc86a0 在集群中可以设置自动加入DNS的CNAME记录 2021-09-20 16:37:48 +08:00
刘祥超
7a1bd29f6f 反向代理源站实现使用域名分组 2021-09-20 11:54:45 +08:00
刘祥超
f1af151080 开源版本也显示域名排行 2021-09-19 16:10:34 +08:00
刘祥超
f39d106bef 实现连通性变化发送通知功能 2021-09-18 14:21:56 +08:00
刘祥超
d9c092cd31 修复创建默认集群时没有写入API令牌的Bug 2021-09-16 15:43:07 +08:00
刘祥超
7d1d138e42 优化域名排行查询速度 2021-09-16 09:23:36 +08:00
刘祥超
89bd70819f 增加查找区域监控对象相关结果API 2021-09-15 17:53:34 +08:00
刘祥超
15156b68e3 修复IP地址不能修改在线状态的Bug 2021-09-15 11:45:00 +08:00
刘祥超
92a3b8f375 健康检查只检查启用的IP地址 2021-09-15 10:44:25 +08:00
刘祥超
6a152b7775 调整部分命名 2021-09-14 19:38:44 +08:00
刘祥超
ea915582c1 IP阈值动作增加WebHook 2021-09-14 15:27:48 +08:00
刘祥超
51b938b9c7 IP阈值增加节点分组和集群相关统计项目 2021-09-14 11:36:22 +08:00
刘祥超
c2f559d48e 优化节点设置交互 2021-09-13 16:47:40 +08:00
刘祥超
a96c1434b6 健康检查支持IPv6地址的节点 2021-09-13 13:46:20 +08:00
刘祥超
d135442a52 IP地址支持手动上线和从备用IP中恢复 2021-09-13 13:45:58 +08:00
刘祥超
0b8501a724 实现节点自动切换到备用IP 2021-09-13 10:51:05 +08:00
刘祥超
7fcc2b7dba 实现基础的IP地址阈值 2021-09-12 20:21:42 +08:00
刘祥超
1ea7fe0213 实现基本的监控终端管理 2021-09-08 19:34:31 +08:00
刘祥超
838d33648f 通过DNS方式申请ACME证书时支持二级域名 2021-09-08 18:23:37 +08:00
刘祥超
6cf4a8ea3e 对域名统计进行分表处理 2021-09-08 17:32:08 +08:00
刘祥超
15c40d6c96 版本号修改0.3.1 2021-09-08 17:31:18 +08:00
刘祥超
6dfecd69b4 提供区域监控上报结果接口 2021-09-06 08:12:48 +08:00
刘祥超
dbc97bc8de 实现基本的区域监控终端管理功能 2021-09-05 11:10:18 +08:00
刘祥超
8308e2e83d 修复边缘节点无法下载文件的Bug 2021-09-03 15:15:27 +08:00
刘祥超
8227138168 修复DNS节点升级文件无法下载的Bug 2021-08-31 22:36:36 +08:00
刘祥超
2227a14ba4 增加独立的IP地址管理功能 2021-08-31 17:24:52 +08:00
刘祥超
6137b44408 企业认证信息中增加节点数限制 2021-08-30 18:57:11 +08:00
刘祥超
f654c65626 查询指标数据时增加索引 2021-08-30 15:23:51 +08:00
刘祥超
674574a6af Update go.mod 2021-08-30 10:56:44 +08:00
刘祥超
f02ab1aae2 优化数据库节点管理 2021-08-30 10:56:31 +08:00
刘祥超
55527bba09 健康检查失败10分钟内不重复提醒 2021-08-30 09:47:17 +08:00
刘祥超
821b607ef2 当修改节点在线状态时重置上线检查次数 2021-08-29 16:57:50 +08:00
刘祥超
afeee89a88 节点返回数据增加isUp字段 2021-08-29 16:57:20 +08:00
刘祥超
a983615464 修复节点转移集群后没有删除老的DNS记录的问题 2021-08-29 16:41:42 +08:00
刘祥超
bd596816d5 将健康检查连续下线次数从1升级为3/修复健康检查可能导致DNS不断同步的问题 2021-08-29 16:01:31 +08:00
刘祥超
ca233c3573 修复一个因为SQL_CACHE而导致子查询产生的错误 2021-08-29 14:07:12 +08:00
刘祥超
fd1f990a0e 访问日志表增加requestBody, responseBody(预留) 2021-08-29 10:37:42 +08:00
刘祥超
1796bb8f96 更新TeaGo,提升SQL解析效率、自动开启SQL查询缓存 2021-08-29 10:21:42 +08:00
刘祥超
3f5e4babc7 改进编译脚本 2021-08-26 16:29:19 +08:00
刘祥超
f508c16f92 删除plus文件 2021-08-26 14:40:07 +08:00
刘祥超
ac0bbd0b99 DNS服务商支持搜索 2021-08-25 18:47:01 +08:00
刘祥超
9017176efb 集群支持使用域名搜索 2021-08-25 18:39:17 +08:00
刘祥超
8b40634e74 节点如果没有设置DNS线路就使用默认线路 2021-08-25 17:16:24 +08:00
刘祥超
cf476f79d6 Admin看板增加默认集群ID 2021-08-25 11:41:23 +08:00
刘祥超
fc38a6ab7e 创建集群时自动创建缓存策略和WAF策略 2021-08-25 11:18:37 +08:00
刘祥超
e4f0dafc1a 增加忽略相似消息周期设置 2021-08-24 20:45:12 +08:00
刘祥超
b7fda0b9cc 消息接收人可以设置接收消息时间段 2021-08-24 17:46:11 +08:00
刘祥超
f4cc5aa087 通知媒介可以设置发送频率 2021-08-24 15:46:53 +08:00
刘祥超
f58724065d 通知媒介增加任务队列查看功能 2021-08-24 14:22:44 +08:00
刘祥超
1e6b42c00c 优化WAF日志查询速度 2021-08-22 16:20:40 +08:00
刘祥超
8d759a104b 优化WAF日志访问速度 2021-08-22 16:00:32 +08:00
刘祥超
72d7ceb94e 提升节点组合配置效率 2021-08-22 11:35:33 +08:00
刘祥超
53f7a0b77e Dashboard可以提示API节点升级 2021-08-21 19:43:46 +08:00
刘祥超
56000a8b8a 优化服务配置更新机制 2021-08-21 17:24:29 +08:00
刘祥超
ab7b2fee3a 自建DNS支持递归查询 2021-08-21 16:46:41 +08:00
刘祥超
d768d46854 DNS访问日志显示匹配的线路 2021-08-20 11:27:16 +08:00
刘祥超
b86c9aad6f 节点排行增加条数限制 2021-08-20 10:10:55 +08:00
刘祥超
df667c6ee6 添加DNS账号时自动读取DNS服务商下域名 2021-08-19 14:26:34 +08:00
刘祥超
70331805d7 节点IP地址可以设置阈值 2021-08-18 16:19:16 +08:00
刘祥超
71dbf86572 节点IP增加是否启用、是否在线状态 2021-08-18 09:24:18 +08:00
刘祥超
0df358d70d 调整版本为0.3.0 2021-08-17 09:44:22 +08:00
刘祥超
93a17ced7c 上传SQL 2021-08-15 20:20:06 +08:00
刘祥超
580d09ef99 IP库查询结果显示城市 2021-08-15 20:08:04 +08:00
刘祥超
fed725d45c 增加通过IP来搜索IP名单的API 2021-08-15 15:42:32 +08:00
刘祥超
350b514fc7 改进细节 2021-08-15 10:39:41 +08:00
刘祥超
71d2671c04 增加SSH认证建议接口 2021-08-14 21:33:17 +08:00
刘祥超
5a22146309 增加SSH登录建议端口接口 2021-08-14 18:07:20 +08:00
刘祥超
ac2fb4c84b 可以远程停止和启动DNS节点 2021-08-12 11:47:39 +08:00
刘祥超
c25e3f18e0 修复边缘节点和DNS节点安装文件冲突的问题 2021-08-11 21:14:01 +08:00
刘祥超
d3e4f28c69 实现DNS节点远程安装 2021-08-11 21:00:29 +08:00
刘祥超
363892efb2 改进脚本 2021-08-11 17:16:54 +08:00
刘祥超
cf36559eea 访问日志显示节点信息 2021-08-10 11:15:15 +08:00
刘祥超
fa3e0ca6ab 自建DNS增加解析测试 2021-08-09 18:41:30 +08:00
刘祥超
7229b0db34 NS日志增加remoteAddr字段 2021-08-09 15:19:38 +08:00
刘祥超
23871804b1 EdgeDNS支持内置线路 2021-08-09 13:57:58 +08:00
刘祥超
5e00bfa4c1 优化节点到API节点连接管理 2021-08-08 16:17:25 +08:00
刘祥超
473a2db335 数据有更改时发送通知 2021-08-08 15:47:48 +08:00
刘祥超
c893de8af7 DNS节点增加在线状态通知 2021-08-08 10:29:48 +08:00
刘祥超
a8cf04d178 访问日志搜索增加域名和IP搜索 2021-08-07 22:04:22 +08:00
刘祥超
e94a7f9a77 运行日志只显示已经设置集群的节点 2021-08-07 16:52:28 +08:00
刘祥超
ccf435ee8e 优化代码 2021-08-07 16:11:35 +08:00
刘祥超
7dc5c5f349 修复utils.DumpResponse()可能会忽略err的问题 2021-08-07 11:04:03 +08:00
刘祥超
566c04f080 边缘节点没有集群的时候视为删除 2021-08-07 10:12:17 +08:00
刘祥超
5a13c7663c 修复HTTPFirewallPolicyService.CheckHTTPFirewallPolicyIPStatus()可能panic的Bug 2021-08-06 14:48:05 +08:00
刘祥超
1df6d579d7 修改一处测试 2021-08-06 14:47:42 +08:00
刘祥超
20110495ab 修复unique key无法升级的问题 2021-08-06 14:22:17 +08:00
刘祥超
ce8d656d65 调整版本为0.2.9 2021-08-06 14:21:54 +08:00
刘祥超
186fe3c365 修复无法升级国家/地区数据的Bug 2021-08-05 20:36:16 +08:00
刘祥超
a9ce2f45df 修复导致无法安装的严重Bug 2021-08-05 19:53:54 +08:00
刘祥超
5105af9918 自建DNS增加全局配置 2021-08-05 16:08:01 +08:00
刘祥超
378c485219 统计节点分组中节点数量时判断节点集群是否存在 2021-08-05 11:28:58 +08:00
刘祥超
2465993e2c 域名解析支持华为云解析DNS 2021-08-04 22:14:54 +08:00
刘祥超
818c1c25a7 调整版本 2021-08-04 15:36:06 +08:00
刘祥超
77c67ccd3f 调整版本号 2021-08-03 14:00:44 +08:00
刘祥超
d855fdedde 修复一个DNS节点和边缘节点地址可能会冲突的问题 2021-08-03 13:35:29 +08:00
刘祥超
95c734c87a 调整版本号 2021-08-03 10:40:32 +08:00
刘祥超
89ff1927b7 使用upgrade命令升级数据库时增加日志显示 2021-08-02 15:23:02 +08:00
刘祥超
1a3aaf2846 节点运行日志增加记录源站ID 2021-08-01 21:54:44 +08:00
刘祥超
7d25019abb 更新SQL 2021-08-01 14:59:52 +08:00
刘祥超
4cfc7d5387 修改服务组合配置为动态 2021-08-01 14:56:08 +08:00
刘祥超
9245cf9cdb 各项统计支持单节点多集群 2021-08-01 11:13:46 +08:00
刘祥超
9e1e57dfd8 初步实现多集群共享节点 2021-07-31 22:23:11 +08:00
刘祥超
87f032bebd 使用ES存储访问日志时自动生成requestId 2021-07-29 17:34:44 +08:00
刘祥超
bda407fc3f 实现基本的访问日志策略 2021-07-29 16:50:59 +08:00
刘祥超
3b2f6060b8 上传统计数据时自动清理旧数据 2021-07-28 17:04:31 +08:00
刘祥超
a15ad7dd04 增加内置统计指标:请求来源统计 2021-07-26 16:50:34 +08:00
刘祥超
a415ef6070 指标图表可以设置忽略空值和其他对象值 2021-07-26 16:44:29 +08:00
刘祥超
d94a822f52 调整若干文字 2021-07-26 11:23:21 +08:00
刘祥超
9e3fb9cf66 版本调整为0.2.6 2021-07-26 11:23:12 +08:00
刘祥超
84a9916d3e 使用Sock管理进程启停 2021-07-25 17:46:47 +08:00
刘祥超
7d1ae0d242 更新SQL和编译脚本 2021-07-25 16:28:20 +08:00
刘祥超
3fbad22218 区分社区版和商业版 2021-07-25 15:46:12 +08:00
刘祥超
5fffc8a833 DNS支持TSIG 2021-07-25 15:08:17 +08:00
刘祥超
2c72d28c48 DNS服务支持密钥管理 2021-07-25 09:43:57 +08:00
刘祥超
45ea803e7a 获取DNS节点配置信息时增加节点UniqueId信息 2021-07-24 10:10:02 +08:00
刘祥超
edd4b59207 规则的内容长度从1024调整为4096 2021-07-23 10:58:07 +08:00
刘祥超
38ad11fda0 v0.2.5.1 2021-07-23 10:57:25 +08:00
刘祥超
961d9b4dbc DNS节点可以自动升级 2021-07-22 18:42:57 +08:00
刘祥超
6436e83d9e 更新SQL 2021-07-22 08:16:06 +08:00
刘祥超
b2d3d483e8 自动清理日志的时候也清理DNS访问日志 2021-07-21 22:12:52 +08:00
刘祥超
89ec81f6b1 修改无法在访问日志中搜索IP的Bug 2021-07-21 22:12:35 +08:00
刘祥超
5124169e28 编译脚本增加arm64 2021-07-21 22:11:59 +08:00
刘祥超
fe4eb3928e 设置 max_prepared_stmt_count 失败时提示更详细 2021-07-21 09:01:37 +08:00
刘祥超
514d968b20 修复访问日志无法查询IP的Bug 2021-07-21 08:08:31 +08:00
刘祥超
3cce13e671 域名排行增加条数限制 2021-07-21 08:08:16 +08:00
刘祥超
6d359b09f2 更新SQL 2021-07-20 22:24:30 +08:00
刘祥超
316b3485ca API节点列表API允许认证节点调用 2021-07-20 19:03:35 +08:00
刘祥超
31c20122b4 域名服务增加停用 /启用 2021-07-20 19:03:10 +08:00
刘祥超
7343d4bfac 增加API令牌相关API 2021-07-20 17:15:44 +08:00
刘祥超
330d5a3d21 在各个地方支持IPv6 2021-07-20 10:55:34 +08:00
刘祥超
709845064f 自动清理过期指标统计数据 2021-07-19 21:01:26 +08:00
刘祥超
be5a89e513 更新SQL 2021-07-19 20:38:59 +08:00
刘祥超
80328b9b5f 增加内置的统计指标 2021-07-19 20:38:30 +08:00
刘祥超
c6a09e131b Dashboard增加指标图表 2021-07-19 17:58:16 +08:00
刘祥超
f52ea4fa4f 优化通过IP查找日志 2021-07-18 17:09:06 +08:00
刘祥超
6cbda588f7 实现WAF通知和记录IP功能 2021-07-18 15:52:34 +08:00
刘祥超
f9e7c3a2e0 WAF支持更多动作 2021-07-14 22:46:23 +08:00
刘祥超
c370dada11 访问日志增加RemoteAddr字段以加速查询 2021-07-14 22:43:31 +08:00
刘祥超
8d71afc176 搜索节点时加入所在集群状态 2021-07-14 13:59:16 +08:00
刘祥超
a39a114316 IP测试时同时也检查绑定的IP名单 2021-07-13 15:49:16 +08:00
刘祥超
3f5c3568db 更新SQL 2021-07-13 15:10:19 +08:00
刘祥超
f50ff00f0b 路径规则改成路由规则 2021-07-13 14:30:30 +08:00
刘祥超
7caa5bee75 优化看板 2021-07-13 11:04:45 +08:00
刘祥超
bf5368929b 更新SQL 2021-07-12 17:37:44 +08:00
刘祥超
ff6d8d9ba3 实现数据看板--WAF 2021-07-12 16:57:02 +08:00
刘祥超
877d42a271 管理界面可以切换风格 2021-07-12 10:21:28 +08:00
刘祥超
21c51cbdc8 实现数据看板-DNS 2021-07-11 21:44:08 +08:00
刘祥超
45e4eb72ac 实现数据看板--用户 2021-07-11 18:05:57 +08:00
刘祥超
11c344eef4 看板增加缓存目录用量 2021-07-08 19:43:09 +08:00
刘祥超
17008d6b03 健康检查失败时,会自动尝试再次连接 2021-07-08 18:37:11 +08:00
刘祥超
5512efbb70 实现服务看板(企业版) 2021-07-07 19:55:37 +08:00
刘祥超
1e0a7612df 节点列表增加下行流量,节点列表可以按CPU、内存、下行流量排序 2021-07-07 15:18:48 +08:00
刘祥超
8589e0d373 实现节点看板(仅对企业版开放) 2021-07-06 20:06:34 +08:00
刘祥超
c5edaef356 优化API节点监听逻辑,提升兼容性
如果用户填写的GPRC监听端口中的地址无法监听,则尝试只监听端口
2021-07-06 15:19:39 +08:00
刘祥超
692eb04c93 DNS节点检查是否需要升级时,加入集群是否可用的条件 2021-07-06 10:27:14 +08:00
刘祥超
83d9a17c2b 集群看板增加指标相关图表 2021-07-05 16:37:53 +08:00
刘祥超
a4422ef7bd 更新SQL 2021-07-05 11:38:45 +08:00
刘祥超
e6e9fcc3c3 实现集群看板 2021-07-05 11:37:22 +08:00
刘祥超
7484243dff 实现基本的图表管理 2021-07-03 15:45:04 +08:00
刘祥超
907676d688 指标数据增加总和数据 2021-07-01 10:39:42 +08:00
刘祥超
54dbe1f3e4 实现基础的统计指标 2021-06-30 19:59:49 +08:00
刘祥超
98a2d61fd1 SSH认证--私钥认证方式增加用户名选项 2021-06-30 14:56:36 +08:00
刘祥超
e544e088be 统计创建浏览器、系统时限制名称长度 2021-06-29 19:38:14 +08:00
刘祥超
86c33256ca 修改版本为0.2.5 2021-06-28 10:32:06 +08:00
刘祥超
91ff73e4e4 更新用户节点版本 2021-06-27 22:22:11 +08:00
刘祥超
e68473a13f 更新SQL 2021-06-27 22:14:35 +08:00
刘祥超
5774beda6f 阶段性提交 2021-06-27 21:59:37 +08:00
刘祥超
4869c11d60 ip2region增加IP格式检查 2021-06-27 17:29:16 +08:00
刘祥超
4bc6f93902 统计时创建系统、浏览器信息时加锁 2021-06-27 15:17:18 +08:00
刘祥超
ce4e079752 修复WAF用户检查的Bug 2021-06-27 08:31:10 +08:00
刘祥超
488df3d150 服务列表可以搜索端口号 2021-06-25 11:05:02 +08:00
刘祥超
109e129cc5 ACME申请证书时可以设置回调URL 2021-06-24 09:27:39 +08:00
刘祥超
447f89399f 更新SQL 2021-06-23 13:13:26 +08:00
刘祥超
206c12c746 实现公用的IP名单 2021-06-23 13:12:54 +08:00
刘祥超
3c14310e3a 变更版本 2021-06-21 14:43:51 +08:00
刘祥超
50055c1045 提交SQL 2021-06-21 11:25:13 +08:00
刘祥超
cca97f5c51 管理员也支持AccessKey,Rest API增加所有的服务 2021-06-20 19:22:24 +08:00
刘祥超
1bfaf041ea 修复用户无法修改关联集群的Bug 2021-06-19 22:19:12 +08:00
刘祥超
927b5b6fd3 阶段性提交 2021-06-17 21:17:53 +08:00
刘祥超
1afaa0bc8d 调整版本为0.2.3 2021-06-17 18:45:55 +08:00
刘祥超
7312a7aa94 调整版本为0.2.2 2021-06-17 18:07:49 +08:00
刘祥超
fd18739b0a REST API输出时增加application/json; charset=utf-8 Header 2021-06-16 10:48:31 +08:00
刘祥超
d92f56e7fe 用户AccessKey:增加最近访问时间、修复AccessKey没有区分用户的Bug 2021-06-16 10:48:08 +08:00
刘祥超
a1a7ec8c93 调整边缘节点版本 2021-06-16 09:37:54 +08:00
刘祥超
951fdf0927 调整版本为0.2.1 2021-06-16 08:31:02 +08:00
刘祥超
7d3328f75b 节点日志计数从1开始(以前是0) 2021-06-14 20:39:39 +08:00
刘祥超
8db39a6b36 更新各个节点版本 2021-06-14 20:39:10 +08:00
刘祥超
71681b1c5d 提供下载最新边缘节点API 2021-06-10 19:21:45 +08:00
刘祥超
6b2a015015 创建缓存策略时默认加入缓存条件 2021-06-10 10:50:56 +08:00
刘祥超
9e74baf37b 路径规则、重写规则、URL跳转规则均支持匹配条件 2021-06-09 21:44:38 +08:00
刘祥超
b651d94bf5 请求统计增加即时、按天 2021-06-08 15:10:08 +08:00
刘祥超
b61433c5f8 更新SQL 2021-06-08 11:23:35 +08:00
刘祥超
c4d2191110 增加服务流量统计 2021-06-08 11:18:27 +08:00
刘祥超
f6159c646c 修改反向代理回源主机名默认值 2021-06-07 17:23:10 +08:00
刘祥超
9cc7d54b64 健康检查支持IPv6 2021-06-07 10:06:16 +08:00
刘祥超
05ce1d327b 边缘节点IP支持IPV6 2021-06-07 10:02:07 +08:00
刘祥超
111df18204 WAF策略支持搜索 2021-06-07 08:58:26 +08:00
刘祥超
bbcdf167b1 缓存策略列表支持搜索 2021-06-07 08:44:19 +08:00
刘祥超
d62a44b1ac 聚合服务相关日志 2021-06-06 13:40:33 +08:00
刘祥超
969bb2bcda 日志级别增加success 2021-06-06 13:40:25 +08:00
刘祥超
6dcd3f2a98 域名服务集群创建时可以选择开启访问日志 2021-06-05 15:39:02 +08:00
刘祥超
7936dc85a9 API认证超时时增加检查系统时钟提示 2021-06-04 15:34:58 +08:00
刘祥超
94c4098de5 兼容部分API老命名 2021-06-04 11:41:50 +08:00
刘祥超
5b0aef8d4a 修复用户创建服务时提示resource not found的问题 2021-06-04 11:23:01 +08:00
刘祥超
4571b95e4a 服务访问日志增加关键词搜索 2021-06-04 10:15:41 +08:00
刘祥超
b9cba4c7df DNS访问日志可以使用关键词搜索 2021-06-04 09:09:14 +08:00
刘祥超
b46b5dc05f 修复域名服务集群检测新版本节点的Bug 2021-06-03 22:08:38 +08:00
刘祥超
ef5630ba4a 实现在域名解析中使用EdgeDNS 2021-06-02 18:13:48 +08:00
刘祥超
fe6c610d56 更新SQL 2021-06-02 11:54:05 +08:00
刘祥超
442dd195ca 域名服务增加访问日志 2021-06-02 11:53:24 +08:00
刘祥超
e9e4abff03 实现域名、记录同步等API 2021-06-01 16:43:00 +08:00
刘祥超
2a3fbd080e [域名服务]实现基本的线路配置 2021-05-30 16:31:43 +08:00
刘祥超
9d7f1a2702 更新SQL 2021-05-27 17:29:31 +08:00
刘祥超
57dcbce775 实现基本的域名、记录管理 2021-05-27 17:09:07 +08:00
刘祥超
a71bf9ed79 实现最基本的域名服务节点管理 2021-05-26 14:40:05 +08:00
刘祥超
725692ce94 优化API命名 2021-05-25 17:48:55 +08:00
刘祥超
ea15096dc3 优化API命名 2021-05-25 17:08:58 +08:00
刘祥超
a07dbb718f 实现域名服务器集群管理 2021-05-25 15:49:13 +08:00
刘祥超
18381b76fb 变更版本为0.2.0 2021-05-25 15:49:04 +08:00
刘祥超
1d31245b4a 上传新SQL 2021-05-24 09:53:17 +08:00
刘祥超
f26bc337c9 缓存策略可以设置全局的缓存条件 2021-05-24 09:24:49 +08:00
刘祥超
05c2955211 SSH认证列表可以使用关键词搜索,返回内容增加用户名 2021-05-23 21:13:20 +08:00
刘祥超
f0ffa48390 节点日志增加服务ID、是否修复等字段 2021-05-23 20:46:51 +08:00
刘祥超
a472b8a3bd 修改版本 2021-05-20 19:41:33 +08:00
刘祥超
cfa2717838 实现日志消息聚合 2021-05-19 20:47:23 +08:00
刘祥超
aece12854e 节点日志可以通过日期、关键词、级别等筛选 2021-05-19 19:03:46 +08:00
刘祥超
02692029ee 增加darwin/arm64编译脚本 2021-05-13 14:57:04 +08:00
693 changed files with 51834 additions and 6856 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*_plus.go
*-plus.sh

29
LICENSE Normal file
View File

@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, LiuXiangChao
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

1
README.md Normal file
View File

@@ -0,0 +1 @@
GoEdge API节点源码

View File

@@ -6,3 +6,4 @@
./build.sh linux mips64
./build.sh linux mips64le
./build.sh darwin amd64
./build.sh darwin arm64

View File

@@ -1,81 +1,125 @@
#!/usr/bin/env bash
function build() {
ROOT=$(dirname $0)
ROOT=$(dirname "$0")
NAME="edge-api"
DIST=$ROOT/"../dist/${NAME}"
OS=${1}
ARCH=${2}
TAG=${3}
NODE_ARCHITECTS=("amd64" "arm64")
if [ -z $OS ]; then
if [ -z "$OS" ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z $ARCH ]; then
if [ -z "$ARCH" ]; then
echo "usage: build.sh OS ARCH"
exit
fi
if [ -z "$TAG" ]; then
TAG="community"
fi
VERSION=$(lookup-version $ROOT/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-v${VERSION}.zip"
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
# check edge-node
NodeVersion=$(lookup-version $ROOT"/../../EdgeNode/internal/const/const.go")
# build edge-node
NodeVersion=$(lookup-version "$ROOT""/../../EdgeNode/internal/const/const.go")
echo "building edge-node v${NodeVersion} ..."
EDGE_NODE_BUILD_SCRIPT=$ROOT"/../../EdgeNode/build/build.sh"
if [ ! -f $EDGE_NODE_BUILD_SCRIPT ]; then
if [ ! -f "$EDGE_NODE_BUILD_SCRIPT" ]; then
echo "unable to find edge-node build script 'EdgeNode/build/build.sh'"
exit
fi
cd $ROOT"/../../EdgeNode/build"
cd "$ROOT""/../../EdgeNode/build" || exit
echo "=============================="
architects=("amd64" "386" "arm64" "mips64" "mips64le")
for arch in "${architects[@]}"; do
./build.sh linux $arch
for arch in "${NODE_ARCHITECTS[@]}"; do
if [ ! -f "$ROOT""/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip" ]; then
./build.sh linux "$arch" $TAG
else
echo "use built node linux/$arch/v${NodeVersion}"
fi
done
echo "=============================="
cd -
cd - || exit
rm -f $ROOT/deploy/*.zip
for arch in "${architects[@]}"; do
cp $ROOT"/../../EdgeNode/dist/edge-node-linux-${arch}-v${NodeVersion}.zip" $ROOT/deploy/
rm -f "$ROOT"/deploy/*.zip
for arch in "${NODE_ARCHITECTS[@]}"; do
cp "$ROOT""/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip" "$ROOT"/deploy/edge-node-linux-"${arch}"-v"${NodeVersion}".zip
done
# build edge-dns
if [ "$TAG" = "plus" ]; then
DNS_ROOT=$ROOT"/../../EdgeDNS"
if [ -d "$DNS_ROOT" ]; then
DNSNodeVersion=$(lookup-version "$ROOT""/../../EdgeDNS/internal/const/const.go")
echo "building edge-dns ${DNSNodeVersion} ..."
EDGE_DNS_NODE_BUILD_SCRIPT=$ROOT"/../../EdgeDNS/build/build.sh"
if [ ! -f "$EDGE_DNS_NODE_BUILD_SCRIPT" ]; then
echo "unable to find edge-dns build script 'EdgeDNS/build/build.sh'"
exit
fi
cd "$ROOT""/../../EdgeDNS/build" || exit
echo "=============================="
architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
./build.sh linux "$arch" $TAG
done
echo "=============================="
cd - || exit
for arch in "${architects[@]}"; do
cp "$ROOT""/../../EdgeDNS/dist/edge-dns-linux-${arch}-v${DNSNodeVersion}.zip" "$ROOT"/deploy/edge-dns-linux-"${arch}"-v"${DNSNodeVersion}".zip
done
fi
fi
# build sql
echo "building sql ..."
${ROOT}/sql.sh
if [ $TAG = "plus" ]; then
echo "building sql ..."
"${ROOT}"/sql.sh
fi
# copy files
echo "copying ..."
if [ ! -d $DIST ]; then
mkdir $DIST
mkdir $DIST/bin
mkdir $DIST/configs
mkdir $DIST/logs
if [ ! -d "$DIST" ]; then
mkdir "$DIST"
mkdir "$DIST"/bin
mkdir "$DIST"/configs
mkdir "$DIST"/logs
fi
cp $ROOT/configs/api.template.yaml $DIST/configs/
cp $ROOT/configs/db.template.yaml $DIST/configs/
cp -R $ROOT/deploy $DIST/
rm -f $dist/deploy/.gitignore
cp -R $ROOT/installers $DIST/
cp -R $ROOT/resources $DIST/
rm -f $DIST/resources/ipdata/ip2region/global_region.csv
rm -f $DIST/resources/ipdata/ip2region/ip.merge.txt
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs/
cp "$ROOT"/configs/db.template.yaml "$DIST"/configs/
cp -R "$ROOT"/deploy "$DIST/"
rm -f "$DIST"/deploy/.gitignore
cp -R "$ROOT"/installers "$DIST"/
cp -R "$ROOT"/resources "$DIST"/
rm -f "$DIST"/resources/ipdata/ip2region/global_region.csv
rm -f "$DIST"/resources/ipdata/ip2region/ip.merge.txt
# building installer
echo "building installer ..."
architects=("amd64" "386")
# building edge installer
echo "building node installer ..."
architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
# TODO support arm, mips ...
env GOOS=linux GOARCH=${arch} go build --ldflags="-s -w" -o $ROOT/installers/edge-installer-helper-linux-${arch} $ROOT/../cmd/installer-helper/main.go
env GOOS=linux GOARCH="${arch}" go build -trimpath -tags $TAG --ldflags="-s -w" -o "$ROOT"/installers/edge-installer-helper-linux-"${arch}" "$ROOT"/../cmd/installer-helper/main.go
done
# building edge dns installer
echo "building dns node installer ..."
architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
# TODO support arm, mips ...
env GOOS=linux GOARCH="${arch}" go build -trimpath -tags $TAG --ldflags="-s -w" -o "$ROOT"/installers/edge-installer-dns-helper-linux-"${arch}" "$ROOT"/../cmd/installer-dns-helper/main.go
done
# building api node
env GOOS=$OS GOARCH=$ARCH go build --ldflags="-s -w" -o $DIST/bin/edge-api $ROOT/../cmd/edge-api/main.go
env GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG --ldflags="-s -w" -o "$DIST"/bin/edge-api "$ROOT"/../cmd/edge-api/main.go
# delete hidden files
find $DIST -name ".DS_Store" -delete
find $DIST -name ".gitignore" -delete
find "$DIST" -name ".DS_Store" -delete
find "$DIST" -name ".gitignore" -delete
echo "zip files"
cd "${DIST}/../" || exit
@@ -91,15 +135,15 @@ function build() {
function lookup-version() {
FILE=$1
VERSION_DATA=$(cat $FILE)
VERSION_DATA=$(cat "$FILE")
re="Version[ ]+=[ ]+\"([0-9.]+)\""
if [[ $VERSION_DATA =~ $re ]]; then
VERSION=${BASH_REMATCH[1]}
echo $VERSION
echo "$VERSION"
else
echo "could not match version"
exit
fi
}
build $1 $2
build "$1" "$2" "$3"

View File

@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/apps"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
@@ -11,6 +12,9 @@ import (
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"io/ioutil"
"log"
"os"
)
@@ -19,12 +23,12 @@ func main() {
if !Tea.IsTesting() {
Tea.Env = "prod"
}
app := apps.NewAppCmd()
var app = apps.NewAppCmd()
app.Version(teaconst.Version)
app.Product(teaconst.ProductName)
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon]")
app.Usage(teaconst.ProcessName + " [start|stop|restart|setup|upgrade|service|daemon|issues]")
app.On("setup", func() {
setupCmd := setup.NewSetupFromCmd()
var setupCmd = setup.NewSetupFromCmd()
err := setupCmd.Run()
result := maps.Map{}
if err != nil {
@@ -44,12 +48,13 @@ func main() {
_, _ = os.Stdout.Write(resultJSON)
})
app.On("upgrade", func() {
fmt.Println("start ...")
executor, err := setup.NewSQLExecutorFromCmd()
if err != nil {
fmt.Println("ERROR: " + err.Error())
return
}
err = executor.Run()
err = executor.Run(true)
if err != nil {
fmt.Println("ERROR: " + err.Error())
return
@@ -67,6 +72,96 @@ func main() {
}
fmt.Println("done")
})
app.On("goman", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "goman"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
instancesJSON, err := json.MarshalIndent(reply.Params, "", " ")
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
fmt.Println(string(instancesJSON))
}
}
})
app.On("debug", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "debug"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var isDebug = maps.NewMap(reply.Params).GetBool("debug")
if isDebug {
fmt.Println("debug on")
} else {
fmt.Println("debug off")
}
}
})
app.On("db.stmt.prepare", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "db.stmt.prepare"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var isOn = maps.NewMap(reply.Params).GetBool("isOn")
if isOn {
fmt.Println("show statements: on")
} else {
fmt.Println("show statements: off")
}
}
})
app.On("db.stmt.count", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "db.stmt.count"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var count = maps.NewMap(reply.Params).GetInt("count")
fmt.Println("prepared statements count: " + types.String(count))
}
})
app.On("issues", func() {
var flagSet = flag.NewFlagSet("issues", flag.ExitOnError)
var formatJSON = false
flagSet.BoolVar(&formatJSON, "json", false, "")
_ = flagSet.Parse(os.Args[2:])
data, err := ioutil.ReadFile(Tea.LogFile("issues.log"))
if err != nil {
if formatJSON {
fmt.Print("[]")
} else {
fmt.Println("no issues yet")
}
} else {
var issueMaps = []maps.Map{}
err = json.Unmarshal(data, &issueMaps)
if err != nil {
if formatJSON {
fmt.Print("[]")
} else {
fmt.Println("no issues yet")
}
} else {
if formatJSON {
fmt.Print(string(data))
} else {
if len(issueMaps) == 0 {
fmt.Println("no issues yet")
} else {
for i, issue := range issueMaps {
fmt.Println("issue " + types.String(i+1) + ": " + issue.GetString("message"))
}
}
}
}
}
})
app.Run(func() {
nodes.NewAPINode().Start()
})

View File

@@ -0,0 +1,73 @@
package main
import (
"flag"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
)
func main() {
cmd := ""
flag.StringVar(&cmd, "cmd", "", "command name: [unzip]")
// unzip
zipPath := ""
targetPath := ""
flag.StringVar(&zipPath, "zip", "", "zip path")
flag.StringVar(&targetPath, "target", "", "target dir")
// parse
flag.Parse()
if len(cmd) == 0 {
stderr("need '-cmd=COMMAND' argument")
} else if cmd == "test" {
// 检查是否正在运行
var sock = gosock.NewTmpSock("edge-dns")
if sock.IsListening() {
// 从systemd中停止
systemctl, _ := exec.LookPath("systemctl")
if len(systemctl) > 0 {
systemctlCmd := exec.Command(systemctl, "stop", "edge-dns")
_ = systemctlCmd.Run()
}
// 从进程中停止
if sock.IsListening() {
_, _ = sock.Send(&gosock.Command{
Code: "stop",
})
}
}
} else if cmd == "unzip" { // 解压
if len(zipPath) == 0 {
stderr("ERROR: need '-zip=PATH' argument")
return
}
if len(targetPath) == 0 {
stderr("ERROR: need '-target=TARGET' argument")
return
}
unzip := utils.NewUnzip(zipPath, targetPath)
err := unzip.Run()
if err != nil {
stderr("ERROR: " + err.Error())
return
}
stdout("ok")
} else {
stderr("ERROR: not recognized command '" + cmd + "'")
}
}
func stdout(s string) {
_, _ = os.Stdout.WriteString(s + "\n")
}
func stderr(s string) {
_, _ = os.Stderr.WriteString(s + "\n")
}

View File

@@ -3,8 +3,9 @@ package main
import (
"flag"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"net"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
)
func main() {
@@ -24,11 +25,21 @@ func main() {
stderr("need '-cmd=COMMAND' argument")
} else if cmd == "test" {
// 检查是否正在运行
path := os.TempDir() + "/edge-node.sock"
conn, err := net.Dial("unix", path)
if err == nil {
_ = conn.Close()
stderr("test node status: edge node is running now, can not install again")
var sock = gosock.NewTmpSock("edge-node")
if sock.IsListening() {
// 从systemd中停止
systemctl, _ := exec.LookPath("systemctl")
if len(systemctl) > 0 {
systemctlCmd := exec.Command(systemctl, "stop", "edge-node")
_ = systemctlCmd.Run()
}
// 从进程中停止
if sock.IsListening() {
_, _ = sock.Send(&gosock.Command{
Code: "stop",
})
}
}
} else if cmd == "unzip" { // 解压
if len(zipPath) == 0 {

3
dist/.gitignore vendored
View File

@@ -1 +1,2 @@
*.zip
*.zip
edge-api

50
go.mod
View File

@@ -1,27 +1,47 @@
module github.com/TeaOSLab/EdgeAPI
go 1.15
go 1.18
replace github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
require (
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/aliyun/alibaba-cloud-sdk-go v1.61.641
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183
github.com/andybalholm/brotli v1.0.4
github.com/cespare/xxhash/v2 v2.1.1
github.com/go-acme/lego/v4 v4.1.2
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-acme/lego/v4 v4.5.2
github.com/go-sql-driver/mysql v1.5.0
github.com/go-yaml/yaml v2.1.0+incompatible
github.com/golang/protobuf v1.4.2
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible
github.com/golang/protobuf v1.5.2
github.com/iwind/TeaGo v0.0.0-20220408111647-f36b9bba3570
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
github.com/mozillazg/go-pinyin v0.18.0
github.com/pkg/sftp v1.12.0
github.com/shirou/gopsutil v2.20.9+incompatible
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299
google.golang.org/grpc v1.32.0
google.golang.org/protobuf v1.25.0
gopkg.in/yaml.v2 v2.4.0 // indirect
github.com/shirou/gopsutil/v3 v3.22.2
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8
google.golang.org/grpc v1.45.0
google.golang.org/protobuf v1.27.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
require (
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/miekg/dns v1.1.43 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
)

456
go.sum
View File

@@ -14,83 +14,109 @@ cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNF
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
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=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/azure-sdk-for-go v32.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/go-autorest/autorest v0.1.0/go.mod h1:AKyIcETwSUFxIcs/Wnq/C+kwCtlEYGUVd7FPNb2slmg=
github.com/Azure/go-autorest/autorest v0.5.0/go.mod h1:9HLKlQjVBH6U3oDfsXOeVc56THsLPw1L03yban4xThw=
github.com/Azure/go-autorest/autorest/adal v0.1.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
github.com/Azure/go-autorest/autorest/adal v0.2.0/go.mod h1:MeS4XhScH55IST095THyTxElntu7WqB7pNbZo8Q5G3E=
github.com/Azure/go-autorest/autorest/azure/auth v0.1.0/go.mod h1:Gf7/i2FUpyb/sGBLIFxTBzrNzBo7aPXXE3ZVeDRwdpM=
github.com/Azure/go-autorest/autorest/azure/cli v0.1.0/go.mod h1:Dk8CUAt/b/PzkfeRsWzVG9Yj3ps8mS8ECztu43rdU8U=
github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA=
github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc=
github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvdeRAgDr0izn4z5Ij88=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.17/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.11/go.mod h1:nBKAnTomx8gDtl+3ZCJv2v0KACFHWTB2drffI1B68Pk=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVFWPAgOgQ/frrJgeYQJPyR5fLFmXko4=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
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/akamai/AkamaiOPEN-edgegrid-golang v0.9.18/go.mod h1:L+HB2uBoDgi3+r1pJEJcbGwyyHhd2QXaGsKLbDwtm8Q=
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.458/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.641 h1:X/Ik2DvrwICTd5hbRPjB7+s/61pk/b40HJ6XHAg2LSc=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.641/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/aws/aws-sdk-go v1.30.20/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 h1:dkj8/dxOQ4L1XpwCzRLqukvUBbxuNdz3FeyvHFnRjmo=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183/go.mod h1:pUKYbK5JQ+1Dfxk80P0qxGqe5dkxDoabbZS7zOcouyA=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/cenkalti/backoff/v4 v4.0.2 h1:JIufpQLbh4DkbQoii76ItQIUFzevQSqOLZca4eamEDs=
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.13.2/go.mod h1:27kfc1apuifUmJhp069y0+hwlKDg4bd8LWlu7oKeZvM=
github.com/cloudflare/cloudflare-go v0.20.0/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpu/goacmedns v0.0.3/go.mod h1:4MipLkI+qScwqtVxcNO6okBhbgRrr7/tKXUSgSL0teQ=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/deepmap/oapi-codegen v1.6.1/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-rendezvous v0.0.0-20200624174652-8d2f3be8b2d9/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dnsimple/dnsimple-go v0.63.0/go.mod h1:O5TJ0/U6r7AfT8niYNlmohpLbCSG+c71tQlGr9SeGrg=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/dnsimple/dnsimple-go v0.70.1/go.mod h1:F9WHww9cC76hrnwGFfAfrqdW99j3MOYasQcIwTS/aUk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exoscale/egoscale v0.23.0/go.mod h1:hRo78jkjkCDKpivQdRBEpNYF5+cVpCJCPDg2/r45KaY=
github.com/exoscale/egoscale v0.67.0/go.mod h1:wi0myUxPsV8SdEtdJHQJxFLL/wEw9fiw9Gs1PWRkvkM=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-acme/lego/v4 v4.1.2 h1:1zROppXkTbAIh7J7AydGD3dFICLIocucJY1NTH/wB64=
github.com/go-acme/lego/v4 v4.1.2/go.mod h1:pIFm5tWkXSgiAEfJ/XQCQIvX1cEvHFwbgLZyx8OVSUE=
github.com/go-acme/lego/v4 v4.5.2 h1:Gg6jta10furQZ+DRknspdFjzboBQ132RmjSgd4CJuH0=
github.com/go-acme/lego/v4 v4.5.2/go.mod h1:mL1DY809LzjvRuaxINNxsI26f5oStVhBGTpJMiinkZM=
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@@ -99,20 +125,23 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
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=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
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/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -132,18 +161,27 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -153,49 +191,77 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
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.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM=
github.com/gophercloud/gophercloud v0.7.0/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss=
github.com/gophercloud/utils v0.0.0-20200508015959-b0167b94122c/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
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/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
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/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df/go.mod h1:QMZY7/J/KSQEhKWFeDesPjMj+wCHReeknARU3wqlyN4=
github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20210125103732-4d79fc3c3d0b h1:niQL2t//041GUaqDPM5D+ldyr0Ng2WKwZJHPRLQhQtk=
github.com/iwind/TeaGo v0.0.0-20210125103732-4d79fc3c3d0b/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20210302120856-7588e79bdbe3 h1:k9K3HHMmkF7HYyIHz21AtmYH4Zdk/8OI98a8P0O8o1I=
github.com/iwind/TeaGo v0.0.0-20210302120856-7588e79bdbe3/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20210325033016-3279bdaa087d h1:FQTYJmZeCMdwM0Bz+C4h31SDBt04ap6A4JOjm+FfYwk=
github.com/iwind/TeaGo v0.0.0-20210325033016-3279bdaa087d/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f h1:r2O8PONj/KiuZjJHVHn7KlCePUIjNtgAmvLfgRafQ8o=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220408111647-f36b9bba3570 h1:zqz2FiMMkSHXWO1EsTRJDPTwX9xQ4uuyD5GAE4JGlhM=
github.com/iwind/TeaGo v0.0.0-20220408111647-f36b9bba3570/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jarcoal/httpmock v1.0.6/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.5/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=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kolo/xmlrpc v0.0.0-20200310150728-e0350524596b/go.mod h1:o03bZfuBwAXHetKXuInt4S7omeXUu62/A845kiycsSQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -209,26 +275,57 @@ 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/labbsr0x/bindman-dns-webhook v1.0.2/go.mod h1:p6b+VCXIR8NYKpDr8/dg1HKfQoRHCdcsROXKvmoehKA=
github.com/labbsr0x/goh v1.0.1/go.mod h1:8K2UhVoaWXcCU7Lxoa2omWnC8gyW8px7/lmO61c027w=
github.com/linode/linodego v0.21.0/go.mod h1:UTpq1JUZD0CZsJ8rt+0CRkqbzrp1MbGakVPt2DXY5Mk=
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible h1:1qp9iks+69h7IGLazAplzS9Ca14HAxuD5c0rbFdPGy4=
github.com/lionsoul2014/ip2region v2.2.0-release+incompatible/go.mod h1:+ZBN7PBoh5gG6/y0ZQ85vJDBe21WnfbRrQQwTfliJJI=
github.com/liquidweb/liquidweb-go v1.6.1/go.mod h1:UDcVnAMDkZxpw4Y7NOHkqoeiGacVLEIG/i5J9cyixzQ=
github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/linode/linodego v0.31.1/go.mod h1:BR0gVkCJffEdIGJSl6bHR80Ty+Uvg/2jkjmrWaFectM=
github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
github.com/liquidweb/go-lwApi v0.0.5/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs=
github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ=
github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHSUiajPQs8T9c/Rc=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
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=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mozillazg/go-pinyin v0.18.0 h1:hQompXO23/0ohH8YNjvfsAITnCQImCiR/Fny8EhIeW0=
github.com/mozillazg/go-pinyin v0.18.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
@@ -237,102 +334,162 @@ github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uY
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/nrdcg/auroradns v1.0.1/go.mod h1:y4pc0i9QXYlFCWrhWrUSIETnZgrf4KuwjDIWmmXo3JI=
github.com/nrdcg/desec v0.5.0/go.mod h1:2ejvMazkav1VdDbv2HeQO7w+Ta1CGHqzQr27ZBYTuEQ=
github.com/nrdcg/desec v0.6.0/go.mod h1:wybWg5cRrNmtXLYpUCPCLvz4jfFNEGZQEnoUiX9WqcY=
github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ=
github.com/nrdcg/freemyip v0.2.0/go.mod h1:HjF0Yz0lSb37HD2ihIyGz9esyGcxbCrrGFLPpKevbx4=
github.com/nrdcg/goinwx v0.8.1/go.mod h1:tILVc10gieBp/5PMvbcYeXM6pVQ+c9jxDZnpaR1UW7c=
github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw=
github.com/nrdcg/porkbun v0.1.1/go.mod h1:JWl/WKnguWos4mjfp4YizvvToigk9qpQwrodOk+CPoA=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/oracle/oci-go-sdk v24.2.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888=
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.12.0 h1:/f3b24xrDhkhddlaobPe2JgBqfdt+gC/NYl0QY9IOuI=
github.com/pkg/sftp v1.12.0/go.mod h1:fUqqXB5vEgVCZ131L+9say31RAri6aF6KDViawhxKK8=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
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=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rainycape/memcache v0.0.0-20150622160815-1031fa0ce2f2/go.mod h1:7tZKcyumwBO6qip7RNQ5r77yrssm9bfCowcLEBcU5IA=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg=
github.com/shirou/gopsutil v2.20.9+incompatible h1:msXs2frUV+O/JLva9EDLpuJ84PrFsdCTCQex8PUdtkQ=
github.com/shirou/gopsutil v2.20.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f/go.mod h1:CJJ5VAbozOl0yEw7nHB9+7BXTJbIn6h7W+f6Gau5IP8=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/skratchdot/open-golang v0.0.0-20160302144031-75fb7ed4208c/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
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.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ=
github.com/softlayer/softlayer-go v1.0.3/go.mod h1:6HepcfAXROz0Rf63krk5hPZyHT6qyx2MNvYyHof7ik4=
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/transip/gotransip/v6 v6.2.0/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/transip/gotransip/v6 v6.6.1/go.mod h1:pQZ36hWWRahCUXkFWlx9Hs711gLd8J4qdgLdRzmtY+g=
github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vultr/govultr v0.5.0/go.mod h1:wZZXZbYbqyY1n3AldoeYNZK4Wnmmoq6dNFkvd5TV3ss=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14/go.mod h1:RWc47jtnVuQv6+lY3c768WtXCas/Xi+U5UFc5xULmYg=
github.com/vultr/govultr/v2 v2.7.1/go.mod h1:BvOhVe6/ZpjwcoL6/unkdQshmbS9VGbowI4QT+3DGVU=
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=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
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.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277/go.mod h1:2X8KaoNd1J0lZV+PxJk/5+DGbO/tpwLR1m++a7FnB/Y=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180621125126-a49355c7e3f8/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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/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-20200622213623-75b288015ac9/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/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -363,13 +520,15 @@ 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/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -381,7 +540,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
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-20190923162816-aa69164e4478/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=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -389,8 +547,17 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200520004742-59133d7f0dd7/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -401,18 +568,21 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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 h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180622082034-63fc586f45fe/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -421,34 +591,66 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -465,13 +667,12 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
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-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -482,11 +683,13 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
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=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -521,11 +724,11 @@ google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
@@ -534,8 +737,10 @@ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
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.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
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=
@@ -544,8 +749,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
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=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -556,17 +764,20 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ns1/ns1-go.v2 v2.4.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ns1/ns1-go.v2 v2.6.2/go.mod h1:GMnKY+ZuoJ+lVLL+78uSTjwTz2jMazq6AfGKQOYhsPk=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -574,9 +785,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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=

View File

@@ -0,0 +1,71 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"strconv"
"time"
)
type BaseStorage struct {
isOk bool
version int
firewallOnly bool
}
func (this *BaseStorage) SetVersion(version int) {
this.version = version
}
func (this *BaseStorage) Version() int {
return this.version
}
func (this *BaseStorage) IsOk() bool {
return this.isOk
}
func (this *BaseStorage) SetOk(isOk bool) {
this.isOk = isOk
}
func (this *BaseStorage) SetFirewallOnly(firewallOnly bool) {
this.firewallOnly = firewallOnly
}
// Marshal 对日志进行编码
func (this *BaseStorage) Marshal(accessLog *pb.HTTPAccessLog) ([]byte, error) {
return json.Marshal(accessLog)
}
// FormatVariables 格式化字符串中的变量
func (this *BaseStorage) FormatVariables(s string) string {
var now = time.Now()
return configutils.ParseVariables(s, func(varName string) (value string) {
switch varName {
case "year":
return strconv.Itoa(now.Year())
case "month":
return fmt.Sprintf("%02d", now.Month())
case "week":
_, week := now.ISOWeek()
return fmt.Sprintf("%02d", week)
case "day":
return fmt.Sprintf("%02d", now.Day())
case "hour":
return fmt.Sprintf("%02d", now.Hour())
case "minute":
return fmt.Sprintf("%02d", now.Minute())
case "second":
return fmt.Sprintf("%02d", now.Second())
case "date":
return fmt.Sprintf("%d%02d%02d", now.Year(), now.Month(), now.Day())
}
return varName
})
}

View File

@@ -0,0 +1,99 @@
package accesslogs
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"os/exec"
"sync"
)
// CommandStorage 通过命令行存储
type CommandStorage struct {
BaseStorage
config *serverconfigs.AccessLogCommandStorageConfig
writeLocker sync.Mutex
}
func NewCommandStorage(config *serverconfigs.AccessLogCommandStorageConfig) *CommandStorage {
return &CommandStorage{config: config}
}
func (this *CommandStorage) Config() interface{} {
return this.config
}
// Start 启动
func (this *CommandStorage) Start() error {
if len(this.config.Command) == 0 {
return errors.New("'command' should not be empty")
}
return nil
}
// 写入日志
func (this *CommandStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
this.writeLocker.Lock()
defer this.writeLocker.Unlock()
cmd := exec.Command(this.config.Command, this.config.Args...)
if len(this.config.Dir) > 0 {
cmd.Dir = this.config.Dir
}
stdout := bytes.NewBuffer([]byte{})
cmd.Stdout = stdout
w, err := cmd.StdinPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = w.Write(data)
if err != nil {
logs.Error(err)
}
_, err = w.Write([]byte("\n"))
if err != nil {
logs.Error(err)
}
}
_ = w.Close()
err = cmd.Wait()
if err != nil {
logs.Error(err)
if stdout.Len() > 0 {
logs.Error(errors.New(string(stdout.Bytes())))
}
}
return nil
}
// Close 关闭
func (this *CommandStorage) Close() error {
return nil
}

View File

@@ -0,0 +1,63 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"os"
"os/exec"
"testing"
"time"
)
func TestCommandStorage_Write(t *testing.T) {
php, err := exec.LookPath("php")
if err != nil { // not found php, so we can not test
t.Log("php:", err)
return
}
cwd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
before := time.Now()
storage := NewCommandStorage(&serverconfigs.AccessLogCommandStorageConfig{
Command: php,
Args: []string{cwd + "/tests/command_storage.php"},
})
err = storage.Start()
if err != nil {
t.Fatal(err)
}
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "GET",
RequestPath: "/hello",
},
{
RequestMethod: "GET",
RequestPath: "/world",
},
{
RequestMethod: "GET",
RequestPath: "/lu",
},
{
RequestMethod: "GET",
RequestPath: "/ping",
},
})
if err != nil {
t.Fatal(err)
}
err = storage.Close()
if err != nil {
t.Fatal(err)
}
t.Log(time.Since(before).Seconds(), "seconds")
}

View File

@@ -0,0 +1,131 @@
package accesslogs
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"io/ioutil"
"net/http"
"regexp"
"strings"
"time"
)
// ESStorage ElasticSearch存储策略
type ESStorage struct {
BaseStorage
config *serverconfigs.AccessLogESStorageConfig
}
func NewESStorage(config *serverconfigs.AccessLogESStorageConfig) *ESStorage {
return &ESStorage{config: config}
}
func (this *ESStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *ESStorage) Start() error {
if len(this.config.Endpoint) == 0 {
return errors.New("'endpoint' should not be nil")
}
if !regexp.MustCompile(`(?i)^(http|https)://`).MatchString(this.config.Endpoint) {
this.config.Endpoint = "http://" + this.config.Endpoint
}
if len(this.config.Index) == 0 {
return errors.New("'index' should not be nil")
}
if len(this.config.MappingType) == 0 {
return errors.New("'mappingType' should not be nil")
}
return nil
}
// 写入日志
func (this *ESStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
bulk := &strings.Builder{}
indexName := this.FormatVariables(this.config.Index)
typeName := this.FormatVariables(this.config.MappingType)
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
if len(accessLog.RequestId) == 0 {
continue
}
opData, err := json.Marshal(map[string]interface{}{
"index": map[string]interface{}{
"_index": indexName,
"_type": typeName,
"_id": accessLog.RequestId,
},
})
if err != nil {
remotelogs.Error("ACCESS_LOG_ES_STORAGE", "write failed: "+err.Error())
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
remotelogs.Error("ACCESS_LOG_ES_STORAGE", "marshal data failed: "+err.Error())
continue
}
bulk.Write(opData)
bulk.WriteString("\n")
bulk.Write(data)
bulk.WriteString("\n")
}
if bulk.Len() == 0 {
return nil
}
req, err := http.NewRequest(http.MethodPost, this.config.Endpoint+"/_bulk", strings.NewReader(bulk.String()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", strings.ReplaceAll(teaconst.ProductName, " ", "-")+"/"+teaconst.Version)
if len(this.config.Username) > 0 || len(this.config.Password) > 0 {
req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(this.config.Username+":"+this.config.Password)))
}
client := utils.SharedHttpClient(10 * time.Second)
defer func() {
_ = req.Body.Close()
}()
resp, err := client.Do(req)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
bodyData, _ := ioutil.ReadAll(resp.Body)
return errors.New("ElasticSearch response status code: " + fmt.Sprintf("%d", resp.StatusCode) + " content: " + string(bodyData))
}
return nil
}
// Close 关闭
func (this *ESStorage) Close() error {
return nil
}

View File

@@ -0,0 +1,53 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"testing"
"time"
)
func TestESStorage_Write(t *testing.T) {
storage := NewESStorage(&serverconfigs.AccessLogESStorageConfig{
Endpoint: "http://127.0.0.1:9200",
Index: "logs",
MappingType: "accessLogs",
Username: "hello",
Password: "world",
})
err := storage.Start()
if err != nil {
t.Fatal(err)
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "POST",
RequestPath: "/1",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
TimeISO8601: "2018-07-23T22:23:35+08:00",
Header: map[string]*pb.Strings{
"Content-Type": {Values: []string{"text/html"}},
},
},
{
RequestMethod: "GET",
RequestPath: "/2",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
TimeISO8601: "2018-07-23T22:23:35+08:00",
Header: map[string]*pb.Strings{
"Content-Type": {Values: []string{"text/css"}},
},
},
})
if err != nil {
t.Fatal(err)
}
}
err = storage.Close()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,130 @@
package accesslogs
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"os"
"path/filepath"
"sync"
)
// FileStorage 文件存储策略
type FileStorage struct {
BaseStorage
config *serverconfigs.AccessLogFileStorageConfig
writeLocker sync.Mutex
files map[string]*os.File // path => *File
filesLocker sync.Mutex
}
func NewFileStorage(config *serverconfigs.AccessLogFileStorageConfig) *FileStorage {
return &FileStorage{
config: config,
}
}
func (this *FileStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *FileStorage) Start() error {
if len(this.config.Path) == 0 {
return errors.New("'path' should not be empty")
}
this.files = map[string]*os.File{}
return nil
}
// Write 写入日志
func (this *FileStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
fp := this.fp()
if fp == nil {
return errors.New("file pointer should not be nil")
}
this.writeLocker.Lock()
defer this.writeLocker.Unlock()
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = fp.Write(data)
if err != nil {
_ = this.Close()
break
}
_, _ = fp.WriteString("\n")
}
return nil
}
// Close 关闭
func (this *FileStorage) Close() error {
this.filesLocker.Lock()
defer this.filesLocker.Unlock()
var resultErr error
for _, f := range this.files {
err := f.Close()
if err != nil {
resultErr = err
}
}
return resultErr
}
func (this *FileStorage) fp() *os.File {
path := this.FormatVariables(this.config.Path)
this.filesLocker.Lock()
defer this.filesLocker.Unlock()
fp, ok := this.files[path]
if ok {
return fp
}
// 关闭其他的文件
for _, f := range this.files {
_ = f.Close()
}
// 是否创建文件目录
if this.config.AutoCreate {
dir := filepath.Dir(path)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
err = os.MkdirAll(dir, 0777)
if err != nil {
logs.Error(err)
return nil
}
}
}
// 打开新文件
fp, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logs.Error(err)
return nil
}
this.files[path] = fp
return fp
}

View File

@@ -0,0 +1,70 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"testing"
"time"
)
func TestFileStorage_Write(t *testing.T) {
storage := NewFileStorage(&serverconfigs.AccessLogFileStorageConfig{
Path: Tea.Root + "/logs/access-${date}.log",
})
err := storage.Start()
if err != nil {
t.Fatal(err)
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestPath: "/hello",
},
{
RequestPath: "/world",
},
})
if err != nil {
t.Fatal(err)
}
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestPath: "/1",
},
{
RequestPath: "/2",
},
})
if err != nil {
t.Fatal(err)
}
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "POST",
RequestPath: "/1",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
{
RequestMethod: "GET",
RequestPath: "/2",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
})
if err != nil {
t.Fatal(err)
}
}
err = storage.Close()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,33 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
// StorageInterface 日志存储接口
type StorageInterface interface {
// Version 获取版本
Version() int
// SetVersion 设置版本
SetVersion(version int)
// SetFirewallOnly 设置是否只处理防火墙相关的访问日志
SetFirewallOnly(firewallOnly bool)
IsOk() bool
SetOk(ok bool)
// Config 获取配置
Config() interface{}
// Start 开启
Start() error
// Write 写入日志
Write(accessLogs []*pb.HTTPAccessLog) error
// Close 关闭
Close() error
}

View File

@@ -0,0 +1,185 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package accesslogs
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types"
"sync"
"time"
)
var SharedStorageManager = NewStorageManager()
type StorageManager struct {
storageMap map[int64]StorageInterface // policyId => Storage
locker sync.Mutex
}
func NewStorageManager() *StorageManager {
return &StorageManager{
storageMap: map[int64]StorageInterface{},
}
}
func (this *StorageManager) Start() {
var ticker = time.NewTicker(1 * time.Minute)
if Tea.IsTesting() {
ticker = time.NewTicker(5 * time.Second)
}
// 启动时执行一次
var err = this.Loop()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "update error: "+err.Error())
}
// 循环执行
for range ticker.C {
err := this.Loop()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "update error: "+err.Error())
}
}
}
// Loop 更新
func (this *StorageManager) Loop() error {
policies, err := models.SharedHTTPAccessLogPolicyDAO.FindAllEnabledAndOnPolicies(nil)
if err != nil {
return err
}
var policyIds = []int64{}
for _, policy := range policies {
if policy.IsOn {
policyIds = append(policyIds, int64(policy.Id))
}
}
this.locker.Lock()
defer this.locker.Unlock()
// 关闭不用的
for policyId, storage := range this.storageMap {
if !lists.ContainsInt64(policyIds, policyId) {
err := storage.Close()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "close '"+types.String(policyId)+"' failed: "+err.Error())
}
delete(this.storageMap, policyId)
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "remove '"+types.String(policyId)+"'")
}
}
for _, policy := range policies {
var policyId = int64(policy.Id)
storage, ok := this.storageMap[policyId]
if ok {
// 检查配置是否有变更
if types.Int(policy.Version) != storage.Version() {
err = storage.Close()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "close policy '"+types.String(policyId)+"' failed: "+err.Error())
// 继续往下执行
}
if len(policy.Options) > 0 {
err = json.Unmarshal(policy.Options, storage.Config())
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "unmarshal policy '"+types.String(policyId)+"' config failed: "+err.Error())
storage.SetOk(false)
continue
}
}
storage.SetVersion(types.Int(policy.Version))
storage.SetFirewallOnly(policy.FirewallOnly == 1)
err := storage.Start()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"' failed: "+err.Error())
continue
}
storage.SetOk(true)
remotelogs.Println("ACCESS_LOG_STORAGE_MANAGER", "restart policy '"+types.String(policyId)+"'")
}
} else {
storage, err := this.createStorage(policy.Type, policy.Options)
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "create policy '"+types.String(policyId)+"' failed: "+err.Error())
continue
}
storage.SetVersion(types.Int(policy.Version))
storage.SetFirewallOnly(policy.FirewallOnly == 1)
this.storageMap[policyId] = storage
err = storage.Start()
if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"' failed: "+err.Error())
continue
}
storage.SetOk(true)
remotelogs.Println("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"'")
}
}
return nil
}
func (this *StorageManager) createStorage(storageType string, optionsJSON []byte) (StorageInterface, error) {
switch storageType {
case serverconfigs.AccessLogStorageTypeFile:
var config = &serverconfigs.AccessLogFileStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewFileStorage(config), nil
case serverconfigs.AccessLogStorageTypeES:
var config = &serverconfigs.AccessLogESStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewESStorage(config), nil
case serverconfigs.AccessLogStorageTypeTCP:
var config = &serverconfigs.AccessLogTCPStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewTCPStorage(config), nil
case serverconfigs.AccessLogStorageTypeSyslog:
var config = &serverconfigs.AccessLogSyslogStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewSyslogStorage(config), nil
case serverconfigs.AccessLogStorageTypeCommand:
var config = &serverconfigs.AccessLogCommandStorageConfig{}
if len(optionsJSON) > 0 {
err := json.Unmarshal(optionsJSON, config)
if err != nil {
return nil, err
}
}
return NewCommandStorage(config), nil
}
return nil, errors.New("invalid policy type '" + storageType + "'")
}

View File

@@ -0,0 +1,17 @@
package accesslogs
import (
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestStorageManager_Loop(t *testing.T) {
dbs.NotifyReady()
var storage = NewStorageManager()
err := storage.Loop()
if err != nil {
t.Fatal(err)
}
t.Log(storage.storageMap)
}

View File

@@ -0,0 +1,15 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
// 写入日志
func (this *StorageManager) Write(policyId int64, accessLogs []*pb.HTTPAccessLog) error {
return nil
}

View File

@@ -0,0 +1,146 @@
package accesslogs
import (
"bytes"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"os/exec"
"runtime"
"strconv"
)
type SyslogStorageProtocol = string
const (
SyslogStorageProtocolTCP SyslogStorageProtocol = "tcp"
SyslogStorageProtocolUDP SyslogStorageProtocol = "udp"
SyslogStorageProtocolNone SyslogStorageProtocol = "none"
SyslogStorageProtocolSocket SyslogStorageProtocol = "socket"
)
type SyslogStoragePriority = int
// SyslogStorage syslog存储策略
type SyslogStorage struct {
BaseStorage
config *serverconfigs.AccessLogSyslogStorageConfig
exe string
}
func NewSyslogStorage(config *serverconfigs.AccessLogSyslogStorageConfig) *SyslogStorage {
return &SyslogStorage{config: config}
}
func (this *SyslogStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *SyslogStorage) Start() error {
if runtime.GOOS != "linux" {
return errors.New("'syslog' storage only works on linux")
}
exe, err := exec.LookPath("logger")
if err != nil {
return err
}
this.exe = exe
return nil
}
// 写入日志
func (this *SyslogStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
args := []string{}
if len(this.config.Tag) > 0 {
args = append(args, "-t", this.config.Tag)
}
if this.config.Priority >= 0 {
args = append(args, "-p", strconv.Itoa(this.config.Priority))
}
switch this.config.Protocol {
case SyslogStorageProtocolTCP:
args = append(args, "-T")
if len(this.config.ServerAddr) > 0 {
args = append(args, "-n", this.config.ServerAddr)
}
if this.config.ServerPort > 0 {
args = append(args, "-P", strconv.Itoa(this.config.ServerPort))
}
case SyslogStorageProtocolUDP:
args = append(args, "-d")
if len(this.config.ServerAddr) > 0 {
args = append(args, "-n", this.config.ServerAddr)
}
if this.config.ServerPort > 0 {
args = append(args, "-P", strconv.Itoa(this.config.ServerPort))
}
case SyslogStorageProtocolSocket:
args = append(args, "-u")
args = append(args, this.config.Socket)
case SyslogStorageProtocolNone:
// do nothing
}
args = append(args, "-S", "10240")
var cmd = exec.Command(this.exe, args...)
var stderrBuffer = &bytes.Buffer{}
cmd.Stderr = stderrBuffer
w, err := cmd.StdinPipe()
if err != nil {
return err
}
err = cmd.Start()
if err != nil {
return err
}
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
remotelogs.Error("ACCESS_LOG_POLICY_SYSLOG", "marshal accesslog failed: "+err.Error())
continue
}
_, err = w.Write(data)
if err != nil {
logs.Error(err)
}
_, err = w.Write([]byte("\n"))
if err != nil {
remotelogs.Error("ACCESS_LOG_POLICY_SYSLOG", "write accesslog failed: "+err.Error())
}
}
_ = w.Close()
err = cmd.Wait()
if err != nil {
return errors.New("send syslog failed: " + err.Error() + ", stderr: " + stderrBuffer.String())
}
return nil
}
// Close 关闭
func (this *SyslogStorage) Close() error {
return nil
}

View File

@@ -0,0 +1,114 @@
package accesslogs
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/logs"
"net"
"sync"
)
// TCPStorage TCP存储策略
type TCPStorage struct {
BaseStorage
config *serverconfigs.AccessLogTCPStorageConfig
writeLocker sync.Mutex
connLocker sync.Mutex
conn net.Conn
}
func NewTCPStorage(config *serverconfigs.AccessLogTCPStorageConfig) *TCPStorage {
return &TCPStorage{config: config}
}
func (this *TCPStorage) Config() interface{} {
return this.config
}
// Start 开启
func (this *TCPStorage) Start() error {
if len(this.config.Network) == 0 {
return errors.New("'network' should not be empty")
}
if len(this.config.Addr) == 0 {
return errors.New("'addr' should not be empty")
}
return nil
}
// 写入日志
func (this *TCPStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
if len(accessLogs) == 0 {
return nil
}
err := this.connect()
if err != nil {
return err
}
conn := this.conn
if conn == nil {
return errors.New("connection should not be nil")
}
this.writeLocker.Lock()
defer this.writeLocker.Unlock()
for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog)
if err != nil {
logs.Error(err)
continue
}
_, err = conn.Write(data)
if err != nil {
_ = this.Close()
break
}
_, err = conn.Write([]byte("\n"))
if err != nil {
_ = this.Close()
break
}
}
return nil
}
// Close 关闭
func (this *TCPStorage) Close() error {
this.connLocker.Lock()
defer this.connLocker.Unlock()
if this.conn != nil {
err := this.conn.Close()
this.conn = nil
return err
}
return nil
}
func (this *TCPStorage) connect() error {
this.connLocker.Lock()
defer this.connLocker.Unlock()
if this.conn != nil {
return nil
}
conn, err := net.Dial(this.config.Network, this.config.Addr)
if err != nil {
return err
}
this.conn = conn
return nil
}

View File

@@ -0,0 +1,72 @@
package accesslogs
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"net"
"testing"
"time"
)
func TestTCPStorage_Write(t *testing.T) {
go func() {
server, err := net.Listen("tcp", "127.0.0.1:9981")
if err != nil {
t.Error(err)
return
}
for {
conn, err := server.Accept()
if err != nil {
break
}
buf := make([]byte, 1024)
for {
n, err := conn.Read(buf)
if n > 0 {
t.Log(string(buf[:n]))
}
if err != nil {
break
}
}
break
}
_ = server.Close()
}()
storage := NewTCPStorage(&serverconfigs.AccessLogTCPStorageConfig{
Network: "tcp",
Addr: "127.0.0.1:9981",
})
err := storage.Start()
if err != nil {
t.Fatal(err)
}
{
err = storage.Write([]*pb.HTTPAccessLog{
{
RequestMethod: "POST",
RequestPath: "/1",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
{
RequestMethod: "GET",
RequestPath: "/2",
TimeLocal: time.Now().Format("2/Jan/2006:15:04:05 -0700"),
},
})
if err != nil {
t.Fatal(err)
}
}
time.Sleep(2 * time.Second)
err = storage.Close()
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,24 @@
<?php
// test command storage
// open access log file
$fp = fopen("/tmp/goedge-command-storage.log", "a+");
// read access logs from stdin
$stdin = fopen("php://stdin", "r");
while(true) {
if (feof($stdin)) {
break;
}
$line = fgets($stdin);
// write to access log file
fwrite($fp, $line);
}
// close file pointers
fclose($fp);
fclose($stdin);
?>

8
internal/acme/account.go Normal file
View File

@@ -0,0 +1,8 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package acme
type Account struct {
EABKid string
EABKey string
}

View File

@@ -63,6 +63,7 @@ func TestGenerate(t *testing.T) {
}
config := lego.NewConfig(myUser)
config.CADirURL = "https://acme.zerossl.com/v2/DV90"
config.Certificate.KeyType = certcrypto.RSA2048
client, err := lego.NewClient(config)
@@ -91,3 +92,53 @@ func TestGenerate(t *testing.T) {
}
t.Log(certificates)
}
func TestGenerate_EAB(t *testing.T) {
acmelog.Logger = log.New(ioutil.Discard, "", log.LstdFlags)
// 生成私钥
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatal(err)
}
myUser := &MyUser{
Email: "test1@teaos.cn",
key: privateKey,
}
config := lego.NewConfig(myUser)
config.CADirURL = "https://acme.zerossl.com/v2/DV90"
config.Certificate.KeyType = certcrypto.RSA2048
client, err := lego.NewClient(config)
if err != nil {
t.Fatal(err)
}
err = client.Challenge.SetDNS01Provider(&MyProvider{t: t})
if err != nil {
t.Fatal(err)
}
// New users will need to register
var reg *registration.Resource
if client.GetExternalAccountRequired() {
reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: "KID",
HmacEncoded: "HAMC KEY",
})
} else {
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
}
myUser.Registration = reg
request := certificate.ObtainRequest{
Domains: []string{"teaos.cn"},
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
t.Fatal(err)
}
t.Log(certificates)
}

View File

@@ -2,37 +2,42 @@ package acme
import (
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/go-acme/lego/v4/challenge/dns01"
"strings"
)
type DNSProvider struct {
raw dnsclients.ProviderInterface
raw dnsclients.ProviderInterface
dnsDomain string
}
func NewDNSProvider(raw dnsclients.ProviderInterface) *DNSProvider {
return &DNSProvider{raw: raw}
func NewDNSProvider(raw dnsclients.ProviderInterface, dnsDomain string) *DNSProvider {
return &DNSProvider{
raw: raw,
dnsDomain: dnsDomain,
}
}
func (this *DNSProvider) Present(domain, token, keyAuth string) error {
fqdn, value := dns01.GetRecord(domain, keyAuth)
// 设置记录
index := strings.Index(fqdn, "."+domain)
index := strings.Index(fqdn, "."+this.dnsDomain)
if index < 0 {
return errors.New("invalid fqdn value")
}
recordName := fqdn[:index]
record, err := this.raw.QueryRecord(domain, recordName, dnsclients.RecordTypeTXT)
record, err := this.raw.QueryRecord(this.dnsDomain, recordName, dnstypes.RecordTypeTXT)
if err != nil {
return errors.New("query DNS record failed: " + err.Error())
}
if record == nil {
err = this.raw.AddRecord(domain, &dnsclients.Record{
err = this.raw.AddRecord(this.dnsDomain, &dnstypes.Record{
Id: "",
Name: recordName,
Type: dnsclients.RecordTypeTXT,
Type: dnstypes.RecordTypeTXT,
Value: value,
Route: this.raw.DefaultRoute(),
})
@@ -40,9 +45,9 @@ func (this *DNSProvider) Present(domain, token, keyAuth string) error {
return errors.New("create DNS record failed: " + err.Error())
}
} else {
err = this.raw.UpdateRecord(domain, record, &dnsclients.Record{
err = this.raw.UpdateRecord(this.dnsDomain, record, &dnstypes.Record{
Name: recordName,
Type: dnsclients.RecordTypeTXT,
Type: dnstypes.RecordTypeTXT,
Value: value,
Route: this.raw.DefaultRoute(),
})

View File

@@ -0,0 +1,43 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package acme
const DefaultProviderCode = "letsencrypt"
type Provider struct {
Name string `json:"name"`
Code string `json:"code"`
Description string `json:"description"`
APIURL string `json:"apiURL"`
RequireEAB bool `json:"requireEAB"`
EABDescription string `json:"eabDescription"`
}
func FindAllProviders() []*Provider {
return []*Provider{
{
Name: "Let's Encrypt",
Code: DefaultProviderCode,
Description: "非盈利组织Let's Encrypt提供的免费证书。",
APIURL: "https://acme-v02.api.letsencrypt.org/directory",
RequireEAB: false,
},
{
Name: "ZeroSSL",
Code: "zerossl",
Description: "相关文档 <a href=\"https://zerossl.com/documentation/acme/\" target=\"_blank\">https://zerossl.com/documentation/acme/</a>。",
APIURL: "https://acme.zerossl.com/v2/DV90",
RequireEAB: true,
EABDescription: "在官网<a href=\"https://app.zerossl.com/developer\" target=\"_blank\">[Developer]</a>页面底部点击\"Generate\"按钮生成。",
},
}
}
func FindProviderWithCode(code string) *Provider {
for _, provider := range FindAllProviders() {
if provider.Code == code {
return provider
}
}
return nil
}

View File

@@ -1,6 +1,7 @@
package acme
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/certificate"
@@ -33,6 +34,14 @@ func (this *Request) OnAuth(onAuth AuthCallback) {
}
func (this *Request) Run() (certData []byte, keyData []byte, err error) {
if this.task.Provider == nil {
err = errors.New("provider should not be nil")
return
}
if this.task.Provider.RequireEAB && this.task.Account == nil {
err = errors.New("account should not be nil when provider require EAB")
}
switch this.task.AuthType {
case AuthTypeDNS:
return this.runDNS()
@@ -68,6 +77,8 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
config := lego.NewConfig(this.task.User)
config.Certificate.KeyType = certcrypto.RSA2048
config.CADirURL = this.task.Provider.APIURL
config.UserAgent = teaconst.ProductName + "/" + teaconst.Version
client, err := lego.NewClient(config)
if err != nil {
@@ -82,17 +93,32 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
return nil, nil, err
}
} else {
resource, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, nil, err
}
err = this.task.User.Register(resource)
if err != nil {
return nil, nil, err
if this.task.Provider.RequireEAB {
resource, err := client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: this.task.Account.EABKid,
HmacEncoded: this.task.Account.EABKey,
})
if err != nil {
return nil, nil, errors.New("register user failed: " + err.Error())
}
err = this.task.User.Register(resource)
if err != nil {
return nil, nil, err
}
} else {
resource, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, nil, err
}
err = this.task.User.Register(resource)
if err != nil {
return nil, nil, err
}
}
}
err = client.Challenge.SetDNS01Provider(NewDNSProvider(this.task.DNSProvider))
err = client.Challenge.SetDNS01Provider(NewDNSProvider(this.task.DNSProvider, this.task.DNSDomain))
if err != nil {
return nil, nil, err
}
@@ -104,7 +130,7 @@ func (this *Request) runDNS() (certData []byte, keyData []byte, err error) {
}
certResource, err := client.Certificate.Obtain(request)
if err != nil {
return nil, nil, err
return nil, nil, errors.New("obtain cert failed: " + err.Error())
}
return certResource.Certificate, certResource.PrivateKey, nil
@@ -122,6 +148,8 @@ func (this *Request) runHTTP() (certData []byte, keyData []byte, err error) {
config := lego.NewConfig(this.task.User)
config.Certificate.KeyType = certcrypto.RSA2048
config.CADirURL = this.task.Provider.APIURL
config.UserAgent = teaconst.ProductName + "/" + teaconst.Version
client, err := lego.NewClient(config)
if err != nil {
@@ -136,13 +164,28 @@ func (this *Request) runHTTP() (certData []byte, keyData []byte, err error) {
return nil, nil, err
}
} else {
resource, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, nil, err
}
err = this.task.User.Register(resource)
if err != nil {
return nil, nil, err
if this.task.Provider.RequireEAB {
resource, err := client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
TermsOfServiceAgreed: true,
Kid: this.task.Account.EABKid,
HmacEncoded: this.task.Account.EABKey,
})
if err != nil {
return nil, nil, errors.New("register user failed: " + err.Error())
}
err = this.task.User.Register(resource)
if err != nil {
return nil, nil, err
}
} else {
resource, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
return nil, nil, err
}
err = this.task.User.Register(resource)
if err != nil {
return nil, nil, err
}
}
}

View File

@@ -39,10 +39,10 @@ func TestRequest_Run_DNS(t *testing.T) {
req := NewRequest(&Task{
User: user,
Type: TaskTypeDNS,
AuthType: AuthTypeDNS,
DNSProvider: dnsProvider,
DNSDomain: "yun4s.cn",
Domains: []string{"yun4s.cn"},
Domains: []string{"www.yun4s.cn"},
})
certData, keyData, err := req.Run()
if err != nil {
@@ -74,9 +74,9 @@ func TestRequest_Run_HTTP(t *testing.T) {
}
req := NewRequest(&Task{
User: user,
Type: TaskTypeHTTP,
Domains: []string{"teaos.cn", "www.teaos.cn", "meloy.cn"},
User: user,
AuthType: AuthTypeHTTP,
Domains: []string{"teaos.cn", "www.teaos.cn", "meloy.cn"},
})
certData, keyData, err := req.runHTTP()
if err != nil {

View File

@@ -10,6 +10,8 @@ const (
)
type Task struct {
Provider *Provider
Account *Account
User *User
AuthType AuthType
Domains []string

View File

@@ -2,8 +2,11 @@ package apps
import (
"fmt"
"github.com/iwind/TeaGo/Tea"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"os"
"os/exec"
"runtime"
@@ -11,7 +14,7 @@ import (
"time"
)
// App命令帮助
// AppCmd App命令帮助
type AppCmd struct {
product string
version string
@@ -20,10 +23,14 @@ type AppCmd struct {
appendStrings []string
directives []*Directive
sock *gosock.Sock
}
func NewAppCmd() *AppCmd {
return &AppCmd{}
return &AppCmd{
sock: gosock.NewTmpSock(teaconst.ProcessName),
}
}
type CommandHelpOption struct {
@@ -31,25 +38,25 @@ type CommandHelpOption struct {
Description string
}
// 产品
// Product 产品
func (this *AppCmd) Product(product string) *AppCmd {
this.product = product
return this
}
// 版本
// Version 版本
func (this *AppCmd) Version(version string) *AppCmd {
this.version = version
return this
}
// 使用方法
// Usage 使用方法
func (this *AppCmd) Usage(usage string) *AppCmd {
this.usage = usage
return this
}
// 选项
// Option 选项
func (this *AppCmd) Option(code string, description string) *AppCmd {
this.options = append(this.options, &CommandHelpOption{
Code: code,
@@ -58,13 +65,13 @@ func (this *AppCmd) Option(code string, description string) *AppCmd {
return this
}
// 附加内容
// Append 附加内容
func (this *AppCmd) Append(appendString string) *AppCmd {
this.appendStrings = append(this.appendStrings, appendString)
return this
}
// 打印
// Print 打印
func (this *AppCmd) Print() {
fmt.Println(this.product + " v" + this.version)
@@ -103,7 +110,7 @@ func (this *AppCmd) Print() {
}
}
// 添加指令
// On 添加指令
func (this *AppCmd) On(arg string, callback func()) {
this.directives = append(this.directives, &Directive{
Arg: arg,
@@ -111,7 +118,7 @@ func (this *AppCmd) On(arg string, callback func()) {
})
}
// 运行
// Run 运行
func (this *AppCmd) Run(main func()) {
// 获取参数
args := os.Args[1:]
@@ -150,9 +157,6 @@ func (this *AppCmd) Run(main func()) {
return
}
// 记录PID
_ = this.writePid()
// 日志
writer := new(LogWriter)
writer.Init()
@@ -164,7 +168,7 @@ func (this *AppCmd) Run(main func()) {
// 版本号
func (this *AppCmd) runVersion() {
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH+")")
fmt.Println(this.product+" v"+this.version, "(build: "+runtime.Version(), runtime.GOOS, runtime.GOARCH, teaconst.Tag+")")
}
// 帮助
@@ -174,9 +178,9 @@ func (this *AppCmd) runHelp() {
// 启动
func (this *AppCmd) runStart() {
proc := this.checkPid()
if proc != nil {
fmt.Println(this.product+" already started, pid:", proc.Pid)
var pid = this.getPID()
if pid > 0 {
fmt.Println(this.product+" already started, pid:", pid)
return
}
@@ -192,18 +196,15 @@ func (this *AppCmd) runStart() {
// 停止
func (this *AppCmd) runStop() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
return
}
// 停止进程
_ = proc.Kill()
_, _ = this.sock.Send(&gosock.Command{Code: "stop"})
// 在Windows上经常不能及时释放资源
_ = DeletePid(Tea.Root + "/bin/pid")
fmt.Println(this.product+" stopped ok, pid:", proc.Pid)
fmt.Println(this.product+" stopped ok, pid:", types.String(pid))
}
// 重启
@@ -215,20 +216,24 @@ func (this *AppCmd) runRestart() {
// 状态
func (this *AppCmd) runStatus() {
proc := this.checkPid()
if proc == nil {
var pid = this.getPID()
if pid == 0 {
fmt.Println(this.product + " not started yet")
} else {
fmt.Println(this.product + " is running, pid: " + fmt.Sprintf("%d", proc.Pid))
return
}
fmt.Println(this.product + " is running, pid: " + types.String(pid))
}
// 检查PID
func (this *AppCmd) checkPid() *os.Process {
return CheckPid(Tea.Root + "/bin/pid")
}
// 获取当前的PID
func (this *AppCmd) getPID() int {
if !this.sock.IsListening() {
return 0
}
// 写入PID
func (this *AppCmd) writePid() error {
return WritePid(Tea.Root + "/bin/pid")
reply, err := this.sock.Send(&gosock.Command{Code: "pid"})
if err != nil {
return 0
}
return maps.NewMap(reply.Params).GetInt("pid")
}

View File

@@ -1,17 +0,0 @@
// +build !windows
package apps
import (
"os"
"syscall"
)
// lock file
func LockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
}
func UnlockFile(fp *os.File) error {
return syscall.Flock(int(fp.Fd()), syscall.LOCK_UN)
}

View File

@@ -1,17 +0,0 @@
// +build windows
package apps
import (
"errors"
"os"
)
// lock file
func LockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}
func UnlockFile(fp *os.File) error {
return errors.New("not implemented on windows")
}

View File

@@ -1,51 +1,108 @@
package apps
import (
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/utils/sizes"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/time"
timeutil "github.com/iwind/TeaGo/utils/time"
"log"
"os"
"runtime"
"strconv"
"strings"
)
type LogWriter struct {
fileAppender *files.Appender
fp *os.File
c chan string
}
func (this *LogWriter) Init() {
// 创建目录
dir := files.NewFile(Tea.LogDir())
var dir = files.NewFile(Tea.LogDir())
if !dir.Exists() {
err := dir.Mkdir()
if err != nil {
log.Println("[error]" + err.Error())
log.Println("[LOG]create log dir failed: " + err.Error())
}
}
logFile := files.NewFile(Tea.LogFile("run.log"))
// 打开要写入的日志文件
appender, err := logFile.Appender()
var logPath = Tea.LogFile("run.log")
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
logs.Error(err)
log.Println("[LOG]open log file failed: " + err.Error())
} else {
this.fileAppender = appender
this.fp = fp
}
this.c = make(chan string, 1024)
// 异步写入文件
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
if fp != nil {
goman.New(func() {
var totalSize int64 = 0
stat, err := fp.Stat()
if err == nil {
totalSize = stat.Size()
}
for message := range this.c {
totalSize += int64(len(message))
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[LOG]write log failed: " + err.Error())
} else {
// 如果太大则Truncate
if totalSize > maxFileSize {
_ = fp.Truncate(0)
totalSize = 0
}
}
}
})
}
}
func (this *LogWriter) Write(message string) {
log.Println(message)
backgroundEnv, _ := os.LookupEnv("EdgeBackground")
if backgroundEnv != "on" {
// 文件和行号
var file string
var line int
if Tea.IsTesting() {
var callDepth = 3
var ok bool
_, file, line, ok = runtime.Caller(callDepth)
if ok {
file = this.packagePath(file)
}
}
if this.fileAppender != nil {
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[error]" + err.Error())
if len(file) > 0 {
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
} else {
log.Println(message)
}
}
this.c <- message
}
func (this *LogWriter) Close() {
if this.fileAppender != nil {
_ = this.fileAppender.Close()
if this.fp != nil {
_ = this.fp.Close()
}
close(this.c)
}
func (this *LogWriter) packagePath(path string) string {
var pieces = strings.Split(path, "/")
if len(pieces) >= 2 {
return strings.Join(pieces[len(pieces)-2:], "/")
}
return path
}

View File

@@ -1,113 +0,0 @@
package apps
import (
"fmt"
"github.com/iwind/TeaGo/types"
"io/ioutil"
"os"
"runtime"
)
var pidFileList = []*os.File{}
// 检查Pid
func CheckPid(path string) *os.Process {
// windows上打开的文件是不能删除的
if runtime.GOOS == "windows" {
if os.Remove(path) == nil {
return nil
}
}
file, err := os.Open(path)
if err != nil {
return nil
}
defer func() {
_ = file.Close()
}()
// 是否能取得Lock
err = LockFile(file)
if err == nil {
_ = UnlockFile(file)
return nil
}
pidBytes, err := ioutil.ReadAll(file)
if err != nil {
return nil
}
pid := types.Int(string(pidBytes))
if pid <= 0 {
return nil
}
proc, _ := os.FindProcess(pid)
return proc
}
// 写入Pid
func WritePid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getpid()))
if err != nil {
return err
}
return nil
}
// 写入Ppid
func WritePpid(path string) error {
fp, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY|os.O_RDONLY, 0666)
if err != nil {
return err
}
if runtime.GOOS != "windows" {
err = LockFile(fp)
if err != nil {
return err
}
}
pidFileList = append(pidFileList, fp) // hold the file pointers
_, err = fp.WriteString(fmt.Sprintf("%d", os.Getppid()))
if err != nil {
return err
}
return nil
}
// 删除Pid
func DeletePid(path string) error {
_, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return nil
}
return err
}
for _, fp := range pidFileList {
_ = UnlockFile(fp)
_ = fp.Close()
}
return os.Remove(path)
}

View File

@@ -1,19 +1,17 @@
package configs
import (
"fmt"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/go-yaml/yaml"
"github.com/iwind/TeaGo/Tea"
"gopkg.in/yaml.v3"
"io/ioutil"
"os"
"path/filepath"
)
var sharedAPIConfig *APIConfig = nil
var PaddingId string
// API节点配置
// APIConfig API节点配置
type APIConfig struct {
NodeId string `yaml:"nodeId" json:"nodeId"`
Secret string `yaml:"secret" json:"secret"`
@@ -21,7 +19,7 @@ type APIConfig struct {
numberId int64 // 数字ID
}
// 获取共享配置
// SharedAPIConfig 获取共享配置
func SharedAPIConfig() (*APIConfig, error) {
sharedLocker.Lock()
defer sharedLocker.Unlock()
@@ -72,7 +70,7 @@ func SharedAPIConfig() (*APIConfig, error) {
{
dbConfigFile := Tea.ConfigFile("db.yaml")
_, err := os.Stat(dbConfigFile)
if err == nil {
if err != nil {
paths := []string{}
homeDir, homeErr := os.UserHomeDir()
if homeErr == nil {
@@ -96,18 +94,18 @@ func SharedAPIConfig() (*APIConfig, error) {
return config, nil
}
// 设置数字ID
// SetNumberId 设置数字ID
func (this *APIConfig) SetNumberId(numberId int64) {
this.numberId = numberId
PaddingId = fmt.Sprintf("%08d", numberId)
teaconst.NodeId = numberId
}
// 获取数字ID
// NumberId 获取数字ID
func (this *APIConfig) NumberId() int64 {
return this.numberId
}
// 保存到文件
// WriteFile 保存到文件
func (this *APIConfig) WriteFile(path string) error {
data, err := yaml.Marshal(this)
if err != nil {

9
internal/const/build.go Normal file
View File

@@ -0,0 +1,9 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package teaconst
const BuildCommunity = true
const BuildPlus = false
const Tag = "community"

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "0.1.0"
Version = "0.4.9"
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -18,8 +18,13 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "0.1.0"
UserNodeVersion = "0.0.7"
AuthorityNodeVersion = "0.0.1"
MonitorNodeVersion = "0.0.1"
NodeVersion = "0.4.9"
UserNodeVersion = "0.3.5"
AuthorityNodeVersion = "0.0.2"
MonitorNodeVersion = "0.0.4"
DNSNodeVersion = "0.2.4"
ReportNodeVersion = "0.1.1"
// SQLVersion SQL版本号
SQLVersion = "2"
)

10
internal/const/vars.go Normal file
View File

@@ -0,0 +1,10 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package teaconst
var (
IsPlus = false
MaxNodes int32 = 0
NodeId int64 = 0
Debug = false
)

View File

@@ -3,6 +3,7 @@ package db
import (
"database/sql"
"database/sql/driver"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
@@ -33,7 +34,7 @@ func TestDB_Instance(t *testing.T) {
if err == driver.ErrBadConn {
return
}
t.Fatal(i, "exec:", err)
t.Error(i, "exec:", err)
}
time.Sleep(1 * time.Second)
}
@@ -42,3 +43,13 @@ func TestDB_Instance(t *testing.T) {
}
time.Sleep(100 * time.Second)
}
func TestDB_Reuse(t *testing.T) {
var dao = models.NewVersionDAO()
for i := 0; i < 20_000; i++ {
_, _, err := dao.Query(nil).Attr("version", i).Reuse(true).FindOne()
if err != nil {
t.Fatal(err)
}
}
}

View File

@@ -0,0 +1,80 @@
package accounts
import (
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAccountDailyStatDAO dbs.DAO
func NewUserAccountDailyStatDAO() *UserAccountDailyStatDAO {
return dbs.NewDAO(&UserAccountDailyStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccountDailyStats",
Model: new(UserAccountDailyStat),
PkName: "id",
},
}).(*UserAccountDailyStatDAO)
}
var SharedUserAccountDailyStatDAO *UserAccountDailyStatDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountDailyStatDAO = NewUserAccountDailyStatDAO()
})
}
// UpdateDailyStat 更新当天统计数据
func (this *UserAccountDailyStatDAO) UpdateDailyStat(tx *dbs.Tx) error {
var day = timeutil.Format("Ymd")
var month = timeutil.Format("Ym")
income, err := SharedUserAccountLogDAO.SumDailyEventTypes(tx, day, userconfigs.AccountIncomeEventTypes)
if err != nil {
return err
}
expense, err := SharedUserAccountLogDAO.SumDailyEventTypes(tx, day, userconfigs.AccountExpenseEventTypes)
if err != nil {
return err
}
if expense < 0 {
expense = -expense
}
return this.Query(tx).
InsertOrUpdateQuickly(maps.Map{
"day": day,
"month": month,
"income": income,
"expense": expense,
}, maps.Map{
"income": income,
"expense": expense,
})
}
// FindDailyStats 查看按天统计
func (this *UserAccountDailyStatDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*UserAccountDailyStat, err error) {
_, err = this.Query(tx).
Between("day", dayFrom, dayTo).
Slice(&result).
FindAll()
return
}
// FindMonthlyStats 查看某月统计
func (this *UserAccountDailyStatDAO) FindMonthlyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*UserAccountDailyStat, err error) {
_, err = this.Query(tx).
Result("SUM(income) AS income", "SUM(expense) AS expense", "month").
Between("day", dayFrom, dayTo).
Group("month").
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,6 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,22 @@
package accounts
// UserAccountDailyStat 账户每日统计
type UserAccountDailyStat struct {
Id uint32 `field:"id"` // ID
Day string `field:"day"` // YYYYMMDD
Month string `field:"month"` // YYYYMM
Income float64 `field:"income"` // 收入
Expense float64 `field:"expense"` // 支出
}
type UserAccountDailyStatOperator struct {
Id interface{} // ID
Day interface{} // YYYYMMDD
Month interface{} // YYYYMM
Income interface{} // 收入
Expense interface{} // 支出
}
func NewUserAccountDailyStatOperator() *UserAccountDailyStatOperator {
return &UserAccountDailyStatOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,253 @@
package accounts
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"time"
)
func init() {
dbs.OnReadyDone(func() {
goman.New(func() {
// 自动支付账单任务
var ticker = time.NewTicker(12 * time.Hour)
for range ticker.C {
if SharedUserAccountDAO.Instance != nil {
err := SharedUserAccountDAO.Instance.RunTx(func(tx *dbs.Tx) error {
return SharedUserAccountDAO.PayBills(tx)
})
if err != nil {
remotelogs.Error("USER_ACCOUNT_DAO", "pay bills task failed: "+err.Error())
}
}
}
})
})
}
type UserAccountDAO dbs.DAO
func NewUserAccountDAO() *UserAccountDAO {
return dbs.NewDAO(&UserAccountDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccounts",
Model: new(UserAccount),
PkName: "id",
},
}).(*UserAccountDAO)
}
var SharedUserAccountDAO *UserAccountDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountDAO = NewUserAccountDAO()
})
}
// FindUserAccountWithUserId 根据用户ID查找用户账户
func (this *UserAccountDAO) FindUserAccountWithUserId(tx *dbs.Tx, userId int64) (*UserAccount, error) {
if userId <= 0 {
return nil, errors.New("invalid userId '" + types.String(userId) + "'")
}
// 用户是否存在
user, err := models.SharedUserDAO.FindEnabledUser(tx, userId, nil)
if err != nil {
return nil, err
}
if user == nil {
return nil, errors.New("invalid userId '" + types.String(userId) + "'")
}
account, err := this.Query(tx).
Attr("userId", userId).
Find()
if err != nil {
return nil, err
}
if account != nil {
return account.(*UserAccount), nil
}
var op = NewUserAccountOperator()
op.UserId = userId
_, err = this.SaveInt64(tx, op)
if err != nil {
return nil, err
}
return this.FindUserAccountWithUserId(tx, userId)
}
// FindUserAccountWithAccountId 根据ID查找用户账户
func (this *UserAccountDAO) FindUserAccountWithAccountId(tx *dbs.Tx, accountId int64) (*UserAccount, error) {
one, err := this.Query(tx).
Pk(accountId).
Find()
if one != nil {
return one.(*UserAccount), nil
}
return nil, err
}
// UpdateUserAccount 操作用户账户
func (this *UserAccountDAO) UpdateUserAccount(tx *dbs.Tx, accountId int64, delta float32, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
account, err := this.FindUserAccountWithAccountId(tx, accountId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid account id '" + types.String(accountId) + "'")
}
var userId = int64(account.UserId)
var deltaFloat64 = float64(delta)
if deltaFloat64 < 0 && account.Total < -deltaFloat64 {
return errors.New("not enough account quota to decrease")
}
// 操作账户
err = this.Query(tx).
Pk(account.Id).
Set("total", dbs.SQL("total+:delta")).
Param("delta", delta).
UpdateQuickly()
if err != nil {
return err
}
// 生成日志
err = SharedUserAccountLogDAO.CreateAccountLog(tx, userId, accountId, delta, 0, eventType, description, params)
if err != nil {
return err
}
return nil
}
// UpdateUserAccountFrozen 操作用户账户冻结余额
func (this *UserAccountDAO) UpdateUserAccountFrozen(tx *dbs.Tx, userId int64, delta float32, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
account, err := this.FindUserAccountWithUserId(tx, userId)
if err != nil {
return err
}
var deltaFloat64 = float64(delta)
if deltaFloat64 < 0 && account.TotalFrozen < -deltaFloat64 {
return errors.New("not enough account frozen quota to decrease")
}
// 操作账户
err = this.Query(tx).
Pk(account.Id).
Set("totalFrozen", dbs.SQL("total+:delta")).
Param("delta", delta).
UpdateQuickly()
if err != nil {
return err
}
// 生成日志
err = SharedUserAccountLogDAO.CreateAccountLog(tx, userId, int64(account.Id), 0, delta, eventType, description, params)
if err != nil {
return err
}
return nil
}
// CountAllAccounts 计算所有账户数量
func (this *UserAccountDAO) CountAllAccounts(tx *dbs.Tx, keyword string) (int64, error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword))")
query.Param("keyword", keyword)
} else {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1)")
}
return query.Count()
}
// ListAccounts 列出单页账户
func (this *UserAccountDAO) ListAccounts(tx *dbs.Tx, keyword string, offset int64, size int64) (result []*UserAccount, err error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword))")
query.Param("keyword", keyword)
} else {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1)")
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// PayBills 尝试自动支付账单
func (this *UserAccountDAO) PayBills(tx *dbs.Tx) error {
bills, err := models.SharedUserBillDAO.FindUnpaidBills(tx, 10000)
if err != nil {
return err
}
// 先支付久远的
lists.Reverse(bills)
for _, bill := range bills {
if bill.Amount <= 0 {
err = models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, int64(bill.Id), true)
if err != nil {
return err
}
continue
}
account, err := SharedUserAccountDAO.FindUserAccountWithUserId(tx, int64(bill.UserId))
if err != nil {
return err
}
if account == nil || account.Total < bill.Amount {
continue
}
// 扣款
err = SharedUserAccountDAO.UpdateUserAccount(tx, int64(account.Id), -float32(bill.Amount), userconfigs.AccountEventTypePayBill, "支付账单"+bill.Code, maps.Map{"billId": bill.Id})
if err != nil {
return err
}
// 改为已支付
err = models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, int64(bill.Id), true)
if err != nil {
return err
}
}
return nil
}
// CheckUserAccount 检查用户账户
func (this *UserAccountDAO) CheckUserAccount(tx *dbs.Tx, userId int64, accountId int64) error {
exists, err := this.Query(tx).
Pk(accountId).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !exists {
return models.ErrNotFound
}
return nil
}

View File

@@ -0,0 +1,18 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestUserAccountDAO_PayBills(t *testing.T) {
dbs.NotifyReady()
err := NewUserAccountDAO().PayBills(nil)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -0,0 +1,129 @@
package accounts
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAccountLogDAO dbs.DAO
func NewUserAccountLogDAO() *UserAccountLogDAO {
return dbs.NewDAO(&UserAccountLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccountLogs",
Model: new(UserAccountLog),
PkName: "id",
},
}).(*UserAccountLogDAO)
}
var SharedUserAccountLogDAO *UserAccountLogDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountLogDAO = NewUserAccountLogDAO()
})
}
// CreateAccountLog 生成用户账户日志
func (this *UserAccountLogDAO) CreateAccountLog(tx *dbs.Tx, userId int64, accountId int64, delta float32, deltaFrozen float32, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
var op = NewUserAccountLogOperator()
op.UserId = userId
op.AccountId = accountId
op.Delta = delta
op.DeltaFrozen = deltaFrozen
account, err := SharedUserAccountDAO.FindUserAccountWithAccountId(tx, accountId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid account id '" + types.String(accountId) + "'")
}
op.Total = account.Total
op.TotalFrozen = account.TotalFrozen
op.EventType = eventType
op.Description = description
if params == nil {
params = maps.Map{}
}
op.Params = params.AsJSON()
op.Day = timeutil.Format("Ymd")
err = this.Save(tx, op)
if err != nil {
return err
}
return SharedUserAccountDailyStatDAO.UpdateDailyStat(tx)
}
// CountAccountLogs 计算日志数量
func (this *UserAccountLogDAO) CountAccountLogs(tx *dbs.Tx, userId int64, accountId int64, keyword string, eventType string) (int64, error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
}
return query.Count()
}
// ListAccountLogs 列出单页日志
func (this *UserAccountLogDAO) ListAccountLogs(tx *dbs.Tx, userId int64, accountId int64, keyword string, eventType string, offset int64, size int64) (result []*UserAccountLog, err error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// SumDailyEventTypes 统计某天数据总和
func (this *UserAccountLogDAO) SumDailyEventTypes(tx *dbs.Tx, day string, eventTypes []userconfigs.AccountEventType) (float32, error) {
if len(eventTypes) == 0 {
return 0, nil
}
result, err := this.Query(tx).
Attr("day", day).
Attr("eventType", eventTypes).
Sum("delta", 0)
if err != nil {
return 0, err
}
return types.Float32(result), nil
}

View File

@@ -0,0 +1,6 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,38 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// UserAccountLog 用户账户日志
type UserAccountLog struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
AccountId uint64 `field:"accountId"` // 账户ID
Delta float64 `field:"delta"` // 操作余额的数量(可为负)
DeltaFrozen float64 `field:"deltaFrozen"` // 操作冻结的数量(可为负)
Total float64 `field:"total"` // 操作后余额
TotalFrozen float64 `field:"totalFrozen"` // 操作后冻结余额
EventType string `field:"eventType"` // 类型
Description string `field:"description"` // 描述文字
Day string `field:"day"` // YYYYMMDD
CreatedAt uint64 `field:"createdAt"` // 时间
Params dbs.JSON `field:"params"` // 参数
}
type UserAccountLogOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
AccountId interface{} // 账户ID
Delta interface{} // 操作余额的数量(可为负)
DeltaFrozen interface{} // 操作冻结的数量(可为负)
Total interface{} // 操作后余额
TotalFrozen interface{} // 操作后冻结余额
EventType interface{} // 类型
Description interface{} // 描述文字
Day interface{} // YYYYMMDD
CreatedAt interface{} // 时间
Params interface{} // 参数
}
func NewUserAccountLogOperator() *UserAccountLogOperator {
return &UserAccountLogOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,20 @@
package accounts
// UserAccount 用户账号
type UserAccount struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
Total float64 `field:"total"` // 可用总余额
TotalFrozen float64 `field:"totalFrozen"` // 冻结余额
}
type UserAccountOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
Total interface{} // 可用总余额
TotalFrozen interface{} // 冻结余额
}
func NewUserAccountOperator() *UserAccountOperator {
return &UserAccountOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,127 @@
package acme
import (
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ACMEProviderAccountStateEnabled = 1 // 已启用
ACMEProviderAccountStateDisabled = 0 // 已禁用
)
type ACMEProviderAccountDAO dbs.DAO
func NewACMEProviderAccountDAO() *ACMEProviderAccountDAO {
return dbs.NewDAO(&ACMEProviderAccountDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeACMEProviderAccounts",
Model: new(ACMEProviderAccount),
PkName: "id",
},
}).(*ACMEProviderAccountDAO)
}
var SharedACMEProviderAccountDAO *ACMEProviderAccountDAO
func init() {
dbs.OnReady(func() {
SharedACMEProviderAccountDAO = NewACMEProviderAccountDAO()
})
}
// EnableACMEProviderAccount 启用条目
func (this *ACMEProviderAccountDAO) EnableACMEProviderAccount(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMEProviderAccountStateEnabled).
Update()
return err
}
// DisableACMEProviderAccount 禁用条目
func (this *ACMEProviderAccountDAO) DisableACMEProviderAccount(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMEProviderAccountStateDisabled).
Update()
return err
}
// FindEnabledACMEProviderAccount 查找启用中的条目
func (this *ACMEProviderAccountDAO) FindEnabledACMEProviderAccount(tx *dbs.Tx, id int64) (*ACMEProviderAccount, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", ACMEProviderAccountStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ACMEProviderAccount), err
}
// FindACMEProviderAccountName 根据主键查找名称
func (this *ACMEProviderAccountDAO) FindACMEProviderAccountName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateAccount 创建账号
func (this *ACMEProviderAccountDAO) CreateAccount(tx *dbs.Tx, name string, providerCode string, eabKid string, eabKey string) (int64, error) {
var op = NewACMEProviderAccountOperator()
op.Name = name
op.ProviderCode = providerCode
op.EabKid = eabKid
op.EabKey = eabKey
op.IsOn = true
op.State = ACMEProviderAccountStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateAccount 修改账号
func (this *ACMEProviderAccountDAO) UpdateAccount(tx *dbs.Tx, accountId int64, name string, eabKid string, eabKey string) error {
if accountId <= 0 {
return errors.New("invalid accountId")
}
var op = NewACMEProviderAccountOperator()
op.Id = accountId
op.Name = name
op.EabKid = eabKid
op.EabKey = eabKey
return this.Save(tx, op)
}
// CountAllEnabledAccounts 计算账号数量
func (this *ACMEProviderAccountDAO) CountAllEnabledAccounts(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
Count()
}
// ListEnabledAccounts 查找单页账号
func (this *ACMEProviderAccountDAO) ListEnabledAccounts(tx *dbs.Tx, offset int64, size int64) (result []*ACMEProviderAccount, err error) {
_, err = this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Offset(offset).
Limit(size).
DescPk().
Slice(&result).
FindAll()
return
}
// FindAllEnabledAccountsWithProviderCode 根据服务商代号查找账号
func (this *ACMEProviderAccountDAO) FindAllEnabledAccountsWithProviderCode(tx *dbs.Tx, providerCode string) (result []*ACMEProviderAccount, err error) {
_, err = this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("providerCode", providerCode).
DescPk().
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,6 @@
package acme
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,28 @@
package acme
// ACMEProviderAccount ACME提供商
type ACMEProviderAccount struct {
Id uint64 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 名称
ProviderCode string `field:"providerCode"` // 代号
Error string `field:"error"` // 最后一条错误信息
EabKid string `field:"eabKid"` // KID
EabKey string `field:"eabKey"` // Key
State uint8 `field:"state"` // 状态
}
type ACMEProviderAccountOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启用
Name interface{} // 名称
ProviderCode interface{} // 代号
Error interface{} // 最后一条错误信息
EabKid interface{} // KID
EabKey interface{} // Key
State interface{} // 状态
}
func NewACMEProviderAccountOperator() *ACMEProviderAccountOperator {
return &ACMEProviderAccountOperator{}
}

View File

@@ -0,0 +1 @@
package acme

View File

@@ -1,20 +1,26 @@
package acme
import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/acme"
acmeutils "github.com/TeaOSLab/EdgeAPI/internal/acme"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/go-acme/lego/v4/registration"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/http"
"time"
)
@@ -44,7 +50,7 @@ func init() {
})
}
// 启用条目
// EnableACMETask 启用条目
func (this *ACMETaskDAO) EnableACMETask(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -53,7 +59,7 @@ func (this *ACMETaskDAO) EnableACMETask(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableACMETask 禁用条目
func (this *ACMETaskDAO) DisableACMETask(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -62,7 +68,7 @@ func (this *ACMETaskDAO) DisableACMETask(tx *dbs.Tx, id int64) error {
return err
}
// 查找启用中的条目
// FindEnabledACMETask 查找启用中的条目
func (this *ACMETaskDAO) FindEnabledACMETask(tx *dbs.Tx, id int64) (*ACMETask, error) {
result, err := this.Query(tx).
Pk(id).
@@ -74,7 +80,7 @@ func (this *ACMETaskDAO) FindEnabledACMETask(tx *dbs.Tx, id int64) (*ACMETask, e
return result.(*ACMETask), err
}
// 计算某个ACME用户相关的任务数量
// CountACMETasksWithACMEUserId 计算某个ACME用户相关的任务数量
func (this *ACMETaskDAO) CountACMETasksWithACMEUserId(tx *dbs.Tx, acmeUserId int64) (int64, error) {
return this.Query(tx).
State(ACMETaskStateEnabled).
@@ -82,7 +88,7 @@ func (this *ACMETaskDAO) CountACMETasksWithACMEUserId(tx *dbs.Tx, acmeUserId int
Count()
}
// 计算某个DNS服务商相关的任务数量
// CountACMETasksWithDNSProviderId 计算某个DNS服务商相关的任务数量
func (this *ACMETaskDAO) CountACMETasksWithDNSProviderId(tx *dbs.Tx, dnsProviderId int64) (int64, error) {
return this.Query(tx).
State(ACMETaskStateEnabled).
@@ -90,7 +96,7 @@ func (this *ACMETaskDAO) CountACMETasksWithDNSProviderId(tx *dbs.Tx, dnsProvider
Count()
}
// 停止某个证书相关任务
// DisableAllTasksWithCertId 停止某个证书相关任务
func (this *ACMETaskDAO) DisableAllTasksWithCertId(tx *dbs.Tx, certId int64) error {
_, err := this.Query(tx).
Attr("certId", certId).
@@ -99,7 +105,7 @@ func (this *ACMETaskDAO) DisableAllTasksWithCertId(tx *dbs.Tx, certId int64) err
return err
}
// 计算所有任务数量
// CountAllEnabledACMETasks 计算所有任务数量
func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string) (int64, error) {
query := dbutils.NewQuery(tx, this, adminId, userId)
if isAvailable || isExpired || expiringDays > 0 {
@@ -119,18 +125,18 @@ func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, use
if len(keyword) > 0 {
query.Where("(domains LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(keyword) > 0 {
query.Where("domains LIKE :keyword").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.State(ACMETaskStateEnabled).
Count()
}
// 列出单页任务
// ListEnabledACMETasks 列出单页任务
func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string, offset int64, size int64) (result []*ACMETask, err error) {
query := dbutils.NewQuery(tx, this, adminId, userId)
if isAvailable || isExpired || expiringDays > 0 {
@@ -149,7 +155,7 @@ func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId
}
if len(keyword) > 0 {
query.Where("(domains LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
State(ACMETaskStateEnabled).
@@ -161,8 +167,8 @@ func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId
return
}
// 创建任务
func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, authType acme.AuthType, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool) (int64, error) {
// CreateACMETask 创建任务
func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, authType acmeutils.AuthType, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) (int64, error) {
op := NewACMETaskOperator()
op.AdminId = adminId
op.UserId = userId
@@ -182,6 +188,7 @@ func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64,
}
op.AutoRenew = autoRenew
op.AuthURL = authURL
op.IsOn = true
op.State = ACMETaskStateEnabled
err := this.Save(tx, op)
@@ -191,8 +198,8 @@ func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64,
return types.Int64(op.Id), nil
}
// 修改任务
func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool) error {
// UpdateACMETask 修改任务
func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) error {
if acmeTaskId <= 0 {
return errors.New("invalid acmeTaskId")
}
@@ -214,11 +221,12 @@ func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId
}
op.AutoRenew = autoRenew
op.AuthURL = authURL
err := this.Save(tx, op)
return err
}
// 检查权限
// CheckACMETask 检查权限
func (this *ACMETaskDAO) CheckACMETask(tx *dbs.Tx, adminId int64, userId int64, acmeTaskId int64) (bool, error) {
return dbutils.NewQuery(tx, this, adminId, userId).
State(ACMETaskStateEnabled).
@@ -226,7 +234,7 @@ func (this *ACMETaskDAO) CheckACMETask(tx *dbs.Tx, adminId int64, userId int64,
Exist()
}
// 设置任务关联的证书
// UpdateACMETaskCert 设置任务关联的证书
func (this *ACMETaskDAO) UpdateACMETaskCert(tx *dbs.Tx, taskId int64, certId int64) error {
if taskId <= 0 {
return errors.New("invalid taskId")
@@ -239,7 +247,7 @@ func (this *ACMETaskDAO) UpdateACMETaskCert(tx *dbs.Tx, taskId int64, certId int
return err
}
// 执行任务并记录日志
// RunTask 执行任务并记录日志
func (this *ACMETaskDAO) RunTask(tx *dbs.Tx, taskId int64) (isOk bool, errMsg string, resultCertId int64) {
isOk, errMsg, resultCertId = this.runTaskWithoutLog(tx, taskId)
@@ -263,7 +271,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
errMsg = "找不到要执行的任务"
return
}
if task.IsOn != 1 {
if !task.IsOn {
errMsg = "任务没有启用"
return
}
@@ -279,13 +287,39 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
return
}
privateKey, err := acme.ParsePrivateKeyFromBase64(user.PrivateKey)
// 服务商
if len(user.ProviderCode) == 0 {
user.ProviderCode = acmeutils.DefaultProviderCode
}
var acmeProvider = acmeutils.FindProviderWithCode(user.ProviderCode)
if acmeProvider == nil {
errMsg = "服务商已不可用"
return
}
// 账号
var acmeAccount *acmeutils.Account
if user.AccountId > 0 {
account, err := SharedACMEProviderAccountDAO.FindEnabledACMEProviderAccount(tx, int64(user.AccountId))
if err != nil {
errMsg = "查询ACME账号时出错" + err.Error()
return
}
if account != nil {
acmeAccount = &acmeutils.Account{
EABKid: account.EabKid,
EABKey: account.EabKey,
}
}
}
privateKey, err := acmeutils.ParsePrivateKeyFromBase64(user.PrivateKey)
if err != nil {
errMsg = "解析私钥时出错:" + err.Error()
return
}
remoteUser := acme.NewUser(user.Email, privateKey, func(resource *registration.Resource) error {
remoteUser := acmeutils.NewUser(user.Email, privateKey, func(resource *registration.Resource) error {
resourceJSON, err := json.Marshal(resource)
if err != nil {
return err
@@ -296,15 +330,15 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
})
if len(user.Registration) > 0 {
err = remoteUser.SetRegistration([]byte(user.Registration))
err = remoteUser.SetRegistration(user.Registration)
if err != nil {
errMsg = "设置注册信息时出错:" + err.Error()
return
}
}
var acmeTask *acme.Task = nil
if task.AuthType == acme.AuthTypeDNS {
var acmeTask *acmeutils.Task = nil
if task.AuthType == acmeutils.AuthTypeDNS {
// DNS服务商
dnsProvider, err := dns.SharedDNSProviderDAO.FindEnabledDNSProvider(tx, int64(task.DnsProviderId))
if err != nil {
@@ -331,26 +365,55 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
return
}
acmeTask = &acme.Task{
acmeTask = &acmeutils.Task{
User: remoteUser,
AuthType: acme.AuthTypeDNS,
AuthType: acmeutils.AuthTypeDNS,
DNSProvider: providerInterface,
DNSDomain: task.DnsDomain,
Domains: task.DecodeDomains(),
}
} else if task.AuthType == acme.AuthTypeHTTP {
acmeTask = &acme.Task{
} else if task.AuthType == acmeutils.AuthTypeHTTP {
acmeTask = &acmeutils.Task{
User: remoteUser,
AuthType: acme.AuthTypeHTTP,
AuthType: acmeutils.AuthTypeHTTP,
Domains: task.DecodeDomains(),
}
}
acmeTask.Provider = acmeProvider
acmeTask.Account = acmeAccount
acmeRequest := acme.NewRequest(acmeTask)
acmeRequest := acmeutils.NewRequest(acmeTask)
acmeRequest.OnAuth(func(domain, token, keyAuth string) {
err := SharedACMEAuthenticationDAO.CreateAuth(tx, taskId, domain, token, keyAuth)
if err != nil {
logs.Println("[ACME]write authentication to database error: " + err.Error())
remotelogs.Error("ACME", "write authentication to database error: "+err.Error())
} else {
// 调用校验URL
if len(task.AuthURL) > 0 {
authJSON, err := json.Marshal(maps.Map{
"domain": domain,
"token": token,
"key": keyAuth,
})
if err != nil {
remotelogs.Error("ACME", "encode auth data failed: '"+task.AuthURL+"'")
} else {
client := utils.SharedHttpClient(5 * time.Second)
req, err := http.NewRequest(http.MethodPost, task.AuthURL, bytes.NewReader(authJSON))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
if err != nil {
remotelogs.Error("ACME", "parse auth url failed '"+task.AuthURL+"': "+err.Error())
} else {
resp, err := client.Do(req)
if err != nil {
remotelogs.Error("ACME", "call auth url failed '"+task.AuthURL+"': "+err.Error())
} else {
_ = resp.Body.Close()
}
}
}
}
}
})
certData, keyData, err := acmeRequest.Run()
@@ -390,7 +453,7 @@ func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool,
return
}
err = models.SharedSSLCertDAO.UpdateCert(tx, resultCertId, cert.IsOn == 1, cert.Name, cert.Description, cert.ServerName, cert.IsCA == 1, certData, keyData, sslConfig.TimeBeginAt, sslConfig.TimeEndAt, sslConfig.DNSNames, sslConfig.CommonNames)
err = models.SharedSSLCertDAO.UpdateCert(tx, resultCertId, cert.IsOn, cert.Name, cert.Description, cert.ServerName, cert.IsCA, certData, keyData, sslConfig.TimeBeginAt, sslConfig.TimeEndAt, sslConfig.DNSNames, sslConfig.CommonNames)
if err != nil {
errMsg = "证书生成成功,但是修改数据库中的证书信息时出错:" + err.Error()
return

View File

@@ -1,10 +1,10 @@
package acme
// ACME任务运行日志
// ACMETaskLog ACME任务运行日志
type ACMETaskLog struct {
Id uint64 `field:"id"` // ID
TaskId uint64 `field:"taskId"` // 任务ID
IsOk uint8 `field:"isOk"` // 是否成功
IsOk bool `field:"isOk"` // 是否成功
Error string `field:"error"` // 错误信息
CreatedAt uint64 `field:"createdAt"` // 运行时间
}

View File

@@ -1,20 +1,23 @@
package acme
// ACME任务
import "github.com/iwind/TeaGo/dbs"
// ACMETask ACME任务
type ACMETask struct {
Id uint64 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
IsOn uint8 `field:"isOn"` // 是否启用
AcmeUserId uint32 `field:"acmeUserId"` // ACME用户ID
DnsDomain string `field:"dnsDomain"` // DNS主域名
DnsProviderId uint64 `field:"dnsProviderId"` // DNS服务商
Domains string `field:"domains"` // 证书域名
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
CertId uint64 `field:"certId"` // 生成的证书ID
AutoRenew uint8 `field:"autoRenew"` // 是否自动更新
AuthType string `field:"authType"` // 认证类型
Id uint64 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
AcmeUserId uint32 `field:"acmeUserId"` // ACME用户ID
DnsDomain string `field:"dnsDomain"` // DNS主域名
DnsProviderId uint64 `field:"dnsProviderId"` // DNS服务商
Domains dbs.JSON `field:"domains"` // 证书域名
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
CertId uint64 `field:"certId"` // 生成的证书ID
AutoRenew uint8 `field:"autoRenew"` // 是否自动更新
AuthType string `field:"authType"` // 认证类型
AuthURL string `field:"authURL"` // 认证URL
}
type ACMETaskOperator struct {
@@ -31,6 +34,7 @@ type ACMETaskOperator struct {
CertId interface{} // 生成的证书ID
AutoRenew interface{} // 是否自动更新
AuthType interface{} // 认证类型
AuthURL interface{} // 认证URL
}
func NewACMETaskOperator() *ACMETaskOperator {

View File

@@ -5,13 +5,13 @@ import (
"github.com/iwind/TeaGo/logs"
)
// 将域名解析成字符串数组
// DecodeDomains 将域名解析成字符串数组
func (this *ACMETask) DecodeDomains() []string {
if len(this.Domains) == 0 || this.Domains == "null" {
if len(this.Domains) == 0 {
return nil
}
result := []string{}
err := json.Unmarshal([]byte(this.Domains), &result)
err := json.Unmarshal(this.Domains, &result)
if err != nil {
logs.Error(err)
return nil

View File

@@ -39,7 +39,7 @@ func init() {
})
}
// 启用条目
// EnableACMEUser 启用条目
func (this *ACMEUserDAO) EnableACMEUser(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -48,7 +48,7 @@ func (this *ACMEUserDAO) EnableACMEUser(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableACMEUser 禁用条目
func (this *ACMEUserDAO) DisableACMEUser(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -69,8 +69,8 @@ func (this *ACMEUserDAO) FindEnabledACMEUser(tx *dbs.Tx, id int64) (*ACMEUser, e
return result.(*ACMEUser), err
}
// 创建用户
func (this *ACMEUserDAO) CreateACMEUser(tx *dbs.Tx, adminId int64, userId int64, email string, description string) (int64, error) {
// CreateACMEUser 创建用户
func (this *ACMEUserDAO) CreateACMEUser(tx *dbs.Tx, adminId int64, userId int64, providerCode string, accountId int64, email string, description string) (int64, error) {
// 生成私钥
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
@@ -86,6 +86,8 @@ func (this *ACMEUserDAO) CreateACMEUser(tx *dbs.Tx, adminId int64, userId int64,
op := NewACMEUserOperator()
op.AdminId = adminId
op.UserId = userId
op.ProviderCode = providerCode
op.AccountId = accountId
op.Email = email
op.Description = description
op.PrivateKey = privateKeyText
@@ -97,7 +99,7 @@ func (this *ACMEUserDAO) CreateACMEUser(tx *dbs.Tx, adminId int64, userId int64,
return types.Int64(op.Id), nil
}
// 修改用户信息
// UpdateACMEUser 修改用户信息
func (this *ACMEUserDAO) UpdateACMEUser(tx *dbs.Tx, acmeUserId int64, description string) error {
if acmeUserId <= 0 {
return errors.New("invalid acmeUserId")
@@ -109,7 +111,7 @@ func (this *ACMEUserDAO) UpdateACMEUser(tx *dbs.Tx, acmeUserId int64, descriptio
return err
}
// 修改用户ACME注册信息
// UpdateACMEUserRegistration 修改用户ACME注册信息
func (this *ACMEUserDAO) UpdateACMEUserRegistration(tx *dbs.Tx, acmeUserId int64, registrationJSON []byte) error {
if acmeUserId <= 0 {
return errors.New("invalid acmeUserId")
@@ -121,8 +123,8 @@ func (this *ACMEUserDAO) UpdateACMEUserRegistration(tx *dbs.Tx, acmeUserId int64
return err
}
// 计算用户数量
func (this *ACMEUserDAO) CountACMEUsersWithAdminId(tx *dbs.Tx, adminId int64, userId int64) (int64, error) {
// CountACMEUsersWithAdminId 计算用户数量
func (this *ACMEUserDAO) CountACMEUsersWithAdminId(tx *dbs.Tx, adminId int64, userId int64, accountId int64) (int64, error) {
query := this.Query(tx)
if adminId > 0 {
query.Attr("adminId", adminId)
@@ -130,13 +132,16 @@ func (this *ACMEUserDAO) CountACMEUsersWithAdminId(tx *dbs.Tx, adminId int64, us
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
return query.
State(ACMEUserStateEnabled).
Count()
}
// 列出当前管理员的用户
// ListACMEUsers 列出当前管理员的用户
func (this *ACMEUserDAO) ListACMEUsers(tx *dbs.Tx, adminId int64, userId int64, offset int64, size int64) (result []*ACMEUser, err error) {
query := this.Query(tx)
if adminId > 0 {
@@ -156,8 +161,8 @@ func (this *ACMEUserDAO) ListACMEUsers(tx *dbs.Tx, adminId int64, userId int64,
return
}
// 查找所有用户
func (this *ACMEUserDAO) FindAllACMEUsers(tx *dbs.Tx, adminId int64, userId int64) (result []*ACMEUser, err error) {
// FindAllACMEUsers 查找所有用户
func (this *ACMEUserDAO) FindAllACMEUsers(tx *dbs.Tx, adminId int64, userId int64, providerCode string) (result []*ACMEUser, err error) {
// 防止没有传入条件导致返回的数据过多
if adminId <= 0 && userId <= 0 {
return nil, errors.New("'adminId' or 'userId' should not be empty")
@@ -170,6 +175,9 @@ func (this *ACMEUserDAO) FindAllACMEUsers(tx *dbs.Tx, adminId int64, userId int6
if userId > 0 {
query.Attr("userId", userId)
}
if len(providerCode) > 0 {
query.Attr("providerCode", providerCode)
}
_, err = query.
State(ACMEUserStateEnabled).
Slice(&result).
@@ -178,7 +186,7 @@ func (this *ACMEUserDAO) FindAllACMEUsers(tx *dbs.Tx, adminId int64, userId int6
return
}
// 检查用户权限
// CheckACMEUser 检查用户权限
func (this *ACMEUserDAO) CheckACMEUser(tx *dbs.Tx, acmeUserId int64, adminId int64, userId int64) (bool, error) {
if acmeUserId <= 0 {
return false, nil

View File

@@ -1,16 +1,20 @@
package acme
//
import "github.com/iwind/TeaGo/dbs"
// ACMEUser ACME用户
type ACMEUser struct {
Id uint64 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
PrivateKey string `field:"privateKey"` // 私钥
Email string `field:"email"` // E-mail
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
Description string `field:"description"` // 备注介绍
Registration string `field:"registration"` // 注册信息
Id uint64 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
PrivateKey string `field:"privateKey"` // 私钥
Email string `field:"email"` // E-mail
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
Description string `field:"description"` // 备注介绍
Registration dbs.JSON `field:"registration"` // 注册信息
ProviderCode string `field:"providerCode"` // 服务商代号
AccountId uint64 `field:"accountId"` // 提供商ID
}
type ACMEUserOperator struct {
@@ -23,6 +27,8 @@ type ACMEUserOperator struct {
State interface{} // 状态
Description interface{} // 备注介绍
Registration interface{} // 注册信息
ProviderCode interface{} // 服务商代号
AccountId interface{} // 提供商ID
}
func NewACMEUserOperator() *ACMEUserOperator {

View File

@@ -35,7 +35,7 @@ func init() {
})
}
// 启用条目
// EnableAdmin 启用条目
func (this *AdminDAO) EnableAdmin(tx *dbs.Tx, id int64) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
@@ -43,7 +43,7 @@ func (this *AdminDAO) EnableAdmin(tx *dbs.Tx, id int64) (rowsAffected int64, err
Update()
}
// 禁用条目
// DisableAdmin 禁用条目
func (this *AdminDAO) DisableAdmin(tx *dbs.Tx, id int64) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
@@ -51,7 +51,7 @@ func (this *AdminDAO) DisableAdmin(tx *dbs.Tx, id int64) (rowsAffected int64, er
Update()
}
// 查找启用中的条目
// FindEnabledAdmin 查找启用中的条目
func (this *AdminDAO) FindEnabledAdmin(tx *dbs.Tx, id int64) (*Admin, error) {
result, err := this.Query(tx).
Pk(id).
@@ -63,7 +63,7 @@ func (this *AdminDAO) FindEnabledAdmin(tx *dbs.Tx, id int64) (*Admin, error) {
return result.(*Admin), err
}
// 检查管理员是否存在
// ExistEnabledAdmin 检查管理员是否存在
func (this *AdminDAO) ExistEnabledAdmin(tx *dbs.Tx, adminId int64) (bool, error) {
return this.Query(tx).
Pk(adminId).
@@ -71,7 +71,7 @@ func (this *AdminDAO) ExistEnabledAdmin(tx *dbs.Tx, adminId int64) (bool, error)
Exist()
}
// 获取管理员名称
// FindAdminFullname 获取管理员名称
func (this *AdminDAO) FindAdminFullname(tx *dbs.Tx, adminId int64) (string, error) {
return this.Query(tx).
Pk(adminId).
@@ -79,7 +79,7 @@ func (this *AdminDAO) FindAdminFullname(tx *dbs.Tx, adminId int64) (string, erro
FindStringCol("")
}
// 检查用户名、密码
// CheckAdminPassword 检查用户名、密码
func (this *AdminDAO) CheckAdminPassword(tx *dbs.Tx, username string, encryptedPassword string) (int64, error) {
if len(username) == 0 || len(encryptedPassword) == 0 {
return 0, nil
@@ -94,7 +94,7 @@ func (this *AdminDAO) CheckAdminPassword(tx *dbs.Tx, username string, encryptedP
FindInt64Col(0)
}
// 根据用户名查询管理员ID
// FindAdminIdWithUsername 根据用户名查询管理员ID
func (this *AdminDAO) FindAdminIdWithUsername(tx *dbs.Tx, username string) (int64, error) {
one, err := this.Query(tx).
Attr("username", username).
@@ -110,7 +110,7 @@ func (this *AdminDAO) FindAdminIdWithUsername(tx *dbs.Tx, username string) (int6
return int64(one.(*Admin).Id), nil
}
// 更改管理员密码
// UpdateAdminPassword 更改管理员密码
func (this *AdminDAO) UpdateAdminPassword(tx *dbs.Tx, adminId int64, password string) error {
if adminId <= 0 {
return errors.New("invalid adminId")
@@ -122,7 +122,7 @@ func (this *AdminDAO) UpdateAdminPassword(tx *dbs.Tx, adminId int64, password st
return err
}
// 创建管理员
// CreateAdmin 创建管理员
func (this *AdminDAO) CreateAdmin(tx *dbs.Tx, username string, canLogin bool, password string, fullname string, isSuper bool, modulesJSON []byte) (int64, error) {
op := NewAdminOperator()
op.IsOn = true
@@ -144,7 +144,7 @@ func (this *AdminDAO) CreateAdmin(tx *dbs.Tx, username string, canLogin bool, pa
return types.Int64(op.Id), nil
}
// 修改管理员个人资料
// UpdateAdminInfo 修改管理员个人资料
func (this *AdminDAO) UpdateAdminInfo(tx *dbs.Tx, adminId int64, fullname string) error {
if adminId <= 0 {
return errors.New("invalid adminId")
@@ -156,7 +156,7 @@ func (this *AdminDAO) UpdateAdminInfo(tx *dbs.Tx, adminId int64, fullname string
return err
}
// 修改管理员详细信息
// UpdateAdmin 修改管理员详细信息
func (this *AdminDAO) UpdateAdmin(tx *dbs.Tx, adminId int64, username string, canLogin bool, password string, fullname string, isSuper bool, modulesJSON []byte, isOn bool) error {
if adminId <= 0 {
return errors.New("invalid adminId")
@@ -180,7 +180,7 @@ func (this *AdminDAO) UpdateAdmin(tx *dbs.Tx, adminId int64, username string, ca
return err
}
// 检查用户名是否存在
// CheckAdminUsername 检查用户名是否存在
func (this *AdminDAO) CheckAdminUsername(tx *dbs.Tx, adminId int64, username string) (bool, error) {
query := this.Query(tx).
State(AdminStateEnabled).
@@ -193,7 +193,7 @@ func (this *AdminDAO) CheckAdminUsername(tx *dbs.Tx, adminId int64, username str
return query.Exist()
}
// 修改管理员登录信息
// UpdateAdminLogin 修改管理员登录信息
func (this *AdminDAO) UpdateAdminLogin(tx *dbs.Tx, adminId int64, username string, password string) error {
if adminId <= 0 {
return errors.New("invalid adminId")
@@ -208,7 +208,7 @@ func (this *AdminDAO) UpdateAdminLogin(tx *dbs.Tx, adminId int64, username strin
return err
}
// 修改管理员可以管理的模块
// UpdateAdminModules 修改管理员可以管理的模块
func (this *AdminDAO) UpdateAdminModules(tx *dbs.Tx, adminId int64, allowModulesJSON []byte) error {
if adminId <= 0 {
return errors.New("invalid adminId")
@@ -223,25 +223,25 @@ func (this *AdminDAO) UpdateAdminModules(tx *dbs.Tx, adminId int64, allowModules
return nil
}
// 查询所有管理的权限
// FindAllAdminModules 查询所有管理的权限
func (this *AdminDAO) FindAllAdminModules(tx *dbs.Tx) (result []*Admin, err error) {
_, err = this.Query(tx).
State(AdminStateEnabled).
Attr("isOn", true).
Result("id", "modules", "isSuper", "fullname").
Result("id", "modules", "isSuper", "fullname", "theme").
Slice(&result).
FindAll()
return
}
// 计算所有管理员数量
// CountAllEnabledAdmins 计算所有管理员数量
func (this *AdminDAO) CountAllEnabledAdmins(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(AdminStateEnabled).
Count()
}
// 列出单页的管理员
// ListEnabledAdmins 列出单页的管理员
func (this *AdminDAO) ListEnabledAdmins(tx *dbs.Tx, offset int64, size int64) (result []*Admin, err error) {
_, err = this.Query(tx).
State(AdminStateEnabled).
@@ -253,3 +253,11 @@ func (this *AdminDAO) ListEnabledAdmins(tx *dbs.Tx, offset int64, size int64) (r
FindAll()
return
}
// UpdateAdminTheme 设置管理员Theme
func (this *AdminDAO) UpdateAdminTheme(tx *dbs.Tx, adminId int64, theme string) error {
return this.Query(tx).
Pk(adminId).
Set("theme", theme).
UpdateQuickly()
}

View File

@@ -1,18 +1,21 @@
package models
// 管理员
import "github.com/iwind/TeaGo/dbs"
// Admin 管理员
type Admin struct {
Id uint32 `field:"id"` // ID
IsOn uint8 `field:"isOn"` // 是否启用
Username string `field:"username"` // 用户名
Password string `field:"password"` // 密码
Fullname string `field:"fullname"` // 全名
IsSuper uint8 `field:"isSuper"` // 是否为超级管理员
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 状态
Modules string `field:"modules"` // 允许的模块
CanLogin uint8 `field:"canLogin"` // 是否可以登录
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
Username string `field:"username"` // 用户名
Password string `field:"password"` // 密码
Fullname string `field:"fullname"` // 全名
IsSuper bool `field:"isSuper"` // 是否为超级管理员
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 状态
Modules dbs.JSON `field:"modules"` // 允许的模块
CanLogin uint8 `field:"canLogin"` // 是否可以登录
Theme string `field:"theme"` // 模板设置
}
type AdminOperator struct {
@@ -27,6 +30,7 @@ type AdminOperator struct {
State interface{} // 状态
Modules interface{} // 允许的模块
CanLogin interface{} // 是否可以登录
Theme interface{} // 模板设置
}
func NewAdminOperator() *AdminOperator {

View File

@@ -1,6 +1,7 @@
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -29,10 +30,23 @@ func init() {
})
}
// 生成AccessToken
func (this *APIAccessTokenDAO) GenerateAccessToken(tx *dbs.Tx, userId int64) (token string, expiresAt int64, err error) {
// GenerateAccessToken 生成AccessToken
func (this *APIAccessTokenDAO) GenerateAccessToken(tx *dbs.Tx, adminId int64, userId int64) (token string, expiresAt int64, err error) {
if adminId <= 0 && userId <= 0 {
err = errors.New("either 'adminId' or 'userId' should not be zero")
return
}
if adminId > 0 {
userId = 0
}
if userId > 0 {
adminId = 0
}
// 查询以前的
accessToken, err := this.Query(tx).
Attr("adminId", adminId).
Attr("userId", userId).
Find()
if err != nil {
@@ -48,6 +62,7 @@ func (this *APIAccessTokenDAO) GenerateAccessToken(tx *dbs.Tx, userId int64) (to
op.Id = accessToken.(*APIAccessToken).Id
}
op.AdminId = adminId
op.UserId = userId
op.Token = token
op.CreatedAt = time.Now().Unix()
@@ -56,7 +71,7 @@ func (this *APIAccessTokenDAO) GenerateAccessToken(tx *dbs.Tx, userId int64) (to
return
}
// 查找AccessToken
// FindAccessToken 查找AccessToken
func (this *APIAccessTokenDAO) FindAccessToken(tx *dbs.Tx, token string) (*APIAccessToken, error) {
one, err := this.Query(tx).
Attr("token", token).

View File

@@ -1,9 +1,10 @@
package models
// API访问令牌
// APIAccessToken API访问令牌
type APIAccessToken struct {
Id uint64 `field:"id"` // ID
UserId uint32 `field:"userId"` // 用户ID
AdminId uint32 `field:"adminId"` // 管理员ID
Token string `field:"token"` // 令牌
CreatedAt uint64 `field:"createdAt"` // 创建时间
ExpiredAt uint64 `field:"expiredAt"` // 过期时间
@@ -12,6 +13,7 @@ type APIAccessToken struct {
type APIAccessTokenOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
AdminId interface{} // 管理员ID
Token interface{} // 令牌
CreatedAt interface{} // 创建时间
ExpiredAt interface{} // 过期时间

View File

@@ -0,0 +1,76 @@
package models
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type APIMethodStatDAO dbs.DAO
func NewAPIMethodStatDAO() *APIMethodStatDAO {
return dbs.NewDAO(&APIMethodStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAPIMethodStats",
Model: new(APIMethodStat),
PkName: "id",
},
}).(*APIMethodStatDAO)
}
var SharedAPIMethodStatDAO *APIMethodStatDAO
func init() {
dbs.OnReady(func() {
SharedAPIMethodStatDAO = NewAPIMethodStatDAO()
})
}
// CreateStat 记录统计数据
func (this *APIMethodStatDAO) CreateStat(tx *dbs.Tx, method string, tag string, costMs float64) error {
var day = timeutil.Format("Ymd")
return this.Query(tx).
Param("costMs", costMs).
InsertOrUpdateQuickly(map[string]interface{}{
"apiNodeId": teaconst.NodeId,
"method": method,
"tag": tag,
"costMs": costMs,
"peekMs": costMs,
"countCalls": 1,
"day": day,
}, map[string]interface{}{
"costMs": dbs.SQL("(costMs*countCalls+:costMs)/(countCalls+1)"),
"peekMs": dbs.SQL("IF(peekMs>:costMs, peekMs, :costMs)"),
"countCalls": dbs.SQL("countCalls+1"),
})
}
// FindAllStatsWithDay 查询当前统计
func (this *APIMethodStatDAO) FindAllStatsWithDay(tx *dbs.Tx, day string) (result []*APIMethodStat, err error) {
_, err = this.Query(tx).
Attr("day", day).
Slice(&result).
FindAll()
return
}
// CountAllStatsWithDay 统计当天数量
func (this *APIMethodStatDAO) CountAllStatsWithDay(tx *dbs.Tx, day string) (int64, error) {
return this.Query(tx).
Attr("day", day).
Count()
}
// Clean 清理数据
func (this *APIMethodStatDAO) Clean(tx *dbs.Tx) error {
var day = timeutil.Format("Ymd")
_, err := this.Query(tx).
Param("day", day).
Where("day<:day").
Delete()
return err
}

View File

@@ -0,0 +1,19 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestAPIMethodStatDAO_CreateStat(t *testing.T) {
var dao = NewAPIMethodStatDAO()
var tx *dbs.Tx
err := dao.CreateStat(tx, "/pb.Hello/World", "tag", 1.123)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -0,0 +1,28 @@
package models
// APIMethodStat API方法统计
type APIMethodStat struct {
Id uint64 `field:"id"` // ID
ApiNodeId uint32 `field:"apiNodeId"` // API节点ID
Method string `field:"method"` // 方法
Tag string `field:"tag"` // 标签方法
CostMs float64 `field:"costMs"` // 耗时Ms
PeekMs float64 `field:"peekMs"` // 峰值耗时
CountCalls uint64 `field:"countCalls"` // 调用次数
Day string `field:"day"` // 日期
}
type APIMethodStatOperator struct {
Id interface{} // ID
ApiNodeId interface{} // API节点ID
Method interface{} // 方法
Tag interface{} // 标签方法
CostMs interface{} // 耗时Ms
PeekMs interface{} // 峰值耗时
CountCalls interface{} // 调用次数
Day interface{} // 日期
}
func NewAPIMethodStatOperator() *APIMethodStatOperator {
return &APIMethodStatOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -3,14 +3,20 @@ package models
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/configs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"net"
"strconv"
"strings"
)
const (
@@ -45,20 +51,41 @@ func (this *APINodeDAO) EnableAPINode(tx *dbs.Tx, id int64) error {
Pk(id).
Set("state", APINodeStateEnabled).
Update()
return err
if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
}
// DisableAPINode 禁用条目
func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, id int64) error {
func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, nodeId int64) error {
_, err := this.Query(tx).
Pk(id).
Pk(nodeId).
Set("state", APINodeStateDisabled).
Update()
return err
if err != nil {
return err
}
err = this.NotifyUpdate(tx, nodeId)
if err != nil {
return err
}
// 删除运行日志
return SharedNodeLogDAO.DeleteNodeLogs(tx, nodeconfigs.NodeRoleAPI, nodeId)
}
// FindEnabledAPINode 查找启用中的条目
func (this *APINodeDAO) FindEnabledAPINode(tx *dbs.Tx, id int64) (*APINode, error) {
func (this *APINodeDAO) FindEnabledAPINode(tx *dbs.Tx, id int64, cacheMap *utils.CacheMap) (*APINode, error) {
var cacheKey = this.Table + ":FindEnabledAPINode:" + types.String(id)
if cacheMap != nil {
cache, ok := cacheMap.Get(cacheKey)
if ok {
return cache.(*APINode), nil
}
}
result, err := this.Query(tx).
Pk(id).
Attr("state", APINodeStateEnabled).
@@ -66,6 +93,11 @@ func (this *APINodeDAO) FindEnabledAPINode(tx *dbs.Tx, id int64) (*APINode, erro
if result == nil {
return nil, err
}
if cacheMap != nil {
cacheMap.Put(cacheKey, result)
}
return result.(*APINode), err
}
@@ -97,7 +129,7 @@ func (this *APINodeDAO) CreateAPINode(tx *dbs.Tx, name string, description strin
return 0, err
}
secret := rands.String(32)
err = NewApiTokenDAO().CreateAPIToken(tx, uniqueId, secret, NodeRoleAPI)
err = NewApiTokenDAO().CreateAPIToken(tx, uniqueId, secret, nodeconfigs.NodeRoleAPI)
if err != nil {
return
}
@@ -132,16 +164,33 @@ func (this *APINodeDAO) CreateAPINode(tx *dbs.Tx, name string, description strin
return
}
err = this.NotifyUpdate(tx, types.Int64(op.Id))
if err != nil {
remotelogs.Error("API_NODE_DAO", err.Error())
}
return types.Int64(op.Id), nil
}
// UpdateAPINode 修改API节点
func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, description string, httpJSON []byte, httpsJSON []byte, restIsOn bool, restHTTPJSON []byte, restHTTPSJSON []byte, accessAddrsJSON []byte, isOn bool) error {
func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, description string, httpJSON []byte, httpsJSON []byte, restIsOn bool, restHTTPJSON []byte, restHTTPSJSON []byte, accessAddrsJSON []byte, isOn bool, isPrimary bool) error {
if nodeId <= 0 {
return errors.New("invalid nodeId")
}
op := NewAPINodeOperator()
// 取消别的Primary
if isPrimary {
err := this.Query(tx).
Neq("id", nodeId).
Attr("isPrimary", true).
Set("isPrimary", false).
UpdateQuickly()
if err != nil {
return err
}
}
var op = NewAPINodeOperator()
op.Id = nodeId
op.Name = name
op.Description = description
@@ -174,8 +223,13 @@ func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, des
op.AccessAddrs = "[]"
}
op.IsPrimary = isPrimary
err := this.Save(tx, op)
return err
if err != nil {
return err
}
return this.NotifyUpdate(tx, nodeId)
}
// FindAllEnabledAPINodes 列出所有可用API节点
@@ -210,6 +264,23 @@ func (this *APINodeDAO) CountAllEnabledAPINodes(tx *dbs.Tx) (int64, error) {
Count()
}
// CountAllEnabledAndOnAPINodes 计算启用中的API节点数量
func (this *APINodeDAO) CountAllEnabledAndOnAPINodes(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Count()
}
// CountAllEnabledAndOnOfflineAPINodes 计算API节点数量
func (this *APINodeDAO) CountAllEnabledAndOnOfflineAPINodes(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Where("(status IS NULL OR NOT JSON_EXTRACT(status, '$.isActive') OR UNIX_TIMESTAMP()-JSON_EXTRACT(status, '$.updatedAt')>60)").
Count()
}
// ListEnabledAPINodes 列出单页的API节点
func (this *APINodeDAO) ListEnabledAPINodes(tx *dbs.Tx, offset int64, size int64) (result []*APINode, err error) {
_, err = this.Query(tx).
@@ -260,6 +331,174 @@ func (this *APINodeDAO) UpdateAPINodeStatus(tx *dbs.Tx, apiNodeId int64, statusJ
return err
}
// CountAllLowerVersionNodes 计算所有节点中低于某个版本的节点数量
func (this *APINodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Where("status IS NOT NULL").
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
Param("version", utils.VersionToLong(version)).
Count()
}
// CountAllEnabledAPINodesWithSSLPolicyIds 计算使用SSL策略的所有API节点数量
func (this *APINodeDAO) CountAllEnabledAPINodesWithSSLPolicyIds(tx *dbs.Tx, sslPolicyIds []int64) (count int64, err error) {
if len(sslPolicyIds) == 0 {
return
}
policyStringIds := []string{}
for _, policyId := range sslPolicyIds {
policyStringIds = append(policyStringIds, strconv.FormatInt(policyId, 10))
}
return this.Query(tx).
State(APINodeStateEnabled).
Where("(FIND_IN_SET(JSON_EXTRACT(https, '$.sslPolicyRef.sslPolicyId'), :policyIds) OR FIND_IN_SET(JSON_EXTRACT(restHTTPS, '$.sslPolicyRef.sslPolicyId'), :policyIds))").
Param("policyIds", strings.Join(policyStringIds, ",")).
Count()
}
// FindAllEnabledAPIAccessIPs 获取所有的API可访问IP地址
func (this *APINodeDAO) FindAllEnabledAPIAccessIPs(tx *dbs.Tx, cacheMap *utils.CacheMap) ([]string, error) {
var cacheKey = this.Table + ":FindAllEnabledAPIAccessIPs"
if cacheMap != nil {
cache, ok := cacheMap.Get(cacheKey)
if ok {
return cache.([]string), nil
}
}
ones, _, err := this.Query(tx).
State(APINodeStateEnabled).
Result("JSON_EXTRACT(accessAddrs, '$[*].host') AS host").
FindOnes()
if err != nil {
return nil, err
}
var result = []string{}
for _, one := range ones {
var host = one.GetString("host")
if len(host) == 0 {
continue
}
var ips = []string{}
err = json.Unmarshal([]byte(host), &ips)
if err != nil {
continue
}
for _, ip := range ips {
if !lists.ContainsString(result, ip) {
if net.ParseIP(ip) == nil {
continue
}
result = append(result, ip)
}
}
}
if cacheMap != nil {
cacheMap.Put(cacheKey, result)
}
return result, nil
}
// CheckAPINodeIsPrimary 检查当前节点是否为Primary节点
func (this *APINodeDAO) CheckAPINodeIsPrimary(tx *dbs.Tx) (bool, error) {
config, err := configs.SharedAPIConfig()
if err != nil {
return false, err
}
isPrimary, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("uniqueId", config.NodeId).
Attr("isPrimary", true).
Exist()
if err != nil {
return false, err
}
if isPrimary {
return true, nil
}
// 检查是否有别的Primary节点
count, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Attr("isPrimary", true).
Count()
if err != nil {
return false, err
}
if count == 0 {
err = this.ResetPrimaryAPINode(tx)
if err != nil {
return false, err
}
return true, nil
}
return false, nil
}
// CheckAPINodeIsPrimaryWithoutErr 检查当前节点是否为Primary节点并忽略错误
func (this *APINodeDAO) CheckAPINodeIsPrimaryWithoutErr() bool {
b, err := this.CheckAPINodeIsPrimary(nil)
return b && err == nil
}
// ResetPrimaryAPINode 重置Primary节点
func (this *APINodeDAO) ResetPrimaryAPINode(tx *dbs.Tx) error {
// 当前是否有Primary节点
apiNode, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Attr("isPrimary", true).
Find()
if err != nil {
return err
}
if apiNode == nil {
// 选择一个作为Primary
// TODO 将来需要考虑API节点离线的情况
apiNodeId, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
ResultPk().
FindInt64Col(0)
if err != nil {
return err
}
if apiNodeId > 0 {
err = this.Query(tx).
Pk(apiNodeId).
Set("isPrimary", true).
UpdateQuickly()
if err != nil {
return err
}
}
}
return nil
}
// NotifyUpdate 通知变更
func (this *APINodeDAO) NotifyUpdate(tx *dbs.Tx, apiNodeId int64) error {
// suppress IDE warning
_ = apiNodeId
err := this.ResetPrimaryAPINode(tx)
if err != nil {
return err
}
return nil
}
// 生成唯一ID
func (this *APINodeDAO) genUniqueId(tx *dbs.Tx) (string, error) {
for {
@@ -276,13 +515,3 @@ func (this *APINodeDAO) genUniqueId(tx *dbs.Tx) (string, error) {
return uniqueId, nil
}
}
// CountAllLowerVersionNodes 计算所有节点中低于某个版本的节点数量
func (this *APINodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Where("status IS NOT NULL").
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
Param("version", utils.VersionToLong(version)).
Count()
}

View File

@@ -1,6 +1,7 @@
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
"runtime"
@@ -27,6 +28,22 @@ func TestAPINodeDAO_FindEnabledAPINodeIdWithAddr(t *testing.T) {
}
}
func TestAPINodeDAO_FindAllEnabledAPIAccessIPs(t *testing.T) {
var cacheMap = utils.NewCacheMap()
t.Log(NewAPINodeDAO().FindAllEnabledAPIAccessIPs(nil, cacheMap))
t.Log(NewAPINodeDAO().FindAllEnabledAPIAccessIPs(nil, cacheMap))
}
func TestAPINodeDAO_CheckAPINodeIsPrimary(t *testing.T) {
var dao = NewAPINodeDAO()
t.Log(dao.CheckAPINodeIsPrimary(nil))
}
func TestAPINodeDAO_ResetPrimaryAPINode(t *testing.T) {
var dao = NewAPINodeDAO()
t.Log(dao.ResetPrimaryAPINode(nil))
}
func BenchmarkAPINodeDAO_New(b *testing.B) {
runtime.GOMAXPROCS(1)
for i := 0; i < b.N; i++ {

View File

@@ -1,26 +1,29 @@
package models
// API节点
import "github.com/iwind/TeaGo/dbs"
// APINode API节点
type APINode struct {
Id uint32 `field:"id"` // ID
IsOn uint8 `field:"isOn"` // 是否启用
ClusterId uint32 `field:"clusterId"` // 专用集群ID
UniqueId string `field:"uniqueId"` // 唯一ID
Secret string `field:"secret"` // 密钥
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述
Http string `field:"http"` // 监听的HTTP配置
Https string `field:"https"` // 监听的HTTPS配置
RestIsOn uint8 `field:"restIsOn"` // 是否开放REST
RestHTTP string `field:"restHTTP"` // REST HTTP配置
RestHTTPS string `field:"restHTTPS"` // REST HTTPS配置
AccessAddrs string `field:"accessAddrs"` // 外部访问地址
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重
Status string `field:"status"` // 运行状态
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
ClusterId uint32 `field:"clusterId"` // 专用集群ID
UniqueId string `field:"uniqueId"` // 唯一ID
Secret string `field:"secret"` // 密钥
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述
Http dbs.JSON `field:"http"` // 监听的HTTP配置
Https dbs.JSON `field:"https"` // 监听的HTTPS配置
RestIsOn uint8 `field:"restIsOn"` // 是否开放REST
RestHTTP dbs.JSON `field:"restHTTP"` // REST HTTP配置
RestHTTPS dbs.JSON `field:"restHTTPS"` // REST HTTPS配置
AccessAddrs dbs.JSON `field:"accessAddrs"` // 外部访问地址
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重
Status dbs.JSON `field:"status"` // 运行状态
IsPrimary bool `field:"isPrimary"` // 是否为主API节点
}
type APINodeOperator struct {
@@ -43,6 +46,7 @@ type APINodeOperator struct {
AdminId interface{} // 管理员ID
Weight interface{} // 权重
Status interface{} // 运行状态
IsPrimary interface{} // 是否为主API节点
}
func NewAPINodeOperator() *APINodeOperator {

View File

@@ -2,17 +2,18 @@ package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/dbs"
)
// 解析HTTP配置
// DecodeHTTP 解析HTTP配置
func (this *APINode) DecodeHTTP() (*serverconfigs.HTTPProtocolConfig, error) {
if !IsNotNull(this.Http) {
return nil, nil
}
config := &serverconfigs.HTTPProtocolConfig{}
err := json.Unmarshal([]byte(this.Http), config)
err := json.Unmarshal(this.Http, config)
if err != nil {
return nil, err
}
@@ -25,13 +26,13 @@ func (this *APINode) DecodeHTTP() (*serverconfigs.HTTPProtocolConfig, error) {
return config, nil
}
// 解析HTTPS配置
func (this *APINode) DecodeHTTPS(tx *dbs.Tx) (*serverconfigs.HTTPSProtocolConfig, error) {
// DecodeHTTPS 解析HTTPS配置
func (this *APINode) DecodeHTTPS(tx *dbs.Tx, cacheMap *utils.CacheMap) (*serverconfigs.HTTPSProtocolConfig, error) {
if !IsNotNull(this.Https) {
return nil, nil
}
config := &serverconfigs.HTTPSProtocolConfig{}
err := json.Unmarshal([]byte(this.Https), config)
err := json.Unmarshal(this.Https, config)
if err != nil {
return nil, err
}
@@ -44,7 +45,7 @@ func (this *APINode) DecodeHTTPS(tx *dbs.Tx) (*serverconfigs.HTTPSProtocolConfig
if config.SSLPolicyRef != nil {
policyId := config.SSLPolicyRef.SSLPolicyId
if policyId > 0 {
sslPolicy, err := SharedSSLPolicyDAO.ComposePolicyConfig(tx, policyId)
sslPolicy, err := SharedSSLPolicyDAO.ComposePolicyConfig(tx, policyId, cacheMap)
if err != nil {
return nil, err
}
@@ -62,14 +63,14 @@ func (this *APINode) DecodeHTTPS(tx *dbs.Tx) (*serverconfigs.HTTPSProtocolConfig
return config, nil
}
// 解析访问地址
// DecodeAccessAddrs 解析访问地址
func (this *APINode) DecodeAccessAddrs() ([]*serverconfigs.NetworkAddressConfig, error) {
if !IsNotNull(this.AccessAddrs) {
return nil, nil
}
addrConfigs := []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal([]byte(this.AccessAddrs), &addrConfigs)
err := json.Unmarshal(this.AccessAddrs, &addrConfigs)
if err != nil {
return nil, err
}
@@ -82,7 +83,7 @@ func (this *APINode) DecodeAccessAddrs() ([]*serverconfigs.NetworkAddressConfig,
return addrConfigs, nil
}
// 解析访问地址,并返回字符串形式
// DecodeAccessAddrStrings 解析访问地址,并返回字符串形式
func (this *APINode) DecodeAccessAddrStrings() ([]string, error) {
addrs, err := this.DecodeAccessAddrs()
if err != nil {
@@ -95,7 +96,7 @@ func (this *APINode) DecodeAccessAddrStrings() ([]string, error) {
return result, nil
}
// 解析Rest HTTP配置
// DecodeRestHTTP 解析Rest HTTP配置
func (this *APINode) DecodeRestHTTP() (*serverconfigs.HTTPProtocolConfig, error) {
if this.RestIsOn != 1 {
return nil, nil
@@ -104,7 +105,7 @@ func (this *APINode) DecodeRestHTTP() (*serverconfigs.HTTPProtocolConfig, error)
return nil, nil
}
config := &serverconfigs.HTTPProtocolConfig{}
err := json.Unmarshal([]byte(this.RestHTTP), config)
err := json.Unmarshal(this.RestHTTP, config)
if err != nil {
return nil, err
}
@@ -117,8 +118,11 @@ func (this *APINode) DecodeRestHTTP() (*serverconfigs.HTTPProtocolConfig, error)
return config, nil
}
// 解析HTTPS配置
func (this *APINode) DecodeRestHTTPS(tx *dbs.Tx) (*serverconfigs.HTTPSProtocolConfig, error) {
// DecodeRestHTTPS 解析HTTPS配置
func (this *APINode) DecodeRestHTTPS(tx *dbs.Tx, cacheMap *utils.CacheMap) (*serverconfigs.HTTPSProtocolConfig, error) {
if cacheMap == nil {
cacheMap = utils.NewCacheMap()
}
if this.RestIsOn != 1 {
return nil, nil
}
@@ -126,7 +130,7 @@ func (this *APINode) DecodeRestHTTPS(tx *dbs.Tx) (*serverconfigs.HTTPSProtocolCo
return nil, nil
}
config := &serverconfigs.HTTPSProtocolConfig{}
err := json.Unmarshal([]byte(this.RestHTTPS), config)
err := json.Unmarshal(this.RestHTTPS, config)
if err != nil {
return nil, err
}
@@ -139,7 +143,7 @@ func (this *APINode) DecodeRestHTTPS(tx *dbs.Tx) (*serverconfigs.HTTPSProtocolCo
if config.SSLPolicyRef != nil {
policyId := config.SSLPolicyRef.SSLPolicyId
if policyId > 0 {
sslPolicy, err := SharedSSLPolicyDAO.ComposePolicyConfig(tx, policyId)
sslPolicy, err := SharedSSLPolicyDAO.ComposePolicyConfig(tx, policyId, cacheMap)
if err != nil {
return nil, err
}

View File

@@ -1,6 +1,7 @@
package models
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -34,7 +35,7 @@ func init() {
})
}
// 启用条目
// EnableApiToken 启用条目
func (this *ApiTokenDAO) EnableApiToken(tx *dbs.Tx, id uint32) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
@@ -42,7 +43,7 @@ func (this *ApiTokenDAO) EnableApiToken(tx *dbs.Tx, id uint32) (rowsAffected int
Update()
}
// 禁用条目
// DisableApiToken 禁用条目
func (this *ApiTokenDAO) DisableApiToken(tx *dbs.Tx, id uint32) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
@@ -50,7 +51,7 @@ func (this *ApiTokenDAO) DisableApiToken(tx *dbs.Tx, id uint32) (rowsAffected in
Update()
}
// 查找启用中的条目
// FindEnabledApiToken 查找启用中的条目
func (this *ApiTokenDAO) FindEnabledApiToken(tx *dbs.Tx, id uint32) (*ApiToken, error) {
result, err := this.Query(tx).
Pk(id).
@@ -62,7 +63,7 @@ func (this *ApiTokenDAO) FindEnabledApiToken(tx *dbs.Tx, id uint32) (*ApiToken,
return result.(*ApiToken), err
}
// 获取可缓存的节点Token信息
// FindEnabledTokenWithNodeCacheable 获取可缓存的节点Token信息
func (this *ApiTokenDAO) FindEnabledTokenWithNodeCacheable(tx *dbs.Tx, nodeId string) (*ApiToken, error) {
SharedCacheLocker.RLock()
token, ok := apiTokenCacheMap[nodeId]
@@ -85,7 +86,7 @@ func (this *ApiTokenDAO) FindEnabledTokenWithNodeCacheable(tx *dbs.Tx, nodeId st
return nil, err
}
// 获取节点Token信息并可以缓存
// FindEnabledTokenWithNode 获取节点Token信息并可以缓存
func (this *ApiTokenDAO) FindEnabledTokenWithNode(tx *dbs.Tx, nodeId string) (*ApiToken, error) {
one, err := this.Query(tx).
Attr("nodeId", nodeId).
@@ -97,7 +98,7 @@ func (this *ApiTokenDAO) FindEnabledTokenWithNode(tx *dbs.Tx, nodeId string) (*A
return nil, err
}
// 根据角色获取节点
// FindEnabledTokenWithRole 根据角色获取节点
func (this *ApiTokenDAO) FindEnabledTokenWithRole(tx *dbs.Tx, role string) (*ApiToken, error) {
one, err := this.Query(tx).
Attr("role", role).
@@ -109,8 +110,8 @@ func (this *ApiTokenDAO) FindEnabledTokenWithRole(tx *dbs.Tx, role string) (*Api
return nil, err
}
// 保存API Token
func (this *ApiTokenDAO) CreateAPIToken(tx *dbs.Tx, nodeId string, secret string, role NodeRole) error {
// CreateAPIToken 保存API Token
func (this *ApiTokenDAO) CreateAPIToken(tx *dbs.Tx, nodeId string, secret string, role nodeconfigs.NodeRole) error {
op := NewApiTokenOperator()
op.NodeId = nodeId
op.Secret = secret
@@ -119,3 +120,13 @@ func (this *ApiTokenDAO) CreateAPIToken(tx *dbs.Tx, nodeId string, secret string
err := this.Save(tx, op)
return err
}
// FindAllEnabledAPITokens 读取API令牌
func (this *ApiTokenDAO) FindAllEnabledAPITokens(tx *dbs.Tx, role string) (result []*ApiToken, err error) {
_, err = this.Query(tx).
Attr("role", role).
State(ApiTokenStateEnabled).
Slice(&result).
FindAll()
return
}

View File

@@ -2,9 +2,11 @@ package authority
import (
"encoding/json"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
@@ -26,6 +28,9 @@ var SharedAuthorityKeyDAO *AuthorityKeyDAO
func init() {
dbs.OnReady(func() {
SharedAuthorityKeyDAO = NewAuthorityKeyDAO()
// 初始化IsPlus值
_, _ = SharedAuthorityKeyDAO.IsPlus(nil)
})
}
@@ -72,7 +77,14 @@ func (this *AuthorityKeyDAO) ReadKey(tx *dbs.Tx) (key *AuthorityKey, err error)
if one == nil {
return nil, nil
}
return one.(*AuthorityKey), nil
key = one.(*AuthorityKey)
// 顺便更新相关变量
if key.DayTo >= timeutil.Format("Y-m-d") {
teaconst.IsPlus = true
}
return
}
// ResetKey 重置Key

View File

@@ -0,0 +1,14 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package authority
import (
"github.com/iwind/TeaGo/dbs"
)
// IsPlus 判断是否为企业版
func (this *AuthorityKeyDAO) IsPlus(tx *dbs.Tx) (bool, error) {
return false, nil
}

View File

@@ -7,17 +7,17 @@ import (
)
func TestAuthorityKeyDAO_UpdateValue(t *testing.T) {
err := NewAuthorityKeyDAO().UpdateValue(nil, "12345678")
err := NewAuthorityKeyDAO().UpdateKey(nil, "12345678", "", "", "", []string{}, "")
if err != nil {
t.Fatal(err)
}
t.Logf("ok")
t.Log("ok")
}
func TestAuthorityKeyDAO_ReadValue(t *testing.T) {
value, err := NewAuthorityKeyDAO().ReadValue(nil)
value, err := NewAuthorityKeyDAO().ReadKey(nil)
if err != nil {
t.Fatal(err)
}
t.Logf(value)
t.Log(value)
}

View File

@@ -1,15 +1,17 @@
package authority
import "github.com/iwind/TeaGo/dbs"
// AuthorityKey 企业版认证信息
type AuthorityKey struct {
Id uint32 `field:"id"` // ID
Value string `field:"value"` // Key值
DayFrom string `field:"dayFrom"` // 开始日期
DayTo string `field:"dayTo"` // 结束日期
Hostname string `field:"hostname"` // Hostname
MacAddresses string `field:"macAddresses"` // MAC地址
UpdatedAt uint64 `field:"updatedAt"` // 创建/修改时间
Company string `field:"company"` // 公司组织
Id uint32 `field:"id"` // ID
Value string `field:"value"` // Key值
DayFrom string `field:"dayFrom"` // 开始日期
DayTo string `field:"dayTo"` // 结束日期
Hostname string `field:"hostname"` // Hostname
MacAddresses dbs.JSON `field:"macAddresses"` // MAC地址
UpdatedAt uint64 `field:"updatedAt"` // 创建/修改时间
Company string `field:"company"` // 公司组织
}
type AuthorityKeyOperator struct {

View File

@@ -1,9 +1,11 @@
package authority
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -113,7 +115,7 @@ func (this *AuthorityNodeDAO) CreateAuthorityNode(tx *dbs.Tx, name string, descr
return 0, err
}
secret := rands.String(32)
err = models.NewApiTokenDAO().CreateAPIToken(tx, uniqueId, secret, models.NodeRoleAuthority)
err = models.NewApiTokenDAO().CreateAPIToken(tx, uniqueId, secret, nodeconfigs.NodeRoleAuthority)
if err != nil {
return
}
@@ -187,13 +189,19 @@ func (this *AuthorityNodeDAO) GenUniqueId(tx *dbs.Tx) (string, error) {
}
// UpdateNodeStatus 更改节点状态
func (this *AuthorityNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, statusJSON []byte) error {
if statusJSON == nil {
func (this *AuthorityNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, nodeStatus *nodeconfigs.NodeStatus) error {
if nodeStatus == nil {
return nil
}
_, err := this.Query(tx).
nodeStatusJSON, err := json.Marshal(nodeStatus)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(nodeId).
Set("status", string(statusJSON)).
Set("status", nodeStatusJSON).
Update()
return err
}

View File

@@ -1,19 +1,21 @@
package authority
import "github.com/iwind/TeaGo/dbs"
// AuthorityNode 监控节点
type AuthorityNode struct {
Id uint32 `field:"id"` // ID
IsOn uint8 `field:"isOn"` // 是否启用
UniqueId string `field:"uniqueId"` // 唯一ID
Secret string `field:"secret"` // 密钥
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重
Status string `field:"status"` // 运行状态
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
UniqueId string `field:"uniqueId"` // 唯一ID
Secret string `field:"secret"` // 密钥
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重
Status dbs.JSON `field:"status"` // 运行状态
}
type AuthorityNodeOperator struct {

View File

@@ -5,6 +5,7 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"strconv"
)
const (
@@ -35,7 +36,7 @@ func init() {
})
}
// 启用条目
// EnableClientBrowser 启用条目
func (this *ClientBrowserDAO) EnableClientBrowser(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
@@ -44,7 +45,7 @@ func (this *ClientBrowserDAO) EnableClientBrowser(tx *dbs.Tx, id uint32) error {
return err
}
// 禁用条目
// DisableClientBrowser 禁用条目
func (this *ClientBrowserDAO) DisableClientBrowser(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
@@ -53,7 +54,7 @@ func (this *ClientBrowserDAO) DisableClientBrowser(tx *dbs.Tx, id uint32) error
return err
}
// 查找启用中的条目
// FindEnabledClientBrowser 查找启用中的条目
func (this *ClientBrowserDAO) FindEnabledClientBrowser(tx *dbs.Tx, id int64) (*ClientBrowser, error) {
result, err := this.Query(tx).
Pk(id).
@@ -65,7 +66,7 @@ func (this *ClientBrowserDAO) FindEnabledClientBrowser(tx *dbs.Tx, id int64) (*C
return result.(*ClientBrowser), err
}
// 根据主键查找名称
// FindClientBrowserName 根据主键查找名称
func (this *ClientBrowserDAO) FindClientBrowserName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
@@ -73,7 +74,7 @@ func (this *ClientBrowserDAO) FindClientBrowserName(tx *dbs.Tx, id uint32) (stri
FindStringCol("")
}
// 根据浏览器名称查找浏览器ID
// FindBrowserIdWithNameCacheable 根据浏览器名称查找浏览器ID
func (this *ClientBrowserDAO) FindBrowserIdWithNameCacheable(tx *dbs.Tx, browserName string) (int64, error) {
SharedCacheLocker.RLock()
browserId, ok := clientBrowserNameAndIdCacheMap[browserName]
@@ -85,7 +86,7 @@ func (this *ClientBrowserDAO) FindBrowserIdWithNameCacheable(tx *dbs.Tx, browser
browserId, err := this.Query(tx).
Where("JSON_CONTAINS(codes, :browserName)").
Param("browserName", "\""+browserName+"\""). // 查询的需要是个JSON字符串所以这里加双引号
Param("browserName", strconv.Quote(browserName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
@@ -102,8 +103,28 @@ func (this *ClientBrowserDAO) FindBrowserIdWithNameCacheable(tx *dbs.Tx, browser
return browserId, nil
}
// 创建浏览器
// CreateBrowser 创建浏览器
func (this *ClientBrowserDAO) CreateBrowser(tx *dbs.Tx, browserName string) (int64, error) {
var maxlength = 50
if len(browserName) > maxlength {
browserName = browserName[:50]
}
SharedCacheLocker.Lock()
defer SharedCacheLocker.Unlock()
// 检查是否已经创建
browserId, err := this.Query(tx).
Attr("name", browserName).
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
if browserId > 0 {
return browserId, nil
}
op := NewClientBrowserOperator()
op.Name = browserName
codes := []string{browserName}

View File

@@ -1,11 +1,13 @@
package models
// 终端浏览器信息
import "github.com/iwind/TeaGo/dbs"
// ClientBrowser 终端浏览器信息
type ClientBrowser struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 浏览器名称
Codes string `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 浏览器名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` // 状态
}
type ClientBrowserOperator struct {

View File

@@ -5,6 +5,7 @@ import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"strconv"
)
const (
@@ -35,7 +36,7 @@ func init() {
})
}
// 启用条目
// EnableClientSystem 启用条目
func (this *ClientSystemDAO) EnableClientSystem(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
@@ -44,7 +45,7 @@ func (this *ClientSystemDAO) EnableClientSystem(tx *dbs.Tx, id uint32) error {
return err
}
// 禁用条目
// DisableClientSystem 禁用条目
func (this *ClientSystemDAO) DisableClientSystem(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
@@ -53,7 +54,7 @@ func (this *ClientSystemDAO) DisableClientSystem(tx *dbs.Tx, id uint32) error {
return err
}
// 查找启用中的条目
// FindEnabledClientSystem 查找启用中的条目
func (this *ClientSystemDAO) FindEnabledClientSystem(tx *dbs.Tx, id int64) (*ClientSystem, error) {
result, err := this.Query(tx).
Pk(id).
@@ -65,7 +66,7 @@ func (this *ClientSystemDAO) FindEnabledClientSystem(tx *dbs.Tx, id int64) (*Cli
return result.(*ClientSystem), err
}
// 根据主键查找名称
// FindClientSystemName 根据主键查找名称
func (this *ClientSystemDAO) FindClientSystemName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
@@ -73,7 +74,7 @@ func (this *ClientSystemDAO) FindClientSystemName(tx *dbs.Tx, id uint32) (string
FindStringCol("")
}
// 根据操作系统名称查找系统ID
// FindSystemIdWithNameCacheable 根据操作系统名称查找系统ID
func (this *ClientSystemDAO) FindSystemIdWithNameCacheable(tx *dbs.Tx, systemName string) (int64, error) {
SharedCacheLocker.RLock()
systemId, ok := clientSystemNameAndIdCacheMap[systemName]
@@ -85,7 +86,7 @@ func (this *ClientSystemDAO) FindSystemIdWithNameCacheable(tx *dbs.Tx, systemNam
systemId, err := this.Query(tx).
Where("JSON_CONTAINS(codes, :systemName)").
Param("systemName", "\""+systemName+"\""). // 查询的需要是个JSON字符串所以这里加双引号
Param("systemName", strconv.Quote(systemName)). // 查询的需要是个JSON字符串所以这里加双引号
ResultPk().
FindInt64Col(0)
if err != nil {
@@ -102,8 +103,28 @@ func (this *ClientSystemDAO) FindSystemIdWithNameCacheable(tx *dbs.Tx, systemNam
return systemId, nil
}
// 创建浏览器
// CreateSystem 创建浏览器
func (this *ClientSystemDAO) CreateSystem(tx *dbs.Tx, systemName string) (int64, error) {
var maxlength = 50
if len(systemName) > maxlength {
systemName = systemName[:50]
}
SharedCacheLocker.Lock()
defer SharedCacheLocker.Unlock()
// 检查是否已经创建
systemId, err := this.Query(tx).
Attr("name", systemName).
ResultPk().
FindInt64Col(0)
if err != nil {
return 0, err
}
if systemId > 0 {
return systemId, nil
}
op := NewClientSystemOperator()
op.Name = systemName

View File

@@ -1,11 +1,13 @@
package models
// 终端操作系统信息
import "github.com/iwind/TeaGo/dbs"
// ClientSystem 终端操作系统信息
type ClientSystem struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 系统名称
Codes string `field:"codes"` // 代号
State uint8 `field:"state"` //
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 系统名称
Codes dbs.JSON `field:"codes"` // 代号
State uint8 `field:"state"` //
}
type ClientSystemOperator struct {

View File

@@ -4,6 +4,7 @@ import (
"encoding/base64"
"github.com/TeaOSLab/EdgeAPI/internal/encrypt"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -39,7 +40,7 @@ func init() {
})
}
// 启用条目
// EnableDBNode 启用条目
func (this *DBNodeDAO) EnableDBNode(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -48,16 +49,21 @@ func (this *DBNodeDAO) EnableDBNode(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
func (this *DBNodeDAO) DisableDBNode(tx *dbs.Tx, id int64) error {
// DisableDBNode 禁用条目
func (this *DBNodeDAO) DisableDBNode(tx *dbs.Tx, nodeId int64) error {
_, err := this.Query(tx).
Pk(id).
Pk(nodeId).
Set("state", DBNodeStateDisabled).
Update()
return err
if err != nil {
return err
}
// 删除运行日志
return SharedNodeLogDAO.DeleteNodeLogs(tx, nodeconfigs.NodeRoleDatabase, nodeId)
}
// 查找启用中的条目
// FindEnabledDBNode 查找启用中的条目
func (this *DBNodeDAO) FindEnabledDBNode(tx *dbs.Tx, id int64) (*DBNode, error) {
result, err := this.Query(tx).
Pk(id).
@@ -71,7 +77,7 @@ func (this *DBNodeDAO) FindEnabledDBNode(tx *dbs.Tx, id int64) (*DBNode, error)
return node, nil
}
// 根据主键查找名称
// FindDBNodeName 根据主键查找名称
func (this *DBNodeDAO) FindDBNodeName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
@@ -79,14 +85,14 @@ func (this *DBNodeDAO) FindDBNodeName(tx *dbs.Tx, id int64) (string, error) {
FindStringCol("")
}
// 计算可用的节点数量
// CountAllEnabledNodes 计算可用的节点数量
func (this *DBNodeDAO) CountAllEnabledNodes(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(DBNodeStateEnabled).
Count()
}
// 获取单页的节点
// ListEnabledNodes 获取单页的节点
func (this *DBNodeDAO) ListEnabledNodes(tx *dbs.Tx, offset int64, size int64) (result []*DBNode, err error) {
_, err = this.Query(tx).
State(DBNodeStateEnabled).
@@ -101,7 +107,7 @@ func (this *DBNodeDAO) ListEnabledNodes(tx *dbs.Tx, offset int64, size int64) (r
return
}
// 创建节点
// CreateDBNode 创建节点
func (this *DBNodeDAO) CreateDBNode(tx *dbs.Tx, isOn bool, name string, description string, host string, port int32, database string, username string, password string, charset string) (int64, error) {
op := NewDBNodeOperator()
op.State = NodeStateEnabled
@@ -121,7 +127,7 @@ func (this *DBNodeDAO) CreateDBNode(tx *dbs.Tx, isOn bool, name string, descript
return types.Int64(op.Id), nil
}
// 修改节点
// UpdateNode 修改节点
func (this *DBNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, isOn bool, name string, description string, host string, port int32, database string, username string, password string, charset string) error {
if nodeId <= 0 {
return errors.New("invalid nodeId")
@@ -141,7 +147,7 @@ func (this *DBNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, isOn bool, name stri
return err
}
// 查找所有可用的数据库节点
// FindAllEnabledAndOnDBNodes 查找所有可用的数据库节点
func (this *DBNodeDAO) FindAllEnabledAndOnDBNodes(tx *dbs.Tx) (result []*DBNode, err error) {
_, err = this.Query(tx).
State(DBNodeStateEnabled).
@@ -155,7 +161,7 @@ func (this *DBNodeDAO) FindAllEnabledAndOnDBNodes(tx *dbs.Tx) (result []*DBNode,
return
}
// 加密密码
// EncodePassword 加密密码
func (this *DBNodeDAO) EncodePassword(password string) string {
if strings.HasPrefix(password, DBNodePasswordEncodedPrefix) {
return password
@@ -164,7 +170,7 @@ func (this *DBNodeDAO) EncodePassword(password string) string {
return DBNodePasswordEncodedPrefix + encodedString
}
// 解密密码
// DecodePassword 解密密码
func (this *DBNodeDAO) DecodePassword(password string) string {
if !strings.HasPrefix(password, DBNodePasswordEncodedPrefix) {
return password
@@ -176,3 +182,15 @@ func (this *DBNodeDAO) DecodePassword(password string) string {
}
return string(encrypt.MagicKeyDecode(data))
}
// CheckNodeIsOn 检查节点是否已经启用
func (this *DBNodeDAO) CheckNodeIsOn(tx *dbs.Tx, nodeId int64) (bool, error) {
isOn, err := this.Query(tx).
Pk(nodeId).
Result("isOn").
FindIntCol(0)
if err != nil {
return false, err
}
return isOn == 1, nil
}

View File

@@ -3,9 +3,12 @@ package models
import (
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
timeutil "github.com/iwind/TeaGo/utils/time"
"hash/crc32"
"regexp"
@@ -15,30 +18,70 @@ import (
"time"
)
var accessLogDBMapping = map[int64]*dbs.DB{} // dbNodeId => DB
var accessLogDAOMapping = map[int64]*HTTPAccessLogDAOWrapper{} // dbNodeId => DAO
var accessLogDBMapping = map[int64]*dbs.DB{} // dbNodeId => DB
var accessLogLocker = &sync.RWMutex{}
var accessLogTableMapping = map[string]bool{} // tableName_crc(dsn) => true
type httpAccessLogDefinition struct {
Name string
HasRemoteAddr bool
HasDomain bool
Exists bool
}
// HTTP服务访问
var httpAccessLogDAOMapping = map[int64]*HTTPAccessLogDAOWrapper{} // dbNodeId => DAO
// DNS服务访问
var nsAccessLogDAOMapping = map[int64]*NSAccessLogDAOWrapper{} // dbNodeId => DAO
var nsAccessLogTableMapping = map[string]bool{} // tableName_crc(dsn) => true
// HTTPAccessLogDAOWrapper HTTP访问日志DAO
type HTTPAccessLogDAOWrapper struct {
DAO *HTTPAccessLogDAO
NodeId int64
}
// NSAccessLogDAOWrapper NS访问日志DAO
type NSAccessLogDAOWrapper struct {
DAO *NSAccessLogDAO
NodeId int64
}
func init() {
initializer := NewDBNodeInitializer()
dbs.OnReadyDone(func() {
go initializer.Start()
goman.New(func() {
initializer.Start()
})
})
}
func AllAccessLogDBs() []*dbs.DB {
accessLogLocker.Lock()
defer accessLogLocker.Unlock()
var result = []*dbs.DB{}
for _, db := range accessLogDBMapping {
result = append(result, db)
}
if len(result) == 0 {
db, _ := dbs.Default()
if db != nil {
result = append(result, db)
}
}
return result
}
// 获取获取DAO
func randomAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) {
func randomHTTPAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) {
accessLogLocker.RLock()
if len(accessLogDAOMapping) == 0 {
if len(httpAccessLogDAOMapping) == 0 {
dao = nil
} else {
for _, d := range accessLogDAOMapping {
for _, d := range httpAccessLogDAOMapping {
dao = d
break
}
@@ -47,8 +90,21 @@ func randomAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) {
return
}
// 检查表格是否存在
func findAccessLogTableName(db *dbs.DB, day string) (tableName string, ok bool, err error) {
func randomNSAccessLogDAO() (dao *NSAccessLogDAOWrapper) {
accessLogLocker.RLock()
if len(nsAccessLogDAOMapping) == 0 {
dao = nil
} else {
for _, d := range nsAccessLogDAOMapping {
dao = d
break
}
}
accessLogLocker.RUnlock()
return
}
func findNSAccessLogTableName(db *dbs.DB, day string) (tableName string, ok bool, err error) {
if !regexp.MustCompile(`^\d{8}$`).MatchString(day) {
err = errors.New("invalid day '" + day + "', should be YYYYMMDD")
return
@@ -59,11 +115,11 @@ func findAccessLogTableName(db *dbs.DB, day string) (tableName string, ok bool,
return "", false, err
}
tableName = "edgeHTTPAccessLogs_" + day
tableName = "edgeNSAccessLogs_" + day
cacheKey := tableName + "_" + fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(config.Dsn)))
accessLogLocker.RLock()
_, ok = accessLogTableMapping[cacheKey]
_, ok = nsAccessLogTableMapping[cacheKey]
accessLogLocker.RUnlock()
if ok {
return tableName, true, nil
@@ -74,22 +130,21 @@ func findAccessLogTableName(db *dbs.DB, day string) (tableName string, ok bool,
return tableName, false, err
}
return tableName, lists.ContainsString(tableNames, tableName), nil
return tableName, utils.ContainsStringInsensitive(tableNames, tableName), nil
}
// 根据日期获取表名
func findAccessLogTable(db *dbs.DB, day string, force bool) (string, error) {
func findNSAccessLogTable(db *dbs.DB, day string, force bool) (string, error) {
config, err := db.Config()
if err != nil {
return "", err
}
tableName := "edgeHTTPAccessLogs_" + day
tableName := "edgeNSAccessLogs_" + day
cacheKey := tableName + "_" + fmt.Sprintf("%d", crc32.ChecksumIEEE([]byte(config.Dsn)))
if !force {
accessLogLocker.RLock()
_, ok := accessLogTableMapping[cacheKey]
_, ok := nsAccessLogTableMapping[cacheKey]
accessLogLocker.RUnlock()
if ok {
return tableName, nil
@@ -101,27 +156,27 @@ func findAccessLogTable(db *dbs.DB, day string, force bool) (string, error) {
return tableName, err
}
if lists.ContainsString(tableNames, tableName) {
if utils.ContainsStringInsensitive(tableNames, tableName) {
accessLogLocker.Lock()
accessLogTableMapping[cacheKey] = true
nsAccessLogTableMapping[cacheKey] = true
accessLogLocker.Unlock()
return tableName, nil
}
// 创建表格
_, err = db.Exec("CREATE TABLE `" + tableName + "` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n `serverId` int(11) unsigned DEFAULT '0' COMMENT '服务ID',\n `nodeId` int(11) unsigned DEFAULT '0' COMMENT '节点ID',\n `status` int(3) unsigned DEFAULT '0' COMMENT '状态码',\n `createdAt` bigint(11) unsigned DEFAULT '0' COMMENT '创建时间',\n `content` json DEFAULT NULL COMMENT '日志内容',\n `requestId` varchar(128) DEFAULT NULL COMMENT '请求ID',\n `firewallPolicyId` int(11) unsigned DEFAULT '0' COMMENT 'WAF策略ID',\n `firewallRuleGroupId` int(11) unsigned DEFAULT '0' COMMENT 'WAF分组ID',\n `firewallRuleSetId` int(11) unsigned DEFAULT '0' COMMENT 'WAF集ID',\n `firewallRuleId` int(11) unsigned DEFAULT '0' COMMENT 'WAF规则ID',\n PRIMARY KEY (`id`),\n KEY `serverId` (`serverId`),\n KEY `nodeId` (`nodeId`),\n KEY `serverId_status` (`serverId`,`status`),\n KEY `requestId` (`requestId`),\n KEY `firewallPolicyId` (`firewallPolicyId`),\n KEY `firewallRuleGroupId` (`firewallRuleGroupId`),\n KEY `firewallRuleSetId` (`firewallRuleSetId`),\n KEY `firewallRuleId` (`firewallRuleId`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='访问日志';")
_, err = db.Exec("CREATE TABLE `" + tableName + "` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n `nodeId` int(11) unsigned DEFAULT '0' COMMENT '节点ID',\n `domainId` int(11) unsigned DEFAULT '0' COMMENT '域名ID',\n `recordId` int(11) unsigned DEFAULT '0' COMMENT '记录ID',\n `content` json DEFAULT NULL COMMENT '访问数据',\n `requestId` varchar(128) DEFAULT NULL COMMENT '请求ID',\n `createdAt` bigint(11) unsigned DEFAULT '0' COMMENT '创建时间',\n `remoteAddr` varchar(128) DEFAULT NULL COMMENT 'IP',\n PRIMARY KEY (`id`),\n KEY `nodeId` (`nodeId`),\n KEY `domainId` (`domainId`),\n KEY `recordId` (`recordId`),\n KEY `requestId` (`requestId`),\n KEY `remoteAddr` (`remoteAddr`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='域名服务访问日志';")
if err != nil {
return tableName, err
}
accessLogLocker.Lock()
accessLogTableMapping[cacheKey] = true
nsAccessLogTableMapping[cacheKey] = true
accessLogLocker.Unlock()
return tableName, nil
}
// 初始化数据库连接
// DBNodeInitializer 初始化数据库连接
type DBNodeInitializer struct {
}
@@ -129,12 +184,12 @@ func NewDBNodeInitializer() *DBNodeInitializer {
return &DBNodeInitializer{}
}
// 启动
// Start 启动
func (this *DBNodeInitializer) Start() {
// 初始运行
err := this.loop()
if err != nil {
logs.Println("[DB_NODE]" + err.Error())
remotelogs.Error("DB_NODE", err.Error())
}
// 定时运行
@@ -142,7 +197,7 @@ func (this *DBNodeInitializer) Start() {
for range ticker.C {
err := this.loop()
if err != nil {
logs.Println("[DB_NODE]" + err.Error())
remotelogs.Error("DB_NODE", err.Error())
}
}
}
@@ -166,8 +221,9 @@ func (this *DBNodeInitializer) loop() error {
if !lists.ContainsInt64(nodeIds, nodeId) {
closingDbs = append(closingDbs, db)
delete(accessLogDBMapping, nodeId)
delete(accessLogDAOMapping, nodeId)
logs.Println("[DB_NODE]close db node '" + strconv.FormatInt(nodeId, 10) + "'")
delete(httpAccessLogDAOMapping, nodeId)
delete(nsAccessLogDAOMapping, nodeId)
remotelogs.Error("DB_NODE", "close db node '"+strconv.FormatInt(nodeId, 10)+"'")
}
}
accessLogLocker.Unlock()
@@ -188,7 +244,7 @@ func (this *DBNodeInitializer) loop() error {
// 检查配置是否有变化
oldConfig, err := db.Config()
if err != nil {
logs.Println("[DB_NODE]read database old config failed: " + err.Error())
remotelogs.Error("DB_NODE", "read database old config failed: "+err.Error())
continue
}
@@ -200,58 +256,101 @@ func (this *DBNodeInitializer) loop() error {
}
if db == nil {
config := &dbs.DBConfig{
var config = &dbs.DBConfig{
Driver: "mysql",
Dsn: dsn,
Prefix: "edge",
}
db, err := dbs.NewInstanceFromConfig(config)
if err != nil {
logs.Println("[DB_NODE]initialize database config failed: " + err.Error())
remotelogs.Error("DB_NODE", "initialize database config failed: "+err.Error())
continue
}
// 检查表是否存在
tableName, err := findAccessLogTable(db, timeutil.Format("Ymd"), false)
if err != nil {
if !strings.Contains(err.Error(), "1050") { // 非表格已存在错误
logs.Println("[DB_NODE]create first table in database node failed: " + err.Error())
// httpAccessLog
{
tableDef, err := SharedHTTPAccessLogManager.FindLastTable(db, timeutil.Format("Ymd"), true)
if err != nil {
remotelogs.Error("DB_NODE", "create first table in database node failed: "+err.Error())
// 创建节点日志
createLogErr := SharedNodeLogDAO.CreateLog(nil, NodeRoleDatabase, nodeId, "error", "ACCESS_LOG", "can not create access log table: "+err.Error(), time.Now().Unix())
createLogErr := SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleDatabase, nodeId, 0, 0, "error", "ACCESS_LOG", "can not create access log table: "+err.Error(), time.Now().Unix(), "", nil)
if createLogErr != nil {
logs.Println("[NODE_LOG]" + createLogErr.Error())
remotelogs.Error("NODE_LOG", createLogErr.Error())
}
continue
} else {
err = nil
}
daoObject := dbs.DAOObject{
Instance: db,
DB: node.Name + "(id:" + strconv.Itoa(int(node.Id)) + ")",
Table: tableDef.Name,
PkName: "id",
Model: new(HTTPAccessLog),
}
err = daoObject.Init()
if err != nil {
remotelogs.Error("DB_NODE", "initialize dao failed: "+err.Error())
continue
}
accessLogLocker.Lock()
accessLogDBMapping[nodeId] = db
dao := &HTTPAccessLogDAO{
DAOObject: daoObject,
}
httpAccessLogDAOMapping[nodeId] = &HTTPAccessLogDAOWrapper{
DAO: dao,
NodeId: nodeId,
}
accessLogLocker.Unlock()
}
daoObject := dbs.DAOObject{
Instance: db,
DB: node.Name + "(id:" + strconv.Itoa(int(node.Id)) + ")",
Table: tableName,
PkName: "id",
Model: new(HTTPAccessLog),
}
err = daoObject.Init()
if err != nil {
logs.Println("[DB_NODE]initialize dao failed: " + err.Error())
continue
}
// nsAccessLog
{
tableName, err := findNSAccessLogTable(db, timeutil.Format("Ymd"), false)
if err != nil {
if !strings.Contains(err.Error(), "1050") { // 非表格已存在错误
remotelogs.Error("DB_NODE", "create first table in database node failed: "+err.Error())
accessLogLocker.Lock()
accessLogDBMapping[nodeId] = db
dao := &HTTPAccessLogDAO{
DAOObject: daoObject,
// 创建节点日志
createLogErr := SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleDatabase, nodeId, 0, 0, "error", "ACCESS_LOG", "can not create access log table: "+err.Error(), time.Now().Unix(), "", nil)
if createLogErr != nil {
remotelogs.Error("NODE_LOG", createLogErr.Error())
}
continue
} else {
err = nil
}
}
daoObject := dbs.DAOObject{
Instance: db,
DB: node.Name + "(id:" + strconv.Itoa(int(node.Id)) + ")",
Table: tableName,
PkName: "id",
Model: new(NSAccessLog),
}
err = daoObject.Init()
if err != nil {
remotelogs.Error("DB_NODE", "initialize dao failed: "+err.Error())
continue
}
accessLogLocker.Lock()
accessLogDBMapping[nodeId] = db
dao := &NSAccessLogDAO{
DAOObject: daoObject,
}
nsAccessLogDAOMapping[nodeId] = &NSAccessLogDAOWrapper{
DAO: dao,
NodeId: nodeId,
}
accessLogLocker.Unlock()
}
accessLogDAOMapping[nodeId] = &HTTPAccessLogDAOWrapper{
DAO: dao,
NodeId: nodeId,
}
accessLogLocker.Unlock()
}
}

View File

@@ -1,9 +1,7 @@
package models
import (
"runtime"
"testing"
"time"
)
func TestDBNodeInitializer_loop(t *testing.T) {
@@ -12,34 +10,5 @@ func TestDBNodeInitializer_loop(t *testing.T) {
if err != nil {
t.Fatal(err)
}
t.Log(len(accessLogDBMapping), len(accessLogDAOMapping))
}
func TestFindAccessLogTable(t *testing.T) {
before := time.Now()
db := SharedHTTPAccessLogDAO.Instance
tableName, err := findAccessLogTable(db, "20201010", false)
if err != nil {
t.Fatal(err)
}
t.Log(tableName)
t.Log(time.Since(before).Seconds()*1000, "ms")
before = time.Now()
tableName, err = findAccessLogTable(db, "20201010", false)
if err != nil {
t.Fatal(err)
}
t.Log(tableName)
t.Log(time.Since(before).Seconds()*1000, "ms")
}
func BenchmarkFindAccessLogTable(b *testing.B) {
db := SharedHTTPAccessLogDAO.Instance
runtime.GOMAXPROCS(1)
for i := 0; i < b.N; i++ {
_, _ = findAccessLogTable(db, "20201010", false)
}
t.Log(len(accessLogDBMapping), len(httpAccessLogDAOMapping))
}

View File

@@ -1,9 +1,9 @@
package models
// 数据库节点
// DBNode 数据库节点
type DBNode struct {
Id uint32 `field:"id"` // ID
IsOn uint8 `field:"isOn"` // 是否启用
IsOn bool `field:"isOn"` // 是否启用
Role string `field:"role"` // 数据库角色
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述

View File

@@ -2,8 +2,9 @@ package dns
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -39,7 +40,7 @@ func init() {
})
}
// 启用条目
// EnableDNSDomain 启用条目
func (this *DNSDomainDAO) EnableDNSDomain(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -48,7 +49,7 @@ func (this *DNSDomainDAO) EnableDNSDomain(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableDNSDomain 禁用条目
func (this *DNSDomainDAO) DisableDNSDomain(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -57,19 +58,30 @@ func (this *DNSDomainDAO) DisableDNSDomain(tx *dbs.Tx, id int64) error {
return err
}
// 查找启用中的条目
func (this *DNSDomainDAO) FindEnabledDNSDomain(tx *dbs.Tx, id int64) (*DNSDomain, error) {
// FindEnabledDNSDomain 查找启用中的条目
func (this *DNSDomainDAO) FindEnabledDNSDomain(tx *dbs.Tx, domainId int64, cacheMap *utils.CacheMap) (*DNSDomain, error) {
var cacheKey = this.Table + ":record:" + types.String(domainId)
if cacheMap != nil {
cache, _ := cacheMap.Get(cacheKey)
if cache != nil {
return cache.(*DNSDomain), nil
}
}
result, err := this.Query(tx).
Pk(id).
Pk(domainId).
Attr("state", DNSDomainStateEnabled).
Find()
if result == nil {
return nil, err
}
if cacheMap != nil {
cacheMap.Put(cacheKey, result)
}
return result.(*DNSDomain), err
}
// 根据主键查找名称
// FindDNSDomainName 根据主键查找名称
func (this *DNSDomainDAO) FindDNSDomainName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
@@ -77,7 +89,7 @@ func (this *DNSDomainDAO) FindDNSDomainName(tx *dbs.Tx, id int64) (string, error
FindStringCol("")
}
// 创建域名
// CreateDomain 创建域名
func (this *DNSDomainDAO) CreateDomain(tx *dbs.Tx, adminId int64, userId int64, providerId int64, name string) (int64, error) {
op := NewDNSDomainOperator()
op.ProviderId = providerId
@@ -86,6 +98,7 @@ func (this *DNSDomainDAO) CreateDomain(tx *dbs.Tx, adminId int64, userId int64,
op.Name = name
op.State = DNSDomainStateEnabled
op.IsOn = true
op.IsUp = true
err := this.Save(tx, op)
if err != nil {
return 0, err
@@ -93,7 +106,7 @@ func (this *DNSDomainDAO) CreateDomain(tx *dbs.Tx, adminId int64, userId int64,
return types.Int64(op.Id), nil
}
// 修改域名
// UpdateDomain 修改域名
func (this *DNSDomainDAO) UpdateDomain(tx *dbs.Tx, domainId int64, name string, isOn bool) error {
if domainId <= 0 {
return errors.New("invalid domainId")
@@ -109,7 +122,7 @@ func (this *DNSDomainDAO) UpdateDomain(tx *dbs.Tx, domainId int64, name string,
return nil
}
// 查询一个服务商下面的所有域名
// FindAllEnabledDomainsWithProviderId 查询一个服务商下面的所有域名
func (this *DNSDomainDAO) FindAllEnabledDomainsWithProviderId(tx *dbs.Tx, providerId int64) (result []*DNSDomain, err error) {
_, err = this.Query(tx).
State(DNSDomainStateEnabled).
@@ -120,7 +133,7 @@ func (this *DNSDomainDAO) FindAllEnabledDomainsWithProviderId(tx *dbs.Tx, provid
return
}
// 计算某个服务商下的域名数量
// CountAllEnabledDomainsWithProviderId 计算某个服务商下的域名数量
func (this *DNSDomainDAO) CountAllEnabledDomainsWithProviderId(tx *dbs.Tx, providerId int64) (int64, error) {
return this.Query(tx).
State(DNSDomainStateEnabled).
@@ -128,7 +141,7 @@ func (this *DNSDomainDAO) CountAllEnabledDomainsWithProviderId(tx *dbs.Tx, provi
Count()
}
// 更新域名数据
// UpdateDomainData 更新域名数据
func (this *DNSDomainDAO) UpdateDomainData(tx *dbs.Tx, domainId int64, data string) error {
if domainId <= 0 {
return errors.New("invalid domainId")
@@ -140,7 +153,7 @@ func (this *DNSDomainDAO) UpdateDomainData(tx *dbs.Tx, domainId int64, data stri
return err
}
// 更新域名解析记录
// UpdateDomainRecords 更新域名解析记录
func (this *DNSDomainDAO) UpdateDomainRecords(tx *dbs.Tx, domainId int64, recordsJSON []byte) error {
if domainId <= 0 {
return errors.New("invalid domainId")
@@ -153,7 +166,7 @@ func (this *DNSDomainDAO) UpdateDomainRecords(tx *dbs.Tx, domainId int64, record
return err
}
// 更新线路
// UpdateDomainRoutes 更新线路
func (this *DNSDomainDAO) UpdateDomainRoutes(tx *dbs.Tx, domainId int64, routesJSON []byte) error {
if domainId <= 0 {
return errors.New("invalid domainId")
@@ -166,8 +179,8 @@ func (this *DNSDomainDAO) UpdateDomainRoutes(tx *dbs.Tx, domainId int64, routesJ
return err
}
// 查找域名线路
func (this *DNSDomainDAO) FindDomainRoutes(tx *dbs.Tx, domainId int64) ([]*dnsclients.Route, error) {
// FindDomainRoutes 查找域名线路
func (this *DNSDomainDAO) FindDomainRoutes(tx *dbs.Tx, domainId int64) ([]*dnstypes.Route, error) {
routes, err := this.Query(tx).
Pk(domainId).
Result("routes").
@@ -178,7 +191,7 @@ func (this *DNSDomainDAO) FindDomainRoutes(tx *dbs.Tx, domainId int64) ([]*dnscl
if len(routes) == 0 || routes == "null" {
return nil, nil
}
result := []*dnsclients.Route{}
result := []*dnstypes.Route{}
err = json.Unmarshal([]byte(routes), &result)
if err != nil {
return nil, err
@@ -186,7 +199,7 @@ func (this *DNSDomainDAO) FindDomainRoutes(tx *dbs.Tx, domainId int64) ([]*dnscl
return result, nil
}
// 查找线路名称
// FindDomainRouteName 查找线路名称
func (this *DNSDomainDAO) FindDomainRouteName(tx *dbs.Tx, domainId int64, routeCode string) (string, error) {
routes, err := this.FindDomainRoutes(tx, domainId)
if err != nil {
@@ -200,7 +213,7 @@ func (this *DNSDomainDAO) FindDomainRouteName(tx *dbs.Tx, domainId int64, routeC
return "", nil
}
// 判断是否有域名可选
// ExistAvailableDomains 判断是否有域名可选
func (this *DNSDomainDAO) ExistAvailableDomains(tx *dbs.Tx) (bool, error) {
subQuery, err := SharedDNSProviderDAO.Query(tx).
Where("state=1"). // 这里要使用非变量
@@ -216,8 +229,10 @@ func (this *DNSDomainDAO) ExistAvailableDomains(tx *dbs.Tx) (bool, error) {
Exist()
}
// 检查域名解析记录是否存在
// ExistDomainRecord 检查域名解析记录是否存在
func (this *DNSDomainDAO) ExistDomainRecord(tx *dbs.Tx, domainId int64, recordName string, recordType string, recordRoute string, recordValue string) (bool, error) {
recordType = strings.ToUpper(recordType)
query := maps.Map{
"name": recordName,
"type": recordType,
@@ -239,10 +254,38 @@ func (this *DNSDomainDAO) ExistDomainRecord(tx *dbs.Tx, domainId int64, recordNa
}
}
}
recordType = strings.ToUpper(recordType)
return this.Query(tx).
Pk(domainId).
Where("JSON_CONTAINS(records, :query)").
Param("query", query.AsJSON()).
Exist()
}
// FindEnabledDomainWithName 根据名称查找某个域名
func (this *DNSDomainDAO) FindEnabledDomainWithName(tx *dbs.Tx, providerId int64, domainName string) (*DNSDomain, error) {
one, err := this.Query(tx).
State(DNSDomainStateEnabled).
Attr("providerId", providerId).
Attr("name", domainName).
Find()
if one != nil {
return one.(*DNSDomain), nil
}
return nil, err
}
// UpdateDomainIsUp 设置是否在线
func (this *DNSDomainDAO) UpdateDomainIsUp(tx *dbs.Tx, domainId int64, isUp bool) error {
return this.Query(tx).
Pk(domainId).
Set("isUp", isUp).
UpdateQuickly()
}
// UpdateDomainIsDeleted 设置域名为删除
func (this *DNSDomainDAO) UpdateDomainIsDeleted(tx *dbs.Tx, domainId int64, isDeleted bool) error {
return this.Query(tx).
Pk(domainId).
Set("isDeleted", isDeleted).
UpdateQuickly()
}

View File

@@ -1,20 +1,24 @@
package dns
// 管理的域名
import "github.com/iwind/TeaGo/dbs"
// DNSDomain 管理的域名
type DNSDomain struct {
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
ProviderId uint32 `field:"providerId"` // 服务商ID
IsOn uint8 `field:"isOn"` // 是否可用
Name string `field:"name"` // 域名
CreatedAt uint64 `field:"createdAt"` // 创建时间
DataUpdatedAt uint64 `field:"dataUpdatedAt"` // 数据更新时间
DataError string `field:"dataError"` // 数据更新错误
Data string `field:"data"` // 原始数据信息
Records string `field:"records"` // 所有解析记录
Routes string `field:"routes"` // 线路数据
State uint8 `field:"state"` // 状态
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
ProviderId uint32 `field:"providerId"` // 服务商ID
IsOn bool `field:"isOn"` // 是否可用
Name string `field:"name"` // 域名
CreatedAt uint64 `field:"createdAt"` // 创建时间
DataUpdatedAt uint64 `field:"dataUpdatedAt"` // 数据更新时间
DataError string `field:"dataError"` // 数据更新错误
Data string `field:"data"` // 原始数据信息
Records dbs.JSON `field:"records"` // 所有解析记录
Routes dbs.JSON `field:"routes"` // 线路数据
IsUp bool `field:"isUp"` // 是否在线
State uint8 `field:"state"` // 状态
IsDeleted bool `field:"isDeleted"` // 是否已删除
}
type DNSDomainOperator struct {
@@ -30,7 +34,9 @@ type DNSDomainOperator struct {
Data interface{} // 原始数据信息
Records interface{} // 所有解析记录
Routes interface{} // 线路数据
IsUp interface{} // 是否在线
State interface{} // 状态
IsDeleted interface{} // 是否已删除
}
func NewDNSDomainOperator() *DNSDomainOperator {

View File

@@ -2,23 +2,23 @@ package dns
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
)
// 获取所有的线路
func (this *DNSDomain) DecodeRoutes() ([]*dnsclients.Route, error) {
if len(this.Routes) == 0 || this.Routes == "null" {
// DecodeRoutes 获取所有的线路
func (this *DNSDomain) DecodeRoutes() ([]*dnstypes.Route, error) {
if len(this.Routes) == 0 {
return nil, nil
}
result := []*dnsclients.Route{}
err := json.Unmarshal([]byte(this.Routes), &result)
result := []*dnstypes.Route{}
err := json.Unmarshal(this.Routes, &result)
if err != nil {
return nil, err
}
return result, nil
}
// 检查是否包含某个线路
// ContainsRouteCode 检查是否包含某个线路
func (this *DNSDomain) ContainsRouteCode(route string) (bool, error) {
routes, err := this.DecodeRoutes()
if err != nil {
@@ -32,14 +32,14 @@ func (this *DNSDomain) ContainsRouteCode(route string) (bool, error) {
return false, nil
}
// 获取所有的记录
func (this *DNSDomain) DecodeRecords() ([]*dnsclients.Record, error) {
// DecodeRecords 获取所有的记录
func (this *DNSDomain) DecodeRecords() ([]*dnstypes.Record, error) {
records := this.Records
if len(records) == 0 || records == "null" {
if len(records) == 0 {
return nil, nil
}
result := []*dnsclients.Record{}
err := json.Unmarshal([]byte(records), &result)
result := []*dnstypes.Record{}
err := json.Unmarshal(records, &result)
if err != nil {
return nil, err
}

View File

@@ -36,7 +36,7 @@ func init() {
})
}
// 启用条目
// EnableDNSProvider 启用条目
func (this *DNSProviderDAO) EnableDNSProvider(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -45,7 +45,7 @@ func (this *DNSProviderDAO) EnableDNSProvider(tx *dbs.Tx, id int64) error {
return err
}
// 禁用条目
// DisableDNSProvider 禁用条目
func (this *DNSProviderDAO) DisableDNSProvider(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
@@ -54,7 +54,7 @@ func (this *DNSProviderDAO) DisableDNSProvider(tx *dbs.Tx, id int64) error {
return err
}
// 查找启用中的条目
// FindEnabledDNSProvider 查找启用中的条目
func (this *DNSProviderDAO) FindEnabledDNSProvider(tx *dbs.Tx, id int64) (*DNSProvider, error) {
result, err := this.Query(tx).
Pk(id).
@@ -66,7 +66,7 @@ func (this *DNSProviderDAO) FindEnabledDNSProvider(tx *dbs.Tx, id int64) (*DNSPr
return result.(*DNSProvider), err
}
// 创建服务商
// CreateDNSProvider 创建服务商
func (this *DNSProviderDAO) CreateDNSProvider(tx *dbs.Tx, adminId int64, userId int64, providerType string, name string, apiParamsJSON []byte) (int64, error) {
op := NewDNSProviderOperator()
op.AdminId = adminId
@@ -84,7 +84,7 @@ func (this *DNSProviderDAO) CreateDNSProvider(tx *dbs.Tx, adminId int64, userId
return types.Int64(op.Id), nil
}
// 修改服务商
// UpdateDNSProvider 修改服务商
func (this *DNSProviderDAO) UpdateDNSProvider(tx *dbs.Tx, dnsProviderId int64, name string, apiParamsJSON []byte) error {
if dnsProviderId <= 0 {
return errors.New("invalid dnsProviderId")
@@ -106,16 +106,42 @@ func (this *DNSProviderDAO) UpdateDNSProvider(tx *dbs.Tx, dnsProviderId int64, n
return nil
}
// 计算服务商数量
func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64) (int64, error) {
return dbutils.NewQuery(tx, this, adminId, userId).
State(DNSProviderStateEnabled).
// CountAllEnabledDNSProviders 计算服务商数量
func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string, providerType string) (int64, error) {
var query = dbutils.NewQuery(tx, this, adminId, userId)
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(domain) > 0 {
query.Where("id IN (SELECT providerId FROM " + SharedDNSDomainDAO.Table + " WHERE state=1 AND name=:domain)")
query.Param("domain", domain)
}
if len(providerType) > 0 {
query.Attr("type", providerType)
}
return query.State(DNSProviderStateEnabled).
Count()
}
// 列出单页服务商
func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, offset int64, size int64) (result []*DNSProvider, err error) {
_, err = dbutils.NewQuery(tx, this, adminId, userId).
// ListEnabledDNSProviders 列出单页服务商
func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string, providerType string, offset int64, size int64) (result []*DNSProvider, err error) {
var query = dbutils.NewQuery(tx, this, adminId, userId)
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(domain) > 0 {
query.Where("id IN (SELECT providerId FROM " + SharedDNSDomainDAO.Table + " WHERE state=1 AND name=:domain)")
query.Param("domain", domain)
}
if len(providerType) > 0 {
query.Attr("type", providerType)
}
_, err = query.
State(DNSProviderStateEnabled).
Offset(offset).
Limit(size).
@@ -125,7 +151,7 @@ func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, u
return
}
// 列出所有服务商
// FindAllEnabledDNSProviders 列出所有服务商
func (this *DNSProviderDAO) FindAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64) (result []*DNSProvider, err error) {
_, err = dbutils.NewQuery(tx, this, adminId, userId).
State(DNSProviderStateEnabled).
@@ -135,7 +161,7 @@ func (this *DNSProviderDAO) FindAllEnabledDNSProviders(tx *dbs.Tx, adminId int64
return
}
// 查询某个类型下的所有服务商
// FindAllEnabledDNSProvidersWithType 查询某个类型下的所有服务商
func (this *DNSProviderDAO) FindAllEnabledDNSProvidersWithType(tx *dbs.Tx, providerType string) (result []*DNSProvider, err error) {
_, err = this.Query(tx).
State(DNSProviderStateEnabled).
@@ -146,7 +172,7 @@ func (this *DNSProviderDAO) FindAllEnabledDNSProvidersWithType(tx *dbs.Tx, provi
return
}
// 更新数据更新时间
// UpdateProviderDataUpdatedTime 更新数据更新时间
func (this *DNSProviderDAO) UpdateProviderDataUpdatedTime(tx *dbs.Tx, providerId int64) error {
_, err := this.Query(tx).
Pk(providerId).

View File

@@ -1,16 +1,18 @@
package dns
// DNS服务商
import "github.com/iwind/TeaGo/dbs"
// DNSProvider DNS服务商
type DNSProvider struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
Type string `field:"type"` // 供应商类型
ApiParams string `field:"apiParams"` // API参数
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
DataUpdatedAt uint64 `field:"dataUpdatedAt"` // 数据同步时间
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
Type string `field:"type"` // 供应商类型
ApiParams dbs.JSON `field:"apiParams"` // API参数
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
DataUpdatedAt uint64 `field:"dataUpdatedAt"` // 数据同步时间
}
type DNSProviderOperator struct {

View File

@@ -5,12 +5,12 @@ import (
"github.com/iwind/TeaGo/maps"
)
// 获取API参数
// DecodeAPIParams 获取API参数
func (this *DNSProvider) DecodeAPIParams() (maps.Map, error) {
if len(this.ApiParams) == 0 || this.ApiParams == "null" {
if len(this.ApiParams) == 0 {
return maps.Map{}, nil
}
result := maps.Map{}
err := json.Unmarshal([]byte(this.ApiParams), &result)
err := json.Unmarshal(this.ApiParams, &result)
return result, err
}

View File

@@ -12,10 +12,11 @@ import (
type DNSTaskType = string
const (
DNSTaskTypeClusterChange DNSTaskType = "clusterChange"
DNSTaskTypeNodeChange DNSTaskType = "nodeChange"
DNSTaskTypeServerChange DNSTaskType = "serverChange"
DNSTaskTypeDomainChange DNSTaskType = "domainChange"
DNSTaskTypeClusterChange DNSTaskType = "clusterChange"
DNSTaskTypeClusterRemoveDomain DNSTaskType = "clusterRemoveDomain" // 从集群中移除域名
DNSTaskTypeNodeChange DNSTaskType = "nodeChange"
DNSTaskTypeServerChange DNSTaskType = "serverChange"
DNSTaskTypeDomainChange DNSTaskType = "domainChange"
)
type DNSTaskDAO dbs.DAO
@@ -40,20 +41,21 @@ func init() {
}
// CreateDNSTask 生成任务
func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int64, nodeId int64, domainId int64, taskType string) error {
func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int64, nodeId int64, domainId int64, recordName string, taskType string) error {
if clusterId <= 0 && serverId <= 0 && nodeId <= 0 && domainId <= 0 {
return nil
}
err := this.Query(tx).InsertOrUpdateQuickly(maps.Map{
"clusterId": clusterId,
"serverId": serverId,
"nodeId": nodeId,
"domainId": domainId,
"updatedAt": time.Now().Unix(),
"type": taskType,
"isDone": false,
"isOk": false,
"error": "",
"clusterId": clusterId,
"serverId": serverId,
"nodeId": nodeId,
"domainId": domainId,
"recordName": recordName,
"updatedAt": time.Now().Unix(),
"type": taskType,
"isDone": false,
"isOk": false,
"error": "",
}, maps.Map{
"updatedAt": time.Now().Unix(),
"isDone": false,
@@ -63,24 +65,29 @@ func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int6
return err
}
// CreateClusterTask 生成集群任务
// CreateClusterTask 生成集群变更任务
func (this *DNSTaskDAO) CreateClusterTask(tx *dbs.Tx, clusterId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, clusterId, 0, 0, 0, taskType)
return this.CreateDNSTask(tx, clusterId, 0, 0, 0, "", taskType)
}
// CreateClusterRemoveTask 生成集群删除域名任务
func (this *DNSTaskDAO) CreateClusterRemoveTask(tx *dbs.Tx, clusterId int64, domainId int64, recordName string) error {
return this.CreateDNSTask(tx, clusterId, 0, 0, domainId, recordName, DNSTaskTypeClusterRemoveDomain)
}
// CreateNodeTask 生成节点任务
func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, nodeId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, 0, nodeId, 0, taskType)
return this.CreateDNSTask(tx, 0, 0, nodeId, 0, "", taskType)
}
// CreateServerTask 生成服务任务
func (this *DNSTaskDAO) CreateServerTask(tx *dbs.Tx, serverId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, serverId, 0, 0, taskType)
func (this *DNSTaskDAO) CreateServerTask(tx *dbs.Tx, clusterId int64, serverId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, clusterId, serverId, 0, 0, "", taskType)
}
// CreateDomainTask 生成域名更新任务
func (this *DNSTaskDAO) CreateDomainTask(tx *dbs.Tx, domainId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, 0, 0, domainId, taskType)
return this.CreateDNSTask(tx, 0, 0, 0, domainId, "", taskType)
}
// FindAllDoingTasks 查找所有正在执行的任务
@@ -94,9 +101,14 @@ func (this *DNSTaskDAO) FindAllDoingTasks(tx *dbs.Tx) (result []*DNSTask, err er
}
// FindAllDoingOrErrorTasks 查找正在执行的和错误的任务
func (this *DNSTaskDAO) FindAllDoingOrErrorTasks(tx *dbs.Tx) (result []*DNSTask, err error) {
_, err = this.Query(tx).
func (this *DNSTaskDAO) FindAllDoingOrErrorTasks(tx *dbs.Tx, nodeClusterId int64) (result []*DNSTask, err error) {
var query = this.Query(tx)
if nodeClusterId > 0 {
query.Attr("clusterId", nodeClusterId)
}
_, err = query.
Where("(isDone=0 OR (isDone=1 AND isOk=0))").
Asc("updatedAt").
AscPk().
Slice(&result).
FindAll()

View File

@@ -9,7 +9,7 @@ import (
func TestDNSTaskDAO_CreateDNSTask(t *testing.T) {
dbs.NotifyReady()
err := SharedDNSTaskDAO.CreateDNSTask(nil, 1, 2, 3, "taskType")
err := SharedDNSTaskDAO.CreateDNSTask(nil, 1, 2, 3, 0, "taskType")
if err != nil {
t.Fatal(err)
}

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