move server under main command closes #106

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-07-15 19:05:12 -04:00
parent f99f1ce934
commit 298ecf1510
No known key found for this signature in database
GPG key ID: 18F3685C0022BFF3
10 changed files with 215 additions and 313 deletions

View file

@ -6,6 +6,5 @@ linux/arm
linux/arm64
linux/amd64
linux/386
solaris/amd64
windows/amd64
windows/386

View file

@ -43,9 +43,6 @@ jobs:
- cross/reg-linux-386
- cross/reg-linux-386.md5
- cross/reg-linux-386.sha256
- cross/reg-solaris-amd64
- cross/reg-solaris-amd64.md5
- cross/reg-solaris-amd64.sha256
- cross/reg-windows-amd64
- cross/reg-windows-amd64.md5
- cross/reg-windows-amd64.sha256

View file

@ -28,5 +28,10 @@ FROM scratch
COPY --from=builder /usr/bin/reg /usr/bin/reg
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
COPY server/static /src/static
COPY server/templates /src/templates
WORKDIR /src
ENTRYPOINT [ "reg" ]
CMD [ "--help" ]

View file

@ -43,7 +43,7 @@ static: ## Builds a static executable
-tags "$(BUILDTAGS) static_build" \
${GO_LDFLAGS_STATIC} -o $(NAME) .
all: clean build fmt lint test staticcheck vet install build-server ## Runs a clean, build, fmt, lint, test, staticcheck, vet and install
all: clean build fmt lint test staticcheck vet install ## Runs a clean, build, fmt, lint, test, staticcheck, vet and install
.PHONY: fmt
fmt: ## Verifies all files have been `gofmt`ed
@ -207,20 +207,6 @@ snakeoil: ## Update snakeoil certs for testing
mv $(CURDIR)/key.pem $(CURDIR)/testutils/snakeoil/key.pem
mv $(CURDIR)/cert.pem $(CURDIR)/testutils/snakeoil/cert.pem
.PHONY: build-server
build-server: $(NAME)-server ## Builds a dynamic executable for reg-server
$(NAME)-server: $(wildcard */*.go) VERSION.txt
@echo "+ $@"
$(GO) build -tags "$(BUILDTAGS)" ${GO_LDFLAGS} -o $(NAME)-server ./server/...
.PHONY: static-server
static-server: ## Builds a static reg-server executable
@echo "+ $@"
CGO_ENABLED=0 $(GO) build \
-tags "$(BUILDTAGS) static_build" \
${GO_LDFLAGS_STATIC} -o $(NAME)-server ./server
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

View file

@ -3,11 +3,13 @@ package main
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/genuinetools/reg/clair"
@ -17,8 +19,10 @@ import (
)
type registryController struct {
reg *registry.Registry
cl *clair.Clair
reg *registry.Registry
cl *clair.Clair
l sync.Mutex
tmpl *template.Template
}
type v1Compatibility struct {
@ -44,7 +48,9 @@ type AnalysisResult struct {
}
func (rc *registryController) repositories(staticDir string) error {
updating = true
rc.l.Lock()
defer rc.l.Unlock()
logrus.Info("fetching catalog")
result := AnalysisResult{
@ -52,7 +58,7 @@ func (rc *registryController) repositories(staticDir string) error {
LastUpdated: time.Now().Local().Format(time.RFC1123),
}
repoList, err := r.Catalog("")
repoList, err := rc.reg.Catalog("")
if err != nil {
return fmt.Errorf("getting catalog failed: %v", err)
}
@ -67,7 +73,7 @@ func (rc *registryController) repositories(staticDir string) error {
result.Repositories = append(result.Repositories, r)
}
// parse & execute the template
// Parse & execute the template.
logrus.Info("executing the template repositories")
path := filepath.Join(staticDir, "index.html")
@ -81,12 +87,11 @@ func (rc *registryController) repositories(staticDir string) error {
}
defer f.Close()
if err := tmpl.ExecuteTemplate(f, "repositories", result); err != nil {
if err := rc.tmpl.ExecuteTemplate(f, "repositories", result); err != nil {
f.Close()
return fmt.Errorf("execute template repositories failed: %v", err)
}
updating = false
return nil
}
@ -179,7 +184,7 @@ func (rc *registryController) tagsHandler(w http.ResponseWriter, r *http.Request
result.Repositories = append(result.Repositories, rp)
}
if err := tmpl.ExecuteTemplate(w, "tags", result); err != nil {
if err := rc.tmpl.ExecuteTemplate(w, "tags", result); err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
@ -245,7 +250,7 @@ func (rc *registryController) vulnerabilitiesHandler(w http.ResponseWriter, r *h
return
}
if err := tmpl.ExecuteTemplate(w, "vulns", result); err != nil {
if err := rc.tmpl.ExecuteTemplate(w, "vulns", result); err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,

18
main.go
View file

@ -4,7 +4,10 @@ import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/genuinetools/pkg/cli"
@ -43,6 +46,7 @@ func main() {
&listCommand{},
&manifestCommand{},
&removeCommand{},
&serverCommand{},
&tagsCommand{},
&vulnsCommand{},
}
@ -69,6 +73,20 @@ func main() {
// Set the before function.
p.Before = func(ctx context.Context) error {
// On ^C, or SIGTERM handle exit.
signals := make(chan os.Signal, 0)
signal.Notify(signals, os.Interrupt)
signal.Notify(signals, syscall.SIGTERM)
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
go func() {
for sig := range signals {
cancel()
logrus.Infof("Received %s, exiting.", sig.String())
os.Exit(0)
}
}()
// Set the log level.
if debug {
logrus.SetLevel(logrus.DebugLevel)

176
server.go Normal file
View file

@ -0,0 +1,176 @@
package main
import (
"context"
"flag"
"fmt"
"html/template"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/genuinetools/reg/clair"
"github.com/gorilla/mux"
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/sirupsen/logrus"
)
const serverHelp = `Run a static UI server for a registry.`
func (cmd *serverCommand) Name() string { return "server" }
func (cmd *serverCommand) Args() string { return "[OPTIONS]" }
func (cmd *serverCommand) ShortHelp() string { return serverHelp }
func (cmd *serverCommand) LongHelp() string { return serverHelp }
func (cmd *serverCommand) Hidden() bool { return false }
func (cmd *serverCommand) Register(fs *flag.FlagSet) {
fs.DurationVar(&cmd.interval, "interval", time.Hour, "interval to generate new index.html's at")
fs.StringVar(&cmd.registryServer, "registry", "", "URL to the private registry (ex. r.j3ss.co)")
fs.StringVar(&cmd.registryServer, "r", "", "URL to the private registry (ex. r.j3ss.co)")
fs.StringVar(&cmd.clairServer, "clair", "", "url to clair instance")
fs.StringVar(&cmd.cert, "cert", "", "path to ssl cert")
fs.StringVar(&cmd.key, "key", "", "path to ssl key")
fs.StringVar(&cmd.port, "port", "8080", "port for server to run on")
fs.BoolVar(&cmd.once, "once", false, "generate an output once and then exit")
}
type serverCommand struct {
interval time.Duration
registryServer string
clairServer string
once bool
cert string
key string
port string
}
func (cmd *serverCommand) Run(ctx context.Context, args []string) error {
// Create the registry client.
r, err := createRegistryClient(cmd.registryServer)
if err != nil {
return err
}
// Create the registry controller for the handlers.
rc := registryController{
reg: r,
}
// Create a clair client if the user passed in a server address.
if len(cmd.clairServer) < 1 {
rc.cl, err = clair.New(cmd.clairServer, clair.Opt{
Insecure: insecure,
Debug: debug,
Timeout: timeout,
})
if err != nil {
return fmt.Errorf("creation of clair client at %s failed: %v", cmd.clairServer, err)
}
}
// Get the path to the static directory.
wd, err := os.Getwd()
if err != nil {
return err
}
staticDir := filepath.Join(wd, "static")
templateDir := filepath.Join(staticDir, "../templates")
// Make sure all the paths exist.
tmplPaths := []string{
staticDir,
filepath.Join(templateDir, "vulns.html"),
filepath.Join(templateDir, "repositories.html"),
filepath.Join(templateDir, "tags.html"),
}
for _, path := range tmplPaths {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("template %s not found", path)
}
}
funcMap := template.FuncMap{
"trim": func(s string) string {
return wordwrap.WrapString(s, 80)
},
"color": func(s string) string {
switch s = strings.ToLower(s); s {
case "high":
return "danger"
case "critical":
return "danger"
case "defcon1":
return "danger"
case "medium":
return "warning"
case "low":
return "info"
case "negligible":
return "info"
case "unknown":
return "default"
default:
return "default"
}
},
}
rc.tmpl = template.Must(template.New("").Funcs(funcMap).ParseGlob(templateDir + "/*.html"))
// Create the initial index.
logrus.Info("creating initial static index")
if err := rc.repositories(staticDir); err != nil {
return fmt.Errorf("creating index failed: %v", err)
}
if cmd.once {
logrus.Info("output generated, exiting...")
return nil
}
ticker := time.NewTicker(cmd.interval)
go func() {
// Create more indexes every X minutes based off interval.
for range ticker.C {
logrus.Info("creating timer based static index")
if err := rc.repositories(staticDir); err != nil {
logrus.Warnf("creating static index failed: %v", err)
}
}
}()
// Create mux server.
mux := mux.NewRouter()
mux.UseEncodedPath()
// Static files handler.
staticHandler := http.FileServer(http.Dir(staticDir))
mux.HandleFunc("/repo/{repo}/tags", rc.tagsHandler)
mux.HandleFunc("/repo/{repo}/tags/", rc.tagsHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns/", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns.json", rc.vulnerabilitiesHandler)
mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", staticHandler))
mux.Handle("/", staticHandler)
// Set up the server.
server := &http.Server{
Addr: ":" + cmd.port,
Handler: mux,
}
logrus.Infof("Starting server on port %q", cmd.port)
if len(cmd.cert) > 0 && len(cmd.key) > 0 {
return server.ListenAndServeTLS(cmd.cert, cmd.key)
}
return server.ListenAndServe()
}

View file

@ -1,34 +0,0 @@
FROM golang:alpine as builder
MAINTAINER Jessica Frazelle <jess@linux.com>
ENV PATH /go/bin:/usr/local/go/bin:$PATH
ENV GOPATH /go
RUN apk add --no-cache \
ca-certificates
RUN set -x \
&& apk add --no-cache --virtual .build-deps \
git \
gcc \
libc-dev \
libgcc \
&& go get -v github.com/genuinetools/reg \
&& cd /go/src/github.com/genuinetools/reg \
&& CGO_ENABLED=0 go build -a -tags netgo -ldflags '-extldflags "-static"' -o /usr/bin/reg-server ./server \
&& apk del .build-deps \
&& rm -rf /go \
&& echo "Build complete."
FROM scratch
COPY --from=builder /usr/bin/reg-server /usr/bin/reg-server
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
COPY static /src/static
COPY templates /src/templates
WORKDIR /src
ENTRYPOINT [ "reg-server" ]
CMD [ "--help" ]

View file

@ -1,250 +0,0 @@
package main
import (
"context"
"flag"
"html/template"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/genuinetools/pkg/cli"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/registry"
"github.com/genuinetools/reg/repoutils"
"github.com/genuinetools/reg/version"
"github.com/gorilla/mux"
wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/sirupsen/logrus"
)
var (
insecure bool
forceNonSSL bool
skipPing bool
interval time.Duration
timeout time.Duration
username string
password string
registryServer string
clairServer string
once bool
cert string
key string
port string
debug bool
updating bool
r *registry.Registry
cl *clair.Clair
tmpl *template.Template
)
func main() {
// Create a new cli program.
p := cli.NewProgram()
p.Name = "reg-server"
p.Description = "Docker registry v2 static UI server"
// Set the GitCommit and Version.
p.GitCommit = version.GITCOMMIT
p.Version = version.VERSION
// Setup the global flags.
p.FlagSet = flag.NewFlagSet("global", flag.ExitOnError)
p.FlagSet.BoolVar(&insecure, "insecure", false, "do not verify tls certificates")
p.FlagSet.BoolVar(&insecure, "k", false, "do not verify tls certificates")
p.FlagSet.BoolVar(&forceNonSSL, "force-non-ssl", false, "force allow use of non-ssl")
p.FlagSet.BoolVar(&forceNonSSL, "f", false, "force allow use of non-ssl")
p.FlagSet.BoolVar(&skipPing, "skip-ping", false, "skip pinging the registry while establishing connection")
p.FlagSet.DurationVar(&interval, "interval", time.Hour, "interval to generate new index.html's at")
p.FlagSet.DurationVar(&timeout, "timeout", time.Minute, "timeout for HTTP requests")
p.FlagSet.StringVar(&username, "username", "", "username for the registry")
p.FlagSet.StringVar(&username, "u", "", "username for the registry")
p.FlagSet.StringVar(&password, "password", "", "password for the registry")
p.FlagSet.StringVar(&password, "p", "", "password for the registry")
p.FlagSet.StringVar(&registryServer, "registry", "", "URL to the private registry (ex. r.j3ss.co)")
p.FlagSet.StringVar(&registryServer, "r", "", "URL to the private registry (ex. r.j3ss.co)")
p.FlagSet.StringVar(&clairServer, "clair", "", "url to clair instance")
p.FlagSet.StringVar(&cert, "cert", "", "path to ssl cert")
p.FlagSet.StringVar(&key, "key", "", "path to ssl key")
p.FlagSet.StringVar(&port, "port", "8080", "port for server to run on")
p.FlagSet.BoolVar(&once, "once", false, "generate an output once and then exit")
p.FlagSet.BoolVar(&debug, "d", false, "enable debug logging")
// Set the before function.
p.Before = func(ctx context.Context) error {
// Set the log level.
if debug {
logrus.SetLevel(logrus.DebugLevel)
}
return nil
}
// Set the main program action.
p.Action = func(ctx context.Context) error {
auth, err := repoutils.GetAuthConfig(username, password, registryServer)
if err != nil {
logrus.Fatal(err)
}
// Create the registry client.
r, err = registry.New(auth, registry.Opt{
Insecure: insecure,
Debug: debug,
SkipPing: skipPing,
Timeout: timeout,
})
if err != nil {
logrus.Fatal(err)
}
// create a clair instance if needed
if len(clairServer) < 1 {
cl, err = clair.New(clairServer, clair.Opt{
Insecure: insecure,
Debug: debug,
Timeout: timeout,
})
if err != nil {
logrus.Warnf("creation of clair failed: %v", err)
}
}
// get the path to the static directory
wd, err := os.Getwd()
if err != nil {
logrus.Fatal(err)
}
staticDir := filepath.Join(wd, "static")
// create the template
templateDir := filepath.Join(staticDir, "../templates")
// make sure all the templates exist
vulns := filepath.Join(templateDir, "vulns.html")
if _, err := os.Stat(vulns); os.IsNotExist(err) {
logrus.Fatalf("Template %s not found", vulns)
}
layout := filepath.Join(templateDir, "repositories.html")
if _, err := os.Stat(layout); os.IsNotExist(err) {
logrus.Fatalf("Template %s not found", layout)
}
tags := filepath.Join(templateDir, "tags.html")
if _, err := os.Stat(tags); os.IsNotExist(err) {
logrus.Fatalf("Template %s not found", tags)
}
funcMap := template.FuncMap{
"trim": func(s string) string {
return wordwrap.WrapString(s, 80)
},
"color": func(s string) string {
switch s = strings.ToLower(s); s {
case "high":
return "danger"
case "critical":
return "danger"
case "defcon1":
return "danger"
case "medium":
return "warning"
case "low":
return "info"
case "negligible":
return "info"
case "unknown":
return "default"
default:
return "default"
}
},
}
tmpl = template.Must(template.New("").Funcs(funcMap).ParseGlob(templateDir + "/*.html"))
rc := registryController{
reg: r,
cl: cl,
}
// create the initial index
logrus.Info("creating initial static index")
if err := rc.repositories(staticDir); err != nil {
logrus.Fatalf("Error creating index: %v", err)
}
if once {
logrus.Info("Output generated")
return nil
}
ticker := time.NewTicker(interval)
go func() {
// create more indexes every X minutes based off interval
for range ticker.C {
if !updating {
logrus.Info("creating timer based static index")
if err := rc.repositories(staticDir); err != nil {
logrus.Warnf("creating static index failed: %v", err)
updating = false
}
} else {
logrus.Warnf("skipping timer based static index update for %s", interval.String())
}
}
}()
// create mux server
mux := mux.NewRouter()
mux.UseEncodedPath()
// static files handler
staticHandler := http.FileServer(http.Dir(staticDir))
mux.HandleFunc("/repo/{repo}/tags", rc.tagsHandler)
mux.HandleFunc("/repo/{repo}/tags/", rc.tagsHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns/", rc.vulnerabilitiesHandler)
mux.HandleFunc("/repo/{repo}/tag/{tag}/vulns.json", rc.vulnerabilitiesHandler)
mux.PathPrefix("/static/").Handler(http.StripPrefix("/static/", staticHandler))
mux.Handle("/", staticHandler)
// set up the server
server := &http.Server{
Addr: ":" + port,
Handler: mux,
}
logrus.Infof("Starting server on port %q", port)
if len(cert) > 0 && len(key) > 0 {
logrus.Fatal(server.ListenAndServeTLS(cert, key))
} else {
logrus.Fatal(server.ListenAndServe())
}
return nil
}
// Run our program.
p.Run()
}

View file

@ -60,7 +60,7 @@ func (cmd *vulnsCommand) Run(ctx context.Context, args []string) error {
Insecure: insecure,
})
if err != nil {
return err
return fmt.Errorf("creation of clair client at %s failed: %v", cmd.clairServer, err)
}
// Get the vulnerability report.