Signed-off-by: storyicon <yuanchao@bilibili.com>
This commit is contained in:
storyicon 2021-07-21 00:24:43 +08:00
commit 9aac714c32
No known key found for this signature in database
GPG key ID: 245915D985F966CF
47 changed files with 5480 additions and 0 deletions

28
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,28 @@
name: Go
on:
push:
tags: [ '**' ]
pull_request:
tags: [ '**' ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
- name: Build
run: make build
- name: Release
uses: fnkr/github-action-ghr@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GHR_COMPRESS: gz
GHR_PATH: /home/runner/work/powerproto/powerproto/dist/
GITHUB_TOKEN: ${{ secrets.ACTIONS_SECRET }}

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.vscode
.idea
dist

19
Makefile Normal file
View file

@ -0,0 +1,19 @@
VERSION=$(shell git describe --tags)
GIT_REVISION=$(shell git rev-parse --short HEAD)
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
BUILD_DATE=$$(date +%Y-%m-%d-%H:%M)
GO_FLAGS := -ldflags "-X main.BuildDate=$(BUILD_DATE) -X main.Branch=$(GIT_BRANCH) -X main.Revision=$(GIT_REVISION) -X main.Version=$(VERSION) -extldflags \"-static\" -s -w" -tags netgo
# Generate binaries for a powerproto release
.PHONY: build
build:
rm -fr ./dist/
mkdir -p ./dist
GOOS="linux" GOARCH="amd64" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-linux-amd64 ./cmd/powerproto
GOOS="linux" GOARCH="arm64" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-linux-arm64 ./cmd/powerproto
GOOS="linux" GOARCH="arm" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-linux-arm ./cmd/powerproto
GOOS="linux" GOARCH="386" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-linux-x86 ./cmd/powerproto
GOOS="windows" GOARCH="386" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-windows-x86.exe ./cmd/powerproto
GOOS="windows" GOARCH="amd64" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-windows-amd64.exe ./cmd/powerproto
GOOS="darwin" GOARCH="amd64" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-darwin-amd64 ./cmd/powerproto
GOOS="darwin" GOARCH="arm64" CGO_ENABLED=0 go build $(GO_FLAGS) -o ./dist/powerproto-darwin-arm64 ./cmd/powerproto

429
README.md Normal file
View file

@ -0,0 +1,429 @@
# PowerProto
[![Go Report Card](https://goreportcard.com/badge/github.com/storyicon/powerproto?t=1)](https://goreportcard.com/report/github.com/storyicon/powerproto) ![TotalLine](https://img.shields.io/tokei/lines/github/storyicon/powerproto?color=77%2C199%2C31) ![last-commit](https://img.shields.io/github/last-commit/storyicon/powerproto) [![GoDoc](https://godoc.org/github.com/storyicon/powerproto?status.svg)](https://godoc.org/github.com/storyicon/powerproto)
**English** | [中文](README_CN.md)
![exmpales](./docs/images/exmaple.gif)
PowerProto is used to solve the following three main problems:
1. lower the usage threshold and usage cost of gRPC.
2. solve the version control problem of protoc and its related plugins (such as protoc-gen-go, protoc-gen-grpc-gateway).
3. efficiently manage the compilation of proto to achieve multi-platform compatibility, one-click installation and compilation.
- [PowerProto](#powerproto)
- [Features](#features)
- [Installation and Dependencies](#installation-and-dependencies)
- [I. Installation via Go](#i-installation-via-go)
- [II. out-of-the-box version](#ii-out-of-the-box-version)
- [Command Introduction](#command-introduction)
- [I. Initial Config](#i-initial-config)
- [II. Tidy Config](#ii-tidy-config)
- [III. Compiling Proto files](#iii-compiling-proto-files)
- [Examples](#examples)
- [Config File](#config-file)
- [Definition](#definition)
- [Matching patterns and working directory](#matching-patterns-and-working-directory)
- [Multi-config](#multi-config)
- [PostAction](#postaction)
- [1. copy](#1-copy)
- [2. move](#2-move)
- [3. remove](#3-remove)
- [4. replace](#4-replace)
## Features
1. realize one-click installation and multi-version management of protoc.
2. realize one-click installation and multi-version management of protoc related plugins (such as protoc-gen-go).
3. manage the compilation of proto through config file instead of shell script to improve readability and compatibility.
4. bootstrap generation of config files, cross-platform compatibility, a config can be compiled in multiple platforms with one click.
5. support batch and recursive compilation of proto files to improve efficiency.
6. cross-platform support PostAction, you can perform some routine operations (such as replacing "omitempty" in all generated files) after the compilation.
7. support PostShell, execute specific shell scripts after the compilation.
## Installation and Dependencies
1. The current version of `PowerProto` relies on `go` and `git` (in the future it may use CDN to pull built binaries directly), please make sure the runtime environment contains these two commands.
2. `protoc` download source is Github, `PowerProto` respects `HTTP_PROXY`, `HTTPS_PROXY` environment variables when downloading `protoc`, if you encounter network problems, please configure your own proxy.
3. When querying the version list of `protoc`, `git ls-remote` is used for `github.com`, if you encounter network problems, please configure the proxy for `git` by yourself.
4. In the current version, downloading and querying plugin versions rely on the `go` command, so if you encounter network problems, please configure the `GOPROXY` environment variable yourself.
5. By default, `user directory/.powerproto` is used as the installation directory, which is used to place the downloaded plug-ins and global config.
6. If you think the name `powerproto` is too long, you can `alias` it into a simpler name to improve the input efficiency, for example, no one will mind if you call it `pp`.
### I. Installation via Go
Installation can be performed by executing the following command directly:
```
go install github.com/storyicon/powerproto/cmd/powerproto
```
### II. out-of-the-box version
You can download the out-of-the-box version via the `Github Release Page`.
## Command Introduction
You can view help with `powerproto -h`, e.g.
```
powerproto -h
powerproto init -h
powerproto tidy -h
powerproto build -h
```
It has the advantage that the documentation on the command line is always consistent with your binary version.
### I. Initial Config
The config can be initialized with the following command.
```
powerproto init
```
### II. Tidy Config
The config can be tidied with the following command.
```
powerproto tidy
```
It will search for a config file named `powerproto.yaml` from the current directory to the parent directory, and will read and tidy the config.
You can also specify which config file to tidy.
```
powerproto tidy [the path of proto file]
```
Tidy the config consists of two main operations:
1. replacing the latest in the version with the real latest version number by querying.
2. install all dependencies defined in the config file.
### III. Compiling Proto files
The Proto file can be compiled with the following command.
```
// Compile the specified proto file
powerproto build xxxx.proto
// Compile all the proto files in the current directory
powerproto build .
// Compile all proto files in the current directory recursively, including subfolders.
powerproto build -r .
```
The execution logic is that for each proto file, the `powerproto.yaml` config file will be searched from the directory where the proto file is located to the ancestor directory:
1. For the found config file, match it with the `scope` in it and use it if it matches.
2. Check and install the dependencies declared in the config file.
3. Compile the proto file according to the `plugins`, `protoc`, `options`, `importPaths` and other configs in the config file。 After all the proto files are compiled, if you specify the `-p` argument, `PostAction` and `PostShell` will also be executed.
Note: The default `working directory` of `PowerProto` is the directory where the `proto file` matches to the config file, it is equivalent to the directory where you execute the `protoc` command. You can change it via `protocWorkDir` in the config file.
## Examples
For example, you have the following file structure in the `/mnt/data/hello` directory:
```
$ pwd
/mnt/data/hello
$ tree
./apis
└── hello.proto
powerproto.yaml
```
The contents of the `powerproto.yaml` file (you can easily generate the config file with the `powerproto init` command) are:
```
scopes:
- ./
protoc: latest
protocWorkDir: ""
plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@latest
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
postActions: []
postShell: ""
```
Execute in any directory:
```
powerproto build -r /mnt/data/hello/apis
```
You can get the compiled file:
```
$ pwd
/mnt/data/hello
$ tree
./apis
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
powerproto.yaml
```
It is equivalent to if you were in the directory where `powerproto.yaml` is located and executed:
```shell script
$POWERPROTO_HOME/protoc/3.17.3/protoc --go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
--proto_path=/mnt/data/hello \
--proto_path=$GOPATH \
--proto_path=$POWERPROTO_HOME/include \
--plugin=protoc-gen-go=$POWERPROTO_HOME/plugins/google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1/protoc-gen-go \
--plugin=protoc-gen-go-grpc=$POWERPROTO_HOME/plugins/google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0/protoc-gen-go-grpc \
/mnt/data/hello/apis/hello.proto
```
## Config File
The config file is used to describe the versions of various dependencies and parameters when compiling the proto file.
It can be easily initialized with `powerproto init`.
### Definition
Take the following config file as an example:
```yaml
# required. scopes is used to define scopes.
# i.e. which directories in the project the current config item is valid for
scopes:
- ./
# required. the version of protoc.
# you can fill in the 'latest', will be automatically converted to the latest version
protoc: 3.17.3
# optional. The working directory for executing the protoc command,
# the default is the directory where the config file is located.
# support mixed environment variables in path, such as $GOPATH
protocWorkDir: ""
# required. it is used to describe which plug-ins are required for compilation
plugins:
# the name, path, and version number of the plugin.
# the address of the plugin must be in path@version format,
# and version can be filled with 'latest', which will be automatically converted to the latest version.
protoc-gen-deepcopy: istio.io/tools/cmd/protoc-gen-deepcopy@latest
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@latest
protoc-gen-go-json: github.com/mitchellh/protoc-gen-go-json@v1.0.0
protoc-gen-grpc-gateway: github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.5.0
# required. defines the parameters of protoc when compiling proto files
options:
- --go_out=paths=source_relative:.
- --go-json_out=.
- --deepcopy_out=source_relative:.
- --grpc-gateway_out=.
- --go-grpc_out=paths=source_relative:.
# required. defines the path of the proto dependency, which will be converted to the --proto_path (-I) parameter.
# support mixed environment variables, such as $GOPATH/include.
# $POWERPROTO_INCLUDE is a special variable that refers to $POWERPROTO_HOME/include, which contains the public proto files provided by protoc by default.
# "." is the folder where the config file is located.
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
# optional. The operation is executed after compilation.
# its working directory is the directory where the config file is located.
# postActions is cross-platform compatible.
# Note that the "-p" parameter must be appended to the "powerproto build" to allow execution of the postActions in the config file
postActions: []
# optional. The shell script that is executed after compilation.
# its working directory is the directory where the config file is located.
# postShell is not cross-platform compatible.
# Note that the "-p" parameter must be appended to the "powerproto build" to allow execution of the postShell in the config file
postShell: |
// do something
```
#### Matching patterns and working directory
When building the proto file, the `powerproto.yaml` config file will be searched from the directory where the proto file is located to the ancestor directory, match with the `scope` in.
The first matched config item will be used for the compilation of this proto file.
When PowerProto executes protoc (and also when it executes postActions and postShell), the default is to use the directory where the config file is located as the working directory. (working directory is equivalent to the directory where you execute the protoc command.)
#### Multi-config
A config file can be filled with multiple configs, which are separated by "---".
In the example below, the apis1 directory uses protoc-gen-go with v1.25.0, while the apis2 directory uses protoc-gen-go with v1.27.0.
```
scopes:
- ./apis1
protoc: v3.17.3
protocWorkDir: ""
plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
postActions: []
postShell: ""
---
scopes:
- ./apis2
protoc: v3.17.3
protocWorkDir: ""
plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
postActions: []
postShell: ""
```
### PostAction
PostAction allows to perform specific actions after all proto files have been compiled. In contrast to `PostShell`, it is cross-platform supported.
For security reasons, the `PostAction` and `PostShell` defined in the config file will only be executed if the `-p` argument is appended to the execution of `powerproto build`.
Currently, PostAction supports the following commands:
| Command | Description | Function Prototype |
| ------- | ---------------------- | ----------------------------------------------------- |
| copy | Copy file or folder | copy(src string, dest string) error |
| move | Move file or folder | move(src string, dest string) error |
| remove | Delete file or folder | remove(path ...string) error |
| replace | Batch replacement of strings in files | replace(pattern string, from string, to string) error |
#### 1. copy
For copying files or folders, the function prototype is:
```
copy(src string, dest string) error
```
For security and config compatibility, only relative paths are allowed in the parameters.
If the target folder already exists, it will be merged.
The following example will copy 'a' from the directory where the config file is located to 'b'.
```yaml
postActions:
- name: copy
args:
- ./a
- ./b
```
#### 2. move
For moving files or folders, the function prototype is:
```
move(src string, dest string) error
```
For security and config compatibility, only relative paths are allowed in the parameters.
If the target folder already exists, it will be merged.
The following example will move 'a' in the directory where the config file is located to 'b'.
```yaml
postActions:
- name: move
args:
- ./a
- ./b
```
#### 3. remove
For deleting files or folders, the function prototype is:
```
remove(path ...string) error
```
For security and config compatibility, only relative paths are allowed in the parameters.
The following example will remove 'a', 'b' and 'c' from the directory where the config file is located:
```yaml
postActions:
- name: remove
args:
- ./a
- ./b
- ./c
```
#### 4. replace
Used for batch replacement of strings in files. Its function prototype is:
```
replace(pattern string, from string, to string) error
```
* `pattern` is a relative path that supports wildcard characters.
* `from` is the string to be replaced.
* `to` is the string to replace with.
The following example will replace `,omitempty` with the empty string in all go files in the apis directory and its subdirectories:
```
postActions:
- name: replace
args:
- ./apis/**/*.go
- ',omitempty'
- ""
```

430
README_CN.md Normal file
View file

@ -0,0 +1,430 @@
# PowerProto
[![Go Report Card](https://goreportcard.com/badge/github.com/storyicon/powerproto?t=1)](https://goreportcard.com/report/github.com/storyicon/powerproto) ![TotalLine](https://img.shields.io/tokei/lines/github/storyicon/powerproto?color=77%2C199%2C31) ![last-commit](https://img.shields.io/github/last-commit/storyicon/powerproto) [![GoDoc](https://godoc.org/github.com/storyicon/powerproto?status.svg)](https://godoc.org/github.com/storyicon/powerproto)
**中文** | [English](README_CN.md)
![exmpales](./docs/images/exmaple.gif)
PowerProto主要用于解决下面三个问题
1. 降低gRPC的使用门槛与使用成本。
2. 解决protoc以及其相关插件比如protoc-gen-go、protoc-gen-grpc-gateway的版本控制问题。
3. 高效管理proto的编译实现多平台兼容、一键安装与编译。
- [PowerProto](#powerproto)
- [功能](#功能)
- [安装与依赖](#安装与依赖)
- [一、通过Go进行安装](#一通过go进行安装)
- [二、开箱即用版本](#二开箱即用版本)
- [命令介绍](#命令介绍)
- [一、初始化配置](#一初始化配置)
- [二、整理配置](#二整理配置)
- [三、编译Proto文件](#三编译proto文件)
- [示例](#示例)
- [配置文件](#配置文件)
- [解释](#解释)
- [匹配模式与工作目录](#匹配模式与工作目录)
- [多配置组合](#多配置组合)
- [PostAction](#postaction)
- [1. copy](#1-copy)
- [2. move](#2-move)
- [3. remove](#3-remove)
- [4. replace](#4-replace)
## 功能
1. 实现protoc的一键安装与多版本管理。
2. 实现protoc相关插件比如protoc-gen-go的一键安装与多版本管理。
3. 通过配置文件管理proto的编译而非shell脚本提高可读性与兼容性。
4. 引导式生成配置文件,跨平台兼容,一份配置在多个平台均可以实现一键编译。
5. 支持批量、递归编译proto文件提高效率。
6. 跨平台支持PostAction可以在编译完成之后执行一些常规操作比如替换掉所有生成文件中的"omitempty")。
7. 支持PostShell在编译完成之后执行特定的shell脚本。
## 安装与依赖
1. 目前版本的 `PowerProto` 依赖 `go` 以及 `git`未来可能会直接使用CDN拉取构建好的二进制请确保运行环境中包含这两个命令。
2. `protoc`的下载源是Github`PowerProto`在下载`protoc`时尊重 `HTTP_PROXY``HTTPS_PROXY`环境变量,如果遇到网络问题,请自行配置代理。
3. 在查询`protoc`的版本列表时,会对`github.com`使用`git ls-remote`,如果遇到网络问题,请自行为`git`配置代理。
4. 在当前版本,下载和查询插件版本均依赖`go`命令,所以如果遇到网络问题,请自行配置 `GOPROXY`环境变量。
5. 默认会使用 `用户目录/.powerproto`作为安装目录,用于放置下载好的各种插件以及全局配置,可以通过 `POWERPROTO_HOME`环境变量来修改安装目录。
6. 如果认为`powerproto`名字太长,可以通过`alias`成一个更简单的名字来提高输入效率,比如没有人会介意你叫它`pp`
### 一、通过Go进行安装
直接执行下面的命令即可进行安装:
```
go install github.com/storyicon/powerproto/cmd/powerproto@latest
```
### 二、开箱即用版本
可以通过 `Github Release Page` 下载开箱即用版本。
## 命令介绍
你可以通过 powerproto -h 来查看帮助,比如:
```
powerproto -h
powerproto init -h
powerproto tidy -h
powerproto build -h
```
它的好处是命令行中的文档永远和你的二进制版本保持一致。而Github上的文档可能会一直是对应最新的二进制。
### 一、初始化配置
可以通过下面的命令进行配置的初始化:
```
powerproto init
```
### 二、整理配置
可以通过下面的命令整理配置:
```
powerproto tidy
```
它将会从当前目录开始向父级目录搜索名为 `powerproto.yaml` 的配置文件,并对配置进行读取和整理。
你也可以指定整理哪个配置文件:
```
powerproto tidy [the path of proto file]
```
整理配置主要包含两个操作:
1. 通过查询将版本中的latest替换为真实的最新版本号。
2. 安装配置文件中定义的所有依赖。
### 三、编译Proto文件
可以通过下面的命令进行Proto文件的编译
```
// 编译指定的proto文件
powerproto build xxxx.proto
// 编译当前目录下的所有proto文件
powerproto build .
// 递归编译当前目录下的所有proto文件包括子文件夹。
powerproto build -r .
```
其执行逻辑是对于每一个proto文件从其文件所在目录开始向父级目录寻找 `powerproto.yaml` 配置文件:
1. 对于找到的配置文件,与其中的`scope`进行匹配,如果匹配则采用。
2. 检查并安装配置文件中声明的依赖。
3. 根据配置文件中的`plugins``protoc``options``importPaths`等配置对proto文件进行编译。 当所有的proto文件都编译完成之后如果你指定了 `-p` 参数,还会进行`PostAction``PostShell`的执行。
注意:`protoc`执行的工作目录默认是`proto文件`匹配到的配置文件所在的目录它相当于你在配置文件所在目录执行protoc命令。你可以通过配置文件中的 `protocWorkDir` 来进行修改。
## 示例
比如你在 `/mnt/data/hello` 目录下拥有下面这样的文件结构:
```
$ pwd
/mnt/data/hello
$ tree
./apis
└── hello.proto
powerproto.yaml
```
`powerproto.yaml` 的文件内容是(你可以通过 `powerproto init` 命令很方便的生成配置文件):
```
scopes:
- ./
protoc: latest
protocWorkDir: ""
plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@latest
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
postActions: []
postShell: ""
```
在任意目录执行:
```
powerproto build -r /mnt/data/hello/apis
```
你都可以得到编译后的文件
```
$ pwd
/mnt/data/hello
$ tree
./apis
├── hello.pb.go
├── hello.proto
└── hello_grpc.pb.go
powerproto.yaml
```
它相当于你在 `powerproto.yaml` 所在目录,执行:
```
$POWERPROTO_HOME/protoc/3.17.3/protoc --go_out=. \
--go_opt=paths=source_relative \
--go-grpc_out=. \
--go-grpc_opt=paths=source_relative \
--proto_path=/mnt/data/hello \
--proto_path=$GOPATH \
--proto_path=$POWERPROTO_HOME/include \
--plugin=protoc-gen-go=$POWERPROTO_HOME/plugins/google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1/protoc-gen-go \
--plugin=protoc-gen-go-grpc=$POWERPROTO_HOME/plugins/google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0/protoc-gen-go-grpc
/mnt/data/hello/apis/hello.proto
```
## 配置文件
配置文件用于描述编译proto文件时各种依赖的版本以及参数等。
可以方便的通过 `powerproto init`进行配置文件的初始化。
### 解释
以下面这份配置文件为例:
```yaml
# 必填scopes 用于定义作用域,即当前配置项对项目中的哪些目录生效
scopes:
- ./
# 必填protoc的版本可以填 latest会自动转换成最新的版本
protoc: 3.17.3
# 选填执行protoc命令的工作目录默认是配置文件所在目录
# 支持路径中混用环境变量,比如$GOPATH
protocWorkDir: ""
# 必填代表scope匹配的目录中的proto文件在编译时需要用到哪些插件
plugins:
# 插件的名字、路径以及版本号。
# 插件的地址必须是 path@version 的格式version可以填latest会自动转换成最新的版本。
protoc-gen-deepcopy: istio.io/tools/cmd/protoc-gen-deepcopy@latest
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@latest
protoc-gen-go-json: github.com/mitchellh/protoc-gen-go-json@v1.0.0
protoc-gen-grpc-gateway: github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.5.0
# 必填定义了编译proto文件时 protoc 的参数
options:
- --go_out=paths=source_relative:.
- --go-json_out=.
- --deepcopy_out=source_relative:.
- --grpc-gateway_out=.
- --go-grpc_out=paths=source_relative:.
# 必填,定义了构建时 protoc 的引用路径,会被转换为 --proto_path (-I) 参数。
# 支持混用环境变量,比如 $GOPATH/include。
# $POWERPROTO_INCLUDE是一个特殊变量指 $POWERPRTO/include里面包含了protoc默认提供的公共proto 文件。
# "." 是指配置文件所在文件夹。
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
# 选填,构建完成之后执行的操作,工作目录是配置文件所在目录
# postActions是跨平台兼容的
# 注意,必须在 powerproto build 时附加 -p 参数才会执行配置文件中的postActions
postActions: []
# 选填构建完成之后执行的shell脚本工作目录是配置文件所在目录
# postShell不是跨平台兼容的。
# 注意,必须在 powerproto build 时附加 -p 参数才会执行配置文件中的postShell
postShell: |
// do something
```
#### 匹配模式与工作目录
在构建proto文件时将会从proto文件所在目录开始向父级目录搜索 `powerproto.yaml` 配置文件,并与其中的 scope进行匹配第一个匹配到的配置将会被用于此proto文件的编译。
在执行protoc时执行postActions、postShell时也是如此是以配置文件所在目录作为工作目录的即相当于你在这个目录执行protoc命令。
#### 多配置组合
一个配置文件中支持填写多份配置,多份配置之间以 "---" 进行分割。
在下面的示例中apis1目录使用的是v1.25.0的protoc-gen-go而apis2目录使用的则是v1.27.0的protoc-gen-go。
```
scopes:
- ./apis1
protoc: v3.17.3
protocWorkDir: ""
plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
postActions: []
postShell: ""
---
scopes:
- ./apis2
protoc: v3.17.3
protocWorkDir: ""
plugins:
protoc-gen-go: google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.0
protoc-gen-go-grpc: google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0
options:
- --go_out=.
- --go_opt=paths=source_relative
- --go-grpc_out=.
- --go-grpc_opt=paths=source_relative
importPaths:
- .
- $GOPATH
- $POWERPROTO_INCLUDE
postActions: []
postShell: ""
```
### PostAction
PostAction允许在所有的proto文件都编译完成之后执行特定的操作。与`PostShell`相比,它是跨平台支持的。
为了安全起见,只有在执行 `powerproto build`时附加上 `-p` 参数,才会执行配置文件中定义的`PostAction``PostShell`
目前PostAction支持下面这些命令
| 命令 | 描述 | 函数原型 |
| ------- | ---------------------- | ----------------------------------------------------- |
| copy | 复制文件或文件夹 | copy(src string, dest string) error |
| move | 移动文件或文件夹 | move(src string, dest string) error |
| remove | 删除文件或文件夹 | remove(path ...string) error |
| replace | 批量替换文件中的字符串 | replace(pattern string, from string, to string) error |
#### 1. copy
用于复制文件或文件夹,其函数原型为:
```
copy(src string, dest string) error
```
为了安全以及配置的兼容性,参数中只允许填写相对路径。
如果目标文件夹已经存在,将会合并。
下面的例子将会把配置文件所在目录下的a复制到b
```yaml
postActions:
- name: copy
args:
- ./a
- ./b
```
#### 2. move
用于移动文件或文件夹,其函数原型为:
```
move(src string, dest string) error
```
为了安全以及配置的兼容性,参数中只允许填写相对路径。
如果目标文件夹已经存在,将会合并。
下面的例子将会把配置文件所在目录下的a移动到b
```yaml
postActions:
- name: move
args:
- ./a
- ./b
```
#### 3. remove
用于删除文件或文件夹,其函数原型为:
```
remove(path ...string) error
```
为了安全以及配置的兼容性,参数中只允许填写相对路径。
下面的例子将会删除配置文件所在目录下的a、b、c
```yaml
postActions:
- name: remove
args:
- ./a
- ./b
- ./c
```
#### 4. replace
用于批量替换文件中的字符串,其函数原型为:
```
replace(pattern string, from string, to string) error
```
其中:
* pattern是支持通配符的相对路径。
* from是要被替换的字符串。
* to是替换为的字符串。
下面的例子将会把apis目录以及其子目录下的所有go文件中的 `,omitempty` 替换为空字符串:
```
postActions:
- name: replace
args:
- ./apis/**/*.go
- ',omitempty'
- ""
```

60
cmd/powerproto/main.go Normal file
View file

@ -0,0 +1,60 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"github.com/spf13/cobra"
cmdbuild "github.com/storyicon/powerproto/cmd/powerproto/subcommands/build"
cmdenv "github.com/storyicon/powerproto/cmd/powerproto/subcommands/env"
cmdinit "github.com/storyicon/powerproto/cmd/powerproto/subcommands/init"
cmdtidy "github.com/storyicon/powerproto/cmd/powerproto/subcommands/tidy"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// Version is set via build flag -ldflags -X main.Version
var (
Version string
Branch string
Revision string
BuildDate string
)
var log = logger.NewDefault("command")
// GetRootCommand is used to get root command
func GetRootCommand() *cobra.Command {
return &cobra.Command{
Use: "[powerproto]",
Version: fmt.Sprintf("%s, branch: %s, revision: %s, buildDate: %s", Version, Branch, Revision, BuildDate),
Short: "powerproto is used to build proto files and version control of protoc and related plug-ins",
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}
}
func main() {
cmdRoot := GetRootCommand()
cmdRoot.AddCommand(
cmdbuild.CommandBuild(log),
cmdinit.CommandInit(log),
cmdtidy.CommandTidy(log),
cmdenv.CommandEnv(log),
)
cmdRoot.Execute()
}

View file

@ -0,0 +1,117 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package build
import (
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/bootstraps"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
const description = `
Examples:
compile specific proto file
powerproto build [proto file]
compile the proto file in the folder, excluding sub folders:
powerproto build [dir]
compile all proto files in the folder recursively, including sub folders:
powerproto build -r [dir]
compile proto files and execute the post actions/shells:
powerproto build -r -a [dir]
`
// compile proto files
// powerproto build -r .
// powerproto build .
// powerproto build xxxxx.proto
func CommandBuild(log logger.Logger) *cobra.Command {
var recursive bool
// todo: this feature is still under development
var dryRun bool
var postScriptEnabled bool
cmd := &cobra.Command{
Use: "build [dir|proto file]",
Short: "compile proto files",
Long: strings.TrimSpace(description),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ctx := cmd.Context()
if dryRun {
ctx = command.WithDryRun(ctx)
}
if !postScriptEnabled {
ctx = command.WithDisableAction(ctx)
}
target, err := filepath.Abs(args[0])
if err != nil {
log.LogFatal(nil, "failed to abs target path: %s", err)
}
fileInfo, err := os.Stat(target)
if err != nil {
log.LogFatal(map[string]interface{}{
"target": target,
}, "failed to stat target: %s", err)
}
var targets []string
if fileInfo.IsDir() {
log.LogInfo(nil, "search proto files...")
if recursive {
targets, err = util.GetFilesWithExtRecursively(target, ".proto")
if err != nil {
log.LogFatal(nil, "failed to walk directory: %s", err)
}
} else {
targets, err = util.GetFilesWithExt(target, ".proto")
if err != nil {
log.LogFatal(nil, "failed to walk directory: %s", err)
}
}
} else {
targets = append(targets, target)
}
if len(targets) == 0 {
log.LogWarn(nil, "no file to compile")
return
}
if err := bootstraps.StepTidyConfig(ctx, targets); err != nil {
log.LogFatal(nil, "failed to tidy config: %+v", err)
return
}
if err := bootstraps.Compile(ctx, targets); err != nil {
log.LogFatal(nil, "failed to compile: %+v", err)
}
log.LogInfo(nil, "succeed! you are ready to go :)")
},
}
flags := cmd.PersistentFlags()
flags.BoolVarP(&recursive, "recursive", "r", recursive, "whether to recursively traverse all child folders")
flags.BoolVarP(&postScriptEnabled, "postScriptEnabled", "p", postScriptEnabled, "when this flag is attached, it will allow the execution of postActions and postShell")
return cmd
}

View file

@ -0,0 +1,57 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package env
import (
"os"
"os/exec"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// Print environment variables related to program operation
func CommandEnv(log logger.Logger) *cobra.Command {
return &cobra.Command{
Use: "env",
Short: "list the environments and binary files",
Run: func(cmd *cobra.Command, args []string) {
log.LogInfo(nil, "[ENVIRONMENT]")
for _, key := range []string{
consts.EnvHomeDir,
"HTTP_PROXY",
"HTTPS_PROXY",
"GOPROXY",
} {
log.LogInfo(nil, "%s=%s", key, os.Getenv(key))
}
log.LogInfo(nil, "[BIN]")
for _, key := range []string {
"go",
"git",
} {
path, err := exec.LookPath(key)
if err != nil {
log.LogError(nil, "failed to find: %s", key)
continue
}
log.LogInfo(nil, "%s=%s", key, path)
}
},
}
}

View file

@ -0,0 +1,114 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package build
import (
"github.com/AlecAivazis/survey/v2"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// UserPreference defines the model of user preference
type UserPreference struct {
Plugins []string `survey:"plugins"`
}
// GetUserPreference is used to get user preference
func GetUserPreference() (*UserPreference, error) {
var preference UserPreference
err := survey.Ask([]*survey.Question{
{
Name: "plugins",
Prompt: &survey.MultiSelect{
Message: "select plugins to use. Later, you can also manually add in the configuration file",
Options: GetWellKnownPluginsOptionValues(),
},
},
}, &preference)
return &preference, err
}
// GetDefaultConfig is used to get default config
func GetDefaultConfig() *configs.Config {
return &configs.Config{
Scopes: []string{
"./",
},
Protoc: "latest",
Plugins: map[string]string{
"protoc-gen-go": "google.golang.org/protobuf/cmd/protoc-gen-go@latest",
"protoc-gen-go-grpc": "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
},
Options: []string{
"--go_out=.",
"--go_opt=paths=source_relative",
"--go-grpc_out=.",
"--go-grpc_opt=paths=source_relative",
},
ImportPaths: []string{
".",
"$GOPATH",
"$POWERPROTO_INCLUDE",
},
}
}
// CommandInit is used to initialize the configuration
// file in the current directory
func CommandInit(log logger.Logger) *cobra.Command {
return &cobra.Command{
Use: "init",
Short: "init a config file in current directory",
Run: func(cmd *cobra.Command, args []string) {
exists, err := util.IsFileExists(consts.ConfigFileName)
if err != nil {
log.LogError(nil, err.Error())
return
}
if exists {
log.LogInfo(nil, "config file already exists in this directory")
return
}
preference, err := GetUserPreference()
if err != nil {
log.LogError(nil, err.Error())
return
}
config := GetDefaultConfig()
if len(preference.Plugins) != 0 {
var compileOptions []string
plugins := map[string]string{}
for _, val := range preference.Plugins {
plugin, ok := GetPluginFromOptionsValue(val)
if ok {
plugins[plugin.Name] = plugin.Pkg
compileOptions = append(compileOptions, plugin.Options...)
}
}
config.Plugins = plugins
config.Options = compileOptions
}
if err := configs.SaveConfigs(consts.ConfigFileName, config); err != nil {
log.LogFatal(nil, "failed to save config: %s", err)
}
log.LogInfo(nil, "succeed! You can use `powerproto tidy` to tidy this config file")
},
}
}

View file

@ -0,0 +1,95 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package build
import (
"fmt"
)
// Plugin defines the plugin options
type Plugin struct {
Name string
Pkg string
Options []string
}
// String implements the standard string interface
func (options *Plugin) String() string {
return fmt.Sprintf("%s: %s", options.Name, options.Pkg)
}
// GetWellKnownPlugins is used to get well known plugins
func GetWellKnownPlugins() []*Plugin {
return []*Plugin{
{
Name: "protoc-gen-go",
Pkg: "google.golang.org/protobuf/cmd/protoc-gen-go@latest",
Options: []string{
"--go_out=.",
"--go_opt=paths=source_relative",
},
},
{
Name: "protoc-gen-go-grpc",
Pkg: "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest",
Options: []string{
"--go-grpc_out=.",
"--go-grpc_opt=paths=source_relative",
},
},
{
Name: "protoc-gen-grpc-gateway",
Pkg: "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest",
Options: []string{
"--grpc-gateway_out=.",
},
},
{
Name: "protoc-gen-deepcopy",
Pkg: "istio.io/tools/cmd/protoc-gen-deepcopy@latest",
Options: []string{
"--deepcopy_out=source_relative:.",
},
},
{
Name: "protoc-gen-go-json",
Pkg: "github.com/mitchellh/protoc-gen-go-json@latest",
Options: []string{
"--go-json_out=.",
},
},
}
}
// GetPluginFromOptionsValue is used to get plugin by option value
func GetPluginFromOptionsValue(val string) (*Plugin, bool) {
plugins := GetWellKnownPlugins()
for _, plugin := range plugins {
if plugin.String() == val {
return plugin, true
}
}
return nil, false
}
// GetWellKnownPluginsOptionValues is used to get option values of well known plugins
func GetWellKnownPluginsOptionValues() []string {
plugins := GetWellKnownPlugins()
packages := make([]string, 0, len(plugins))
for _, plugin := range plugins {
packages = append(packages, plugin.String())
}
return packages
}

View file

@ -0,0 +1,108 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tidy
import (
"context"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/storyicon/powerproto/pkg/bootstraps"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar"
)
func tidy(ctx context.Context,
pluginManager pluginmanager.PluginManager,
configFilePath string) error {
progress := progressbar.GetProgressBar(1)
progress.SetPrefix("tidy config")
err := bootstraps.StepTidyConfigFile(ctx, pluginManager, progress, configFilePath)
if err != nil {
return err
}
progress.Incr()
progress.Wait()
configItems, err := configs.LoadConfigItems(configFilePath)
if err != nil {
return err
}
if err := bootstraps.StepInstallProtoc(ctx, pluginManager, configItems); err != nil {
return err
}
if err := bootstraps.StepInstallPlugins(ctx, pluginManager, configItems); err != nil {
return err
}
return nil
}
// By default, clean the powerproto.yaml of the current directory and all parent directories
// You can also explicitly specify the configuration file to clean up
func CommandTidy(log logger.Logger) *cobra.Command {
return &cobra.Command{
Use: "tidy [config file]",
Short: "tidy the config file. It will replace the version number and install the protoc and proto plugins that declared in the config file",
Run: func(cmd *cobra.Command, args []string) {
var targets []string
if len(args) != 0 {
for _, arg := range args {
dir, _ := filepath.Abs(arg)
targets = append(targets, dir)
}
} else {
dir, err := os.Getwd()
if err != nil {
log.LogFatal(nil, "failed to get current dir: %s", err)
}
targets = configs.ListConfigPaths(dir)
}
pluginManager, err := pluginmanager.NewPluginManager(pluginmanager.NewConfig(), log)
if err != nil {
log.LogFatal(nil, "failed to create plugin manager: %s", err)
}
configMap := map[string]struct{}{}
for _, path := range targets {
exists, err := util.IsFileExists(path)
if err != nil {
log.LogFatal(map[string]interface{}{
"path": path,
"err": err,
}, "failed to tidy config")
}
if !exists {
continue
}
log.LogInfo(nil, "tidy %s", path)
if err := tidy(cmd.Context(), pluginManager, path); err != nil {
log.LogFatal(map[string]interface{}{
"path": path,
"err": err,
}, "failed to tidy config")
}
configMap[path] = struct{}{}
}
log.LogInfo(nil, "these following config files were tidied:")
for path := range configMap {
log.LogInfo(nil, " %s", path)
}
log.LogInfo(nil, "\r\nsucceeded, you are ready to go :)")
},
}
}

BIN
docs/images/exmaple.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

34
go.mod Normal file
View file

@ -0,0 +1,34 @@
module github.com/storyicon/powerproto
go 1.16
require (
github.com/AlecAivazis/survey/v2 v2.2.14
github.com/bmatcuk/doublestar v1.3.4
github.com/dsnet/compress v0.0.1 // indirect
github.com/fatih/color v1.12.0
github.com/frankban/quicktest v1.13.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/go-multierror v1.0.0
github.com/json-iterator/go v1.1.11
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mholt/archiver v3.1.1+incompatible
github.com/nwaples/rardecode v1.1.0 // indirect
github.com/onsi/ginkgo v1.16.4
github.com/onsi/gomega v1.14.0
github.com/otiai10/copy v1.6.0
github.com/pierrec/lz4 v2.6.1+incompatible // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.11.0
github.com/rs/zerolog v1.23.0
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/ulikunitz/xz v0.5.10 // indirect
github.com/vbauerster/mpb/v7 v7.0.3
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
golang.org/x/mod v0.4.2
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)

763
go.sum Normal file
View file

@ -0,0 +1,763 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
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/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
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=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.2.14 h1:aTYTaCh1KLd+YWilkeJ65Ph78g48NVQ3ay9xmaNIyhk=
github.com/AlecAivazis/survey/v2 v2.2.14/go.mod h1:TH2kPCDU3Kqq7pLbnCWwZXDBjnhZtmsCle5EiYDJ2fg=
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/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
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/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
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/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
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.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
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-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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-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=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
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.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
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/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
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 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
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-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 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
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.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/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
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/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/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/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
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.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
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.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/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
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 h1:ep6kpPVwmr/nTbklSx2nrLNSIO62DoYAhnPNIMhK8gI=
github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/otiai10/copy v1.6.0 h1:IinKAryFFuPONZ7cm6T6E2QX/vcJwSnlaA5lfoaXIiQ=
github.com/otiai10/copy v1.6.0/go.mod h1:XWfuS3CrI0R6IE0FbgHsEazaXO8G0LpMp9o8tos0x4E=
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.2 h1:VYWnrP5fXmz1MXvjuUvcBrXSjGE6xjON+axB/UrpO3E=
github.com/otiai10/mint v1.3.2/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
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/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbauerster/mpb/v7 v7.0.3 h1:NfX0pHWhlDTev15M/C3qmSTM1EiIjcS+/d6qS6H4FnI=
github.com/vbauerster/mpb/v7 v7.0.3/go.mod h1:NXGsfPGx6G2JssqvEcULtDqUrxuuYs4llpv8W6ZUpzk=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
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.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/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-20190108225652-1e06a53dbb7e/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=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-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=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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-20201031054903-ff519b6c9102/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-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
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-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
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=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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-20210220032951-036812b2e83c/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=
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-20181116152217-5ac8a444bdc5/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-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=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20190904154756-749cb33beabd/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-20191120155948-bd437916bb0e/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-20200106162015-b016eb3dc98e/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-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/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-20210603081109-ebe580a85c40/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-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/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/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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/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=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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-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=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/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-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
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=
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=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/grpc v1.19.0/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=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
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 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/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=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/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.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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-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=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

285
pkg/bootstraps/build.go Normal file
View file

@ -0,0 +1,285 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bootstraps
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/component/actionmanager"
"github.com/storyicon/powerproto/pkg/component/compilermanager"
"github.com/storyicon/powerproto/pkg/component/configmanager"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/concurrent"
"github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar"
)
// StepLookUpConfigs is used to lookup config files according to target proto files
func StepLookUpConfigs(
ctx context.Context,
targets []string,
configManager configmanager.ConfigManager,
) ([]configs.ConfigItem, error) {
progress := progressbar.GetProgressBar(len(targets))
progress.SetPrefix("Lookup configs of proto files")
var configItems []configs.ConfigItem
deduplicate := map[string]struct{}{}
for _, target := range targets {
cfg, err := configManager.GetConfig(ctx, target)
if err != nil {
return nil, err
}
if _, exists := deduplicate[cfg.ID()]; !exists {
configItems = append(configItems, cfg)
deduplicate[cfg.ID()] = struct{}{}
}
progress.SetSuffix("load %s", cfg.ID())
progress.Incr()
}
progress.SetSuffix("success!")
progress.Wait()
fmt.Printf("the following %d configurations will be used: \r\n", len(configItems))
for _, config := range configItems {
fmt.Printf(" %s \r\n", config.Path())
}
if len(configItems) == 0 {
return nil, errors.New("no config file matched, please check the scope of config file " +
"or use 'powerproto init' to create config file")
}
return configItems, nil
}
// StepInstallProtoc is used to install protoc
func StepInstallProtoc(ctx context.Context,
pluginManager pluginmanager.PluginManager,
configItems []configs.ConfigItem) error {
deduplicate := map[string]struct{}{}
for _, config := range configItems {
version := config.Config().Protoc
deduplicate[version] = struct{}{}
}
progress := progressbar.GetProgressBar(len(deduplicate))
progress.SetPrefix("Install protoc")
versionsMap := map[string]struct{}{}
for version := range deduplicate {
if version == "latest" {
progress.SetSuffix("query latest version of protoc")
latestVersion, err := pluginManager.GetProtocLatestVersion(ctx)
if err != nil {
return errors.Wrap(err, "failed to list protoc versions")
}
version = latestVersion
}
progress.SetSuffix("check cache of protoc %s", version)
exists, _, err := pluginManager.IsProtocInstalled(ctx, version)
if err != nil {
return err
}
if exists {
progress.SetSuffix("the %s version of protoc is already cached", version)
} else {
progress.SetSuffix("install %s version of protoc", version)
_, err = pluginManager.InstallProtoc(ctx, version)
if err != nil {
return err
}
progress.SetSuffix("the %s version of protoc is installed", version)
}
versionsMap[version] = struct{}{}
progress.Incr()
}
progress.Wait()
fmt.Println("the following versions of protoc will be used:", util.SetToSlice(versionsMap))
return nil
}
// StepInstallPlugins is used to install plugins
func StepInstallPlugins(ctx context.Context,
pluginManager pluginmanager.PluginManager,
configItems []configs.ConfigItem,
) error {
deduplicate := map[string]struct{}{}
for _, config := range configItems {
for _, pkg := range config.Config().Plugins {
deduplicate[pkg] = struct{}{}
}
}
progress := progressbar.GetProgressBar(len(deduplicate))
progress.SetPrefix("Install plugins")
pluginsMap := map[string]struct{}{}
for pkg := range deduplicate {
path, version, ok := util.SplitGoPackageVersion(pkg)
if !ok {
return errors.Errorf("invalid format: %s, should in path@version format", pkg)
}
if version == "latest" {
progress.SetSuffix("query latest version of %s", path)
data, err := pluginManager.GetPluginLatestVersion(ctx, path)
if err != nil {
return err
}
version = data
pkg = util.JoinGoPackageVersion(path, version)
}
progress.SetSuffix("check cache of %s", pkg)
exists, _, err := pluginManager.IsPluginInstalled(ctx, path, version)
if err != nil {
return err
}
if exists {
progress.SetSuffix("%s is cached", pkg)
} else {
progress.SetSuffix("installing %s", pkg)
_, err := pluginManager.InstallPlugin(ctx, path, version)
if err != nil {
panic(err)
}
progress.SetSuffix("%s installed", pkg)
}
pluginsMap[pkg] = struct{}{}
progress.Incr()
}
progress.SetSuffix("all plugins have been installed")
progress.Wait()
fmt.Println("the following plugins will be used:")
for pkg := range pluginsMap {
fmt.Printf(" %s\r\n", pkg)
}
return nil
}
// StepCompile is used to compile proto files
func StepCompile(ctx context.Context,
compilerManager compilermanager.CompilerManager,
targets []string,
) error {
progress := progressbar.GetProgressBar(len(targets))
progress.SetPrefix("Compile Proto Files")
c := concurrent.NewErrGroup(ctx, 10)
for _, target := range targets {
func(target string) {
c.Go(func(ctx context.Context) error {
progress.SetSuffix(target)
comp, err := compilerManager.GetCompiler(ctx, target)
if err != nil {
return err
}
if err := comp.Compile(ctx, target); err != nil {
return err
}
progress.Incr()
return nil
})
}(target)
}
if err := c.Wait(); err != nil {
return err
}
progress.Wait()
return nil
}
// StepPostAction is used to execute post actions
func StepPostAction(ctx context.Context,
actionsManager actionmanager.ActionManager,
configItems []configs.ConfigItem) error {
progress := progressbar.GetProgressBar(len(configItems))
progress.SetPrefix("PostAction")
for _, cfg := range configItems {
progress.SetSuffix(cfg.Path())
if err := actionsManager.ExecutePostAction(ctx, cfg); err != nil {
return err
}
progress.Incr()
}
progress.Wait()
return nil
}
// StepPostShell is used to execute post shell
func StepPostShell(ctx context.Context,
actionsManager actionmanager.ActionManager,
configItems []configs.ConfigItem) error {
progress := progressbar.GetProgressBar(len(configItems))
progress.SetPrefix("PostShell")
for _, cfg := range configItems {
progress.SetSuffix(cfg.Path())
if err := actionsManager.ExecutePostShell(ctx, cfg); err != nil {
return err
}
progress.Incr()
}
progress.Wait()
return nil
}
// Compile is used to compile proto files
func Compile(ctx context.Context, targets []string) error {
log := logger.NewDefault("compile")
log.SetLogLevel(logger.LevelError)
configManager, err := configmanager.NewConfigManager(log)
if err != nil {
return err
}
pluginManager, err := pluginmanager.NewPluginManager(pluginmanager.NewConfig(), log)
if err != nil {
return err
}
compilerManager, err := compilermanager.NewCompilerManager(ctx, log, configManager, pluginManager)
if err != nil {
return err
}
actionManager, err := actionmanager.NewActionManager(log)
if err != nil {
return err
}
configItems, err := StepLookUpConfigs(ctx, targets, configManager)
if err != nil {
return err
}
if err := StepInstallProtoc(ctx, pluginManager, configItems); err != nil {
return err
}
if err := StepInstallPlugins(ctx, pluginManager, configItems); err != nil {
return err
}
if err := StepCompile(ctx, compilerManager, targets); err != nil {
return err
}
if !command.IsDisableAction(ctx) {
if err := StepPostAction(ctx, actionManager, configItems); err != nil {
return err
}
if err := StepPostShell(ctx, actionManager, configItems); err != nil {
return err
}
} else {
log.LogWarn(nil, "PostAction and PostShell is skipped. If you need to allow execution, please append '-a' to command flags to enable")
}
log.LogInfo(nil, "Good job! you are ready to go :)")
return nil
}

118
pkg/bootstraps/tidy.go Normal file
View file

@ -0,0 +1,118 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package bootstraps
import (
"context"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/component/configmanager"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger"
"github.com/storyicon/powerproto/pkg/util/progressbar"
)
// StepTidyConfig is used to tidy configs by proto file targets
func StepTidyConfig(ctx context.Context, targets []string) error {
log := logger.NewDefault("tidy")
log.SetLogLevel(logger.LevelError)
configManager, err := configmanager.NewConfigManager(log)
if err != nil {
return err
}
pluginManager, err := pluginmanager.NewPluginManager(pluginmanager.NewConfig(), log)
if err != nil {
return err
}
configPaths := map[string]struct{}{}
for _, target := range targets {
cfg, err := configManager.GetConfig(ctx, target)
if err != nil {
return err
}
configPaths[cfg.Path()] = struct{}{}
}
if len(configPaths) == 0 {
return nil
}
progress := progressbar.GetProgressBar(len(configPaths))
progress.SetPrefix("tidy configs")
for path := range configPaths {
progress.SetSuffix(path)
err := StepTidyConfigFile(ctx, pluginManager, progress, path)
if err != nil {
return err
}
progress.Incr()
}
progress.SetSuffix("success!")
progress.Wait()
return nil
}
// StepTidyConfig is used clean config
// It will amend the 'latest' version to the latest version number in 'vx.y.z' format
func StepTidyConfigFile(ctx context.Context,
pluginManager pluginmanager.PluginManager,
progress progressbar.ProgressBar,
configFilePath string,
) error {
progress.SetSuffix("load config: %s", configFilePath)
configItems, err := configs.LoadConfigs(configFilePath)
if err != nil {
return err
}
var cleanable bool
for _, item := range configItems {
if item.Protoc == "latest" {
progress.SetSuffix("query latest version of protoc")
version, err := pluginManager.GetProtocLatestVersion(ctx)
if err != nil {
return err
}
item.Protoc = version
cleanable = true
}
for name, pkg := range item.Plugins {
path, version, ok := util.SplitGoPackageVersion(pkg)
if !ok {
return errors.Errorf("invalid package format: %s, should be in path@version format", pkg)
}
if version == "latest" {
progress.SetSuffix("query latest version of %s", path)
version, err := pluginManager.GetPluginLatestVersion(ctx, path)
if err != nil {
return err
}
item.Plugins[name] = util.JoinGoPackageVersion(path, version)
cleanable = true
}
}
}
if cleanable {
progress.SetSuffix("save %s", configFilePath)
if err := configs.SaveConfigs(configFilePath, configItems...); err != nil {
return err
}
}
progress.SetSuffix("config file tidied: %s", configFilePath)
return nil
}

View file

@ -0,0 +1,31 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"context"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// CommonOptions is common options of action
type CommonOptions struct {
ConfigFilePath string
}
// ActionFunc defines the common prototype of action func
type ActionFunc func(ctx context.Context,
log logger.Logger,
args []string, options *CommonOptions) error

View file

@ -0,0 +1,64 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"context"
"path/filepath"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ActionCopy is used to copy directory or file from src to dest
// Its args prototype is:
// args: (src string, dest string)
func ActionCopy(ctx context.Context, log logger.Logger, args []string, options *CommonOptions) error {
if len(args) != 2 || util.ContainsEmpty(args...) {
return errors.Errorf("expected length of args is 3, but received %d", len(args))
}
var (
source = args[0]
destination = args[1]
path = filepath.Dir(options.ConfigFilePath)
absSource = filepath.Join(path, source)
absDestination = filepath.Join(path, destination)
)
if filepath.IsAbs(source) {
return errors.Errorf("absolute source %s is not allowed in action move", source)
}
if filepath.IsAbs(destination) {
return errors.Errorf("absolute destination %s is not allowed in action move", destination)
}
if command.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{
"action": "copy",
"from": absSource,
"to": absDestination,
}, "DryRun")
return nil
}
if err := util.CopyDirectory(absSource, absDestination); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,68 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"context"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ActionMove is used to move directory or file from src to dest
// Its args prototype is:
// args: (src string, dest string)
func ActionMove(ctx context.Context, log logger.Logger, args []string, options *CommonOptions) error {
if len(args) != 2 || util.ContainsEmpty(args...) {
return errors.Errorf("expected length of args is 3, but received %d", len(args))
}
var (
source = args[0]
destination = args[1]
path = filepath.Dir(options.ConfigFilePath)
absSource = filepath.Join(path, source)
absDestination = filepath.Join(path, destination)
)
if filepath.IsAbs(source) {
return errors.Errorf("absolute source %s is not allowed in action move", source)
}
if filepath.IsAbs(destination) {
return errors.Errorf("absolute destination %s is not allowed in action move", destination)
}
if command.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{
"action": "move",
"from": absSource,
"to": absDestination,
}, "DryRun")
return nil
}
if err := util.CopyDirectory(absSource, absDestination); err != nil {
return err
}
if err := os.RemoveAll(absSource); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,51 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"context"
"os"
"path/filepath"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ActionRemove is used to delete directories or files
// Its args prototype is:
// args: (path ...string)
func ActionRemove(ctx context.Context, log logger.Logger, args []string, options *CommonOptions) error {
for _, arg := range args {
if filepath.IsAbs(arg) {
return errors.Errorf("absolute path %s is not allowed in action remove", arg)
}
path := filepath.Join(filepath.Dir(options.ConfigFilePath), arg)
if command.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{
"action": "remove",
"target": path,
}, "DryRun")
return nil
}
if err := os.RemoveAll(path); err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,80 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actions
import (
"bytes"
"context"
"io/fs"
"io/ioutil"
"path/filepath"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ActionReplace is used to replace text in bulk
// Its args prototype is:
// args: (pattern string, from string, to string)
// pattern is used to match files
func ActionReplace(ctx context.Context, log logger.Logger, args []string, options *CommonOptions) error {
if len(args) != 3 {
return errors.Errorf("expected length of args is 3, but received %d", len(args))
}
var (
pattern = args[0]
path = filepath.Dir(options.ConfigFilePath)
from = args[1]
to = args[2]
)
if pattern == "" || from == "" {
return errors.Errorf("pattern and from arguments in action replace can not be empty")
}
if filepath.IsAbs(pattern) {
return errors.Errorf("absolute path %s is not allowed in action replace", pattern)
}
pattern = filepath.Join(path, pattern)
return filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
if info.IsDir() {
return nil
}
matched, err := util.MatchPath(pattern, path)
if err != nil {
panic(err)
}
if matched {
if command.IsDryRun(ctx) {
log.LogInfo(map[string]interface{}{
"action": "replace",
"file": path,
"from": from,
"to": to,
}, "DryRun")
return nil
}
data, err := ioutil.ReadFile(path)
if err != nil {
return err
}
data = bytes.ReplaceAll(data, []byte(from), []byte(to))
return ioutil.WriteFile(path, data, fs.ModePerm)
}
return nil
})
}

View file

@ -0,0 +1,42 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actionmanager
import (
"fmt"
"github.com/storyicon/powerproto/pkg/util/command"
)
// ErrPostShell defines the post shell command error
type ErrPostShell struct {
Path string
*command.ErrCommandExec
}
// ErrPostAction defines the post action error
type ErrPostAction struct {
Path string
Name string
Arguments []string
Err error
}
// Error implements the standard error interface
func (err *ErrPostAction) Error() string {
return fmt.Sprintf("failed to execute action: %s, path: %s, arguments: %s, err: %s",
err.Name, err.Path, err.Arguments, err.Err,
)
}

View file

@ -0,0 +1,100 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package actionmanager
import (
"context"
"fmt"
"path/filepath"
"github.com/storyicon/powerproto/pkg/component/actionmanager/actions"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ActionManager is used to manage actions
type ActionManager interface {
// ExecutePostShell is used to execute post shell in config item
ExecutePostShell(ctx context.Context, config configs.ConfigItem) error
// ExecutePostAction is used to execute post action in config item
ExecutePostAction(ctx context.Context, config configs.ConfigItem) error
}
// BasicActionManager is a basic implement of ActionManager
type BasicActionManager struct {
logger.Logger
// map[string]ActionFunc
actions map[string]actions.ActionFunc
}
// NewActionManager is used to create action manager
func NewActionManager(log logger.Logger) (ActionManager, error) {
return NewBasicActionManager(log)
}
// NewBasicActionManager is used to create a BasicActionManager
func NewBasicActionManager(log logger.Logger) (*BasicActionManager, error) {
return &BasicActionManager{
Logger: log.NewLogger("actionmanager"),
actions: map[string]actions.ActionFunc{
"move": actions.ActionMove,
"replace": actions.ActionReplace,
"remove": actions.ActionRemove,
"copy": actions.ActionCopy,
},
}, nil
}
// ExecutePostShell is used to execute post shell in config item
func (m *BasicActionManager) ExecutePostShell(ctx context.Context, config configs.ConfigItem) error {
script := config.Config().PostShell
if script == "" {
return nil
}
dir := filepath.Dir(config.Path())
_, err := command.Execute(ctx, m.Logger, dir, "/bin/sh", []string{
"-c", script,
}, nil)
if err != nil {
return &ErrPostShell{
Path: config.Path(),
ErrCommandExec: err,
}
}
return nil
}
// ExecutePostAction is used to execute post action in config item
func (m *BasicActionManager) ExecutePostAction(ctx context.Context, config configs.ConfigItem) error {
for _, action := range config.Config().PostActions {
actionFunc, ok := m.actions[action.Name]
if !ok {
return fmt.Errorf("unknown action: %s", action.Name)
}
if err := actionFunc(ctx, m.Logger, action.Args, &actions.CommonOptions{
ConfigFilePath: config.Path(),
}); err != nil {
return &ErrPostAction{
Path: config.Path(),
Name: action.Name,
Arguments: action.Args,
Err: err,
}
}
}
return nil
}

View file

@ -0,0 +1,179 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compilermanager
import (
"context"
"fmt"
"path/filepath"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// Compiler is used to compile proto file
type Compiler interface {
// Compile is used to compile proto file
Compile(ctx context.Context, protoFilePath string) error
// GetConfig is used to return config that the compiler used
GetConfig(ctx context.Context) configs.ConfigItem
}
var _ Compiler = &BasicCompiler{}
// BasicCompiler is the basic implement of Compiler
type BasicCompiler struct {
logger.Logger
config configs.ConfigItem
pluginManager pluginmanager.PluginManager
protocPath string
arguments []string
dir string
}
// NewCompiler is used to create a compiler
func NewCompiler(
ctx context.Context,
log logger.Logger,
pluginManager pluginmanager.PluginManager,
config configs.ConfigItem,
) (Compiler, error) {
return NewBasicCompiler(ctx, log, pluginManager, config)
}
// NewBasicCompiler is used to create a basic compiler
func NewBasicCompiler(
ctx context.Context,
log logger.Logger,
pluginManager pluginmanager.PluginManager,
config configs.ConfigItem,
) (*BasicCompiler, error) {
basic := &BasicCompiler{
Logger: log.NewLogger("compiler"),
config: config,
pluginManager: pluginManager,
}
if err := basic.calcProto(ctx); err != nil {
return nil, err
}
if err := basic.calcArguments(ctx); err != nil {
return nil, err
}
if err := basic.calcDir(ctx); err != nil {
return nil, err
}
return basic, nil
}
// Compile is used to compile proto file
func (b *BasicCompiler) Compile(ctx context.Context, protoFilePath string) error {
arguments := make([]string, len(b.arguments))
copy(arguments, b.arguments)
arguments = append(arguments, protoFilePath)
_, err := command.Execute(ctx,
b.Logger, b.dir, b.protocPath, arguments, nil)
if err != nil {
return &ErrCompile{
ErrCommandExec: err,
}
}
return nil
}
// GetConfig is used to return config that the compiler used
func (b *BasicCompiler) GetConfig(ctx context.Context) configs.ConfigItem {
return b.config
}
func (b *BasicCompiler) calcDir(ctx context.Context) error {
if dir := b.config.Config().ProtocWorkDir; dir != "" {
dir = util.RenderPathWithEnv(dir)
if !filepath.IsAbs(dir) {
dir = filepath.Join(b.config.Path(), dir)
}
b.dir = dir
} else {
b.dir = filepath.Dir(b.config.Path())
}
return nil
}
func (b *BasicCompiler) calcProto(ctx context.Context) error {
cfg := b.config
protocVersion := cfg.Config().Protoc
if protocVersion == "latest" {
latestVersion, err := b.pluginManager.GetProtocLatestVersion(ctx)
if err != nil {
return err
}
protocVersion = latestVersion
}
localPath, err := b.pluginManager.InstallProtoc(ctx, protocVersion)
if err != nil {
return err
}
b.protocPath = localPath
return nil
}
func (b *BasicCompiler) calcArguments(ctx context.Context) error {
cfg := b.config
arguments := make([]string, len(cfg.Config().Options))
copy(arguments, cfg.Config().Options)
dir := filepath.Dir(cfg.Path())
// build import paths
for _, path := range cfg.Config().ImportPaths {
if path == consts.KeyPowerProtoInclude {
path = b.pluginManager.IncludePath(ctx)
}
path = util.RenderPathWithEnv(path)
if !filepath.IsAbs(path) {
path = filepath.Join(dir, path)
}
arguments = append(arguments, "--proto_path="+path)
}
// build plugin options
for name, pkg := range cfg.Config().Plugins {
path, version, ok := util.SplitGoPackageVersion(pkg)
if !ok {
return errors.Errorf("failed to parse: %s", pkg)
}
if version == "latest" {
latestVersion, err := b.pluginManager.GetPluginLatestVersion(ctx, path)
if err != nil {
return err
}
version = latestVersion
}
local, err := b.pluginManager.InstallPlugin(ctx, path, version)
if err != nil {
return errors.Wrap(err, "failed to get plugin path")
}
arg := fmt.Sprintf("--plugin=%s=%s", name, local)
arguments = append(arguments, arg)
}
b.arguments = arguments
return nil
}

View file

@ -0,0 +1,24 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compilermanager
import (
"github.com/storyicon/powerproto/pkg/util/command"
)
// ErrCompile defines the compile error
type ErrCompile struct {
*command.ErrCommandExec
}

View file

@ -0,0 +1,88 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package compilermanager
import (
"context"
"sync"
"github.com/storyicon/powerproto/pkg/component/configmanager"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// CompilerManager is to manage compiler
type CompilerManager interface {
// GetCompiler is used to get compiler of specified proto file path
GetCompiler(ctx context.Context, protoFilePath string) (Compiler, error)
}
// BasicCompilerManager is the basic implement of CompilerManager
type BasicCompilerManager struct {
logger.Logger
configManager configmanager.ConfigManager
pluginManager pluginmanager.PluginManager
tree map[string]Compiler
treeLock sync.RWMutex
}
var _ CompilerManager = &BasicCompilerManager{}
// NewCompilerManager is used to create CompilerManager
func NewCompilerManager(ctx context.Context,
log logger.Logger,
configManager configmanager.ConfigManager,
pluginManager pluginmanager.PluginManager,
) (CompilerManager, error) {
return NewBasicCompilerManager(ctx, log, configManager, pluginManager)
}
// BasicCompilerManager is used to create basic CompilerManager
func NewBasicCompilerManager(ctx context.Context,
log logger.Logger,
configManager configmanager.ConfigManager,
pluginManager pluginmanager.PluginManager,
) (*BasicCompilerManager, error) {
return &BasicCompilerManager{
Logger: log.NewLogger("compilermanager"),
configManager: configManager,
pluginManager: pluginManager,
tree: map[string]Compiler{},
}, nil
}
// GetCompiler is used to get compiler of specified proto file path
func (b *BasicCompilerManager) GetCompiler(ctx context.Context, protoFilePath string) (Compiler, error) {
config, err := b.configManager.GetConfig(ctx, protoFilePath)
if err != nil {
return nil, err
}
b.treeLock.Lock()
defer b.treeLock.Unlock()
c, ok := b.tree[config.Path()]
if ok {
return c, nil
}
compiler, err := NewCompiler(ctx, b.Logger, b.pluginManager, config)
if err != nil {
return nil, err
}
b.tree[config.ID()] = compiler
return compiler, nil
}

View file

@ -0,0 +1,103 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configmanager
import (
"context"
"os"
"path/filepath"
"strings"
"sync"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/configs"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ConfigManager is used to manage config
type ConfigManager interface {
// GetCompiler is used to get config of specified proto file path
GetConfig(ctx context.Context, protoFilePath string) (configs.ConfigItem, error)
}
// NewConfigManager is used to create ConfigManager
func NewConfigManager(log logger.Logger) (ConfigManager, error) {
return NewBasicConfigManager(log)
}
// BasicConfigManager is the basic implement of ConfigManager
type BasicConfigManager struct {
logger.Logger
tree map[string][]configs.ConfigItem
treeLock sync.RWMutex
}
// New is used to create a basic ConfigManager
func NewBasicConfigManager(log logger.Logger) (*BasicConfigManager, error) {
return &BasicConfigManager{
Logger: log.NewLogger("configmanager"),
tree: map[string][]configs.ConfigItem{},
}, nil
}
// GetCompiler is used to get config of specified proto file path
func (b *BasicConfigManager) GetConfig(ctx context.Context, protoFilePath string) (configs.ConfigItem, error) {
possiblePath := configs.ListConfigPaths(filepath.Dir(protoFilePath))
for _, configFilePath := range possiblePath {
items, err := b.loadConfig(configFilePath)
if err != nil {
return nil, err
}
dir := filepath.Dir(configFilePath)
for _, config := range items {
for _, scope := range config.Config().Scopes {
scopePath := filepath.Join(dir, scope)
if strings.Contains(protoFilePath, scopePath) {
return config, nil
}
}
}
}
return nil, errors.Errorf("unable to find config: %s", protoFilePath)
}
func (b *BasicConfigManager) loadConfig(configFilePath string) ([]configs.ConfigItem, error) {
b.treeLock.Lock()
defer b.treeLock.Unlock()
configItems, ok := b.tree[configFilePath]
if !ok {
fileInfo, err := os.Stat(configFilePath)
if err != nil {
if os.IsNotExist(err) {
b.tree[configFilePath] = nil
return nil, nil
}
return nil, err
}
if fileInfo.IsDir() {
b.tree[configFilePath] = nil
return nil, nil
}
data, err := configs.LoadConfigItems(configFilePath)
if err != nil {
return nil, errors.WithMessagef(err, "failed to decode: %s", configFilePath)
}
b.tree[configFilePath] = data
return data, nil
}
return configItems, nil
}

View file

@ -0,0 +1,43 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"fmt"
"github.com/storyicon/powerproto/pkg/util/command"
)
// ErrGoInstall defines the go install error
type ErrGoInstall struct {
*command.ErrCommandExec
}
// ErrGoList defines the go list error
type ErrGoList struct {
*command.ErrCommandExec
}
// ErrHTTPDownload defines the download error
type ErrHTTPDownload struct {
Url string
Err error
Code int
}
// Error implements the error interface
func (err *ErrHTTPDownload) Error() string {
return fmt.Sprintf("failed to download %s, code: %d, err: %s", err.Url, err.Code, err.Err)
}

View file

@ -0,0 +1,215 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"context"
"errors"
"io/fs"
"os"
"strings"
"sync"
"time"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger"
)
var defaultExecuteTimeout = time.Second * 60
// PluginManager is used to manage plugins
type PluginManager interface {
// GetPluginLatestVersion is used to get the latest version of plugin
GetPluginLatestVersion(ctx context.Context, path string) (string, error)
// ListPluginVersions is used to list the versions of plugin
ListPluginVersions(ctx context.Context, path string) ([]string, error)
// IsPluginInstalled is used to check whether the plugin is installed
IsPluginInstalled(ctx context.Context, path string, version string) (bool, string, error)
// InstallPlugin is used to install plugin
InstallPlugin(ctx context.Context, path string, version string) (local string, err error)
// GetProtocLatestVersion is used to geet the latest version of protoc
GetProtocLatestVersion(ctx context.Context) (string, error)
// ListProtocVersions is used to list protoc version
ListProtocVersions(ctx context.Context) ([]string, error)
// IsProtocInstalled is used to check whether the protoc is installed
IsProtocInstalled(ctx context.Context, version string) (bool, string, error)
// InstallProtoc is used to install protoc of specified version
InstallProtoc(ctx context.Context, version string) (local string, err error)
// IncludePath returns the default include path
IncludePath(ctx context.Context) string
}
// Config defines the config of PluginManager
type Config struct {
StorageDir string `json:"storage"`
}
// NewConfig is used to create config
func NewConfig() *Config {
return &Config{
StorageDir: consts.GetHomeDir(),
}
}
// NewPluginManager is used to create PluginManager
func NewPluginManager(cfg *Config, log logger.Logger) (PluginManager, error) {
return NewBasicPluginManager(cfg.StorageDir, log)
}
// BasicPluginManager is the basic implement of PluginManager
type BasicPluginManager struct {
logger.Logger
storageDir string
versions map[string][]string
versionsLock sync.RWMutex
}
// NewBasicPluginManager is used to create basic PluginManager
func NewBasicPluginManager(storageDir string, log logger.Logger) (*BasicPluginManager, error) {
return &BasicPluginManager{
Logger: log.NewLogger("pluginmanager"),
storageDir: storageDir,
versions: map[string][]string{},
}, nil
}
// GetPluginLatestVersion is used to get the latest version of plugin
func (b *BasicPluginManager) GetPluginLatestVersion(ctx context.Context, path string) (string, error) {
versions, err := b.ListPluginVersions(ctx, path)
if err != nil {
return "", err
}
if len(versions) == 0 {
return "", errors.New("no version list")
}
return versions[len(versions)-1], nil
}
// ListPluginVersions is used to list the versions of plugin
func (b *BasicPluginManager) ListPluginVersions(ctx context.Context, path string) ([]string, error) {
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
defer cancel()
b.versionsLock.RLock()
versions, ok := b.versions[path]
b.versionsLock.RUnlock()
if ok {
return versions, nil
}
versions, err := ListsGoPackageVersionsAmbiguously(ctx, b.Logger, path)
if err != nil {
return nil, err
}
b.versionsLock.Lock()
b.versions[path] = versions
b.versionsLock.Unlock()
return versions, nil
}
// IsPluginInstalled is used to check whether the plugin is installed
func (b *BasicPluginManager) IsPluginInstalled(ctx context.Context, path string, version string) (bool, string, error) {
return IsPluginInstalled(ctx, b.storageDir, path, version)
}
// InstallPlugin is used to install plugin
func (b *BasicPluginManager) InstallPlugin(ctx context.Context, path string, version string) (local string, err error) {
return InstallPluginUsingGo(ctx, b.Logger, b.storageDir, path, version)
}
// GetProtocLatestVersion is used to geet the latest version of protoc
func (b *BasicPluginManager) GetProtocLatestVersion(ctx context.Context) (string, error) {
versions, err := b.ListProtocVersions(ctx)
if err != nil {
return "", err
}
if len(versions) == 0 {
return "", errors.New("no version list")
}
return versions[len(versions)-1], nil
}
// ListProtocVersions is used to list protoc version
func (b *BasicPluginManager) ListProtocVersions(ctx context.Context) ([]string, error) {
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
defer cancel()
b.versionsLock.RLock()
versions, ok := b.versions["protoc"]
b.versionsLock.RUnlock()
if ok {
return versions, nil
}
versions, err := ListGitTags(ctx, b.Logger, consts.ProtobufRepository)
if err != nil {
return nil, err
}
b.versionsLock.Lock()
b.versions["protoc"] = versions
b.versionsLock.Unlock()
return versions, nil
}
// IsProtocInstalled is used to check whether the protoc is installed
func (b *BasicPluginManager) IsProtocInstalled(ctx context.Context, version string) (bool, string, error) {
if strings.HasPrefix(version, "v") {
version = strings.TrimPrefix(version, "v")
}
return IsProtocInstalled(ctx, b.storageDir, version)
}
// InstallProtoc is used to install protoc of specified version
func (b *BasicPluginManager) InstallProtoc(ctx context.Context, version string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, defaultExecuteTimeout)
defer cancel()
if strings.HasPrefix(version, "v") {
version = strings.TrimPrefix(version, "v")
}
local := PathForProtoc(b.storageDir, version)
exists, err := util.IsFileExists(local)
if err != nil {
return "", err
}
if exists {
return local, nil
}
release, err := GetProtocRelease(ctx, version)
if err != nil {
return "", err
}
defer release.Clear()
// merge include files
includeDir := PathForInclude(b.storageDir)
if err := util.CopyDirectory(release.GetIncludePath(), includeDir); err != nil {
return "", err
}
// download protoc file
if err := util.CopyFile(release.GetProtocPath(), local); err != nil {
return "", err
}
// * it is required on unix system
if err := os.Chmod(local, fs.ModePerm); err != nil {
return "", err
}
return local, nil
}
// IncludePath returns the default include path
func (b *BasicPluginManager) IncludePath(ctx context.Context) string {
return PathForInclude(b.storageDir)
}

View file

@ -0,0 +1,92 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"path"
"path/filepath"
"golang.org/x/mod/module"
"github.com/storyicon/powerproto/pkg/util"
)
// PathForInclude is used to get the local directory of include files
func PathForInclude(storageDir string) string {
return filepath.Join(storageDir, "include")
}
// PathForProtoc is used to get the local binary location where the specified version protoc should be stored
func PathForProtoc(storageDir string, version string) string {
return filepath.Join(storageDir, "protoc", version, util.GetBinaryFileName("protoc"))
}
// GetPluginPath is used to get the plugin path
func GetPluginPath(path string, version string) (string, error) {
enc, err := module.EscapePath(path)
if err != nil {
return "", err
}
encVer, err := module.EscapeVersion(version)
if err != nil {
return "", err
}
return filepath.Join(enc + "@" + encVer), nil
}
// PathForPluginDir is used to get the local directory where the specified version plug-in should be stored
func PathForPluginDir(storageDir string, path string, version string) (string, error) {
pluginPath, err := GetPluginPath(path, version)
if err != nil {
return "", err
}
return filepath.Join(storageDir, "plugins", pluginPath), nil
}
// PathForPlugin is used to get the binary path of plugin
func PathForPlugin(storageDir string, path string, version string) (string, error) {
name := GetGoPkgExecName(path)
dir, err := PathForPluginDir(storageDir, path, version)
if err != nil {
return "", err
}
return filepath.Join(dir, util.GetBinaryFileName(name)), nil
}
// isVersionElement reports whether s is a well-formed path version element:
// v2, v3, v10, etc, but not v0, v05, v1.
// `src\cmd\go\internal\load\pkg.go:1209`
func isVersionElement(s string) bool {
if len(s) < 2 || s[0] != 'v' || s[1] == '0' || s[1] == '1' && len(s) == 2 {
return false
}
for i := 1; i < len(s); i++ {
if s[i] < '0' || '9' < s[i] {
return false
}
}
return true
}
// GetGoPkgExecName is used to parse binary name from pkg uri
// `src\cmd\go\internal\load\pkg.go:1595`
func GetGoPkgExecName(pkgPath string) string {
_, elem := path.Split(pkgPath)
if elem != pkgPath && isVersionElement(elem) {
_, elem = path.Split(path.Dir(pkgPath))
}
return elem
}

View file

@ -0,0 +1,174 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"context"
"path/filepath"
"strings"
"time"
"github.com/hashicorp/go-multierror"
jsoniter "github.com/json-iterator/go"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// IsPluginInstalled is used to check whether a plugin is installed
func IsPluginInstalled(ctx context.Context,
storageDir string,
path string, version string) (bool, string, error) {
local, err := PathForPlugin(storageDir, path, version)
if err != nil {
return false, "", err
}
exists, err := util.IsFileExists(local)
if err != nil {
return false, "", err
}
if exists {
return true, local, nil
}
return false, "", nil
}
// InstallPluginUsingGo is used to install plugin using golang
func InstallPluginUsingGo(ctx context.Context,
log logger.Logger,
storageDir string,
path string, version string) (string, error) {
exists, local, err := IsPluginInstalled(ctx, storageDir, path, version)
if err != nil {
return "", err
}
if exists {
return local, nil
}
local, err = PathForPlugin(storageDir, path, version)
if err != nil {
return "", err
}
dir := filepath.Dir(local)
uri := util.JoinGoPackageVersion(path, version)
_, err2 := command.Execute(ctx, log, "", "go", []string{
"install", uri,
}, []string{"GOBIN=" + dir, "GO111MODULE=on"})
if err2 != nil {
return "", &ErrGoInstall{
ErrCommandExec: err2,
}
}
return local, nil
}
// ///////////////// Version Control /////////////////
// Module defines the model of go list data
type Module struct {
Path string // module path
Version string // module version
Versions []string // available module versions (with -versions)
Replace *Module // replaced by this module
Time *time.Time // time version was created
Update *Module // available update, if any (with -u)
Main bool // is this the main module?
Indirect bool // is this module only an indirect dependency of main module?
Dir string // directory holding files for this module, if any
GoMod string // path to go.mod file used when loading this module, if any
GoVersion string // go version used in module
Retracted string // retraction information, if any (with -retracted or -u)
Error *ModuleError // error loading module
}
// ModuleError defines the module error
type ModuleError struct {
Err string // the error itself
}
// ListGoPackageVersions is list go package versions
func ListGoPackageVersions(ctx context.Context, log logger.Logger, path string) ([]string, error) {
// query from latest version
// If latest is not specified here, the queried version
// may be restricted to the current project go.mod/go.sum
pkg := util.JoinGoPackageVersion(path, "latest")
data, err := command.Execute(ctx, log, "", "go", []string{
"list", "-m", "-json", "-versions", pkg,
}, []string{
"GO111MODULE=on",
})
if err != nil {
return nil, &ErrGoList{
ErrCommandExec: err,
}
}
var module Module
if err := jsoniter.Unmarshal(data, &module); err != nil {
return nil, err
}
if len(module.Versions) != 0 {
return module.Versions, nil
}
return []string{module.Version}, nil
}
// ListsGoPackageVersionsAmbiguously is used to list go package versions ambiguously
func ListsGoPackageVersionsAmbiguously(ctx context.Context, log logger.Logger, pkg string) ([]string, error) {
type Result struct {
err error
pkg string
versions []string
}
items := strings.Split(pkg, "/")
dataMap := make([]*Result, len(items))
notify := make(chan struct{}, 1)
maxIndex := len(items) - 1
for i := maxIndex; i >= 1; i-- {
go func(i int) {
pkg := strings.Join(items[0:i+1], "/")
versions, err := ListGoPackageVersions(context.TODO(), log, pkg)
dataMap[maxIndex-i] = &Result{
pkg: pkg,
versions: versions,
err: err,
}
notify <- struct{}{}
}(i)
}
OutLoop:
for {
select {
case <-notify:
var errs error
for _, data := range dataMap {
if data == nil {
continue OutLoop
}
if data.err != nil {
errs = multierror.Append(errs, data.err)
}
if data.versions != nil {
return data.versions, nil
}
}
return nil, errs
case <-ctx.Done():
return nil, ctx.Err()
}
}
}

View file

@ -0,0 +1,27 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestPluginmanager(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Pluginmanager Suite")
}

View file

@ -0,0 +1,73 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager_test
import (
"context"
"path/filepath"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/storyicon/powerproto/pkg/component/pluginmanager"
"github.com/storyicon/powerproto/pkg/util/logger"
)
var _ = Describe("Pluginmanager", func() {
cfg := pluginmanager.NewConfig()
cfg.StorageDir, _ = filepath.Abs("./tests")
const pluginPkg = "google.golang.org/protobuf/cmd/protoc-gen-go"
var manager pluginmanager.PluginManager
It("should able to init", func() {
pluginManager, err := pluginmanager.NewPluginManager(cfg, logger.NewDefault("pluginmanager"))
Expect(err).To(BeNil())
Expect(pluginManager).To(Not(BeNil()))
manager = pluginManager
})
It("should able to install protoc", func() {
versions, err := manager.ListProtocVersions(context.TODO())
Expect(err).To(BeNil())
Expect(len(versions) > 0).To(BeTrue())
latestVersion, err := manager.GetProtocLatestVersion(context.TODO())
Expect(err).To(BeNil())
Expect(latestVersion).To(Equal(versions[len(versions)-1]))
local, err := manager.InstallProtoc(context.TODO(), latestVersion)
Expect(err).To(BeNil())
exists, local, err := manager.IsProtocInstalled(context.TODO(), latestVersion)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
Expect(len(local) != 0).To(BeTrue())
})
It("should able to install plugin", func() {
versions, err := manager.ListPluginVersions(context.TODO(), pluginPkg)
Expect(err).To(BeNil())
Expect(len(versions) > 0).To(BeTrue())
latestVersion, err := manager.GetPluginLatestVersion(context.TODO(), pluginPkg)
Expect(err).To(BeNil())
Expect(latestVersion).To(Equal(versions[len(versions)-1]))
local, err := manager.InstallPlugin(context.TODO(), pluginPkg, latestVersion)
Expect(err).To(BeNil())
Expect(len(local) > 0).To(BeTrue())
exists, local, err := manager.IsPluginInstalled(context.TODO(), pluginPkg, latestVersion)
Expect(err).To(BeNil())
Expect(exists).To(BeTrue())
Expect(len(local) != 0).To(BeTrue())
})
})

View file

@ -0,0 +1,186 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pluginmanager
import (
"context"
"fmt"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/mholt/archiver"
"github.com/pkg/errors"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/command"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// ProtocRelease defines the release of protoc
type ProtocRelease struct {
workspace string
}
// GetIncludePath is used to get the include path
func (p *ProtocRelease) GetIncludePath() string {
return filepath.Join(p.workspace, "include")
}
// GetProtocPath is used to get the protoc path
func (p *ProtocRelease) GetProtocPath() string {
return filepath.Join(p.workspace, "bin", util.GetBinaryFileName("protoc"))
}
// Clear is used to clear the workspace
func (p *ProtocRelease) Clear() error {
return os.RemoveAll(p.workspace)
}
// GetProtocRelease is used to download protoc release
func GetProtocRelease(ctx context.Context, version string) (*ProtocRelease, error) {
workspace, err := os.MkdirTemp("", "")
if err != nil {
return nil, err
}
suffix, err := inferProtocReleaseSuffix()
if err != nil {
return nil, err
}
filename := fmt.Sprintf("protoc-%s-%s.zip", version, suffix)
url := fmt.Sprintf("https://github.com/protocolbuffers/protobuf/"+
"releases/download/v%s/%s", version, filename)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, &ErrHTTPDownload{
Url: url,
Err: err,
}
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, &ErrHTTPDownload{
Url: url,
Err: err,
}
}
zipFilePath := filepath.Join(workspace, filename)
if err := downloadFile(resp, zipFilePath); err != nil {
return nil, &ErrHTTPDownload{
Url: url,
Err: err,
Code: resp.StatusCode,
}
}
zip := archiver.NewZip()
if err := zip.Unarchive(zipFilePath, workspace); err != nil {
return nil, err
}
return &ProtocRelease{
workspace: workspace,
}, nil
}
// IsProtocInstalled is used to check whether the protoc version is installed
func IsProtocInstalled(ctx context.Context, storageDir string, version string) (bool, string, error) {
local := PathForProtoc(storageDir, version)
exists, err := util.IsFileExists(local)
if err != nil {
return false, "", err
}
return exists, local, nil
}
// ErrGitList defines the git list error
type ErrGitList struct {
*command.ErrCommandExec
}
// ListGitTags is used to list the git tags of specified repository
func ListGitTags(ctx context.Context, log logger.Logger, repo string) ([]string, error) {
data, err := command.Execute(ctx, log, "", "git", []string{
"ls-remote", "--tags", "--refs", "--sort", "version:refname", repo,
}, nil)
if err != nil {
return nil, &ErrGitList{
ErrCommandExec: err,
}
}
var tags []string
for _, line := range strings.Split(string(data), "\n") {
f := strings.Fields(line)
if len(f) != 2 {
continue
}
if strings.HasPrefix(f[1], "refs/tags/") {
tags = append(tags, strings.TrimPrefix(f[1], "refs/tags/"))
}
}
return tags, nil
}
func inferProtocReleaseSuffix() (string, error) {
goos := strings.ToLower(runtime.GOOS)
arch := strings.ToLower(runtime.GOARCH)
switch goos {
case "linux":
switch arch {
case "arm64":
return "linux-aarch_64", nil
case "ppc64le":
return "linux-ppcle_64", nil
case "s390x":
return "linux-s390_64", nil
case "386":
return "linux-x86_32", nil
case "amd64":
return "linux-x86_64", nil
}
case "darwin":
return "osx-x86_64", nil
case "windows":
switch arch {
case "386":
return "win32", nil
case "amd64":
return "win64", nil
}
}
return "", errors.New("protoc did not release on this platform")
}
func downloadFile(resp *http.Response, destination string) error {
if err := os.MkdirAll(filepath.Dir(destination), fs.ModePerm); err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.Errorf("unexpected code %d for url: %s", resp.StatusCode, resp.Request.URL.String())
}
file, err := os.OpenFile(destination, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fs.ModePerm)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
if err != nil {
return err
}
return nil
}

109
pkg/configs/configs.go Normal file
View file

@ -0,0 +1,109 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configs
import (
"bytes"
"io/fs"
"io/ioutil"
"path/filepath"
"gopkg.in/yaml.v3"
"github.com/storyicon/powerproto/pkg/consts"
"github.com/storyicon/powerproto/pkg/util"
)
// Config defines the config model
type Config struct {
Scopes []string `json:"scopes" yaml:"scopes"`
Protoc string `json:"protoc" yaml:"protoc"`
ProtocWorkDir string `json:"protocWorkDir" yaml:"protocWorkDir"`
Plugins map[string]string `json:"plugins" yaml:"plugins"`
Options []string `json:"options" yaml:"options"`
ImportPaths []string `json:"importPaths" yaml:"importPaths"`
PostActions []*PostAction `json:"postActions" yaml:"postActions"`
PostShell string `json:"postShell" yaml:"postShell"`
}
// PostAction defines the Action model
type PostAction struct {
Name string `json:"name" yaml:"name"`
Args []string `json:"args" yaml:"args"`
}
// SaveConfigs is used to save configs into files
func SaveConfigs(path string, configs ...*Config) error {
parts := make([][]byte, 0, len(configs))
for _, config := range configs {
data, err := yaml.Marshal(config)
if err != nil {
return err
}
parts = append(parts, data)
}
data := bytes.Join(parts, []byte("\r\n---\r\n"))
if err := ioutil.WriteFile(path, data, fs.ModePerm); err != nil {
return err
}
return nil
}
// LoadConfigs is used to load config from specified path
func LoadConfigs(path string) ([]*Config, error) {
raw, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
items, err := util.SplitYAML(raw)
if err != nil {
return nil, err
}
var ret []*Config
for _, item := range items {
var config Config
if err := yaml.Unmarshal(item, &config); err != nil {
return nil, err
}
ret = append(ret, &config)
}
return ret, nil
}
// Similar to LoadConfigs, but obtains the abstraction of the Config prototype structure
func LoadConfigItems(path string) ([]ConfigItem, error) {
data, err := LoadConfigs(path)
if err != nil {
return nil, err
}
return GetConfigItems(data, path), nil
}
// ListConfigPaths is used to list all possible config paths
func ListConfigPaths(sourceDir string) []string {
var paths []string
cur := sourceDir
for {
filePath := filepath.Join(cur, consts.ConfigFileName)
paths = append(paths, filePath)
next := filepath.Dir(cur)
if next == cur {
break
}
cur = next
}
paths = append(paths, consts.PathForGlobalConfig())
return paths
}

67
pkg/configs/item.go Normal file
View file

@ -0,0 +1,67 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package configs
import (
"fmt"
)
// ConfigItem is the wrapper of Config
type ConfigItem interface {
// ID is used to return to config unique id
ID() string
// Path is used to return the config path
Path() string
// Config is used to return the Config
Config() *Config
}
// GetConfigs is used to generate Config from given config entity
func GetConfigItems(data []*Config, path string) []ConfigItem {
ret := make([]ConfigItem, 0, len(data))
for i, item := range data {
ret = append(ret, newConfigItem(item, path, i))
}
return ret
}
type configItem struct {
id string
c *Config
path string
}
// ID is used to return to config unique id
func (c *configItem) ID() string {
return c.id
}
// Path is used to return the config path
func (c *configItem) Path() string {
return c.path
}
// Config is used to return the Config
func (c *configItem) Config() *Config {
return c.c
}
func newConfigItem(c *Config, path string, idx int) ConfigItem {
return &configItem{
id: fmt.Sprintf("%s:%d", path, idx),
c: c,
path: path,
}
}

69
pkg/consts/consts.go Normal file
View file

@ -0,0 +1,69 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package consts
import (
"os"
"path/filepath"
"github.com/storyicon/powerproto/pkg/util/logger"
)
// defines a set of const value
const (
// ConfigFileName defines the config file name
ConfigFileName = "powerproto.yaml"
// The default include can be referenced by this key in import paths
KeyPowerProtoInclude = "$POWERPROTO_INCLUDE"
// Defines the program directory of PowerProto, including various binary and include files
EnvHomeDir = "POWERPROTO_HOME"
// ProtobufRepository defines the protobuf repository
ProtobufRepository = "https://github.com/protocolbuffers/protobuf"
)
var homeDir string
var log = logger.NewDefault("consts")
// GetHomeDir is used to get cached homeDir
func GetHomeDir() string {
return homeDir
}
// PathForGlobalConfig is used to get path of global config
func PathForGlobalConfig() string {
return filepath.Join(GetHomeDir(), ConfigFileName)
}
func getHomeDir() (string, error) {
val := os.Getenv(EnvHomeDir)
if val != "" {
return filepath.Abs(val)
}
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".powerproto"), nil
}
func init() {
var err error
homeDir, err = getHomeDir()
if err != nil {
log.LogFatal(nil, "Please set the working directory of PowerProto by "+
"configuring the environment variable %s", EnvHomeDir)
}
}

109
pkg/util/command/command.go Normal file
View file

@ -0,0 +1,109 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package command
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"github.com/storyicon/powerproto/pkg/util"
"github.com/storyicon/powerproto/pkg/util/logger"
)
type dryRun struct{}
type ignoreDryRun struct{}
type disableAction struct{}
// WithIgnoreDryRun is used to inject ignore dryRun flag into context
func WithIgnoreDryRun(ctx context.Context) context.Context {
return context.WithValue(ctx, ignoreDryRun{}, "true")
}
// WithDisableAction is used to disable post action/shell
func WithDisableAction(ctx context.Context) context.Context {
return context.WithValue(ctx, disableAction{}, "true")
}
// WithDryRun is used to inject dryRun flag into context
func WithDryRun(ctx context.Context) context.Context {
return context.WithValue(ctx, dryRun{}, "true")
}
// IsDisableAction is used to decide whether to disable PostAction and PostShell
func IsDisableAction(ctx context.Context) bool {
return ctx.Value(disableAction{}) != nil
}
// IsIgnoreDryRun is used to determine whether it is currently in ignore DryRun mode
func IsIgnoreDryRun(ctx context.Context) bool {
return ctx.Value(ignoreDryRun{}) != nil
}
// IsDryRun is used to determine whether it is currently in DryRun mode
func IsDryRun(ctx context.Context) bool {
return ctx.Value(dryRun{}) != nil
}
// Execute is used to execute commands, return stdout and execute errors
func Execute(ctx context.Context,
log logger.Logger,
dir string, name string, arguments []string, env []string) ([]byte, *ErrCommandExec) {
cmd := exec.CommandContext(ctx, name, arguments...)
cmd.Env = append(os.Environ(), env...)
cmd.Dir = dir
if IsDryRun(ctx) && !IsIgnoreDryRun(ctx) {
log.LogInfo(map[string]interface{}{
"command": cmd.String(),
"dir": cmd.Dir,
}, "DryRun")
return nil, nil
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return nil, &ErrCommandExec{
Err: err,
Dir: cmd.Dir,
Command: cmd.String(),
ExitCode: util.GetExitCode(err),
Stdout: stdout.String(),
Stderr: stderr.String(),
}
}
return stdout.Bytes(), nil
}
// ErrCommandExec defines the command error
type ErrCommandExec struct {
Err error
Dir string
Command string
ExitCode int
Stdout string
Stderr string
}
// Error implements the error interface
func (err *ErrCommandExec) Error() string {
return fmt.Sprintf("failed to execute %s in %s, stderr: %s, exit code %d, %s",
err.Command, err.Dir, err.Stderr, err.ExitCode, err.Err,
)
}

View file

@ -0,0 +1,27 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package command_test
import (
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestCommand(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Command Suite")
}

View file

@ -0,0 +1,38 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package command_test
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/storyicon/powerproto/pkg/util/command"
)
var _ = Describe("Command", func() {
ctx := context.Background()
It("should able to dryRun", func() {
Expect(command.IsDryRun(ctx)).To(BeFalse())
ctx = command.WithDryRun(ctx)
Expect(command.IsDryRun(ctx)).To(BeTrue())
})
It("should able to ignore dryRun", func() {
Expect(command.IsIgnoreDryRun(ctx)).To(BeFalse())
ctx = command.WithIgnoreDryRun(ctx)
Expect(command.IsIgnoreDryRun(ctx)).To(BeTrue())
})
})

View file

@ -0,0 +1,38 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package concurrent
import (
"io"
"sync"
)
// Buffer is goroutine safety buffer
type Buffer struct {
writer io.Writer
mutex sync.Mutex
}
// Write implements the io.Writer
func (s *Buffer) Write(p []byte) (n int, err error) {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.writer.Write(p)
}
// NewBuffer is used to create a new buffer
func NewBuffer(writer io.Writer) io.Writer {
return &Buffer{writer: writer}
}

View file

@ -0,0 +1,72 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package concurrent
import (
"context"
"sync"
)
// ErrGroup is another ErrGroup implement
type ErrGroup struct {
ctx context.Context
cancel context.CancelFunc
limit chan struct{}
errOnce sync.Once
err error
wg sync.WaitGroup
}
// NewErrGroup is used to create a new ErrGroup
func NewErrGroup(ctx context.Context, concurrency int) *ErrGroup {
ctx, cancel := context.WithCancel(ctx)
return &ErrGroup{
ctx: ctx,
cancel: cancel,
limit: make(chan struct{}, concurrency),
}
}
// Wait is used to wait ErrGroup finish
func (g *ErrGroup) Wait() error {
g.wg.Wait()
g.cancel()
return g.err
}
// Go is used to start a new goroutine
func (g *ErrGroup) Go(f func(ctx context.Context) error) {
if g.err != nil {
return
}
g.limit <- struct{}{}
g.wg.Add(1)
go func() {
defer func() {
<-g.limit
g.wg.Done()
}()
if err := f(g.ctx); err != nil {
g.cancel()
g.errOnce.Do(func() {
g.err = err
g.cancel()
})
}
}()
}

131
pkg/util/file.go Normal file
View file

@ -0,0 +1,131 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"io"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"github.com/bmatcuk/doublestar"
filecopy "github.com/otiai10/copy"
"github.com/pkg/errors"
)
// MatchPath is used to match path with specified pattern
func MatchPath(pattern string, path string) (bool, error) {
return doublestar.PathMatch(pattern, path)
}
// CopyDirectory is used to copy directory
// If dst already exists, it will be merged
func CopyDirectory(src, dst string) error {
return filecopy.Copy(src, dst)
}
// CopyFile is used to copy file from src to dst
func CopyFile(src, dst string) error {
sourceFileStat, err := os.Stat(src)
if err != nil {
return err
}
if !sourceFileStat.Mode().IsRegular() {
return errors.Errorf("%s is not a regular file", src)
}
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
if err := os.MkdirAll(filepath.Dir(dst), fs.ModePerm); err != nil {
return err
}
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
_, err = io.Copy(destination, source)
return err
}
// IsFileExists is used to check whether the file exists
func IsFileExists(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
if info.IsDir() {
return false, errors.Errorf("%s is not a file", path)
}
return true, nil
}
// IsDirExists is used to check whether the dir exists
func IsDirExists(path string) (bool, error) {
info, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
if !info.IsDir() {
return false, errors.Errorf("%s is not a directory", path)
}
return true, nil
}
// GetFilesWithExtRecursively is used to recursively list files with a specific suffix
// expectExt should contain the prefix '.'
func GetFilesWithExtRecursively(target string, targetExt string) ([]string, error) {
var data []string
err := filepath.Walk(target, func(path string, info fs.FileInfo, err error) error {
if info.IsDir() {
return nil
}
ext := filepath.Ext(path)
if ext == targetExt {
data = append(data, path)
}
return nil
})
return data, err
}
// GetFilesWithExtRecursively is used to list files with a specific suffix
// expectExt should contain the prefix '.'
func GetFilesWithExt(dir string, targetExt string) ([]string, error) {
children, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}
var data []string
for _, child := range children {
if child.IsDir() {
continue
}
if ext := filepath.Ext(child.Name()); ext != targetExt {
continue
}
data = append(data, filepath.Join(dir, child.Name()))
}
return data, nil
}

217
pkg/util/logger/logger.go Normal file
View file

@ -0,0 +1,217 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package logger
import (
"fmt"
"io"
"os"
"sort"
"strings"
"github.com/fatih/color"
jsoniter "github.com/json-iterator/go"
"github.com/prometheus/client_golang/prometheus"
)
// Logger defines the basic log library implementation
type Logger interface {
// LogDebug print a message with debug level.
LogDebug(fields map[string]interface{}, format string, args ...interface{})
// LogInfo print a message with info level.
LogInfo(fields map[string]interface{}, format string, args ...interface{})
// LogWarn print a message with warn level.
LogWarn(fields map[string]interface{}, format string, args ...interface{})
// LogError print a message with error level.
LogError(fields map[string]interface{}, format string, args ...interface{})
// LogFatal print a message with fatal level.
LogFatal(fields map[string]interface{}, format string, args ...interface{})
// NewLogger is used to derive a new child Logger
NewLogger(component string) Logger
// SetLogLevel is used to set log level
SetLogLevel(level Level) Logger
}
// BasicLogger simply implements Logger
type BasicLogger struct {
cfg *Config
component string
registerer prometheus.Registerer
}
// Config defines the config structure
type Config struct {
Pretty bool
Level Level
}
type Level struct {
Name string
Color color.Attribute
Index int
Writer io.Writer
}
var (
LevelDebug = Level{Name: "debug", Color: color.FgWhite, Index: 0, Writer: os.Stdout}
LevelInfo = Level{Name: "info", Color: color.FgWhite, Index: 1, Writer: os.Stdout}
LevelWarn = Level{Name: "warn", Color: color.FgYellow, Index: 2, Writer: os.Stderr}
LevelError = Level{Name: "error", Color: color.FgHiRed, Index: 3, Writer: os.Stderr}
LevelFatal = Level{Name: "fatal", Color: color.FgRed, Index: 4, Writer: os.Stderr}
)
// NewConfig is used to init config with default values
func NewConfig() *Config {
return &Config{
Pretty: false,
Level: LevelDebug,
}
}
// NewDefault is used to initialize a simple Logger
func NewDefault(component string) Logger {
logger, err := New(NewConfig(), component, prometheus.DefaultRegisterer)
if err != nil {
panic(err)
}
return logger
}
// New is used to init service
func New(cfg *Config, component string, registerer prometheus.Registerer) (Logger, error) {
if cfg == nil {
cfg = NewConfig()
}
service := &BasicLogger{
cfg: cfg,
component: component,
registerer: registerer,
}
return service, nil
}
// LogDebug print a message with debug level.
func (b *BasicLogger) LogDebug(fields map[string]interface{}, format string, args ...interface{}) {
b.log(LevelDebug, fields, format, args...)
}
// LogInfo print a message with info level.
func (b *BasicLogger) LogInfo(fields map[string]interface{}, format string, args ...interface{}) {
b.log(LevelInfo, fields, format, args...)
}
// LogWarn print a message with warn level.
func (b *BasicLogger) LogWarn(fields map[string]interface{}, format string, args ...interface{}) {
b.log(LevelWarn, fields, format, args...)
}
// LogError print a message with error level.
func (b *BasicLogger) LogError(fields map[string]interface{}, format string, args ...interface{}) {
b.log(LevelError, fields, format, args...)
}
// LogFatal print a message with fatal level.
func (b *BasicLogger) LogFatal(fields map[string]interface{}, format string, args ...interface{}) {
b.log(LevelFatal, fields, format, args...)
}
// NewLogger is used to derive a new child Logger
func (b *BasicLogger) NewLogger(component string) Logger {
name := strings.Join([]string{b.component, component}, ".")
logger, err := New(b.cfg, name, b.registerer)
if err != nil {
b.LogWarn(map[string]interface{}{
"name": name,
}, "failed to extend logger: %s", err)
return b
}
return logger
}
// SetLogLevel is used to set log level
func (b *BasicLogger) SetLogLevel(level Level) Logger {
b.cfg.Level = level
return b
}
func (b *BasicLogger) log(level Level, fields map[string]interface{}, format string, args ...interface{}) {
if b.cfg.Level.Index > level.Index {
return
}
if fields == nil {
fields = map[string]interface{}{}
}
// if b.cfg.Level == LevelDebug {
// if _, file, line, ok := runtime.Caller(4); ok {
// fields["file"] = fmt.Sprintf("%s:%d", path.Base(file), line)
// }
// if b.component != "" {
// fields["component"] = b.component
// }
// }
if b.cfg.Pretty {
dict := map[string]interface{}{}
for key, val := range fields {
dict[key] = val
}
dict["level"] = level.Name
dict["message"] = fmt.Sprintf(format, args...)
_ = jsoniter.NewEncoder(level.Writer).Encode(dict)
return
}
var buf []byte
buf = appendString(buf, fmt.Sprintf(format, args...))
if len(fields) > 0 {
buf = appendTab(buf)
buf = appendFields(buf, fields)
}
buf = appendLF(buf)
fmt.Fprintf(level.Writer, color.New(level.Color).Sprint(string(buf)))
if level == LevelFatal {
os.Exit(1)
}
}
func appendFields(dst []byte, fields map[string]interface{}) []byte {
keys := make([]string, 0, len(fields))
for key := range fields {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
dst = appendField(dst, key, fields[key])
dst = appendTab(dst)
}
return dst
}
func appendTab(dst []byte) []byte {
return append(dst, ' ')
}
func appendField(dst []byte, key string, val interface{}) []byte {
dst = appendString(dst, fmt.Sprintf("%s=%v", key, val))
return dst
}
func appendString(dst []byte, s string) []byte {
dst = append(dst, []byte(s)...)
return dst
}
func appendLF(dst []byte) []byte {
return append(dst, '\n')
}

View file

@ -0,0 +1,123 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package progressbar
import (
"fmt"
"github.com/fatih/color"
"github.com/vbauerster/mpb/v7"
"github.com/vbauerster/mpb/v7/decor"
)
// ProgressBar implements a customizable progress bar
type ProgressBar interface {
// Incr is used to increase progress
Incr()
// Wait is used to wait for the rendering of the progress bar to complete
Wait()
// SetPrefix is used to set the prefix of progress bar
SetPrefix(format string, args ...interface{})
// SetSuffix is used to set the suffix of progress bar
SetSuffix(format string, args ...interface{})
}
type progressBar struct {
container *mpb.Progress
bar *mpb.Bar
prefix string
suffix string
}
// SetPrefix is used to set the prefix of progress bar
func (s *progressBar) SetPrefix(format string, args ...interface{}) {
s.prefix = fmt.Sprintf(format, args...)
}
// SetSuffix is used to set the suffix of progress bar
func (s *progressBar) SetSuffix(format string, args ...interface{}) {
s.suffix = fmt.Sprintf(format, args...)
}
func newEmbedProgressBar(container *mpb.Progress, bar *mpb.Bar) *progressBar {
return &progressBar{
container: container,
bar: bar,
}
}
func (s *progressBar) Incr() {
s.bar.Increment()
}
func (s *progressBar) Wait() {
s.container.Wait()
}
func getSpinner() []string {
activeState := "[ " + color.GreenString("●") + " ] "
defaultState := "[ ] "
return []string{
activeState,
activeState,
activeState,
defaultState,
defaultState,
defaultState,
}
}
// GetProgressBar is used to get progress bar
func GetProgressBar(count int) ProgressBar {
var progressBar *progressBar
container := mpb.New()
bar := container.Add(int64(count),
mpb.NewBarFiller(mpb.BarStyle().Lbound("[").
Filler(color.GreenString("=")).
Tip(color.GreenString(">")).Padding(" ").Rbound("]")),
mpb.PrependDecorators(
func() decor.Decorator {
frames := getSpinner()
var count uint
return decor.Any(func(statistics decor.Statistics) string {
if statistics.Completed {
return frames[0]
}
frame := frames[count%uint(len(frames))]
count++
return frame
})
}(),
decor.Any(func(statistics decor.Statistics) string {
if progressBar != nil {
return progressBar.prefix
}
return ""
}),
),
mpb.AppendDecorators(
decor.NewPercentage("%d "),
decor.Any(func(statistics decor.Statistics) string {
if progressBar != nil {
return fmt.Sprintf("(%d/%d) %s", statistics.Current, count, progressBar.suffix)
}
return ""
}),
),
mpb.BarWidth(15),
)
progressBar = newEmbedProgressBar(container, bar)
return progressBar
}

106
pkg/util/util.go Normal file
View file

@ -0,0 +1,106 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"syscall"
)
// ContainsEmpty is used to check whether items contains empty string
func ContainsEmpty(items ...string) bool {
for _, item := range items {
if item == "" {
return true
}
}
return false
}
// SetToSlice is used to convert set<string> to slice<string>
func SetToSlice(set map[string]struct{}) []string {
data := make([]string, 0, len(set))
for key := range set {
data = append(data, key)
}
return data
}
// GetMapKeys is used to get the keys of map
func GetMapKeys(dict map[string]string) []string {
data := make([]string, 0, len(dict))
for key := range dict {
data = append(data, key)
}
return data
}
// GetExitCode is used to parse exit code from cmd error
func GetExitCode(err error) int {
if exitErr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
return status.ExitStatus()
}
}
return 1
}
var regexpEnvironmentVar = regexp.MustCompile(`\$[A-Za-z_]+`)
// RenderPathWithEnv is used to render path with environment
func RenderPathWithEnv(path string) string {
matches := regexpEnvironmentVar.FindAllString(path, -1)
for _, match := range matches {
path = strings.ReplaceAll(path, match, os.Getenv(match[1:]))
}
return filepath.Clean(path)
}
// SplitGoPackageVersion is used to split go package version
func SplitGoPackageVersion(pkg string) (path string, version string, ok bool) {
i := strings.Index(pkg, "@")
if i == -1 {
return "", "", false
}
return pkg[:i], pkg[i+1:], true
}
// JoinGoPackageVersion is used to join go path and versions
func JoinGoPackageVersion(path, version string) string {
return strings.Join([]string{
path, version,
}, "@")
}
// GetBinaryFileName is used to get os based binary file name
func GetBinaryFileName(name string) string {
if runtime.GOOS == "windows" {
if !strings.HasSuffix(name, ".exe") {
return name + ".exe"
}
return name
}
return name
}

74
pkg/util/yaml.go Normal file
View file

@ -0,0 +1,74 @@
// Copyright 2021 storyicon@foxmail.com
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/hashicorp/go-multierror"
"gopkg.in/yaml.v2"
)
// SplitYAML is used to split yaml
func SplitYAML(data []byte) ([][]byte, error) {
decoder := yaml.NewDecoder(bytes.NewReader(data))
var parts [][]byte
for {
var value interface{}
err := decoder.Decode(&value)
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
part, err := yaml.Marshal(value)
if err != nil {
return nil, err
}
parts = append(parts, part)
}
return parts, nil
}
// DumpYaml is used to dump yaml into stdout
func DumpYaml(cfg interface{}) {
out, err := yaml.Marshal(cfg)
if err != nil {
fmt.Fprintln(os.Stderr, err)
} else {
fmt.Printf("%s\n", out)
}
}
// LoadConfig read YAML-formatted config from filename into cfg.
func LoadConfig(filename string, pointer interface{}) error {
buf, err := ioutil.ReadFile(filename)
if err != nil {
return multierror.Prefix(err, "Error reading config file")
}
err = yaml.UnmarshalStrict(buf, pointer)
if err != nil {
return multierror.Prefix(err, "Error parsing config file")
}
return nil
}