├── .github └── workflows │ └── publish.yml ├── .gitignore ├── Makefile ├── README.md ├── apps ├── autoindex │ ├── app.yml │ ├── docker-compose.yml │ └── icon.png ├── awtrix2 │ ├── app.yml │ ├── docker-compose.yml │ └── icon.png ├── motivation │ ├── app.yml │ ├── docker-compose.yml │ └── icon.png ├── samba │ ├── app.yml │ └── docker-compose.yml └── yarr │ ├── app.yml │ └── docker-compose.yml ├── cli ├── cmd │ ├── convert │ │ └── convert.go │ └── generate │ │ └── generate.go ├── converter │ └── convert.go ├── modules │ ├── app │ │ ├── context.go │ │ ├── loader.go │ │ └── loader_plugins.go │ ├── docker-compose │ │ ├── codec.go │ │ └── compose.go │ ├── icon │ │ └── icon.go │ ├── portainer │ │ └── generater.go │ ├── unraid │ │ └── source.go │ └── yacht │ │ └── yacht.go ├── pipe │ ├── context.go │ └── pipe.go ├── project │ ├── application.go │ ├── codec.go │ └── project.go ├── shctl.go └── wasm │ └── wasm.go ├── config.unraid.yml ├── config.yml ├── go.mod └── go.sum /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | deploy: 8 | name: Deploy 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Set up Go 1.15 12 | uses: actions/setup-go@v1 13 | with: 14 | go-version: 1.15 15 | id: go 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | - name: Cache gomod 19 | uses: actions/cache@v1 20 | with: 21 | path: ~/go/pkg/mod 22 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 23 | restore-keys: | 24 | ${{ runner.os }}-go- 25 | - name: Build 26 | run: | 27 | go build cli/shctl.go 28 | make update_unraid 29 | make generate 30 | - name: Deploy to GitHub Pages 31 | uses: JamesIves/github-pages-deploy-action@3.7.1 32 | with: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | BRANCH: gh-pages 35 | FOLDER: dist 36 | CLEAN: true 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | dist -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | update_unraid: 2 | @mkdir -p build 3 | wget https://raw.githubusercontent.com/Squidly271/AppFeed/master/applicationFeed.json -O build/applicationFeed.json 4 | 5 | clean: 6 | rm -rf dist/* 7 | 8 | generate: 9 | mkdir -p dist 10 | ./shctl generate 11 | ./shctl generate -c config.unraid.yml 12 | 13 | wasm: 14 | GOOS=js GOARCH=wasm go build -o shs.wasm ./cli/wasm 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selfhosted Store 2 | 3 | Multi-platform selfhosted services templates generated from `docker-compose.yml`. 4 | 5 | ## Features 6 | 7 | Nothing completed yet. 8 | 9 | - [ ] Web selfhosted application store generater. 10 | - [ ] Dcoker compose file converter, as a go and wasm module. 11 | - [ ] Convert some popular selfhosted application template to other format. 12 | - [x] Unraid Community Applications 13 | 14 | ## Templates 15 | 16 | This repository provides two applications stores: 17 | 18 | - `Apps`: Generated form path `./apps` in this repository. 19 | - `Unraid`: Generated form [Unraid Community Applications](https://github.com/Squidly271/AppFeed). 20 | 21 | ### Templates Address 22 | 23 | | Store | Format | URL | 24 | | --- | --- | --- | 25 | | Apps | Yacht | https://yangkghjh.github.io/selfhosted_store/apps/templates/yacht/yacht.json | 26 | | Apps | Portainer | https://yangkghjh.github.io/selfhosted_store/apps/templates/portainer/template.json | 27 | | Unraid | Yacht | https://yangkghjh.github.io/selfhosted_store/unraid/templates/yacht/yacht.json | 28 | | Unraid | Portainer | https://yangkghjh.github.io/selfhosted_store/unraid/templates/portainer/template.json | 29 | 30 | ## Plans 31 | 32 | - [x] Generate from `Unraid Community Applications` 33 | - [x] Portainer 2.0 template format 34 | - [ ] Unriad template format 35 | - [ ] App store site 36 | - [ ] Provide `docker run` command for apps 37 | - [ ] Multi services support 38 | - [ ] Kubernates deployment support 39 | - [ ] Synology docker app template 40 | -------------------------------------------------------------------------------- /apps/autoindex/app.yml: -------------------------------------------------------------------------------- 1 | type: container 2 | name: AutoIndex 3 | description: Lightweight go web server that provides a searchable directory index. Optimized for handling large numbers of files (100k+) and remote file systems (with high latency) through a continously updated directory cache. 4 | platform: linux 5 | -------------------------------------------------------------------------------- /apps/autoindex/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | autoindex: 4 | container_name: autoindex 5 | image: yangkghjh/autoindex:latest 6 | ports: 7 | - 4000:4000 8 | restart: always 9 | volumes: 10 | - "/data:/data" 11 | -------------------------------------------------------------------------------- /apps/autoindex/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkghjh/selfhosted_store/4818cdbbced2be4ba6077207fd83f47e614a8a0e/apps/autoindex/icon.png -------------------------------------------------------------------------------- /apps/awtrix2/app.yml: -------------------------------------------------------------------------------- 1 | type: container 2 | name: AWTRIX2 3 | description: (AWsome maTRIX) is a full color dot matrix that displays applications from simple time display to Fortnite account statistics. 4 | platform: linux 5 | -------------------------------------------------------------------------------- /apps/awtrix2/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | awtrix2: 4 | container_name: awtrix2 5 | image: whyet/awtrix2:latest 6 | ports: 7 | - 7000:7000 8 | - 7001:7001 9 | - 5568:5568/udp 10 | restart: always 11 | volumes: 12 | - "!data/awtrix2:/data" 13 | -------------------------------------------------------------------------------- /apps/awtrix2/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkghjh/selfhosted_store/4818cdbbced2be4ba6077207fd83f47e614a8a0e/apps/awtrix2/icon.png -------------------------------------------------------------------------------- /apps/motivation/app.yml: -------------------------------------------------------------------------------- 1 | type: container 2 | name: Motivation 3 | description: A web page with your age. 4 | platform: linux 5 | -------------------------------------------------------------------------------- /apps/motivation/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.0" 2 | 3 | services: 4 | motivation: 5 | container_name: motivation 6 | image: yangkghjh/motivation:latest 7 | restart: unless-stopped 8 | ports: 9 | - 80:80 -------------------------------------------------------------------------------- /apps/motivation/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yangkghjh/selfhosted_store/4818cdbbced2be4ba6077207fd83f47e614a8a0e/apps/motivation/icon.png -------------------------------------------------------------------------------- /apps/samba/app.yml: -------------------------------------------------------------------------------- 1 | type: container 2 | name: Samba 3 | description: Since 1992, Samba has provided secure, stable and fast file and print services for all clients using the SMB/CIFS protocol, such as all versions of DOS and Windows, OS/2, Linux and many others. 4 | categories: 5 | - Files 6 | platform: linux 7 | note: "Open with \\\\IP\\yacht ,Iamges: https://hub.docker.com/r/dperson/samba" 8 | -------------------------------------------------------------------------------- /apps/samba/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | samba: 4 | container_name: samba 5 | image: dperson/samba 6 | ports: 7 | - 139:139/tcp 8 | - 445:445/tcp 9 | restart: always 10 | volumes: 11 | - "/yacht:/mount" 12 | environment: 13 | - SHARE=yacht;/mount 14 | -------------------------------------------------------------------------------- /apps/yarr/app.yml: -------------------------------------------------------------------------------- 1 | type: container 2 | name: Yarr 3 | description: 开源 RSS 阅读器,Go 实现,数据存储于 SQLite。 4 | categories: 5 | - Read 6 | platform: linux 7 | note: "通过 IP:7070 打开。" 8 | -------------------------------------------------------------------------------- /apps/yarr/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | yarr: 4 | container_name: yarr 5 | image: yangkghjh/yarr:latest 6 | ports: 7 | - 7070:7070/tcp # WebUI 8 | restart: always 9 | volumes: 10 | - "!data/yarr:/data" 11 | -------------------------------------------------------------------------------- /cli/cmd/convert/convert.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/yankghjh/selfhosted_store/cli/converter" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var ( 13 | // Command for convert 14 | Command = &cobra.Command{ 15 | Use: "convert", 16 | Short: "convert format between compose files and templates", 17 | Run: run, 18 | } 19 | input string 20 | from string 21 | to string 22 | ) 23 | 24 | func init() { 25 | Command.Flags().StringVarP(&input, "input", "i", "docker-compose.yml", "input file") 26 | Command.Flags().StringVarP(&from, "from", "f", "docker-compose", "source format") 27 | Command.Flags().StringVarP(&to, "to", "t", "docker-compose", "target format") 28 | } 29 | 30 | func run(cmd *cobra.Command, args []string) { 31 | payload, err := ioutil.ReadFile(input) 32 | if err != nil { 33 | fmt.Printf("read input file %s error: %s\n", input, err.Error()) 34 | return 35 | } 36 | 37 | output, err := converter.Convert(from, to, payload) 38 | if err != nil { 39 | fmt.Printf("convert error: %s\n", err.Error()) 40 | return 41 | } 42 | 43 | fmt.Println(string(output)) 44 | } 45 | -------------------------------------------------------------------------------- /cli/cmd/generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/spf13/cobra" 8 | "github.com/spf13/viper" 9 | 10 | "github.com/yankghjh/selfhosted_store/cli/project" 11 | 12 | // modules 13 | _ "github.com/yankghjh/selfhosted_store/cli/modules/app" 14 | _ "github.com/yankghjh/selfhosted_store/cli/modules/portainer" 15 | _ "github.com/yankghjh/selfhosted_store/cli/modules/unraid" 16 | _ "github.com/yankghjh/selfhosted_store/cli/modules/yacht" 17 | ) 18 | 19 | var ( 20 | // Command for generate 21 | Command = &cobra.Command{ 22 | Use: "generate", 23 | Short: "generate site and templates by configure file", 24 | Run: run, 25 | } 26 | cfgFile string 27 | cfg *viper.Viper 28 | ) 29 | 30 | func init() { 31 | cfg = viper.New() 32 | Command.PersistentFlags().StringVarP(&cfgFile, "config", "c", "config.yml", "Config file (default is config.json)") 33 | } 34 | 35 | func run(cmd *cobra.Command, args []string) { 36 | starttime := time.Now() 37 | cfg.SetConfigFile(cfgFile) 38 | 39 | err := cfg.ReadInConfig() 40 | if err != nil { 41 | fmt.Println("no conifg file found, use default config") 42 | return 43 | } 44 | 45 | p := project.NewProject(cfg) 46 | 47 | loaders := cfg.GetStringMap("loaders") 48 | if len(loaders) == 0 { 49 | fmt.Println("no loader found in config file") 50 | return 51 | } 52 | for name := range loaders { 53 | err := p.AddLoader(name) 54 | if err != nil { 55 | fmt.Printf("parse loader error: %s\n", err) 56 | return 57 | } 58 | } 59 | 60 | generaters := cfg.GetStringMap("generaters") 61 | for name := range generaters { 62 | err := p.AddGenerater(name) 63 | if err != nil { 64 | fmt.Printf("parse generater error: %s\n", err) 65 | return 66 | } 67 | } 68 | 69 | err = p.Run() 70 | if err != nil { 71 | fmt.Printf("generate error: %s\n", err) 72 | return 73 | } 74 | 75 | fmt.Printf("parsed %d apps in %s\n", len(p.Apps), time.Now().Sub(starttime)) 76 | } 77 | -------------------------------------------------------------------------------- /cli/converter/convert.go: -------------------------------------------------------------------------------- 1 | package converter 2 | 3 | import ( 4 | "fmt" 5 | 6 | // modules 7 | _ "github.com/yankghjh/selfhosted_store/cli/modules/docker-compose" 8 | 9 | "github.com/yankghjh/selfhosted_store/cli/project" 10 | ) 11 | 12 | // Convert application form source format to target format 13 | func Convert(srcFormat, dstFormat string, payload []byte) ([]byte, error) { 14 | decoder := project.LoadDecoder(srcFormat) 15 | if decoder == nil { 16 | return nil, fmt.Errorf("decoder %s not found", srcFormat) 17 | } 18 | 19 | applications, err := decoder(payload) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | encoder := project.LoadEncoder(dstFormat) 25 | if encoder == nil { 26 | return nil, fmt.Errorf("encoder %s not found", dstFormat) 27 | } 28 | 29 | result, err := encoder(applications) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | return result, nil 35 | } 36 | -------------------------------------------------------------------------------- /cli/modules/app/context.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/yankghjh/selfhosted_store/cli/project" 7 | ) 8 | 9 | // Context of the app load pipe 10 | type Context struct { 11 | *project.Operator 12 | Path string 13 | Name string 14 | } 15 | 16 | // NewContext for the pipeline of one app 17 | func NewContext(o *project.Operator, name, path string) *Context { 18 | return &Context{ 19 | Operator: o, 20 | Name: name, 21 | Path: path, 22 | } 23 | } 24 | 25 | // GetPath of source file in app path 26 | func (c *Context) GetPath(paths ...string) string { 27 | return c.Path + "/" + strings.Join(paths, "/") 28 | } 29 | -------------------------------------------------------------------------------- /cli/modules/app/loader.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/yankghjh/selfhosted_store/cli/project" 8 | ) 9 | 10 | func init() { 11 | project.RegisterLoader("app", Loader) 12 | } 13 | 14 | // LoaderPlugin plugin for app loader 15 | type LoaderPlugin func(*Context, *project.Application) error 16 | 17 | // Loader load app from app path 18 | func Loader(o *project.Operator) error { 19 | path := o.Config.GetString("path") 20 | files, err := ioutil.ReadDir(path) 21 | if err != nil { 22 | return fmt.Errorf("read source path error: %s", err.Error()) 23 | } 24 | 25 | ctxs := []*Context{} 26 | 27 | for _, f := range files { 28 | if f.IsDir() { 29 | ctx := NewContext(o, f.Name(), path+"/"+f.Name()) 30 | ctxs = append(ctxs, ctx) 31 | } 32 | } 33 | 34 | for _, ctx := range ctxs { 35 | a := project.NewApplication() 36 | a.Name = ctx.Name 37 | 38 | plugins := []LoaderPlugin{LoadDockerCompose, LoadApp, LoadIcon} 39 | for _, f := range plugins { 40 | if err := f(ctx, a); err != nil { 41 | return fmt.Errorf("load for %s error: %s", a.Name, err.Error()) 42 | } 43 | } 44 | 45 | o.Project.Apps = append(o.Project.Apps, a) 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /cli/modules/app/loader_plugins.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/spf13/viper" 9 | compose "github.com/yankghjh/selfhosted_store/cli/modules/docker-compose" 10 | "github.com/yankghjh/selfhosted_store/cli/project" 11 | ) 12 | 13 | // LoadApp from app.yml 14 | func LoadApp(ctx *Context, a *project.Application) error { 15 | path := ctx.GetPath("app.yml") 16 | data := viper.New() 17 | data.SetConfigType("yml") 18 | 19 | file, err := os.Open(path) 20 | if err != nil { 21 | return fmt.Errorf("open file %s error: %s", path, err.Error()) 22 | } 23 | 24 | err = data.ReadConfig(file) 25 | if err != nil { 26 | return fmt.Errorf("read file %s error: %s", path, err.Error()) 27 | } 28 | 29 | a.Name = data.GetString("name") 30 | a.Description = data.GetString("description") 31 | a.Overview = data.GetString("overview") 32 | 33 | return nil 34 | } 35 | 36 | // LoadDockerCompose from docker-compose.yml 37 | func LoadDockerCompose(ctx *Context, a *project.Application) error { 38 | path := ctx.GetPath("docker-compose.yml") 39 | payload, err := ioutil.ReadFile(path) 40 | if err != nil { 41 | return fmt.Errorf("read file %s error: %s", path, err.Error()) 42 | } 43 | 44 | compose.LoadApplication(a, payload) 45 | if err != nil { 46 | return fmt.Errorf("load application form %s error: %s", path, err.Error()) 47 | } 48 | 49 | return nil 50 | } 51 | 52 | // LoadIcon from icon.png 53 | func LoadIcon(ctx *Context, a *project.Application) error { 54 | path := ctx.GetPath("icon.png") 55 | 56 | _, err := os.Stat(path) 57 | if err != nil { 58 | if os.IsExist(err) { 59 | return fmt.Errorf("stat icon %s error: %s", path, err.Error()) 60 | } 61 | return nil 62 | } 63 | 64 | iconDistpath := ctx.Config.GetString("icon.distpath") 65 | if iconDistpath == "" { 66 | iconDistpath = "assets/icon" 67 | } 68 | folderPath := ctx.Operator.Project.Dist + "/" + iconDistpath 69 | os.MkdirAll(folderPath, os.ModePerm) 70 | distpath := folderPath + "/" + ctx.Name + ".png" 71 | 72 | err = copyFile(path, distpath) 73 | if err != nil { 74 | return fmt.Errorf("copy icon %s error: %s", path, err.Error()) 75 | } 76 | 77 | a.Icon = ctx.Config.GetString("icon.basepath") + ctx.Name + ".png" 78 | 79 | return nil 80 | } 81 | 82 | func copyFile(src, dst string) error { 83 | input, err := ioutil.ReadFile(src) 84 | if err != nil { 85 | return fmt.Errorf("read file %s error: %s", src, err.Error()) 86 | } 87 | 88 | err = ioutil.WriteFile(dst, input, 0644) 89 | if err != nil { 90 | return fmt.Errorf("write file %s error: %s", dst, err.Error()) 91 | } 92 | 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /cli/modules/docker-compose/codec.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "github.com/docker/cli/cli/compose/types" 5 | "github.com/yankghjh/selfhosted_store/cli/project" 6 | "gopkg.in/yaml.v2" 7 | ) 8 | 9 | func init() { 10 | project.RegisterDecoder("docker-compose", Decoder) 11 | project.RegisterEncoder("docker-compose", Encoder) 12 | } 13 | 14 | // Decoder for docker-compose.yml 15 | func Decoder(payload []byte) (*project.Application, error) { 16 | a := project.NewApplication() 17 | 18 | err := LoadApplication(a, payload) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return a, nil 24 | } 25 | 26 | // Encoder for docker-compose.yml 27 | func Encoder(a *project.Application) ([]byte, error) { 28 | var services types.Services = make([]types.ServiceConfig, len(a.Services)) 29 | 30 | for i, service := range a.Services { 31 | services[i] = *service 32 | } 33 | 34 | cfg := &types.Config{ 35 | Version: "3.0", 36 | Services: services, 37 | } 38 | 39 | out, err := yaml.Marshal(cfg) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | return out, nil 45 | } 46 | -------------------------------------------------------------------------------- /cli/modules/docker-compose/compose.go: -------------------------------------------------------------------------------- 1 | package compose 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | 7 | "github.com/docker/cli/cli/compose/loader" 8 | "github.com/docker/cli/cli/compose/types" 9 | "github.com/yankghjh/selfhosted_store/cli/pipe" 10 | "github.com/yankghjh/selfhosted_store/cli/project" 11 | ) 12 | 13 | func init() { 14 | pipe.RegisterSourceLoader("docker-compose", Loader) 15 | } 16 | 17 | // Compose struct for docker-compose 18 | type Compose struct { 19 | Config *types.Config 20 | } 21 | 22 | // Loader for docker-compose.yml 23 | func Loader(pipe *pipe.Pipe, ctx *pipe.Context) error { 24 | path := ctx.GetPath("docker-compose.yml") 25 | 26 | payload, err := ioutil.ReadFile(path) 27 | if err != nil { 28 | return fmt.Errorf("read file %s error: %s", path, err.Error()) 29 | } 30 | 31 | source, err := loader.ParseYAML(payload) 32 | if err != nil { 33 | return fmt.Errorf("parse yaml %s error: %s", path, err.Error()) 34 | } 35 | 36 | config, err := loader.Load(types.ConfigDetails{ 37 | ConfigFiles: []types.ConfigFile{ 38 | {Filename: "docker-compose.yml", Config: source}, 39 | }, 40 | Environment: map[string]string{}, 41 | }) 42 | if err != nil { 43 | return fmt.Errorf("load docker compose conifg error: %s", err.Error()) 44 | } 45 | 46 | ctx.Set("compose", &Compose{ 47 | Config: config, 48 | }) 49 | 50 | return nil 51 | } 52 | 53 | // LoadApplication from docker-compose.yml 54 | func LoadApplication(a *project.Application, payload []byte) error { 55 | source, err := loader.ParseYAML(payload) 56 | if err != nil { 57 | return fmt.Errorf("parse docker compose yaml error: %s", err.Error()) 58 | } 59 | 60 | config, err := loader.Load(types.ConfigDetails{ 61 | ConfigFiles: []types.ConfigFile{ 62 | {Filename: "docker-compose.yml", Config: source}, 63 | }, 64 | Environment: map[string]string{}, 65 | }) 66 | if err != nil { 67 | return fmt.Errorf("load docker compose conifg error: %s", err.Error()) 68 | } 69 | 70 | if len(config.Services) == 0 { 71 | return fmt.Errorf("load docker compose services error: no service found") 72 | } 73 | 74 | for _, service := range config.Services { 75 | a.Services = append(a.Services, &service) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /cli/modules/icon/icon.go: -------------------------------------------------------------------------------- 1 | package icon 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "github.com/yankghjh/selfhosted_store/cli/pipe" 9 | ) 10 | 11 | func init() { 12 | pipe.RegisterInitFunc("icon", InitFunc) 13 | pipe.RegisterSourceLoader("icon", Loader) 14 | } 15 | 16 | // Icon for the app 17 | type Icon struct { 18 | URL string 19 | } 20 | 21 | // InitFunc make dir for icons 22 | func InitFunc(pipe *pipe.Pipe) error { 23 | os.MkdirAll(pipe.GetDistPath("assets/icons"), os.ModePerm) 24 | return nil 25 | } 26 | 27 | // Loader for app.yml 28 | func Loader(pipe *pipe.Pipe, ctx *pipe.Context) error { 29 | path := ctx.GetPath("icon.png") 30 | 31 | _, err := os.Stat(path) 32 | if err != nil { 33 | if os.IsExist(err) { 34 | return fmt.Errorf("stat icon %s error: %s", path, err.Error()) 35 | } 36 | return nil 37 | } 38 | 39 | distpath := pipe.GetDistPath("assets/icons/" + ctx.Name + ".png") 40 | 41 | err = copyFile(path, distpath) 42 | if err != nil { 43 | return fmt.Errorf("copy icon %s error: %s", path, err.Error()) 44 | } 45 | 46 | icon := &Icon{ 47 | URL: pipe.Config.GetString("icon.basepath") + ctx.Name + ".png", 48 | } 49 | 50 | ctx.Set("icon", icon) 51 | 52 | return nil 53 | } 54 | 55 | func copyFile(src, dst string) error { 56 | input, err := ioutil.ReadFile(src) 57 | if err != nil { 58 | return fmt.Errorf("read file %s error: %s", src, err.Error()) 59 | } 60 | 61 | err = ioutil.WriteFile(dst, input, 0644) 62 | if err != nil { 63 | return fmt.Errorf("write file %s error: %s", dst, err.Error()) 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /cli/modules/portainer/generater.go: -------------------------------------------------------------------------------- 1 | package portainer 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/yankghjh/selfhosted_store/cli/project" 11 | ) 12 | 13 | func init() { 14 | project.RegisterGenerater("portainer", Generater) 15 | } 16 | 17 | // Dataset portainer template format 18 | type Dataset struct { 19 | Version string `json:"version"` 20 | Templates []*Template `json:"templates"` 21 | } 22 | 23 | // Template struct for portainer application 24 | type Template struct { 25 | Type int `json:"type"` 26 | Title string `json:"title"` 27 | Description string `json:"description,omitempty"` 28 | Categories []string `json:"categories,omitempty"` 29 | Platform string `json:"platform"` 30 | Note string `json:"note,omitempty"` 31 | Logo string `json:"logo,omitempty"` 32 | 33 | Name string `json:"name"` 34 | Command string `json:"command,omitempty"` 35 | Image string `json:"image"` 36 | RestartPolicy string `json:"restart_policy,omitempty"` 37 | NetworkMode string `json:"network_mode,omitempty"` 38 | Ports []string `json:"ports,omitempty"` 39 | Volumes []VolumeConfig `json:"volumes,omitempty"` 40 | Environment []EnvironmentConfig `json:"env,omitempty"` 41 | } 42 | 43 | // VolumeConfig for portainer template volumn bind 44 | type VolumeConfig struct { 45 | Container string `json:"container"` 46 | Bind string `json:"bind"` 47 | } 48 | 49 | // EnvironmentConfig for portainer template environment 50 | // TODO label and description 51 | type EnvironmentConfig struct { 52 | Name string `json:"name"` 53 | Label string `json:"label,omitempty"` 54 | Default string `json:"default,omitempty"` 55 | Description string `json:"description,omitempty"` 56 | } 57 | 58 | // Generater portainer template 59 | func Generater(o *project.Operator) error { 60 | res, err := Convert(o.Project.Apps) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | path := o.Project.GetDistPath("templates", "portainer") 66 | os.MkdirAll(path, os.ModePerm) 67 | filename := path + "/template.json" 68 | err = ioutil.WriteFile(filename, res, os.ModePerm) 69 | if err != nil { 70 | return fmt.Errorf("write template file [%s] error: %s", filename, err.Error()) 71 | } 72 | 73 | return nil 74 | } 75 | 76 | // Convert applications to portainer template 77 | func Convert(apps []*project.Application) ([]byte, error) { 78 | dataset := &Dataset{ 79 | Version: "2", 80 | Templates: []*Template{}, 81 | } 82 | 83 | for _, app := range apps { 84 | t, err := ConvertApplication(app) 85 | if err != nil { 86 | return nil, fmt.Errorf("convert application to yacht template error: %s", err.Error()) 87 | } 88 | dataset.Templates = append(dataset.Templates, t) 89 | } 90 | 91 | res, err := json.MarshalIndent(dataset, "", " ") 92 | if err != nil { 93 | return nil, fmt.Errorf("marshal templates error: %s", err.Error()) 94 | } 95 | 96 | return res, nil 97 | } 98 | 99 | // ConvertApplication convert single application 100 | func ConvertApplication(a *project.Application) (*Template, error) { 101 | t := new(Template) 102 | service := a.Services[0] 103 | 104 | t.Type = 1 105 | t.Title = a.Name 106 | t.Description = a.Overview 107 | t.Categories = a.Category 108 | t.Platform = "linux" 109 | t.Note = a.Description 110 | t.Logo = a.Icon 111 | 112 | t.Name = service.ContainerName 113 | t.Image = service.Image 114 | t.RestartPolicy = service.Restart 115 | t.NetworkMode = service.NetworkMode 116 | 117 | if len(service.Ports) > 0 { 118 | t.Ports = []string{} 119 | 120 | for _, port := range service.Ports { 121 | published := strconv.Itoa(int(port.Published)) 122 | target := strconv.Itoa(int(port.Target)) 123 | t.Ports = append(t.Ports, published+":"+target+"/"+port.Protocol) 124 | } 125 | 126 | } 127 | 128 | if len(service.Volumes) > 0 { 129 | t.Volumes = []VolumeConfig{} 130 | 131 | for _, volumn := range service.Volumes { 132 | t.Volumes = append(t.Volumes, VolumeConfig{ 133 | Container: volumn.Target, 134 | Bind: volumn.Source, 135 | }) 136 | } 137 | } 138 | 139 | if len(service.Environment) > 0 { 140 | t.Environment = []EnvironmentConfig{} 141 | 142 | for name, point := range service.Environment { 143 | value := "" 144 | if point != nil { 145 | value = *point 146 | } 147 | t.Environment = append(t.Environment, EnvironmentConfig{ 148 | Name: name, 149 | Label: name, 150 | Default: value, 151 | }) 152 | } 153 | } 154 | 155 | return t, nil 156 | } 157 | -------------------------------------------------------------------------------- /cli/modules/unraid/source.go: -------------------------------------------------------------------------------- 1 | package unraid 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/docker/cli/cli/compose/types" 11 | "github.com/spf13/cast" 12 | 13 | "github.com/yankghjh/selfhosted_store/cli/project" 14 | ) 15 | 16 | func init() { 17 | project.RegisterLoader("unraid", Loader) 18 | } 19 | 20 | // Application struct for unraid community application feed 21 | type Application struct { 22 | Plugin bool 23 | Name string 24 | Description string 25 | Overview string 26 | Category string 27 | Icon string 28 | Repository string 29 | Environment interface{} 30 | Networking interface{} 31 | Data interface{} 32 | Config interface{} 33 | 34 | network map[string]*types.ServicePortConfig 35 | volumn map[string]*types.ServiceVolumeConfig 36 | environment map[string]*string 37 | networkMode string 38 | } 39 | 40 | // FeedFile struct for unraid community application feed 41 | type FeedFile struct { 42 | Apps int `json:"apps"` 43 | AppList []*Application `json:"applist"` 44 | } 45 | 46 | var defaultRestartPolicy string = "unless-stopped" 47 | 48 | // Loader for unraid community applications 49 | func Loader(o *project.Operator) error { 50 | path := o.Config.GetString("application_feed_file") 51 | 52 | payload, err := ioutil.ReadFile(path) 53 | if err != nil { 54 | return fmt.Errorf("read file %s error: %s", path, err.Error()) 55 | } 56 | 57 | appList, err := LoadApplications(payload) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | for _, a := range appList { 63 | if a.Plugin || a.Repository == "" { 64 | continue 65 | } 66 | 67 | a.Parse() 68 | o.Project.Apps = append(o.Project.Apps, a.ToProjectApplication()) 69 | } 70 | 71 | return nil 72 | } 73 | 74 | // LoadApplications form application feed file data 75 | func LoadApplications(payload []byte) ([]*Application, error) { 76 | feed := &FeedFile{ 77 | AppList: []*Application{}, 78 | } 79 | 80 | err := json.Unmarshal(payload, feed) 81 | 82 | if err != nil { 83 | return nil, fmt.Errorf("unmarshal unraid application_feed_file error: %s", err.Error()) 84 | } 85 | 86 | return feed.AppList, nil 87 | } 88 | 89 | // Parse unraid application after unmarshaled form json 90 | func (a *Application) Parse() error { 91 | a.network = map[string]*types.ServicePortConfig{} 92 | a.volumn = map[string]*types.ServiceVolumeConfig{} 93 | a.environment = map[string]*string{} 94 | // config 95 | if a.Config != nil { 96 | cfgs := []map[string]interface{}{} 97 | cos := cast.ToSlice(a.Config) 98 | if len(cos) > 0 { 99 | for _, c := range cos { 100 | if cfg := cast.ToStringMap(c); cfg != nil { 101 | cfgs = append(cfgs, cfg) 102 | } 103 | } 104 | } else if cfg := cast.ToStringMap(a.Config); cfg != nil { 105 | cfgs = append(cfgs, cfg) 106 | } 107 | 108 | for _, item := range cfgs { 109 | a.parseConfigItem(item) 110 | } 111 | } 112 | 113 | a.parseEnvironment() 114 | a.parsePorts() 115 | a.parseVolumns() 116 | 117 | return nil 118 | } 119 | 120 | // ToProjectApplication convert to project application 121 | func (a *Application) ToProjectApplication() *project.Application { 122 | app := project.NewApplication() 123 | 124 | app.Name = a.Name 125 | app.Description = a.Description 126 | app.Overview = a.Overview 127 | app.Icon = a.Icon 128 | app.Category = strings.Split(a.Category, " ") 129 | 130 | app.Services = append(app.Services, a.GetServiceConfig()) 131 | 132 | return app 133 | } 134 | 135 | // GetServiceConfig from application 136 | func (a *Application) GetServiceConfig() *types.ServiceConfig { 137 | service := &types.ServiceConfig{} 138 | service.ContainerName = a.Name 139 | service.Environment = a.environment 140 | service.Image = a.Repository 141 | service.Restart = defaultRestartPolicy 142 | service.NetworkMode = a.networkMode 143 | 144 | ports := []types.ServicePortConfig{} 145 | for _, p := range a.network { 146 | ports = append(ports, *p) 147 | } 148 | service.Ports = ports 149 | 150 | volumns := []types.ServiceVolumeConfig{} 151 | for _, v := range a.volumn { 152 | volumns = append(volumns, *v) 153 | } 154 | service.Volumes = volumns 155 | 156 | return service 157 | } 158 | 159 | func (a *Application) parseConfigItem(v map[string]interface{}) { 160 | attributes := cast.ToStringMapString(v["@attributes"]) 161 | value := cast.ToString(v["value"]) 162 | if value == "" { 163 | value = attributes["Default"] 164 | } 165 | 166 | switch attributes["Type"] { 167 | case "Port": 168 | a.addNetwork(&types.ServicePortConfig{ 169 | Published: cast.ToUint32(value), 170 | Target: cast.ToUint32(attributes["Target"]), 171 | Protocol: attributes["Mode"], 172 | }) 173 | case "Path": 174 | a.addVolumn(&types.ServiceVolumeConfig{ 175 | Target: attributes["Target"], 176 | Source: value, 177 | }) 178 | case "Variable": 179 | a.addEnvironment(attributes["Target"], value) 180 | } 181 | } 182 | 183 | func (a *Application) addNetwork(n *types.ServicePortConfig) { 184 | if n.Protocol != "tcp" && n.Protocol != "udp" { 185 | n.Protocol = "tcp" 186 | } 187 | name := strconv.Itoa(int(n.Target)) + "/" + n.Mode 188 | 189 | if _, isExisted := a.network[name]; isExisted { 190 | return 191 | } 192 | 193 | a.network[name] = n 194 | } 195 | 196 | func (a *Application) addVolumn(v *types.ServiceVolumeConfig) { 197 | if _, isExisted := a.volumn[v.Target]; isExisted { 198 | return 199 | } 200 | 201 | a.volumn[v.Target] = v 202 | } 203 | 204 | func (a *Application) addEnvironment(key, value string) { 205 | a.environment[key] = &value 206 | } 207 | 208 | // parse environment of application 209 | func (a *Application) parseEnvironment() { 210 | if a.Environment == nil { 211 | return 212 | } 213 | 214 | v, ok := cast.ToStringMap(a.Environment)["Variable"] 215 | if !ok { 216 | return 217 | } 218 | 219 | vs := cast.ToSlice(v) 220 | if len(vs) == 0 { 221 | vs = []interface{}{v} 222 | } 223 | 224 | for _, pair := range vs { 225 | kv := cast.ToStringMapString(pair) 226 | key := kv["Name"] 227 | if key != "" { 228 | a.addEnvironment(key, kv["Value"]) 229 | } 230 | } 231 | } 232 | 233 | // parse ports of application 234 | func (a *Application) parsePorts() { 235 | if a.Networking == nil { 236 | return 237 | } 238 | 239 | network := cast.ToStringMap(a.Networking) 240 | if m, ok := network["Mode"]; ok { 241 | mode := cast.ToString(m) 242 | if mode == "bridge" || mode == "host" || mode == "none" { 243 | a.networkMode = mode 244 | } 245 | } 246 | 247 | publish, ok := network["Publish"] 248 | if !ok { 249 | return 250 | } 251 | 252 | pts, ok := cast.ToStringMap(publish)["Port"] 253 | if !ok { 254 | return 255 | } 256 | 257 | ps := cast.ToSlice(pts) 258 | if len(ps) == 0 { 259 | ps = []interface{}{pts} 260 | } 261 | 262 | for _, port := range ps { 263 | kv := cast.ToStringMapString(port) 264 | if kv["ContainerPort"] != "" { 265 | protocol := kv["Protocol"] 266 | a.addNetwork(&types.ServicePortConfig{ 267 | Published: cast.ToUint32(kv["HostPort"]), 268 | Target: cast.ToUint32(kv["ContainerPort"]), 269 | Protocol: protocol, 270 | }) 271 | } 272 | } 273 | } 274 | 275 | // parse volumns of application 276 | func (a *Application) parseVolumns() { 277 | if a.Data == nil { 278 | return 279 | } 280 | 281 | vs, ok := cast.ToStringMap(a.Data)["Volume"] 282 | if !ok { 283 | return 284 | } 285 | 286 | vos := cast.ToSlice(vs) 287 | if len(vos) == 0 { 288 | vos = []interface{}{vs} 289 | } 290 | 291 | for _, v := range vos { 292 | kv := cast.ToStringMapString(v) 293 | if kv["ContainerDir"] != "" { 294 | a.addVolumn(&types.ServiceVolumeConfig{ 295 | Target: kv["ContainerDir"], 296 | Source: kv["HostDir"], 297 | }) 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /cli/modules/yacht/yacht.go: -------------------------------------------------------------------------------- 1 | package yacht 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/yankghjh/selfhosted_store/cli/project" 11 | 12 | "github.com/yankghjh/selfhosted_store/cli/pipe" 13 | ) 14 | 15 | func init() { 16 | project.RegisterGenerater("yacht", Generater) 17 | } 18 | 19 | // Dataset yacht app dataset 20 | type Dataset struct { 21 | Templates []*Template 22 | } 23 | 24 | // Template struct for Yacht application 25 | type Template struct { 26 | Type int `json:"type"` 27 | Title string `json:"title"` 28 | Description string `json:"description,omitempty"` 29 | Categories []string `json:"categories"` 30 | Platform string `json:"platform"` 31 | Note string `json:"note,omitempty"` 32 | Logo string `json:"logo,omitempty"` 33 | 34 | Name string `json:"name"` 35 | Image string `json:"image"` 36 | RestartPolicy string `json:"restart_policy,omitempty"` 37 | NetworkMode string `json:"network_mode,omitempty"` 38 | Ports []map[string]string `json:"ports,omitempty"` 39 | Volumes []VolumeConfig `json:"volumes,omitempty"` 40 | Environment []EnvironmentConfig `json:"env,omitempty"` 41 | } 42 | 43 | // VolumeConfig for Yacht template volumn bind 44 | type VolumeConfig struct { 45 | Container string `json:"container"` 46 | Bind string `json:"bind"` 47 | } 48 | 49 | // EnvironmentConfig for Yacht template environment 50 | type EnvironmentConfig struct { 51 | Name string `json:"name"` 52 | Label string `json:"label"` 53 | Default string `json:"default"` 54 | } 55 | 56 | // InitFunc init dataset 57 | func InitFunc(pipe *pipe.Pipe) error { 58 | pipe.Set("yacht", &Dataset{ 59 | Templates: []*Template{}, 60 | }) 61 | 62 | return nil 63 | } 64 | 65 | // Generater yacht template 66 | func Generater(o *project.Operator) error { 67 | res, err := Convert(o.Project.Apps) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | path := o.Project.GetDistPath("templates", "yacht") 73 | os.MkdirAll(path, os.ModePerm) 74 | filename := path + "/yacht.json" 75 | err = ioutil.WriteFile(filename, res, os.ModePerm) 76 | if err != nil { 77 | return fmt.Errorf("write template file [%s] error: %s", filename, err.Error()) 78 | } 79 | 80 | return nil 81 | } 82 | 83 | // Convert applications to yacht template 84 | func Convert(apps []*project.Application) ([]byte, error) { 85 | dataset := []*Template{} 86 | 87 | for _, app := range apps { 88 | t, err := ConvertApplication(app) 89 | if err != nil { 90 | return nil, fmt.Errorf("convert application to yacht template error: %s", err.Error()) 91 | } 92 | dataset = append(dataset, t) 93 | } 94 | 95 | res, err := json.MarshalIndent(dataset, "", " ") 96 | if err != nil { 97 | return nil, fmt.Errorf("marshal templates error: %s", err.Error()) 98 | } 99 | 100 | return res, nil 101 | } 102 | 103 | // ConvertApplication convert single application 104 | func ConvertApplication(a *project.Application) (*Template, error) { 105 | t := new(Template) 106 | service := a.Services[0] 107 | 108 | t.Type = 1 109 | t.Title = a.Name 110 | t.Description = a.Overview 111 | t.Categories = a.Category 112 | t.Platform = "linux" 113 | t.Note = a.Description 114 | t.Logo = a.Icon 115 | 116 | t.Name = service.ContainerName 117 | t.Image = service.Image 118 | t.RestartPolicy = service.Restart 119 | t.NetworkMode = service.NetworkMode 120 | 121 | if len(service.Ports) > 0 { 122 | t.Ports = []map[string]string{} 123 | ports := map[string]string{} 124 | 125 | for _, port := range service.Ports { 126 | published := strconv.Itoa(int(port.Published)) 127 | target := strconv.Itoa(int(port.Target)) 128 | ports[target] = published + ":" + target + "/" + port.Protocol 129 | } 130 | 131 | t.Ports = []map[string]string{ports} 132 | } 133 | 134 | if len(service.Volumes) > 0 { 135 | t.Volumes = []VolumeConfig{} 136 | 137 | for _, volumn := range service.Volumes { 138 | t.Volumes = append(t.Volumes, VolumeConfig{ 139 | Container: volumn.Target, 140 | Bind: volumn.Source, 141 | }) 142 | } 143 | } 144 | 145 | if len(service.Environment) > 0 { 146 | t.Environment = []EnvironmentConfig{} 147 | 148 | for name, point := range service.Environment { 149 | value := "" 150 | if point != nil { 151 | value = *point 152 | } 153 | t.Environment = append(t.Environment, EnvironmentConfig{ 154 | Name: name, 155 | Label: name, 156 | Default: value, 157 | }) 158 | } 159 | } 160 | 161 | return t, nil 162 | } 163 | -------------------------------------------------------------------------------- /cli/pipe/context.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import "strings" 4 | 5 | // Context of the pipe 6 | type Context struct { 7 | Path string 8 | Name string 9 | data map[string]interface{} 10 | } 11 | 12 | // NewContext for the pipeline of one app 13 | func NewContext(name string, path string) *Context { 14 | return &Context{ 15 | Name: name, 16 | Path: path, 17 | data: map[string]interface{}{}, 18 | } 19 | } 20 | 21 | // Get value store in context 22 | func (c *Context) Get(key string) interface{} { 23 | value, ok := c.data[key] 24 | if !ok { 25 | return nil 26 | } 27 | 28 | return value 29 | } 30 | 31 | // Set vaule to context 32 | func (c *Context) Set(key string, value interface{}) { 33 | c.data[key] = value 34 | } 35 | 36 | // GetPath of source file in app path 37 | func (c *Context) GetPath(paths ...string) string { 38 | return c.Path + "/" + strings.Join(paths, "/") 39 | } 40 | -------------------------------------------------------------------------------- /cli/pipe/pipe.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | /** 12 | 0. Call init functions 13 | 1. Load apps 14 | 2. For the each of apps: 15 | 1. Execute source loaders 16 | 2. Execute source handlers 17 | 3. Call finish fucntions 18 | **/ 19 | 20 | var initFuncs map[string]InitFunc 21 | var finishFuncs map[string]FinishFunc 22 | var sourceLoaders map[string]SourceLoader 23 | var sourceHandlers map[string]SourceHandler 24 | 25 | func init() { 26 | initFuncs = map[string]InitFunc{} 27 | finishFuncs = map[string]FinishFunc{} 28 | sourceLoaders = map[string]SourceLoader{} 29 | sourceHandlers = map[string]SourceHandler{} 30 | } 31 | 32 | // Pipe is the pipeline for the templetes generator 33 | type Pipe struct { 34 | Opition 35 | Count int 36 | Apps []*Context 37 | 38 | data map[string]interface{} 39 | } 40 | 41 | // Opition for the pipe 42 | type Opition struct { 43 | SourcePath string 44 | DistPath string 45 | Sources []string 46 | Handlers []string 47 | Config *viper.Viper 48 | SkipSourceFile bool 49 | } 50 | 51 | // InitFunc called before loading apps 52 | type InitFunc func(pipe *Pipe) error 53 | 54 | // FinishFunc called after handling apps 55 | type FinishFunc func(pipe *Pipe) error 56 | 57 | // SourceLoader load source and save to context 58 | type SourceLoader func(pipe *Pipe, ctx *Context) error 59 | 60 | // SourceHandler process the loaded source 61 | type SourceHandler func(pipe *Pipe, ctx *Context) error 62 | 63 | // NewPipe with the opition 64 | func NewPipe(opt Opition) (*Pipe, error) { 65 | p := &Pipe{ 66 | Opition: opt, 67 | Apps: []*Context{}, 68 | 69 | data: map[string]interface{}{}, 70 | } 71 | 72 | for _, source := range p.Sources { 73 | if _, ok := sourceLoaders[source]; !ok { 74 | return nil, fmt.Errorf("source %s not exist", source) 75 | } 76 | } 77 | 78 | for _, handler := range p.Handlers { 79 | if _, ok := sourceHandlers[handler]; !ok { 80 | return nil, fmt.Errorf("handler %s not exist", handler) 81 | } 82 | } 83 | 84 | return p, nil 85 | } 86 | 87 | // Get value stored in pipe 88 | func (p *Pipe) Get(key string) interface{} { 89 | value, ok := p.data[key] 90 | if !ok { 91 | return nil 92 | } 93 | 94 | return value 95 | } 96 | 97 | // Set vaule to pipe 98 | func (p *Pipe) Set(key string, value interface{}) { 99 | p.data[key] = value 100 | } 101 | 102 | // GetDistPath get path of dist 103 | func (p *Pipe) GetDistPath(paths ...string) string { 104 | return p.DistPath + "/" + strings.Join(paths, "/") 105 | } 106 | 107 | // Run the pipe 108 | func (p *Pipe) Run() error { 109 | for name, initFunc := range initFuncs { 110 | if err := initFunc(p); err != nil { 111 | return fmt.Errorf("init %s error: %s", name, err.Error()) 112 | } 113 | } 114 | 115 | if !p.SkipSourceFile { 116 | files, err := ioutil.ReadDir(p.SourcePath) 117 | if err != nil { 118 | return fmt.Errorf("read source path error: %s", err.Error()) 119 | } 120 | 121 | for _, f := range files { 122 | if f.IsDir() { 123 | p.Apps = append(p.Apps, NewContext(f.Name(), p.SourcePath+"/"+f.Name())) 124 | } 125 | } 126 | } 127 | 128 | p.Count = len(p.Apps) 129 | 130 | for _, ctx := range p.Apps { 131 | // load source 132 | for _, source := range p.Sources { 133 | f := sourceLoaders[source] 134 | 135 | err := f(p, ctx) 136 | if err != nil { 137 | return fmt.Errorf("load source [%s] of path [%s] error: %s", source, ctx.Path, err.Error()) 138 | } 139 | } 140 | 141 | // handle 142 | for _, handler := range p.Handlers { 143 | f := sourceHandlers[handler] 144 | 145 | err := f(p, ctx) 146 | if err != nil { 147 | return fmt.Errorf("run handler [%s] of path [%s] error: %s", handler, ctx.Path, err.Error()) 148 | } 149 | } 150 | } 151 | 152 | for name, finishFunc := range finishFuncs { 153 | if err := finishFunc(p); err != nil { 154 | return fmt.Errorf("finish %s error: %s", name, err.Error()) 155 | } 156 | } 157 | 158 | return nil 159 | } 160 | 161 | // RegisterInitFunc register init function 162 | func RegisterInitFunc(name string, f InitFunc) { 163 | initFuncs[name] = f 164 | } 165 | 166 | // RegisterFinishFunc register finish function 167 | func RegisterFinishFunc(name string, f FinishFunc) { 168 | finishFuncs[name] = f 169 | } 170 | 171 | // RegisterSourceLoader register source loader 172 | func RegisterSourceLoader(name string, f SourceLoader) { 173 | sourceLoaders[name] = f 174 | } 175 | 176 | // RegisterSourceHandler register source handler 177 | func RegisterSourceHandler(name string, f SourceHandler) { 178 | sourceHandlers[name] = f 179 | } 180 | -------------------------------------------------------------------------------- /cli/project/application.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import "github.com/docker/cli/cli/compose/types" 4 | 5 | // Application is a selfhosted application 6 | type Application struct { 7 | Name string 8 | Description string 9 | Overview string 10 | Category []string 11 | Icon string 12 | Services []*types.ServiceConfig 13 | } 14 | 15 | // NewApplication create new application 16 | func NewApplication() *Application { 17 | return &Application{ 18 | Services: []*types.ServiceConfig{}, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cli/project/codec.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | // Decoder from template to template applications 4 | type Decoder func([]byte) (*Application, error) 5 | 6 | // Encoder from applications to template 7 | type Encoder func(*Application) ([]byte, error) 8 | 9 | var ( 10 | decoders map[string]Decoder 11 | encoders map[string]Encoder 12 | ) 13 | 14 | func init() { 15 | decoders = make(map[string]Decoder) 16 | encoders = make(map[string]Encoder) 17 | } 18 | 19 | // RegisterDecoder register decoder 20 | func RegisterDecoder(name string, f Decoder) { 21 | decoders[name] = f 22 | } 23 | 24 | // RegisterEncoder register encoder 25 | func RegisterEncoder(name string, f Encoder) { 26 | encoders[name] = f 27 | } 28 | 29 | // LoadDecoder load decoder 30 | func LoadDecoder(name string) Decoder { 31 | if f, ok := decoders[name]; ok { 32 | return f 33 | } 34 | 35 | return nil 36 | } 37 | 38 | // LoadEncoder load encoder 39 | func LoadEncoder(name string) Encoder { 40 | if f, ok := encoders[name]; ok { 41 | return f 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /cli/project/project.go: -------------------------------------------------------------------------------- 1 | package project 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | type ( 11 | // Loader application 12 | Loader func(o *Operator) error 13 | // Generater site/template 14 | Generater func(o *Operator) error 15 | ) 16 | 17 | var ( 18 | loaders map[string]Loader 19 | generaters map[string]Generater 20 | ) 21 | 22 | func init() { 23 | loaders = make(map[string]Loader) 24 | generaters = make(map[string]Generater) 25 | } 26 | 27 | // RegisterLoader register loader 28 | func RegisterLoader(name string, f Loader) { 29 | loaders[name] = f 30 | } 31 | 32 | // RegisterGenerater register generater 33 | func RegisterGenerater(name string, f Generater) { 34 | generaters[name] = f 35 | } 36 | 37 | // Project is the universal data struct for selfhosted template 38 | type Project struct { 39 | Dist string 40 | Loaders []*Operator 41 | Generaters []*Operator 42 | Config *viper.Viper 43 | Apps []*Application 44 | } 45 | 46 | // Operator loader or generater 47 | type Operator struct { 48 | Name string 49 | Type string 50 | Config *viper.Viper 51 | Project *Project 52 | } 53 | 54 | // NewProject create new project 55 | func NewProject(cfg *viper.Viper) *Project { 56 | return &Project{ 57 | Config: cfg, 58 | Dist: cfg.GetString("dist"), 59 | Apps: []*Application{}, 60 | Loaders: []*Operator{}, 61 | Generaters: []*Operator{}, 62 | } 63 | } 64 | 65 | // Run the project 66 | func (p *Project) Run() error { 67 | for _, o := range p.Loaders { 68 | err := loaders[o.Type](o) 69 | if err != nil { 70 | return fmt.Errorf("run loader %s(%s) error: %s", o.Name, o.Type, err) 71 | } 72 | } 73 | 74 | for _, o := range p.Generaters { 75 | err := generaters[o.Type](o) 76 | if err != nil { 77 | return fmt.Errorf("run generater %s(%s) error: %s", o.Name, o.Type, err) 78 | } 79 | } 80 | 81 | return nil 82 | } 83 | 84 | // AddLoader add loader 85 | func (p *Project) AddLoader(name string) error { 86 | cfg := p.Config.Sub("loaders." + name) 87 | 88 | t := cfg.GetString("type") 89 | if _, ok := loaders[t]; !ok { 90 | return fmt.Errorf("no such loader type [%s]", t) 91 | } 92 | 93 | p.Loaders = append(p.Loaders, &Operator{ 94 | Name: name, 95 | Type: t, 96 | Config: cfg, 97 | Project: p, 98 | }) 99 | 100 | return nil 101 | } 102 | 103 | // AddGenerater add generater 104 | func (p *Project) AddGenerater(name string) error { 105 | cfg := p.Config.Sub("generaters." + name) 106 | 107 | t := cfg.GetString("type") 108 | if _, ok := generaters[t]; !ok { 109 | return fmt.Errorf("no such generater type [%s]", t) 110 | } 111 | 112 | p.Generaters = append(p.Generaters, &Operator{ 113 | Name: name, 114 | Type: t, 115 | Config: cfg, 116 | Project: p, 117 | }) 118 | 119 | return nil 120 | } 121 | 122 | // GetDistPath of project 123 | func (p *Project) GetDistPath(paths ...string) string { 124 | return p.Dist + "/" + strings.Join(paths, "/") 125 | } 126 | -------------------------------------------------------------------------------- /cli/shctl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/yankghjh/selfhosted_store/cli/cmd/convert" 7 | "github.com/yankghjh/selfhosted_store/cli/cmd/generate" 8 | ) 9 | 10 | var ( 11 | command = &cobra.Command{ 12 | Use: "shctl", 13 | Short: "Shctl is a selfhosted store generater", 14 | } 15 | ) 16 | 17 | func init() { 18 | command.AddCommand(generate.Command) 19 | command.AddCommand(convert.Command) 20 | } 21 | 22 | func main() { 23 | command.Execute() 24 | } 25 | -------------------------------------------------------------------------------- /cli/wasm/wasm.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "syscall/js" 6 | 7 | "github.com/yankghjh/selfhosted_store/cli/converter" 8 | ) 9 | 10 | type convertResult struct { 11 | Success bool `json:"success"` 12 | Error string `json:"error"` 13 | Result string `json:"result"` 14 | } 15 | 16 | func convertFunc(this js.Value, args []js.Value) interface{} { 17 | output, err := converter.Convert( 18 | args[0].String(), args[1].String(), []byte(args[2].String()), 19 | ) 20 | var result convertResult 21 | if err != nil { 22 | result = convertResult{ 23 | Success: false, 24 | Error: err.Error(), 25 | } 26 | } else { 27 | result = convertResult{ 28 | Success: true, 29 | Result: string(output), 30 | } 31 | } 32 | 33 | r, _ := json.Marshal(&result) 34 | 35 | return js.ValueOf(string(r)) 36 | } 37 | 38 | func main() { 39 | done := make(chan int, 0) 40 | js.Global().Set("ShsConvert", js.FuncOf(convertFunc)) 41 | <-done 42 | } 43 | -------------------------------------------------------------------------------- /config.unraid.yml: -------------------------------------------------------------------------------- 1 | dist: dist/unraid 2 | loaders: 3 | unraid: 4 | type: unraid 5 | application_feed_file: build/applicationFeed.json 6 | generaters: 7 | yacht: 8 | type: yacht 9 | portainer: 10 | type: portainer -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | loaders: 2 | apps: 3 | type: app 4 | path: apps 5 | icon: 6 | basepath: https://yangkghjh.github.io/selfhosted_store/apps/assets/icon/ 7 | generaters: 8 | yacht: 9 | type: yacht 10 | portainer: 11 | type: portainer 12 | dist: dist/apps -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/yankghjh/selfhosted_store 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/containerd/containerd v1.4.1 // indirect 7 | github.com/docker/cli v0.0.0-20200915230204-cd8016b6bcc5 8 | github.com/docker/distribution v2.7.1+incompatible // indirect 9 | github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible // indirect 10 | github.com/docker/go-connections v0.4.0 // indirect 11 | github.com/docker/go-units v0.4.0 // indirect 12 | github.com/gogo/protobuf v1.3.1 // indirect 13 | github.com/imdario/mergo v0.3.11 // indirect 14 | github.com/mattn/go-shellwords v1.0.10 // indirect 15 | github.com/mitchellh/mapstructure v1.3.3 // indirect 16 | github.com/opencontainers/go-digest v1.0.0 // indirect 17 | github.com/opencontainers/image-spec v1.0.1 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/sirupsen/logrus v1.7.0 // indirect 20 | github.com/spf13/cast v1.3.0 21 | github.com/spf13/cobra v1.1.1 22 | github.com/spf13/viper v1.7.1 23 | github.com/stretchr/testify v1.3.0 // indirect 24 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect 25 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 26 | github.com/xeipuuv/gojsonschema v0.0.0-20160323030313-93e72a773fad // indirect 27 | google.golang.org/grpc v1.33.0 // indirect 28 | gopkg.in/yaml.v2 v2.3.0 29 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 30 | gotest.tools v2.2.0+incompatible 31 | gotest.tools/v3 v3.0.3 32 | ) 33 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 9 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 10 | cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= 11 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 12 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 13 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 14 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 15 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 16 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 17 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 18 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 19 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 20 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 21 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 22 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 23 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 24 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 25 | github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= 26 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 27 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 28 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 29 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 30 | github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY= 31 | github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= 32 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 33 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 34 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 35 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 36 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 37 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 38 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 39 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 41 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 42 | github.com/docker/cli v0.0.0-20200915230204-cd8016b6bcc5 h1:lO0MpAq24a0qAVJ4Sr7oFDLTTHQJL2KHk/X0E1CXcPI= 43 | github.com/docker/cli v0.0.0-20200915230204-cd8016b6bcc5/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= 44 | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= 45 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 46 | github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo= 47 | github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 48 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 49 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 50 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 51 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 52 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 53 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 54 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 55 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 56 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 57 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 58 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 59 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 60 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 61 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 62 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 63 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 64 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 65 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 66 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 67 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 68 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 69 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 70 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 71 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 72 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 73 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 74 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 75 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 76 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 77 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 78 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 79 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 80 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 81 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 82 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 83 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 84 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 85 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 86 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 87 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 88 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 89 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 90 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 91 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 92 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 93 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 94 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 95 | github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= 96 | github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 97 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 98 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 99 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 100 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 101 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 102 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 103 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 104 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 105 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 106 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 107 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 108 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 109 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 110 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 111 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 112 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 113 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 114 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 115 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 116 | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= 117 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 118 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 119 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 120 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 121 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 122 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 123 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 124 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 125 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 126 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 127 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 128 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 129 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 130 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 131 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 132 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 133 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 134 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 135 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 136 | github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= 137 | github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 138 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 139 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 140 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 141 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 142 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 143 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 144 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 145 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 146 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 147 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 148 | github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= 149 | github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 150 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 151 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 152 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 153 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 154 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 155 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 156 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 157 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 158 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 159 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 160 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 161 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 162 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 163 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 164 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 165 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 166 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 167 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 168 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 169 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 170 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 171 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 172 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 173 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 174 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 175 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 176 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 177 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 178 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 179 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 180 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 181 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 182 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 183 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 184 | github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= 185 | github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 186 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 187 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 188 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 189 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 190 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 191 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 192 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 193 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 194 | github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= 195 | github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= 196 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 197 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 198 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 199 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 200 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 201 | github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 202 | github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= 203 | github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= 204 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 205 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 206 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 207 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 208 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 209 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 210 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 211 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= 212 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 213 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 214 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 215 | github.com/xeipuuv/gojsonschema v0.0.0-20160323030313-93e72a773fad h1:LIwN+8bLzKvIuCiV5yT1nICcW/8yNfU5jVV1SHhcPco= 216 | github.com/xeipuuv/gojsonschema v0.0.0-20160323030313-93e72a773fad/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= 217 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 218 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 219 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 220 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 221 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 222 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 223 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 224 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 225 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 226 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 227 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 228 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 229 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 230 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 231 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 232 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 233 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 234 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 235 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 236 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 237 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 238 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 239 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 240 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 241 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 242 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 243 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 244 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 245 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 246 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 247 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 248 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 249 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 250 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 251 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 252 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 253 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 254 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 255 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 256 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 257 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 258 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 259 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 260 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 261 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 262 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 263 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 264 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 265 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 266 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 267 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 268 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 269 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 270 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 271 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 272 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 273 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 274 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 275 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 276 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 277 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 278 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 279 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 280 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 281 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 282 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= 283 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 284 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 285 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 286 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 287 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 288 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 289 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 290 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 291 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 292 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 293 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 294 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 295 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 296 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 297 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 298 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 299 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 300 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 301 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 302 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 303 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 304 | golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 305 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 306 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 307 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 308 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 309 | golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 310 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 311 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 312 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 313 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 314 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 315 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 316 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 317 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 318 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 319 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 320 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 321 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 322 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 323 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 324 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 325 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 326 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 327 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 328 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 329 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= 330 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 331 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 332 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 333 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 334 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 335 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 336 | google.golang.org/grpc v1.33.0 h1:IBKSUNL2uBS2DkJBncPP+TwT0sp9tgA8A75NjHt6umg= 337 | google.golang.org/grpc v1.33.0/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= 338 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 339 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 340 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 341 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 342 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 343 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 344 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 345 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 346 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 347 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 348 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 349 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 350 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 351 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 352 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 353 | gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= 354 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 355 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 356 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 357 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 358 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 359 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 360 | --------------------------------------------------------------------------------