mirror of
https://github.com/genuinetools/reg.git
synced 2024-09-28 11:46:20 -04:00
324 lines
7.7 KiB
Go
324 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/docker/distribution/manifest/schema1"
|
|
"github.com/docker/docker/cliconfig"
|
|
"github.com/docker/engine-api/types"
|
|
humanize "github.com/dustin/go-humanize"
|
|
"github.com/jessfraz/reg/registry"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
const (
|
|
// VERSION is the binary version.
|
|
VERSION = "v0.1.0"
|
|
|
|
dockerConfigPath = ".docker/config.json"
|
|
)
|
|
|
|
var (
|
|
updating = false
|
|
)
|
|
|
|
// preload initializes any global options and configuration
|
|
// before the main or sub commands are run.
|
|
func preload(c *cli.Context) (err error) {
|
|
if c.GlobalBool("debug") {
|
|
logrus.SetLevel(logrus.DebugLevel)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
app := cli.NewApp()
|
|
app.Name = "reg-server"
|
|
app.Version = VERSION
|
|
app.Author = "@jessfraz"
|
|
app.Email = "no-reply@butts.com"
|
|
app.Usage = "Docker registry v2 static UI server."
|
|
app.Before = preload
|
|
app.Flags = []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "debug, d",
|
|
Usage: "run in debug mode",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "username, u",
|
|
Usage: "username for the registry",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "password, p",
|
|
Usage: "password for the registry",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "registry, r",
|
|
Usage: "URL to the provate registry (ex. r.j3ss.co)",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "port",
|
|
Value: "8080",
|
|
Usage: "port for server to run on",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "cert",
|
|
Usage: "path to ssl cert",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "key",
|
|
Usage: "path to ssl key",
|
|
},
|
|
cli.StringFlag{
|
|
Name: "interval",
|
|
Value: "5m",
|
|
Usage: "interval to generate new index.html's at",
|
|
},
|
|
}
|
|
app.Action = func(c *cli.Context) error {
|
|
auth, err := getAuthConfig(c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// create the registry client
|
|
r, err := registry.New(auth, c.GlobalBool("debug"), c.GlobalBool("skipverify"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// get the path to the static directory
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
staticDir := filepath.Join(wd, "static")
|
|
|
|
// create the initial index
|
|
if err := createStaticIndex(r, staticDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
// parse the duration
|
|
dur, err := time.ParseDuration(c.String("interval"))
|
|
if err != nil {
|
|
logrus.Fatalf("parsing %s as duration failed: %v", c.String("interval"), err)
|
|
}
|
|
ticker := time.NewTicker(dur)
|
|
|
|
go func() {
|
|
// create more indexes every X minutes based off interval
|
|
for range ticker.C {
|
|
if !updating {
|
|
if err := createStaticIndex(r, staticDir); err != nil {
|
|
logrus.Warnf("creating static index failed: %v", err)
|
|
updating = false
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
// create mux server
|
|
mux := http.NewServeMux()
|
|
|
|
// static files handler
|
|
staticHandler := http.FileServer(http.Dir(staticDir))
|
|
mux.Handle("/", staticHandler)
|
|
// TODO: add handler for individual repos
|
|
|
|
// set up the server
|
|
port := c.String("port")
|
|
server := &http.Server{
|
|
Addr: ":" + port,
|
|
Handler: mux,
|
|
}
|
|
logrus.Infof("Starting server on port %q", port)
|
|
if c.String("cert") != "" && c.String("key") != "" {
|
|
logrus.Fatal(server.ListenAndServeTLS(c.String("cert"), c.String("key")))
|
|
} else {
|
|
logrus.Fatal(server.ListenAndServe())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
app.Run(os.Args)
|
|
}
|
|
|
|
func getAuthConfig(c *cli.Context) (types.AuthConfig, error) {
|
|
if c.GlobalString("username") != "" && c.GlobalString("password") != "" && c.GlobalString("registry") != "" {
|
|
return types.AuthConfig{
|
|
Username: c.GlobalString("username"),
|
|
Password: c.GlobalString("password"),
|
|
ServerAddress: c.GlobalString("registry"),
|
|
}, nil
|
|
}
|
|
|
|
dcfg, err := cliconfig.Load(cliconfig.ConfigDir())
|
|
if err != nil {
|
|
return types.AuthConfig{}, fmt.Errorf("Loading config file failed: %v", err)
|
|
}
|
|
|
|
// return error early if there are no auths saved
|
|
if !dcfg.ContainsAuth() {
|
|
if c.GlobalString("registry") != "" {
|
|
return types.AuthConfig{
|
|
ServerAddress: c.GlobalString("registry"),
|
|
}, nil
|
|
}
|
|
return types.AuthConfig{}, fmt.Errorf("No auth was present in %s, please pass a registry, username, and password", cliconfig.ConfigDir())
|
|
}
|
|
|
|
// if they passed a specific registry, return those creds _if_ they exist
|
|
if c.GlobalString("registry") != "" {
|
|
if creds, ok := dcfg.AuthConfigs[c.GlobalString("registry")]; ok {
|
|
return creds, nil
|
|
}
|
|
return types.AuthConfig{}, fmt.Errorf("No authentication credentials exist for %s", c.GlobalString("registry"))
|
|
}
|
|
|
|
// set the auth config as the registryURL, username and Password
|
|
for _, creds := range dcfg.AuthConfigs {
|
|
return creds, nil
|
|
}
|
|
|
|
return types.AuthConfig{}, fmt.Errorf("Could not find any authentication credentials")
|
|
}
|
|
|
|
func getRepoAndRef(c *cli.Context) (repo, ref string, err error) {
|
|
if len(c.Args()) < 1 {
|
|
return "", "", errors.New("pass the name of the repository")
|
|
}
|
|
|
|
arg := c.Args()[0]
|
|
parts := []string{}
|
|
if strings.Contains(arg, "@") {
|
|
parts = strings.Split(c.Args()[0], "@")
|
|
} else if strings.Contains(arg, ":") {
|
|
parts = strings.Split(c.Args()[0], ":")
|
|
} else {
|
|
parts = []string{arg}
|
|
}
|
|
|
|
repo = parts[0]
|
|
ref = "latest"
|
|
if len(parts) > 1 {
|
|
ref = parts[1]
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
type data struct {
|
|
RegistryURL string
|
|
LastUpdated string
|
|
Repos []repository
|
|
}
|
|
|
|
type repository struct {
|
|
Name string
|
|
Tag string
|
|
RepoURI string
|
|
CreatedDate string
|
|
}
|
|
|
|
type v1Compatibility struct {
|
|
ID string `json:"id"`
|
|
Created time.Time `json:"created"`
|
|
}
|
|
|
|
func createStaticIndex(r *registry.Registry, staticDir string) error {
|
|
updating = true
|
|
logrus.Info("fetching catalog")
|
|
repoList, err := r.Catalog("")
|
|
if err != nil {
|
|
return fmt.Errorf("getting catalog failed: %v", err)
|
|
}
|
|
|
|
logrus.Info("fetching tags")
|
|
var repos []repository
|
|
for _, repo := range repoList {
|
|
// get the tags
|
|
tags, err := r.Tags(repo)
|
|
if err != nil {
|
|
return fmt.Errorf("getting tags for %s failed: %v", repo, err)
|
|
}
|
|
for _, tag := range tags {
|
|
// get the manifest
|
|
|
|
manifest, err := r.Manifest(repo, tag)
|
|
if err != nil {
|
|
return fmt.Errorf("getting tags for %s:%s failed: %v", repo, tag, err)
|
|
}
|
|
|
|
var createdDate string
|
|
if m1, ok := manifest.(schema1.SignedManifest); ok {
|
|
history := m1.History
|
|
for _, h := range history {
|
|
var comp v1Compatibility
|
|
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
|
|
return fmt.Errorf("unmarshal v1compatibility failed: %v", err)
|
|
}
|
|
createdDate = humanize.Time(comp.Created)
|
|
break
|
|
}
|
|
}
|
|
|
|
repoURI := fmt.Sprintf("%s/%s", r.Domain, repo)
|
|
if tag != "latest" {
|
|
repoURI += ":" + tag
|
|
}
|
|
|
|
repos = append(repos, repository{
|
|
Name: repo,
|
|
Tag: tag,
|
|
RepoURI: repoURI,
|
|
CreatedDate: createdDate,
|
|
})
|
|
}
|
|
}
|
|
|
|
// create temporoary file to save template to
|
|
logrus.Info("creating temporary file for template")
|
|
f, err := ioutil.TempFile("", "reg-server")
|
|
if err != nil {
|
|
return fmt.Errorf("creating temp file failed: %v", err)
|
|
}
|
|
defer f.Close()
|
|
defer os.Remove(f.Name())
|
|
|
|
// parse & execute the template
|
|
logrus.Info("parsing and executing the template")
|
|
templateDir := filepath.Join(staticDir, "../templates")
|
|
lp := filepath.Join(templateDir, "layout.html")
|
|
|
|
d := data{
|
|
RegistryURL: r.Domain,
|
|
Repos: repos,
|
|
LastUpdated: time.Now().Local().Format(time.RFC1123),
|
|
}
|
|
tmpl := template.Must(template.New("").ParseFiles(lp))
|
|
if err := tmpl.ExecuteTemplate(f, "layout", d); err != nil {
|
|
return fmt.Errorf("execute template failed: %v", err)
|
|
}
|
|
f.Close()
|
|
|
|
index := filepath.Join(staticDir, "index.html")
|
|
logrus.Infof("renaming the temporary file %s to %s", f.Name(), index)
|
|
if err := os.Rename(f.Name(), index); err != nil {
|
|
return fmt.Errorf("renaming result from %s to %s failed: %v", f.Name(), index, err)
|
|
}
|
|
updating = false
|
|
return nil
|
|
}
|