reg/handlers.go
Jess Frazelle 298ecf1510
move server under main command closes #106
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
2018-07-15 19:05:12 -04:00

263 lines
6.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/genuinetools/reg/clair"
"github.com/genuinetools/reg/registry"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type registryController struct {
reg *registry.Registry
cl *clair.Clair
l sync.Mutex
tmpl *template.Template
}
type v1Compatibility struct {
ID string `json:"id"`
Created time.Time `json:"created"`
}
// A Repository holds data after a vulnerability scan of a single repo
type Repository struct {
Name string `json:"name"`
Tag string `json:"tag"`
Created time.Time `json:"created"`
URI string `json:"uri"`
VulnerabilityReport clair.VulnerabilityReport `json:"vulnerability"`
}
// A AnalysisResult holds all vulnerabilities of a scan
type AnalysisResult struct {
Repositories []Repository `json:"repositories"`
RegistryDomain string `json:"registryDomain"`
Name string `json:"name"`
LastUpdated string `json:"lastUpdated"`
}
func (rc *registryController) repositories(staticDir string) error {
rc.l.Lock()
defer rc.l.Unlock()
logrus.Info("fetching catalog")
result := AnalysisResult{
RegistryDomain: rc.reg.Domain,
LastUpdated: time.Now().Local().Format(time.RFC1123),
}
repoList, err := rc.reg.Catalog("")
if err != nil {
return fmt.Errorf("getting catalog failed: %v", err)
}
for _, repo := range repoList {
repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
r := Repository{
Name: repo,
URI: repoURI,
}
result.Repositories = append(result.Repositories, r)
}
// Parse & execute the template.
logrus.Info("executing the template repositories")
path := filepath.Join(staticDir, "index.html")
if err := os.MkdirAll(filepath.Dir(path), 0644); err != nil {
return err
}
logrus.Debugf("creating/opening file %s", path)
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
if err := rc.tmpl.ExecuteTemplate(f, "repositories", result); err != nil {
f.Close()
return fmt.Errorf("execute template repositories failed: %v", err)
}
return nil
}
func (rc *registryController) tagsHandler(w http.ResponseWriter, r *http.Request) {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Info("fetching tags")
vars := mux.Vars(r)
repo, err := url.QueryUnescape(vars["repo"])
if err != nil || repo == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty repo")
return
}
tags, err := rc.reg.Tags(repo)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Errorf("getting tags for %s failed: %v", repo, err)
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "No tags found")
return
}
// Error out if there are no tags / images (the above err != nil does not error out when nothing has been found)
if len(tags) == 0 {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "No tags found")
return
}
result := AnalysisResult{
RegistryDomain: rc.reg.Domain,
LastUpdated: time.Now().Local().Format(time.RFC1123),
Name: repo,
}
for _, tag := range tags {
// get the manifest
m1, err := rc.reg.ManifestV1(repo, tag)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
"repo": repo,
"tag": tag,
}).Errorf("getting v1 manifest for %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Manifest not found")
return
}
var createdDate time.Time
for _, h := range m1.History {
var comp v1Compatibility
if err := json.Unmarshal([]byte(h.V1Compatibility), &comp); err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Errorf("unmarshal v1 manifest for %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
createdDate = comp.Created
break
}
repoURI := fmt.Sprintf("%s/%s", rc.reg.Domain, repo)
if tag != "latest" {
repoURI += ":" + tag
}
rp := Repository{
Name: repo,
Tag: tag,
URI: repoURI,
Created: createdDate,
}
result.Repositories = append(result.Repositories, rp)
}
if err := rc.tmpl.ExecuteTemplate(w, "tags", result); err != nil {
logrus.WithFields(logrus.Fields{
"func": "tags",
"URL": r.URL,
"method": r.Method,
}).Errorf("template rendering failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (rc *registryController) vulnerabilitiesHandler(w http.ResponseWriter, r *http.Request) {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Info("fetching vulnerabilities")
vars := mux.Vars(r)
repo, err := url.QueryUnescape(vars["repo"])
tag := vars["tag"]
if err != nil || repo == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty repo")
return
}
if tag == "" {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Empty tag")
return
}
result := clair.VulnerabilityReport{}
if rc.cl != nil {
result, err = rc.cl.Vulnerabilities(rc.reg, repo, tag)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("vulnerability scanning for %s:%s failed: %v", repo, tag, err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
if strings.HasSuffix(r.URL.String(), ".json") {
js, err := json.Marshal(result)
if err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("json marshal failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(js)
return
}
if err := rc.tmpl.ExecuteTemplate(w, "vulns", result); err != nil {
logrus.WithFields(logrus.Fields{
"func": "vulnerabilities",
"URL": r.URL,
"method": r.Method,
}).Errorf("template rendering failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}