From 0c0df403d1dfcb1ea9e39e602b7096750f3d5bb5 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Thu, 10 Sep 2020 12:27:46 -0500 Subject: [PATCH 01/15] Initial Overhaul Signed-off-by: jolheiser --- .gitignore | 7 +- .golangci.yml | 23 -- Makefile | 79 +--- README.md | 36 +- cmd/config.go | 35 -- cmd/list.go | 21 - cmd/remove.go | 56 --- cmd/server.go | 31 -- flags/flags.go | 219 ++++++++++ go.mod | 18 +- go.sum | 382 ++++++++++++++++++ main.go | 46 ++- modules/router/router.go | 59 --- modules/router/templates/foot.go | 6 - modules/router/templates/info.go | 5 - modules/router/templates/vanity.go | 9 - router/cache.go | 32 ++ router/cron.go | 60 +++ router/router.go | 72 ++++ router/templates/foot.go | 7 + router/templates/head.go | 23 ++ {modules/router => router}/templates/index.go | 2 +- router/templates/vanity.go | 10 + service/gitea.go | 75 ++++ service/github.go | 82 ++++ service/service.go | 85 ++++ vanity.service | 9 +- version/version.go | 3 + 28 files changed, 1145 insertions(+), 347 deletions(-) delete mode 100644 .golangci.yml delete mode 100644 cmd/config.go delete mode 100644 cmd/list.go delete mode 100644 cmd/remove.go delete mode 100644 cmd/server.go create mode 100644 flags/flags.go delete mode 100644 modules/router/router.go delete mode 100644 modules/router/templates/foot.go delete mode 100644 modules/router/templates/info.go delete mode 100644 modules/router/templates/vanity.go create mode 100644 router/cache.go create mode 100644 router/cron.go create mode 100644 router/router.go create mode 100644 router/templates/foot.go create mode 100644 router/templates/head.go rename {modules/router => router}/templates/index.go (71%) create mode 100644 router/templates/vanity.go create mode 100644 service/gitea.go create mode 100644 service/github.go create mode 100644 service/service.go create mode 100644 version/version.go diff --git a/.gitignore b/.gitignore index d4357fd..d40611e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # GoLand .idea -# Binaries -/vanity* -!vanity.service \ No newline at end of file +# Vanity +/vanity +/vanity.exe +.vanity.toml \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 6d71439..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,23 +0,0 @@ -linters: - enable: - - deadcode - - dogsled - - dupl - - errcheck - - funlen - - gocognit - - goconst - - gocritic - - gocyclo - - gofmt - - golint - - gosimple - - govet - - misspell - - prealloc - - staticcheck - - structcheck - - typecheck - - unparam - - unused - - varcheck \ No newline at end of file diff --git a/Makefile b/Makefile index 61f0049..b9739b7 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,10 @@ -DIST := dist GO ?= go -SHASUM ?= shasum -a 256 -ifneq ($(DRONE_TAG),) - VERSION ?= $(subst v,,$(DRONE_TAG)) - LONG_VERSION ?= $(VERSION) -else - ifneq ($(DRONE_BRANCH),) - VERSION ?= $(subst release/v,,$(DRONE_BRANCH)) - else - VERSION ?= master - endif - LONG_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') -endif - -LDFLAGS := $(LDFLAGS) -X "go.jolheiser.com/vanity/modules/config.Version=$(LONG_VERSION)" +VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') .PHONY: build build: - $(GO) build -ldflags '-s -w $(LDFLAGS)' - -.PHONY: lint -lint: - @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - export BINARY="golangci-lint"; \ - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1; \ - fi - golangci-lint run --timeout 5m + $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/version.Version=$(VERSION)"' .PHONY: fmt fmt: @@ -34,53 +12,8 @@ fmt: .PHONY: test test: - $(GO) test -race ./... + $(GO) test --race ./... -.PHONY: release -release: release-dirs check-xgo release-windows release-linux release-darwin release-copy release-compress release-check - -.PHONY: check-xgo -check-xgo: - @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u src.techknowlogick.com/xgo; \ - fi - -.PHONY: release-dirs -release-dirs: - mkdir -p $(DIST)/binaries $(DIST)/release - -.PHONY: release-windows -release-windows: - xgo -dest $(DIST)/binaries -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vanity-$(VERSION) . -ifeq ($(CI),drone) - cp /build/* $(DIST)/binaries -endif - -.PHONY: release-linux -release-linux: - xgo -dest $(DIST)/binaries -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out vanity-$(VERSION) . -ifeq ($(CI),drone) - cp /build/* $(DIST)/binaries -endif - -.PHONY: release-darwin -release-darwin: - xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vanity-$(VERSION) . -ifeq ($(CI),drone) - cp /build/* $(DIST)/binaries -endif - -.PHONY: release-copy -release-copy: - cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done; - -.PHONY: release-check -release-check: - cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done; - -.PHONY: release-compress -release-compress: - @hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \ - fi - cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done; \ No newline at end of file +.PHONY: vet +vet: + $(GO) vet ./... \ No newline at end of file diff --git a/README.md b/README.md index 094eb2c..664a5ac 100644 --- a/README.md +++ b/README.md @@ -3,5 +3,39 @@ A simple web service to serve vanity Go imports. Feel free to check it out using [my instance](https://go.jolheiser.com/). ## Configuration -See [the sample](config.sample.toml). + +``` +NAME: + vanity - Vanity Go Imports + +USAGE: + vanity [global options] command [command options] [arguments...] + +VERSION: + 0.1.0+2-g49cd123 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --config value Path to a config file (default: ".vanity.toml") [$VANITY_CONFIG] + --port value Port to run the vanity server on (default: 7777) [$VANITY_PORT] + --domain value Domain, e.g. go.domain.tld [$VANITY_DOMAIN] + --service value Service type (Gitea, GitHub, GitLab) (default: "gitea") [$VANITY_SERVICE] + --base-url value Base URL to service [$VANITY_BASE_URL] + --namespace value Owner namespace [$VANITY_NAMESPACE] + --token value Access token [$VANITY_TOKEN] + --include value Repository names to include (regex) [$VANITY_INCLUDE] + --exclude value Repository names to exclude (regex) [$VANITY_EXCLUDE] + --private Include private repositories (default: false) [$VANITY_PRIVATE] + --fork Include forked repositories (default: false) [$VANITY_FORK] + --mirror Include mirrored repositories (default: false) [$VANITY_MIRROR] + --archive Include archived repositories (default: false) [$VANITY_ARCHIVE] + --interval value Interval between updating repositories (default: 15m0s) [$VANITY_INTERVAL] + --debug Debug logging (default: false) [$VANITY_DEBUG] + --help, -h show help (default: false) + --version, -v print the version (default: false) + +``` + Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). \ No newline at end of file diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 909eb69..0000000 --- a/cmd/config.go +++ /dev/null @@ -1,35 +0,0 @@ -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" -) - -var Config = cli.Command{ - Name: "config", - Aliases: []string{"cfg"}, - Usage: "Configure vanity", - Action: doConfig, -} - -func doConfig(ctx *cli.Context) error { - urlQuestion := &survey.Input{ - Message: "domain", - Default: "go.domain.tld", - } - var urlAnswer string - - if err := survey.AskOne(urlQuestion, &urlAnswer); err != nil { - return err - } - - config.Domain = urlAnswer - if err := config.Save(); err != nil { - return err - } - - beaver.Info("domain saved!") - return nil -} diff --git a/cmd/list.go b/cmd/list.go deleted file mode 100644 index ff7bbbf..0000000 --- a/cmd/list.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" -) - -var List = cli.Command{ - Name: "list", - Aliases: []string{"l"}, - Usage: "List packages", - Action: doList, -} - -func doList(ctx *cli.Context) error { - for _, pkg := range config.Packages { - beaver.Infof("%s (%s) -> %s | %s", pkg.Name, pkg.Path, pkg.Repo, pkg.SSH) - } - return nil -} diff --git a/cmd/remove.go b/cmd/remove.go deleted file mode 100644 index c556891..0000000 --- a/cmd/remove.go +++ /dev/null @@ -1,56 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" - "strings" -) - -var Remove = cli.Command{ - Name: "remove", - Aliases: []string{"rm"}, - Usage: "Remove a package", - Action: doRemove, -} - -func doRemove(ctx *cli.Context) error { - pkgQuestion := &survey.Input{ - Message: "Package name", - } - var pkgAnswer string - - if err := survey.AskOne(pkgQuestion, &pkgAnswer); err != nil { - return err - } - - for idx, p := range config.Packages { - if strings.EqualFold(p.Name, pkgAnswer) { - confirm := &survey.Confirm{ - Message: fmt.Sprintf("Are you sure you want to remove %s (%s) ?", p.Name, p.Repo), - Default: false, - } - var answer bool - - if err := survey.AskOne(confirm, &answer); err != nil { - return err - } - - if answer { - config.Packages = append(config.Packages[:idx], config.Packages[idx+1:]...) - if err := config.Save(); err != nil { - return err - } - beaver.Infof("Removed `%s` from vanity.", p.Name) - break - } - - beaver.Infof("Did not remove `%s` from vanity.", p.Name) - break - } - } - - return nil -} diff --git a/cmd/server.go b/cmd/server.go deleted file mode 100644 index 36b102e..0000000 --- a/cmd/server.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/router" - "net/http" -) - -var Server = cli.Command{ - Name: "server", - Usage: "Start the vanity server", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "port", - Aliases: []string{"p"}, - Usage: "Port to run the vanity server on", - Value: "3333", - }, - }, - Action: doServer, -} - -func doServer(ctx *cli.Context) error { - beaver.Infof("Running vanity server at http://localhost:%s", ctx.String("port")) - if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { - return err - } - return nil -} diff --git a/flags/flags.go b/flags/flags.go new file mode 100644 index 0000000..f861191 --- /dev/null +++ b/flags/flags.go @@ -0,0 +1,219 @@ +package flags + +import ( + "github.com/BurntSushi/toml" + "os" + "regexp" + "strings" + "time" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +type flagConfig struct { + Port int `toml:"port"` + Domain string `toml:"domain"` + Service string `toml:"service"` + BaseURL string `toml:"base_url"` + Namespace string `toml:"namespace"` + Token string `toml:"token"` + Include []*regexp.Regexp `toml:"-"` + Exclude []*regexp.Regexp `toml:"-"` + Private bool `toml:"private"` + Fork bool `toml:"fork"` + Mirror bool `toml:"mirror"` + Archive bool `toml:"archive"` + Interval time.Duration `toml:"interval"` + Debug bool `toml:"debug"` + + Overrides map[string]nameOverride `toml:"overrides"` +} + +type nameOverride struct { + Name string `toml:"name"` +} + +var ( + Config = flagConfig{ + Overrides: make(map[string]nameOverride), + } + + configPath string + include cli.StringSlice + exclude cli.StringSlice +) + +var Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Usage: "Path to a config file", + EnvVars: []string{"VANITY_CONFIG"}, + Destination: &configPath, + }, + &cli.IntFlag{ + Name: "port", + Usage: "Port to run the vanity server on", + Value: 7777, + EnvVars: []string{"VANITY_PORT"}, + Destination: &Config.Port, + }, + &cli.StringFlag{ + Name: "domain", + Usage: "Domain, e.g. go.domain.tld", + EnvVars: []string{"VANITY_DOMAIN"}, + Required: true, + Destination: &Config.Domain, + }, + &cli.StringFlag{ + Name: "service", + Usage: "Service type (Gitea, GitHub, GitLab)", + Value: "gitea", + EnvVars: []string{"VANITY_SERVICE"}, + Destination: &Config.Service, + }, + &cli.StringFlag{ + Name: "base-url", + Usage: "Base URL to service", + EnvVars: []string{"VANITY_BASE_URL"}, + Required: true, + Destination: &Config.BaseURL, + }, + &cli.StringFlag{ + Name: "namespace", + Usage: "Owner namespace", + EnvVars: []string{"VANITY_NAMESPACE"}, + Required: true, + Destination: &Config.Namespace, + }, + &cli.StringFlag{ + Name: "token", + Usage: "Access token", + EnvVars: []string{"VANITY_TOKEN"}, + Required: true, + Destination: &Config.Token, + }, + &cli.StringSliceFlag{ + Name: "include", + Usage: "Repository names to include (regex)", + EnvVars: []string{"VANITY_INCLUDE"}, + Destination: &include, + }, + &cli.StringSliceFlag{ + Name: "exclude", + Usage: "Repository names to exclude (regex)", + EnvVars: []string{"VANITY_EXCLUDE"}, + Destination: &exclude, + }, + &cli.BoolFlag{ + Name: "private", + Usage: "Include private repositories", + EnvVars: []string{"VANITY_PRIVATE"}, + Destination: &Config.Private, + }, + &cli.BoolFlag{ + Name: "fork", + Usage: "Include forked repositories", + EnvVars: []string{"VANITY_FORK"}, + Destination: &Config.Private, + }, + &cli.BoolFlag{ + Name: "mirror", + Usage: "Include mirrored repositories", + EnvVars: []string{"VANITY_MIRROR"}, + Destination: &Config.Mirror, + }, + &cli.BoolFlag{ + Name: "archive", + Usage: "Include archived repositories", + EnvVars: []string{"VANITY_ARCHIVE"}, + Destination: &Config.Archive, + }, + &cli.DurationFlag{ + Name: "interval", + Usage: "Interval between updating repositories", + Value: time.Minute * 15, + EnvVars: []string{"VANITY_INTERVAL"}, + Destination: &Config.Interval, + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Debug logging", + EnvVars: []string{"VANITY_DEBUG"}, + Destination: &Config.Debug, + }, +} + +func Before(ctx *cli.Context) error { + setConfig(ctx) + + Config.Include = make([]*regexp.Regexp, len(include.Value())) + for idx, i := range include.Value() { + Config.Include[idx] = regexp.MustCompile(i) + } + + Config.Exclude = make([]*regexp.Regexp, len(exclude.Value())) + for idx, e := range exclude.Value() { + Config.Exclude[idx] = regexp.MustCompile(e) + } + + if Config.Debug { + beaver.Console.Level = beaver.DEBUG + } + + return nil +} + +func setConfig(ctx *cli.Context) { + for _, env := range os.Environ() { + kv := strings.Split(env, "=") + if strings.HasPrefix(kv[0], "VANITY_OVERRIDES_") { + override := strings.ToLower(strings.TrimPrefix(kv[0], "VANITY_OVERRIDES_")) + Config.Overrides[override] = nameOverride{kv[1]} + } + } + + var cfg flagConfig + if configPath != "" { + beaver.Infof("Loading configuration from %s", configPath) + _, err := toml.DecodeFile(configPath, &cfg) + if err != nil { + beaver.Errorf("Could not load configuration from %s: %v", configPath, err) + return + } + } + + if !ctx.IsSet("port") && cfg.Port > 0 { + Config.Port = cfg.Port + } + if !ctx.IsSet("domain") && cfg.Domain != "" { + Config.Domain = cfg.Domain + } + if !ctx.IsSet("service") && cfg.Service != "" { + Config.Service = cfg.Service + } + if !ctx.IsSet("base-url") && cfg.BaseURL != "" { + Config.BaseURL = cfg.BaseURL + } + if !ctx.IsSet("namespace") && cfg.Namespace != "" { + Config.Namespace = cfg.Namespace + } + if !ctx.IsSet("token") && cfg.Token != "" { + Config.Token = cfg.Token + } + if !ctx.IsSet("private") && cfg.Private { + Config.Private = cfg.Private + } + if !ctx.IsSet("fork") && cfg.Fork { + Config.Fork = cfg.Fork + } + if !ctx.IsSet("mirror") && cfg.Mirror { + Config.Mirror = cfg.Mirror + } + if !ctx.IsSet("archive") && cfg.Archive { + Config.Archive = cfg.Archive + } + if !ctx.IsSet("debug") && cfg.Debug { + Config.Debug = cfg.Debug + } +} diff --git a/go.mod b/go.mod index 5f45c19..f6496db 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,19 @@ module go.jolheiser.com/vanity go 1.12 require ( - github.com/AlecAivazis/survey/v2 v2.0.5 + code.gitea.io/sdk/gitea v0.12.2 + github.com/AlecAivazis/survey/v2 v2.1.1 github.com/BurntSushi/toml v0.3.1 - github.com/go-chi/chi v4.0.3+incompatible + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/go-chi/chi v4.1.2+incompatible + github.com/google/go-github/v32 v32.1.0 + github.com/mattn/go-colorable v0.1.7 // indirect + github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/stretchr/testify v1.3.0 // indirect - github.com/urfave/cli/v2 v2.1.1 - go.jolheiser.com/beaver v1.0.1 + github.com/urfave/cli/v2 v2.2.0 + go.jolheiser.com/beaver v1.0.2 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect + golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 + golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect + golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index cf31248..731c883 100644 --- a/go.sum +++ b/go.sum @@ -1,32 +1,158 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.gitea.io/sdk v0.0.0-20200910013755-dd1ecd4b3d26 h1:fCCTRT/PTo7NXAtR3HW/6L7V0CYVMc1x0Fzzwc8E95M= +code.gitea.io/sdk/gitea v0.12.2 h1:NQI8b/CT9AEQjsxbVIZ6gsPUXv38moT5y1ocN7n1YcQ= +code.gitea.io/sdk/gitea v0.12.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AlecAivazis/survey/v2 v2.0.5 h1:xpZp+Q55wi5C7Iaze+40onHnEkex1jSc34CltJjOoPM= github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= +github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= +github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= +github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= +github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= @@ -35,25 +161,281 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.jolheiser.com/beaver v1.0.1 h1:gt3aGEr5Bj4ZjDF1g8t8OYOGRCRXGaanGR9CmXUxez8= go.jolheiser.com/beaver v1.0.1/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= +go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index c39894d..3d9e74c 100644 --- a/main.go +++ b/main.go @@ -1,31 +1,43 @@ package main import ( + "fmt" + "net/http" + "os" + + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/router" + "go.jolheiser.com/vanity/version" + "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/cmd" - "go.jolheiser.com/vanity/modules/config" - "os" ) func main() { - - // config loads on init - app := cli.NewApp() app.Name = "vanity" app.Usage = "Vanity Go Imports" - app.Version = config.Version - app.Commands = []*cli.Command{ - &cmd.Add, - &cmd.Remove, - &cmd.List, - &cmd.Config, - &cmd.Server, + app.Version = version.Version + app.Action = doAction + app.Flags = flags.Flags + app.Before = flags.Before + + beaver.Console.Format = beaver.FormatOptions{ + TimePrefix: true, + StackPrefix: true, + StackLimit: 15, + LevelPrefix: true, + LevelColor: true, } - app.EnableBashCompletion = true - err := app.Run(os.Args) - if err != nil { - beaver.Error(err) + + if err := app.Run(os.Args); err != nil { + beaver.Fatal(err) } } + +func doAction(ctx *cli.Context) error { + if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { + return err + } + return nil +} diff --git a/modules/router/router.go b/modules/router/router.go deleted file mode 100644 index a2bb0d2..0000000 --- a/modules/router/router.go +++ /dev/null @@ -1,59 +0,0 @@ -package router - -import ( - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" - "go.jolheiser.com/vanity/modules/router/templates" - "html/template" - "net/http" - "runtime" - "strings" - "time" -) - -var ( - index = template.Must(template.New("index").Parse(templates.Head + templates.Index + templates.Info + templates.Foot)) - vanity = template.Must(template.New("vanity").Parse(templates.Head + templates.Vanity + templates.Info + templates.Foot)) - cache = config.PackageMap() -) - -func Init() *chi.Mux { - r := chi.NewRouter() - r.Use(middleware.RedirectSlashes) - r.Use(middleware.Recoverer) - r.Use(middleware.Timeout(30 * time.Second)) - - r.Get("/", doGet) - r.Get("/*", doPackage) - - return r -} - -func doGet(res http.ResponseWriter, req *http.Request) { - if err := index.Execute(res, map[string]interface{}{ - "Packages": config.Packages, - "AppVer": config.Version, - "GoVer": runtime.Version(), - }); err != nil { - beaver.Error(err) - } -} - -func doPackage(res http.ResponseWriter, req *http.Request) { - key := chi.URLParam(req, "*") - pkg, ok := cache[strings.Split(key, "/")[0]] - if !ok { - http.NotFound(res, req) - return - } - - if err := vanity.Execute(res, map[string]interface{}{ - "Package": pkg, - "AppVer": config.Version, - "GoVer": runtime.Version(), - }); err != nil { - beaver.Error(err) - } -} diff --git a/modules/router/templates/foot.go b/modules/router/templates/foot.go deleted file mode 100644 index 23860a6..0000000 --- a/modules/router/templates/foot.go +++ /dev/null @@ -1,6 +0,0 @@ -package templates - -var Foot = ` - - -` diff --git a/modules/router/templates/info.go b/modules/router/templates/info.go deleted file mode 100644 index d187bcd..0000000 --- a/modules/router/templates/info.go +++ /dev/null @@ -1,5 +0,0 @@ -package templates - -var Info = ` -Version: {{.AppVer}} | {{.GoVer}} -` diff --git a/modules/router/templates/vanity.go b/modules/router/templates/vanity.go deleted file mode 100644 index c2fb2a0..0000000 --- a/modules/router/templates/vanity.go +++ /dev/null @@ -1,9 +0,0 @@ -package templates - -var Vanity = ` -

Index

-
-

Name: {{.Package.Name}}

-

Source: {{.Package.Repo}}

-

Description: {{.Package.Description}}

-` diff --git a/router/cache.go b/router/cache.go new file mode 100644 index 0000000..f343fc8 --- /dev/null +++ b/router/cache.go @@ -0,0 +1,32 @@ +package router + +import ( + "sync" + + "go.jolheiser.com/vanity/service" +) + +var cache = &packageCache{ + packages: make(map[string]*service.Package), +} + +type packageCache struct { + packages map[string]*service.Package + sync.Mutex +} + +func (c *packageCache) Update(packages map[string]*service.Package) { + c.Lock() + c.packages = packages + c.Unlock() +} + +func (c *packageCache) Names() []string { + names := make([]string, len(c.packages)) + idx := 0 + for name := range c.packages { + names[idx] = name + idx++ + } + return names +} diff --git a/router/cron.go b/router/cron.go new file mode 100644 index 0000000..b53ae45 --- /dev/null +++ b/router/cron.go @@ -0,0 +1,60 @@ +package router + +import ( + "strings" + "time" + + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/service" + + "go.jolheiser.com/beaver" +) + +var svc service.Service + +func cronStart() { + ticker := time.NewTicker(flags.Config.Interval) + for { + <-ticker.C + beaver.Debug("Running package update...") + cronUpdate() + beaver.Debugf("Finished package update: %s", cache.Names()) + } +} + +func cronUpdate() { + packages, err := svc.Packages() + if err != nil { + beaver.Errorf("could not update packages: %v", err) + return + } + + // Filter + for name, pkg := range packages { + if err := service.Check(pkg); err != nil { + beaver.Debug(err) + delete(packages, name) + continue + } + if !svc.GoMod(pkg) { + beaver.Debugf("%s isn't a Go project", pkg.Name) + delete(packages, name) + continue + } + beaver.Debugf("Including %s", pkg.Name) + } + + // Overrides + for name, pkg := range packages { + for key, override := range flags.Config.Overrides { + if strings.EqualFold(name, key) { + beaver.Debugf("Overriding %s -> %s", name, override.Name) + delete(packages, key) + pkg.Name = override.Name + packages[override.Name] = pkg + } + } + } + + cache.Update(packages) +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..12af25e --- /dev/null +++ b/router/router.go @@ -0,0 +1,72 @@ +package router + +import ( + "fmt" + "go.jolheiser.com/vanity/flags" + "html/template" + "net/http" + "runtime" + "strings" + "time" + + "go.jolheiser.com/vanity/router/templates" + "go.jolheiser.com/vanity/service" + "go.jolheiser.com/vanity/version" + + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "go.jolheiser.com/beaver" +) + +var ( + index = template.Must(template.New("index").Parse(templates.Head + templates.Index + templates.Foot)) + vanity = template.Must(template.New("vanity").Parse(templates.Head + templates.Vanity + templates.Foot)) +) + +func Init() *chi.Mux { + r := chi.NewRouter() + r.Use(middleware.RedirectSlashes) + r.Use(middleware.Recoverer) + r.Use(middleware.Timeout(30 * time.Second)) + + r.Get("/", doIndex) + r.Get("/*", doVanity) + + svc = service.New() + + beaver.Info("Warming up cache...") + cronUpdate() + beaver.Infof("Finished warming up cache: %s", cache.Names()) + go cronStart() + + beaver.Infof("Running vanity server at http://localhost:%d", flags.Config.Port) + return r +} + +func doIndex(res http.ResponseWriter, _ *http.Request) { + if err := index.Execute(res, map[string]interface{}{ + "Packages": cache.packages, + "AppVer": version.Version, + "GoVer": runtime.Version(), + }); err != nil { + beaver.Error(err) + } +} + +func doVanity(res http.ResponseWriter, req *http.Request) { + key := chi.URLParam(req, "*") + pkg, ok := cache.packages[strings.Split(key, "/")[0]] + if !ok { + http.NotFound(res, req) + return + } + + if err := vanity.Execute(res, map[string]interface{}{ + "Package": pkg, + "AppVer": version.Version, + "GoVer": runtime.Version(), + "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(), pkg.HTTP, svc.GoDir(pkg), svc.GoFile(pkg)), + }); err != nil { + beaver.Error(err) + } +} diff --git a/router/templates/foot.go b/router/templates/foot.go new file mode 100644 index 0000000..82e0492 --- /dev/null +++ b/router/templates/foot.go @@ -0,0 +1,7 @@ +package templates + +var Foot = ` +Version: {{.AppVer}} | {{.GoVer}} + + +` diff --git a/router/templates/head.go b/router/templates/head.go new file mode 100644 index 0000000..f42e825 --- /dev/null +++ b/router/templates/head.go @@ -0,0 +1,23 @@ +package templates + +var Head = ` + + + + + {{if .Package}} + + + + + + + + + + + {{end}} + Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} + + +` diff --git a/modules/router/templates/index.go b/router/templates/index.go similarity index 71% rename from modules/router/templates/index.go rename to router/templates/index.go index 4fc06c0..7e6845e 100644 --- a/modules/router/templates/index.go +++ b/router/templates/index.go @@ -6,7 +6,7 @@ var Index = `

Imports:

` diff --git a/router/templates/vanity.go b/router/templates/vanity.go new file mode 100644 index 0000000..1614b6b --- /dev/null +++ b/router/templates/vanity.go @@ -0,0 +1,10 @@ +package templates + +var Vanity = ` +

Index

+
+

Name: {{.Package.Name}}

+

Source: {{.Package.URL}}

+{{if .Package.Description}}

Description: {{.Package.Description}}

{{end}} +

Documentation: https://pkg.go.dev/{{.Package.Module}}

+` diff --git a/service/gitea.go b/service/gitea.go new file mode 100644 index 0000000..40e00b2 --- /dev/null +++ b/service/gitea.go @@ -0,0 +1,75 @@ +package service + +import ( + "fmt" + + "go.jolheiser.com/vanity/flags" + + "code.gitea.io/sdk/gitea" +) + +var _ Service = &Gitea{} + +func NewGitea() *Gitea { + client := gitea.NewClient(flags.Config.BaseURL, flags.Config.Token) + return &Gitea{ + client: client, + } +} + +type Gitea struct { + client *gitea.Client +} + +func (g Gitea) Packages() (map[string]*Package, error) { + packages := make(map[string]*Package) + page := 0 + for { + opts := gitea.ListReposOptions{ + ListOptions: gitea.ListOptions{ + Page: page, + PageSize: 50, + }, + } + + repos, err := g.client.ListUserRepos(flags.Config.Namespace, opts) + if err != nil { + return nil, err + } + + for _, repo := range repos { + packages[repo.Name] = &Package{ + Name: repo.Name, + Description: repo.Description, + Branch: repo.DefaultBranch, + URL: repo.HTMLURL, + HTTP: repo.CloneURL, + SSH: repo.SSHURL, + private: repo.Private, + fork: repo.Fork, + mirror: repo.Mirror, + archive: repo.Archived, + } + } + + page++ + if len(repos) == 0 { + break + } + } + + return packages, nil +} + +func (g Gitea) GoDir(pkg *Package) string { + return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.URL, pkg.Branch) +} + +func (g Gitea) GoFile(pkg *Package) string { + return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) +} + +func (g Gitea) GoMod(pkg *Package) bool { + _, err := g.client.GetFile(flags.Config.Namespace, pkg.Name, pkg.Branch, "go.mod") + return err == nil +} diff --git a/service/github.go b/service/github.go new file mode 100644 index 0000000..cbf6750 --- /dev/null +++ b/service/github.go @@ -0,0 +1,82 @@ +package service + +import ( + "context" + "fmt" + "github.com/google/go-github/v32/github" + "golang.org/x/oauth2" + + "go.jolheiser.com/vanity/flags" +) + +var _ Service = &GitHub{} + +func NewGitHub() *GitHub { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: flags.Config.Token}, + ) + client := oauth2.NewClient(context.Background(), ts) + return &GitHub{ + client: github.NewClient(client), + } +} + +type GitHub struct { + client *github.Client +} + +func (g GitHub) Packages() (map[string]*Package, error) { + packages := make(map[string]*Package) + page := 0 + for { + opts := github.RepositoryListOptions{ + ListOptions: github.ListOptions{ + Page: page, + PerPage: 50, + }, + } + + repos, _, err := g.client.Repositories.List(context.Background(), flags.Config.Namespace, &opts) + if err != nil { + return nil, err + } + + for _, repo := range repos { + packages[repo.GetName()] = &Package{ + Name: repo.GetName(), + Description: repo.GetDescription(), + Branch: repo.GetDefaultBranch(), + URL: repo.GetHTMLURL(), + HTTP: repo.GetCloneURL(), + SSH: repo.GetSSHURL(), + private: repo.GetPrivate(), + fork: repo.GetFork(), + mirror: false, + archive: repo.GetArchived(), + } + } + + page++ + if len(repos) == 0 { + break + } + } + + return packages, nil +} + +func (g GitHub) GoDir(pkg *Package) string { + return fmt.Sprintf("%s/tree/%s{/dir}", pkg.URL, pkg.Branch) +} + +func (g GitHub) GoFile(pkg *Package) string { + return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) +} + +func (g GitHub) GoMod(pkg *Package) bool { + _, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Config.Namespace, pkg.Name, "go.mod", + &github.RepositoryContentGetOptions{ + Ref: pkg.Branch, + }) + return err == nil +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..e2866b8 --- /dev/null +++ b/service/service.go @@ -0,0 +1,85 @@ +package service + +import ( + "fmt" + "strings" + + "go.jolheiser.com/vanity/flags" +) + +type Package struct { + Name string + Description string + Branch string + URL string + HTTP string + SSH string + + private bool + fork bool + mirror bool + archive bool +} + +func (p *Package) Module() string { + return fmt.Sprintf("%s/%s", flags.Config.Domain, strings.ToLower(p.Name)) +} + +type Service interface { + Packages() (map[string]*Package, error) + GoDir(*Package) string + GoFile(*Package) string + GoMod(*Package) bool +} + +func New() Service { + switch strings.ToLower(flags.Config.Service) { + case "gitea": + return NewGitea() + case "github": + return NewGitHub() + } + return nil +} + +func Check(pkg *Package) error { + + // Private + if pkg.private && !flags.Config.Private { + return fmt.Errorf("%s is private and --private wasn't used", pkg.Name) + } + + // Forked + if pkg.fork && !flags.Config.Fork { + return fmt.Errorf("%s is a fork and --fork wasn't used", pkg.Name) + } + + // Mirrored + if pkg.mirror && !flags.Config.Mirror { + return fmt.Errorf("%s is a mirror and --mirror wasn't used", pkg.Name) + } + + // Archived + if pkg.archive && !flags.Config.Archive { + return fmt.Errorf("%s is archived and --archive wasn't used", pkg.Name) + } + + // Exclusions + for _, exclude := range flags.Config.Exclude { + if exclude.MatchString(pkg.Name) { + return fmt.Errorf("%s is was excluded by the rule %s", pkg.Name, exclude.String()) + } + } + + // Inclusions + if len(flags.Config.Include) > 0 { + for _, include := range flags.Config.Include { + if include.MatchString(pkg.Name) { + return fmt.Errorf("%s is was included by the rule %s", pkg.Name, include.String()) + } + } + return fmt.Errorf("%s is wasn't included by any existing inclusion rule", pkg.Name) + } + + return nil +} diff --git a/vanity.service b/vanity.service index 283b935..a8efdbc 100644 --- a/vanity.service +++ b/vanity.service @@ -8,9 +8,14 @@ RestartSec=2s Type=simple User=vanity Group=vanity -ExecStart=/usr/local/bin/vanity server -p 7777 +ExecStart=/usr/local/bin/vanity Restart=always -Environment=USER=vanity HOME=/var/lib/vanity + +# Required +Environment=VANITY_BASE_URL= +Environment=VANITY_NAMESPACE= +Environment=VANITY_TOKEN= +Environment=VANITY_DOMAIN= [Install] WantedBy=multi-user.target \ No newline at end of file diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..933d4fa --- /dev/null +++ b/version/version.go @@ -0,0 +1,3 @@ +package version + +var Version = "develop" -- 2.41.0 From 6d7150e0a2cf3a1b8f24a35c47b4d7f0f899110f Mon Sep 17 00:00:00 2001 From: jolheiser Date: Thu, 10 Sep 2020 13:43:38 -0500 Subject: [PATCH 02/15] Add GitLab support Signed-off-by: jolheiser --- cmd/add.go | 79 ------------------ flags/flags.go | 26 +++++- go.mod | 6 +- go.sum | 62 +++++--------- modules/config/config.go | 133 ------------------------------- modules/router/templates/head.go | 15 ---- router/router.go | 2 +- service/github.go | 9 ++- service/gitlab.go | 83 +++++++++++++++++++ service/service.go | 2 + 10 files changed, 134 insertions(+), 283 deletions(-) delete mode 100644 cmd/add.go delete mode 100644 modules/config/config.go delete mode 100644 modules/router/templates/head.go create mode 100644 service/gitlab.go diff --git a/cmd/add.go b/cmd/add.go deleted file mode 100644 index b85a35b..0000000 --- a/cmd/add.go +++ /dev/null @@ -1,79 +0,0 @@ -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" -) - -var Add = cli.Command{ - Name: "add", - Usage: "Add a package", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "force", - Aliases: []string{"f"}, - Usage: "Overwrite existing package without prompt", - }, - }, - Action: doAdd, -} - -func doAdd(ctx *cli.Context) error { - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name"}, - Validate: survey.Required, - }, - { - Name: "path", - Prompt: &survey.Input{Message: "Path"}, - Validate: survey.Required, - }, - { - Name: "repo", - Prompt: &survey.Input{Message: "Repository HTTP(S) URL"}, - Validate: survey.Required, - }, - { - Name: "ssh", - Prompt: &survey.Input{Message: "Repository SSH URL"}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description"}, - Validate: survey.Required, - }, - } - answers := struct { - Name string - Path string - Repo string - SSH string - Description string - }{} - - if err := survey.Ask(questions, &answers); err != nil { - return err - } - - pkg := config.Package{ - Name: answers.Name, - Path: answers.Path, - Repo: answers.Repo, - SSH: answers.SSH, - Description: answers.Description, - } - config.AddPackages(ctx.Bool("force"), pkg) - - if err := config.Save(); err != nil { - return err - } - - beaver.Infof("Added `%s` to vanity.", pkg.Name) - return nil -} diff --git a/flags/flags.go b/flags/flags.go index f861191..8cba56e 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -1,12 +1,13 @@ package flags import ( - "github.com/BurntSushi/toml" + "net/url" "os" "regexp" "strings" "time" + "github.com/BurntSushi/toml" "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" ) @@ -16,6 +17,7 @@ type flagConfig struct { Domain string `toml:"domain"` Service string `toml:"service"` BaseURL string `toml:"base_url"` + URL *url.URL `toml:"-"` Namespace string `toml:"namespace"` Token string `toml:"token"` Include []*regexp.Regexp `toml:"-"` @@ -60,7 +62,7 @@ var Flags = []cli.Flag{ }, &cli.StringFlag{ Name: "domain", - Usage: "Domain, e.g. go.domain.tld", + Usage: "Vanity domain, e.g. go.domain.tld", EnvVars: []string{"VANITY_DOMAIN"}, Required: true, Destination: &Config.Domain, @@ -74,9 +76,8 @@ var Flags = []cli.Flag{ }, &cli.StringFlag{ Name: "base-url", - Usage: "Base URL to service", + Usage: "BaseURL URL to service", EnvVars: []string{"VANITY_BASE_URL"}, - Required: true, Destination: &Config.BaseURL, }, &cli.StringFlag{ @@ -147,6 +148,23 @@ var Flags = []cli.Flag{ func Before(ctx *cli.Context) error { setConfig(ctx) + if Config.BaseURL == "" { + switch strings.ToLower(Config.Service) { + case "gitea": + Config.BaseURL = "https://gitea.com" + case "github": + Config.BaseURL = "https://github.com" + case "gitlab": + Config.BaseURL = "https://gitlab.com" + } + } + + u, err := url.Parse(Config.BaseURL) + if err != nil { + return err + } + Config.URL = u + Config.Include = make([]*regexp.Regexp, len(include.Value())) for idx, i := range include.Value() { Config.Include[idx] = regexp.MustCompile(i) diff --git a/go.mod b/go.mod index f6496db..5637c84 100644 --- a/go.mod +++ b/go.mod @@ -4,18 +4,14 @@ go 1.12 require ( code.gitea.io/sdk/gitea v0.12.2 - github.com/AlecAivazis/survey/v2 v2.1.1 github.com/BurntSushi/toml v0.3.1 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-chi/chi v4.1.2+incompatible github.com/google/go-github/v32 v32.1.0 - github.com/mattn/go-colorable v0.1.7 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/mitchellh/go-homedir v1.1.0 github.com/urfave/cli/v2 v2.2.0 + github.com/xanzy/go-gitlab v0.37.0 go.jolheiser.com/beaver v1.0.2 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect - golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index 731c883..8a0ffdb 100644 --- a/go.sum +++ b/go.sum @@ -30,19 +30,12 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -code.gitea.io/sdk v0.0.0-20200910013755-dd1ecd4b3d26 h1:fCCTRT/PTo7NXAtR3HW/6L7V0CYVMc1x0Fzzwc8E95M= code.gitea.io/sdk/gitea v0.12.2 h1:NQI8b/CT9AEQjsxbVIZ6gsPUXv38moT5y1ocN7n1YcQ= code.gitea.io/sdk/gitea v0.12.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AlecAivazis/survey/v2 v2.0.5 h1:xpZp+Q55wi5C7Iaze+40onHnEkex1jSc34CltJjOoPM= -github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= -github.com/AlecAivazis/survey/v2 v2.1.1 h1:LEMbHE0pLj75faaVEKClEX1TM4AJmmnOh9eimREzLWI= -github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -60,8 +53,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= -github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -100,8 +91,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= @@ -118,37 +109,25 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY= +github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= -github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -158,19 +137,16 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= -github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/xanzy/go-gitlab v0.37.0 h1:Z/CQkjj5VwbWVYVL7S70kS/TFj5H/pJumV7xbJ0YUQ8= +github.com/xanzy/go-gitlab v0.37.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.jolheiser.com/beaver v1.0.1 h1:gt3aGEr5Bj4ZjDF1g8t8OYOGRCRXGaanGR9CmXUxez8= -go.jolheiser.com/beaver v1.0.1/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -181,8 +157,6 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -220,6 +194,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -247,6 +222,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -264,13 +240,10 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -280,7 +253,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -304,6 +276,7 @@ golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -348,6 +321,7 @@ golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -366,6 +340,7 @@ google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -425,6 +400,7 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/modules/config/config.go b/modules/config/config.go deleted file mode 100644 index faa8b03..0000000 --- a/modules/config/config.go +++ /dev/null @@ -1,133 +0,0 @@ -package config - -import ( - "fmt" - "github.com/AlecAivazis/survey/v2" - "github.com/BurntSushi/toml" - "github.com/mitchellh/go-homedir" - "go.jolheiser.com/beaver" - "os" - "path" - "strings" -) - -var ( - configPath string - cfg *Config - Version = "develop" - - // Config items - - Domain string - Packages []Package -) - -type Config struct { - Domain string `toml:"domain"` - Packages []Package `toml:"package" json:"packages"` -} - -type Package struct { - Name string `toml:"name"` - Path string `toml:"path"` - Repo string `toml:"repo"` - SSH string `toml:"ssh"` - Description string `toml:"description"` -} - -func (pkg Package) Module() string { - return fmt.Sprintf("%s/%s", Domain, pkg.Path) -} - -// Load on init so that CLI contexts are correctly populated -func init() { - home, err := homedir.Dir() - if err != nil { - beaver.Fatalf("could not locate home directory: %v", err) - } - configPath = fmt.Sprintf("%s/.vanity/config.toml", home) - - if _, err := os.Stat(configPath); os.IsNotExist(err) { - if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil { - beaver.Fatalf("could not create vanity home: %v", err) - } - - if _, err := os.Create(configPath); err != nil { - beaver.Fatalf("could not create vanity config: %v", err) - } - } - - if _, err := toml.DecodeFile(configPath, &cfg); err != nil { - beaver.Fatalf("could not decode vanity config: %v", err) - } - - dupe := make(map[string]bool) - for _, pkg := range cfg.Packages { - name := strings.ToLower(pkg.Name) - if ok := dupe[name]; ok { - beaver.Fatalf("duplicate package for %s", pkg.Name) - } - dupe[name] = true - } - - Domain = cfg.Domain - Packages = cfg.Packages -} - -func Save() error { - cfg.Domain = Domain - cfg.Packages = Packages - - fi, err := os.Create(configPath) - if err != nil { - return err - } - defer fi.Close() - - if err := toml.NewEncoder(fi).Encode(cfg); err != nil { - return err - } - - return nil -} - -func PackageMap() map[string]Package { - pkgs := make(map[string]Package) - for _, pkg := range Packages { - pkgs[pkg.Path] = pkg - } - return pkgs -} - -func AddPackages(force bool, pkgs ...Package) { - for _, pkg := range pkgs { - for idx, p := range Packages { - if strings.EqualFold(p.Name, pkg.Name) { - if force { - Packages[idx] = pkg - break - } - - forceQuestion := &survey.Confirm{ - Message: fmt.Sprintf("Package `%s` (%s) already exists. Overwrite with `%s`?", p.Name, p.Repo, p.Repo), - Default: false, - } - var forceAnswer bool - - if err := survey.AskOne(forceQuestion, &forceAnswer); err != nil { - beaver.Error(err) - break - } - - if !forceAnswer { - beaver.Errorf("leaving package `%s` as-is", pkg.Name) - break - } - - Packages[idx] = pkg - break - } - } - Packages = append(Packages, pkg) - } -} diff --git a/modules/router/templates/head.go b/modules/router/templates/head.go deleted file mode 100644 index 92af7f1..0000000 --- a/modules/router/templates/head.go +++ /dev/null @@ -1,15 +0,0 @@ -package templates - -var Head = ` - - - - - {{if .Package}} - - - {{end}} - Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} - - -` diff --git a/router/router.go b/router/router.go index 12af25e..0d7daa7 100644 --- a/router/router.go +++ b/router/router.go @@ -2,13 +2,13 @@ package router import ( "fmt" - "go.jolheiser.com/vanity/flags" "html/template" "net/http" "runtime" "strings" "time" + "go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/router/templates" "go.jolheiser.com/vanity/service" "go.jolheiser.com/vanity/version" diff --git a/service/github.go b/service/github.go index cbf6750..42e742f 100644 --- a/service/github.go +++ b/service/github.go @@ -3,10 +3,11 @@ package service import ( "context" "fmt" - "github.com/google/go-github/v32/github" - "golang.org/x/oauth2" "go.jolheiser.com/vanity/flags" + + "github.com/google/go-github/v32/github" + "golang.org/x/oauth2" ) var _ Service = &GitHub{} @@ -16,8 +17,10 @@ func NewGitHub() *GitHub { &oauth2.Token{AccessToken: flags.Config.Token}, ) client := oauth2.NewClient(context.Background(), ts) + ghClient := github.NewClient(client) + ghClient.BaseURL = flags.Config.URL return &GitHub{ - client: github.NewClient(client), + client: ghClient, } } diff --git a/service/gitlab.go b/service/gitlab.go new file mode 100644 index 0000000..e9f7da2 --- /dev/null +++ b/service/gitlab.go @@ -0,0 +1,83 @@ +package service + +import ( + "fmt" + "html" + + "go.jolheiser.com/vanity/flags" + + "github.com/xanzy/go-gitlab" + "go.jolheiser.com/beaver" +) + +var _ Service = &GitLab{} + +func NewGitLab() *GitLab { + client, err := gitlab.NewClient(flags.Config.Token, gitlab.WithBaseURL(flags.Config.BaseURL)) + if err != nil { + beaver.Errorf("could not create GitLab client: %v", err) + } + return &GitLab{ + client: client, + } +} + +type GitLab struct { + client *gitlab.Client +} + +func (g GitLab) Packages() (map[string]*Package, error) { + packages := make(map[string]*Package) + page := 0 + for { + opts := gitlab.ListProjectsOptions{ + ListOptions: gitlab.ListOptions{ + Page: page, + PerPage: 50, + }, + } + + repos, _, err := g.client.Projects.ListUserProjects(flags.Config.Namespace, &opts) + if err != nil { + return nil, err + } + + for _, repo := range repos { + packages[repo.Name] = &Package{ + Name: repo.Name, + Description: repo.Description, + Branch: repo.DefaultBranch, + URL: repo.WebURL, + HTTP: repo.HTTPURLToRepo, + SSH: repo.SSHURLToRepo, + private: repo.Visibility != gitlab.PublicVisibility, + fork: repo.ForkedFromProject != nil, + mirror: repo.Mirror, + archive: repo.Archived, + } + } + + page++ + if len(repos) == 0 { + break + } + } + + return packages, nil +} + +func (g GitLab) GoDir(pkg *Package) string { + return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.URL, pkg.Branch) +} + +func (g GitLab) GoFile(pkg *Package) string { + return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) +} + +func (g GitLab) GoMod(pkg *Package) bool { + id := fmt.Sprintf("%s/%s", flags.Config.Namespace, pkg.Name) + _, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{ + Ref: &pkg.Branch, + }) + return err == nil +} diff --git a/service/service.go b/service/service.go index e2866b8..9fec061 100644 --- a/service/service.go +++ b/service/service.go @@ -38,6 +38,8 @@ func New() Service { return NewGitea() case "github": return NewGitHub() + case "gitlab": + return NewGitLab() } return nil } -- 2.41.0 From 35b809dc85aa731996deaa1ddd1398edbf26b12e Mon Sep 17 00:00:00 2001 From: jolheiser Date: Fri, 11 Sep 2020 20:38:18 -0500 Subject: [PATCH 03/15] Better config options, change package structure Signed-off-by: jolheiser --- Makefile | 12 ++- README.md | 46 ++++++++- api/package.go | 24 +++++ {version => api}/version.go | 2 +- config.sample.toml | 12 --- docker/Dockerfile | 11 ++ docker/docker-compose.yml | 14 +++ flags/config.go | 101 ++++++++++++++++++ flags/flags.go | 197 +++++++++++++++--------------------- main.go | 4 +- router/cache.go | 8 +- router/cron.go | 27 +++-- router/router.go | 10 +- router/templates/head.go | 4 +- router/templates/vanity.go | 2 +- service/gitea.go | 39 +++---- service/github.go | 44 ++++---- service/gitlab.go | 41 ++++---- service/off.go | 23 +++++ service/service.go | 48 +++------ 20 files changed, 422 insertions(+), 247 deletions(-) create mode 100644 api/package.go rename {version => api}/version.go (60%) delete mode 100644 config.sample.toml create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml create mode 100644 flags/config.go create mode 100644 service/off.go diff --git a/Makefile b/Makefile index b9739b7..fa8e027 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') .PHONY: build build: - $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/version.Version=$(VERSION)"' + $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/api.Version=$(VERSION)"' .PHONY: fmt fmt: @@ -16,4 +16,12 @@ test: .PHONY: vet vet: - $(GO) vet ./... \ No newline at end of file + $(GO) vet ./... + +.PHONY: docker-build +docker-build: + docker build -f docker/Dockerfile -t jolheiser/vanity . + +.PHONY: docker-push +docker-push: + docker push jolheiser/vanity \ No newline at end of file diff --git a/README.md b/README.md index 664a5ac..912686c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ A simple web service to serve vanity Go imports. Feel free to check it out using ## Configuration +When choosing a service, the default `base-url` will be the default server of that service: + +| Service | Default | +|:-------:|:------------------:| +| Gitea | https://gitea.com | +| GitHub | https://github.com | +| GitLab | https://gitlab.com | + +Configuration will be set, in order of priority + +1. Flags +2. Environment +3. Config file + ``` NAME: vanity - Vanity Go Imports @@ -12,15 +26,15 @@ USAGE: vanity [global options] command [command options] [arguments...] VERSION: - 0.1.0+2-g49cd123 + 0.1.0+3-g6d7150e COMMANDS: help, h Shows a list of commands or help for one command GLOBAL OPTIONS: - --config value Path to a config file (default: ".vanity.toml") [$VANITY_CONFIG] + --config value Path to a config file [$VANITY_CONFIG] --port value Port to run the vanity server on (default: 7777) [$VANITY_PORT] - --domain value Domain, e.g. go.domain.tld [$VANITY_DOMAIN] + --domain value Vanity domain, e.g. go.domain.tld [$VANITY_DOMAIN] --service value Service type (Gitea, GitHub, GitLab) (default: "gitea") [$VANITY_SERVICE] --base-url value Base URL to service [$VANITY_BASE_URL] --namespace value Owner namespace [$VANITY_NAMESPACE] @@ -31,11 +45,33 @@ GLOBAL OPTIONS: --fork Include forked repositories (default: false) [$VANITY_FORK] --mirror Include mirrored repositories (default: false) [$VANITY_MIRROR] --archive Include archived repositories (default: false) [$VANITY_ARCHIVE] + --override value Repository name to override (NAME=OVERRIDE) [$VANITY_OVERRIDE] --interval value Interval between updating repositories (default: 15m0s) [$VANITY_INTERVAL] --debug Debug logging (default: false) [$VANITY_DEBUG] --help, -h show help (default: false) --version, -v print the version (default: false) - ``` -Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). \ No newline at end of file +Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). + +## Docker + +```sh +docker run \ + --env VANITY_DOMAIN=go.domain.tld \ + --env VANITY_NAMESPACE= \ + --env VANITY_TOKEN= \ + --publish 80:7777 \ + --restart always + jolheiser/vanity:latest +``` + +## Overrides + +Certain modules may not align perfectly with their repository name. + +Overrides are available via config or by setting an environment variable `VANITY_OVERRIDE_PACKAGE=NAME` + +## Config-only Mode + +To run Vanity in config-only mode for packages, set `--service` to `off`. \ No newline at end of file diff --git a/api/package.go b/api/package.go new file mode 100644 index 0000000..afa0ac7 --- /dev/null +++ b/api/package.go @@ -0,0 +1,24 @@ +package api + +import ( + "fmt" + "strings" +) + +type Package struct { + Name string `toml:"name"` + Description string `toml:"description"` + Branch string `toml:"branch"` + WebURL string `toml:"web_url"` + CloneHTTP string `toml:"clone_http"` + CloneSSH string `toml:"clone_ssh"` + + Private bool `toml:"-"` + Fork bool `toml:"-"` + Mirror bool `toml:"-"` + Archive bool `toml:"-"` +} + +func (p *Package) Module(domain string) string { + return fmt.Sprintf("%s/%s", domain, strings.ToLower(p.Name)) +} diff --git a/version/version.go b/api/version.go similarity index 60% rename from version/version.go rename to api/version.go index 933d4fa..af9422a 100644 --- a/version/version.go +++ b/api/version.go @@ -1,3 +1,3 @@ -package version +package api var Version = "develop" diff --git a/config.sample.toml b/config.sample.toml deleted file mode 100644 index 1d90d76..0000000 --- a/config.sample.toml +++ /dev/null @@ -1,12 +0,0 @@ -# For each package, an entry like the following -[[package]] - # The name of the package (can be anything) - name = "Go-Vanity" - # The path to the package (this NEEDS to match the import path) - path = "vanity" - # The repository to direct go-import to - repo = "https://gitea.com/jolheiser/vanity.git" - # git-import for SSH (optional) - ssh = "git@gitea.com:jolheiser/vanity.git" - # A description of the project (optional) - description = "The code responsible for hosting this service!" diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..086fee7 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.15-alpine as builder +RUN apk --no-cache add build-base git +COPY . /app +WORKDIR /app +RUN make build + +FROM alpine:latest +LABEL maintainer="john.olheiser@gmail.com" +COPY --from=builder /app/vanity vanity +EXPOSE 7777 +ENTRYPOINT ["/vanity"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..8b20c7c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,14 @@ +version: "2" + +services: + vanity: + image: jolheiser/vanity:latest + environment: + - VANITY_DOMAIN=go.domain.tld + - VANITY_NAMESPACE= + - VANITY_TOKEN=\ + #- VANITY_SERVICE=gitea + #- VANITY_BASE_URL=https://gitea.com + restart: always + ports: + - "80:7777" \ No newline at end of file diff --git a/flags/config.go b/flags/config.go new file mode 100644 index 0000000..9becb12 --- /dev/null +++ b/flags/config.go @@ -0,0 +1,101 @@ +package flags + +import ( + "os" + "strings" + "time" + + "go.jolheiser.com/vanity/api" + + "github.com/BurntSushi/toml" + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +type tomlConfig struct { + Port int `toml:"port"` + Domain string `toml:"domain"` + Service string `toml:"service"` + BaseURL string `toml:"base_url"` + Namespace string `toml:"namespace"` + Token string `toml:"token"` + Include []string `toml:"include"` + Exclude []string `toml:"exclude"` + Private bool `toml:"private"` + Fork bool `toml:"fork"` + Mirror bool `toml:"mirror"` + Archive bool `toml:"archive"` + Override []string `toml:"override"` + Interval time.Duration `toml:"interval"` + Debug bool `toml:"debug"` + + Packages []*api.Package `toml:"packages"` +} + +func setConfig(ctx *cli.Context) { + for _, env := range os.Environ() { + kv := strings.Split(env, "=") + if strings.HasPrefix(kv[0], "VANITY_OVERRIDES_") { + override := strings.ToLower(strings.TrimPrefix(kv[0], "VANITY_OVERRIDES_")) + Override[override] = kv[1] + } + } + + var cfg tomlConfig + if configPath != "" { + beaver.Infof("Loading configuration from %s", configPath) + _, err := toml.DecodeFile(configPath, &cfg) + if err != nil { + beaver.Errorf("Could not load configuration from %s: %v", configPath, err) + return + } + } + + if !ctx.IsSet("port") && cfg.Port > 0 { + Port = cfg.Port + } + if !ctx.IsSet("domain") && cfg.Domain != "" { + Domain = cfg.Domain + } + if !ctx.IsSet("service") && cfg.Service != "" { + Service = cfg.Service + } + if !ctx.IsSet("base-url") && cfg.BaseURL != "" { + baseURL = cfg.BaseURL + } + if !ctx.IsSet("namespace") && cfg.Namespace != "" { + Namespace = cfg.Namespace + } + if !ctx.IsSet("token") && cfg.Token != "" { + Token = cfg.Token + } + if !ctx.IsSet("include") && len(cfg.Include) > 0 { + _ = include.Set(strings.Join(cfg.Include, ",")) + } + if !ctx.IsSet("exclude") && len(cfg.Exclude) > 0 { + _ = exclude.Set(strings.Join(cfg.Exclude, ",")) + } + if !ctx.IsSet("override") && len(cfg.Override) > 0 { + _ = override.Set(strings.Join(cfg.Override, ",")) + } + if !ctx.IsSet("private") && cfg.Private { + Private = cfg.Private + } + if !ctx.IsSet("fork") && cfg.Fork { + Fork = cfg.Fork + } + if !ctx.IsSet("mirror") && cfg.Mirror { + Mirror = cfg.Mirror + } + if !ctx.IsSet("archive") && cfg.Archive { + Archive = cfg.Archive + } + if !ctx.IsSet("interval") && cfg.Interval.Seconds() > 0 { + Interval = cfg.Interval + } + if !ctx.IsSet("debug") && cfg.Debug { + Debug = cfg.Debug + } + + ConfigPackages = cfg.Packages +} diff --git a/flags/flags.go b/flags/flags.go index 8cba56e..19b81c6 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -1,49 +1,43 @@ package flags import ( + "errors" + "fmt" "net/url" - "os" "regexp" "strings" "time" - "github.com/BurntSushi/toml" + "go.jolheiser.com/vanity/api" + "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" ) -type flagConfig struct { - Port int `toml:"port"` - Domain string `toml:"domain"` - Service string `toml:"service"` - BaseURL string `toml:"base_url"` - URL *url.URL `toml:"-"` - Namespace string `toml:"namespace"` - Token string `toml:"token"` - Include []*regexp.Regexp `toml:"-"` - Exclude []*regexp.Regexp `toml:"-"` - Private bool `toml:"private"` - Fork bool `toml:"fork"` - Mirror bool `toml:"mirror"` - Archive bool `toml:"archive"` - Interval time.Duration `toml:"interval"` - Debug bool `toml:"debug"` - - Overrides map[string]nameOverride `toml:"overrides"` -} - -type nameOverride struct { - Name string `toml:"name"` -} - var ( - Config = flagConfig{ - Overrides: make(map[string]nameOverride), - } - configPath string + baseURL string include cli.StringSlice exclude cli.StringSlice + override cli.StringSlice + + Port int + Domain string + Service string + BaseURL *url.URL + Namespace string + Token string + Include []*regexp.Regexp + Exclude []*regexp.Regexp + Private bool + Fork bool + Mirror bool + Archive bool + Override = make(map[string]string) + Interval time.Duration + Debug bool + + ConfigPackages []*api.Package ) var Flags = []cli.Flag{ @@ -58,41 +52,39 @@ var Flags = []cli.Flag{ Usage: "Port to run the vanity server on", Value: 7777, EnvVars: []string{"VANITY_PORT"}, - Destination: &Config.Port, + Destination: &Port, }, &cli.StringFlag{ Name: "domain", Usage: "Vanity domain, e.g. go.domain.tld", EnvVars: []string{"VANITY_DOMAIN"}, Required: true, - Destination: &Config.Domain, + Destination: &Domain, }, &cli.StringFlag{ Name: "service", Usage: "Service type (Gitea, GitHub, GitLab)", Value: "gitea", EnvVars: []string{"VANITY_SERVICE"}, - Destination: &Config.Service, + Destination: &Service, }, &cli.StringFlag{ Name: "base-url", - Usage: "BaseURL URL to service", + Usage: "Base URL to service", EnvVars: []string{"VANITY_BASE_URL"}, - Destination: &Config.BaseURL, + Destination: &baseURL, }, &cli.StringFlag{ Name: "namespace", Usage: "Owner namespace", EnvVars: []string{"VANITY_NAMESPACE"}, - Required: true, - Destination: &Config.Namespace, + Destination: &Namespace, }, &cli.StringFlag{ Name: "token", Usage: "Access token", EnvVars: []string{"VANITY_TOKEN"}, - Required: true, - Destination: &Config.Token, + Destination: &Token, }, &cli.StringSliceFlag{ Name: "include", @@ -110,128 +102,103 @@ var Flags = []cli.Flag{ Name: "private", Usage: "Include private repositories", EnvVars: []string{"VANITY_PRIVATE"}, - Destination: &Config.Private, + Destination: &Private, }, &cli.BoolFlag{ Name: "fork", Usage: "Include forked repositories", EnvVars: []string{"VANITY_FORK"}, - Destination: &Config.Private, + Destination: &Fork, }, &cli.BoolFlag{ Name: "mirror", Usage: "Include mirrored repositories", EnvVars: []string{"VANITY_MIRROR"}, - Destination: &Config.Mirror, + Destination: &Mirror, }, &cli.BoolFlag{ Name: "archive", Usage: "Include archived repositories", EnvVars: []string{"VANITY_ARCHIVE"}, - Destination: &Config.Archive, + Destination: &Archive, + }, + &cli.StringSliceFlag{ + Name: "override", + Usage: "Repository name to override (NAME=OVERRIDE)", + EnvVars: []string{"VANITY_OVERRIDE"}, + Destination: &override, }, &cli.DurationFlag{ Name: "interval", Usage: "Interval between updating repositories", Value: time.Minute * 15, EnvVars: []string{"VANITY_INTERVAL"}, - Destination: &Config.Interval, + Destination: &Interval, }, &cli.BoolFlag{ Name: "debug", Usage: "Debug logging", EnvVars: []string{"VANITY_DEBUG"}, - Destination: &Config.Debug, + Destination: &Debug, }, } func Before(ctx *cli.Context) error { setConfig(ctx) - if Config.BaseURL == "" { - switch strings.ToLower(Config.Service) { - case "gitea": - Config.BaseURL = "https://gitea.com" - case "github": - Config.BaseURL = "https://github.com" - case "gitlab": - Config.BaseURL = "https://gitlab.com" - } + var defaultURL string + var configOnly bool + switch strings.ToLower(Service) { + case "gitea": + defaultURL = "https://gitea.com" + case "github": + defaultURL = "https://github.com" + case "gitlab": + defaultURL = "https://gitlab.com" + case "off": + configOnly = true + beaver.Infof("Running in config-only mode") + defaultURL = "https://domain.tld" + default: + return errors.New("unrecognized service type") } - u, err := url.Parse(Config.BaseURL) + if baseURL == "" { + baseURL = defaultURL + } + + var err error + BaseURL, err = url.Parse(baseURL) if err != nil { return err } - Config.URL = u - Config.Include = make([]*regexp.Regexp, len(include.Value())) + if !configOnly { + errs := make([]string, 0, 2) + if Namespace == "" { + errs = append(errs, "namespace") + } + if Token == "" { + errs = append(errs, "token") + } + if len(errs) > 0 { + return fmt.Errorf("%s is required with a service", strings.Join(errs, ", ")) + } + } + + Include = make([]*regexp.Regexp, len(include.Value())) for idx, i := range include.Value() { - Config.Include[idx] = regexp.MustCompile(i) + Include[idx] = regexp.MustCompile(i) } - Config.Exclude = make([]*regexp.Regexp, len(exclude.Value())) + Exclude = make([]*regexp.Regexp, len(exclude.Value())) for idx, e := range exclude.Value() { - Config.Exclude[idx] = regexp.MustCompile(e) + Exclude[idx] = regexp.MustCompile(e) } - if Config.Debug { + if Debug { beaver.Console.Level = beaver.DEBUG } return nil } - -func setConfig(ctx *cli.Context) { - for _, env := range os.Environ() { - kv := strings.Split(env, "=") - if strings.HasPrefix(kv[0], "VANITY_OVERRIDES_") { - override := strings.ToLower(strings.TrimPrefix(kv[0], "VANITY_OVERRIDES_")) - Config.Overrides[override] = nameOverride{kv[1]} - } - } - - var cfg flagConfig - if configPath != "" { - beaver.Infof("Loading configuration from %s", configPath) - _, err := toml.DecodeFile(configPath, &cfg) - if err != nil { - beaver.Errorf("Could not load configuration from %s: %v", configPath, err) - return - } - } - - if !ctx.IsSet("port") && cfg.Port > 0 { - Config.Port = cfg.Port - } - if !ctx.IsSet("domain") && cfg.Domain != "" { - Config.Domain = cfg.Domain - } - if !ctx.IsSet("service") && cfg.Service != "" { - Config.Service = cfg.Service - } - if !ctx.IsSet("base-url") && cfg.BaseURL != "" { - Config.BaseURL = cfg.BaseURL - } - if !ctx.IsSet("namespace") && cfg.Namespace != "" { - Config.Namespace = cfg.Namespace - } - if !ctx.IsSet("token") && cfg.Token != "" { - Config.Token = cfg.Token - } - if !ctx.IsSet("private") && cfg.Private { - Config.Private = cfg.Private - } - if !ctx.IsSet("fork") && cfg.Fork { - Config.Fork = cfg.Fork - } - if !ctx.IsSet("mirror") && cfg.Mirror { - Config.Mirror = cfg.Mirror - } - if !ctx.IsSet("archive") && cfg.Archive { - Config.Archive = cfg.Archive - } - if !ctx.IsSet("debug") && cfg.Debug { - Config.Debug = cfg.Debug - } -} diff --git a/main.go b/main.go index 3d9e74c..85cce38 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,9 @@ import ( "net/http" "os" + "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/router" - "go.jolheiser.com/vanity/version" "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" @@ -17,7 +17,7 @@ func main() { app := cli.NewApp() app.Name = "vanity" app.Usage = "Vanity Go Imports" - app.Version = version.Version + app.Version = api.Version app.Action = doAction app.Flags = flags.Flags app.Before = flags.Before diff --git a/router/cache.go b/router/cache.go index f343fc8..c5a4c68 100644 --- a/router/cache.go +++ b/router/cache.go @@ -3,19 +3,19 @@ package router import ( "sync" - "go.jolheiser.com/vanity/service" + "go.jolheiser.com/vanity/api" ) var cache = &packageCache{ - packages: make(map[string]*service.Package), + packages: make(map[string]*api.Package), } type packageCache struct { - packages map[string]*service.Package + packages map[string]*api.Package sync.Mutex } -func (c *packageCache) Update(packages map[string]*service.Package) { +func (c *packageCache) Update(packages map[string]*api.Package) { c.Lock() c.packages = packages c.Unlock() diff --git a/router/cron.go b/router/cron.go index b53ae45..af6b21d 100644 --- a/router/cron.go +++ b/router/cron.go @@ -13,7 +13,7 @@ import ( var svc service.Service func cronStart() { - ticker := time.NewTicker(flags.Config.Interval) + ticker := time.NewTicker(flags.Interval) for { <-ticker.C beaver.Debug("Running package update...") @@ -36,8 +36,16 @@ func cronUpdate() { delete(packages, name) continue } - if !svc.GoMod(pkg) { - beaver.Debugf("%s isn't a Go project", pkg.Name) + goMod, err := svc.GoMod(pkg) + if err != nil { + beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) + delete(packages, name) + continue + } + lines := strings.Split(goMod, "\n") + line := strings.Fields(lines[0]) + if !strings.HasPrefix(line[1], flags.Domain) { + beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) delete(packages, name) continue } @@ -46,15 +54,20 @@ func cronUpdate() { // Overrides for name, pkg := range packages { - for key, override := range flags.Config.Overrides { + for key, override := range flags.Override { if strings.EqualFold(name, key) { - beaver.Debugf("Overriding %s -> %s", name, override.Name) + beaver.Debugf("Overriding %s -> %s", name, override) delete(packages, key) - pkg.Name = override.Name - packages[override.Name] = pkg + pkg.Name = override + packages[override] = pkg } } } + // Add packages manually added to config + for _, pkg := range flags.ConfigPackages { + packages[pkg.Name] = pkg + } + cache.Update(packages) } diff --git a/router/router.go b/router/router.go index 0d7daa7..1928511 100644 --- a/router/router.go +++ b/router/router.go @@ -8,10 +8,10 @@ import ( "strings" "time" + "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/router/templates" "go.jolheiser.com/vanity/service" - "go.jolheiser.com/vanity/version" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" @@ -39,14 +39,14 @@ func Init() *chi.Mux { beaver.Infof("Finished warming up cache: %s", cache.Names()) go cronStart() - beaver.Infof("Running vanity server at http://localhost:%d", flags.Config.Port) + beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) return r } func doIndex(res http.ResponseWriter, _ *http.Request) { if err := index.Execute(res, map[string]interface{}{ "Packages": cache.packages, - "AppVer": version.Version, + "AppVer": api.Version, "GoVer": runtime.Version(), }); err != nil { beaver.Error(err) @@ -63,9 +63,9 @@ func doVanity(res http.ResponseWriter, req *http.Request) { if err := vanity.Execute(res, map[string]interface{}{ "Package": pkg, - "AppVer": version.Version, + "AppVer": api.Version, "GoVer": runtime.Version(), - "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(), pkg.HTTP, svc.GoDir(pkg), svc.GoFile(pkg)), + "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, svc.GoDir(pkg), svc.GoFile(pkg)), }); err != nil { beaver.Error(err) } diff --git a/router/templates/head.go b/router/templates/head.go index f42e825..08ed1a5 100644 --- a/router/templates/head.go +++ b/router/templates/head.go @@ -11,11 +11,11 @@ var Head = ` - + - + {{end}} Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} diff --git a/router/templates/vanity.go b/router/templates/vanity.go index 1614b6b..bed416a 100644 --- a/router/templates/vanity.go +++ b/router/templates/vanity.go @@ -4,7 +4,7 @@ var Vanity = `

Index


Name: {{.Package.Name}}

-

Source: {{.Package.URL}}

+

Source: {{.Package.WebURL}}

{{if .Package.Description}}

Description: {{.Package.Description}}

{{end}}

Documentation: https://pkg.go.dev/{{.Package.Module}}

` diff --git a/service/gitea.go b/service/gitea.go index 40e00b2..49017cb 100644 --- a/service/gitea.go +++ b/service/gitea.go @@ -3,6 +3,7 @@ package service import ( "fmt" + "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" "code.gitea.io/sdk/gitea" @@ -11,7 +12,7 @@ import ( var _ Service = &Gitea{} func NewGitea() *Gitea { - client := gitea.NewClient(flags.Config.BaseURL, flags.Config.Token) + client := gitea.NewClient(flags.BaseURL.String(), flags.Token) return &Gitea{ client: client, } @@ -21,8 +22,8 @@ type Gitea struct { client *gitea.Client } -func (g Gitea) Packages() (map[string]*Package, error) { - packages := make(map[string]*Package) +func (g Gitea) Packages() (map[string]*api.Package, error) { + packages := make(map[string]*api.Package) page := 0 for { opts := gitea.ListReposOptions{ @@ -32,23 +33,23 @@ func (g Gitea) Packages() (map[string]*Package, error) { }, } - repos, err := g.client.ListUserRepos(flags.Config.Namespace, opts) + repos, err := g.client.ListUserRepos(flags.Namespace, opts) if err != nil { return nil, err } for _, repo := range repos { - packages[repo.Name] = &Package{ + packages[repo.Name] = &api.Package{ Name: repo.Name, Description: repo.Description, Branch: repo.DefaultBranch, - URL: repo.HTMLURL, - HTTP: repo.CloneURL, - SSH: repo.SSHURL, - private: repo.Private, - fork: repo.Fork, - mirror: repo.Mirror, - archive: repo.Archived, + WebURL: repo.HTMLURL, + CloneHTTP: repo.CloneURL, + CloneSSH: repo.SSHURL, + Private: repo.Private, + Fork: repo.Fork, + Mirror: repo.Mirror, + Archive: repo.Archived, } } @@ -61,15 +62,15 @@ func (g Gitea) Packages() (map[string]*Package, error) { return packages, nil } -func (g Gitea) GoDir(pkg *Package) string { - return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.URL, pkg.Branch) +func (g Gitea) GoDir(pkg *api.Package) string { + return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.WebURL, pkg.Branch) } -func (g Gitea) GoFile(pkg *Package) string { - return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) +func (g Gitea) GoFile(pkg *api.Package) string { + return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) } -func (g Gitea) GoMod(pkg *Package) bool { - _, err := g.client.GetFile(flags.Config.Namespace, pkg.Name, pkg.Branch, "go.mod") - return err == nil +func (g Gitea) GoMod(pkg *api.Package) (string, error) { + content, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") + return string(content), err } diff --git a/service/github.go b/service/github.go index 42e742f..a124ac9 100644 --- a/service/github.go +++ b/service/github.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" "github.com/google/go-github/v32/github" @@ -14,11 +15,11 @@ var _ Service = &GitHub{} func NewGitHub() *GitHub { ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: flags.Config.Token}, + &oauth2.Token{AccessToken: flags.Token}, ) client := oauth2.NewClient(context.Background(), ts) ghClient := github.NewClient(client) - ghClient.BaseURL = flags.Config.URL + ghClient.BaseURL = flags.BaseURL return &GitHub{ client: ghClient, } @@ -28,8 +29,8 @@ type GitHub struct { client *github.Client } -func (g GitHub) Packages() (map[string]*Package, error) { - packages := make(map[string]*Package) +func (g GitHub) Packages() (map[string]*api.Package, error) { + packages := make(map[string]*api.Package) page := 0 for { opts := github.RepositoryListOptions{ @@ -39,23 +40,23 @@ func (g GitHub) Packages() (map[string]*Package, error) { }, } - repos, _, err := g.client.Repositories.List(context.Background(), flags.Config.Namespace, &opts) + repos, _, err := g.client.Repositories.List(context.Background(), flags.Namespace, &opts) if err != nil { return nil, err } for _, repo := range repos { - packages[repo.GetName()] = &Package{ + packages[repo.GetName()] = &api.Package{ Name: repo.GetName(), Description: repo.GetDescription(), Branch: repo.GetDefaultBranch(), - URL: repo.GetHTMLURL(), - HTTP: repo.GetCloneURL(), - SSH: repo.GetSSHURL(), - private: repo.GetPrivate(), - fork: repo.GetFork(), - mirror: false, - archive: repo.GetArchived(), + WebURL: repo.GetHTMLURL(), + CloneHTTP: repo.GetCloneURL(), + CloneSSH: repo.GetSSHURL(), + Private: repo.GetPrivate(), + Fork: repo.GetFork(), + Mirror: false, + Archive: repo.GetArchived(), } } @@ -68,18 +69,21 @@ func (g GitHub) Packages() (map[string]*Package, error) { return packages, nil } -func (g GitHub) GoDir(pkg *Package) string { - return fmt.Sprintf("%s/tree/%s{/dir}", pkg.URL, pkg.Branch) +func (g GitHub) GoDir(pkg *api.Package) string { + return fmt.Sprintf("%s/tree/%s{/dir}", pkg.WebURL, pkg.Branch) } -func (g GitHub) GoFile(pkg *Package) string { - return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) +func (g GitHub) GoFile(pkg *api.Package) string { + return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) } -func (g GitHub) GoMod(pkg *Package) bool { - _, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Config.Namespace, pkg.Name, "go.mod", +func (g GitHub) GoMod(pkg *api.Package) (string, error) { + content, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Namespace, pkg.Name, "go.mod", &github.RepositoryContentGetOptions{ Ref: pkg.Branch, }) - return err == nil + if err != nil { + return "", err + } + return content.GetContent() } diff --git a/service/gitlab.go b/service/gitlab.go index e9f7da2..6adb5d7 100644 --- a/service/gitlab.go +++ b/service/gitlab.go @@ -4,6 +4,7 @@ import ( "fmt" "html" + "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" "github.com/xanzy/go-gitlab" @@ -13,7 +14,7 @@ import ( var _ Service = &GitLab{} func NewGitLab() *GitLab { - client, err := gitlab.NewClient(flags.Config.Token, gitlab.WithBaseURL(flags.Config.BaseURL)) + client, err := gitlab.NewClient(flags.Token, gitlab.WithBaseURL(flags.BaseURL.String())) if err != nil { beaver.Errorf("could not create GitLab client: %v", err) } @@ -26,8 +27,8 @@ type GitLab struct { client *gitlab.Client } -func (g GitLab) Packages() (map[string]*Package, error) { - packages := make(map[string]*Package) +func (g GitLab) Packages() (map[string]*api.Package, error) { + packages := make(map[string]*api.Package) page := 0 for { opts := gitlab.ListProjectsOptions{ @@ -37,23 +38,23 @@ func (g GitLab) Packages() (map[string]*Package, error) { }, } - repos, _, err := g.client.Projects.ListUserProjects(flags.Config.Namespace, &opts) + repos, _, err := g.client.Projects.ListUserProjects(flags.Namespace, &opts) if err != nil { return nil, err } for _, repo := range repos { - packages[repo.Name] = &Package{ + packages[repo.Name] = &api.Package{ Name: repo.Name, Description: repo.Description, Branch: repo.DefaultBranch, - URL: repo.WebURL, - HTTP: repo.HTTPURLToRepo, - SSH: repo.SSHURLToRepo, - private: repo.Visibility != gitlab.PublicVisibility, - fork: repo.ForkedFromProject != nil, - mirror: repo.Mirror, - archive: repo.Archived, + WebURL: repo.WebURL, + CloneHTTP: repo.HTTPURLToRepo, + CloneSSH: repo.SSHURLToRepo, + Private: repo.Visibility != gitlab.PublicVisibility, + Fork: repo.ForkedFromProject != nil, + Mirror: repo.Mirror, + Archive: repo.Archived, } } @@ -66,18 +67,18 @@ func (g GitLab) Packages() (map[string]*Package, error) { return packages, nil } -func (g GitLab) GoDir(pkg *Package) string { - return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.URL, pkg.Branch) +func (g GitLab) GoDir(pkg *api.Package) string { + return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.WebURL, pkg.Branch) } -func (g GitLab) GoFile(pkg *Package) string { - return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) +func (g GitLab) GoFile(pkg *api.Package) string { + return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) } -func (g GitLab) GoMod(pkg *Package) bool { - id := fmt.Sprintf("%s/%s", flags.Config.Namespace, pkg.Name) - _, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{ +func (g GitLab) GoMod(pkg *api.Package) (string, error) { + id := fmt.Sprintf("%s/%s", flags.Namespace, pkg.Name) + content, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{ Ref: &pkg.Branch, }) - return err == nil + return string(content), err } diff --git a/service/off.go b/service/off.go new file mode 100644 index 0000000..b029866 --- /dev/null +++ b/service/off.go @@ -0,0 +1,23 @@ +package service + +import "go.jolheiser.com/vanity/api" + +var _ Service = Off{} + +type Off struct{} + +func (o Off) Packages() (map[string]*api.Package, error) { + return make(map[string]*api.Package), nil +} + +func (o Off) GoDir(*api.Package) string { + return "" +} + +func (o Off) GoFile(*api.Package) string { + return "" +} + +func (o Off) GoMod(*api.Package) (string, error) { + return "", nil +} diff --git a/service/service.go b/service/service.go index 9fec061..3d7c32e 100644 --- a/service/service.go +++ b/service/service.go @@ -4,78 +4,62 @@ import ( "fmt" "strings" + "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" ) -type Package struct { - Name string - Description string - Branch string - URL string - HTTP string - SSH string - - private bool - fork bool - mirror bool - archive bool -} - -func (p *Package) Module() string { - return fmt.Sprintf("%s/%s", flags.Config.Domain, strings.ToLower(p.Name)) -} - type Service interface { - Packages() (map[string]*Package, error) - GoDir(*Package) string - GoFile(*Package) string - GoMod(*Package) bool + Packages() (map[string]*api.Package, error) + GoDir(*api.Package) string + GoFile(*api.Package) string + GoMod(*api.Package) (string, error) } func New() Service { - switch strings.ToLower(flags.Config.Service) { + switch strings.ToLower(flags.Service) { case "gitea": return NewGitea() case "github": return NewGitHub() case "gitlab": return NewGitLab() + default: + return Off{} } - return nil } -func Check(pkg *Package) error { +func Check(pkg *api.Package) error { // Private - if pkg.private && !flags.Config.Private { + if pkg.Private && !flags.Private { return fmt.Errorf("%s is private and --private wasn't used", pkg.Name) } // Forked - if pkg.fork && !flags.Config.Fork { + if pkg.Fork && !flags.Fork { return fmt.Errorf("%s is a fork and --fork wasn't used", pkg.Name) } // Mirrored - if pkg.mirror && !flags.Config.Mirror { + if pkg.Mirror && !flags.Mirror { return fmt.Errorf("%s is a mirror and --mirror wasn't used", pkg.Name) } // Archived - if pkg.archive && !flags.Config.Archive { + if pkg.Archive && !flags.Archive { return fmt.Errorf("%s is archived and --archive wasn't used", pkg.Name) } // Exclusions - for _, exclude := range flags.Config.Exclude { + for _, exclude := range flags.Exclude { if exclude.MatchString(pkg.Name) { return fmt.Errorf("%s is was excluded by the rule %s", pkg.Name, exclude.String()) } } // Inclusions - if len(flags.Config.Include) > 0 { - for _, include := range flags.Config.Include { + if len(flags.Include) > 0 { + for _, include := range flags.Include { if include.MatchString(pkg.Name) { return fmt.Errorf("%s is was included by the rule %s", pkg.Name, include.String()) } -- 2.41.0 From ab84fdea976823eaf594536e0d5bc6f53e429a64 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Fri, 11 Sep 2020 20:44:44 -0500 Subject: [PATCH 04/15] README cleanup Signed-off-by: jolheiser --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 912686c..18b6dd5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A simple web service to serve vanity Go imports. Feel free to check it out using [my instance](https://go.jolheiser.com/). +Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). + ## Configuration When choosing a service, the default `base-url` will be the default server of that service: @@ -12,12 +14,6 @@ When choosing a service, the default `base-url` will be the default server of th | GitHub | https://github.com | | GitLab | https://gitlab.com | -Configuration will be set, in order of priority - -1. Flags -2. Environment -3. Config file - ``` NAME: vanity - Vanity Go Imports @@ -52,8 +48,6 @@ GLOBAL OPTIONS: --version, -v print the version (default: false) ``` -Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). - ## Docker ```sh -- 2.41.0 From c8b30a9731e45c969d0bd21438d48f0c5a66c37a Mon Sep 17 00:00:00 2001 From: jolheiser Date: Fri, 11 Sep 2020 22:21:34 -0500 Subject: [PATCH 05/15] More debug output and template fixes Signed-off-by: jolheiser --- flags/flags.go | 13 +++++++++++++ router/router.go | 1 + router/templates/head.go | 2 +- router/templates/vanity.go | 2 +- service/service.go | 6 +++--- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/flags/flags.go b/flags/flags.go index 19b81c6..767691d 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -200,5 +200,18 @@ func Before(ctx *cli.Context) error { beaver.Console.Level = beaver.DEBUG } + beaver.Debugf("Port: %d", Port) + beaver.Debugf("Domain: %s", Domain) + beaver.Debugf("Service: %s", Service) + beaver.Debugf("Base URL: %s", baseURL) + beaver.Debugf("Namespace: %s", Namespace) + beaver.Debugf("Include: %s", include.Value()) + beaver.Debugf("Exclude: %s", exclude.Value()) + beaver.Debugf("Private: %t", Private) + beaver.Debugf("Fork: %t", Fork) + beaver.Debugf("Mirror: %t", Mirror) + beaver.Debugf("Archive: %t", Archive) + beaver.Debugf("Override: %s", override.Value()) + beaver.Debugf("Interval: %s", Interval) return nil } diff --git a/router/router.go b/router/router.go index 1928511..2a77243 100644 --- a/router/router.go +++ b/router/router.go @@ -63,6 +63,7 @@ func doVanity(res http.ResponseWriter, req *http.Request) { if err := vanity.Execute(res, map[string]interface{}{ "Package": pkg, + "Module": pkg.Module(flags.Domain), "AppVer": api.Version, "GoVer": runtime.Version(), "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, svc.GoDir(pkg), svc.GoFile(pkg)), diff --git a/router/templates/head.go b/router/templates/head.go index 08ed1a5..9fb8c57 100644 --- a/router/templates/head.go +++ b/router/templates/head.go @@ -11,7 +11,7 @@ var Head = ` - + diff --git a/router/templates/vanity.go b/router/templates/vanity.go index bed416a..8a76d19 100644 --- a/router/templates/vanity.go +++ b/router/templates/vanity.go @@ -6,5 +6,5 @@ var Vanity = `

Name: {{.Package.Name}}

Source: {{.Package.WebURL}}

{{if .Package.Description}}

Description: {{.Package.Description}}

{{end}} -

Documentation: https://pkg.go.dev/{{.Package.Module}}

+

Documentation: https://pkg.go.dev/{{.Module}}

` diff --git a/service/service.go b/service/service.go index 3d7c32e..2934fe8 100644 --- a/service/service.go +++ b/service/service.go @@ -53,7 +53,7 @@ func Check(pkg *api.Package) error { // Exclusions for _, exclude := range flags.Exclude { if exclude.MatchString(pkg.Name) { - return fmt.Errorf("%s is was excluded by the rule %s", pkg.Name, exclude.String()) + return fmt.Errorf("%s was excluded by the rule %s", pkg.Name, exclude.String()) } } @@ -61,10 +61,10 @@ func Check(pkg *api.Package) error { if len(flags.Include) > 0 { for _, include := range flags.Include { if include.MatchString(pkg.Name) { - return fmt.Errorf("%s is was included by the rule %s", pkg.Name, include.String()) + return fmt.Errorf("%s was included by the rule %s", pkg.Name, include.String()) } } - return fmt.Errorf("%s is wasn't included by any existing inclusion rule", pkg.Name) + return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) } return nil -- 2.41.0 From 693e58df7ff5cc0789a7d818a16e2d5ac191e951 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Sat, 12 Sep 2020 15:23:25 +0000 Subject: [PATCH 06/15] Vanity overhaul (#3) More debug output and template fixes Signed-off-by: jolheiser README cleanup Signed-off-by: jolheiser Better config options, change package structure Signed-off-by: jolheiser Add GitLab support Signed-off-by: jolheiser Initial Overhaul Signed-off-by: jolheiser Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/vanity/pulls/3 --- .gitignore | 7 +- .golangci.yml | 23 - Makefile | 83 +--- README.md | 68 ++- api/package.go | 24 + api/version.go | 3 + cmd/add.go | 79 ---- cmd/config.go | 35 -- cmd/list.go | 21 - cmd/remove.go | 56 --- cmd/server.go | 31 -- config.sample.toml | 12 - docker/Dockerfile | 11 + docker/docker-compose.yml | 14 + flags/config.go | 101 +++++ flags/flags.go | 217 +++++++++ go.mod | 16 +- go.sum | 422 ++++++++++++++++-- main.go | 46 +- modules/config/config.go | 133 ------ modules/router/router.go | 59 --- modules/router/templates/foot.go | 6 - modules/router/templates/head.go | 15 - modules/router/templates/info.go | 5 - modules/router/templates/vanity.go | 9 - router/cache.go | 32 ++ router/cron.go | 73 +++ router/router.go | 73 +++ router/templates/foot.go | 7 + router/templates/head.go | 23 + {modules/router => router}/templates/index.go | 2 +- router/templates/vanity.go | 10 + service/gitea.go | 76 ++++ service/github.go | 89 ++++ service/gitlab.go | 84 ++++ service/off.go | 23 + service/service.go | 71 +++ vanity.service | 9 +- 38 files changed, 1450 insertions(+), 618 deletions(-) delete mode 100644 .golangci.yml create mode 100644 api/package.go create mode 100644 api/version.go delete mode 100644 cmd/add.go delete mode 100644 cmd/config.go delete mode 100644 cmd/list.go delete mode 100644 cmd/remove.go delete mode 100644 cmd/server.go delete mode 100644 config.sample.toml create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml create mode 100644 flags/config.go create mode 100644 flags/flags.go delete mode 100644 modules/config/config.go delete mode 100644 modules/router/router.go delete mode 100644 modules/router/templates/foot.go delete mode 100644 modules/router/templates/head.go delete mode 100644 modules/router/templates/info.go delete mode 100644 modules/router/templates/vanity.go create mode 100644 router/cache.go create mode 100644 router/cron.go create mode 100644 router/router.go create mode 100644 router/templates/foot.go create mode 100644 router/templates/head.go rename {modules/router => router}/templates/index.go (71%) create mode 100644 router/templates/vanity.go create mode 100644 service/gitea.go create mode 100644 service/github.go create mode 100644 service/gitlab.go create mode 100644 service/off.go create mode 100644 service/service.go diff --git a/.gitignore b/.gitignore index d4357fd..d40611e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # GoLand .idea -# Binaries -/vanity* -!vanity.service \ No newline at end of file +# Vanity +/vanity +/vanity.exe +.vanity.toml \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 6d71439..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,23 +0,0 @@ -linters: - enable: - - deadcode - - dogsled - - dupl - - errcheck - - funlen - - gocognit - - goconst - - gocritic - - gocyclo - - gofmt - - golint - - gosimple - - govet - - misspell - - prealloc - - staticcheck - - structcheck - - typecheck - - unparam - - unused - - varcheck \ No newline at end of file diff --git a/Makefile b/Makefile index 61f0049..fa8e027 100644 --- a/Makefile +++ b/Makefile @@ -1,32 +1,10 @@ -DIST := dist GO ?= go -SHASUM ?= shasum -a 256 -ifneq ($(DRONE_TAG),) - VERSION ?= $(subst v,,$(DRONE_TAG)) - LONG_VERSION ?= $(VERSION) -else - ifneq ($(DRONE_BRANCH),) - VERSION ?= $(subst release/v,,$(DRONE_BRANCH)) - else - VERSION ?= master - endif - LONG_VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') -endif - -LDFLAGS := $(LDFLAGS) -X "go.jolheiser.com/vanity/modules/config.Version=$(LONG_VERSION)" +VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') .PHONY: build build: - $(GO) build -ldflags '-s -w $(LDFLAGS)' - -.PHONY: lint -lint: - @hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - export BINARY="golangci-lint"; \ - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.23.1; \ - fi - golangci-lint run --timeout 5m + $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/api.Version=$(VERSION)"' .PHONY: fmt fmt: @@ -34,53 +12,16 @@ fmt: .PHONY: test test: - $(GO) test -race ./... + $(GO) test --race ./... -.PHONY: release -release: release-dirs check-xgo release-windows release-linux release-darwin release-copy release-compress release-check +.PHONY: vet +vet: + $(GO) vet ./... -.PHONY: check-xgo -check-xgo: - @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u src.techknowlogick.com/xgo; \ - fi +.PHONY: docker-build +docker-build: + docker build -f docker/Dockerfile -t jolheiser/vanity . -.PHONY: release-dirs -release-dirs: - mkdir -p $(DIST)/binaries $(DIST)/release - -.PHONY: release-windows -release-windows: - xgo -dest $(DIST)/binaries -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vanity-$(VERSION) . -ifeq ($(CI),drone) - cp /build/* $(DIST)/binaries -endif - -.PHONY: release-linux -release-linux: - xgo -dest $(DIST)/binaries -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64,linux/mips64le,linux/mips,linux/mipsle' -out vanity-$(VERSION) . -ifeq ($(CI),drone) - cp /build/* $(DIST)/binaries -endif - -.PHONY: release-darwin -release-darwin: - xgo -dest $(DIST)/binaries -tags 'netgo osusergo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vanity-$(VERSION) . -ifeq ($(CI),drone) - cp /build/* $(DIST)/binaries -endif - -.PHONY: release-copy -release-copy: - cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done; - -.PHONY: release-check -release-check: - cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done; - -.PHONY: release-compress -release-compress: - @hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - $(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \ - fi - cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done; \ No newline at end of file +.PHONY: docker-push +docker-push: + docker push jolheiser/vanity \ No newline at end of file diff --git a/README.md b/README.md index 094eb2c..18b6dd5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,70 @@ A simple web service to serve vanity Go imports. Feel free to check it out using [my instance](https://go.jolheiser.com/). +Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). + ## Configuration -See [the sample](config.sample.toml). -Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). \ No newline at end of file + +When choosing a service, the default `base-url` will be the default server of that service: + +| Service | Default | +|:-------:|:------------------:| +| Gitea | https://gitea.com | +| GitHub | https://github.com | +| GitLab | https://gitlab.com | + +``` +NAME: + vanity - Vanity Go Imports + +USAGE: + vanity [global options] command [command options] [arguments...] + +VERSION: + 0.1.0+3-g6d7150e + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --config value Path to a config file [$VANITY_CONFIG] + --port value Port to run the vanity server on (default: 7777) [$VANITY_PORT] + --domain value Vanity domain, e.g. go.domain.tld [$VANITY_DOMAIN] + --service value Service type (Gitea, GitHub, GitLab) (default: "gitea") [$VANITY_SERVICE] + --base-url value Base URL to service [$VANITY_BASE_URL] + --namespace value Owner namespace [$VANITY_NAMESPACE] + --token value Access token [$VANITY_TOKEN] + --include value Repository names to include (regex) [$VANITY_INCLUDE] + --exclude value Repository names to exclude (regex) [$VANITY_EXCLUDE] + --private Include private repositories (default: false) [$VANITY_PRIVATE] + --fork Include forked repositories (default: false) [$VANITY_FORK] + --mirror Include mirrored repositories (default: false) [$VANITY_MIRROR] + --archive Include archived repositories (default: false) [$VANITY_ARCHIVE] + --override value Repository name to override (NAME=OVERRIDE) [$VANITY_OVERRIDE] + --interval value Interval between updating repositories (default: 15m0s) [$VANITY_INTERVAL] + --debug Debug logging (default: false) [$VANITY_DEBUG] + --help, -h show help (default: false) + --version, -v print the version (default: false) +``` + +## Docker + +```sh +docker run \ + --env VANITY_DOMAIN=go.domain.tld \ + --env VANITY_NAMESPACE= \ + --env VANITY_TOKEN= \ + --publish 80:7777 \ + --restart always + jolheiser/vanity:latest +``` + +## Overrides + +Certain modules may not align perfectly with their repository name. + +Overrides are available via config or by setting an environment variable `VANITY_OVERRIDE_PACKAGE=NAME` + +## Config-only Mode + +To run Vanity in config-only mode for packages, set `--service` to `off`. \ No newline at end of file diff --git a/api/package.go b/api/package.go new file mode 100644 index 0000000..afa0ac7 --- /dev/null +++ b/api/package.go @@ -0,0 +1,24 @@ +package api + +import ( + "fmt" + "strings" +) + +type Package struct { + Name string `toml:"name"` + Description string `toml:"description"` + Branch string `toml:"branch"` + WebURL string `toml:"web_url"` + CloneHTTP string `toml:"clone_http"` + CloneSSH string `toml:"clone_ssh"` + + Private bool `toml:"-"` + Fork bool `toml:"-"` + Mirror bool `toml:"-"` + Archive bool `toml:"-"` +} + +func (p *Package) Module(domain string) string { + return fmt.Sprintf("%s/%s", domain, strings.ToLower(p.Name)) +} diff --git a/api/version.go b/api/version.go new file mode 100644 index 0000000..af9422a --- /dev/null +++ b/api/version.go @@ -0,0 +1,3 @@ +package api + +var Version = "develop" diff --git a/cmd/add.go b/cmd/add.go deleted file mode 100644 index b85a35b..0000000 --- a/cmd/add.go +++ /dev/null @@ -1,79 +0,0 @@ -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" -) - -var Add = cli.Command{ - Name: "add", - Usage: "Add a package", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "force", - Aliases: []string{"f"}, - Usage: "Overwrite existing package without prompt", - }, - }, - Action: doAdd, -} - -func doAdd(ctx *cli.Context) error { - - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name"}, - Validate: survey.Required, - }, - { - Name: "path", - Prompt: &survey.Input{Message: "Path"}, - Validate: survey.Required, - }, - { - Name: "repo", - Prompt: &survey.Input{Message: "Repository HTTP(S) URL"}, - Validate: survey.Required, - }, - { - Name: "ssh", - Prompt: &survey.Input{Message: "Repository SSH URL"}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Input{Message: "Description"}, - Validate: survey.Required, - }, - } - answers := struct { - Name string - Path string - Repo string - SSH string - Description string - }{} - - if err := survey.Ask(questions, &answers); err != nil { - return err - } - - pkg := config.Package{ - Name: answers.Name, - Path: answers.Path, - Repo: answers.Repo, - SSH: answers.SSH, - Description: answers.Description, - } - config.AddPackages(ctx.Bool("force"), pkg) - - if err := config.Save(); err != nil { - return err - } - - beaver.Infof("Added `%s` to vanity.", pkg.Name) - return nil -} diff --git a/cmd/config.go b/cmd/config.go deleted file mode 100644 index 909eb69..0000000 --- a/cmd/config.go +++ /dev/null @@ -1,35 +0,0 @@ -package cmd - -import ( - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" -) - -var Config = cli.Command{ - Name: "config", - Aliases: []string{"cfg"}, - Usage: "Configure vanity", - Action: doConfig, -} - -func doConfig(ctx *cli.Context) error { - urlQuestion := &survey.Input{ - Message: "domain", - Default: "go.domain.tld", - } - var urlAnswer string - - if err := survey.AskOne(urlQuestion, &urlAnswer); err != nil { - return err - } - - config.Domain = urlAnswer - if err := config.Save(); err != nil { - return err - } - - beaver.Info("domain saved!") - return nil -} diff --git a/cmd/list.go b/cmd/list.go deleted file mode 100644 index ff7bbbf..0000000 --- a/cmd/list.go +++ /dev/null @@ -1,21 +0,0 @@ -package cmd - -import ( - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" -) - -var List = cli.Command{ - Name: "list", - Aliases: []string{"l"}, - Usage: "List packages", - Action: doList, -} - -func doList(ctx *cli.Context) error { - for _, pkg := range config.Packages { - beaver.Infof("%s (%s) -> %s | %s", pkg.Name, pkg.Path, pkg.Repo, pkg.SSH) - } - return nil -} diff --git a/cmd/remove.go b/cmd/remove.go deleted file mode 100644 index c556891..0000000 --- a/cmd/remove.go +++ /dev/null @@ -1,56 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" - "strings" -) - -var Remove = cli.Command{ - Name: "remove", - Aliases: []string{"rm"}, - Usage: "Remove a package", - Action: doRemove, -} - -func doRemove(ctx *cli.Context) error { - pkgQuestion := &survey.Input{ - Message: "Package name", - } - var pkgAnswer string - - if err := survey.AskOne(pkgQuestion, &pkgAnswer); err != nil { - return err - } - - for idx, p := range config.Packages { - if strings.EqualFold(p.Name, pkgAnswer) { - confirm := &survey.Confirm{ - Message: fmt.Sprintf("Are you sure you want to remove %s (%s) ?", p.Name, p.Repo), - Default: false, - } - var answer bool - - if err := survey.AskOne(confirm, &answer); err != nil { - return err - } - - if answer { - config.Packages = append(config.Packages[:idx], config.Packages[idx+1:]...) - if err := config.Save(); err != nil { - return err - } - beaver.Infof("Removed `%s` from vanity.", p.Name) - break - } - - beaver.Infof("Did not remove `%s` from vanity.", p.Name) - break - } - } - - return nil -} diff --git a/cmd/server.go b/cmd/server.go deleted file mode 100644 index 36b102e..0000000 --- a/cmd/server.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/router" - "net/http" -) - -var Server = cli.Command{ - Name: "server", - Usage: "Start the vanity server", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "port", - Aliases: []string{"p"}, - Usage: "Port to run the vanity server on", - Value: "3333", - }, - }, - Action: doServer, -} - -func doServer(ctx *cli.Context) error { - beaver.Infof("Running vanity server at http://localhost:%s", ctx.String("port")) - if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { - return err - } - return nil -} diff --git a/config.sample.toml b/config.sample.toml deleted file mode 100644 index 1d90d76..0000000 --- a/config.sample.toml +++ /dev/null @@ -1,12 +0,0 @@ -# For each package, an entry like the following -[[package]] - # The name of the package (can be anything) - name = "Go-Vanity" - # The path to the package (this NEEDS to match the import path) - path = "vanity" - # The repository to direct go-import to - repo = "https://gitea.com/jolheiser/vanity.git" - # git-import for SSH (optional) - ssh = "git@gitea.com:jolheiser/vanity.git" - # A description of the project (optional) - description = "The code responsible for hosting this service!" diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..086fee7 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,11 @@ +FROM golang:1.15-alpine as builder +RUN apk --no-cache add build-base git +COPY . /app +WORKDIR /app +RUN make build + +FROM alpine:latest +LABEL maintainer="john.olheiser@gmail.com" +COPY --from=builder /app/vanity vanity +EXPOSE 7777 +ENTRYPOINT ["/vanity"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..8b20c7c --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,14 @@ +version: "2" + +services: + vanity: + image: jolheiser/vanity:latest + environment: + - VANITY_DOMAIN=go.domain.tld + - VANITY_NAMESPACE= + - VANITY_TOKEN=\ + #- VANITY_SERVICE=gitea + #- VANITY_BASE_URL=https://gitea.com + restart: always + ports: + - "80:7777" \ No newline at end of file diff --git a/flags/config.go b/flags/config.go new file mode 100644 index 0000000..9becb12 --- /dev/null +++ b/flags/config.go @@ -0,0 +1,101 @@ +package flags + +import ( + "os" + "strings" + "time" + + "go.jolheiser.com/vanity/api" + + "github.com/BurntSushi/toml" + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +type tomlConfig struct { + Port int `toml:"port"` + Domain string `toml:"domain"` + Service string `toml:"service"` + BaseURL string `toml:"base_url"` + Namespace string `toml:"namespace"` + Token string `toml:"token"` + Include []string `toml:"include"` + Exclude []string `toml:"exclude"` + Private bool `toml:"private"` + Fork bool `toml:"fork"` + Mirror bool `toml:"mirror"` + Archive bool `toml:"archive"` + Override []string `toml:"override"` + Interval time.Duration `toml:"interval"` + Debug bool `toml:"debug"` + + Packages []*api.Package `toml:"packages"` +} + +func setConfig(ctx *cli.Context) { + for _, env := range os.Environ() { + kv := strings.Split(env, "=") + if strings.HasPrefix(kv[0], "VANITY_OVERRIDES_") { + override := strings.ToLower(strings.TrimPrefix(kv[0], "VANITY_OVERRIDES_")) + Override[override] = kv[1] + } + } + + var cfg tomlConfig + if configPath != "" { + beaver.Infof("Loading configuration from %s", configPath) + _, err := toml.DecodeFile(configPath, &cfg) + if err != nil { + beaver.Errorf("Could not load configuration from %s: %v", configPath, err) + return + } + } + + if !ctx.IsSet("port") && cfg.Port > 0 { + Port = cfg.Port + } + if !ctx.IsSet("domain") && cfg.Domain != "" { + Domain = cfg.Domain + } + if !ctx.IsSet("service") && cfg.Service != "" { + Service = cfg.Service + } + if !ctx.IsSet("base-url") && cfg.BaseURL != "" { + baseURL = cfg.BaseURL + } + if !ctx.IsSet("namespace") && cfg.Namespace != "" { + Namespace = cfg.Namespace + } + if !ctx.IsSet("token") && cfg.Token != "" { + Token = cfg.Token + } + if !ctx.IsSet("include") && len(cfg.Include) > 0 { + _ = include.Set(strings.Join(cfg.Include, ",")) + } + if !ctx.IsSet("exclude") && len(cfg.Exclude) > 0 { + _ = exclude.Set(strings.Join(cfg.Exclude, ",")) + } + if !ctx.IsSet("override") && len(cfg.Override) > 0 { + _ = override.Set(strings.Join(cfg.Override, ",")) + } + if !ctx.IsSet("private") && cfg.Private { + Private = cfg.Private + } + if !ctx.IsSet("fork") && cfg.Fork { + Fork = cfg.Fork + } + if !ctx.IsSet("mirror") && cfg.Mirror { + Mirror = cfg.Mirror + } + if !ctx.IsSet("archive") && cfg.Archive { + Archive = cfg.Archive + } + if !ctx.IsSet("interval") && cfg.Interval.Seconds() > 0 { + Interval = cfg.Interval + } + if !ctx.IsSet("debug") && cfg.Debug { + Debug = cfg.Debug + } + + ConfigPackages = cfg.Packages +} diff --git a/flags/flags.go b/flags/flags.go new file mode 100644 index 0000000..767691d --- /dev/null +++ b/flags/flags.go @@ -0,0 +1,217 @@ +package flags + +import ( + "errors" + "fmt" + "net/url" + "regexp" + "strings" + "time" + + "go.jolheiser.com/vanity/api" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var ( + configPath string + baseURL string + include cli.StringSlice + exclude cli.StringSlice + override cli.StringSlice + + Port int + Domain string + Service string + BaseURL *url.URL + Namespace string + Token string + Include []*regexp.Regexp + Exclude []*regexp.Regexp + Private bool + Fork bool + Mirror bool + Archive bool + Override = make(map[string]string) + Interval time.Duration + Debug bool + + ConfigPackages []*api.Package +) + +var Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "config", + Usage: "Path to a config file", + EnvVars: []string{"VANITY_CONFIG"}, + Destination: &configPath, + }, + &cli.IntFlag{ + Name: "port", + Usage: "Port to run the vanity server on", + Value: 7777, + EnvVars: []string{"VANITY_PORT"}, + Destination: &Port, + }, + &cli.StringFlag{ + Name: "domain", + Usage: "Vanity domain, e.g. go.domain.tld", + EnvVars: []string{"VANITY_DOMAIN"}, + Required: true, + Destination: &Domain, + }, + &cli.StringFlag{ + Name: "service", + Usage: "Service type (Gitea, GitHub, GitLab)", + Value: "gitea", + EnvVars: []string{"VANITY_SERVICE"}, + Destination: &Service, + }, + &cli.StringFlag{ + Name: "base-url", + Usage: "Base URL to service", + EnvVars: []string{"VANITY_BASE_URL"}, + Destination: &baseURL, + }, + &cli.StringFlag{ + Name: "namespace", + Usage: "Owner namespace", + EnvVars: []string{"VANITY_NAMESPACE"}, + Destination: &Namespace, + }, + &cli.StringFlag{ + Name: "token", + Usage: "Access token", + EnvVars: []string{"VANITY_TOKEN"}, + Destination: &Token, + }, + &cli.StringSliceFlag{ + Name: "include", + Usage: "Repository names to include (regex)", + EnvVars: []string{"VANITY_INCLUDE"}, + Destination: &include, + }, + &cli.StringSliceFlag{ + Name: "exclude", + Usage: "Repository names to exclude (regex)", + EnvVars: []string{"VANITY_EXCLUDE"}, + Destination: &exclude, + }, + &cli.BoolFlag{ + Name: "private", + Usage: "Include private repositories", + EnvVars: []string{"VANITY_PRIVATE"}, + Destination: &Private, + }, + &cli.BoolFlag{ + Name: "fork", + Usage: "Include forked repositories", + EnvVars: []string{"VANITY_FORK"}, + Destination: &Fork, + }, + &cli.BoolFlag{ + Name: "mirror", + Usage: "Include mirrored repositories", + EnvVars: []string{"VANITY_MIRROR"}, + Destination: &Mirror, + }, + &cli.BoolFlag{ + Name: "archive", + Usage: "Include archived repositories", + EnvVars: []string{"VANITY_ARCHIVE"}, + Destination: &Archive, + }, + &cli.StringSliceFlag{ + Name: "override", + Usage: "Repository name to override (NAME=OVERRIDE)", + EnvVars: []string{"VANITY_OVERRIDE"}, + Destination: &override, + }, + &cli.DurationFlag{ + Name: "interval", + Usage: "Interval between updating repositories", + Value: time.Minute * 15, + EnvVars: []string{"VANITY_INTERVAL"}, + Destination: &Interval, + }, + &cli.BoolFlag{ + Name: "debug", + Usage: "Debug logging", + EnvVars: []string{"VANITY_DEBUG"}, + Destination: &Debug, + }, +} + +func Before(ctx *cli.Context) error { + setConfig(ctx) + + var defaultURL string + var configOnly bool + switch strings.ToLower(Service) { + case "gitea": + defaultURL = "https://gitea.com" + case "github": + defaultURL = "https://github.com" + case "gitlab": + defaultURL = "https://gitlab.com" + case "off": + configOnly = true + beaver.Infof("Running in config-only mode") + defaultURL = "https://domain.tld" + default: + return errors.New("unrecognized service type") + } + + if baseURL == "" { + baseURL = defaultURL + } + + var err error + BaseURL, err = url.Parse(baseURL) + if err != nil { + return err + } + + if !configOnly { + errs := make([]string, 0, 2) + if Namespace == "" { + errs = append(errs, "namespace") + } + if Token == "" { + errs = append(errs, "token") + } + if len(errs) > 0 { + return fmt.Errorf("%s is required with a service", strings.Join(errs, ", ")) + } + } + + Include = make([]*regexp.Regexp, len(include.Value())) + for idx, i := range include.Value() { + Include[idx] = regexp.MustCompile(i) + } + + Exclude = make([]*regexp.Regexp, len(exclude.Value())) + for idx, e := range exclude.Value() { + Exclude[idx] = regexp.MustCompile(e) + } + + if Debug { + beaver.Console.Level = beaver.DEBUG + } + + beaver.Debugf("Port: %d", Port) + beaver.Debugf("Domain: %s", Domain) + beaver.Debugf("Service: %s", Service) + beaver.Debugf("Base URL: %s", baseURL) + beaver.Debugf("Namespace: %s", Namespace) + beaver.Debugf("Include: %s", include.Value()) + beaver.Debugf("Exclude: %s", exclude.Value()) + beaver.Debugf("Private: %t", Private) + beaver.Debugf("Fork: %t", Fork) + beaver.Debugf("Mirror: %t", Mirror) + beaver.Debugf("Archive: %t", Archive) + beaver.Debugf("Override: %s", override.Value()) + beaver.Debugf("Interval: %s", Interval) + return nil +} diff --git a/go.mod b/go.mod index 5f45c19..5637c84 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,15 @@ module go.jolheiser.com/vanity go 1.12 require ( - github.com/AlecAivazis/survey/v2 v2.0.5 + code.gitea.io/sdk/gitea v0.12.2 github.com/BurntSushi/toml v0.3.1 - github.com/go-chi/chi v4.0.3+incompatible - github.com/mitchellh/go-homedir v1.1.0 - github.com/stretchr/testify v1.3.0 // indirect - github.com/urfave/cli/v2 v2.1.1 - go.jolheiser.com/beaver v1.0.1 + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/go-chi/chi v4.1.2+incompatible + github.com/google/go-github/v32 v32.1.0 + github.com/urfave/cli/v2 v2.2.0 + github.com/xanzy/go-gitlab v0.37.0 + go.jolheiser.com/beaver v1.0.2 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect + golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 + golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect ) diff --git a/go.sum b/go.sum index cf31248..8a0ffdb 100644 --- a/go.sum +++ b/go.sum @@ -1,59 +1,417 @@ -github.com/AlecAivazis/survey/v2 v2.0.5 h1:xpZp+Q55wi5C7Iaze+40onHnEkex1jSc34CltJjOoPM= -github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +code.gitea.io/sdk/gitea v0.12.2 h1:NQI8b/CT9AEQjsxbVIZ6gsPUXv38moT5y1ocN7n1YcQ= +code.gitea.io/sdk/gitea v0.12.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= -github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi v4.0.3+incompatible h1:gakN3pDJnzZN5jqFV2TEdF66rTfKeITyR8qu6ekICEY= -github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= -github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= -github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= +github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= +github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY= +github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= -github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -go.jolheiser.com/beaver v1.0.1 h1:gt3aGEr5Bj4ZjDF1g8t8OYOGRCRXGaanGR9CmXUxez8= -go.jolheiser.com/beaver v1.0.1/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/xanzy/go-gitlab v0.37.0 h1:Z/CQkjj5VwbWVYVL7S70kS/TFj5H/pJumV7xbJ0YUQ8= +github.com/xanzy/go-gitlab v0.37.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= +go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc= -golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index c39894d..85cce38 100644 --- a/main.go +++ b/main.go @@ -1,31 +1,43 @@ package main import ( + "fmt" + "net/http" + "os" + + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/router" + "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/cmd" - "go.jolheiser.com/vanity/modules/config" - "os" ) func main() { - - // config loads on init - app := cli.NewApp() app.Name = "vanity" app.Usage = "Vanity Go Imports" - app.Version = config.Version - app.Commands = []*cli.Command{ - &cmd.Add, - &cmd.Remove, - &cmd.List, - &cmd.Config, - &cmd.Server, + app.Version = api.Version + app.Action = doAction + app.Flags = flags.Flags + app.Before = flags.Before + + beaver.Console.Format = beaver.FormatOptions{ + TimePrefix: true, + StackPrefix: true, + StackLimit: 15, + LevelPrefix: true, + LevelColor: true, } - app.EnableBashCompletion = true - err := app.Run(os.Args) - if err != nil { - beaver.Error(err) + + if err := app.Run(os.Args); err != nil { + beaver.Fatal(err) } } + +func doAction(ctx *cli.Context) error { + if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { + return err + } + return nil +} diff --git a/modules/config/config.go b/modules/config/config.go deleted file mode 100644 index faa8b03..0000000 --- a/modules/config/config.go +++ /dev/null @@ -1,133 +0,0 @@ -package config - -import ( - "fmt" - "github.com/AlecAivazis/survey/v2" - "github.com/BurntSushi/toml" - "github.com/mitchellh/go-homedir" - "go.jolheiser.com/beaver" - "os" - "path" - "strings" -) - -var ( - configPath string - cfg *Config - Version = "develop" - - // Config items - - Domain string - Packages []Package -) - -type Config struct { - Domain string `toml:"domain"` - Packages []Package `toml:"package" json:"packages"` -} - -type Package struct { - Name string `toml:"name"` - Path string `toml:"path"` - Repo string `toml:"repo"` - SSH string `toml:"ssh"` - Description string `toml:"description"` -} - -func (pkg Package) Module() string { - return fmt.Sprintf("%s/%s", Domain, pkg.Path) -} - -// Load on init so that CLI contexts are correctly populated -func init() { - home, err := homedir.Dir() - if err != nil { - beaver.Fatalf("could not locate home directory: %v", err) - } - configPath = fmt.Sprintf("%s/.vanity/config.toml", home) - - if _, err := os.Stat(configPath); os.IsNotExist(err) { - if err := os.MkdirAll(path.Dir(configPath), os.ModePerm); err != nil { - beaver.Fatalf("could not create vanity home: %v", err) - } - - if _, err := os.Create(configPath); err != nil { - beaver.Fatalf("could not create vanity config: %v", err) - } - } - - if _, err := toml.DecodeFile(configPath, &cfg); err != nil { - beaver.Fatalf("could not decode vanity config: %v", err) - } - - dupe := make(map[string]bool) - for _, pkg := range cfg.Packages { - name := strings.ToLower(pkg.Name) - if ok := dupe[name]; ok { - beaver.Fatalf("duplicate package for %s", pkg.Name) - } - dupe[name] = true - } - - Domain = cfg.Domain - Packages = cfg.Packages -} - -func Save() error { - cfg.Domain = Domain - cfg.Packages = Packages - - fi, err := os.Create(configPath) - if err != nil { - return err - } - defer fi.Close() - - if err := toml.NewEncoder(fi).Encode(cfg); err != nil { - return err - } - - return nil -} - -func PackageMap() map[string]Package { - pkgs := make(map[string]Package) - for _, pkg := range Packages { - pkgs[pkg.Path] = pkg - } - return pkgs -} - -func AddPackages(force bool, pkgs ...Package) { - for _, pkg := range pkgs { - for idx, p := range Packages { - if strings.EqualFold(p.Name, pkg.Name) { - if force { - Packages[idx] = pkg - break - } - - forceQuestion := &survey.Confirm{ - Message: fmt.Sprintf("Package `%s` (%s) already exists. Overwrite with `%s`?", p.Name, p.Repo, p.Repo), - Default: false, - } - var forceAnswer bool - - if err := survey.AskOne(forceQuestion, &forceAnswer); err != nil { - beaver.Error(err) - break - } - - if !forceAnswer { - beaver.Errorf("leaving package `%s` as-is", pkg.Name) - break - } - - Packages[idx] = pkg - break - } - } - Packages = append(Packages, pkg) - } -} diff --git a/modules/router/router.go b/modules/router/router.go deleted file mode 100644 index a2bb0d2..0000000 --- a/modules/router/router.go +++ /dev/null @@ -1,59 +0,0 @@ -package router - -import ( - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/modules/config" - "go.jolheiser.com/vanity/modules/router/templates" - "html/template" - "net/http" - "runtime" - "strings" - "time" -) - -var ( - index = template.Must(template.New("index").Parse(templates.Head + templates.Index + templates.Info + templates.Foot)) - vanity = template.Must(template.New("vanity").Parse(templates.Head + templates.Vanity + templates.Info + templates.Foot)) - cache = config.PackageMap() -) - -func Init() *chi.Mux { - r := chi.NewRouter() - r.Use(middleware.RedirectSlashes) - r.Use(middleware.Recoverer) - r.Use(middleware.Timeout(30 * time.Second)) - - r.Get("/", doGet) - r.Get("/*", doPackage) - - return r -} - -func doGet(res http.ResponseWriter, req *http.Request) { - if err := index.Execute(res, map[string]interface{}{ - "Packages": config.Packages, - "AppVer": config.Version, - "GoVer": runtime.Version(), - }); err != nil { - beaver.Error(err) - } -} - -func doPackage(res http.ResponseWriter, req *http.Request) { - key := chi.URLParam(req, "*") - pkg, ok := cache[strings.Split(key, "/")[0]] - if !ok { - http.NotFound(res, req) - return - } - - if err := vanity.Execute(res, map[string]interface{}{ - "Package": pkg, - "AppVer": config.Version, - "GoVer": runtime.Version(), - }); err != nil { - beaver.Error(err) - } -} diff --git a/modules/router/templates/foot.go b/modules/router/templates/foot.go deleted file mode 100644 index 23860a6..0000000 --- a/modules/router/templates/foot.go +++ /dev/null @@ -1,6 +0,0 @@ -package templates - -var Foot = ` - - -` diff --git a/modules/router/templates/head.go b/modules/router/templates/head.go deleted file mode 100644 index 92af7f1..0000000 --- a/modules/router/templates/head.go +++ /dev/null @@ -1,15 +0,0 @@ -package templates - -var Head = ` - - - - - {{if .Package}} - - - {{end}} - Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} - - -` diff --git a/modules/router/templates/info.go b/modules/router/templates/info.go deleted file mode 100644 index d187bcd..0000000 --- a/modules/router/templates/info.go +++ /dev/null @@ -1,5 +0,0 @@ -package templates - -var Info = ` -Version: {{.AppVer}} | {{.GoVer}} -` diff --git a/modules/router/templates/vanity.go b/modules/router/templates/vanity.go deleted file mode 100644 index c2fb2a0..0000000 --- a/modules/router/templates/vanity.go +++ /dev/null @@ -1,9 +0,0 @@ -package templates - -var Vanity = ` -

Index

-
-

Name: {{.Package.Name}}

-

Source: {{.Package.Repo}}

-

Description: {{.Package.Description}}

-` diff --git a/router/cache.go b/router/cache.go new file mode 100644 index 0000000..c5a4c68 --- /dev/null +++ b/router/cache.go @@ -0,0 +1,32 @@ +package router + +import ( + "sync" + + "go.jolheiser.com/vanity/api" +) + +var cache = &packageCache{ + packages: make(map[string]*api.Package), +} + +type packageCache struct { + packages map[string]*api.Package + sync.Mutex +} + +func (c *packageCache) Update(packages map[string]*api.Package) { + c.Lock() + c.packages = packages + c.Unlock() +} + +func (c *packageCache) Names() []string { + names := make([]string, len(c.packages)) + idx := 0 + for name := range c.packages { + names[idx] = name + idx++ + } + return names +} diff --git a/router/cron.go b/router/cron.go new file mode 100644 index 0000000..af6b21d --- /dev/null +++ b/router/cron.go @@ -0,0 +1,73 @@ +package router + +import ( + "strings" + "time" + + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/service" + + "go.jolheiser.com/beaver" +) + +var svc service.Service + +func cronStart() { + ticker := time.NewTicker(flags.Interval) + for { + <-ticker.C + beaver.Debug("Running package update...") + cronUpdate() + beaver.Debugf("Finished package update: %s", cache.Names()) + } +} + +func cronUpdate() { + packages, err := svc.Packages() + if err != nil { + beaver.Errorf("could not update packages: %v", err) + return + } + + // Filter + for name, pkg := range packages { + if err := service.Check(pkg); err != nil { + beaver.Debug(err) + delete(packages, name) + continue + } + goMod, err := svc.GoMod(pkg) + if err != nil { + beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) + delete(packages, name) + continue + } + lines := strings.Split(goMod, "\n") + line := strings.Fields(lines[0]) + if !strings.HasPrefix(line[1], flags.Domain) { + beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) + delete(packages, name) + continue + } + beaver.Debugf("Including %s", pkg.Name) + } + + // Overrides + for name, pkg := range packages { + for key, override := range flags.Override { + if strings.EqualFold(name, key) { + beaver.Debugf("Overriding %s -> %s", name, override) + delete(packages, key) + pkg.Name = override + packages[override] = pkg + } + } + } + + // Add packages manually added to config + for _, pkg := range flags.ConfigPackages { + packages[pkg.Name] = pkg + } + + cache.Update(packages) +} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..2a77243 --- /dev/null +++ b/router/router.go @@ -0,0 +1,73 @@ +package router + +import ( + "fmt" + "html/template" + "net/http" + "runtime" + "strings" + "time" + + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/router/templates" + "go.jolheiser.com/vanity/service" + + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "go.jolheiser.com/beaver" +) + +var ( + index = template.Must(template.New("index").Parse(templates.Head + templates.Index + templates.Foot)) + vanity = template.Must(template.New("vanity").Parse(templates.Head + templates.Vanity + templates.Foot)) +) + +func Init() *chi.Mux { + r := chi.NewRouter() + r.Use(middleware.RedirectSlashes) + r.Use(middleware.Recoverer) + r.Use(middleware.Timeout(30 * time.Second)) + + r.Get("/", doIndex) + r.Get("/*", doVanity) + + svc = service.New() + + beaver.Info("Warming up cache...") + cronUpdate() + beaver.Infof("Finished warming up cache: %s", cache.Names()) + go cronStart() + + beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) + return r +} + +func doIndex(res http.ResponseWriter, _ *http.Request) { + if err := index.Execute(res, map[string]interface{}{ + "Packages": cache.packages, + "AppVer": api.Version, + "GoVer": runtime.Version(), + }); err != nil { + beaver.Error(err) + } +} + +func doVanity(res http.ResponseWriter, req *http.Request) { + key := chi.URLParam(req, "*") + pkg, ok := cache.packages[strings.Split(key, "/")[0]] + if !ok { + http.NotFound(res, req) + return + } + + if err := vanity.Execute(res, map[string]interface{}{ + "Package": pkg, + "Module": pkg.Module(flags.Domain), + "AppVer": api.Version, + "GoVer": runtime.Version(), + "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, svc.GoDir(pkg), svc.GoFile(pkg)), + }); err != nil { + beaver.Error(err) + } +} diff --git a/router/templates/foot.go b/router/templates/foot.go new file mode 100644 index 0000000..82e0492 --- /dev/null +++ b/router/templates/foot.go @@ -0,0 +1,7 @@ +package templates + +var Foot = ` +Version: {{.AppVer}} | {{.GoVer}} + + +` diff --git a/router/templates/head.go b/router/templates/head.go new file mode 100644 index 0000000..9fb8c57 --- /dev/null +++ b/router/templates/head.go @@ -0,0 +1,23 @@ +package templates + +var Head = ` + + + + + {{if .Package}} + + + + + + + + + + + {{end}} + Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} + + +` diff --git a/modules/router/templates/index.go b/router/templates/index.go similarity index 71% rename from modules/router/templates/index.go rename to router/templates/index.go index 4fc06c0..7e6845e 100644 --- a/modules/router/templates/index.go +++ b/router/templates/index.go @@ -6,7 +6,7 @@ var Index = `

Imports:

` diff --git a/router/templates/vanity.go b/router/templates/vanity.go new file mode 100644 index 0000000..8a76d19 --- /dev/null +++ b/router/templates/vanity.go @@ -0,0 +1,10 @@ +package templates + +var Vanity = ` +

Index

+
+

Name: {{.Package.Name}}

+

Source: {{.Package.WebURL}}

+{{if .Package.Description}}

Description: {{.Package.Description}}

{{end}} +

Documentation: https://pkg.go.dev/{{.Module}}

+` diff --git a/service/gitea.go b/service/gitea.go new file mode 100644 index 0000000..49017cb --- /dev/null +++ b/service/gitea.go @@ -0,0 +1,76 @@ +package service + +import ( + "fmt" + + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + + "code.gitea.io/sdk/gitea" +) + +var _ Service = &Gitea{} + +func NewGitea() *Gitea { + client := gitea.NewClient(flags.BaseURL.String(), flags.Token) + return &Gitea{ + client: client, + } +} + +type Gitea struct { + client *gitea.Client +} + +func (g Gitea) Packages() (map[string]*api.Package, error) { + packages := make(map[string]*api.Package) + page := 0 + for { + opts := gitea.ListReposOptions{ + ListOptions: gitea.ListOptions{ + Page: page, + PageSize: 50, + }, + } + + repos, err := g.client.ListUserRepos(flags.Namespace, opts) + if err != nil { + return nil, err + } + + for _, repo := range repos { + packages[repo.Name] = &api.Package{ + Name: repo.Name, + Description: repo.Description, + Branch: repo.DefaultBranch, + WebURL: repo.HTMLURL, + CloneHTTP: repo.CloneURL, + CloneSSH: repo.SSHURL, + Private: repo.Private, + Fork: repo.Fork, + Mirror: repo.Mirror, + Archive: repo.Archived, + } + } + + page++ + if len(repos) == 0 { + break + } + } + + return packages, nil +} + +func (g Gitea) GoDir(pkg *api.Package) string { + return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.WebURL, pkg.Branch) +} + +func (g Gitea) GoFile(pkg *api.Package) string { + return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) +} + +func (g Gitea) GoMod(pkg *api.Package) (string, error) { + content, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") + return string(content), err +} diff --git a/service/github.go b/service/github.go new file mode 100644 index 0000000..a124ac9 --- /dev/null +++ b/service/github.go @@ -0,0 +1,89 @@ +package service + +import ( + "context" + "fmt" + + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + + "github.com/google/go-github/v32/github" + "golang.org/x/oauth2" +) + +var _ Service = &GitHub{} + +func NewGitHub() *GitHub { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: flags.Token}, + ) + client := oauth2.NewClient(context.Background(), ts) + ghClient := github.NewClient(client) + ghClient.BaseURL = flags.BaseURL + return &GitHub{ + client: ghClient, + } +} + +type GitHub struct { + client *github.Client +} + +func (g GitHub) Packages() (map[string]*api.Package, error) { + packages := make(map[string]*api.Package) + page := 0 + for { + opts := github.RepositoryListOptions{ + ListOptions: github.ListOptions{ + Page: page, + PerPage: 50, + }, + } + + repos, _, err := g.client.Repositories.List(context.Background(), flags.Namespace, &opts) + if err != nil { + return nil, err + } + + for _, repo := range repos { + packages[repo.GetName()] = &api.Package{ + Name: repo.GetName(), + Description: repo.GetDescription(), + Branch: repo.GetDefaultBranch(), + WebURL: repo.GetHTMLURL(), + CloneHTTP: repo.GetCloneURL(), + CloneSSH: repo.GetSSHURL(), + Private: repo.GetPrivate(), + Fork: repo.GetFork(), + Mirror: false, + Archive: repo.GetArchived(), + } + } + + page++ + if len(repos) == 0 { + break + } + } + + return packages, nil +} + +func (g GitHub) GoDir(pkg *api.Package) string { + return fmt.Sprintf("%s/tree/%s{/dir}", pkg.WebURL, pkg.Branch) +} + +func (g GitHub) GoFile(pkg *api.Package) string { + return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) +} + +func (g GitHub) GoMod(pkg *api.Package) (string, error) { + content, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Namespace, pkg.Name, "go.mod", + &github.RepositoryContentGetOptions{ + Ref: pkg.Branch, + }) + if err != nil { + return "", err + } + return content.GetContent() +} diff --git a/service/gitlab.go b/service/gitlab.go new file mode 100644 index 0000000..6adb5d7 --- /dev/null +++ b/service/gitlab.go @@ -0,0 +1,84 @@ +package service + +import ( + "fmt" + "html" + + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + + "github.com/xanzy/go-gitlab" + "go.jolheiser.com/beaver" +) + +var _ Service = &GitLab{} + +func NewGitLab() *GitLab { + client, err := gitlab.NewClient(flags.Token, gitlab.WithBaseURL(flags.BaseURL.String())) + if err != nil { + beaver.Errorf("could not create GitLab client: %v", err) + } + return &GitLab{ + client: client, + } +} + +type GitLab struct { + client *gitlab.Client +} + +func (g GitLab) Packages() (map[string]*api.Package, error) { + packages := make(map[string]*api.Package) + page := 0 + for { + opts := gitlab.ListProjectsOptions{ + ListOptions: gitlab.ListOptions{ + Page: page, + PerPage: 50, + }, + } + + repos, _, err := g.client.Projects.ListUserProjects(flags.Namespace, &opts) + if err != nil { + return nil, err + } + + for _, repo := range repos { + packages[repo.Name] = &api.Package{ + Name: repo.Name, + Description: repo.Description, + Branch: repo.DefaultBranch, + WebURL: repo.WebURL, + CloneHTTP: repo.HTTPURLToRepo, + CloneSSH: repo.SSHURLToRepo, + Private: repo.Visibility != gitlab.PublicVisibility, + Fork: repo.ForkedFromProject != nil, + Mirror: repo.Mirror, + Archive: repo.Archived, + } + } + + page++ + if len(repos) == 0 { + break + } + } + + return packages, nil +} + +func (g GitLab) GoDir(pkg *api.Package) string { + return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.WebURL, pkg.Branch) +} + +func (g GitLab) GoFile(pkg *api.Package) string { + return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) +} + +func (g GitLab) GoMod(pkg *api.Package) (string, error) { + id := fmt.Sprintf("%s/%s", flags.Namespace, pkg.Name) + content, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{ + Ref: &pkg.Branch, + }) + return string(content), err +} diff --git a/service/off.go b/service/off.go new file mode 100644 index 0000000..b029866 --- /dev/null +++ b/service/off.go @@ -0,0 +1,23 @@ +package service + +import "go.jolheiser.com/vanity/api" + +var _ Service = Off{} + +type Off struct{} + +func (o Off) Packages() (map[string]*api.Package, error) { + return make(map[string]*api.Package), nil +} + +func (o Off) GoDir(*api.Package) string { + return "" +} + +func (o Off) GoFile(*api.Package) string { + return "" +} + +func (o Off) GoMod(*api.Package) (string, error) { + return "", nil +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..2934fe8 --- /dev/null +++ b/service/service.go @@ -0,0 +1,71 @@ +package service + +import ( + "fmt" + "strings" + + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" +) + +type Service interface { + Packages() (map[string]*api.Package, error) + GoDir(*api.Package) string + GoFile(*api.Package) string + GoMod(*api.Package) (string, error) +} + +func New() Service { + switch strings.ToLower(flags.Service) { + case "gitea": + return NewGitea() + case "github": + return NewGitHub() + case "gitlab": + return NewGitLab() + default: + return Off{} + } +} + +func Check(pkg *api.Package) error { + + // Private + if pkg.Private && !flags.Private { + return fmt.Errorf("%s is private and --private wasn't used", pkg.Name) + } + + // Forked + if pkg.Fork && !flags.Fork { + return fmt.Errorf("%s is a fork and --fork wasn't used", pkg.Name) + } + + // Mirrored + if pkg.Mirror && !flags.Mirror { + return fmt.Errorf("%s is a mirror and --mirror wasn't used", pkg.Name) + } + + // Archived + if pkg.Archive && !flags.Archive { + return fmt.Errorf("%s is archived and --archive wasn't used", pkg.Name) + } + + // Exclusions + for _, exclude := range flags.Exclude { + if exclude.MatchString(pkg.Name) { + return fmt.Errorf("%s was excluded by the rule %s", pkg.Name, exclude.String()) + } + } + + // Inclusions + if len(flags.Include) > 0 { + for _, include := range flags.Include { + if include.MatchString(pkg.Name) { + return fmt.Errorf("%s was included by the rule %s", pkg.Name, include.String()) + } + } + return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) + } + + return nil +} diff --git a/vanity.service b/vanity.service index 283b935..a8efdbc 100644 --- a/vanity.service +++ b/vanity.service @@ -8,9 +8,14 @@ RestartSec=2s Type=simple User=vanity Group=vanity -ExecStart=/usr/local/bin/vanity server -p 7777 +ExecStart=/usr/local/bin/vanity Restart=always -Environment=USER=vanity HOME=/var/lib/vanity + +# Required +Environment=VANITY_BASE_URL= +Environment=VANITY_NAMESPACE= +Environment=VANITY_TOKEN= +Environment=VANITY_DOMAIN= [Install] WantedBy=multi-user.target \ No newline at end of file -- 2.41.0 From c6b66c469177ebefc3c132aae35c60062673f94b Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sat, 12 Sep 2020 10:41:21 -0500 Subject: [PATCH 07/15] Add license Signed-off-by: jolheiser --- LICENSE | 7 +++++++ README.md | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0f02641 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2020 John Olheiser + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 18b6dd5..8841a43 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,8 @@ Overrides are available via config or by setting an environment variable `VANITY ## Config-only Mode -To run Vanity in config-only mode for packages, set `--service` to `off`. \ No newline at end of file +To run Vanity in config-only mode for packages, set `--service` to `off`. + +## License + +[MIT](LICENSE) \ No newline at end of file -- 2.41.0 From e7c3ac2d0f3677cf10a8985f5b83523b30917fe6 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sat, 12 Sep 2020 13:04:48 -0500 Subject: [PATCH 08/15] Fix docker compose Signed-off-by: jolheiser --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8b20c7c..f7d7440 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -6,7 +6,7 @@ services: environment: - VANITY_DOMAIN=go.domain.tld - VANITY_NAMESPACE= - - VANITY_TOKEN=\ + - VANITY_TOKEN= #- VANITY_SERVICE=gitea #- VANITY_BASE_URL=https://gitea.com restart: always -- 2.41.0 From c3e03c14082fd19a762b7b7644b90c29038f1a04 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 22 Feb 2021 04:23:06 +0800 Subject: [PATCH 09/15] Shiny things (#8) Resolves #4 Resolves #5 Resolves #6 Change list: * Adds topics format * Groups projects by their topics/tags, any without topics are tossed in `other` * Adds minimal page for `go-get` or `git-import` URL query parameters * Changes the version information to explicitly mention Vanity and Go. * Adds manual update mode as an alternative to the automatic intervals. * Moved templates to `.tmpl` files and using `//go:embed` to add to binary. Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/vanity/pulls/8 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- README.md | 19 +++++++ api/package.go | 13 +++-- flags/config.go | 8 ++- flags/flags.go | 19 +++++++ go.mod | 8 ++- go.sum | 24 ++++---- main.go | 6 +- router/api.go | 71 +++++++++++++++++++++++ router/cache.go | 106 ++++++++++++++++++++++++++++++----- router/cron.go | 73 +++++------------------- router/router.go | 67 ++++++++++++++-------- router/templates.go | 44 +++++++++++++++ router/templates/foot.go | 7 --- router/templates/foot.tmpl | 25 +++++++++ router/templates/head.go | 23 -------- router/templates/head.tmpl | 21 +++++++ router/templates/import.tmpl | 20 +++++++ router/templates/index.go | 12 ---- router/templates/index.tmpl | 29 ++++++++++ router/templates/vanity.go | 10 ---- router/templates/vanity.tmpl | 20 +++++++ service/gitea.go | 27 +++++++-- service/github.go | 1 + service/gitlab.go | 1 + service/service.go | 8 ++- 25 files changed, 482 insertions(+), 180 deletions(-) create mode 100644 router/api.go create mode 100644 router/templates.go delete mode 100644 router/templates/foot.go create mode 100644 router/templates/foot.tmpl delete mode 100644 router/templates/head.go create mode 100644 router/templates/head.tmpl create mode 100644 router/templates/import.tmpl delete mode 100644 router/templates/index.go create mode 100644 router/templates/index.tmpl delete mode 100644 router/templates/vanity.go create mode 100644 router/templates/vanity.tmpl diff --git a/README.md b/README.md index 8841a43..16e5b9e 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,25 @@ Overrides are available via config or by setting an environment variable `VANITY To run Vanity in config-only mode for packages, set `--service` to `off`. +## Manual Mode + +To run Vanity without automatic updating, use `--manual`. + +When running with manual-mode, the provided button or `/_/update` endpoint can be used once every `--interval`. + +## Topic Lists + +By setting `--topics`, anyone visiting the index page will see packages grouped by their topics. + +Regardless of the setting, you can switch beteween list-view and topic-view with the provided button +or changing the URL between `?format=list` and `?format=topics`. + +## API + +In order to preserve namespaces for packages, Vanity's API uses the URL `/_/{endpoint}` + +Vanity currently supports `/_/status` and `/_/update`, to get some status information and update the package cache respectively. + ## License [MIT](LICENSE) \ No newline at end of file diff --git a/api/package.go b/api/package.go index afa0ac7..af5a74e 100644 --- a/api/package.go +++ b/api/package.go @@ -6,12 +6,13 @@ import ( ) type Package struct { - Name string `toml:"name"` - Description string `toml:"description"` - Branch string `toml:"branch"` - WebURL string `toml:"web_url"` - CloneHTTP string `toml:"clone_http"` - CloneSSH string `toml:"clone_ssh"` + Name string `toml:"name"` + Description string `toml:"description"` + Branch string `toml:"branch"` + WebURL string `toml:"web_url"` + CloneHTTP string `toml:"clone_http"` + CloneSSH string `toml:"clone_ssh"` + Topics []string `toml:"topics"` Private bool `toml:"-"` Fork bool `toml:"-"` diff --git a/flags/config.go b/flags/config.go index 9becb12..cb1a9d0 100644 --- a/flags/config.go +++ b/flags/config.go @@ -7,7 +7,7 @@ import ( "go.jolheiser.com/vanity/api" - "github.com/BurntSushi/toml" + "github.com/pelletier/go-toml" "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" ) @@ -44,11 +44,15 @@ func setConfig(ctx *cli.Context) { var cfg tomlConfig if configPath != "" { beaver.Infof("Loading configuration from %s", configPath) - _, err := toml.DecodeFile(configPath, &cfg) + tree, err := toml.LoadFile(configPath) if err != nil { beaver.Errorf("Could not load configuration from %s: %v", configPath, err) return } + if err = tree.Unmarshal(&cfg); err != nil { + beaver.Errorf("Could not unmarshal configuration from %s: %v", configPath, err) + return + } } if !ctx.IsSet("port") && cfg.Port > 0 { diff --git a/flags/flags.go b/flags/flags.go index 767691d..c3be0d3 100644 --- a/flags/flags.go +++ b/flags/flags.go @@ -35,6 +35,8 @@ var ( Archive bool Override = make(map[string]string) Interval time.Duration + Manual bool + Topics bool Debug bool ConfigPackages []*api.Package @@ -135,6 +137,18 @@ var Flags = []cli.Flag{ EnvVars: []string{"VANITY_INTERVAL"}, Destination: &Interval, }, + &cli.BoolFlag{ + Name: "manual", + Usage: "Disable cron and only update with endpoint", + EnvVars: []string{"VANITY_MANUAL"}, + Destination: &Manual, + }, + &cli.BoolFlag{ + Name: "topics", + Usage: "Group projects by topic by default", + EnvVars: []string{"VANITY_TOPICS"}, + Destination: &Topics, + }, &cli.BoolFlag{ Name: "debug", Usage: "Debug logging", @@ -196,6 +210,10 @@ func Before(ctx *cli.Context) error { Exclude[idx] = regexp.MustCompile(e) } + if Manual { + beaver.Info("Running in manual mode") + } + if Debug { beaver.Console.Level = beaver.DEBUG } @@ -213,5 +231,6 @@ func Before(ctx *cli.Context) error { beaver.Debugf("Archive: %t", Archive) beaver.Debugf("Override: %s", override.Value()) beaver.Debugf("Interval: %s", Interval) + beaver.Debugf("Manual: %t", Manual) return nil } diff --git a/go.mod b/go.mod index 5637c84..e04bf33 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,18 @@ module go.jolheiser.com/vanity -go 1.12 +go 1.16 require ( - code.gitea.io/sdk/gitea v0.12.2 - github.com/BurntSushi/toml v0.3.1 + code.gitea.io/sdk/gitea v0.13.2 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-chi/chi v4.1.2+incompatible + github.com/go-chi/cors v1.1.1 // indirect github.com/google/go-github/v32 v32.1.0 + github.com/pelletier/go-toml v1.8.1 github.com/urfave/cli/v2 v2.2.0 github.com/xanzy/go-gitlab v0.37.0 go.jolheiser.com/beaver v1.0.2 + go.jolheiser.com/overlay v0.0.2 // indirect golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect diff --git a/go.sum b/go.sum index 8a0ffdb..f381b7e 100644 --- a/go.sum +++ b/go.sum @@ -30,10 +30,9 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -code.gitea.io/sdk/gitea v0.12.2 h1:NQI8b/CT9AEQjsxbVIZ6gsPUXv38moT5y1ocN7n1YcQ= -code.gitea.io/sdk/gitea v0.12.2/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY= +code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA= +code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -42,7 +41,6 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -55,6 +53,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= +github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -115,19 +115,19 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY= github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -149,12 +149,13 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +go.jolheiser.com/overlay v0.0.2 h1:cwEHLbWqdH7lEOG87WUwgUGVqfOWBsWe03FiHHmuTWE= +go.jolheiser.com/overlay v0.0.2/go.mod h1:xNbssakJ3HjK4RnjuP38q9yQNS4wxXKsyprYIWWr2bg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -198,7 +199,6 @@ golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -238,7 +238,6 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -247,7 +246,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -268,7 +266,6 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -400,7 +397,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/main.go b/main.go index 85cce38..9562d94 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,11 @@ func main() { } func doAction(ctx *cli.Context) error { - if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), router.Init()); err != nil { + mux, err := router.Init() + if err != nil { + return err + } + if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), mux); err != nil { return err } return nil diff --git a/router/api.go b/router/api.go new file mode 100644 index 0000000..9687b53 --- /dev/null +++ b/router/api.go @@ -0,0 +1,71 @@ +package router + +import ( + "encoding/json" + "github.com/go-chi/chi" + "github.com/go-chi/cors" + "go.jolheiser.com/beaver" + "go.jolheiser.com/vanity/api" + "go.jolheiser.com/vanity/flags" + "net/http" + "runtime" + "time" +) + +func apiRoutes() *chi.Mux { + r := chi.NewRouter() + r.Use(cors.AllowAll().Handler) + + r.Get("/status", doAPIStatus) + r.Get("/update", doAPIUpdate) + + return r +} + +func doAPIStatus(res http.ResponseWriter, _ *http.Request) { + res.Header().Set("Content-Type", "application/json") + + var nextUpdate *string + if !lastUpdate.IsZero() { + nu := lastUpdate.Add(flags.Interval).Format(time.RFC3339) + nextUpdate = &nu + } + + resp := map[string]interface{}{ + "vanity_version": api.Version, + "go_version": runtime.Version(), + "num_packages": len(cache.Packages), + "next_update": nextUpdate, + } + + data, err := json.Marshal(&resp) + if err != nil { + beaver.Errorf("could not marshal API status: %v", err) + data = []byte("{}") + } + + if _, err = res.Write(data); err != nil { + beaver.Errorf("could not write response: %v", err) + } +} + +func doAPIUpdate(res http.ResponseWriter, _ *http.Request) { + res.Header().Set("Content-Type", "application/json") + + resp := map[string]bool{ + "updated": false, + } + if canUpdate { + updateCache() + resp["updated"] = true + } + + payload, err := json.Marshal(resp) + if err != nil { + beaver.Errorf("could not marshal payload: %v", err) + } + + if _, err = res.Write(payload); err != nil { + beaver.Errorf("could not write response: %v", err) + } +} diff --git a/router/cache.go b/router/cache.go index c5a4c68..88e37d9 100644 --- a/router/cache.go +++ b/router/cache.go @@ -1,32 +1,110 @@ package router import ( + "go.jolheiser.com/beaver" + "go.jolheiser.com/vanity/flags" + "go.jolheiser.com/vanity/service" + "strings" "sync" "go.jolheiser.com/vanity/api" ) var cache = &packageCache{ - packages: make(map[string]*api.Package), + Packages: make(map[string]*api.Package), } -type packageCache struct { - packages map[string]*api.Package - sync.Mutex -} +type packageList map[string]*api.Package -func (c *packageCache) Update(packages map[string]*api.Package) { - c.Lock() - c.packages = packages - c.Unlock() -} - -func (c *packageCache) Names() []string { - names := make([]string, len(c.packages)) +func (p packageList) Names() []string { + names := make([]string, len(p)) idx := 0 - for name := range c.packages { + for name := range p { names[idx] = name idx++ } return names } + +func (p packageList) Topics() map[string][]*api.Package { + topics := make(map[string][]*api.Package, 0) + for _, pkg := range p { + if len(pkg.Topics) == 0 { + if tt, ok := topics["other"]; ok { + topics["other"] = append(tt, pkg) + } else { + topics["other"] = []*api.Package{pkg} + } + } + for _, t := range pkg.Topics { + if tt, ok := topics[t]; ok { + topics[t] = append(tt, pkg) + } else { + topics[t] = []*api.Package{pkg} + } + } + } + return topics +} + +type packageCache struct { + Packages packageList + sync.Mutex +} + +func (c *packageCache) Update(packages map[string]*api.Package) { + c.Lock() + c.Packages = packages + c.Unlock() +} + +func updateCache() { + packages, err := svc.Packages() + if err != nil { + beaver.Errorf("could not update packages: %v", err) + return + } + + // Filter + for name, pkg := range packages { + if err := service.Check(pkg); err != nil { + beaver.Debug(err) + delete(packages, name) + continue + } + goMod, err := svc.GoMod(pkg) + if err != nil { + beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) + delete(packages, name) + continue + } + lines := strings.Split(goMod, "\n") + line := strings.Fields(lines[0]) + if !strings.HasPrefix(line[1], flags.Domain) { + beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) + delete(packages, name) + continue + } + beaver.Debugf("Including %s", pkg.Name) + } + + // Overrides + for name, pkg := range packages { + for key, override := range flags.Override { + if strings.EqualFold(name, key) { + beaver.Debugf("Overriding %s -> %s", name, override) + delete(packages, key) + pkg.Name = override + packages[override] = pkg + } + } + } + + // Add packages manually added to config + for _, pkg := range flags.ConfigPackages { + packages[pkg.Name] = pkg + } + + cache.Update(packages) + canUpdate = false +} diff --git a/router/cron.go b/router/cron.go index af6b21d..bc609c6 100644 --- a/router/cron.go +++ b/router/cron.go @@ -1,73 +1,30 @@ package router import ( - "strings" + "go.jolheiser.com/beaver" + "go.jolheiser.com/vanity/service" "time" "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/service" - - "go.jolheiser.com/beaver" ) -var svc service.Service +var ( + svc service.Service + lastUpdate time.Time + canUpdate bool +) func cronStart() { + canUpdate = true ticker := time.NewTicker(flags.Interval) for { <-ticker.C - beaver.Debug("Running package update...") - cronUpdate() - beaver.Debugf("Finished package update: %s", cache.Names()) + if !flags.Manual && canUpdate { + beaver.Debug("Running package update...") + updateCache() + beaver.Debugf("Finished package update: %s", cache.Packages.Names()) + lastUpdate = time.Now() + } + canUpdate = true } } - -func cronUpdate() { - packages, err := svc.Packages() - if err != nil { - beaver.Errorf("could not update packages: %v", err) - return - } - - // Filter - for name, pkg := range packages { - if err := service.Check(pkg); err != nil { - beaver.Debug(err) - delete(packages, name) - continue - } - goMod, err := svc.GoMod(pkg) - if err != nil { - beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) - delete(packages, name) - continue - } - lines := strings.Split(goMod, "\n") - line := strings.Fields(lines[0]) - if !strings.HasPrefix(line[1], flags.Domain) { - beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) - delete(packages, name) - continue - } - beaver.Debugf("Including %s", pkg.Name) - } - - // Overrides - for name, pkg := range packages { - for key, override := range flags.Override { - if strings.EqualFold(name, key) { - beaver.Debugf("Overriding %s -> %s", name, override) - delete(packages, key) - pkg.Name = override - packages[override] = pkg - } - } - } - - // Add packages manually added to config - for _, pkg := range flags.ConfigPackages { - packages[pkg.Name] = pkg - } - - cache.Update(packages) -} diff --git a/router/router.go b/router/router.go index 2a77243..1b742b8 100644 --- a/router/router.go +++ b/router/router.go @@ -4,13 +4,10 @@ import ( "fmt" "html/template" "net/http" - "runtime" "strings" "time" - "go.jolheiser.com/vanity/api" "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/router/templates" "go.jolheiser.com/vanity/service" "github.com/go-chi/chi" @@ -18,56 +15,78 @@ import ( "go.jolheiser.com/beaver" ) -var ( - index = template.Must(template.New("index").Parse(templates.Head + templates.Index + templates.Foot)) - vanity = template.Must(template.New("vanity").Parse(templates.Head + templates.Vanity + templates.Foot)) -) +var tmpl *template.Template + +func Init() (*chi.Mux, error) { + var err error + tmpl, err = Templates() + if err != nil { + return nil, err + } -func Init() *chi.Mux { r := chi.NewRouter() r.Use(middleware.RedirectSlashes) r.Use(middleware.Recoverer) - r.Use(middleware.Timeout(30 * time.Second)) + r.Use(middleware.Timeout(60 * time.Second)) r.Get("/", doIndex) r.Get("/*", doVanity) + r.Mount("/_", apiRoutes()) svc = service.New() beaver.Info("Warming up cache...") - cronUpdate() - beaver.Infof("Finished warming up cache: %s", cache.Names()) + updateCache() + beaver.Infof("Finished warming up cache: %s", cache.Packages.Names()) go cronStart() beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) - return r + return r, nil } -func doIndex(res http.ResponseWriter, _ *http.Request) { - if err := index.Execute(res, map[string]interface{}{ - "Packages": cache.packages, - "AppVer": api.Version, - "GoVer": runtime.Version(), +func doIndex(res http.ResponseWriter, req *http.Request) { + format := "list" + if flags.Topics { + format = "topics" + } + q := req.URL.Query().Get("format") + if q != "" { + format = q + } + + if err := tmpl.Lookup("index.tmpl").Execute(res, map[string]interface{}{ + "Packages": cache.Packages, + "Index": true, + "Format": format, }); err != nil { - beaver.Error(err) + beaver.Errorf("could not write response: %v", err) } } func doVanity(res http.ResponseWriter, req *http.Request) { key := chi.URLParam(req, "*") - pkg, ok := cache.packages[strings.Split(key, "/")[0]] + pkg, ok := cache.Packages[strings.Split(key, "/")[0]] if !ok { http.NotFound(res, req) return } - if err := vanity.Execute(res, map[string]interface{}{ + ctx := map[string]interface{}{ "Package": pkg, "Module": pkg.Module(flags.Domain), - "AppVer": api.Version, - "GoVer": runtime.Version(), "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, svc.GoDir(pkg), svc.GoFile(pkg)), - }); err != nil { - beaver.Error(err) + "Index": false, + } + + q := req.URL.Query() + if q.Get("go-get") != "" || q.Get("git-import") != "" { + if err := tmpl.Lookup("import.tmpl").Execute(res, ctx); err != nil { + beaver.Errorf("could not write response: %v", err) + } + return + } + + if err := tmpl.Lookup("vanity.tmpl").Execute(res, ctx); err != nil { + beaver.Errorf("could not write response: %v", err) } } diff --git a/router/templates.go b/router/templates.go new file mode 100644 index 0000000..5bd899d --- /dev/null +++ b/router/templates.go @@ -0,0 +1,44 @@ +package router + +import ( + "embed" + "go.jolheiser.com/overlay" + "go.jolheiser.com/vanity/api" + "html/template" + "os" + "path/filepath" + "runtime" +) + +//go:embed templates +var templates embed.FS + +func Templates() (*template.Template, error) { + bin, err := os.Executable() + if err != nil { + return nil, err + } + customPath := os.Getenv("VANITY_CUSTOM") + if customPath == "" { + customPath = filepath.Join(bin, "custom") + } + + ofs, err := overlay.New(customPath, templates) + if err != nil { + return nil, err + } + + return template.New("vanity").Funcs(funcMap).ParseFS(ofs, "templates/*") +} + +var funcMap = template.FuncMap{ + "AppVer": func() string { + return api.Version + }, + "GoVer": func() string { + return runtime.Version() + }, + "CanUpdate": func() bool { + return canUpdate + }, +} diff --git a/router/templates/foot.go b/router/templates/foot.go deleted file mode 100644 index 82e0492..0000000 --- a/router/templates/foot.go +++ /dev/null @@ -1,7 +0,0 @@ -package templates - -var Foot = ` -Version: {{.AppVer}} | {{.GoVer}} - - -` diff --git a/router/templates/foot.tmpl b/router/templates/foot.tmpl new file mode 100644 index 0000000..4cf7348 --- /dev/null +++ b/router/templates/foot.tmpl @@ -0,0 +1,25 @@ +
+ +

+Vanity Version: +{{AppVer}} +

+Go Version: +{{GoVer}} + + + + diff --git a/router/templates/head.go b/router/templates/head.go deleted file mode 100644 index 9fb8c57..0000000 --- a/router/templates/head.go +++ /dev/null @@ -1,23 +0,0 @@ -package templates - -var Head = ` - - - - - {{if .Package}} - - - - - - - - - - - {{end}} - Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} - - -` diff --git a/router/templates/head.tmpl b/router/templates/head.tmpl new file mode 100644 index 0000000..00435a2 --- /dev/null +++ b/router/templates/head.tmpl @@ -0,0 +1,21 @@ + + + + + {{if .Package}} + + + + + + + + + + + {{end}} + Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} + + +

Index

+
\ No newline at end of file diff --git a/router/templates/import.tmpl b/router/templates/import.tmpl new file mode 100644 index 0000000..c883b67 --- /dev/null +++ b/router/templates/import.tmpl @@ -0,0 +1,20 @@ + + + + + + + + + + + + + {{.Module}} + + +go get {{.Module}} +
+git-import {{.Module}} + + \ No newline at end of file diff --git a/router/templates/index.go b/router/templates/index.go deleted file mode 100644 index 7e6845e..0000000 --- a/router/templates/index.go +++ /dev/null @@ -1,12 +0,0 @@ -package templates - -var Index = ` -

Index

-
-

Imports:

- -` diff --git a/router/templates/index.tmpl b/router/templates/index.tmpl new file mode 100644 index 0000000..604c6e5 --- /dev/null +++ b/router/templates/index.tmpl @@ -0,0 +1,29 @@ +{{template "head.tmpl" .}} +

{{if eq .Format "list"}}Imports{{else}}Topics{{end}}:

+ {{if eq .Format "list"}} + + {{else}} + {{range $topic, $packages := .Packages.Topics}} +
+ {{$topic}} + +
+ {{end}} + {{end}} +
+
+ {{if eq .Format "list"}} + + {{else}} + + {{end}} +
+{{template "foot.tmpl" .}} \ No newline at end of file diff --git a/router/templates/vanity.go b/router/templates/vanity.go deleted file mode 100644 index 8a76d19..0000000 --- a/router/templates/vanity.go +++ /dev/null @@ -1,10 +0,0 @@ -package templates - -var Vanity = ` -

Index

-
-

Name: {{.Package.Name}}

-

Source: {{.Package.WebURL}}

-{{if .Package.Description}}

Description: {{.Package.Description}}

{{end}} -

Documentation: https://pkg.go.dev/{{.Module}}

-` diff --git a/router/templates/vanity.tmpl b/router/templates/vanity.tmpl new file mode 100644 index 0000000..e36b63c --- /dev/null +++ b/router/templates/vanity.tmpl @@ -0,0 +1,20 @@ +{{template "head.tmpl" .}} +

+ Name: + {{.Package.Name}} +

+

+ Source: + {{.Package.WebURL}} +

+ {{if .Package.Description}} +

+ Description: + {{.Package.Description}} +

+ {{end}} +

+ Documentation: + https://pkg.go.dev/{{.Module}} +

+{{template "foot.tmpl" .}} \ No newline at end of file diff --git a/service/gitea.go b/service/gitea.go index 49017cb..b18a54e 100644 --- a/service/gitea.go +++ b/service/gitea.go @@ -7,12 +7,16 @@ import ( "go.jolheiser.com/vanity/flags" "code.gitea.io/sdk/gitea" + "go.jolheiser.com/beaver" ) var _ Service = &Gitea{} func NewGitea() *Gitea { - client := gitea.NewClient(flags.BaseURL.String(), flags.Token) + client, err := gitea.NewClient(flags.BaseURL.String(), gitea.SetToken(flags.Token)) + if err != nil { + beaver.Errorf("could not create Gitea client: %v", err) + } return &Gitea{ client: client, } @@ -24,6 +28,12 @@ type Gitea struct { func (g Gitea) Packages() (map[string]*api.Package, error) { packages := make(map[string]*api.Package) + topicOpts := gitea.ListRepoTopicsOptions{ + ListOptions: gitea.ListOptions{ + Page: 1, + PageSize: 50, + }, + } page := 0 for { opts := gitea.ListReposOptions{ @@ -33,13 +43,13 @@ func (g Gitea) Packages() (map[string]*api.Package, error) { }, } - repos, err := g.client.ListUserRepos(flags.Namespace, opts) + repos, _, err := g.client.ListUserRepos(flags.Namespace, opts) if err != nil { return nil, err } for _, repo := range repos { - packages[repo.Name] = &api.Package{ + pkg := &api.Package{ Name: repo.Name, Description: repo.Description, Branch: repo.DefaultBranch, @@ -51,6 +61,15 @@ func (g Gitea) Packages() (map[string]*api.Package, error) { Mirror: repo.Mirror, Archive: repo.Archived, } + + // Get tags + topics, _, err := g.client.ListRepoTopics(flags.Namespace, repo.Name, topicOpts) + if err != nil { + beaver.Warnf("could not get topics for %s: %v", repo.Name, err) + } + pkg.Topics = topics + + packages[repo.Name] = pkg } page++ @@ -71,6 +90,6 @@ func (g Gitea) GoFile(pkg *api.Package) string { } func (g Gitea) GoMod(pkg *api.Package) (string, error) { - content, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") + content, _, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") return string(content), err } diff --git a/service/github.go b/service/github.go index a124ac9..3f11861 100644 --- a/service/github.go +++ b/service/github.go @@ -57,6 +57,7 @@ func (g GitHub) Packages() (map[string]*api.Package, error) { Fork: repo.GetFork(), Mirror: false, Archive: repo.GetArchived(), + Topics: repo.Topics, } } diff --git a/service/gitlab.go b/service/gitlab.go index 6adb5d7..b101775 100644 --- a/service/gitlab.go +++ b/service/gitlab.go @@ -55,6 +55,7 @@ func (g GitLab) Packages() (map[string]*api.Package, error) { Fork: repo.ForkedFromProject != nil, Mirror: repo.Mirror, Archive: repo.Archived, + Topics: repo.TagList, } } diff --git a/service/service.go b/service/service.go index 2934fe8..327401e 100644 --- a/service/service.go +++ b/service/service.go @@ -59,12 +59,16 @@ func Check(pkg *api.Package) error { // Inclusions if len(flags.Include) > 0 { + var included bool for _, include := range flags.Include { if include.MatchString(pkg.Name) { - return fmt.Errorf("%s was included by the rule %s", pkg.Name, include.String()) + included = true + break } } - return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) + if !included { + return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) + } } return nil -- 2.41.0 From 2f688b839bb3d5c287f98bebef62e0d7e9c69c58 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 22 Feb 2021 04:28:35 +0800 Subject: [PATCH 10/15] Update docker for Go 1.16 (#9) Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/vanity/pulls/9 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 086fee7..e67bce4 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.15-alpine as builder +FROM golang:1.16-alpine as builder RUN apk --no-cache add build-base git COPY . /app WORKDIR /app -- 2.41.0 From c4be5e64b6ada67ca0772c0a0585373189c4e6a1 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Tue, 23 Feb 2021 22:45:01 +0800 Subject: [PATCH 11/15] Add link to docs for vanity imports (#10) Reviewed-on: https://gitea.com/jolheiser/vanity/pulls/10 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16e5b9e..db6c6c4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Vanity -A simple web service to serve vanity Go imports. Feel free to check it out using [my instance](https://go.jolheiser.com/). +A simple web service to serve [vanity Go imports](https://golang.org/cmd/go/#hdr-Remote_import_paths). Feel free to check it out using [my instance](https://go.jolheiser.com/). Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). -- 2.41.0 From cf984234fd536d8f189ee9d7d8cdcbef21023419 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Fri, 23 Apr 2021 11:21:53 +0800 Subject: [PATCH 12/15] Convert to gpm-style service (#11) This is a **massively** breaking change from `v0.2.0`. For consumers there will be no difference, `go-get` and `git-import` are both still supported. The change will be for the admin regarding how package management works. Prior to merging, `v0.2.0` should be moved to another branch in case myself or another party wants to continue with that style of service. This version follows a similar implementation to [gpm](https://gitea.com/jolheiser/gpm) (and indeed, some code was copied nearly line-by-line) ----- Vanity runs as a service, same as before. However, rather than automatic cron-style updates using a third-party API, now the service owner uses their local `vanity` binary with a matching `token` to... * `vanity add` a new package * `vanity update` an existing package * `vanity remove` a package This allows much finer control over which packages are in the service and should required almost no downtime once the service is started other than to update the service itself. As well, it allows mixing of git providers. There's also an SDK, which is nice to have. Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/vanity/pulls/11 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- .gitignore | 2 +- Makefile | 24 ++- README.md | 72 +------ api/package.go | 25 --- api/version.go | 3 - cmd/add.go | 69 ++++++ cmd/cmd.go | 181 ++++++++++++++++ cmd/flags/flags.go | 14 ++ cmd/remove.go | 67 ++++++ cmd/server.go | 55 +++++ cmd/update.go | 76 +++++++ contrib/contrib.go | 28 +++ contrib/vanity.service | 19 ++ database/database.go | 84 ++++++++ database/errors.go | 16 ++ docker/docker-compose.yml | 3 - flags/config.go | 105 --------- flags/flags.go | 236 --------------------- go-vanity/client.go | 53 +++++ go-vanity/go.mod | 3 + go-vanity/package.go | 104 +++++++++ go-vanity/source.go | 45 ++++ go-vanity/vanity_test.go | 194 +++++++++++++++++ go.mod | 16 +- go.sum | 399 +++-------------------------------- main.go | 27 +-- router/api.go | 71 ------- router/cache.go | 110 ---------- router/cron.go | 30 --- router/router.go | 244 +++++++++++++++------ router/router_test.go | 175 +++++++++++++++ router/templates.go | 22 +- router/templates/base.tmpl | 32 +++ router/templates/foot.tmpl | 25 --- router/templates/head.tmpl | 21 -- router/templates/import.tmpl | 26 +-- router/templates/index.tmpl | 36 +--- router/templates/vanity.tmpl | 5 +- service/gitea.go | 95 --------- service/github.go | 90 -------- service/gitlab.go | 85 -------- service/off.go | 23 -- service/service.go | 75 ------- vanity.service | 21 -- 44 files changed, 1478 insertions(+), 1628 deletions(-) delete mode 100644 api/package.go delete mode 100644 api/version.go create mode 100644 cmd/add.go create mode 100644 cmd/cmd.go create mode 100644 cmd/flags/flags.go create mode 100644 cmd/remove.go create mode 100644 cmd/server.go create mode 100644 cmd/update.go create mode 100644 contrib/contrib.go create mode 100644 contrib/vanity.service create mode 100644 database/database.go create mode 100644 database/errors.go delete mode 100644 flags/config.go delete mode 100644 flags/flags.go create mode 100644 go-vanity/client.go create mode 100644 go-vanity/go.mod create mode 100644 go-vanity/package.go create mode 100644 go-vanity/source.go create mode 100644 go-vanity/vanity_test.go delete mode 100644 router/api.go delete mode 100644 router/cache.go delete mode 100644 router/cron.go create mode 100644 router/router_test.go create mode 100644 router/templates/base.tmpl delete mode 100644 router/templates/foot.tmpl delete mode 100644 router/templates/head.tmpl delete mode 100644 service/gitea.go delete mode 100644 service/github.go delete mode 100644 service/gitlab.go delete mode 100644 service/off.go delete mode 100644 service/service.go delete mode 100644 vanity.service diff --git a/.gitignore b/.gitignore index d40611e..9185e86 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ # Vanity /vanity /vanity.exe -.vanity.toml \ No newline at end of file +/vanity.db \ No newline at end of file diff --git a/Makefile b/Makefile index fa8e027..a3af058 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,29 @@ VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') .PHONY: build build: - $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/api.Version=$(VERSION)"' + $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/router.Version=$(VERSION)"' .PHONY: fmt -fmt: - $(GO) fmt ./... +fmt: fmt-cli fmt-lib .PHONY: test -test: - $(GO) test --race ./... +test: test-cli test-lib + +.PHONY: fmt-cli +fmt-cli: + $(GO) fmt ./... + +.PHONY: test-cli +test-cli: + $(GO) test -race ./... + +.PHONY: fmt-lib +fmt-lib: + @cd go-vanity && $(GO) fmt ./... + +.PHONY: test-lib +test-lib: + @cd go-vanity && $(GO) test -race ./... .PHONY: vet vet: diff --git a/README.md b/README.md index db6c6c4..edc23b8 100644 --- a/README.md +++ b/README.md @@ -4,90 +4,20 @@ A simple web service to serve [vanity Go imports](https://golang.org/cmd/go/#hdr Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). -## Configuration - -When choosing a service, the default `base-url` will be the default server of that service: - -| Service | Default | -|:-------:|:------------------:| -| Gitea | https://gitea.com | -| GitHub | https://github.com | -| GitLab | https://gitlab.com | - -``` -NAME: - vanity - Vanity Go Imports - -USAGE: - vanity [global options] command [command options] [arguments...] - -VERSION: - 0.1.0+3-g6d7150e - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --config value Path to a config file [$VANITY_CONFIG] - --port value Port to run the vanity server on (default: 7777) [$VANITY_PORT] - --domain value Vanity domain, e.g. go.domain.tld [$VANITY_DOMAIN] - --service value Service type (Gitea, GitHub, GitLab) (default: "gitea") [$VANITY_SERVICE] - --base-url value Base URL to service [$VANITY_BASE_URL] - --namespace value Owner namespace [$VANITY_NAMESPACE] - --token value Access token [$VANITY_TOKEN] - --include value Repository names to include (regex) [$VANITY_INCLUDE] - --exclude value Repository names to exclude (regex) [$VANITY_EXCLUDE] - --private Include private repositories (default: false) [$VANITY_PRIVATE] - --fork Include forked repositories (default: false) [$VANITY_FORK] - --mirror Include mirrored repositories (default: false) [$VANITY_MIRROR] - --archive Include archived repositories (default: false) [$VANITY_ARCHIVE] - --override value Repository name to override (NAME=OVERRIDE) [$VANITY_OVERRIDE] - --interval value Interval between updating repositories (default: 15m0s) [$VANITY_INTERVAL] - --debug Debug logging (default: false) [$VANITY_DEBUG] - --help, -h show help (default: false) - --version, -v print the version (default: false) -``` - ## Docker ```sh docker run \ --env VANITY_DOMAIN=go.domain.tld \ - --env VANITY_NAMESPACE= \ --env VANITY_TOKEN= \ --publish 80:7777 \ --restart always jolheiser/vanity:latest ``` -## Overrides - -Certain modules may not align perfectly with their repository name. - -Overrides are available via config or by setting an environment variable `VANITY_OVERRIDE_PACKAGE=NAME` - -## Config-only Mode - -To run Vanity in config-only mode for packages, set `--service` to `off`. - -## Manual Mode - -To run Vanity without automatic updating, use `--manual`. - -When running with manual-mode, the provided button or `/_/update` endpoint can be used once every `--interval`. - -## Topic Lists - -By setting `--topics`, anyone visiting the index page will see packages grouped by their topics. - -Regardless of the setting, you can switch beteween list-view and topic-view with the provided button -or changing the URL between `?format=list` and `?format=topics`. - ## API -In order to preserve namespaces for packages, Vanity's API uses the URL `/_/{endpoint}` - -Vanity currently supports `/_/status` and `/_/update`, to get some status information and update the package cache respectively. +Check out the [SDK](go-vanity). ## License diff --git a/api/package.go b/api/package.go deleted file mode 100644 index af5a74e..0000000 --- a/api/package.go +++ /dev/null @@ -1,25 +0,0 @@ -package api - -import ( - "fmt" - "strings" -) - -type Package struct { - Name string `toml:"name"` - Description string `toml:"description"` - Branch string `toml:"branch"` - WebURL string `toml:"web_url"` - CloneHTTP string `toml:"clone_http"` - CloneSSH string `toml:"clone_ssh"` - Topics []string `toml:"topics"` - - Private bool `toml:"-"` - Fork bool `toml:"-"` - Mirror bool `toml:"-"` - Archive bool `toml:"-"` -} - -func (p *Package) Module(domain string) string { - return fmt.Sprintf("%s/%s", domain, strings.ToLower(p.Name)) -} diff --git a/api/version.go b/api/version.go deleted file mode 100644 index af9422a..0000000 --- a/api/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package api - -var Version = "develop" diff --git a/cmd/add.go b/cmd/add.go new file mode 100644 index 0000000..ad7c815 --- /dev/null +++ b/cmd/add.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "context" + "net/url" + + "go.jolheiser.com/vanity/cmd/flags" + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/go-vanity" + + "github.com/AlecAivazis/survey/v2" + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Add = cli.Command{ + Name: "add", + Aliases: []string{"a"}, + Usage: "Add a package", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Overwrite existing package without prompt", + Destination: &flags.Force, + }, + &cli.BoolFlag{ + Name: "local", + Aliases: []string{"l"}, + Usage: "local mode", + Destination: &flags.Local, + }, + }, + Before: localOrToken, + Action: doAdd, +} + +func doAdd(_ *cli.Context) error { + pkg, err := pkgPrompt(vanity.Package{}) + if err != nil { + return err + } + + if flags.Local { + db, err := database.Load(flags.Database) + if err != nil { + return err + } + if err := db.PutPackage(pkg); err != nil { + return err + } + } else { + client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) + if err := client.Add(context.Background(), pkg); err != nil { + return err + } + } + + beaver.Infof("Added %s", yellow.Format(pkg.Name)) + return nil +} + +func validURL(ans interface{}) error { + if err := survey.Required(ans); err != nil { + return err + } + _, err := url.Parse(ans.(string)) + return err +} diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..b60c8a4 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,181 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + + "go.jolheiser.com/vanity/cmd/flags" + "go.jolheiser.com/vanity/contrib" + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/go-vanity" + "go.jolheiser.com/vanity/router" + + "github.com/AlecAivazis/survey/v2" + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver/color" +) + +var yellow = color.FgYellow + +func New() *cli.App { + app := cli.NewApp() + app.Name = "vanity" + app.Usage = "Vanity Import URLs" + app.Version = router.Version + app.Commands = []*cli.Command{ + &Add, + &Remove, + &Server, + &Update, + } + app.Flags = []cli.Flag{ + &cli.StringFlag{ + Name: "server", + Aliases: []string{"s"}, + Usage: "vanity server to use", + Value: vanity.DefaultServer, + EnvVars: []string{"VANITY_SERVER"}, + Destination: &flags.Server, + }, + &cli.StringFlag{ + Name: "token", + Aliases: []string{"t"}, + Usage: "vanity auth token to use", + DefaultText: "${VANITY_TOKEN}", + EnvVars: []string{"VANITY_TOKEN"}, + Destination: &flags.Token, + }, + &cli.StringFlag{ + Name: "database", + Aliases: []string{"d"}, + Usage: "path to vanity database for server", + Value: dbPath(), + DefaultText: "`${HOME}/vanity.db` or `${BINPATH}/vanity.db`", + EnvVars: []string{"VANITY_DATABASE"}, + Destination: &flags.Database, + }, + &cli.BoolFlag{ + Name: "systemd-service", + Usage: "Output example systemd service", + Destination: &flags.SystemdService, + Hidden: true, + }, + } + app.Action = action + return app +} + +func action(ctx *cli.Context) error { + if flags.SystemdService { + fmt.Println(contrib.SystemdService) + return nil + } + return cli.ShowAppHelp(ctx) +} + +func dbPath() string { + fn := "vanity.db" + home, err := os.UserHomeDir() + if err != nil { + bin, err := os.Executable() + if err != nil { + return fn + } + return filepath.Join(filepath.Dir(bin), fn) + } + return filepath.Join(home, fn) +} + +func localOrToken(_ *cli.Context) error { + if flags.Local && flags.Token == "" { + return errors.New("server interaction requires --token") + } + return nil +} + +func listPackages() ([]vanity.Package, error) { + var pkgs []vanity.Package + if flags.Local { + db, err := database.Load(flags.Database) + if err != nil { + return pkgs, err + } + pkgs, err = db.Packages() + if err != nil { + return pkgs, err + } + } else { + client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) + info, err := client.Info(context.Background()) + if err != nil { + return pkgs, err + } + pkgs = info.Packages + } + return pkgs, nil +} + +func pkgPrompt(def vanity.Package) (vanity.Package, error) { + if def.Branch == "" { + def.Branch = "main" + } + var pkg vanity.Package + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name", Default: def.Name}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Multiline{Message: "Description", Default: def.Description}, + Validate: survey.Required, + }, + { + Name: "branch", + Prompt: &survey.Input{Message: "Branch", Default: def.Branch}, + Validate: survey.Required, + }, + { + Name: "weburl", + Prompt: &survey.Input{Message: "Web URL", Default: def.WebURL}, + Validate: validURL, + }, + } + if err := survey.Ask(questions, &pkg); err != nil { + return pkg, err + } + + defHTTP, defSSH := def.CloneHTTP, def.CloneSSH + if def.WebURL != pkg.WebURL { + u, err := url.Parse(pkg.WebURL) + if err != nil { + return pkg, err + } + defHTTP = pkg.WebURL + ".git" + defSSH = fmt.Sprintf("git@%s:%s.git", u.Host, strings.TrimPrefix(u.Path, "/")) + } + + questions = []*survey.Question{ + { + Name: "clonehttp", + Prompt: &survey.Input{Message: "HTTP(S) CLone URL", Default: defHTTP}, + Validate: validURL, + }, + { + Name: "clonessh", + Prompt: &survey.Input{Message: "SSH CLone URL", Default: defSSH}, + Validate: survey.Required, + }, + } + if err := survey.Ask(questions, &pkg); err != nil { + return pkg, err + } + + return pkg, nil +} diff --git a/cmd/flags/flags.go b/cmd/flags/flags.go new file mode 100644 index 0000000..b9b584a --- /dev/null +++ b/cmd/flags/flags.go @@ -0,0 +1,14 @@ +package flags + +var ( + Server string + Domain string + Token string + Database string + + Local bool + Force bool + Port int + + SystemdService bool +) diff --git a/cmd/remove.go b/cmd/remove.go new file mode 100644 index 0000000..4de7bf6 --- /dev/null +++ b/cmd/remove.go @@ -0,0 +1,67 @@ +package cmd + +import ( + "context" + + "go.jolheiser.com/vanity/cmd/flags" + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/go-vanity" + + "github.com/AlecAivazis/survey/v2" + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Remove = cli.Command{ + Name: "remove", + Aliases: []string{"rm"}, + Usage: "Remove package(s)", + Before: localOrToken, + Action: doRemove, +} + +func doRemove(_ *cli.Context) error { + pkgs, err := listPackages() + if err != nil { + return err + } + + pkgSlice := make([]string, len(pkgs)) + pkgMap := make(map[string]vanity.Package) + for idx, pkg := range pkgs { + pkgSlice[idx] = pkg.Name + pkgMap[pkg.Name] = pkg + } + + pkgQuestion := &survey.Select{ + Message: "Select package to remove", + Options: pkgSlice, + } + + var pkgName string + if err := survey.AskOne(pkgQuestion, &pkgName); err != nil { + return err + } + + pkg := vanity.Package{ + Name: pkgName, + } + + if flags.Local { + db, err := database.Load(flags.Database) + if err != nil { + return err + } + if err := db.RemovePackage(pkg.Name); err != nil { + return err + } + } else { + client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) + if err := client.Remove(context.Background(), pkg); err != nil { + return err + } + } + + beaver.Infof("Removed %s", yellow.Format(pkgName)) + return nil +} diff --git a/cmd/server.go b/cmd/server.go new file mode 100644 index 0000000..9a28c1a --- /dev/null +++ b/cmd/server.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "errors" + "fmt" + "net/http" + + "go.jolheiser.com/vanity/cmd/flags" + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/router" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Server = cli.Command{ + Name: "server", + Aliases: []string{"web"}, + Usage: "Start the vanity server", + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Aliases: []string{"p"}, + Usage: "Port to run the vanity server on", + Value: 3333, + EnvVars: []string{"VANITY_PORT"}, + Destination: &flags.Port, + }, + &cli.StringFlag{ + Name: "domain", + Aliases: []string{"d"}, + Usage: "The Go module domain (e.g. go.jolheiser.com)", + EnvVars: []string{"VANITY_DOMAIN"}, + Destination: &flags.Domain, + }, + }, + Action: doServer, +} + +func doServer(_ *cli.Context) error { + if flags.Token == "" || flags.Domain == "" { + return errors.New("vanity server requires --token and --domain") + } + + db, err := database.Load(flags.Database) + if err != nil { + beaver.Fatalf("could not load database at %s: %v", flags.Database, err) + } + + beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) + if err := http.ListenAndServe(fmt.Sprintf(":%d", flags.Port), router.New(flags.Token, db)); err != nil { + return err + } + return nil +} diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..c9b3f0f --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "context" + + "go.jolheiser.com/vanity/cmd/flags" + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/go-vanity" + + "github.com/AlecAivazis/survey/v2" + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Update = cli.Command{ + Name: "update", + Aliases: []string{"u"}, + Usage: "Update a package", + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "local", + Aliases: []string{"l"}, + Usage: "local mode", + Destination: &flags.Local, + }, + }, + Before: localOrToken, + Action: doUpdate, +} + +func doUpdate(_ *cli.Context) error { + pkgs, err := listPackages() + if err != nil { + return err + } + + pkgSlice := make([]string, len(pkgs)) + pkgMap := make(map[string]vanity.Package) + for idx, pkg := range pkgs { + pkgSlice[idx] = pkg.Name + pkgMap[pkg.Name] = pkg + } + + pkgQuestion := &survey.Select{ + Message: "Select package to update", + Options: pkgSlice, + } + + var pkgName string + if err := survey.AskOne(pkgQuestion, &pkgName); err != nil { + return err + } + + pkg, err := pkgPrompt(pkgMap[pkgName]) + if err != nil { + return err + } + + if flags.Local { + db, err := database.Load(flags.Database) + if err != nil { + return err + } + if err := db.PutPackage(pkg); err != nil { + return err + } + } else { + client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) + if err := client.Update(context.Background(), pkg); err != nil { + return err + } + } + + beaver.Infof("Updated %s", yellow.Format(pkgName)) + return nil +} diff --git a/contrib/contrib.go b/contrib/contrib.go new file mode 100644 index 0000000..65e707e --- /dev/null +++ b/contrib/contrib.go @@ -0,0 +1,28 @@ +package contrib + +import ( + _ "embed" + "os" + "strings" +) + +//go:embed vanity.service +var SystemdService string + +func init() { + bin, err := os.Executable() + if err != nil { + return + } + SystemdService = os.Expand(SystemdService, func(s string) string { + switch strings.ToUpper(s) { + case "BIN": + return bin + case "VANITY_TOKEN": + return os.Getenv("VANITY_TOKEN") + case "VANITY_DOMAIN": + return os.Getenv("VANITY_DOMAIN") + } + return "" + }) +} diff --git a/contrib/vanity.service b/contrib/vanity.service new file mode 100644 index 0000000..31d161a --- /dev/null +++ b/contrib/vanity.service @@ -0,0 +1,19 @@ +[Unit] +Description=Vanity Go Imports +After=syslog.target +After=network.target + +[Service] +RestartSec=2s +Type=simple +User=vanity +Group=vanity +ExecStart=${bin} server +Restart=always + +# Required +Environment=VANITY_TOKEN=${VANITY_TOKEN} +Environment=VANITY_DOMAIN=${VANITY_DOMAIN} + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..aba2c68 --- /dev/null +++ b/database/database.go @@ -0,0 +1,84 @@ +package database + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + + "go.jolheiser.com/vanity/go-vanity" + + "go.etcd.io/bbolt" +) + +var packageBucket = []byte("packages") + +type Database struct { + db *bbolt.DB +} + +func Load(dbPath string) (*Database, error) { + if err := os.MkdirAll(filepath.Dir(dbPath), os.ModePerm); err != nil { + return nil, err + } + db, err := bbolt.Open(dbPath, os.ModePerm, nil) + if err != nil { + return nil, err + } + return &Database{ + db: db, + }, db.Update(func(tx *bbolt.Tx) error { + _, err := tx.CreateBucketIfNotExists(packageBucket) + return err + }) +} + +func (d *Database) Package(name string) (vanity.Package, error) { + var pkg vanity.Package + data, err := d.PackageJSON(name) + if err != nil { + return pkg, err + } + return pkg, json.NewDecoder(bytes.NewReader(data)).Decode(&pkg) +} + +func (d *Database) PackageJSON(name string) (pkg []byte, err error) { + return pkg, d.db.View(func(tx *bbolt.Tx) error { + pkg = tx.Bucket(packageBucket).Get([]byte(name)) + if pkg == nil { + return ErrPackageNotFound{ + Name: name, + } + } + return nil + }) +} + +func (d *Database) Packages() (pkgs []vanity.Package, err error) { + return pkgs, d.db.View(func(tx *bbolt.Tx) error { + return tx.Bucket(packageBucket).ForEach(func(key, val []byte) error { + var pkg vanity.Package + if err := json.NewDecoder(bytes.NewReader(val)).Decode(&pkg); err != nil { + return err + } + pkgs = append(pkgs, pkg) + return nil + }) + }) +} + +func (d *Database) PutPackage(pkg vanity.Package) error { + return d.db.Update(func(tx *bbolt.Tx) error { + data, err := json.Marshal(pkg) + if err != nil { + return err + } + return tx.Bucket(packageBucket).Put([]byte(pkg.Name), data) + }) +} + +func (d *Database) RemovePackage(name string) error { + return d.db.Update(func(tx *bbolt.Tx) error { + return tx.Bucket(packageBucket).Delete([]byte(name)) + }) +} diff --git a/database/errors.go b/database/errors.go new file mode 100644 index 0000000..b1e0e6a --- /dev/null +++ b/database/errors.go @@ -0,0 +1,16 @@ +package database + +import "fmt" + +type ErrPackageNotFound struct { + Name string +} + +func (e ErrPackageNotFound) Error() string { + return fmt.Sprintf("package not found: %s", e.Name) +} + +func IsErrPackageNotFound(err error) bool { + _, ok := err.(ErrPackageNotFound) + return ok +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index f7d7440..eb6df61 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -5,10 +5,7 @@ services: image: jolheiser/vanity:latest environment: - VANITY_DOMAIN=go.domain.tld - - VANITY_NAMESPACE= - VANITY_TOKEN= - #- VANITY_SERVICE=gitea - #- VANITY_BASE_URL=https://gitea.com restart: always ports: - "80:7777" \ No newline at end of file diff --git a/flags/config.go b/flags/config.go deleted file mode 100644 index cb1a9d0..0000000 --- a/flags/config.go +++ /dev/null @@ -1,105 +0,0 @@ -package flags - -import ( - "os" - "strings" - "time" - - "go.jolheiser.com/vanity/api" - - "github.com/pelletier/go-toml" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" -) - -type tomlConfig struct { - Port int `toml:"port"` - Domain string `toml:"domain"` - Service string `toml:"service"` - BaseURL string `toml:"base_url"` - Namespace string `toml:"namespace"` - Token string `toml:"token"` - Include []string `toml:"include"` - Exclude []string `toml:"exclude"` - Private bool `toml:"private"` - Fork bool `toml:"fork"` - Mirror bool `toml:"mirror"` - Archive bool `toml:"archive"` - Override []string `toml:"override"` - Interval time.Duration `toml:"interval"` - Debug bool `toml:"debug"` - - Packages []*api.Package `toml:"packages"` -} - -func setConfig(ctx *cli.Context) { - for _, env := range os.Environ() { - kv := strings.Split(env, "=") - if strings.HasPrefix(kv[0], "VANITY_OVERRIDES_") { - override := strings.ToLower(strings.TrimPrefix(kv[0], "VANITY_OVERRIDES_")) - Override[override] = kv[1] - } - } - - var cfg tomlConfig - if configPath != "" { - beaver.Infof("Loading configuration from %s", configPath) - tree, err := toml.LoadFile(configPath) - if err != nil { - beaver.Errorf("Could not load configuration from %s: %v", configPath, err) - return - } - if err = tree.Unmarshal(&cfg); err != nil { - beaver.Errorf("Could not unmarshal configuration from %s: %v", configPath, err) - return - } - } - - if !ctx.IsSet("port") && cfg.Port > 0 { - Port = cfg.Port - } - if !ctx.IsSet("domain") && cfg.Domain != "" { - Domain = cfg.Domain - } - if !ctx.IsSet("service") && cfg.Service != "" { - Service = cfg.Service - } - if !ctx.IsSet("base-url") && cfg.BaseURL != "" { - baseURL = cfg.BaseURL - } - if !ctx.IsSet("namespace") && cfg.Namespace != "" { - Namespace = cfg.Namespace - } - if !ctx.IsSet("token") && cfg.Token != "" { - Token = cfg.Token - } - if !ctx.IsSet("include") && len(cfg.Include) > 0 { - _ = include.Set(strings.Join(cfg.Include, ",")) - } - if !ctx.IsSet("exclude") && len(cfg.Exclude) > 0 { - _ = exclude.Set(strings.Join(cfg.Exclude, ",")) - } - if !ctx.IsSet("override") && len(cfg.Override) > 0 { - _ = override.Set(strings.Join(cfg.Override, ",")) - } - if !ctx.IsSet("private") && cfg.Private { - Private = cfg.Private - } - if !ctx.IsSet("fork") && cfg.Fork { - Fork = cfg.Fork - } - if !ctx.IsSet("mirror") && cfg.Mirror { - Mirror = cfg.Mirror - } - if !ctx.IsSet("archive") && cfg.Archive { - Archive = cfg.Archive - } - if !ctx.IsSet("interval") && cfg.Interval.Seconds() > 0 { - Interval = cfg.Interval - } - if !ctx.IsSet("debug") && cfg.Debug { - Debug = cfg.Debug - } - - ConfigPackages = cfg.Packages -} diff --git a/flags/flags.go b/flags/flags.go deleted file mode 100644 index c3be0d3..0000000 --- a/flags/flags.go +++ /dev/null @@ -1,236 +0,0 @@ -package flags - -import ( - "errors" - "fmt" - "net/url" - "regexp" - "strings" - "time" - - "go.jolheiser.com/vanity/api" - - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" -) - -var ( - configPath string - baseURL string - include cli.StringSlice - exclude cli.StringSlice - override cli.StringSlice - - Port int - Domain string - Service string - BaseURL *url.URL - Namespace string - Token string - Include []*regexp.Regexp - Exclude []*regexp.Regexp - Private bool - Fork bool - Mirror bool - Archive bool - Override = make(map[string]string) - Interval time.Duration - Manual bool - Topics bool - Debug bool - - ConfigPackages []*api.Package -) - -var Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "config", - Usage: "Path to a config file", - EnvVars: []string{"VANITY_CONFIG"}, - Destination: &configPath, - }, - &cli.IntFlag{ - Name: "port", - Usage: "Port to run the vanity server on", - Value: 7777, - EnvVars: []string{"VANITY_PORT"}, - Destination: &Port, - }, - &cli.StringFlag{ - Name: "domain", - Usage: "Vanity domain, e.g. go.domain.tld", - EnvVars: []string{"VANITY_DOMAIN"}, - Required: true, - Destination: &Domain, - }, - &cli.StringFlag{ - Name: "service", - Usage: "Service type (Gitea, GitHub, GitLab)", - Value: "gitea", - EnvVars: []string{"VANITY_SERVICE"}, - Destination: &Service, - }, - &cli.StringFlag{ - Name: "base-url", - Usage: "Base URL to service", - EnvVars: []string{"VANITY_BASE_URL"}, - Destination: &baseURL, - }, - &cli.StringFlag{ - Name: "namespace", - Usage: "Owner namespace", - EnvVars: []string{"VANITY_NAMESPACE"}, - Destination: &Namespace, - }, - &cli.StringFlag{ - Name: "token", - Usage: "Access token", - EnvVars: []string{"VANITY_TOKEN"}, - Destination: &Token, - }, - &cli.StringSliceFlag{ - Name: "include", - Usage: "Repository names to include (regex)", - EnvVars: []string{"VANITY_INCLUDE"}, - Destination: &include, - }, - &cli.StringSliceFlag{ - Name: "exclude", - Usage: "Repository names to exclude (regex)", - EnvVars: []string{"VANITY_EXCLUDE"}, - Destination: &exclude, - }, - &cli.BoolFlag{ - Name: "private", - Usage: "Include private repositories", - EnvVars: []string{"VANITY_PRIVATE"}, - Destination: &Private, - }, - &cli.BoolFlag{ - Name: "fork", - Usage: "Include forked repositories", - EnvVars: []string{"VANITY_FORK"}, - Destination: &Fork, - }, - &cli.BoolFlag{ - Name: "mirror", - Usage: "Include mirrored repositories", - EnvVars: []string{"VANITY_MIRROR"}, - Destination: &Mirror, - }, - &cli.BoolFlag{ - Name: "archive", - Usage: "Include archived repositories", - EnvVars: []string{"VANITY_ARCHIVE"}, - Destination: &Archive, - }, - &cli.StringSliceFlag{ - Name: "override", - Usage: "Repository name to override (NAME=OVERRIDE)", - EnvVars: []string{"VANITY_OVERRIDE"}, - Destination: &override, - }, - &cli.DurationFlag{ - Name: "interval", - Usage: "Interval between updating repositories", - Value: time.Minute * 15, - EnvVars: []string{"VANITY_INTERVAL"}, - Destination: &Interval, - }, - &cli.BoolFlag{ - Name: "manual", - Usage: "Disable cron and only update with endpoint", - EnvVars: []string{"VANITY_MANUAL"}, - Destination: &Manual, - }, - &cli.BoolFlag{ - Name: "topics", - Usage: "Group projects by topic by default", - EnvVars: []string{"VANITY_TOPICS"}, - Destination: &Topics, - }, - &cli.BoolFlag{ - Name: "debug", - Usage: "Debug logging", - EnvVars: []string{"VANITY_DEBUG"}, - Destination: &Debug, - }, -} - -func Before(ctx *cli.Context) error { - setConfig(ctx) - - var defaultURL string - var configOnly bool - switch strings.ToLower(Service) { - case "gitea": - defaultURL = "https://gitea.com" - case "github": - defaultURL = "https://github.com" - case "gitlab": - defaultURL = "https://gitlab.com" - case "off": - configOnly = true - beaver.Infof("Running in config-only mode") - defaultURL = "https://domain.tld" - default: - return errors.New("unrecognized service type") - } - - if baseURL == "" { - baseURL = defaultURL - } - - var err error - BaseURL, err = url.Parse(baseURL) - if err != nil { - return err - } - - if !configOnly { - errs := make([]string, 0, 2) - if Namespace == "" { - errs = append(errs, "namespace") - } - if Token == "" { - errs = append(errs, "token") - } - if len(errs) > 0 { - return fmt.Errorf("%s is required with a service", strings.Join(errs, ", ")) - } - } - - Include = make([]*regexp.Regexp, len(include.Value())) - for idx, i := range include.Value() { - Include[idx] = regexp.MustCompile(i) - } - - Exclude = make([]*regexp.Regexp, len(exclude.Value())) - for idx, e := range exclude.Value() { - Exclude[idx] = regexp.MustCompile(e) - } - - if Manual { - beaver.Info("Running in manual mode") - } - - if Debug { - beaver.Console.Level = beaver.DEBUG - } - - beaver.Debugf("Port: %d", Port) - beaver.Debugf("Domain: %s", Domain) - beaver.Debugf("Service: %s", Service) - beaver.Debugf("Base URL: %s", baseURL) - beaver.Debugf("Namespace: %s", Namespace) - beaver.Debugf("Include: %s", include.Value()) - beaver.Debugf("Exclude: %s", exclude.Value()) - beaver.Debugf("Private: %t", Private) - beaver.Debugf("Fork: %t", Fork) - beaver.Debugf("Mirror: %t", Mirror) - beaver.Debugf("Archive: %t", Archive) - beaver.Debugf("Override: %s", override.Value()) - beaver.Debugf("Interval: %s", Interval) - beaver.Debugf("Manual: %t", Manual) - return nil -} diff --git a/go-vanity/client.go b/go-vanity/client.go new file mode 100644 index 0000000..9c1b76d --- /dev/null +++ b/go-vanity/client.go @@ -0,0 +1,53 @@ +package vanity + +import ( + "context" + "io" + "net/http" +) + +const ( + DefaultServer = "https://go.jolheiser.com" + TokenHeader = "X-Vanity-Token" +) + +type Client struct { + token string + server string + http *http.Client +} + +func New(token string, opts ...ClientOption) *Client { + c := &Client{ + token: token, + server: DefaultServer, + http: http.DefaultClient, + } + for _, opt := range opts { + opt(c) + } + return c +} + +type ClientOption func(*Client) + +func WithHTTP(client *http.Client) ClientOption { + return func(c *Client) { + c.http = client + } +} + +func WithServer(server string) ClientOption { + return func(c *Client) { + c.server = server + } +} + +func (c *Client) newRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, method, url, body) + if err != nil { + return nil, err + } + req.Header.Set(TokenHeader, c.token) + return req, nil +} diff --git a/go-vanity/go.mod b/go-vanity/go.mod new file mode 100644 index 0000000..97706e6 --- /dev/null +++ b/go-vanity/go.mod @@ -0,0 +1,3 @@ +module go.jolheiser.com/vanity/go-vanity + +go 1.16 diff --git a/go-vanity/package.go b/go-vanity/package.go new file mode 100644 index 0000000..1e8ef76 --- /dev/null +++ b/go-vanity/package.go @@ -0,0 +1,104 @@ +package vanity + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "strings" +) + +type Info struct { + Version string `json:"version"` + NumPackages int `json:"num_packages"` + Packages []Package `json:"packages"` +} + +type Package struct { + Name string `json:"name"` + Description string `json:"description"` + Branch string `json:"branch"` + WebURL string `json:"web_url"` + CloneHTTP string `json:"clone_http"` + CloneSSH string `json:"clone_ssh"` +} + +func (p Package) Module(domain string) string { + return fmt.Sprintf("%s/%s", strings.TrimSuffix(domain, "/"), strings.ToLower(p.Name)) +} + +// Info gets Info from a vanity server +func (c *Client) Info(ctx context.Context) (Info, error) { + var info Info + resp, err := c.crud(ctx, Package{}, http.MethodOptions) + if err != nil { + return info, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return info, fmt.Errorf("could not get info: %s", resp.Status) + } + + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + return info, err + } + + return info, nil +} + +// Add adds a new Package to a vanity server +func (c *Client) Add(ctx context.Context, pkg Package) error { + resp, err := c.crud(ctx, pkg, http.MethodPost) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusCreated { + return fmt.Errorf("could not add package: %s", resp.Status) + } + return nil +} + +// Update updates a Package on a vanity server +func (c *Client) Update(ctx context.Context, pkg Package) error { + resp, err := c.crud(ctx, pkg, http.MethodPatch) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("could not update package: %s", resp.Status) + } + + return nil +} + +// Remove removes a Package from a vanity server +func (c *Client) Remove(ctx context.Context, pkg Package) error { + resp, err := c.crud(ctx, pkg, http.MethodDelete) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("could not remove package: %s", resp.Status) + } + + return nil +} + +func (c *Client) crud(ctx context.Context, pkg Package, method string) (*http.Response, error) { + payload, err := json.Marshal(pkg) + if err != nil { + return nil, err + } + + req, err := c.newRequest(ctx, method, c.server, bytes.NewReader(payload)) + if err != nil { + return nil, err + } + + return c.http.Do(req) +} diff --git a/go-vanity/source.go b/go-vanity/source.go new file mode 100644 index 0000000..ea58e74 --- /dev/null +++ b/go-vanity/source.go @@ -0,0 +1,45 @@ +package vanity + +import ( + "errors" + "fmt" + "strings" +) + +type SourceDirFile struct { + Dir string + File string +} + +func GiteaSDF(pkg Package) SourceDirFile { + return SourceDirFile{ + Dir: fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.WebURL, pkg.Branch), + File: fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch), + } +} + +func GitHubSDF(pkg Package) SourceDirFile { + return SourceDirFile{ + Dir: fmt.Sprintf("%s/tree/%s{/dir}", pkg.WebURL, pkg.Branch), + File: fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch), + } +} + +func GitLabSDF(pkg Package) SourceDirFile { + return SourceDirFile{ + Dir: fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.WebURL, pkg.Branch), + File: fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch), + } +} + +func AnalyzeSDF(pkg Package) (SourceDirFile, error) { + switch { + case strings.Contains(pkg.WebURL, "gitea.com"): + return GiteaSDF(pkg), nil + case strings.Contains(pkg.WebURL, "github.com"): + return GitHubSDF(pkg), nil + case strings.Contains(pkg.WebURL, "gitlab.com"): + return GitLabSDF(pkg), nil + } + return SourceDirFile{}, errors.New("could not detect SDF") +} diff --git a/go-vanity/vanity_test.go b/go-vanity/vanity_test.go new file mode 100644 index 0000000..93f9852 --- /dev/null +++ b/go-vanity/vanity_test.go @@ -0,0 +1,194 @@ +package vanity + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +var ( + server *httptest.Server + token = "TestingLibrary" + version = "VanityTest" + + packages = []Package{ + { + Name: "test1", + Description: "test1", + Branch: "main", + WebURL: "https://gitea.com/jolheiser/test1", + CloneHTTP: "https://gitea.com/jolheiser/test1.git", + CloneSSH: "https://gitea.com/jolheiser/test1", + }, + } +) + +func TestMain(m *testing.M) { + server = httptest.NewServer(http.HandlerFunc(testServer)) + os.Exit(m.Run()) +} + +func TestClient(t *testing.T) { + ctx := context.Background() + client := New("", WithServer(server.URL)) + + // Info + checkInfo(t, client, 1) + + pkg1 := Package{ + Name: "test1", + Description: "test1", + Branch: "main", + WebURL: "https://gitea.com/jolheiser/test1", + CloneHTTP: "https://gitea.com/jolheiser/test1.git", + CloneSSH: "https://gitea.com/jolheiser/test1", + } + pkg2 := Package{ + Name: "test2", + Description: "test2", + Branch: "main", + WebURL: "https://gitea.com/jolheiser/test2", + CloneHTTP: "https://gitea.com/jolheiser/test2.git", + CloneSSH: "https://gitea.com/jolheiser/test2", + } + + // Add (without token) + if err := client.Add(ctx, pkg1); err == nil { + t.Log("adding without token should fail") + t.Fail() + } + + // Add (with token) + client = New(token, WithServer(server.URL)) + checkAdd(t, client, pkg1, pkg2) + + // Info (after second package) + checkInfo(t, client, 2) + + // Update package + checkUpdate(t, client, pkg1) + + // Remove + checkRemove(t, client, pkg1) + + // Info (final) + checkInfo(t, client, 1) +} + +func checkInfo(t *testing.T, client *Client, numPackages int) { + info, err := client.Info(context.Background()) + if err != nil { + t.Logf("info should not return error: %v\n", err) + t.Fail() + } + if info.Version != version || info.NumPackages != numPackages { + t.Log("info did not match expected") + t.Fail() + } +} + +func checkAdd(t *testing.T, client *Client, pkg1, pkg2 Package) { + ctx := context.Background() + if err := client.Add(ctx, pkg2); err != nil { + t.Logf("pkg2 should be added: %v\n", err) + t.Fail() + } + // Duplicate package + if err := client.Add(ctx, pkg1); err == nil { + t.Log("pkg1 should already exist") + t.Fail() + } +} + +func checkUpdate(t *testing.T, client *Client, pkg Package) { + ctx := context.Background() + // Update invalid package + if err := client.Update(ctx, Package{Name: "test4"}); err == nil { + t.Log("should not be able to update invalid package") + t.Fail() + } + + // Update valid package + if err := client.Update(ctx, pkg); err != nil { + t.Logf("should be able to update valid package: %v\n", err) + t.Fail() + } +} + +func checkRemove(t *testing.T, client *Client, pkg Package) { + ctx := context.Background() + if err := client.Remove(ctx, pkg); err != nil { + t.Logf("should be able to remove package: %v\n", err) + t.Fail() + } + + // Remove (idempotent) + if err := client.Remove(ctx, pkg); err != nil { + t.Logf("should be able to remove package idempotently: %v\n", err) + t.Fail() + } +} + +func testServer(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/": + switch r.Method { + case http.MethodOptions: + resp := Info{ + Version: version, + NumPackages: len(packages), + } + _ = json.NewEncoder(w).Encode(resp) + case http.MethodPost, http.MethodPatch, http.MethodDelete: + if r.Header.Get(TokenHeader) != token { + w.WriteHeader(http.StatusUnauthorized) + return + } + var pkg Package + if err := json.NewDecoder(r.Body).Decode(&pkg); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + switch r.Method { + case http.MethodPost: + for _, p := range packages { + if p.Name == pkg.Name { + w.WriteHeader(http.StatusConflict) + return + } + } + packages = append(packages, pkg) + w.WriteHeader(http.StatusCreated) + case http.MethodPatch: + for idx, p := range packages { + if p.Name == pkg.Name { + packages[idx] = pkg + return + } + } + w.WriteHeader(http.StatusNotFound) + case http.MethodDelete: + for idx, p := range packages { + if p.Name == pkg.Name { + packages = append(packages[:idx], packages[idx+1:]...) + } + } + } + return + } + return + default: + name := strings.TrimPrefix(r.URL.Path, "/") + for _, pkg := range packages { + if pkg.Name == name { + _ = json.NewEncoder(w).Encode(pkg) + return + } + } + } + w.WriteHeader(http.StatusNotImplemented) +} diff --git a/go.mod b/go.mod index e04bf33..2da1ea9 100644 --- a/go.mod +++ b/go.mod @@ -2,18 +2,18 @@ module go.jolheiser.com/vanity go 1.16 +replace go.jolheiser.com/vanity/go-vanity => ./go-vanity + require ( - code.gitea.io/sdk/gitea v0.13.2 + github.com/AlecAivazis/survey/v2 v2.2.8 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-chi/chi v4.1.2+incompatible - github.com/go-chi/cors v1.1.1 // indirect - github.com/google/go-github/v32 v32.1.0 - github.com/pelletier/go-toml v1.8.1 github.com/urfave/cli/v2 v2.2.0 - github.com/xanzy/go-gitlab v0.37.0 + go.etcd.io/bbolt v1.3.5 go.jolheiser.com/beaver v1.0.2 - go.jolheiser.com/overlay v0.0.2 // indirect - golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect - golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 + go.jolheiser.com/overlay v0.0.2 + go.jolheiser.com/vanity/go-vanity v0.0.0-00010101000000-000000000000 + golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect + golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index f381b7e..b1d1258 100644 --- a/go.sum +++ b/go.sum @@ -1,413 +1,62 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -code.gitea.io/sdk/gitea v0.13.2 h1:wAnT/J7Z62q3fJXbgnecoaOBh8CM1Qq0/DakWxiv4yA= -code.gitea.io/sdk/gitea v0.13.2/go.mod h1:lee2y8LeV3kQb2iK+hHlMqoadL4bp27QOkOV/hawLKg= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/AlecAivazis/survey/v2 v2.2.8 h1:TgxCwybKdBckmC+/P9/5h49rw/nAHe/itZL0dgHs+Q0= +github.com/AlecAivazis/survey/v2 v2.2.8/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-chi/cors v1.1.1 h1:eHuqxsIw89iXcWnWUN8R72JMibABJTN/4IOYI5WERvw= -github.com/go-chi/cors v1.1.1/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II= -github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-retryablehttp v0.6.4 h1:BbgctKO892xEyOXnGiaAwIoSq1QZ/SS4AhjoAh9DnfY= -github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ= +github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/xanzy/go-gitlab v0.37.0 h1:Z/CQkjj5VwbWVYVL7S70kS/TFj5H/pJumV7xbJ0YUQ8= -github.com/xanzy/go-gitlab v0.37.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= go.jolheiser.com/overlay v0.0.2 h1:cwEHLbWqdH7lEOG87WUwgUGVqfOWBsWe03FiHHmuTWE= go.jolheiser.com/overlay v0.0.2/go.mod h1:xNbssakJ3HjK4RnjuP38q9yQNS4wxXKsyprYIWWr2bg= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go index 9562d94..59f2e39 100644 --- a/main.go +++ b/main.go @@ -1,26 +1,14 @@ package main import ( - "fmt" - "net/http" "os" - "go.jolheiser.com/vanity/api" - "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/router" + "go.jolheiser.com/vanity/cmd" - "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" ) func main() { - app := cli.NewApp() - app.Name = "vanity" - app.Usage = "Vanity Go Imports" - app.Version = api.Version - app.Action = doAction - app.Flags = flags.Flags - app.Before = flags.Before beaver.Console.Format = beaver.FormatOptions{ TimePrefix: true, @@ -30,18 +18,7 @@ func main() { LevelColor: true, } - if err := app.Run(os.Args); err != nil { + if err := cmd.New().Run(os.Args); err != nil { beaver.Fatal(err) } } - -func doAction(ctx *cli.Context) error { - mux, err := router.Init() - if err != nil { - return err - } - if err := http.ListenAndServe(fmt.Sprintf(":%s", ctx.String("port")), mux); err != nil { - return err - } - return nil -} diff --git a/router/api.go b/router/api.go deleted file mode 100644 index 9687b53..0000000 --- a/router/api.go +++ /dev/null @@ -1,71 +0,0 @@ -package router - -import ( - "encoding/json" - "github.com/go-chi/chi" - "github.com/go-chi/cors" - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/api" - "go.jolheiser.com/vanity/flags" - "net/http" - "runtime" - "time" -) - -func apiRoutes() *chi.Mux { - r := chi.NewRouter() - r.Use(cors.AllowAll().Handler) - - r.Get("/status", doAPIStatus) - r.Get("/update", doAPIUpdate) - - return r -} - -func doAPIStatus(res http.ResponseWriter, _ *http.Request) { - res.Header().Set("Content-Type", "application/json") - - var nextUpdate *string - if !lastUpdate.IsZero() { - nu := lastUpdate.Add(flags.Interval).Format(time.RFC3339) - nextUpdate = &nu - } - - resp := map[string]interface{}{ - "vanity_version": api.Version, - "go_version": runtime.Version(), - "num_packages": len(cache.Packages), - "next_update": nextUpdate, - } - - data, err := json.Marshal(&resp) - if err != nil { - beaver.Errorf("could not marshal API status: %v", err) - data = []byte("{}") - } - - if _, err = res.Write(data); err != nil { - beaver.Errorf("could not write response: %v", err) - } -} - -func doAPIUpdate(res http.ResponseWriter, _ *http.Request) { - res.Header().Set("Content-Type", "application/json") - - resp := map[string]bool{ - "updated": false, - } - if canUpdate { - updateCache() - resp["updated"] = true - } - - payload, err := json.Marshal(resp) - if err != nil { - beaver.Errorf("could not marshal payload: %v", err) - } - - if _, err = res.Write(payload); err != nil { - beaver.Errorf("could not write response: %v", err) - } -} diff --git a/router/cache.go b/router/cache.go deleted file mode 100644 index 88e37d9..0000000 --- a/router/cache.go +++ /dev/null @@ -1,110 +0,0 @@ -package router - -import ( - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/service" - "strings" - "sync" - - "go.jolheiser.com/vanity/api" -) - -var cache = &packageCache{ - Packages: make(map[string]*api.Package), -} - -type packageList map[string]*api.Package - -func (p packageList) Names() []string { - names := make([]string, len(p)) - idx := 0 - for name := range p { - names[idx] = name - idx++ - } - return names -} - -func (p packageList) Topics() map[string][]*api.Package { - topics := make(map[string][]*api.Package, 0) - for _, pkg := range p { - if len(pkg.Topics) == 0 { - if tt, ok := topics["other"]; ok { - topics["other"] = append(tt, pkg) - } else { - topics["other"] = []*api.Package{pkg} - } - } - for _, t := range pkg.Topics { - if tt, ok := topics[t]; ok { - topics[t] = append(tt, pkg) - } else { - topics[t] = []*api.Package{pkg} - } - } - } - return topics -} - -type packageCache struct { - Packages packageList - sync.Mutex -} - -func (c *packageCache) Update(packages map[string]*api.Package) { - c.Lock() - c.Packages = packages - c.Unlock() -} - -func updateCache() { - packages, err := svc.Packages() - if err != nil { - beaver.Errorf("could not update packages: %v", err) - return - } - - // Filter - for name, pkg := range packages { - if err := service.Check(pkg); err != nil { - beaver.Debug(err) - delete(packages, name) - continue - } - goMod, err := svc.GoMod(pkg) - if err != nil { - beaver.Debugf("No go.mod could be found in the root directory of %s", pkg.Name) - delete(packages, name) - continue - } - lines := strings.Split(goMod, "\n") - line := strings.Fields(lines[0]) - if !strings.HasPrefix(line[1], flags.Domain) { - beaver.Debugf("%s is a Go project, however its module does not include this domain", pkg.Name) - delete(packages, name) - continue - } - beaver.Debugf("Including %s", pkg.Name) - } - - // Overrides - for name, pkg := range packages { - for key, override := range flags.Override { - if strings.EqualFold(name, key) { - beaver.Debugf("Overriding %s -> %s", name, override) - delete(packages, key) - pkg.Name = override - packages[override] = pkg - } - } - } - - // Add packages manually added to config - for _, pkg := range flags.ConfigPackages { - packages[pkg.Name] = pkg - } - - cache.Update(packages) - canUpdate = false -} diff --git a/router/cron.go b/router/cron.go deleted file mode 100644 index bc609c6..0000000 --- a/router/cron.go +++ /dev/null @@ -1,30 +0,0 @@ -package router - -import ( - "go.jolheiser.com/beaver" - "go.jolheiser.com/vanity/service" - "time" - - "go.jolheiser.com/vanity/flags" -) - -var ( - svc service.Service - lastUpdate time.Time - canUpdate bool -) - -func cronStart() { - canUpdate = true - ticker := time.NewTicker(flags.Interval) - for { - <-ticker.C - if !flags.Manual && canUpdate { - beaver.Debug("Running package update...") - updateCache() - beaver.Debugf("Finished package update: %s", cache.Packages.Names()) - lastUpdate = time.Now() - } - canUpdate = true - } -} diff --git a/router/router.go b/router/router.go index 1b742b8..8720345 100644 --- a/router/router.go +++ b/router/router.go @@ -1,92 +1,208 @@ package router import ( + "encoding/json" "fmt" - "html/template" + "io" "net/http" "strings" "time" - "go.jolheiser.com/vanity/flags" - "go.jolheiser.com/vanity/service" + "go.jolheiser.com/vanity/cmd/flags" + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/go-vanity" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "go.jolheiser.com/beaver" ) -var tmpl *template.Template - -func Init() (*chi.Mux, error) { - var err error - tmpl, err = Templates() - if err != nil { - return nil, err - } - +func New(token string, db *database.Database) *chi.Mux { r := chi.NewRouter() r.Use(middleware.RedirectSlashes) r.Use(middleware.Recoverer) r.Use(middleware.Timeout(60 * time.Second)) - r.Get("/", doIndex) - r.Get("/*", doVanity) - r.Mount("/_", apiRoutes()) + r.Get("/", indexGET(db)) + r.Options("/", infoPackages(db)) + r.Post("/", addUpdatePackage(db, token)) + r.Patch("/", addUpdatePackage(db, token)) + r.Delete("/", removePackage(db, token)) + r.Get("/*", vanityGET(db)) - svc = service.New() - - beaver.Info("Warming up cache...") - updateCache() - beaver.Infof("Finished warming up cache: %s", cache.Packages.Names()) - go cronStart() - - beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) - return r, nil + return r } -func doIndex(res http.ResponseWriter, req *http.Request) { - format := "list" - if flags.Topics { - format = "topics" - } - q := req.URL.Query().Get("format") - if q != "" { - format = q - } +func indexGET(db *database.Database) http.HandlerFunc { + return func(res http.ResponseWriter, req *http.Request) { + packages, err := db.Packages() + if err != nil { + beaver.Errorf("could not load packages: %v", err) + http.Error(res, "could not load packages", http.StatusInternalServerError) + return + } - if err := tmpl.Lookup("index.tmpl").Execute(res, map[string]interface{}{ - "Packages": cache.Packages, - "Index": true, - "Format": format, - }); err != nil { - beaver.Errorf("could not write response: %v", err) - } -} + tpl, err := tmpl("index.tmpl") + if err != nil { + beaver.Warnf("could not load index template: %v", err) + } -func doVanity(res http.ResponseWriter, req *http.Request) { - key := chi.URLParam(req, "*") - pkg, ok := cache.Packages[strings.Split(key, "/")[0]] - if !ok { - http.NotFound(res, req) - return - } - - ctx := map[string]interface{}{ - "Package": pkg, - "Module": pkg.Module(flags.Domain), - "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, svc.GoDir(pkg), svc.GoFile(pkg)), - "Index": false, - } - - q := req.URL.Query() - if q.Get("go-get") != "" || q.Get("git-import") != "" { - if err := tmpl.Lookup("import.tmpl").Execute(res, ctx); err != nil { + if err := tpl.Execute(res, map[string]interface{}{ + "Packages": packages, + "Index": true, + }); err != nil { beaver.Errorf("could not write response: %v", err) } - return - } - - if err := tmpl.Lookup("vanity.tmpl").Execute(res, ctx); err != nil { - beaver.Errorf("could not write response: %v", err) + } +} + +func vanityGET(db *database.Database) http.HandlerFunc { + return func(res http.ResponseWriter, req *http.Request) { + key := chi.URLParam(req, "*") + key = strings.Split(key, "/")[0] + + pkg, err := db.Package(key) + if err != nil { + if database.IsErrPackageNotFound(err) { + http.NotFound(res, req) + return + } + http.Error(res, "could not load package", http.StatusInternalServerError) + return + } + + sdf, err := vanity.AnalyzeSDF(pkg) + if err != nil { + beaver.Warnf("could not get SDF for %s: %v", key, err) + } + + ctx := map[string]interface{}{ + "Package": pkg, + "Module": pkg.Module(flags.Domain), + "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, sdf.Dir, sdf.File), + "Index": false, + } + + q := req.URL.Query() + if q.Get("go-get") != "" || q.Get("git-import") != "" { + tpl, err := tmpl("import.tmpl") + if err != nil { + beaver.Warnf("could not load import template: %v", err) + } + if err := tpl.Execute(res, ctx); err != nil { + beaver.Errorf("could not write response: %v", err) + } + return + } + + tpl, err := tmpl("vanity.tmpl") + if err != nil { + beaver.Warnf("could not load vanity template: %v", err) + } + if err := tpl.Execute(res, ctx); err != nil { + beaver.Errorf("could not write response: %v", err) + } + } +} + +func infoPackages(db *database.Database) func(http.ResponseWriter, *http.Request) { + return func(res http.ResponseWriter, req *http.Request) { + packages, err := db.Packages() + if err != nil { + http.Error(res, "could not load package", http.StatusInternalServerError) + return + } + + info := vanity.Info{ + Version: Version, + NumPackages: len(packages), + Packages: packages, + } + + if err := json.NewEncoder(res).Encode(info); err != nil { + http.Error(res, "could not marshal info", http.StatusInternalServerError) + } + } +} + +func addUpdatePackage(db *database.Database, token string) func(http.ResponseWriter, *http.Request) { + return func(res http.ResponseWriter, req *http.Request) { + if req.Header.Get(vanity.TokenHeader) != token { + res.WriteHeader(http.StatusUnauthorized) + return + } + + data, err := io.ReadAll(req.Body) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + defer req.Body.Close() + + var pkg vanity.Package + if err := json.Unmarshal(data, &pkg); err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + + exists, err := db.PackageJSON(pkg.Name) + if err != nil && !database.IsErrPackageNotFound(err) { + res.WriteHeader(http.StatusInternalServerError) + return + } + + switch req.Method { + case http.MethodPost: + if exists != nil { + res.WriteHeader(http.StatusConflict) + return + } + case http.MethodPatch: + if exists == nil { + res.WriteHeader(http.StatusNotFound) + return + } + } + + if err := db.PutPackage(pkg); err != nil { + res.WriteHeader(http.StatusInternalServerError) + return + } + + switch req.Method { + case http.MethodPost: + res.WriteHeader(http.StatusCreated) + case http.MethodPatch: + res.WriteHeader(http.StatusOK) + } + } +} + +func removePackage(db *database.Database, token string) func(http.ResponseWriter, *http.Request) { + return func(res http.ResponseWriter, req *http.Request) { + if req.Header.Get(vanity.TokenHeader) != token { + res.WriteHeader(http.StatusUnauthorized) + return + } + + data, err := io.ReadAll(req.Body) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + defer req.Body.Close() + + var pkg vanity.Package + if err := json.Unmarshal(data, &pkg); err != nil { + res.WriteHeader(http.StatusBadRequest) + return + } + + if err := db.RemovePackage(pkg.Name); err != nil { + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.WriteHeader(http.StatusOK) } } diff --git a/router/router_test.go b/router/router_test.go new file mode 100644 index 0000000..dea64e6 --- /dev/null +++ b/router/router_test.go @@ -0,0 +1,175 @@ +package router + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "go.jolheiser.com/vanity/database" + "go.jolheiser.com/vanity/go-vanity" + + "go.jolheiser.com/beaver" +) + +var ( + server *httptest.Server + token = "TestingRouter" +) + +// NOTE: The router test is more or less a copy/paste from go-vanity +// However, this ensures that testing is the same with the "real" router and DB +func TestMain(m *testing.M) { + tmp, err := os.MkdirTemp(os.TempDir(), "vanity") + if err != nil { + panic(err) + } + dbPath := filepath.Join(tmp, "vanity.db") + + db, err := database.Load(dbPath) + if err != nil { + beaver.Fatalf("could not load database at %s: %v", dbPath, err) + } + + server = httptest.NewServer(New(token, db)) + + code := m.Run() + + // Cleanup + if err := os.RemoveAll(tmp); err != nil { + panic(err) + } + + os.Exit(code) +} + +func TestRouter(t *testing.T) { + ctx := context.Background() + client := vanity.New("", vanity.WithServer(server.URL)) + + // Info + checkInfo(t, client, 0) + + pkg1 := vanity.Package{ + Name: "test1", + Description: "test1", + Branch: "main", + WebURL: "https://gitea.com/jolheiser/test1", + CloneHTTP: "https://gitea.com/jolheiser/test1.git", + CloneSSH: "https://gitea.com/jolheiser/test1", + } + pkg2 := vanity.Package{ + Name: "test2", + Description: "test2", + Branch: "main", + WebURL: "https://gitea.com/jolheiser/test2", + CloneHTTP: "https://gitea.com/jolheiser/test2.git", + CloneSSH: "https://gitea.com/jolheiser/test2", + } + + // Add (without token) + if err := client.Add(ctx, pkg1); err == nil { + t.Log("adding without token should fail") + t.Fail() + } + + // Add (with token) + client = vanity.New(token, vanity.WithServer(server.URL)) + checkAdd(t, client, pkg1, pkg2) + + // Info (after second package) + checkInfo(t, client, 2) + + // Check invalid package (404) + checkResp(t, "test3", http.StatusNotFound) + + // Check valid package (200) + checkResp(t, "test1", http.StatusOK) + + // Check valid sub-package (200) + checkResp(t, "test1/foo/bar", http.StatusOK) + + // Update package + checkUpdate(t, client, pkg1) + + // Remove + checkRemove(t, client, pkg1) + + // Info (final) + checkInfo(t, client, 1) +} + +func checkInfo(t *testing.T, client *vanity.Client, numPackages int) { + info, err := client.Info(context.Background()) + if err != nil { + t.Logf("info should not return error: %v\n", err) + t.Fail() + } + if info.Version != Version || info.NumPackages != numPackages { + t.Log("info did not match expected") + t.Fail() + } +} + +func checkAdd(t *testing.T, client *vanity.Client, pkg1, pkg2 vanity.Package) { + ctx := context.Background() + if err := client.Add(ctx, pkg1); err != nil { + t.Logf("pkg1 should be added: %v\n", err) + t.Fail() + } + if err := client.Add(ctx, pkg2); err != nil { + t.Logf("pkg2 should be added: %v\n", err) + t.Fail() + } + // Duplicate package + if err := client.Add(ctx, pkg1); err == nil { + t.Log("pkg1 should already exist") + t.Fail() + } +} + +func checkUpdate(t *testing.T, client *vanity.Client, pkg vanity.Package) { + ctx := context.Background() + // Update invalid package + if err := client.Update(ctx, vanity.Package{Name: "test4"}); err == nil { + t.Log("should not be able to update invalid package") + t.Fail() + } + + // Update valid package + if err := client.Update(ctx, pkg); err != nil { + t.Logf("should be able to update valid package: %v\n", err) + t.Fail() + } +} + +func checkRemove(t *testing.T, client *vanity.Client, pkg vanity.Package) { + ctx := context.Background() + if err := client.Remove(ctx, pkg); err != nil { + t.Logf("should be able to remove package: %v\n", err) + t.Fail() + } + + // Remove (idempotent) + if err := client.Remove(ctx, pkg); err != nil { + t.Logf("should be able to remove package idempotently: %v\n", err) + t.Fail() + } +} + +func checkResp(t *testing.T, path string, status int) { + resp, err := http.Get(fmt.Sprintf("%s/%s", server.URL, path)) + if err != nil { + t.Logf("could not GET %s: %v", path, err) + t.Fail() + return + } + + if resp.StatusCode != status { + t.Logf("incorrect response from %s, expected %d but got %d", path, status, resp.StatusCode) + t.Fail() + } +} diff --git a/router/templates.go b/router/templates.go index 5bd899d..8442df7 100644 --- a/router/templates.go +++ b/router/templates.go @@ -2,18 +2,21 @@ package router import ( "embed" - "go.jolheiser.com/overlay" - "go.jolheiser.com/vanity/api" "html/template" "os" "path/filepath" "runtime" + + "go.jolheiser.com/overlay" ) -//go:embed templates -var templates embed.FS +var ( + //go:embed templates + templateFS embed.FS + Version string +) -func Templates() (*template.Template, error) { +func tmpl(name string) (*template.Template, error) { bin, err := os.Executable() if err != nil { return nil, err @@ -23,22 +26,19 @@ func Templates() (*template.Template, error) { customPath = filepath.Join(bin, "custom") } - ofs, err := overlay.New(customPath, templates) + ofs, err := overlay.New(customPath, templateFS) if err != nil { return nil, err } - return template.New("vanity").Funcs(funcMap).ParseFS(ofs, "templates/*") + return template.New(name).Funcs(funcMap).ParseFS(ofs, "templates/base.tmpl", "templates/"+name) } var funcMap = template.FuncMap{ "AppVer": func() string { - return api.Version + return Version }, "GoVer": func() string { return runtime.Version() }, - "CanUpdate": func() bool { - return canUpdate - }, } diff --git a/router/templates/base.tmpl b/router/templates/base.tmpl new file mode 100644 index 0000000..b6a03a2 --- /dev/null +++ b/router/templates/base.tmpl @@ -0,0 +1,32 @@ +{{define "base"}} + + + + + {{if .Package}} + + + + + + + + + + + {{end}} + Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} + + +

Index

+
+ {{block "content" .}}{{end}} +
+ Vanity Version: + {{AppVer}} +

+ Go Version: + {{GoVer}} + + +{{end}} \ No newline at end of file diff --git a/router/templates/foot.tmpl b/router/templates/foot.tmpl deleted file mode 100644 index 4cf7348..0000000 --- a/router/templates/foot.tmpl +++ /dev/null @@ -1,25 +0,0 @@ -
- -

-Vanity Version: -{{AppVer}} -

-Go Version: -{{GoVer}} - - - - diff --git a/router/templates/head.tmpl b/router/templates/head.tmpl deleted file mode 100644 index 00435a2..0000000 --- a/router/templates/head.tmpl +++ /dev/null @@ -1,21 +0,0 @@ - - - - - {{if .Package}} - - - - - - - - - - - {{end}} - Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} - - -

Index

-
\ No newline at end of file diff --git a/router/templates/import.tmpl b/router/templates/import.tmpl index c883b67..5ed23c0 100644 --- a/router/templates/import.tmpl +++ b/router/templates/import.tmpl @@ -1,20 +1,6 @@ - - - - - - - - - - - - - {{.Module}} - - -go get {{.Module}} -
-git-import {{.Module}} - - \ No newline at end of file +{{template "base" .}} +{{define "content"}} + go get {{.Module}} +
+ git-import {{.Module}} +{{end}} \ No newline at end of file diff --git a/router/templates/index.tmpl b/router/templates/index.tmpl index 604c6e5..c072a03 100644 --- a/router/templates/index.tmpl +++ b/router/templates/index.tmpl @@ -1,29 +1,9 @@ -{{template "head.tmpl" .}} -

{{if eq .Format "list"}}Imports{{else}}Topics{{end}}:

- {{if eq .Format "list"}} - - {{else}} - {{range $topic, $packages := .Packages.Topics}} -
- {{$topic}} - -
+{{template "base" .}} +{{define "content"}} +

Imports:

+
    + {{range $path, $package := .Packages}} +
  • {{$package.Name}}
  • {{end}} - {{end}} -
    -
    - {{if eq .Format "list"}} - - {{else}} - - {{end}} -
    -{{template "foot.tmpl" .}} \ No newline at end of file +
+{{end}} \ No newline at end of file diff --git a/router/templates/vanity.tmpl b/router/templates/vanity.tmpl index e36b63c..30c4d3c 100644 --- a/router/templates/vanity.tmpl +++ b/router/templates/vanity.tmpl @@ -1,4 +1,5 @@ -{{template "head.tmpl" .}} +{{template "base" .}} +{{define "content"}}

Name: {{.Package.Name}} @@ -17,4 +18,4 @@ Documentation: https://pkg.go.dev/{{.Module}}

-{{template "foot.tmpl" .}} \ No newline at end of file +{{end}} \ No newline at end of file diff --git a/service/gitea.go b/service/gitea.go deleted file mode 100644 index b18a54e..0000000 --- a/service/gitea.go +++ /dev/null @@ -1,95 +0,0 @@ -package service - -import ( - "fmt" - - "go.jolheiser.com/vanity/api" - "go.jolheiser.com/vanity/flags" - - "code.gitea.io/sdk/gitea" - "go.jolheiser.com/beaver" -) - -var _ Service = &Gitea{} - -func NewGitea() *Gitea { - client, err := gitea.NewClient(flags.BaseURL.String(), gitea.SetToken(flags.Token)) - if err != nil { - beaver.Errorf("could not create Gitea client: %v", err) - } - return &Gitea{ - client: client, - } -} - -type Gitea struct { - client *gitea.Client -} - -func (g Gitea) Packages() (map[string]*api.Package, error) { - packages := make(map[string]*api.Package) - topicOpts := gitea.ListRepoTopicsOptions{ - ListOptions: gitea.ListOptions{ - Page: 1, - PageSize: 50, - }, - } - page := 0 - for { - opts := gitea.ListReposOptions{ - ListOptions: gitea.ListOptions{ - Page: page, - PageSize: 50, - }, - } - - repos, _, err := g.client.ListUserRepos(flags.Namespace, opts) - if err != nil { - return nil, err - } - - for _, repo := range repos { - pkg := &api.Package{ - Name: repo.Name, - Description: repo.Description, - Branch: repo.DefaultBranch, - WebURL: repo.HTMLURL, - CloneHTTP: repo.CloneURL, - CloneSSH: repo.SSHURL, - Private: repo.Private, - Fork: repo.Fork, - Mirror: repo.Mirror, - Archive: repo.Archived, - } - - // Get tags - topics, _, err := g.client.ListRepoTopics(flags.Namespace, repo.Name, topicOpts) - if err != nil { - beaver.Warnf("could not get topics for %s: %v", repo.Name, err) - } - pkg.Topics = topics - - packages[repo.Name] = pkg - } - - page++ - if len(repos) == 0 { - break - } - } - - return packages, nil -} - -func (g Gitea) GoDir(pkg *api.Package) string { - return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.WebURL, pkg.Branch) -} - -func (g Gitea) GoFile(pkg *api.Package) string { - return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) -} - -func (g Gitea) GoMod(pkg *api.Package) (string, error) { - content, _, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod") - return string(content), err -} diff --git a/service/github.go b/service/github.go deleted file mode 100644 index 3f11861..0000000 --- a/service/github.go +++ /dev/null @@ -1,90 +0,0 @@ -package service - -import ( - "context" - "fmt" - - "go.jolheiser.com/vanity/api" - "go.jolheiser.com/vanity/flags" - - "github.com/google/go-github/v32/github" - "golang.org/x/oauth2" -) - -var _ Service = &GitHub{} - -func NewGitHub() *GitHub { - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: flags.Token}, - ) - client := oauth2.NewClient(context.Background(), ts) - ghClient := github.NewClient(client) - ghClient.BaseURL = flags.BaseURL - return &GitHub{ - client: ghClient, - } -} - -type GitHub struct { - client *github.Client -} - -func (g GitHub) Packages() (map[string]*api.Package, error) { - packages := make(map[string]*api.Package) - page := 0 - for { - opts := github.RepositoryListOptions{ - ListOptions: github.ListOptions{ - Page: page, - PerPage: 50, - }, - } - - repos, _, err := g.client.Repositories.List(context.Background(), flags.Namespace, &opts) - if err != nil { - return nil, err - } - - for _, repo := range repos { - packages[repo.GetName()] = &api.Package{ - Name: repo.GetName(), - Description: repo.GetDescription(), - Branch: repo.GetDefaultBranch(), - WebURL: repo.GetHTMLURL(), - CloneHTTP: repo.GetCloneURL(), - CloneSSH: repo.GetSSHURL(), - Private: repo.GetPrivate(), - Fork: repo.GetFork(), - Mirror: false, - Archive: repo.GetArchived(), - Topics: repo.Topics, - } - } - - page++ - if len(repos) == 0 { - break - } - } - - return packages, nil -} - -func (g GitHub) GoDir(pkg *api.Package) string { - return fmt.Sprintf("%s/tree/%s{/dir}", pkg.WebURL, pkg.Branch) -} - -func (g GitHub) GoFile(pkg *api.Package) string { - return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) -} - -func (g GitHub) GoMod(pkg *api.Package) (string, error) { - content, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Namespace, pkg.Name, "go.mod", - &github.RepositoryContentGetOptions{ - Ref: pkg.Branch, - }) - if err != nil { - return "", err - } - return content.GetContent() -} diff --git a/service/gitlab.go b/service/gitlab.go deleted file mode 100644 index b101775..0000000 --- a/service/gitlab.go +++ /dev/null @@ -1,85 +0,0 @@ -package service - -import ( - "fmt" - "html" - - "go.jolheiser.com/vanity/api" - "go.jolheiser.com/vanity/flags" - - "github.com/xanzy/go-gitlab" - "go.jolheiser.com/beaver" -) - -var _ Service = &GitLab{} - -func NewGitLab() *GitLab { - client, err := gitlab.NewClient(flags.Token, gitlab.WithBaseURL(flags.BaseURL.String())) - if err != nil { - beaver.Errorf("could not create GitLab client: %v", err) - } - return &GitLab{ - client: client, - } -} - -type GitLab struct { - client *gitlab.Client -} - -func (g GitLab) Packages() (map[string]*api.Package, error) { - packages := make(map[string]*api.Package) - page := 0 - for { - opts := gitlab.ListProjectsOptions{ - ListOptions: gitlab.ListOptions{ - Page: page, - PerPage: 50, - }, - } - - repos, _, err := g.client.Projects.ListUserProjects(flags.Namespace, &opts) - if err != nil { - return nil, err - } - - for _, repo := range repos { - packages[repo.Name] = &api.Package{ - Name: repo.Name, - Description: repo.Description, - Branch: repo.DefaultBranch, - WebURL: repo.WebURL, - CloneHTTP: repo.HTTPURLToRepo, - CloneSSH: repo.SSHURLToRepo, - Private: repo.Visibility != gitlab.PublicVisibility, - Fork: repo.ForkedFromProject != nil, - Mirror: repo.Mirror, - Archive: repo.Archived, - Topics: repo.TagList, - } - } - - page++ - if len(repos) == 0 { - break - } - } - - return packages, nil -} - -func (g GitLab) GoDir(pkg *api.Package) string { - return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.WebURL, pkg.Branch) -} - -func (g GitLab) GoFile(pkg *api.Package) string { - return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch) -} - -func (g GitLab) GoMod(pkg *api.Package) (string, error) { - id := fmt.Sprintf("%s/%s", flags.Namespace, pkg.Name) - content, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{ - Ref: &pkg.Branch, - }) - return string(content), err -} diff --git a/service/off.go b/service/off.go deleted file mode 100644 index b029866..0000000 --- a/service/off.go +++ /dev/null @@ -1,23 +0,0 @@ -package service - -import "go.jolheiser.com/vanity/api" - -var _ Service = Off{} - -type Off struct{} - -func (o Off) Packages() (map[string]*api.Package, error) { - return make(map[string]*api.Package), nil -} - -func (o Off) GoDir(*api.Package) string { - return "" -} - -func (o Off) GoFile(*api.Package) string { - return "" -} - -func (o Off) GoMod(*api.Package) (string, error) { - return "", nil -} diff --git a/service/service.go b/service/service.go deleted file mode 100644 index 327401e..0000000 --- a/service/service.go +++ /dev/null @@ -1,75 +0,0 @@ -package service - -import ( - "fmt" - "strings" - - "go.jolheiser.com/vanity/api" - "go.jolheiser.com/vanity/flags" -) - -type Service interface { - Packages() (map[string]*api.Package, error) - GoDir(*api.Package) string - GoFile(*api.Package) string - GoMod(*api.Package) (string, error) -} - -func New() Service { - switch strings.ToLower(flags.Service) { - case "gitea": - return NewGitea() - case "github": - return NewGitHub() - case "gitlab": - return NewGitLab() - default: - return Off{} - } -} - -func Check(pkg *api.Package) error { - - // Private - if pkg.Private && !flags.Private { - return fmt.Errorf("%s is private and --private wasn't used", pkg.Name) - } - - // Forked - if pkg.Fork && !flags.Fork { - return fmt.Errorf("%s is a fork and --fork wasn't used", pkg.Name) - } - - // Mirrored - if pkg.Mirror && !flags.Mirror { - return fmt.Errorf("%s is a mirror and --mirror wasn't used", pkg.Name) - } - - // Archived - if pkg.Archive && !flags.Archive { - return fmt.Errorf("%s is archived and --archive wasn't used", pkg.Name) - } - - // Exclusions - for _, exclude := range flags.Exclude { - if exclude.MatchString(pkg.Name) { - return fmt.Errorf("%s was excluded by the rule %s", pkg.Name, exclude.String()) - } - } - - // Inclusions - if len(flags.Include) > 0 { - var included bool - for _, include := range flags.Include { - if include.MatchString(pkg.Name) { - included = true - break - } - } - if !included { - return fmt.Errorf("%s wasn't included by any existing inclusion rule", pkg.Name) - } - } - - return nil -} diff --git a/vanity.service b/vanity.service deleted file mode 100644 index a8efdbc..0000000 --- a/vanity.service +++ /dev/null @@ -1,21 +0,0 @@ -[Unit] -Description=Vanity Go Imports -After=syslog.target -After=network.target - -[Service] -RestartSec=2s -Type=simple -User=vanity -Group=vanity -ExecStart=/usr/local/bin/vanity -Restart=always - -# Required -Environment=VANITY_BASE_URL= -Environment=VANITY_NAMESPACE= -Environment=VANITY_TOKEN= -Environment=VANITY_DOMAIN= - -[Install] -WantedBy=multi-user.target \ No newline at end of file -- 2.41.0 From 0b3fd9c04c45856a2fa0886bd21b7d0aea7084b2 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sun, 9 May 2021 05:02:27 +0000 Subject: [PATCH 13/15] Add sakura for CSS (#12) Resolves #7 Reviewed-on: https://git.jojodev.com/jolheiser/vanity/pulls/12 Co-authored-by: jolheiser Co-committed-by: jolheiser --- .drone.yml | 43 ++++++ README.md | 3 + go.mod | 6 +- go.sum | 11 +- router/router.go | 7 +- router/templates.go | 29 ++++- router/templates/base.tmpl | 2 + router/templates/static/sakura.css | 202 +++++++++++++++++++++++++++++ 8 files changed, 283 insertions(+), 20 deletions(-) create mode 100644 .drone.yml create mode 100644 router/templates/static/sakura.css diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..6182b87 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,43 @@ +--- +kind: pipeline +name: compliance +trigger: + event: + - pull_request +steps: + - name: build + pull: always + image: golang:1.16 + commands: + - make test + - make build + - name: check + pull: always + image: golang:1.16 + commands: + - make vet + + +--- +kind: pipeline +name: release +trigger: + event: + - push + branch: + - main +steps: + - name: build + pull: always + image: golang:1.16 + commands: + - make build + - name: gitea-release + pull: always + image: jolheiser/drone-gitea-main:latest + settings: + token: + from_secret: gitea_token + base: https://git.jojodev.com + files: + - "vanity" \ No newline at end of file diff --git a/README.md b/README.md index edc23b8..d6d306e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Vanity +[![Go Reference](https://pkg.go.dev/badge/go.jolheiser.com/vanity.svg)](https://pkg.go.dev/go.jolheiser.com/vanity) +[![Build Status](https://ci.jojodev.com/api/badges/jolheiser/vanity/status.svg)](https://ci.jojodev.com/jolheiser/vanity) + A simple web service to serve [vanity Go imports](https://golang.org/cmd/go/#hdr-Remote_import_paths). Feel free to check it out using [my instance](https://go.jolheiser.com/). Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). diff --git a/go.mod b/go.mod index 2da1ea9..981270b 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,13 @@ replace go.jolheiser.com/vanity/go-vanity => ./go-vanity require ( github.com/AlecAivazis/survey/v2 v2.2.8 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect - github.com/go-chi/chi v4.1.2+incompatible + github.com/go-chi/chi/v5 v5.0.3 github.com/urfave/cli/v2 v2.2.0 go.etcd.io/bbolt v1.3.5 go.jolheiser.com/beaver v1.0.2 - go.jolheiser.com/overlay v0.0.2 + go.jolheiser.com/overlay v0.0.3 go.jolheiser.com/vanity/go-vanity v0.0.0-00010101000000-000000000000 - golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index b1d1258..26fca36 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec= -github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= +github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -36,22 +36,19 @@ go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= -go.jolheiser.com/overlay v0.0.2 h1:cwEHLbWqdH7lEOG87WUwgUGVqfOWBsWe03FiHHmuTWE= -go.jolheiser.com/overlay v0.0.2/go.mod h1:xNbssakJ3HjK4RnjuP38q9yQNS4wxXKsyprYIWWr2bg= +go.jolheiser.com/overlay v0.0.3 h1:5WoXtnRi1w5KkU9xq+si/wV0GINOMJphfSOvgG2l53I= +go.jolheiser.com/overlay v0.0.3/go.mod h1:xNbssakJ3HjK4RnjuP38q9yQNS4wxXKsyprYIWWr2bg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/router/router.go b/router/router.go index 8720345..0d38e24 100644 --- a/router/router.go +++ b/router/router.go @@ -12,17 +12,18 @@ import ( "go.jolheiser.com/vanity/database" "go.jolheiser.com/vanity/go-vanity" - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" "go.jolheiser.com/beaver" ) func New(token string, db *database.Database) *chi.Mux { r := chi.NewRouter() - r.Use(middleware.RedirectSlashes) r.Use(middleware.Recoverer) r.Use(middleware.Timeout(60 * time.Second)) + r.Mount("/_/", http.StripPrefix("/_/", static())) + r.Get("/", indexGET(db)) r.Options("/", infoPackages(db)) r.Post("/", addUpdatePackage(db, token)) diff --git a/router/templates.go b/router/templates.go index 8442df7..0191c20 100644 --- a/router/templates.go +++ b/router/templates.go @@ -3,37 +3,49 @@ package router import ( "embed" "html/template" + "io/fs" + "net/http" "os" "path/filepath" "runtime" + "strings" "go.jolheiser.com/overlay" + + "go.jolheiser.com/vanity/cmd/flags" ) var ( //go:embed templates templateFS embed.FS + ofs = overlay.MustNew(customRoot(), templateFS) Version string ) -func tmpl(name string) (*template.Template, error) { +func customRoot() string { bin, err := os.Executable() if err != nil { - return nil, err + bin = "" } customPath := os.Getenv("VANITY_CUSTOM") if customPath == "" { customPath = filepath.Join(bin, "custom") } + return customPath +} - ofs, err := overlay.New(customPath, templateFS) - if err != nil { - return nil, err - } - +func tmpl(name string) (*template.Template, error) { return template.New(name).Funcs(funcMap).ParseFS(ofs, "templates/base.tmpl", "templates/"+name) } +func static() http.Handler { + sub, err := fs.Sub(ofs, "templates/static") + if err != nil { + return nil + } + return http.FileServer(http.FS(sub)) +} + var funcMap = template.FuncMap{ "AppVer": func() string { return Version @@ -41,4 +53,7 @@ var funcMap = template.FuncMap{ "GoVer": func() string { return runtime.Version() }, + "Domain": func() string { + return strings.TrimSuffix(flags.Domain, "/") + }, } diff --git a/router/templates/base.tmpl b/router/templates/base.tmpl index b6a03a2..85a37fd 100644 --- a/router/templates/base.tmpl +++ b/router/templates/base.tmpl @@ -16,6 +16,8 @@ {{end}} Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} + +

Index

diff --git a/router/templates/static/sakura.css b/router/templates/static/sakura.css new file mode 100644 index 0000000..d73d1d8 --- /dev/null +++ b/router/templates/static/sakura.css @@ -0,0 +1,202 @@ +/* Sakura.css v1.3.1 + * ================ + * Minimal css theme. + * Project: https://github.com/oxalorg/sakura/ + */ +/* Body */ +:root { + --color-blossom: #1d7484; + --color-fade: #982c61; + --color-bg: #f9f9f9; + --color-bg-alt: #f1f1f1; + --color-text: #4a4a4a; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-blossom: #ffffff; + --color-fade: #c9c9c9; + --color-bg: #222222; + --color-bg-alt: #4a4a4a; + --color-text: #c9c9c9; + } +} + +html { + font-size: 62.5%; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; } + +body { + font-size: 1.8rem; + line-height: 1.618; + max-width: 38em; + margin: auto; + color: var(--color-text); + background-color: var(--color-bg); + padding: 13px; } + +@media (max-width: 684px) { + body { + font-size: 1.53rem; } } + +@media (max-width: 382px) { + body { + font-size: 1.35rem; } } + +h1, h2, h3, h4, h5, h6 { + line-height: 1.1; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; + font-weight: 700; + margin-top: 3rem; + margin-bottom: 1.5rem; + overflow-wrap: break-word; + word-wrap: break-word; + -ms-word-break: break-all; + word-break: break-word; } + +h1 { + font-size: 2.35em; } + +h2 { + font-size: 2.00em; } + +h3 { + font-size: 1.75em; } + +h4 { + font-size: 1.5em; } + +h5 { + font-size: 1.25em; } + +h6 { + font-size: 1em; } + +p { + margin-top: 0px; + margin-bottom: 2.5rem; } + +small, sub, sup { + font-size: 75%; } + +hr { + border-color: var(--color-blossom); } + +a { + text-decoration: none; + color: var(--color-blossom); } +a:hover { + color: var(--color-fade); + border-bottom: 2px solid var(--color-text); } +a:visited { + color: var(--color-blossom); } + +ul { + padding-left: 1.4em; + margin-top: 0px; + margin-bottom: 2.5rem; } + +li { + margin-bottom: 0.4em; } + +blockquote { + margin-left: 0px; + margin-right: 0px; + padding-left: 1em; + padding-top: 0.8em; + padding-bottom: 0.8em; + padding-right: 0.8em; + border-left: 5px solid var(--color-blossom); + margin-bottom: 2.5rem; + background-color: var(--color-bg-alt); } + +blockquote p { + margin-bottom: 0; } + +img, video { + height: auto; + max-width: 100%; + margin-top: 0px; + margin-bottom: 2.5rem; } + +/* Pre and Code */ +pre { + background-color: var(--color-bg-alt); + display: block; + padding: 1em; + overflow-x: auto; + margin-top: 0px; + margin-bottom: 2.5rem; } + +code { + font-size: 0.9em; + padding: 0 0.5em; + background-color: var(--color-bg-alt); + white-space: pre-wrap; } + +pre > code { + padding: 0; + background-color: transparent; + white-space: pre; } + +/* Tables */ +table { + text-align: justify; + width: 100%; + border-collapse: collapse; } + +td, th { + padding: 0.5em; + border-bottom: 1px solid var(--color-bg-alt); } + +/* Buttons, forms and input */ +input, textarea { + border: 1px solid var(--color-text); } +input:focus, textarea:focus { + border: 1px solid var(--color-blossom); } + +textarea { + width: 100%; } + +.button, button, input[type="submit"], input[type="reset"], input[type="button"] { + display: inline-block; + padding: 5px 10px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background-color: var(--color-blossom); + color: var(--color-bg); + border-radius: 1px; + border: 1px solid var(--color-blossom); + cursor: pointer; + box-sizing: border-box; } +.button[disabled], button[disabled], input[type="submit"][disabled], input[type="reset"][disabled], input[type="button"][disabled] { + cursor: default; + opacity: .5; } +.button:focus:enabled, .button:hover:enabled, button:focus:enabled, button:hover:enabled, input[type="submit"]:focus:enabled, input[type="submit"]:hover:enabled, input[type="reset"]:focus:enabled, input[type="reset"]:hover:enabled, input[type="button"]:focus:enabled, input[type="button"]:hover:enabled { + background-color: var(--color-fade); + border-color: var(--color-fade); + color: var(--color-bg); + outline: 0; } + +textarea, select, input { + color: var(--color-text); + padding: 6px 10px; + /* The 6px vertically centers text on FF, ignored by Webkit */ + margin-bottom: 10px; + background-color: var(--color-bg-alt); + border: 1px solid var(--color-bg-alt ); + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; } +textarea:focus, select:focus, input:focus { + border: 1px solid var(--color-blossom); + outline: 0; } + +input[type="checkbox"]:focus { + outline: 1px dotted var(--color-blossom); } + +label, legend, fieldset { + display: block; + margin-bottom: .5rem; + font-weight: 600; } \ No newline at end of file -- 2.41.0 From 2d644e2711970ec63edfb1a7a84977d80cbb0baf Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sun, 9 May 2021 16:22:19 +0000 Subject: [PATCH 14/15] Fix sakura (#13) Reviewed-on: https://git.jojodev.com/jolheiser/vanity/pulls/13 Co-authored-by: jolheiser Co-committed-by: jolheiser --- router/templates/base.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/templates/base.tmpl b/router/templates/base.tmpl index 85a37fd..a6ccb48 100644 --- a/router/templates/base.tmpl +++ b/router/templates/base.tmpl @@ -17,7 +17,7 @@ {{end}} Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}} - +

Index

-- 2.41.0 From 6ef23a7c5bcc76663b5b008a3ddc9e580711ebe1 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sun, 20 Mar 2022 04:15:24 +0000 Subject: [PATCH 15/15] Refactor for ff, zerolog, and re-integrate SDK (#14) Reviewed-on: https://git.jojodev.com/jolheiser/vanity/pulls/14 --- .drone.yml | 43 ----- .gitignore | 6 +- .woodpecker.yml | 37 ++++ Makefile | 41 ---- cmd/add.go | 69 ------- cmd/cmd.go | 181 ------------------ cmd/flags/flags.go | 14 -- cmd/remove.go | 67 ------- cmd/server.go | 55 ------ cmd/update.go | 76 -------- cmd/vanity-cli/add.go | 36 ++++ cmd/vanity-cli/cmd.go | 127 ++++++++++++ cmd/vanity-cli/main.go | 18 ++ cmd/vanity-cli/remove.go | 48 +++++ cmd/vanity-cli/update.go | 49 +++++ cmd/vanity-server/main.go | 18 ++ cmd/vanity-server/server.go | 75 ++++++++ contrib/contrib.go | 28 --- go-vanity/go.mod | 3 - go.mod | 11 +- go.sum | 57 ++++-- main.go | 24 --- {go-vanity => sdk}/client.go | 2 +- {go-vanity => sdk}/package.go | 2 +- {go-vanity => sdk}/source.go | 2 +- {go-vanity => sdk}/vanity_test.go | 2 +- {database => server/database}/database.go | 12 +- {database => server/database}/errors.go | 0 {router => server/router}/router.go | 55 +++--- {router => server/router}/router_test.go | 28 +-- {router => server/router}/templates.go | 28 +-- {router => server/router}/templates/base.tmpl | 0 .../router}/templates/import.tmpl | 0 .../router}/templates/index.tmpl | 0 .../router}/templates/static/sakura.css | 0 .../router}/templates/vanity.tmpl | 0 tools.go | 8 + 37 files changed, 524 insertions(+), 698 deletions(-) delete mode 100644 .drone.yml create mode 100644 .woodpecker.yml delete mode 100644 Makefile delete mode 100644 cmd/add.go delete mode 100644 cmd/cmd.go delete mode 100644 cmd/flags/flags.go delete mode 100644 cmd/remove.go delete mode 100644 cmd/server.go delete mode 100644 cmd/update.go create mode 100644 cmd/vanity-cli/add.go create mode 100644 cmd/vanity-cli/cmd.go create mode 100644 cmd/vanity-cli/main.go create mode 100644 cmd/vanity-cli/remove.go create mode 100644 cmd/vanity-cli/update.go create mode 100644 cmd/vanity-server/main.go create mode 100644 cmd/vanity-server/server.go delete mode 100644 contrib/contrib.go delete mode 100644 go-vanity/go.mod delete mode 100644 main.go rename {go-vanity => sdk}/client.go (98%) rename {go-vanity => sdk}/package.go (99%) rename {go-vanity => sdk}/source.go (98%) rename {go-vanity => sdk}/vanity_test.go (99%) rename {database => server/database}/database.go (85%) rename {database => server/database}/errors.go (100%) rename {router => server/router}/router.go (74%) rename {router => server/router}/router_test.go (81%) rename {router => server/router}/templates.go (60%) rename {router => server/router}/templates/base.tmpl (100%) rename {router => server/router}/templates/import.tmpl (100%) rename {router => server/router}/templates/index.tmpl (100%) rename {router => server/router}/templates/static/sakura.css (100%) rename {router => server/router}/templates/vanity.tmpl (100%) create mode 100644 tools.go diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 6182b87..0000000 --- a/.drone.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -kind: pipeline -name: compliance -trigger: - event: - - pull_request -steps: - - name: build - pull: always - image: golang:1.16 - commands: - - make test - - make build - - name: check - pull: always - image: golang:1.16 - commands: - - make vet - - ---- -kind: pipeline -name: release -trigger: - event: - - push - branch: - - main -steps: - - name: build - pull: always - image: golang:1.16 - commands: - - make build - - name: gitea-release - pull: always - image: jolheiser/drone-gitea-main:latest - settings: - token: - from_secret: gitea_token - base: https://git.jojodev.com - files: - - "vanity" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9185e86..d9e9c82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ # GoLand -.idea +.idea/ # Vanity -/vanity -/vanity.exe -/vanity.db \ No newline at end of file +/vanity* \ No newline at end of file diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..aa443a6 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,37 @@ +clone: + git: + image: woodpeckerci/plugin-git:next + +pipeline: + compliance: + image: golang:1.17 + commands: + - go test -race ./... + - go run github.com/rs/zerolog/cmd/lint ./cmd/vanity-cli + - go run github.com/rs/zerolog/cmd/lint ./cmd/vanity-server + - go vet ./... + when: + event: pull_request + + build: + image: golang:1.17 + commands: + - GOOS="linux" go build ./cmd/vanity-cli + - GOOS="windows" go build ./cmd/vanity-cli + - GOOS="linux" go build ./cmd/vanity-server + - GOOS="windows" go build ./cmd/vanity-server + + release-main: + image: jolheiser/drone-gitea-main:latest + settings: + base: https://git.jojodev.com + token: + from_secret: gitea_token + files: + - "vanity-cli" + - "vanity-cli.exe" + - "vanity-server" + - "vanity-server.exe" + when: + event: push + branch: main diff --git a/Makefile b/Makefile deleted file mode 100644 index a3af058..0000000 --- a/Makefile +++ /dev/null @@ -1,41 +0,0 @@ -GO ?= go - -VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') - -.PHONY: build -build: - $(GO) build -ldflags '-s -w -X "go.jolheiser.com/vanity/router.Version=$(VERSION)"' - -.PHONY: fmt -fmt: fmt-cli fmt-lib - -.PHONY: test -test: test-cli test-lib - -.PHONY: fmt-cli -fmt-cli: - $(GO) fmt ./... - -.PHONY: test-cli -test-cli: - $(GO) test -race ./... - -.PHONY: fmt-lib -fmt-lib: - @cd go-vanity && $(GO) fmt ./... - -.PHONY: test-lib -test-lib: - @cd go-vanity && $(GO) test -race ./... - -.PHONY: vet -vet: - $(GO) vet ./... - -.PHONY: docker-build -docker-build: - docker build -f docker/Dockerfile -t jolheiser/vanity . - -.PHONY: docker-push -docker-push: - docker push jolheiser/vanity \ No newline at end of file diff --git a/cmd/add.go b/cmd/add.go deleted file mode 100644 index ad7c815..0000000 --- a/cmd/add.go +++ /dev/null @@ -1,69 +0,0 @@ -package cmd - -import ( - "context" - "net/url" - - "go.jolheiser.com/vanity/cmd/flags" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/go-vanity" - - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" -) - -var Add = cli.Command{ - Name: "add", - Aliases: []string{"a"}, - Usage: "Add a package", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "force", - Aliases: []string{"f"}, - Usage: "Overwrite existing package without prompt", - Destination: &flags.Force, - }, - &cli.BoolFlag{ - Name: "local", - Aliases: []string{"l"}, - Usage: "local mode", - Destination: &flags.Local, - }, - }, - Before: localOrToken, - Action: doAdd, -} - -func doAdd(_ *cli.Context) error { - pkg, err := pkgPrompt(vanity.Package{}) - if err != nil { - return err - } - - if flags.Local { - db, err := database.Load(flags.Database) - if err != nil { - return err - } - if err := db.PutPackage(pkg); err != nil { - return err - } - } else { - client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) - if err := client.Add(context.Background(), pkg); err != nil { - return err - } - } - - beaver.Infof("Added %s", yellow.Format(pkg.Name)) - return nil -} - -func validURL(ans interface{}) error { - if err := survey.Required(ans); err != nil { - return err - } - _, err := url.Parse(ans.(string)) - return err -} diff --git a/cmd/cmd.go b/cmd/cmd.go deleted file mode 100644 index b60c8a4..0000000 --- a/cmd/cmd.go +++ /dev/null @@ -1,181 +0,0 @@ -package cmd - -import ( - "context" - "errors" - "fmt" - "net/url" - "os" - "path/filepath" - "strings" - - "go.jolheiser.com/vanity/cmd/flags" - "go.jolheiser.com/vanity/contrib" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/go-vanity" - "go.jolheiser.com/vanity/router" - - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver/color" -) - -var yellow = color.FgYellow - -func New() *cli.App { - app := cli.NewApp() - app.Name = "vanity" - app.Usage = "Vanity Import URLs" - app.Version = router.Version - app.Commands = []*cli.Command{ - &Add, - &Remove, - &Server, - &Update, - } - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "server", - Aliases: []string{"s"}, - Usage: "vanity server to use", - Value: vanity.DefaultServer, - EnvVars: []string{"VANITY_SERVER"}, - Destination: &flags.Server, - }, - &cli.StringFlag{ - Name: "token", - Aliases: []string{"t"}, - Usage: "vanity auth token to use", - DefaultText: "${VANITY_TOKEN}", - EnvVars: []string{"VANITY_TOKEN"}, - Destination: &flags.Token, - }, - &cli.StringFlag{ - Name: "database", - Aliases: []string{"d"}, - Usage: "path to vanity database for server", - Value: dbPath(), - DefaultText: "`${HOME}/vanity.db` or `${BINPATH}/vanity.db`", - EnvVars: []string{"VANITY_DATABASE"}, - Destination: &flags.Database, - }, - &cli.BoolFlag{ - Name: "systemd-service", - Usage: "Output example systemd service", - Destination: &flags.SystemdService, - Hidden: true, - }, - } - app.Action = action - return app -} - -func action(ctx *cli.Context) error { - if flags.SystemdService { - fmt.Println(contrib.SystemdService) - return nil - } - return cli.ShowAppHelp(ctx) -} - -func dbPath() string { - fn := "vanity.db" - home, err := os.UserHomeDir() - if err != nil { - bin, err := os.Executable() - if err != nil { - return fn - } - return filepath.Join(filepath.Dir(bin), fn) - } - return filepath.Join(home, fn) -} - -func localOrToken(_ *cli.Context) error { - if flags.Local && flags.Token == "" { - return errors.New("server interaction requires --token") - } - return nil -} - -func listPackages() ([]vanity.Package, error) { - var pkgs []vanity.Package - if flags.Local { - db, err := database.Load(flags.Database) - if err != nil { - return pkgs, err - } - pkgs, err = db.Packages() - if err != nil { - return pkgs, err - } - } else { - client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) - info, err := client.Info(context.Background()) - if err != nil { - return pkgs, err - } - pkgs = info.Packages - } - return pkgs, nil -} - -func pkgPrompt(def vanity.Package) (vanity.Package, error) { - if def.Branch == "" { - def.Branch = "main" - } - var pkg vanity.Package - questions := []*survey.Question{ - { - Name: "name", - Prompt: &survey.Input{Message: "Name", Default: def.Name}, - Validate: survey.Required, - }, - { - Name: "description", - Prompt: &survey.Multiline{Message: "Description", Default: def.Description}, - Validate: survey.Required, - }, - { - Name: "branch", - Prompt: &survey.Input{Message: "Branch", Default: def.Branch}, - Validate: survey.Required, - }, - { - Name: "weburl", - Prompt: &survey.Input{Message: "Web URL", Default: def.WebURL}, - Validate: validURL, - }, - } - if err := survey.Ask(questions, &pkg); err != nil { - return pkg, err - } - - defHTTP, defSSH := def.CloneHTTP, def.CloneSSH - if def.WebURL != pkg.WebURL { - u, err := url.Parse(pkg.WebURL) - if err != nil { - return pkg, err - } - defHTTP = pkg.WebURL + ".git" - defSSH = fmt.Sprintf("git@%s:%s.git", u.Host, strings.TrimPrefix(u.Path, "/")) - } - - questions = []*survey.Question{ - { - Name: "clonehttp", - Prompt: &survey.Input{Message: "HTTP(S) CLone URL", Default: defHTTP}, - Validate: validURL, - }, - { - Name: "clonessh", - Prompt: &survey.Input{Message: "SSH CLone URL", Default: defSSH}, - Validate: survey.Required, - }, - } - if err := survey.Ask(questions, &pkg); err != nil { - return pkg, err - } - - return pkg, nil -} diff --git a/cmd/flags/flags.go b/cmd/flags/flags.go deleted file mode 100644 index b9b584a..0000000 --- a/cmd/flags/flags.go +++ /dev/null @@ -1,14 +0,0 @@ -package flags - -var ( - Server string - Domain string - Token string - Database string - - Local bool - Force bool - Port int - - SystemdService bool -) diff --git a/cmd/remove.go b/cmd/remove.go deleted file mode 100644 index 4de7bf6..0000000 --- a/cmd/remove.go +++ /dev/null @@ -1,67 +0,0 @@ -package cmd - -import ( - "context" - - "go.jolheiser.com/vanity/cmd/flags" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/go-vanity" - - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" -) - -var Remove = cli.Command{ - Name: "remove", - Aliases: []string{"rm"}, - Usage: "Remove package(s)", - Before: localOrToken, - Action: doRemove, -} - -func doRemove(_ *cli.Context) error { - pkgs, err := listPackages() - if err != nil { - return err - } - - pkgSlice := make([]string, len(pkgs)) - pkgMap := make(map[string]vanity.Package) - for idx, pkg := range pkgs { - pkgSlice[idx] = pkg.Name - pkgMap[pkg.Name] = pkg - } - - pkgQuestion := &survey.Select{ - Message: "Select package to remove", - Options: pkgSlice, - } - - var pkgName string - if err := survey.AskOne(pkgQuestion, &pkgName); err != nil { - return err - } - - pkg := vanity.Package{ - Name: pkgName, - } - - if flags.Local { - db, err := database.Load(flags.Database) - if err != nil { - return err - } - if err := db.RemovePackage(pkg.Name); err != nil { - return err - } - } else { - client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) - if err := client.Remove(context.Background(), pkg); err != nil { - return err - } - } - - beaver.Infof("Removed %s", yellow.Format(pkgName)) - return nil -} diff --git a/cmd/server.go b/cmd/server.go deleted file mode 100644 index 9a28c1a..0000000 --- a/cmd/server.go +++ /dev/null @@ -1,55 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - "net/http" - - "go.jolheiser.com/vanity/cmd/flags" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/router" - - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" -) - -var Server = cli.Command{ - Name: "server", - Aliases: []string{"web"}, - Usage: "Start the vanity server", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Aliases: []string{"p"}, - Usage: "Port to run the vanity server on", - Value: 3333, - EnvVars: []string{"VANITY_PORT"}, - Destination: &flags.Port, - }, - &cli.StringFlag{ - Name: "domain", - Aliases: []string{"d"}, - Usage: "The Go module domain (e.g. go.jolheiser.com)", - EnvVars: []string{"VANITY_DOMAIN"}, - Destination: &flags.Domain, - }, - }, - Action: doServer, -} - -func doServer(_ *cli.Context) error { - if flags.Token == "" || flags.Domain == "" { - return errors.New("vanity server requires --token and --domain") - } - - db, err := database.Load(flags.Database) - if err != nil { - beaver.Fatalf("could not load database at %s: %v", flags.Database, err) - } - - beaver.Infof("Running vanity server at http://localhost:%d", flags.Port) - if err := http.ListenAndServe(fmt.Sprintf(":%d", flags.Port), router.New(flags.Token, db)); err != nil { - return err - } - return nil -} diff --git a/cmd/update.go b/cmd/update.go deleted file mode 100644 index c9b3f0f..0000000 --- a/cmd/update.go +++ /dev/null @@ -1,76 +0,0 @@ -package cmd - -import ( - "context" - - "go.jolheiser.com/vanity/cmd/flags" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/go-vanity" - - "github.com/AlecAivazis/survey/v2" - "github.com/urfave/cli/v2" - "go.jolheiser.com/beaver" -) - -var Update = cli.Command{ - Name: "update", - Aliases: []string{"u"}, - Usage: "Update a package", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "local", - Aliases: []string{"l"}, - Usage: "local mode", - Destination: &flags.Local, - }, - }, - Before: localOrToken, - Action: doUpdate, -} - -func doUpdate(_ *cli.Context) error { - pkgs, err := listPackages() - if err != nil { - return err - } - - pkgSlice := make([]string, len(pkgs)) - pkgMap := make(map[string]vanity.Package) - for idx, pkg := range pkgs { - pkgSlice[idx] = pkg.Name - pkgMap[pkg.Name] = pkg - } - - pkgQuestion := &survey.Select{ - Message: "Select package to update", - Options: pkgSlice, - } - - var pkgName string - if err := survey.AskOne(pkgQuestion, &pkgName); err != nil { - return err - } - - pkg, err := pkgPrompt(pkgMap[pkgName]) - if err != nil { - return err - } - - if flags.Local { - db, err := database.Load(flags.Database) - if err != nil { - return err - } - if err := db.PutPackage(pkg); err != nil { - return err - } - } else { - client := vanity.New(flags.Token, vanity.WithServer(flags.Server)) - if err := client.Update(context.Background(), pkg); err != nil { - return err - } - } - - beaver.Infof("Updated %s", yellow.Format(pkgName)) - return nil -} diff --git a/cmd/vanity-cli/add.go b/cmd/vanity-cli/add.go new file mode 100644 index 0000000..e02750c --- /dev/null +++ b/cmd/vanity-cli/add.go @@ -0,0 +1,36 @@ +package main + +import ( + "context" + "net/url" + + "go.jolheiser.com/vanity/sdk" + + "github.com/AlecAivazis/survey/v2" + "github.com/rs/zerolog/log" +) + +func add(token, server *string) func(context.Context, []string) error { + return func(ctx context.Context, args []string) error { + pkg, err := pkgPrompt(sdk.Package{}) + if err != nil { + return err + } + + client := sdk.New(*token, sdk.WithServer(*server)) + if err := client.Add(context.Background(), pkg); err != nil { + return err + } + + log.Info().Msgf("Added %q", pkg.Name) + return nil + } +} + +func validURL(ans interface{}) error { + if err := survey.Required(ans); err != nil { + return err + } + _, err := url.Parse(ans.(string)) + return err +} diff --git a/cmd/vanity-cli/cmd.go b/cmd/vanity-cli/cmd.go new file mode 100644 index 0000000..f1cf6b7 --- /dev/null +++ b/cmd/vanity-cli/cmd.go @@ -0,0 +1,127 @@ +package main + +import ( + "context" + "flag" + "fmt" + "net/url" + "strings" + + "go.jolheiser.com/vanity/sdk" + + "github.com/AlecAivazis/survey/v2" + "github.com/peterbourgon/ff/v3" + "github.com/peterbourgon/ff/v3/ffcli" + "github.com/peterbourgon/ff/v3/fftoml" +) + +func New() *ffcli.Command { + fs := flag.NewFlagSet("vanity", flag.ExitOnError) + serverFlag := fs.String("server", sdk.DefaultServer, "vanity server to use") + tokenFlag := fs.String("token", "", "vanity auth token to use") + + cmd := &ffcli.Command{ + Name: "vanity", + ShortUsage: "vanity [add|update|remove]", + ShortHelp: "Vanity CLI", + LongHelp: "Vanity CLI to work with a remote Vanity server", + FlagSet: fs, + Options: []ff.Option{ + ff.WithEnvVarPrefix("VANITY"), + ff.WithAllowMissingConfigFile(true), + ff.WithConfigFileFlag("config"), + ff.WithConfigFileParser(fftoml.New().Parse), + }, + Subcommands: []*ffcli.Command{ + { + Name: "add", + ShortHelp: "add package", + LongHelp: "add a package to the vanity server", + Exec: add(tokenFlag, serverFlag), + }, + { + Name: "remove", + ShortHelp: "remove package", + LongHelp: "remove a package from the vanity server", + Exec: remove(tokenFlag, serverFlag), + }, + { + Name: "update", + ShortHelp: "update package", + LongHelp: "update a package on the vanity server", + Exec: update(tokenFlag, serverFlag), + }, + }, + } + + return cmd +} + +func listPackages(token, server string) ([]sdk.Package, error) { + client := sdk.New(token, sdk.WithServer(server)) + info, err := client.Info(context.Background()) + if err != nil { + return nil, err + } + return info.Packages, nil +} + +func pkgPrompt(def sdk.Package) (sdk.Package, error) { + if def.Branch == "" { + def.Branch = "main" + } + var pkg sdk.Package + questions := []*survey.Question{ + { + Name: "name", + Prompt: &survey.Input{Message: "Name", Default: def.Name}, + Validate: survey.Required, + }, + { + Name: "description", + Prompt: &survey.Multiline{Message: "Description", Default: def.Description}, + Validate: survey.Required, + }, + { + Name: "branch", + Prompt: &survey.Input{Message: "Branch", Default: def.Branch}, + Validate: survey.Required, + }, + { + Name: "weburl", + Prompt: &survey.Input{Message: "Web URL", Default: def.WebURL}, + Validate: validURL, + }, + } + if err := survey.Ask(questions, &pkg); err != nil { + return pkg, err + } + + defHTTP, defSSH := def.CloneHTTP, def.CloneSSH + if def.WebURL != pkg.WebURL { + u, err := url.Parse(pkg.WebURL) + if err != nil { + return pkg, err + } + defHTTP = pkg.WebURL + ".git" + defSSH = fmt.Sprintf("git@%s:%s.git", u.Host, strings.TrimPrefix(u.Path, "/")) + } + + questions = []*survey.Question{ + { + Name: "clonehttp", + Prompt: &survey.Input{Message: "HTTP(S) CLone URL", Default: defHTTP}, + Validate: validURL, + }, + { + Name: "clonessh", + Prompt: &survey.Input{Message: "SSH CLone URL", Default: defSSH}, + Validate: survey.Required, + }, + } + if err := survey.Ask(questions, &pkg); err != nil { + return pkg, err + } + + return pkg, nil +} diff --git a/cmd/vanity-cli/main.go b/cmd/vanity-cli/main.go new file mode 100644 index 0000000..9fd71fe --- /dev/null +++ b/cmd/vanity-cli/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "context" + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + + cmd := New() + if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { + log.Fatal().Err(err).Msg("") + } +} diff --git a/cmd/vanity-cli/remove.go b/cmd/vanity-cli/remove.go new file mode 100644 index 0000000..802dca7 --- /dev/null +++ b/cmd/vanity-cli/remove.go @@ -0,0 +1,48 @@ +package main + +import ( + "context" + + "go.jolheiser.com/vanity/sdk" + + "github.com/AlecAivazis/survey/v2" + "github.com/rs/zerolog/log" +) + +func remove(token, server *string) func(context.Context, []string) error { + return func(ctx context.Context, args []string) error { + pkgs, err := listPackages(*token, *server) + if err != nil { + return err + } + + pkgSlice := make([]string, len(pkgs)) + pkgMap := make(map[string]sdk.Package) + for idx, pkg := range pkgs { + pkgSlice[idx] = pkg.Name + pkgMap[pkg.Name] = pkg + } + + pkgQuestion := &survey.Select{ + Message: "Select package to remove", + Options: pkgSlice, + } + + var pkgName string + if err := survey.AskOne(pkgQuestion, &pkgName); err != nil { + return err + } + + pkg := sdk.Package{ + Name: pkgName, + } + + client := sdk.New(*token, sdk.WithServer(*server)) + if err := client.Remove(context.Background(), pkg); err != nil { + return err + } + + log.Info().Msgf("Removed %q", pkgName) + return nil + } +} diff --git a/cmd/vanity-cli/update.go b/cmd/vanity-cli/update.go new file mode 100644 index 0000000..04a2d9a --- /dev/null +++ b/cmd/vanity-cli/update.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + + "go.jolheiser.com/vanity/sdk" + + "github.com/AlecAivazis/survey/v2" + "github.com/rs/zerolog/log" +) + +func update(token, server *string) func(context.Context, []string) error { + return func(ctx context.Context, args []string) error { + pkgs, err := listPackages(*token, *server) + if err != nil { + return err + } + + pkgSlice := make([]string, len(pkgs)) + pkgMap := make(map[string]sdk.Package) + for idx, pkg := range pkgs { + pkgSlice[idx] = pkg.Name + pkgMap[pkg.Name] = pkg + } + + pkgQuestion := &survey.Select{ + Message: "Select package to update", + Options: pkgSlice, + } + + var pkgName string + if err := survey.AskOne(pkgQuestion, &pkgName); err != nil { + return err + } + + pkg, err := pkgPrompt(pkgMap[pkgName]) + if err != nil { + return err + } + + client := sdk.New(*token, sdk.WithServer(*server)) + if err := client.Update(context.Background(), pkg); err != nil { + return err + } + + log.Info().Msgf("Updated %q", pkgName) + return nil + } +} diff --git a/cmd/vanity-server/main.go b/cmd/vanity-server/main.go new file mode 100644 index 0000000..9fd71fe --- /dev/null +++ b/cmd/vanity-server/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "context" + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + + cmd := New() + if err := cmd.ParseAndRun(context.Background(), os.Args[1:]); err != nil { + log.Fatal().Err(err).Msg("") + } +} diff --git a/cmd/vanity-server/server.go b/cmd/vanity-server/server.go new file mode 100644 index 0000000..5ba58f7 --- /dev/null +++ b/cmd/vanity-server/server.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "net/http" + "os" + "path/filepath" + + "go.jolheiser.com/vanity/server/database" + "go.jolheiser.com/vanity/server/router" + + "github.com/peterbourgon/ff/v3" + "github.com/peterbourgon/ff/v3/ffcli" + "github.com/peterbourgon/ff/v3/fftoml" + "github.com/rs/zerolog/log" +) + +func New() *ffcli.Command { + fs := flag.NewFlagSet("vanity", flag.ExitOnError) + portFlag := fs.Int("port", 3333, "Port to run the vanity server on") + domainFlag := fs.String("domain", "", "The Go module domain (e.g. go.jolheiser.com)") + tokenFlag := fs.String("token", "", "vanity auth token to use") + dbFlag := fs.String("database", dbPath(), "The path to the database") + + cmd := &ffcli.Command{ + Name: "vanity", + ShortUsage: "vanity", + ShortHelp: "Vanity Server", + LongHelp: "Vanity Server to serve the Go module vanity domain", + FlagSet: fs, + Options: []ff.Option{ + ff.WithEnvVarPrefix("VANITY"), + ff.WithAllowMissingConfigFile(true), + ff.WithConfigFileFlag("config"), + ff.WithConfigFileParser(fftoml.New().Parse), + }, + Exec: server(portFlag, domainFlag, tokenFlag, dbFlag), + } + return cmd +} + +func server(port *int, domain, token, dbPath *string) func(context.Context, []string) error { + return func(ctx context.Context, args []string) error { + if *token == "" || *domain == "" { + return errors.New("vanity server requires --token and --domain") + } + + db, err := database.Load(*dbPath) + if err != nil { + log.Fatal().Msgf("could not load database at %s: %v", *dbPath, err) + } + + log.Info().Msgf("Running vanity server at http://localhost:%d", *port) + if err := http.ListenAndServe(fmt.Sprintf(":%d", *port), router.New(*token, *domain, db)); err != nil { + return err + } + return nil + } +} + +func dbPath() string { + fn := "vanity.db" + home, err := os.UserHomeDir() + if err != nil { + bin, err := os.Executable() + if err != nil { + return fn + } + return filepath.Join(filepath.Dir(bin), fn) + } + return filepath.Join(home, fn) +} diff --git a/contrib/contrib.go b/contrib/contrib.go deleted file mode 100644 index 65e707e..0000000 --- a/contrib/contrib.go +++ /dev/null @@ -1,28 +0,0 @@ -package contrib - -import ( - _ "embed" - "os" - "strings" -) - -//go:embed vanity.service -var SystemdService string - -func init() { - bin, err := os.Executable() - if err != nil { - return - } - SystemdService = os.Expand(SystemdService, func(s string) string { - switch strings.ToUpper(s) { - case "BIN": - return bin - case "VANITY_TOKEN": - return os.Getenv("VANITY_TOKEN") - case "VANITY_DOMAIN": - return os.Getenv("VANITY_DOMAIN") - } - return "" - }) -} diff --git a/go-vanity/go.mod b/go-vanity/go.mod deleted file mode 100644 index 97706e6..0000000 --- a/go-vanity/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module go.jolheiser.com/vanity/go-vanity - -go 1.16 diff --git a/go.mod b/go.mod index 981270b..bad26dc 100644 --- a/go.mod +++ b/go.mod @@ -2,18 +2,11 @@ module go.jolheiser.com/vanity go 1.16 -replace go.jolheiser.com/vanity/go-vanity => ./go-vanity - require ( github.com/AlecAivazis/survey/v2 v2.2.8 - github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-chi/chi/v5 v5.0.3 - github.com/urfave/cli/v2 v2.2.0 + github.com/peterbourgon/ff/v3 v3.1.2 + github.com/rs/zerolog v1.26.1 go.etcd.io/bbolt v1.3.5 - go.jolheiser.com/beaver v1.0.2 go.jolheiser.com/overlay v0.0.3 - go.jolheiser.com/vanity/go-vanity v0.0.0-00010101000000-000000000000 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 // indirect - golang.org/x/text v0.3.3 // indirect ) diff --git a/go.sum b/go.sum index 26fca36..43c6b0c 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,15 @@ github.com/AlecAivazis/survey/v2 v2.2.8 h1:TgxCwybKdBckmC+/P9/5h49rw/nAHe/itZL0dgHs+Q0= github.com/AlecAivazis/survey/v2 v2.2.8/go.mod h1:9DYvHgXtiXm6nCn+jXnOXLKbH+Yo9u8fAS/SduGdoPk= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw= github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.3 h1:khYQBdPivkYG1s1TAzDQG1f6eX4kD2TItYVZexL5rS4= github.com/go-chi/chi/v5 v5.0.3/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ= github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -22,38 +22,59 @@ github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/peterbourgon/ff/v3 v3.1.2 h1:0GNhbRhO9yHA4CC27ymskOsuRpmX0YQxwxM9UPiP6JM= +github.com/peterbourgon/ff/v3 v3.1.2/go.mod h1:XNJLY8EIl6MjMVjBS4F0+G0LYoAqs0DTa4rmHHukKDE= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.26.1 h1:/ihwxqH+4z8UxyI70wM1z9yCvkWcfz/a3mj48k/Zngc= +github.com/rs/zerolog v1.26.1/go.mod h1:/wSSJWX7lVrsOwlbyTRSOJvqRlc+WjWlfes+CiJ+tmc= github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= -go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= go.jolheiser.com/overlay v0.0.3 h1:5WoXtnRi1w5KkU9xq+si/wV0GINOMJphfSOvgG2l53I= go.jolheiser.com/overlay v0.0.3/go.mod h1:xNbssakJ3HjK4RnjuP38q9yQNS4wxXKsyprYIWWr2bg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e h1:1SzTfNOXwIS2oWiMF+6qu0OUDKb0dauo6MoDUQyu+yU= +golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009 h1:W0lCpv29Hv0UaM1LXb9QlBHLNP8UFfcKjblhVCWftOM= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go deleted file mode 100644 index 59f2e39..0000000 --- a/main.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -import ( - "os" - - "go.jolheiser.com/vanity/cmd" - - "go.jolheiser.com/beaver" -) - -func main() { - - beaver.Console.Format = beaver.FormatOptions{ - TimePrefix: true, - StackPrefix: true, - StackLimit: 15, - LevelPrefix: true, - LevelColor: true, - } - - if err := cmd.New().Run(os.Args); err != nil { - beaver.Fatal(err) - } -} diff --git a/go-vanity/client.go b/sdk/client.go similarity index 98% rename from go-vanity/client.go rename to sdk/client.go index 9c1b76d..9fe6bda 100644 --- a/go-vanity/client.go +++ b/sdk/client.go @@ -1,4 +1,4 @@ -package vanity +package sdk import ( "context" diff --git a/go-vanity/package.go b/sdk/package.go similarity index 99% rename from go-vanity/package.go rename to sdk/package.go index 1e8ef76..7d13e33 100644 --- a/go-vanity/package.go +++ b/sdk/package.go @@ -1,4 +1,4 @@ -package vanity +package sdk import ( "bytes" diff --git a/go-vanity/source.go b/sdk/source.go similarity index 98% rename from go-vanity/source.go rename to sdk/source.go index ea58e74..79fba7b 100644 --- a/go-vanity/source.go +++ b/sdk/source.go @@ -1,4 +1,4 @@ -package vanity +package sdk import ( "errors" diff --git a/go-vanity/vanity_test.go b/sdk/vanity_test.go similarity index 99% rename from go-vanity/vanity_test.go rename to sdk/vanity_test.go index 93f9852..9715f4d 100644 --- a/go-vanity/vanity_test.go +++ b/sdk/vanity_test.go @@ -1,4 +1,4 @@ -package vanity +package sdk import ( "context" diff --git a/database/database.go b/server/database/database.go similarity index 85% rename from database/database.go rename to server/database/database.go index aba2c68..ae58402 100644 --- a/database/database.go +++ b/server/database/database.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "go.jolheiser.com/vanity/go-vanity" + "go.jolheiser.com/vanity/sdk" "go.etcd.io/bbolt" ) @@ -33,8 +33,8 @@ func Load(dbPath string) (*Database, error) { }) } -func (d *Database) Package(name string) (vanity.Package, error) { - var pkg vanity.Package +func (d *Database) Package(name string) (sdk.Package, error) { + var pkg sdk.Package data, err := d.PackageJSON(name) if err != nil { return pkg, err @@ -54,10 +54,10 @@ func (d *Database) PackageJSON(name string) (pkg []byte, err error) { }) } -func (d *Database) Packages() (pkgs []vanity.Package, err error) { +func (d *Database) Packages() (pkgs []sdk.Package, err error) { return pkgs, d.db.View(func(tx *bbolt.Tx) error { return tx.Bucket(packageBucket).ForEach(func(key, val []byte) error { - var pkg vanity.Package + var pkg sdk.Package if err := json.NewDecoder(bytes.NewReader(val)).Decode(&pkg); err != nil { return err } @@ -67,7 +67,7 @@ func (d *Database) Packages() (pkgs []vanity.Package, err error) { }) } -func (d *Database) PutPackage(pkg vanity.Package) error { +func (d *Database) PutPackage(pkg sdk.Package) error { return d.db.Update(func(tx *bbolt.Tx) error { data, err := json.Marshal(pkg) if err != nil { diff --git a/database/errors.go b/server/database/errors.go similarity index 100% rename from database/errors.go rename to server/database/errors.go diff --git a/router/router.go b/server/router/router.go similarity index 74% rename from router/router.go rename to server/router/router.go index 0d38e24..a000b58 100644 --- a/router/router.go +++ b/server/router/router.go @@ -8,56 +8,55 @@ import ( "strings" "time" - "go.jolheiser.com/vanity/cmd/flags" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/go-vanity" + "go.jolheiser.com/vanity/sdk" + "go.jolheiser.com/vanity/server/database" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" - "go.jolheiser.com/beaver" + "github.com/rs/zerolog/log" ) -func New(token string, db *database.Database) *chi.Mux { +func New(token, domain string, db *database.Database) *chi.Mux { r := chi.NewRouter() r.Use(middleware.Recoverer) r.Use(middleware.Timeout(60 * time.Second)) r.Mount("/_/", http.StripPrefix("/_/", static())) - r.Get("/", indexGET(db)) + r.Get("/", indexGET(domain, db)) r.Options("/", infoPackages(db)) r.Post("/", addUpdatePackage(db, token)) r.Patch("/", addUpdatePackage(db, token)) r.Delete("/", removePackage(db, token)) - r.Get("/*", vanityGET(db)) + r.Get("/*", vanityGET(domain, db)) return r } -func indexGET(db *database.Database) http.HandlerFunc { +func indexGET(domain string, db *database.Database) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { packages, err := db.Packages() if err != nil { - beaver.Errorf("could not load packages: %v", err) + log.Error().Msgf("could not load packages: %v", err) http.Error(res, "could not load packages", http.StatusInternalServerError) return } - tpl, err := tmpl("index.tmpl") + tpl, err := tmpl(domain, "index.tmpl") if err != nil { - beaver.Warnf("could not load index template: %v", err) + log.Warn().Msgf("could not load index template: %v", err) } if err := tpl.Execute(res, map[string]interface{}{ "Packages": packages, "Index": true, }); err != nil { - beaver.Errorf("could not write response: %v", err) + log.Error().Msgf("could not write response: %v", err) } } } -func vanityGET(db *database.Database) http.HandlerFunc { +func vanityGET(domain string, db *database.Database) http.HandlerFunc { return func(res http.ResponseWriter, req *http.Request) { key := chi.URLParam(req, "*") key = strings.Split(key, "/")[0] @@ -72,36 +71,36 @@ func vanityGET(db *database.Database) http.HandlerFunc { return } - sdf, err := vanity.AnalyzeSDF(pkg) + sdf, err := sdk.AnalyzeSDF(pkg) if err != nil { - beaver.Warnf("could not get SDF for %s: %v", key, err) + log.Warn().Msgf("could not get SDF for %s: %v", key, err) } ctx := map[string]interface{}{ "Package": pkg, - "Module": pkg.Module(flags.Domain), - "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(flags.Domain), pkg.CloneHTTP, sdf.Dir, sdf.File), + "Module": pkg.Module(domain), + "GoSource": fmt.Sprintf("%s %s %s %s", pkg.Module(domain), pkg.CloneHTTP, sdf.Dir, sdf.File), "Index": false, } q := req.URL.Query() if q.Get("go-get") != "" || q.Get("git-import") != "" { - tpl, err := tmpl("import.tmpl") + tpl, err := tmpl(domain, "import.tmpl") if err != nil { - beaver.Warnf("could not load import template: %v", err) + log.Warn().Msgf("could not load import template: %v", err) } if err := tpl.Execute(res, ctx); err != nil { - beaver.Errorf("could not write response: %v", err) + log.Error().Msgf("could not write response: %v", err) } return } - tpl, err := tmpl("vanity.tmpl") + tpl, err := tmpl(domain, "vanity.tmpl") if err != nil { - beaver.Warnf("could not load vanity template: %v", err) + log.Warn().Msgf("could not load vanity template: %v", err) } if err := tpl.Execute(res, ctx); err != nil { - beaver.Errorf("could not write response: %v", err) + log.Error().Msgf("could not write response: %v", err) } } } @@ -114,7 +113,7 @@ func infoPackages(db *database.Database) func(http.ResponseWriter, *http.Request return } - info := vanity.Info{ + info := sdk.Info{ Version: Version, NumPackages: len(packages), Packages: packages, @@ -128,7 +127,7 @@ func infoPackages(db *database.Database) func(http.ResponseWriter, *http.Request func addUpdatePackage(db *database.Database, token string) func(http.ResponseWriter, *http.Request) { return func(res http.ResponseWriter, req *http.Request) { - if req.Header.Get(vanity.TokenHeader) != token { + if req.Header.Get(sdk.TokenHeader) != token { res.WriteHeader(http.StatusUnauthorized) return } @@ -140,7 +139,7 @@ func addUpdatePackage(db *database.Database, token string) func(http.ResponseWri } defer req.Body.Close() - var pkg vanity.Package + var pkg sdk.Package if err := json.Unmarshal(data, &pkg); err != nil { res.WriteHeader(http.StatusBadRequest) return @@ -181,7 +180,7 @@ func addUpdatePackage(db *database.Database, token string) func(http.ResponseWri func removePackage(db *database.Database, token string) func(http.ResponseWriter, *http.Request) { return func(res http.ResponseWriter, req *http.Request) { - if req.Header.Get(vanity.TokenHeader) != token { + if req.Header.Get(sdk.TokenHeader) != token { res.WriteHeader(http.StatusUnauthorized) return } @@ -193,7 +192,7 @@ func removePackage(db *database.Database, token string) func(http.ResponseWriter } defer req.Body.Close() - var pkg vanity.Package + var pkg sdk.Package if err := json.Unmarshal(data, &pkg); err != nil { res.WriteHeader(http.StatusBadRequest) return diff --git a/router/router_test.go b/server/router/router_test.go similarity index 81% rename from router/router_test.go rename to server/router/router_test.go index dea64e6..4fc40ed 100644 --- a/router/router_test.go +++ b/server/router/router_test.go @@ -9,10 +9,10 @@ import ( "path/filepath" "testing" - "go.jolheiser.com/vanity/database" - "go.jolheiser.com/vanity/go-vanity" + "go.jolheiser.com/vanity/sdk" + "go.jolheiser.com/vanity/server/database" - "go.jolheiser.com/beaver" + "github.com/rs/zerolog/log" ) var ( @@ -31,10 +31,10 @@ func TestMain(m *testing.M) { db, err := database.Load(dbPath) if err != nil { - beaver.Fatalf("could not load database at %s: %v", dbPath, err) + log.Fatal().Msgf("could not load database at %s: %v", dbPath, err) } - server = httptest.NewServer(New(token, db)) + server = httptest.NewServer(New(token, "", db)) code := m.Run() @@ -48,12 +48,12 @@ func TestMain(m *testing.M) { func TestRouter(t *testing.T) { ctx := context.Background() - client := vanity.New("", vanity.WithServer(server.URL)) + client := sdk.New("", sdk.WithServer(server.URL)) // Info checkInfo(t, client, 0) - pkg1 := vanity.Package{ + pkg1 := sdk.Package{ Name: "test1", Description: "test1", Branch: "main", @@ -61,7 +61,7 @@ func TestRouter(t *testing.T) { CloneHTTP: "https://gitea.com/jolheiser/test1.git", CloneSSH: "https://gitea.com/jolheiser/test1", } - pkg2 := vanity.Package{ + pkg2 := sdk.Package{ Name: "test2", Description: "test2", Branch: "main", @@ -77,7 +77,7 @@ func TestRouter(t *testing.T) { } // Add (with token) - client = vanity.New(token, vanity.WithServer(server.URL)) + client = sdk.New(token, sdk.WithServer(server.URL)) checkAdd(t, client, pkg1, pkg2) // Info (after second package) @@ -102,7 +102,7 @@ func TestRouter(t *testing.T) { checkInfo(t, client, 1) } -func checkInfo(t *testing.T, client *vanity.Client, numPackages int) { +func checkInfo(t *testing.T, client *sdk.Client, numPackages int) { info, err := client.Info(context.Background()) if err != nil { t.Logf("info should not return error: %v\n", err) @@ -114,7 +114,7 @@ func checkInfo(t *testing.T, client *vanity.Client, numPackages int) { } } -func checkAdd(t *testing.T, client *vanity.Client, pkg1, pkg2 vanity.Package) { +func checkAdd(t *testing.T, client *sdk.Client, pkg1, pkg2 sdk.Package) { ctx := context.Background() if err := client.Add(ctx, pkg1); err != nil { t.Logf("pkg1 should be added: %v\n", err) @@ -131,10 +131,10 @@ func checkAdd(t *testing.T, client *vanity.Client, pkg1, pkg2 vanity.Package) { } } -func checkUpdate(t *testing.T, client *vanity.Client, pkg vanity.Package) { +func checkUpdate(t *testing.T, client *sdk.Client, pkg sdk.Package) { ctx := context.Background() // Update invalid package - if err := client.Update(ctx, vanity.Package{Name: "test4"}); err == nil { + if err := client.Update(ctx, sdk.Package{Name: "test4"}); err == nil { t.Log("should not be able to update invalid package") t.Fail() } @@ -146,7 +146,7 @@ func checkUpdate(t *testing.T, client *vanity.Client, pkg vanity.Package) { } } -func checkRemove(t *testing.T, client *vanity.Client, pkg vanity.Package) { +func checkRemove(t *testing.T, client *sdk.Client, pkg sdk.Package) { ctx := context.Background() if err := client.Remove(ctx, pkg); err != nil { t.Logf("should be able to remove package: %v\n", err) diff --git a/router/templates.go b/server/router/templates.go similarity index 60% rename from router/templates.go rename to server/router/templates.go index 0191c20..8afcfb8 100644 --- a/router/templates.go +++ b/server/router/templates.go @@ -11,8 +11,6 @@ import ( "strings" "go.jolheiser.com/overlay" - - "go.jolheiser.com/vanity/cmd/flags" ) var ( @@ -34,8 +32,8 @@ func customRoot() string { return customPath } -func tmpl(name string) (*template.Template, error) { - return template.New(name).Funcs(funcMap).ParseFS(ofs, "templates/base.tmpl", "templates/"+name) +func tmpl(domain, name string) (*template.Template, error) { + return template.New(name).Funcs(funcMap(domain)).ParseFS(ofs, "templates/base.tmpl", "templates/"+name) } func static() http.Handler { @@ -46,14 +44,16 @@ func static() http.Handler { return http.FileServer(http.FS(sub)) } -var funcMap = template.FuncMap{ - "AppVer": func() string { - return Version - }, - "GoVer": func() string { - return runtime.Version() - }, - "Domain": func() string { - return strings.TrimSuffix(flags.Domain, "/") - }, +func funcMap(domain string) template.FuncMap { + return template.FuncMap{ + "AppVer": func() string { + return Version + }, + "GoVer": func() string { + return runtime.Version() + }, + "Domain": func() string { + return strings.TrimSuffix(domain, "/") + }, + } } diff --git a/router/templates/base.tmpl b/server/router/templates/base.tmpl similarity index 100% rename from router/templates/base.tmpl rename to server/router/templates/base.tmpl diff --git a/router/templates/import.tmpl b/server/router/templates/import.tmpl similarity index 100% rename from router/templates/import.tmpl rename to server/router/templates/import.tmpl diff --git a/router/templates/index.tmpl b/server/router/templates/index.tmpl similarity index 100% rename from router/templates/index.tmpl rename to server/router/templates/index.tmpl diff --git a/router/templates/static/sakura.css b/server/router/templates/static/sakura.css similarity index 100% rename from router/templates/static/sakura.css rename to server/router/templates/static/sakura.css diff --git a/router/templates/vanity.tmpl b/server/router/templates/vanity.tmpl similarity index 100% rename from router/templates/vanity.tmpl rename to server/router/templates/vanity.tmpl diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..048b2fa --- /dev/null +++ b/tools.go @@ -0,0 +1,8 @@ +//go:build tools +// +build tools + +package main + +import ( + _ "github.com/rs/zerolog/cmd/lint" +) -- 2.41.0