From d5e5061266d91d1ae4712fbd790e9329c84c0de8 Mon Sep 17 00:00:00 2001 From: "yuan.bo" Date: Wed, 21 Feb 2024 13:42:01 +0800 Subject: [PATCH] Support OCIv1 image spec --- README.md | 146 +++++++----------------------------------- cmd/build.sh | 13 ++++ cmd/main.go | 152 ++++++++++++++++++++++++++++++++++---------- core/compression.go | 10 +++ core/context.go | 54 ++++++++-------- core/helper.go | 6 ++ core/i18n.go | 2 +- core/manifest.go | 68 ++++++++++++++++---- core/offline.go | 20 ++++-- core/online.go | 34 +++++++++- core/source.go | 19 ++++-- go.mod | 60 +++++++++++++++-- go.sum | 46 +++++++++++--- 13 files changed, 407 insertions(+), 223 deletions(-) create mode 100644 cmd/build.sh diff --git a/README.md b/README.md index d9a35f2..6fe5499 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,19 @@ # 容器镜像传输工具 -## 2022.7 揽月版更新说明 +## 说明 -#### 上传镜像改名功能 -在输入镜像列表的地方通过 `->` 来定义新的名称,比如: -``` -10.45.80.1/public/alpine:3.5 -> alpine-2:3.5-2 -10.45.80.1/public/alpine:3.5 -> public-2/alpine-2:3.5-2 -``` -支持上面两种格式。 -在离线传输模式下,如果需要改名,在上传时选择编辑镜像列表,然后按照上面的样式修改镜像名称,然后再次点击上传按钮即可。 -对于命令行模式在传入的镜像列表文件中按照这个格式输入即可。 - -#### 下载支持Docker兼容的格式 -在配置文件中将singlefile和dockerfile两个配置项设置为true后,下载的tar包即可以直接使用docker或者ctr命令导入到本地。 -比直接使用Docker命令的优势有: -- 无须安装docker,在windows(界面)和linux都可以使用 -- 中间无多次加解压的过程,速度更快 -- 可以通过缓存加速下载 +1. 遵从 Apache 2.0 协议 +2. fork 自 wct-devops/image-transmit -#### 下载导入containerd主机 -可以指定-dst=ctr来实现导入到containerd的本地镜像库 +## 修改说明 -## 背景 -各团队在给项目发布版本的时候,都会涉及到将容器镜像从公司的官方仓库复制到项目的仓库上,一般我们需要让客户找一台Linux作为跳板机,安装好Docker工具,然后docker pull/docker tag/docker push的方式来进行,这台Linux主机需要有单独申请访问外网的权限。这个申请流程比较麻烦,而更常见的是客户会提供一个Windows跳板机。在Windows上安装Docker比较麻烦,而且由于Docker镜像占用空间较多,还需要定时的清理或者维护跳板机,否则容易出现各种异常。 +1. 支持 OCI 镜像 +2. 配置文件中增加平台和部分自定义选项 -目前我们版本升级都通过界面实现了一键操作的傻瓜方式,而传输镜像因为涉及网络权限和跳板机,很多还是手工操作。因此开发了这个镜像传输工具方便大家使用。 +## 配置文件 -工具的优势有: -- 绿色版,无需安装任何三方工具,直接下载一个EXE程序(大小仅5M)到主机,参考样例写一个cfg.yaml配置文件即可运行 -- 最小化占用CPU和存储资源,对机器要求比较低,传输效率高 -- 支持Windows下的界面操作,不懂Docker命令的小白也可以搞定 -- 并发数和重试数支持可配置,可以根据网络条件调整,充分利用带宽 -- 支持直传模式,可以将镜像直接从A仓库传到B仓库,中间不落地 -- 支持Windows界面/Windows命令行/Linux命令行的使用方式(龙舟版新增) -- 支持离线模式,用下载模式将镜像压缩为压缩包,然后上传模式将文件上传到目标仓库(龙舟版新增) -- 离线模式支持快速压缩,比docker save|gzip的方式更快,压缩比更高(龙舟版新增) -- 离线模式支持增量发布,可以将上次发布的镜像层跳过,从而大幅降低压缩包大小(龙舟版新增) -- 上传模式支持直接将镜像导入到本地的Docker中,而非一个仓库(龙舟版新增) -- 支持本地缓存,可以减少数据传输(龙舟版新增) -- 支持守护模式,能够自动拉取新增镜像并传输到目标库(东风版新增) -- 支持在任务结束时发送钉钉通知(东风版新增) +在工具的目录下,放置一个配置文件cfg.yaml(或者放在 data 目录下, 即 `data/cfg.yaml`),其内容参考如下: -## 配置文件 -在工具的目录下,放置一个配置文件cfg.yaml(或者data目录下),其内容参考如下: ```yaml source: # 源仓库信息配置,可以支持多个 - registry: "http://10.45.80.1" @@ -56,6 +26,7 @@ target: # 目标仓库信息配置,可以支持多个 password: #repository: # 可选配置,是否修改镜像名称,假如填写值yyyy,则会将源仓库的10.45.80.1/xxxx/image:tag统一改成10.45.46.109/yyyy/image:tag #name: #可选配置,指定名称 +platform: linux/amd64 # 保存文件的时候, 只会保存指定平台的镜像 #maxconn: 5 # 可选配置,最大并发数,默认5 #retries: 2 # 可选配置,最大重试次数,默认2 #singlefile: false #可选配置,是否生成单一文件,默认关 @@ -66,52 +37,36 @@ target: # 目标仓库信息配置,可以支持多个 # pathname: cache # 缓存目录 # keepdays: 7 # 缓存最长保留时间,默认7 # keepsize: 10 # 缓存目录最大使用量,单位G,默认10 -#outprefix: # 可选配置,用于指定生成的压缩文件的前缀,也可以在执行命令时使用-out参数来指定 +#outprefix: # 可选配置,用于指定生成的压缩文件的前缀,也可以在执行命令时使用-out参数来指定, 最终前缀为 ___.tar #interval: 60 # 可选配置,守护模式下定时扫描的时间间隔,默认60 #dingtalk: # 可选配置,用于发送钉钉通知,支持多个 #- token : # 用于配置钉钉令牌 # secret: # 用于配置钉钉密钥 ``` -## 界面截图 -![image](https://user-images.githubusercontent.com/11539396/121972464-c3073d80-cdad-11eb-8067-ac1d26cba791.png) ## 使用说明 -#### 直传模式 -假设客户提供的跳板机可以连接公网上的仓库,也可以同时连接内网的仓库,则优先推荐使用这个模式。直传模式逐个从源仓库拉取镜像,然后直接推动到目标仓库,文件不落地,网络带宽有保障即可,同时只会传输目标仓库上不存在的分层,因此效率是最高的,如果直通网络有保障,这个是最简单的模式。 -1. 选择源仓库和目标仓库,按需调整并发度和异常重试次数 -2. 在左侧输入框输入需要传输的镜像列表,会自动替换镜像列表URL地址信息,统一使用选择的源仓库的URL地址,无需手工去替换 -3. 可以点击校验用来验证一下源镜像列表和目标镜像列表信息匹配是否正确 -4. 点击【直传】按钮,启动镜像复制,界面会自动刷新日志和实时统计 -5. 用户可以点击【停止】中断镜像的传输 -> 直传模式下是否启用本地缓存的说明 -> 启用本地缓存后,向目标传输的数据文件都会在缓存目录中保存一份,下次传输时会优先使用缓存的包。有两种场景使用本地缓存可以有所帮助。 -> 1. 每次需要向多个目标仓库同步相同的镜像,使用本地缓存,一些包只需要从源仓库下载一次 -> 2. 跳板机网络不稳定,使用缓存可以尽量减少一次重复的包传输 +1. 这里主要说明离线模式和增量模式, 其他模式(直传模式, 守护模式)参考源仓库说明 +2. 主要说明命令行模式 -#### 守护模式 -守护模式是直传模式的进一步改进,此模式会定时去捞取源库镜像列表的所有tag,并且将新的tag自动传输到目标仓库,这样就可以实现镜像构建成功后自动传输,并且会发送钉钉通知。现场可以在某个跳板机上部署一个常驻应用,就可以定时自动将所内的镜像传输到现场的仓库中。 +#### 离线场景 -> 判断新镜像的方法的说明 -> 目前判断新镜像的方式是根据tag来判断,比如 v3.10 > v3.5, C_20210819100000 > C_20210818100000, 只会自动传输大于等于指定的tag的镜像 +一些客户不允许提供可以开放公网访问的跳板机,因此不能使用直传模式,需要使用离线模式,即先在研发中心将镜像打包成文件,然后转移到跳板机,然后从跳板机上将文件上传到目标仓库。整个过程需要使用到【Save Mode】和【Upload Mode】两个选项 + +1. 启动镜像下载,程序会在程序所在的目录下按照【日期】创建一个新文件夹,并且根据时间戳创建对应的数据文件和描述文件,示例如下: -#### 离线场景 -一些客户不允许提供可以开放公网访问的跳板机,因此不能使用直传模式,需要使用离线模式,即先在研发中心将镜像打包成文件,然后转移到跳板机,然后从跳板机上将文件上传到目标仓库。整个过程需要使用到【下载】和【上传】两个按钮。 -1. 选择源仓库,按需调整并发度和异常重试次数,以及增量或者全量模式,是否使用单一文件模式等 -2. 在左侧输入框输入需要传输的镜像列表,会自动替换镜像列表URL地址信息,统一使用选择的源仓库的URL地址,无需手工去替换 -3. 可以点击校验用来验证一下源镜像列表信息是否正确 -4. 点击【下载】按钮,启动镜像下载,程序会在程序所在的目录下按照【日期】创建一个新文件夹,并且根据时间戳创建对应的数据文件和描述文件,示例如下: ``` ├─20210612 │ img_full_202106122344_0.tar │ img_full_202106122344_1.tar │ img_full_202106122344_meta.yaml ``` + 用户也可以通过outprefix参数来指定一个自己的前缀。 -5. 用户将生成的几个文件全部传输给客户,放置到跳板机的某个目录下。 -6. 登录跳板机,选择目标仓库,按需调整并发度和异常重试次数,点击【上传】按钮,在弹出文件框中选择上一步中传输过去的描述文件,确认信息后,开始上传 -7. 上传时会根据描述文件,对数据文件进行个数/大小的校验,如果报错,请检查数据文件是否传输完整。 +1. 用户将生成的几个文件全部传输给客户,放置到跳板机的某个目录下。 +2. 登录跳板机,选择目标仓库,按需调整并发度和异常重试次数,点击【上传】按钮,在弹出文件框中选择上一步中传输过去的描述文件,确认信息后,开始上传 +3. 上传时会根据描述文件,对数据文件进行个数/大小的校验,如果报错,请检查数据文件是否传输完整。 > 关于增量和全量的说明 > 在离线传输模式下,由于无法同时连接源仓库和目标仓库,因此无法判断需要传输哪些镜像分层,全量模式就是将所有的分层都打包到数据文件中,是最安全的方式,但是这种模式下数据文件会比较大。 @@ -138,10 +93,9 @@ zoms@172.16.85.48[/home/zoms]$ image-transmit Invalid args, please refer the help Image Transmit-DragonBoat-WhaleCloud DevOps Team ./image-transmit [OPTIONS] -Examples: +Examples: Save mode: ./image-transmit -src=nj -lst=img.lst Increment save mode: ./image-transmit -src=nj -lst=img.lst -inc=img_full_202106122344_meta.yaml - Transmit mode: ./image-transmit -src=nj -lst=img.lst -dst=gz Upload mode: ./image-transmit -dst=gz -img=img_full_202106122344_meta.yaml More description please refer to github.com/wct-devops/image-transmit -dst string @@ -182,68 +136,14 @@ http://10.45.80.1/public/alpine:3.12.2 [2021-06-14 21:42:17] Create meta file: data/20210614/img_full_202106142142_meta.yaml ``` -``` -C:\Users\WangYuMu\go\src\github.com\wct-devops\image-transmit\cmd>image-transmit-cmd.exe -命令行参数不正确,请查看帮助 -云雀-镜像传输工具-龙舟版-浩鲸DevOps团队 -image-transmit-cmd.exe [选项] -例子: - 下载模式: image-transmit-cmd.exe -src=nj -lst=img.lst - 增量下载模式: image-transmit-cmd.exe -src=nj -lst=img.lst -inc=img_full_202106122344_meta.yaml - 直传模式: image-transmit-cmd.exe -src=nj -lst=img.lst -dst=gz - 上传模式: image-transmit-cmd.exe -dst=gz -img=img_full_202106122344_meta.yaml -更多帮助可以访问github.com/wct-devops/image-transmit - -dst string - 目标仓库名称, 默认为第一个 - -img string - 需要上传的镜像规格文件(*meta.yaml) - -inc string - 指定增量模式下参考的镜像规格文件(*meta.yaml) - -lst string - 镜像列表文件,一行一个 - -src string - 源仓库名称, 默认为配置文件中的第一个仓库 - -out string - 输出压缩文件的前缀 -C:\Users\WangYuMu\go\src\github.com\wct-devops\image-transmit\cmd>(echo 10.45.80.21/public/alpine:3.11 && echo 10.45.80.21/public/alpine:3.12.1 ) | image-transmit-cmd.exe --src=nj -[2021-06-14 21:52:28] 读取2个镜像 -[2021-06-14 21:52:28] 生成数据文件: img_full_202106142152_0.tar -[2021-06-14 21:52:28] 生成数据文件: img_full_202106142152_1.tar -[2021-06-14 21:52:31] 完成10.45.80.1/public/alpine:3.11下载任务创建 -[2021-06-14 21:52:35] 完成10.45.80.1/public/alpine:3.12.1下载任务创建 -[2021-06-14 21:52:35] 开始处理任务,总计2个,请稍后... -无效:0 总计:2 成功:2 失败:0 正在处理:0 下载速度:0.0B/s 上传速度:0.0B/s 总下载:0.0B 总上传:0.0B 耗时: -[2021-06-14 21:52:35] 从 10.45.80.1/public/alpine:3.11 下载manifest完成 -[2021-06-14 21:52:35] 从 10.45.80.1/public/alpine:3.12.1 下载manifest完成 -[2021-06-14 21:52:35] 下载blob sha256:188c0c94c7c5(2.7MB) 自 10.45.80.1/public/alpine:3.12.1 完成 -[2021-06-14 21:52:35] 下载blob sha256:cbdbe7a5bc2a(2.7MB) 自 10.45.80.1/public/alpine:3.11 完成 -无效:0 总计:2 成功:0 失败:0 正在处理:2 下载速度:0.0B/s 上传速度:0.0B/s 总下载:0.0B 总上传:0.0B 耗时: -无效:0 总计:2 成功:0 失败:0 正在处理:2 下载速度:0.0B/s 上传速度:0.0B/s 总下载:0.0B 总上传:0.0B 耗时: -无效:0 总计:2 成功:0 失败:0 正在处理:2 下载速度:0.0B/s 上传速度:0.0B/s 总下载:0.0B 总上传:0.0B 耗时: -[2021-06-14 21:52:39] 下载blob sha256:f70734b6a266(1.5KB) 自 10.45.80.1/public/alpine:3.11 完成 -无效:0 总计:2 成功:1 失败:0 正在处理:1 下载速度:549.8KB/s 上传速度:0.0B/s 总下载:2.7MB 总上传:0.0B 耗时: -[2021-06-14 21:52:39] 下载blob sha256:d6e46aa2470d(1.5KB) 自 10.45.80.1/public/alpine:3.12.1 完成 -[2021-06-14 21:52:39] 任务处理结束,总计2任务,0任务失败 -[2021-06-14 21:52:39] 生成压缩规格文件: data\20210614\img_full_202106142152_meta.yaml -``` - > 如何将镜像直接导入到本机的Docker? > 通常我们上传的压缩包是直接导入到镜像仓库中,但是有些场景,比如一键部署时,需要将镜像直接导入到本地Docker来使用,这个时候可以直接指定参数-dst=docker就可以将目标设置为本地docker -> 关于不同压缩方式的说明 -> 目前支持两种方式tar和squashfs,两种模式的区别有: -> 1. tar全程不需要压缩和解压重新处理,因此打包和解包效率非常高,打包时间一般是squashfs的一半 -> 2. squashfs需要将容器镜像每一层的包都解开到本地,需要占用大量本地文件系统空间,但是由于squashfs文件系统支持重复文件识别等固实压缩优化,比tar模式节省30%左右大小 -> 3. 由于squashfs需要将每一层压缩包都解开到临时目录,并逐个扫描压缩,容器镜像中存在很多系统文件,因此对权限要求比较高,需要使用root账号或者sudo命令来执行,否则会报错 -> 4. 目前Golang并没有非常完善的squashfs解压缩和tar解压到文件系统的包,目前测试比较稳定的是在Linux下,使用Linux自带的mksquashfs、tar工具来进行解包和打包,因此windows下不建议使用squashfs,除非你能确认镜像包中只包含普通权限的文件,可以尝试。在windows下压缩需要下载squashfs.zip包并解压到同一目录下 -> 5. squashfs的temp目录大小需要足够存放整个镜像未压缩的文件 - > 建议使用缓存目录 > 如果使用一些固定的机器来给项目发布镜像,可以打开缓存,这样可以避免每次重复下载已有的镜像层,大大提高打包的效率 ## 版本下载说明 -请到[release](https://github.com/wct-devops/image-transmit/releases)页面下载 +请到 release 页面下载 - image-transmit : Linux命令行版 - image-transmit-cmd.zip : Windows命令行版 - image-transmit-gui.zip : Windows桌面版 @@ -256,7 +156,7 @@ C:\Users\WangYuMu\go\src\github.com\wct-devops\image-transmit\cmd>(echo 10.45.80 2. 在环境变量中指定lang参数 ## 致谢 -使用到的开源库: +使用到的开源库: github.com/AliyunContainerService/image-syncer github.com/lxn/walk github.com/klauspost/compress/zstd diff --git a/cmd/build.sh b/cmd/build.sh new file mode 100644 index 0000000..c3d77bb --- /dev/null +++ b/cmd/build.sh @@ -0,0 +1,13 @@ + export CGO_ENABLED=0 +export GOOS=linux +export GOARCH=arm64 + +go build -ldflags "-s -w" -o ../image-transmit.arm64 +#upx image-transmit.arm64 + +export CGO_ENABLED=0 +export GOOS=linux +export GOARCH=amd64 + +go build -ldflags "-s -w" -o ../image-transmit.amd64 +#upx image-transmit.amd64 diff --git a/cmd/main.go b/cmd/main.go index 4475ec1..68ccefc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,7 +6,9 @@ import ( "io/ioutil" "os" "path/filepath" + "reflect" "runtime" + "strconv" "strings" "time" @@ -26,8 +28,8 @@ var ( flConfLst *string flConfInc *string flConfImg *string - flConfOut *string flConfWat *bool + flOpt *string ) func main() { @@ -71,6 +73,14 @@ func main() { os.Exit(1) } + if CONF.Platform == "" { + CONF.Platform = "linux/amd64" + } + + if CONF.OutPrefix == "" { + CONF.OutPrefix = "img" + } + if CONF.MaxConn == 0 { CONF.MaxConn = runtime.NumCPU() } @@ -117,21 +127,22 @@ func main() { flConfLst = flag.String("lst", "", I18n.Sprintf("Image list file, one image each line")) flConfInc = flag.String("inc", "", I18n.Sprintf("The referred image meta file(*meta.yaml) in increment mode")) flConfImg = flag.String("img", "", I18n.Sprintf("Image meta file to upload(*meta.yaml)")) - flConfOut = flag.String("out", "", I18n.Sprintf("Output filename prefix")) flConfWat = flag.Bool("watch", false, I18n.Sprintf("Watch mode")) + flOpt = flag.String("opt", "", I18n.Sprintf("Options: override some options in cfg.yaml")) flag.Usage = func() { - fmt.Println(I18n.Sprintf("Image Transmit-Ghang'e-WhaleCloud DevOps Team")) - fmt.Print(I18n.Sprintf("%s [OPTIONS]\n", os.Args[0])) - fmt.Print(I18n.Sprintf("Examples: \n")) - fmt.Print(I18n.Sprintf(" Save mode: %s -src=nj -lst=img.lst\n", os.Args[0])) - fmt.Print(I18n.Sprintf(" Increment save mode: %s -src=nj -lst=img.lst -inc=img_full_202106122344_meta.yaml\n", os.Args[0])) - fmt.Print(I18n.Sprintf(" Transmit mode: %s -src=nj -lst=img.lst -dst=gz\n", os.Args[0])) - fmt.Print(I18n.Sprintf(" Watch mode: %s -src=nj -lst=img.lst -dst=gz --watch\n", os.Args[0])) - fmt.Print(I18n.Sprintf(" Upload mode: %s -dst=gz -img=img_full_202106122344_meta.yaml [-lst=img.lst]\n", os.Args[0])) - fmt.Print(I18n.Sprintf("More description please refer to github.com/wct-devops/image-transmit\n")) + fmt.Println(I18n.Sprintf("\n Easy Image Transmit Tool")) + fmt.Print(I18n.Sprintf("[Usage]: \n")) + fmt.Print(I18n.Sprintf(" Save mode(save tarball from registry): %s -src=nj -lst=img.lst\n", os.Args[0])) + fmt.Print(I18n.Sprintf(" Increment save mode: %s -src=nj -lst=img.lst -inc=img_full_202106122344_meta.yaml\n", os.Args[0])) + fmt.Print(I18n.Sprintf(" Load mode(to docker or ctr): %s -dst=docker -img=img_full_202106122344_meta.yaml [-lst=img.lst]\n", os.Args[0])) + fmt.Print(I18n.Sprintf(" Load mode(to registry): %s -dst=gz -img=img_full_202106122344_meta.yaml [-lst=img.lst]\n", os.Args[0])) + fmt.Print(I18n.Sprintf(" Transmit mode: %s -src=nj -lst=img.lst -dst=gz\n", os.Args[0])) + fmt.Print(I18n.Sprintf(" Watch mode: %s -src=nj -lst=img.lst -dst=gz --watch\n", os.Args[0])) + fmt.Print(I18n.Sprintf("[Options]:\n%s\n", os.Args[0])) flag.PrintDefaults() } + flag.Parse() if len(*flConfSrc) > 0 { @@ -166,10 +177,6 @@ func main() { } } - if len(*flConfOut) > 0 { - CONF.OutPrefix = *flConfOut - } - var lc *LocalCache if CONF.Cache.Pathname != "" { keepDays := 7 @@ -188,11 +195,49 @@ func main() { ctx := NewTaskContext(log, lc, lt) ctx.Reset() + + // override most of the config from command line + opts := strings.Split(*flOpt, ",") + for _, opt := range opts { + keyValue := strings.Split(opt, "=") + if len(keyValue) != 2 { + continue + } + + key := keyValue[0] + value := keyValue[1] + typeOfYamlCfg := reflect.TypeOf(YamlCfg{}) + immutable := reflect.ValueOf(CONF) + for i := 0; i < typeOfYamlCfg.NumField(); i++ { + field := typeOfYamlCfg.Field(i) + tag := reflect.StructTag(field.Tag) + yamlTag := tag.Get("yaml") + // fmt.Printf("field' name is %s, yaml tag is %s\n", field.Name, strings.Split(yamlTag, ",")) + _, found := findStrInSlice(strings.Split(yamlTag, ","), key) + if found { + fieldValue := immutable.Elem().FieldByName(field.Name) + switch field.Type.Kind() { + case reflect.String: + fieldValue.SetString(value) + case reflect.Int: + fieldValue.SetInt(int64(atoi(value))) + case reflect.Bool: + fieldValue.SetBool(value == "true") + } + } + } + } + + if len(CONF.Platform) > 0 { + ctx.Platform = CONF.Platform + } + if len(CONF.DingTalk) > 0 { ctx.Notify = NewDingTalkWapper(CONF.DingTalk) } if len(*flConfSrc) > 0 && len(*flConfDst) > 0 { + // transmit mode: src -> dst err := readImgList(ctx) if err != nil { os.Exit(1) @@ -206,10 +251,22 @@ func main() { EndAction(ctx) } } else if len(*flConfImg) > 0 && len(*flConfDst) > 0 { + // load mode + // ignore local filesystem blob validate in dockerSaver + if strings.Contains(*flConfImg, "_incr_") { + ctx.DockerSaverBlobValidate = true + } else { + ctx.DockerSaverBlobValidate = false + } + err := readImgList(ctx) + if err != nil { + os.Exit(1) + } BeginAction(ctx) upload(ctx) EndAction(ctx) } else if len(*flConfSrc) > 0 { + // save mode, save image listed in image list err := readImgList(ctx) if err != nil { os.Exit(1) @@ -232,26 +289,53 @@ func readImgList(ctx *TaskContext) error { getInputList(string(b)) } else { var s string - for { - var l string - _, err := fmt.Scanln(&l) - if len(l) < 1 || err != nil { - break + + done := make(chan bool) + go func() { + for { + var l string + _, err := fmt.Scanln(&l) + if len(l) < 1 || err != nil { + break + } + s = s + "\n" + l } - s = s + "\n" + l + done <- true + }() + + select { + case <-done: + if len(s) > 0 { + if len(s) > 0 { + getInputList(s) + } + } + case <-time.After(100 * time.Microsecond): } - if len(s) > 0 { - getInputList(s) - } - } - if len(imgList) < 1 { - return ctx.Errorf(I18n.Sprintf("Empty image list")) } + ctx.Info(I18n.Sprintf("Get %v images", len(imgList))) return nil } +func atoi(s string) int { + i, err := strconv.Atoi(s) + if err != nil { + return 0 + } + return i +} + +func findStrInSlice(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} + func getInputList(input string) { input = strings.ReplaceAll(input, "\t", "") if CheckInvalidChar(strings.ReplaceAll(strings.ReplaceAll(input, "\r", ""), "\n", "")) { @@ -389,7 +473,6 @@ func download(ctx *TaskContext) error { CONF.MaxConn = len(imgList) } c, _ := NewClient(CONF.MaxConn, CONF.Retries, ctx) - var prefixPathname string var prefixFilename string if len(CONF.OutPrefix) > 0 { @@ -397,6 +480,8 @@ func download(ctx *TaskContext) error { if prefixPathIdx > 0 { prefixPathname = CONF.OutPrefix[0:prefixPathIdx] prefixFilename = CONF.OutPrefix[prefixPathIdx+1:] + } else { + prefixFilename = CONF.OutPrefix } } @@ -408,13 +493,14 @@ func download(ctx *TaskContext) error { var workName string if len(*flConfInc) > 1 { - workName = time.Now().Format("img_incr_200601021504") + workName = time.Now().Format("incr_200601021504") } else { - workName = time.Now().Format("img_full_200601021504") + workName = time.Now().Format("full_200601021504") } + arch := strings.ToLower(strings.Split(CONF.Platform, "/")[1]) if len(prefixFilename) > 0 { - workName = prefixFilename + "_" + workName + workName = prefixFilename + "_" + arch + "_" + workName } ctx.CreateCompressionMetadata(CONF.Compressor) @@ -506,9 +592,7 @@ func upload(ctx *TaskContext) error { } ctx.Info(I18n.Sprintf("The img file contains %v images:\n%s", len(cm.Manifests), strings.Join(srcImgUrlList, "\n"))) - if len(*flConfLst) > 0 { - readImgList(ctx) - } else { + if len(imgList) == 0 { getInputList(strings.Join(srcImgUrlList, "\n")) // if no input list then take the original } diff --git a/core/compression.go b/core/compression.go index 06b9877..05568a9 100644 --- a/core/compression.go +++ b/core/compression.go @@ -17,6 +17,8 @@ import ( log "github.com/cihub/seelog" "github.com/containers/image/v5/types" + + // dockerClient "github.com/docker/docker/client" "github.com/klauspost/compress/zstd" "github.com/pierrec/lz4" "github.com/ulikunitz/xz" @@ -389,6 +391,7 @@ type DockerSaver struct { wait *sync.Mutex repositories map[string]map[string]string manifests [](map[string]interface{}) + // dockerCli *dockerClient.Client } func NewDockerSaver(ctx *TaskContext, target string) *DockerSaver { @@ -401,6 +404,10 @@ func NewDockerSaver(ctx *TaskContext, target string) *DockerSaver { if target == "docker" || target == "ctr" { var cmd *exec.Cmd if target == "docker" { + // dockerCli, err := dockerClient.NewClientWithOpts(client.FromEnv) + // if err != nil { + // panic(err) + // } cmd = exec.Command("docker", "load") } else { cmd = exec.Command("ctr", "image", "import", "/dev/stdin") @@ -422,7 +429,9 @@ func NewDockerSaver(ctx *TaskContext, target string) *DockerSaver { log.Error(err) } }() + tarWriter = tar.NewWriter(cmdWriter) + } else { cmdWriter, err = os.Create(target) if err != nil { @@ -438,6 +447,7 @@ func NewDockerSaver(ctx *TaskContext, target string) *DockerSaver { ctx: ctx, wait: wait, repositories: repositories, + // dockerCli: dockerCli, } } diff --git a/core/context.go b/core/context.go index b33d9b6..998caa9 100644 --- a/core/context.go +++ b/core/context.go @@ -9,30 +9,32 @@ import ( ) type TaskContext struct { - log CtxLogger - byteDown int64 - byteUp int64 - timeDown time.Duration - timeUp time.Duration - secStart int64 - secEnd int64 - parallelism int - failedTask int - invalidTask int - totalTask int - waitTask int - statChan chan int - Cache *LocalCache - Temp *LocalTemp - History *History - TarWriter []*ImageCompressedTarWriter - SingleWriter *SingleTarWriter - CompMeta *CompressionMetadata - SquashfsTar *SquashfsTar - Context context.Context - CancelFunc context.CancelFunc - Notify Notify - DockerTarget string + log CtxLogger + byteDown int64 + byteUp int64 + timeDown time.Duration + timeUp time.Duration + secStart int64 + secEnd int64 + parallelism int + failedTask int + invalidTask int + totalTask int + waitTask int + statChan chan int + Cache *LocalCache + Temp *LocalTemp + History *History + TarWriter []*ImageCompressedTarWriter + SingleWriter *SingleTarWriter + CompMeta *CompressionMetadata + SquashfsTar *SquashfsTar + Context context.Context + CancelFunc context.CancelFunc + Notify Notify + DockerTarget string + Platform string + DockerSaverBlobValidate bool } func NewTaskContext(log CtxLogger, lc *LocalCache, lt *LocalTemp) *TaskContext { @@ -196,7 +198,7 @@ func (t *TaskContext) GetStatus() string { totalSec = time.Now().Unix() - t.secStart } } - return fmt.Sprint(I18n.Sprintf("Invalid:%v All:%v OK:%v Err:%v Doing:%v Speed:v%s/s ^%s/s Total:v%s ^%s Time:%s", - t.invalidTask, t.totalTask, t.totalTask-t.waitTask-t.failedTask-t.parallelism, t.failedTask, + return fmt.Sprint(I18n.Sprintf("All:%v Invalid:%v OK:%v Err:%v Doing:%v Speed:↓%s/s ↑%s/s Total:↓%s ↑%s Time:%s", + t.totalTask, t.invalidTask, t.totalTask-t.waitTask-t.failedTask-t.parallelism, t.failedTask, t.parallelism, FormatByteSize(int64(float64(t.byteDown)/(float64(t.timeDown)/float64(time.Second)))), FormatByteSize(int64(float64(t.byteUp)/(float64(t.timeUp)/float64(time.Second)))), FormatByteSize(t.byteDown), FormatByteSize(t.byteUp), FormatSeconds(totalSec))) } diff --git a/core/helper.go b/core/helper.go index fb1c36e..9f3044d 100644 --- a/core/helper.go +++ b/core/helper.go @@ -25,6 +25,7 @@ type Repo struct { type YamlCfg struct { SrcRepos []Repo `yaml:"source,omitempty"` DstRepos []Repo `yaml:"target,omitempty"` + Platform string `yaml:"platform,omitempty"` MaxConn int `yaml:"maxconn,omitempty"` Retries int `yaml:"retries,omitempty"` SingleFile bool `yaml:"singlefile,omitempty"` @@ -71,6 +72,11 @@ func GenRepoUrl(srcReg string, dstReg string, dstRepo string, rawURL string) (sr src = strings.TrimSpace(rawSrcURL) } + if !strings.HasPrefix(srcReg, "https://hub.docker.com") { + srcReg = strings.TrimPrefix( + strings.TrimPrefix(strings.TrimSpace(srcReg), "http://"), "https://") + } + rawSrcURL = strings.TrimPrefix( strings.TrimPrefix( diff --git a/core/i18n.go b/core/i18n.go index 56a8f2b..7b40b1c 100644 --- a/core/i18n.go +++ b/core/i18n.go @@ -123,7 +123,7 @@ func init() { message.SetString(language.Chinese, "Generated a download task for %s", "完成 %s下载任务创建") message.SetString(language.Chinese, "Generated a upload task for %s", "完成 %s 上传任务创建") message.SetString(language.Chinese, "Create data file: %s", "生成数据文件: %s") - message.SetString(language.Chinese, "Invalid:%v All:%v OK:%v Err:%v Doing:%v Speed:v%s/s ^%s/s Total:v%s ^%s Time:%s", "无效:%v 总计:%v 成功:%v 失败:%v 处理中:%v 速度:下%s/s 上%s/s 总计:下%s 上%s 耗时:%s") + message.SetString(language.Chinese, "All:%v Invalid:%v OK:%v Err:%v Doing:%v Speed:v%s/s ^%s/s Total:v%s ^%s Time:%s", "总计:%v 无效:%v 成功:%v 失败:%v 处理中:%v 速度:下%s/s 上%s/s 总计:下%s 上%s 耗时:%s") message.SetString(language.Chinese, "Day", "天") message.SetString(language.Chinese, "Hou", "时") message.SetString(language.Chinese, "Min", "分") diff --git a/core/manifest.go b/core/manifest.go index 75a07b7..220d08b 100644 --- a/core/manifest.go +++ b/core/manifest.go @@ -3,51 +3,93 @@ package core import ( "fmt" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containers/image/v5/manifest" ) // ManifestHandler expends the ability of handling manifest list in schema2, but it's not finished yet // return the digest array of manifests in the manifest list if exist. -func ManifestHandler(m []byte, t string, i *ImageSource) ([]manifest.Manifest, error) { +func ManifestHandler(m []byte, t string, i *ImageSource) ([]manifest.Manifest, []byte, error) { var manifestInfoSlice []manifest.Manifest if t == manifest.DockerV2Schema2MediaType { manifestInfo, err := manifest.Schema2FromManifest(m) if err != nil { - return nil, err + return nil, nil, err } manifestInfoSlice = append(manifestInfoSlice, manifestInfo) - return manifestInfoSlice, nil + return manifestInfoSlice, m, nil } else if t == manifest.DockerV2Schema1MediaType || t == manifest.DockerV2Schema1SignedMediaType { manifestInfo, err := manifest.Schema1FromManifest(m) if err != nil { - return nil, err + return nil, nil, err } manifestInfoSlice = append(manifestInfoSlice, manifestInfo) - return manifestInfoSlice, nil + return manifestInfoSlice, m, nil } else if t == manifest.DockerV2ListMediaType { manifestSchemaListInfo, err := manifest.Schema2ListFromManifest(m) if err != nil { - return nil, err + return nil, nil, err } - + realManifestByte := m for _, manifestDescriptorElem := range manifestSchemaListInfo.Manifests { manifestByte, manifestType, err := i.source.GetManifest(i.ctx, &manifestDescriptorElem.Digest) if err != nil { - return nil, err + return nil, nil, err } - - platformSpecManifest, err := ManifestHandler(manifestByte, manifestType, i) + // If platform mismatch, ignore + if manifestDescriptorElem.Platform.OS != i.sysctx.OSChoice || manifestDescriptorElem.Platform.Architecture != i.sysctx.ArchitectureChoice { + continue + } + platformSpecManifest, singleManifestByte, err := ManifestHandler(manifestByte, manifestType, i) if err != nil { - return nil, err + return nil, nil, err } manifestInfoSlice = append(manifestInfoSlice, platformSpecManifest...) + realManifestByte = singleManifestByte } - return manifestInfoSlice, nil + return manifestInfoSlice, realManifestByte, nil } - return nil, fmt.Errorf("unsupported manifest type: %v", t) + if t == imgspecv1.MediaTypeImageManifest { + manifestInfo, err := manifest.OCI1FromManifest(m) + if err != nil { + return nil, m, err + } + manifestInfoSlice = append(manifestInfoSlice, manifestInfo) + return manifestInfoSlice, m, nil + } else if t == imgspecv1.MediaTypeImageIndex { + manifestSchemaListInfo, err := manifest.OCI1IndexFromManifest(m) + if err != nil { + return nil, nil, err + } + realManifestByte := m + + for _, manifestDescriptorElem := range manifestSchemaListInfo.Manifests { + + manifestByte, manifestType, err := i.source.GetManifest(i.ctx, &manifestDescriptorElem.Digest) + if err != nil { + return nil, nil, err + } + // If platform mismatch, ignore + if manifestDescriptorElem.Platform != nil { + if manifestDescriptorElem.Platform.OS != i.sysctx.OSChoice || manifestDescriptorElem.Platform.Architecture != i.sysctx.ArchitectureChoice { + continue + } + } + + platformSpecManifest, singleManifestByte, err := ManifestHandler(manifestByte, manifestType, i) + if err != nil { + return nil, nil, err + } + realManifestByte = singleManifestByte + manifestInfoSlice = append(manifestInfoSlice, platformSpecManifest...) + } + return manifestInfoSlice, realManifestByte, nil + } + return nil, nil, fmt.Errorf("unsupported manifest type: %v", t) } diff --git a/core/offline.go b/core/offline.go index c29e496..b0a07bf 100644 --- a/core/offline.go +++ b/core/offline.go @@ -51,17 +51,19 @@ func (t *OfflineDownTask) Status() string { func (t *OfflineDownTask) Run(tid int) error { srcUrl := fmt.Sprintf("%s/%s:%s", t.is.GetRegistry(), t.is.GetRepository(), t.is.GetTag()) defer t.ctx.CompMeta.ClearDoing(tid) + // manifest list or index manifestByte, manifestType, err := t.is.GetManifest() if err != nil { return errors.New(I18n.Sprintf("Failed to get manifest from %s error: %v", srcUrl, err)) } t.ctx.Info(I18n.Sprintf("Get manifest from %s", srcUrl)) - blobInfos, err := t.is.GetBlobInfos(manifestByte, manifestType) + blobInfos, realManifestByte, err := t.is.GetBlobInfos(manifestByte, manifestType) if err != nil { return errors.New(I18n.Sprintf("Get blob info from %s error: %v", srcUrl, err)) } - t.ctx.CompMeta.AddImage(t.url, string(manifestByte)) + + t.ctx.CompMeta.AddImage(t.url, string(realManifestByte)) for _, b := range blobInfos { begin := time.Now() @@ -69,8 +71,8 @@ func (t *OfflineDownTask) Run(tid int) error { if t.ctx.Cancel() { return errors.New(I18n.Sprintf("User cancelled...")) } - - if t.ctx.CompMeta.BlobExists(b.Digest.Hex()) || t.ctx.CompMeta.BlobStart(b.Digest.Hex(), tid) { + // should export empty layer (b.Size == 32) + if (b.Size > 32 && t.ctx.CompMeta.BlobExists(b.Digest.Hex())) || t.ctx.CompMeta.BlobStart(b.Digest.Hex(), tid) { t.ctx.Debug(I18n.Sprintf("Skip blob: %s", ShortenString(b.Digest.String(), 19))) continue } @@ -286,6 +288,10 @@ func (t *OfflineUploadTask) Run(tid int) error { defer r.Close() rdr, name, size, eof, err := r.ReadFileStreamByName(b.Digest.Hex()) if eof { + // Incre dockerSave mode, consider eof as found + if dockerSaver != nil && t.ctx.DockerSaverBlobValidate { + found = true + } continue } if err != nil { @@ -299,10 +305,15 @@ func (t *OfflineUploadTask) Run(tid int) error { } if reader == nil { + if dockerSaver != nil && t.ctx.DockerSaverBlobValidate { + found = true + } continue } if dockerSaver != nil { + // Will not check blob exists in local + // TODO: Check in local docker by docker client if i == 0 { dockerSaver.AppendFileStream(b.Digest.Hex()+".json", b.Size, reader) } else { @@ -311,6 +322,7 @@ func (t *OfflineUploadTask) Run(tid int) error { found = true break } else { + begin := time.Now() err = t.ids.PutABlob(ioutil.NopCloser(reader), b) if err != nil { diff --git a/core/online.go b/core/online.go index 924f9be..0e2196f 100644 --- a/core/online.go +++ b/core/online.go @@ -6,6 +6,8 @@ import ( "strings" "time" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/pkg/blobinfocache/none" "github.com/pkg/errors" @@ -83,7 +85,7 @@ func (t *OnlineTask) Run(idx int) error { } t.ctx.Info(I18n.Sprintf("Get manifest from %s", t.srcUrl)) - blobInfos, err := t.source.GetBlobInfos(manifestByte, manifestType) + blobInfos, _, err := t.source.GetBlobInfos(manifestByte, manifestType) if err != nil { return errors.New(I18n.Sprintf("Get blob info from %s error: %v", t.srcUrl, err)) } @@ -170,8 +172,8 @@ func (t *OnlineTask) Run(idx int) error { } } - //Push manifest list if manifestType == manifest.DockerV2ListMediaType { + //Push manifest list (docker) manifestSchemaListInfo, err := manifest.Schema2ListFromManifest(manifestByte) if err != nil { return err @@ -198,6 +200,34 @@ func (t *OnlineTask) Run(idx int) error { t.ctx.Info(I18n.Sprintf("Put manifestList to %s", t.dstUrl)) + } else if manifestType == imgspecv1.MediaTypeImageIndex { + // Push manifest list (OCI1) + manifestSchemaListInfo, err := manifest.OCI1IndexFromManifest(manifestByte) + if err != nil { + return err + } + + var subManifestByte []byte + + // push manifest to destination + for _, manifestDescriptorElem := range manifestSchemaListInfo.Manifests { + subManifestByte, _, err = t.source.source.GetManifest(t.source.ctx, &manifestDescriptorElem.Digest) + if err != nil { + return errors.New(I18n.Sprintf("Get manifest %v of OS:%s Architecture:%s for manifest list error: %v", manifestDescriptorElem.Digest, manifestDescriptorElem.Platform.OS, manifestDescriptorElem.Platform.Architecture, err)) + } + + if err := t.destination.PushManifest(subManifestByte); err != nil { + return errors.New(I18n.Sprintf("Put manifest to %s error: %v", t.dstUrl, err)) + } + } + + // push manifest list to destination + if err := t.destination.PushManifest(manifestByte); err != nil { + return errors.New(I18n.Sprintf("Put manifestList to %s error: %v", t.dstUrl, err)) + } + + t.ctx.Info(I18n.Sprintf("Put manifestList to %s", t.dstUrl)) + } else { // push manifest to destination if err := t.destination.PushManifest(manifestByte); err != nil { diff --git a/core/source.go b/core/source.go index 6345a33..2c5a3b2 100644 --- a/core/source.go +++ b/core/source.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "strings" "github.com/containers/image/v5/docker" "github.com/containers/image/v5/types" @@ -59,6 +60,13 @@ func NewImageSource(pCtx context.Context, registry, repository, tag, username, p } } + // Set OS and Architecture + confPlat := strings.Split(CONF.Platform, "/") + confOs := confPlat[0] + confArch := confPlat[1] + sysctx.OSChoice = confOs + sysctx.ArchitectureChoice = confArch + var rawSource types.ImageSource if tag != "" { // if tag ids empty, will attach to the "latest" tag, and will get a error if "latest" ids not exist @@ -88,14 +96,14 @@ func (i *ImageSource) GetManifest() ([]byte, string, error) { } // GetBlobInfos get blobs from source image. -func (i *ImageSource) GetBlobInfos(manifestByte []byte, manifestType string) ([]types.BlobInfo, error) { +func (i *ImageSource) GetBlobInfos(manifestByte []byte, manifestType string) ([]types.BlobInfo, []byte, error) { if i.source == nil { - return nil, fmt.Errorf("cannot get blobs without specfied a tag") + return nil, nil, fmt.Errorf("cannot get blobs without specfied a tag") } - manifestInfoSlice, err := ManifestHandler(manifestByte, manifestType, i) + manifestInfoSlice, realManifestByte, err := ManifestHandler(manifestByte, manifestType, i) if err != nil { - return nil, err + return nil, nil, err } // get a Blobs @@ -112,7 +120,8 @@ func (i *ImageSource) GetBlobInfos(manifestByte []byte, manifestType string) ([] } } - return srcBlobs, nil + return srcBlobs, realManifestByte, nil + } // GetABlob gets a blob from remote image diff --git a/go.mod b/go.mod index 6bb37a2..912140e 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,72 @@ module github.com/wct-devops/image-transmit -go 1.14 +go 1.17 require ( github.com/blinkbean/dingtalk v0.0.0-20201231030509-45a553a84503 github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 - github.com/codeclysm/extract/v3 v3.0.2 // indirect github.com/containers/image/v5 v5.12.0 - github.com/frankban/quicktest v1.13.0 // indirect + github.com/docker/docker v1.4.2-0.20191219165747-a9416c67da9f github.com/klauspost/compress v1.12.2 github.com/lxn/walk v0.0.0-20210112085537-c389da54e794 - github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 github.com/opencontainers/go-digest v1.0.0 + github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6 github.com/pierrec/lz4 v2.6.1+incompatible github.com/pkg/errors v0.9.1 github.com/ulikunitz/xz v0.5.10 github.com/vbatts/tar-split v0.11.1 github.com/wangyumu/extract/v3 v3.0.3 github.com/wangyumu/squashfs v0.4.1-0.20210619005902-780e9a1161fa - golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 - golang.org/x/text v0.3.4 - gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect + golang.org/x/sys v0.15.0 + golang.org/x/text v0.14.0 gopkg.in/yaml.v2 v2.4.0 ) + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/codeclysm/extract/v3 v3.0.2 // indirect + github.com/containerd/containerd v1.5.0-beta.4 // indirect + github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b // indirect + github.com/containers/ocicrypt v1.1.0 // indirect + github.com/containers/storage v1.30.1 // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.6.3 // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-metrics v0.0.1 // indirect + github.com/docker/go-units v0.4.0 // indirect + github.com/frankban/quicktest v1.13.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.4.3 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/gorilla/mux v1.7.4 // indirect + github.com/h2non/filetype v1.0.6 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/json-iterator/go v1.1.10 // indirect + github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/lxn/win v0.0.0-20210218163916-a377121e959e // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/moby/sys/mountinfo v0.4.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/opencontainers/runc v1.0.0-rc93 // indirect + github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d // indirect + github.com/pierrec/lz4/v4 v4.1.6 // indirect + github.com/prometheus/client_golang v1.7.1 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.10.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect + golang.org/x/net v0.19.0 // indirect + google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect + google.golang.org/grpc v1.33.2 // indirect + google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect +) diff --git a/go.sum b/go.sum index d7b4810..f44d9a2 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,7 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= @@ -141,6 +142,7 @@ github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMX github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4 h1:zjz4MOAOFgdBlwid2nNUlJ3YLpVi/97L36lfMYJex60= github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= @@ -287,6 +289,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -345,7 +348,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79 h1:ATVz3rDvK4xX0nHx57zYSHRVIK/+lFwln9KJr8wvuk0= github.com/gopherjs/gopherjs v0.0.0-20210420193930-a4630ec28c79/go.mod h1:Opf9rtYVq0eTyX+aRVmRO9hE8ERAozcdrBxWG9Q6mkQ= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -403,7 +405,6 @@ github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 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/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/juju/clock v0.0.0-20180524022203-d293bb356ca4/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA= @@ -485,6 +486,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ 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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mtrmac/gpgme v0.1.2/go.mod h1:GYYHnGSuS7HK3zVS2n3y73y0okK/BeKzwnn5jgiVFNI= @@ -609,10 +611,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= 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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -677,6 +677,7 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ 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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= @@ -710,6 +711,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 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= @@ -740,6 +743,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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= @@ -775,8 +780,12 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R 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-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 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= @@ -791,6 +800,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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= @@ -853,20 +864,34 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= 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/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 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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 h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/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= @@ -907,6 +932,8 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -950,6 +977,7 @@ google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfG 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/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8= google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -964,6 +992,7 @@ 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.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 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= @@ -1017,6 +1046,7 @@ 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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=