Better config options, change package structure

Signed-off-by: jolheiser <john.olheiser@gmail.com>
pull/3/head
jolheiser 2020-09-11 20:38:18 -05:00
parent 6d7150e0a2
commit 35b809dc85
Signed by: jolheiser
GPG Key ID: B853ADA5DA7BBF7A
20 changed files with 422 additions and 247 deletions

View File

@ -4,7 +4,7 @@ VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
.PHONY: build .PHONY: build
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 .PHONY: fmt
fmt: fmt:
@ -16,4 +16,12 @@ test:
.PHONY: vet .PHONY: vet
vet: vet:
$(GO) 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

View File

@ -4,6 +4,20 @@ A simple web service to serve vanity Go imports. Feel free to check it out using
## Configuration ## 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: NAME:
vanity - Vanity Go Imports vanity - Vanity Go Imports
@ -12,15 +26,15 @@ USAGE:
vanity [global options] command [command options] [arguments...] vanity [global options] command [command options] [arguments...]
VERSION: VERSION:
0.1.0+2-g49cd123 0.1.0+3-g6d7150e
COMMANDS: COMMANDS:
help, h Shows a list of commands or help for one command help, h Shows a list of commands or help for one command
GLOBAL OPTIONS: 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] --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] --service value Service type (Gitea, GitHub, GitLab) (default: "gitea") [$VANITY_SERVICE]
--base-url value Base URL to service [$VANITY_BASE_URL] --base-url value Base URL to service [$VANITY_BASE_URL]
--namespace value Owner namespace [$VANITY_NAMESPACE] --namespace value Owner namespace [$VANITY_NAMESPACE]
@ -31,11 +45,33 @@ GLOBAL OPTIONS:
--fork Include forked repositories (default: false) [$VANITY_FORK] --fork Include forked repositories (default: false) [$VANITY_FORK]
--mirror Include mirrored repositories (default: false) [$VANITY_MIRROR] --mirror Include mirrored repositories (default: false) [$VANITY_MIRROR]
--archive Include archived repositories (default: false) [$VANITY_ARCHIVE] --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] --interval value Interval between updating repositories (default: 15m0s) [$VANITY_INTERVAL]
--debug Debug logging (default: false) [$VANITY_DEBUG] --debug Debug logging (default: false) [$VANITY_DEBUG]
--help, -h show help (default: false) --help, -h show help (default: false)
--version, -v print the version (default: false) --version, -v print the version (default: false)
``` ```
Vanity also supports [git-import](https://gitea.com/jolheiser/git-import). Vanity also supports [git-import](https://gitea.com/jolheiser/git-import).
## Docker
```sh
docker run \
--env VANITY_DOMAIN=go.domain.tld \
--env VANITY_NAMESPACE=<jolheiser> \
--env VANITY_TOKEN=<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`.

24
api/package.go 100644
View File

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

View File

@ -1,3 +1,3 @@
package version package api
var Version = "develop" var Version = "develop"

View File

@ -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!"

11
docker/Dockerfile 100644
View File

@ -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"]

View File

@ -0,0 +1,14 @@
version: "2"
services:
vanity:
image: jolheiser/vanity:latest
environment:
- VANITY_DOMAIN=go.domain.tld
- VANITY_NAMESPACE=<jolheiser>
- VANITY_TOKEN=<token>\
#- VANITY_SERVICE=gitea
#- VANITY_BASE_URL=https://gitea.com
restart: always
ports:
- "80:7777"

101
flags/config.go 100644
View File

@ -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
}

View File

@ -1,49 +1,43 @@
package flags package flags
import ( import (
"errors"
"fmt"
"net/url" "net/url"
"os"
"regexp" "regexp"
"strings" "strings"
"time" "time"
"github.com/BurntSushi/toml" "go.jolheiser.com/vanity/api"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.jolheiser.com/beaver" "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 ( var (
Config = flagConfig{
Overrides: make(map[string]nameOverride),
}
configPath string configPath string
baseURL string
include cli.StringSlice include cli.StringSlice
exclude 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{ var Flags = []cli.Flag{
@ -58,41 +52,39 @@ var Flags = []cli.Flag{
Usage: "Port to run the vanity server on", Usage: "Port to run the vanity server on",
Value: 7777, Value: 7777,
EnvVars: []string{"VANITY_PORT"}, EnvVars: []string{"VANITY_PORT"},
Destination: &Config.Port, Destination: &Port,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "domain", Name: "domain",
Usage: "Vanity domain, e.g. go.domain.tld", Usage: "Vanity domain, e.g. go.domain.tld",
EnvVars: []string{"VANITY_DOMAIN"}, EnvVars: []string{"VANITY_DOMAIN"},
Required: true, Required: true,
Destination: &Config.Domain, Destination: &Domain,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "service", Name: "service",
Usage: "Service type (Gitea, GitHub, GitLab)", Usage: "Service type (Gitea, GitHub, GitLab)",
Value: "gitea", Value: "gitea",
EnvVars: []string{"VANITY_SERVICE"}, EnvVars: []string{"VANITY_SERVICE"},
Destination: &Config.Service, Destination: &Service,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "base-url", Name: "base-url",
Usage: "BaseURL URL to service", Usage: "Base URL to service",
EnvVars: []string{"VANITY_BASE_URL"}, EnvVars: []string{"VANITY_BASE_URL"},
Destination: &Config.BaseURL, Destination: &baseURL,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "namespace", Name: "namespace",
Usage: "Owner namespace", Usage: "Owner namespace",
EnvVars: []string{"VANITY_NAMESPACE"}, EnvVars: []string{"VANITY_NAMESPACE"},
Required: true, Destination: &Namespace,
Destination: &Config.Namespace,
}, },
&cli.StringFlag{ &cli.StringFlag{
Name: "token", Name: "token",
Usage: "Access token", Usage: "Access token",
EnvVars: []string{"VANITY_TOKEN"}, EnvVars: []string{"VANITY_TOKEN"},
Required: true, Destination: &Token,
Destination: &Config.Token,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "include", Name: "include",
@ -110,128 +102,103 @@ var Flags = []cli.Flag{
Name: "private", Name: "private",
Usage: "Include private repositories", Usage: "Include private repositories",
EnvVars: []string{"VANITY_PRIVATE"}, EnvVars: []string{"VANITY_PRIVATE"},
Destination: &Config.Private, Destination: &Private,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "fork", Name: "fork",
Usage: "Include forked repositories", Usage: "Include forked repositories",
EnvVars: []string{"VANITY_FORK"}, EnvVars: []string{"VANITY_FORK"},
Destination: &Config.Private, Destination: &Fork,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "mirror", Name: "mirror",
Usage: "Include mirrored repositories", Usage: "Include mirrored repositories",
EnvVars: []string{"VANITY_MIRROR"}, EnvVars: []string{"VANITY_MIRROR"},
Destination: &Config.Mirror, Destination: &Mirror,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "archive", Name: "archive",
Usage: "Include archived repositories", Usage: "Include archived repositories",
EnvVars: []string{"VANITY_ARCHIVE"}, 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{ &cli.DurationFlag{
Name: "interval", Name: "interval",
Usage: "Interval between updating repositories", Usage: "Interval between updating repositories",
Value: time.Minute * 15, Value: time.Minute * 15,
EnvVars: []string{"VANITY_INTERVAL"}, EnvVars: []string{"VANITY_INTERVAL"},
Destination: &Config.Interval, Destination: &Interval,
}, },
&cli.BoolFlag{ &cli.BoolFlag{
Name: "debug", Name: "debug",
Usage: "Debug logging", Usage: "Debug logging",
EnvVars: []string{"VANITY_DEBUG"}, EnvVars: []string{"VANITY_DEBUG"},
Destination: &Config.Debug, Destination: &Debug,
}, },
} }
func Before(ctx *cli.Context) error { func Before(ctx *cli.Context) error {
setConfig(ctx) setConfig(ctx)
if Config.BaseURL == "" { var defaultURL string
switch strings.ToLower(Config.Service) { var configOnly bool
case "gitea": switch strings.ToLower(Service) {
Config.BaseURL = "https://gitea.com" case "gitea":
case "github": defaultURL = "https://gitea.com"
Config.BaseURL = "https://github.com" case "github":
case "gitlab": defaultURL = "https://github.com"
Config.BaseURL = "https://gitlab.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 { if err != nil {
return err 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() { 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() { 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 beaver.Console.Level = beaver.DEBUG
} }
return nil 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
}
}

View File

@ -5,9 +5,9 @@ import (
"net/http" "net/http"
"os" "os"
"go.jolheiser.com/vanity/api"
"go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/flags"
"go.jolheiser.com/vanity/router" "go.jolheiser.com/vanity/router"
"go.jolheiser.com/vanity/version"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.jolheiser.com/beaver" "go.jolheiser.com/beaver"
@ -17,7 +17,7 @@ func main() {
app := cli.NewApp() app := cli.NewApp()
app.Name = "vanity" app.Name = "vanity"
app.Usage = "Vanity Go Imports" app.Usage = "Vanity Go Imports"
app.Version = version.Version app.Version = api.Version
app.Action = doAction app.Action = doAction
app.Flags = flags.Flags app.Flags = flags.Flags
app.Before = flags.Before app.Before = flags.Before

View File

@ -3,19 +3,19 @@ package router
import ( import (
"sync" "sync"
"go.jolheiser.com/vanity/service" "go.jolheiser.com/vanity/api"
) )
var cache = &packageCache{ var cache = &packageCache{
packages: make(map[string]*service.Package), packages: make(map[string]*api.Package),
} }
type packageCache struct { type packageCache struct {
packages map[string]*service.Package packages map[string]*api.Package
sync.Mutex sync.Mutex
} }
func (c *packageCache) Update(packages map[string]*service.Package) { func (c *packageCache) Update(packages map[string]*api.Package) {
c.Lock() c.Lock()
c.packages = packages c.packages = packages
c.Unlock() c.Unlock()

View File

@ -13,7 +13,7 @@ import (
var svc service.Service var svc service.Service
func cronStart() { func cronStart() {
ticker := time.NewTicker(flags.Config.Interval) ticker := time.NewTicker(flags.Interval)
for { for {
<-ticker.C <-ticker.C
beaver.Debug("Running package update...") beaver.Debug("Running package update...")
@ -36,8 +36,16 @@ func cronUpdate() {
delete(packages, name) delete(packages, name)
continue continue
} }
if !svc.GoMod(pkg) { goMod, err := svc.GoMod(pkg)
beaver.Debugf("%s isn't a Go project", pkg.Name) 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) delete(packages, name)
continue continue
} }
@ -46,15 +54,20 @@ func cronUpdate() {
// Overrides // Overrides
for name, pkg := range packages { for name, pkg := range packages {
for key, override := range flags.Config.Overrides { for key, override := range flags.Override {
if strings.EqualFold(name, key) { if strings.EqualFold(name, key) {
beaver.Debugf("Overriding %s -> %s", name, override.Name) beaver.Debugf("Overriding %s -> %s", name, override)
delete(packages, key) delete(packages, key)
pkg.Name = override.Name pkg.Name = override
packages[override.Name] = pkg packages[override] = pkg
} }
} }
} }
// Add packages manually added to config
for _, pkg := range flags.ConfigPackages {
packages[pkg.Name] = pkg
}
cache.Update(packages) cache.Update(packages)
} }

View File

@ -8,10 +8,10 @@ import (
"strings" "strings"
"time" "time"
"go.jolheiser.com/vanity/api"
"go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/flags"
"go.jolheiser.com/vanity/router/templates" "go.jolheiser.com/vanity/router/templates"
"go.jolheiser.com/vanity/service" "go.jolheiser.com/vanity/service"
"go.jolheiser.com/vanity/version"
"github.com/go-chi/chi" "github.com/go-chi/chi"
"github.com/go-chi/chi/middleware" "github.com/go-chi/chi/middleware"
@ -39,14 +39,14 @@ func Init() *chi.Mux {
beaver.Infof("Finished warming up cache: %s", cache.Names()) beaver.Infof("Finished warming up cache: %s", cache.Names())
go cronStart() 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 return r
} }
func doIndex(res http.ResponseWriter, _ *http.Request) { func doIndex(res http.ResponseWriter, _ *http.Request) {
if err := index.Execute(res, map[string]interface{}{ if err := index.Execute(res, map[string]interface{}{
"Packages": cache.packages, "Packages": cache.packages,
"AppVer": version.Version, "AppVer": api.Version,
"GoVer": runtime.Version(), "GoVer": runtime.Version(),
}); err != nil { }); err != nil {
beaver.Error(err) beaver.Error(err)
@ -63,9 +63,9 @@ func doVanity(res http.ResponseWriter, req *http.Request) {
if err := vanity.Execute(res, map[string]interface{}{ if err := vanity.Execute(res, map[string]interface{}{
"Package": pkg, "Package": pkg,
"AppVer": version.Version, "AppVer": api.Version,
"GoVer": runtime.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 { }); err != nil {
beaver.Error(err) beaver.Error(err)
} }

View File

@ -11,11 +11,11 @@ var Head = `
<meta name="og:description" content="{{.Package.Description}}"/> <meta name="og:description" content="{{.Package.Description}}"/>
<!-- Go --> <!-- Go -->
<meta name="go-import" content="{{.Package.Module}} git {{.Package.HTTP}}"/> <meta name="go-import" content="{{.Package.Module}} git {{.Package.CloneHTTP}}"/>
<meta name="go-source" content="{{.GoSource}}"> <meta name="go-source" content="{{.GoSource}}">
<!-- Git Import --> <!-- Git Import -->
<meta name="git-import" content="{{.Package.Name}} {{.Package.HTTP}} {{.Package.SSH}}"/> <meta name="git-import" content="{{.Package.Name}} {{.Package.CloneHTTP}} {{.Package.CloneSSH}}"/>
{{end}} {{end}}
<title>Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}}</title> <title>Vanity - {{if .Package}}{{.Package.Name}}{{else}}Index{{end}}</title>
</head> </head>

View File

@ -4,7 +4,7 @@ var Vanity = `
<h1><a href="../">Index</a></h1> <h1><a href="../">Index</a></h1>
<hr/> <hr/>
<p><strong>Name: </strong>{{.Package.Name}}</p> <p><strong>Name: </strong>{{.Package.Name}}</p>
<p><strong>Source: </strong><a href="{{.Package.URL}}">{{.Package.URL}}</a></p> <p><strong>Source: </strong><a href="{{.Package.WebURL}}">{{.Package.WebURL}}</a></p>
{{if .Package.Description}}<p><strong>Description: </strong>{{.Package.Description}}</p>{{end}} {{if .Package.Description}}<p><strong>Description: </strong>{{.Package.Description}}</p>{{end}}
<p><strong>Documentation: <a href="https://pkg.go.dev/{{.Package.Module}}">https://pkg.go.dev/{{.Package.Module}}</a></strong></p> <p><strong>Documentation: <a href="https://pkg.go.dev/{{.Package.Module}}">https://pkg.go.dev/{{.Package.Module}}</a></strong></p>
` `

View File

@ -3,6 +3,7 @@ package service
import ( import (
"fmt" "fmt"
"go.jolheiser.com/vanity/api"
"go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/flags"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
@ -11,7 +12,7 @@ import (
var _ Service = &Gitea{} var _ Service = &Gitea{}
func NewGitea() *Gitea { func NewGitea() *Gitea {
client := gitea.NewClient(flags.Config.BaseURL, flags.Config.Token) client := gitea.NewClient(flags.BaseURL.String(), flags.Token)
return &Gitea{ return &Gitea{
client: client, client: client,
} }
@ -21,8 +22,8 @@ type Gitea struct {
client *gitea.Client client *gitea.Client
} }
func (g Gitea) Packages() (map[string]*Package, error) { func (g Gitea) Packages() (map[string]*api.Package, error) {
packages := make(map[string]*Package) packages := make(map[string]*api.Package)
page := 0 page := 0
for { for {
opts := gitea.ListReposOptions{ 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 { if err != nil {
return nil, err return nil, err
} }
for _, repo := range repos { for _, repo := range repos {
packages[repo.Name] = &Package{ packages[repo.Name] = &api.Package{
Name: repo.Name, Name: repo.Name,
Description: repo.Description, Description: repo.Description,
Branch: repo.DefaultBranch, Branch: repo.DefaultBranch,
URL: repo.HTMLURL, WebURL: repo.HTMLURL,
HTTP: repo.CloneURL, CloneHTTP: repo.CloneURL,
SSH: repo.SSHURL, CloneSSH: repo.SSHURL,
private: repo.Private, Private: repo.Private,
fork: repo.Fork, Fork: repo.Fork,
mirror: repo.Mirror, Mirror: repo.Mirror,
archive: repo.Archived, Archive: repo.Archived,
} }
} }
@ -61,15 +62,15 @@ func (g Gitea) Packages() (map[string]*Package, error) {
return packages, nil return packages, nil
} }
func (g Gitea) GoDir(pkg *Package) string { func (g Gitea) GoDir(pkg *api.Package) string {
return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.URL, pkg.Branch) return fmt.Sprintf("%s/src/branch/%s{/dir}", pkg.WebURL, pkg.Branch)
} }
func (g Gitea) GoFile(pkg *Package) string { func (g Gitea) GoFile(pkg *api.Package) string {
return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) return fmt.Sprintf("%s/src/branch/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch)
} }
func (g Gitea) GoMod(pkg *Package) bool { func (g Gitea) GoMod(pkg *api.Package) (string, error) {
_, err := g.client.GetFile(flags.Config.Namespace, pkg.Name, pkg.Branch, "go.mod") content, err := g.client.GetFile(flags.Namespace, pkg.Name, pkg.Branch, "go.mod")
return err == nil return string(content), err
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"go.jolheiser.com/vanity/api"
"go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/flags"
"github.com/google/go-github/v32/github" "github.com/google/go-github/v32/github"
@ -14,11 +15,11 @@ var _ Service = &GitHub{}
func NewGitHub() *GitHub { func NewGitHub() *GitHub {
ts := oauth2.StaticTokenSource( ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: flags.Config.Token}, &oauth2.Token{AccessToken: flags.Token},
) )
client := oauth2.NewClient(context.Background(), ts) client := oauth2.NewClient(context.Background(), ts)
ghClient := github.NewClient(client) ghClient := github.NewClient(client)
ghClient.BaseURL = flags.Config.URL ghClient.BaseURL = flags.BaseURL
return &GitHub{ return &GitHub{
client: ghClient, client: ghClient,
} }
@ -28,8 +29,8 @@ type GitHub struct {
client *github.Client client *github.Client
} }
func (g GitHub) Packages() (map[string]*Package, error) { func (g GitHub) Packages() (map[string]*api.Package, error) {
packages := make(map[string]*Package) packages := make(map[string]*api.Package)
page := 0 page := 0
for { for {
opts := github.RepositoryListOptions{ 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 { if err != nil {
return nil, err return nil, err
} }
for _, repo := range repos { for _, repo := range repos {
packages[repo.GetName()] = &Package{ packages[repo.GetName()] = &api.Package{
Name: repo.GetName(), Name: repo.GetName(),
Description: repo.GetDescription(), Description: repo.GetDescription(),
Branch: repo.GetDefaultBranch(), Branch: repo.GetDefaultBranch(),
URL: repo.GetHTMLURL(), WebURL: repo.GetHTMLURL(),
HTTP: repo.GetCloneURL(), CloneHTTP: repo.GetCloneURL(),
SSH: repo.GetSSHURL(), CloneSSH: repo.GetSSHURL(),
private: repo.GetPrivate(), Private: repo.GetPrivate(),
fork: repo.GetFork(), Fork: repo.GetFork(),
mirror: false, Mirror: false,
archive: repo.GetArchived(), Archive: repo.GetArchived(),
} }
} }
@ -68,18 +69,21 @@ func (g GitHub) Packages() (map[string]*Package, error) {
return packages, nil return packages, nil
} }
func (g GitHub) GoDir(pkg *Package) string { func (g GitHub) GoDir(pkg *api.Package) string {
return fmt.Sprintf("%s/tree/%s{/dir}", pkg.URL, pkg.Branch) return fmt.Sprintf("%s/tree/%s{/dir}", pkg.WebURL, pkg.Branch)
} }
func (g GitHub) GoFile(pkg *Package) string { func (g GitHub) GoFile(pkg *api.Package) string {
return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) return fmt.Sprintf("%s/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch)
} }
func (g GitHub) GoMod(pkg *Package) bool { func (g GitHub) GoMod(pkg *api.Package) (string, error) {
_, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Config.Namespace, pkg.Name, "go.mod", content, _, _, err := g.client.Repositories.GetContents(context.Background(), flags.Namespace, pkg.Name, "go.mod",
&github.RepositoryContentGetOptions{ &github.RepositoryContentGetOptions{
Ref: pkg.Branch, Ref: pkg.Branch,
}) })
return err == nil if err != nil {
return "", err
}
return content.GetContent()
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"html" "html"
"go.jolheiser.com/vanity/api"
"go.jolheiser.com/vanity/flags" "go.jolheiser.com/vanity/flags"
"github.com/xanzy/go-gitlab" "github.com/xanzy/go-gitlab"
@ -13,7 +14,7 @@ import (
var _ Service = &GitLab{} var _ Service = &GitLab{}
func NewGitLab() *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 { if err != nil {
beaver.Errorf("could not create GitLab client: %v", err) beaver.Errorf("could not create GitLab client: %v", err)
} }
@ -26,8 +27,8 @@ type GitLab struct {
client *gitlab.Client client *gitlab.Client
} }
func (g GitLab) Packages() (map[string]*Package, error) { func (g GitLab) Packages() (map[string]*api.Package, error) {
packages := make(map[string]*Package) packages := make(map[string]*api.Package)
page := 0 page := 0
for { for {
opts := gitlab.ListProjectsOptions{ 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 { if err != nil {
return nil, err return nil, err
} }
for _, repo := range repos { for _, repo := range repos {
packages[repo.Name] = &Package{ packages[repo.Name] = &api.Package{
Name: repo.Name, Name: repo.Name,
Description: repo.Description, Description: repo.Description,
Branch: repo.DefaultBranch, Branch: repo.DefaultBranch,
URL: repo.WebURL, WebURL: repo.WebURL,
HTTP: repo.HTTPURLToRepo, CloneHTTP: repo.HTTPURLToRepo,
SSH: repo.SSHURLToRepo, CloneSSH: repo.SSHURLToRepo,
private: repo.Visibility != gitlab.PublicVisibility, Private: repo.Visibility != gitlab.PublicVisibility,
fork: repo.ForkedFromProject != nil, Fork: repo.ForkedFromProject != nil,
mirror: repo.Mirror, Mirror: repo.Mirror,
archive: repo.Archived, Archive: repo.Archived,
} }
} }
@ -66,18 +67,18 @@ func (g GitLab) Packages() (map[string]*Package, error) {
return packages, nil return packages, nil
} }
func (g GitLab) GoDir(pkg *Package) string { func (g GitLab) GoDir(pkg *api.Package) string {
return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.URL, pkg.Branch) return fmt.Sprintf("%s/-/tree/%s{/dir}", pkg.WebURL, pkg.Branch)
} }
func (g GitLab) GoFile(pkg *Package) string { func (g GitLab) GoFile(pkg *api.Package) string {
return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.URL, pkg.Branch) return fmt.Sprintf("%s/-/blob/%s{/dir}/{file}#L{line}", pkg.WebURL, pkg.Branch)
} }
func (g GitLab) GoMod(pkg *Package) bool { func (g GitLab) GoMod(pkg *api.Package) (string, error) {
id := fmt.Sprintf("%s/%s", flags.Config.Namespace, pkg.Name) id := fmt.Sprintf("%s/%s", flags.Namespace, pkg.Name)
_, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{ content, _, err := g.client.RepositoryFiles.GetRawFile(html.EscapeString(id), "go.mod", &gitlab.GetRawFileOptions{
Ref: &pkg.Branch, Ref: &pkg.Branch,
}) })
return err == nil return string(content), err
} }

23
service/off.go 100644
View File

@ -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
}

View File

@ -4,78 +4,62 @@ import (
"fmt" "fmt"
"strings" "strings"
"go.jolheiser.com/vanity/api"
"go.jolheiser.com/vanity/flags" "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 { type Service interface {
Packages() (map[string]*Package, error) Packages() (map[string]*api.Package, error)
GoDir(*Package) string GoDir(*api.Package) string
GoFile(*Package) string GoFile(*api.Package) string
GoMod(*Package) bool GoMod(*api.Package) (string, error)
} }
func New() Service { func New() Service {
switch strings.ToLower(flags.Config.Service) { switch strings.ToLower(flags.Service) {
case "gitea": case "gitea":
return NewGitea() return NewGitea()
case "github": case "github":
return NewGitHub() return NewGitHub()
case "gitlab": case "gitlab":
return NewGitLab() return NewGitLab()
default:
return Off{}
} }
return nil
} }
func Check(pkg *Package) error { func Check(pkg *api.Package) error {
// Private // 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) return fmt.Errorf("%s is private and --private wasn't used", pkg.Name)
} }
// Forked // 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) return fmt.Errorf("%s is a fork and --fork wasn't used", pkg.Name)
} }
// Mirrored // 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) return fmt.Errorf("%s is a mirror and --mirror wasn't used", pkg.Name)
} }
// Archived // 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) return fmt.Errorf("%s is archived and --archive wasn't used", pkg.Name)
} }
// Exclusions // Exclusions
for _, exclude := range flags.Config.Exclude { for _, exclude := range flags.Exclude {
if exclude.MatchString(pkg.Name) { if exclude.MatchString(pkg.Name) {
return fmt.Errorf("%s is was excluded by the rule %s", pkg.Name, exclude.String()) return fmt.Errorf("%s is was excluded by the rule %s", pkg.Name, exclude.String())
} }
} }
// Inclusions // Inclusions
if len(flags.Config.Include) > 0 { if len(flags.Include) > 0 {
for _, include := range flags.Config.Include { for _, include := range flags.Include {
if include.MatchString(pkg.Name) { 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 was included by the rule %s", pkg.Name, include.String())
} }