Skip to content

Commit

Permalink
Rework spyglass lenses to be isolated properly.
Browse files Browse the repository at this point in the history
  • Loading branch information
Katharine committed Nov 27, 2018
1 parent 444a938 commit 33d0bbc
Show file tree
Hide file tree
Showing 45 changed files with 1,502 additions and 1,110 deletions.
22 changes: 18 additions & 4 deletions prow/cmd/deck/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,28 @@ container_image(

prow_image(
name = "image",
base = ":asset-base",
base = ":spyglass-lenses",
visibility = ["//visibility:public"],
)

container_image(
name = "spyglass-lenses",
base = ":asset-base",
data_path = "/prow/spyglass/lenses",
directory = "/lenses",
files = [
"//prow/spyglass/lenses:resources",
"//prow/spyglass/lenses:templates",
],
)

go_binary(
name = "deck",
data = [
":templates",
"//prow/cmd/deck/static",
"//prow/spyglass/lenses:resources",
"//prow/spyglass/lenses:templates",
],
embed = [":go_default_library"],
pure = "on",
Expand Down Expand Up @@ -79,9 +92,10 @@ go_library(
"//prow/pluginhelp:go_default_library",
"//prow/prstatus:go_default_library",
"//prow/spyglass:go_default_library",
"//prow/spyglass/viewers/buildlog:go_default_library",
"//prow/spyglass/viewers/junit:go_default_library",
"//prow/spyglass/viewers/metadata:go_default_library",
"//prow/spyglass/lenses:go_default_library",
"//prow/spyglass/lenses/buildlog:go_default_library",
"//prow/spyglass/lenses/junit:go_default_library",
"//prow/spyglass/lenses/metadata:go_default_library",
"//prow/tide:go_default_library",
"//vendor/cloud.google.com/go/storage:go_default_library",
"//vendor/github.com/NYTimes/gziphandler:go_default_library",
Expand Down
136 changes: 84 additions & 52 deletions prow/cmd/deck/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ import (

// Import standard spyglass viewers

_ "k8s.io/test-infra/prow/spyglass/viewers/buildlog"
_ "k8s.io/test-infra/prow/spyglass/viewers/junit"
_ "k8s.io/test-infra/prow/spyglass/viewers/metadata"
"k8s.io/test-infra/prow/spyglass/lenses"
_ "k8s.io/test-infra/prow/spyglass/lenses/buildlog"
_ "k8s.io/test-infra/prow/spyglass/lenses/junit"
_ "k8s.io/test-infra/prow/spyglass/lenses/metadata"
)

type options struct {
Expand All @@ -73,6 +74,7 @@ type options struct {
staticFilesLocation string
templateFilesLocation string
spyglass bool
spyglassFilesLocation string
gcsCredentialsFile string
}

Expand Down Expand Up @@ -107,13 +109,18 @@ func gatherOptions() options {
flag.BoolVar(&o.hiddenOnly, "hidden-only", false, "Show only hidden jobs. Useful for serving hidden jobs behind an oauth proxy.")
flag.StringVar(&o.pregeneratedData, "pregenerated-data", "", "Use API output from another prow instance. Used by the prow/cmd/deck/runlocal script")
flag.BoolVar(&o.spyglass, "spyglass", false, "Use Prow built-in job viewing instead of Gubernator")
flag.StringVar(&o.spyglassFilesLocation, "spyglass-files-location", "/lenses", "Location of the static files for spyglass.")
flag.StringVar(&o.staticFilesLocation, "static-files-location", "/static", "Path to the static files")
flag.StringVar(&o.templateFilesLocation, "template-files-location", "/template", "Path to the template files")
flag.StringVar(&o.gcsCredentialsFile, "gcs-credentials-file", "", "Path to the GCS credentials file")
flag.Parse()
return o
}

func staticHandlerFromDir(dir string) http.Handler {
return gziphandler.GzipHandler(handleCached(http.FileServer(http.Dir(dir))))
}

func main() {
o := gatherOptions()
if err := o.Validate(); err != nil {
Expand All @@ -126,10 +133,6 @@ func main() {

mux := http.NewServeMux()

staticHandlerFromDir := func(dir string) http.Handler {
return gziphandler.GzipHandler(handleCached(http.FileServer(http.Dir(dir))))
}

// setup config agent, pod log clients etc.
configAgent := &config.Agent{}
if err := configAgent.Start(o.configPath, o.jobConfigPath); err != nil {
Expand Down Expand Up @@ -351,7 +354,8 @@ func initSpyglass(configAgent *config.Agent, o options, mux *http.ServeMux, ja *
}
sg := spyglass.New(ja, configAgent, c)

mux.Handle("/view/render", gziphandler.GzipHandler(handleArtifactView(sg, configAgent)))
mux.Handle("/spyglass/static/", http.StripPrefix("/spyglass/static", staticHandlerFromDir(o.spyglassFilesLocation)))
mux.Handle("/spyglass/lens/", gziphandler.GzipHandler(http.StripPrefix("/spyglass/lens/", handleArtifactView(o, sg, configAgent))))
mux.Handle("/view/", gziphandler.GzipHandler(handleRequestJobViews(sg, configAgent, o)))
mux.Handle("/job-history/", gziphandler.GzipHandler(handleJobHistory(o, configAgent, c)))
}
Expand Down Expand Up @@ -537,7 +541,11 @@ func renderSpyglass(sg *spyglass.Spyglass, ca *config.Agent, src string, o optio
}
}

lenses := sg.Views(viewerCache)
ls := sg.Lenses(viewerCache)
lensNames := []string{}
for _, l := range ls {
lensNames = append(lensNames, l.Name())
}

jobHistLink := ""
jobPath, err := sg.JobPath(src)
Expand All @@ -547,17 +555,19 @@ func renderSpyglass(sg *spyglass.Spyglass, ca *config.Agent, src string, o optio
logrus.Infof("job history link: %s", jobHistLink)

var viewBuf bytes.Buffer
type ViewsTemplate struct {
Views []spyglass.Lens
Source string
ViewerCache map[string][]string
JobHistLink string
}
vTmpl := ViewsTemplate{
Views: lenses,
Source: src,
ViewerCache: viewerCache,
JobHistLink: jobHistLink,
type lensesTemplate struct {
Lenses []lenses.Lens
LensNames []string
Source string
LensArtifacts map[string][]string
JobHistLink string
}
lTmpl := lensesTemplate{
Lenses: ls,
LensNames: lensNames,
Source: src,
LensArtifacts: viewerCache,
JobHistLink: jobHistLink,
}
t := template.New("spyglass.html")

Expand All @@ -569,7 +579,7 @@ func renderSpyglass(sg *spyglass.Spyglass, ca *config.Agent, src string, o optio
return "", fmt.Errorf("error parsing template: %v", err)
}

if err = t.Execute(&viewBuf, vTmpl); err != nil {
if err = t.Execute(&viewBuf, lTmpl); err != nil {
return "", fmt.Errorf("error rendering template: %v", err)
}
renderElapsed := time.Since(renderStart)
Expand All @@ -585,55 +595,77 @@ func renderSpyglass(sg *spyglass.Spyglass, ca *config.Agent, src string, o optio
// Query params:
// - name: required, specifies the name of the viewer to load
// - src: required, specifies the job source from which to fetch artifacts
func handleArtifactView(sg *spyglass.Spyglass, ca *config.Agent) http.HandlerFunc {
func handleArtifactView(o options, sg *spyglass.Spyglass, ca *config.Agent) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
//start := time.Now()
setHeadersNoCaching(w)
w.Header().Set("Content-Type", "application/json")
name := r.URL.Query().Get("name")
src := r.URL.Query().Get("src")
if name == "" {
http.Error(w, "missing name query parameter", http.StatusBadRequest)
return
}
if src == "" {
http.Error(w, "missing src query parameter", http.StatusBadRequest)
pathSegments := strings.Split(r.URL.Path, "/")
if len(pathSegments) != 2 {
http.NotFound(w, r)
return
}
lensName := pathSegments[0]
resource := pathSegments[1]

body, err := ioutil.ReadAll(r.Body)
lens, err := lenses.GetLens(lensName)
if err != nil {
http.Error(w, "failed to read body", http.StatusBadRequest)
http.Error(w, fmt.Sprintf("No such template: %s (%v)", lensName, err), http.StatusNotFound)
return
}

viewReq := &spyglass.ViewRequest{}
err = json.Unmarshal(body, viewReq)
if err != nil {
http.Error(w, "failed to unmarshal request body", http.StatusBadRequest)
return
}
lensResourcesDir := lenses.ResourceDirForLens(o.spyglassFilesLocation, lens.Name())

lens, err := sg.Refresh(src, "", ca.Config().Deck.Spyglass.SizeLimit, viewReq)
reqString := r.URL.Query().Get("req")
var request spyglass.LensRequest
err = json.Unmarshal([]byte(reqString), &request)
if err != nil {
logrus.WithError(err).Error("failed to refresh view")
http.Error(w, "failed to refresh view", http.StatusInternalServerError)
http.Error(w, fmt.Sprintf("Failed to parse request: %v", err), http.StatusBadRequest)
return
}

pd, err := json.Marshal(lens)
artifacts, err := sg.FetchArtifacts(request.Source, "", ca.Config().Deck.Spyglass.SizeLimit, request.Artifacts)
if err != nil {
logrus.WithError(err).Error("Error marshaling payload.")
http.Error(w, "failed to refresh view", http.StatusInternalServerError)
http.Error(w, fmt.Sprintf("Failed to retrieve expected artifacts: %v", err), http.StatusInternalServerError)
return
}

fmt.Fprint(w, string(pd))
elapsed := time.Since(start)
logrus.WithFields(logrus.Fields{
"duration": elapsed,
"viewer": name,
}).Infof("Refreshed view.") //TODO (paulangton): move these load times next to the title of the viewer expose in Prometheus metrics
if resource == "iframe" {
t, err := template.ParseFiles(path.Join(o.templateFilesLocation, "spyglass-lens.html"))
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load template: %v", err), http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "text/html; encoding=utf-8")
t.Execute(w, struct {
Title string
BaseURL string
Head template.HTML
Body template.HTML
}{
lens.Title(),
"/spyglass/static/" + lensName + "/",
template.HTML(lens.Header(artifacts, lensResourcesDir)),
template.HTML(lens.Body(artifacts, lensResourcesDir, "")),
})
} else if resource == "rerender" {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to read body: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; encoding=utf-8")
w.Write([]byte(lens.Body(artifacts, lensResourcesDir, string(data))))
} else if resource == "callback" {
data, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to read body: %v", err), http.StatusInternalServerError)
return
}
w.Write([]byte(lens.Callback(artifacts, lensResourcesDir, string(data))))
} else {
http.NotFound(w, r)
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion prow/cmd/deck/runlocal
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ curl "$HOST/data.js?var=allBuilds" > data.js
curl "$HOST/tide.js?var=tideData" > tide.js
curl "$HOST/plugin-help.js?var=allHelp" > plugin-help.js
curl "$HOST/pr-data.js" > pr-data.js
bazel run //prow/cmd/deck:deck -- --pregenerated-data=${DIR}/localdata --static-files-location=./prow/cmd/deck/static --template-files-location=./prow/cmd/deck/template --config-path ${DIR}/../../config.yaml --spyglass
bazel run //prow/cmd/deck:deck -- --pregenerated-data=${DIR}/localdata --static-files-location=./prow/cmd/deck/static --template-files-location=./prow/cmd/deck/template --spyglass-files-location=./prow/spyglass/lenses --config-path ${DIR}/../../config.yaml --spyglass
29 changes: 28 additions & 1 deletion prow/cmd/deck/static/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,40 @@ rollup_bundle(

ts_library(
name = "spyglass",
srcs = glob(["spyglass/*.ts"]),
srcs = ["spyglass/spyglass.ts"],
deps = [
":spyglass_common",
],
)

ts_library(
name = "spyglass_lens",
srcs = ["spyglass/lens.ts"],
deps = [
":spyglass_common",
],
)

ts_library(
name = "spyglass_common",
srcs = ["spyglass/common.ts"],
)

rollup_bundle(
name = "spyglass_bundle",
entry_point = "prow/cmd/deck/static/spyglass/spyglass",
deps = [
":spyglass",
":spyglass_common",
],
)

rollup_bundle(
name = "spyglass_lens_bundle",
entry_point = "prow/cmd/deck/static/spyglass/lens",
deps = [
":spyglass_common",
":spyglass_lens",
],
)

Expand All @@ -122,6 +148,7 @@ filegroup(
":pr_bundle",
":prow_bundle",
":spyglass_bundle",
":spyglass_lens_bundle",
":tide_bundle",
],
)
Expand Down
47 changes: 47 additions & 0 deletions prow/cmd/deck/static/spyglass/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export interface BaseMessage {
type: string;
}

function isBaseMessage(data: any): data is BaseMessage {
return typeof data.type === 'string';
}

export interface ContentUpdatedMessage extends BaseMessage{
type: 'contentUpdated';
height: number;
}

export interface RequestMessage extends BaseMessage {
type: 'request';
data: string;
}

export interface RequestPageMessage extends BaseMessage {
type: 'requestPage';
data: string;
}

export interface UpdatePageMessage extends BaseMessage {
type: 'updatePage';
data: string;
}

export interface Response extends BaseMessage {
type: 'response';
data: string;
}

export function isResponse(data: any): data is Response {
return isBaseMessage(data) && data.type === 'response';
}

export type Message = ContentUpdatedMessage | RequestMessage | RequestPageMessage | UpdatePageMessage | Response;

export interface TransitMessage {
id: number;
message: Message;
}

export function isTransitMessage(data: any): data is TransitMessage {
return typeof data.id === 'number' && data.message && typeof data.message.type === 'string';
}
Loading

0 comments on commit 33d0bbc

Please sign in to comment.