Skip to content

Commit

Permalink
Load vanity urls from yaml configmap (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort authored Dec 30, 2024
1 parent ceba08c commit e250d11
Show file tree
Hide file tree
Showing 12 changed files with 141 additions and 32 deletions.
3 changes: 3 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
VANITY_MAINTENANCE=false
VANITY_DOMAIN=go.rotational.io
VANITY_DEFAULT_BRANCH=main
VANITY_CONFIG_MAP=tmp/vanity.yaml
VANITY_LOG_LEVEL=info
VANITY_CONSOLE_LOG=true
VANITY_BIND_ADDR=127.0.0.1:8000
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ go.work.sum

# env file
.env
tmp
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Golang Vanity URLs for Import Paths

This package implements a server that can respond to the golang import paths protocol and redirect `go tool` to the correct repository path so that we can host our go packages at `go.rotational.dev` instead of `github.com/rotationalio`.
This package implements a server that can respond to the golang import paths protocol and redirect `go tool` to the correct repository path so that we can host our go packages at `go.rotational.io` instead of `github.com/rotationalio`.

This server implements some Rotational specific tools. If you're interested in hosting your own vanity URLs for Go packages, I suggest reading [Making a Golang Vanity URL](https://medium.com/@JonNRb/making-a-golang-vanity-url-f56d8eec5f6c) by Jon Betti and using his [go.jonrb.io/vanity](https://pkg.go.dev/go.jonnrb.io/vanity) server as a starting place.
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Config struct {
Maintenance bool `default:"false" desc:"if true, the server will start in maintenance mode"`
Domain string `default:"" desc:"specify the domain of the vanity URLs, otherwise the request host will be used"`
DefaultBranch string `split_words:"true" default:"main" desc:"specify the default branch to use for repository references"`
ConfigMap string `split_words:"true" required:"true" desc:"location of yaml file with module configurations"`
LogLevel logger.LevelDecoder `split_words:"true" default:"info" desc:"specify the verbosity of logging (trace, debug, info, warn, error, fatal panic)"`
ConsoleLog bool `split_words:"true" default:"false" desc:"if true logs colorized human readable output instead of json"`
BindAddr string `split_words:"true" default:":3264" desc:"the ip address and port to bind the server on"`
Expand Down
14 changes: 10 additions & 4 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import (
)

var testEnv = map[string]string{
"VANITY_MAINTENANCE": "true",
"VANITY_LOG_LEVEL": "debug",
"VANITY_CONSOLE_LOG": "true",
"VANITY_BIND_ADDR": "127.0.0.1:443",
"VANITY_MAINTENANCE": "true",
"VANITY_DOMAIN": "go.example.com",
"VANITY_DEFAULT_BRANCH": "develop",
"VANITY_CONFIG_MAP": "path/to/vanity.yaml",
"VANITY_LOG_LEVEL": "debug",
"VANITY_CONSOLE_LOG": "true",
"VANITY_BIND_ADDR": "127.0.0.1:443",
}

func TestConfig(t *testing.T) {
Expand All @@ -27,6 +30,9 @@ func TestConfig(t *testing.T) {

// Ensure configuration is correctly set from the environment
require.True(t, conf.Maintenance)
require.Equal(t, testEnv["VANITY_DOMAIN"], conf.Domain)
require.Equal(t, testEnv["VANITY_DEFAULT_BRANCH"], conf.DefaultBranch)
require.Equal(t, testEnv["VANITY_CONFIG_MAP"], conf.ConfigMap)
require.Equal(t, zerolog.DebugLevel, conf.GetLogLevel())
require.True(t, conf.ConsoleLog)
require.Equal(t, testEnv["VANITY_BIND_ADDR"], conf.BindAddr)
Expand Down
13 changes: 6 additions & 7 deletions server/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// Sets up the server's middleware and routes.
func (s *Server) setupRoutes() (err error) {
func (s *Server) setupRoutes(pkgs []*vanity.GoPackage) (err error) {
middleware := []middleware.Middleware{
logger.HTTPLogger("vanity", vanity.Version()),
s.Maintenance(),
Expand All @@ -29,14 +29,13 @@ func (s *Server) setupRoutes() (err error) {
// Application Routes
static, _ := fs.Sub(content, "static")
s.router.ServeFiles("/static/*filepath", http.FS(static))
s.addRoute(http.MethodGet, "/", s.HomePage(), middleware...)
s.addRoute(http.MethodGet, "/", s.HomePage(pkgs), middleware...)

// Golang Vanity Handling
pkg := &vanity.GoPackage{Domain: "go.rotational.io", Repository: "https://github.com/rotationalio/confire"}
pkg.Resolve(nil)
s.addRoute(http.MethodGet, "/rotationalio/confire", Vanity(pkg), middleware...)
s.addRoute(http.MethodGet, "/rotationalio/confire/*filepath", Vanity(pkg), middleware...)

for _, pkg := range pkgs {
s.addRoute(http.MethodGet, "/"+pkg.Module, Vanity(pkg), middleware...)
s.addRoute(http.MethodGet, "/"+pkg.Module+"/*filepath", Vanity(pkg), middleware...)
}
return nil
}

Expand Down
9 changes: 8 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

"github.com/julienschmidt/httprouter"
"github.com/rotationalio/vanity"
"github.com/rotationalio/vanity/config"
"github.com/rotationalio/vanity/logger"
"github.com/rs/zerolog"
Expand Down Expand Up @@ -58,6 +59,12 @@ func New(conf config.Config) (s *Server, err error) {
log.Logger = zerolog.New(console).With().Timestamp().Logger()
}

// Load the vanity URLs from the config map
var pkgs []*vanity.GoPackage
if pkgs, err = vanity.Load(&conf); err != nil {
return nil, err
}

// Create the server and prepare to serve.
s = &Server{
conf: conf,
Expand All @@ -69,7 +76,7 @@ func New(conf config.Config) (s *Server, err error) {
s.router.RedirectFixedPath = true
s.router.HandleMethodNotAllowed = true
s.router.RedirectTrailingSlash = true
if err = s.setupRoutes(); err != nil {
if err = s.setupRoutes(pkgs); err != nil {
return nil, err
}

Expand Down
19 changes: 19 additions & 0 deletions server/static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,23 @@ header div {

.redirect {
padding: 32px 0;
}

.packages {
width: 100%;
}

.packages table {
margin: 20px auto;
}

table {
text-align: left;
vertical-align: middle;
border-collapse: collapse;
}

th, td {
border: 0px solid black;
padding: 2px 10px;
}
22 changes: 22 additions & 0 deletions server/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,27 @@
<h1 class="text-white">Rotational Go Packages</h1>
</div>
</header>

<div class="packages">
<table>
<thead>
<tr>
<th>Module</th>
<th>Import</th>
<th>Source</th>
</tr>
</thead>
<tbody>
{{ range . }}
<tr>
<td>{{ .Module }}</td>
<td><a href="{{ .GoDoc }}">{{ .Import }}</a></td>
<td><a href="{{ .Source }}">{{ .Source }}</a></td>
</tr>
{{ end }}
</tbody>
</table>
</div>

</body>
</html>
8 changes: 4 additions & 4 deletions server/templates/vanity.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="go-import" content="{{ .GoImportMeta }}" />
<meta name="go-source" content="{{ .GoSourceMeta }}" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="go-import" content="{{ .GoImportMeta }}">
<meta name="go-source" content="{{ .GoSourceMeta }}">
<meta http-equiv="refresh" content="0; url={{ .Redirect }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex,follow">

<title>Rotational Go Packages</title>

<link rel="icon" type="image/ico" href="/static/img/favicon.ico">
<link rel="stylesheet" href="/static/css/style.css" />
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<header >
Expand Down
24 changes: 22 additions & 2 deletions server/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,38 @@ import (
"net/http"

"github.com/julienschmidt/httprouter"
"github.com/rotationalio/vanity"
)

//go:embed all:templates
//go:embed all:static
var content embed.FS

func (s *Server) HomePage() httprouter.Handle {
type homeTableItem struct {
Module string
Import string
Source string
GoDoc string
}

func (s *Server) HomePage(pkgs []*vanity.GoPackage) httprouter.Handle {
// Compile the template for serving the home page.
templates, _ := fs.Sub(content, "templates")
index := template.Must(template.ParseFS(templates, "*.html"))

// Compile the package information for serving the modules table
table := make([]homeTableItem, 0, len(pkgs))
for _, pkg := range pkgs {
table = append(table, homeTableItem{
Module: pkg.Module,
Import: pkg.Import(),
Source: pkg.Repository,
GoDoc: pkg.Redirect(),
})
}

return func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
if err := index.ExecuteTemplate(w, "index.html", nil); err != nil {
if err := index.ExecuteTemplate(w, "index.html", table); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
Expand Down
57 changes: 44 additions & 13 deletions vanity.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package vanity

import (
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/rotationalio/vanity/config"
"gopkg.in/yaml.v3"
)

const (
Expand All @@ -27,14 +30,39 @@ var validProtocols = map[string]struct{}{
}

type GoPackage struct {
Domain string `json:"-"` // the vanity URL domain to use
Module string `json:"-"` // the module name where go.mod is located; parsed from the repository
Package string `json:"-"` // the full package path being requested for correct redirects
Protocol string `json:"protocol"` // can be "git", "github", or "gogs" -- defaults to "git"
Repository string `json:"repository"` // a path to the public repository starting with https://
Branch string `json:"branch"` // the name of the default branch -- defaults to "main"
repo *url.URL `json:"-"` // the parsed repository URL
user string `json:"-"` // the user or organization from the repository
Domain string `yaml:"-"` // the vanity URL domain to use
Module string `yaml:"-"` // the module name where go.mod is located; parsed from the repository
Package string `yaml:"-"` // the full package path being requested for correct redirects
Protocol string `yaml:"protocol"` // can be "git", "github", or "gogs" -- defaults to "git"
Repository string `yaml:"repository"` // a path to the public repository starting with https://
Branch string `yaml:"branch"` // the name of the default branch -- defaults to "main"
repo *url.URL `yaml:"-"` // the parsed repository URL
user string `yaml:"-"` // the user or organization from the repository
}

func Load(conf *config.Config) (pkgs []*GoPackage, err error) {
if err = conf.Validate(); err != nil {
return nil, err
}

var f *os.File
if f, err = os.Open(conf.ConfigMap); err != nil {
return nil, fmt.Errorf("could not open %q: %w", conf.ConfigMap, err)
}
defer f.Close()

pkgs = make([]*GoPackage, 0)
if err = yaml.NewDecoder(f).Decode(&pkgs); err != nil {
return nil, fmt.Errorf("could not decode vanity urls config: %w", err)
}

for i, pkg := range pkgs {
if err = pkg.Resolve(conf); err != nil {
return nil, fmt.Errorf("could not resolve config %d: %w", i+1, err)
}
}

return pkgs, nil
}

func (p *GoPackage) Resolve(conf *config.Config) (err error) {
Expand Down Expand Up @@ -107,11 +135,14 @@ func (p *GoPackage) WithRequest(r *http.Request) GoPackage {
}

func (p GoPackage) Redirect() string {
return godoc.ResolveReference(
&url.URL{
Path: filepath.Join("/", p.Domain, p.Package),
},
).String()
var uri *url.URL
if p.Package != "" {
uri = godoc.ResolveReference(&url.URL{Path: filepath.Join("/", p.Domain, p.Package)})
} else {
uri = godoc.ResolveReference(&url.URL{Path: filepath.Join("/", p.Domain, p.Module)})
}

return uri.String()
}

func (p GoPackage) GoImportMeta() string {
Expand Down

0 comments on commit e250d11

Please sign in to comment.