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()) }