github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/spyglass.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package spyglass creates views for Prow job artifacts. 18 package spyglass 19 20 import ( 21 "fmt" 22 "path" 23 "sort" 24 "strings" 25 26 "cloud.google.com/go/storage" 27 "github.com/sirupsen/logrus" 28 29 "k8s.io/test-infra/prow/config" 30 "k8s.io/test-infra/prow/deck/jobs" 31 "k8s.io/test-infra/prow/kube" 32 "k8s.io/test-infra/prow/spyglass/lenses" 33 ) 34 35 // Key types specify the way Spyglass will fetch artifact handles 36 const ( 37 gcsKeyType = "gcs" 38 prowKeyType = "prowjob" 39 ) 40 41 // Spyglass records which sets of artifacts need views for a Prow job. The metaphor 42 // can be understood as follows: A spyglass receives light from a source through 43 // an eyepiece, which has a lens that ultimately presents a view of the light source 44 // to the observer. Spyglass receives light (artifacts) via a 45 // source (src) through the eyepiece (Eyepiece) and presents the view (what you see 46 // in your browser) via a lens (Lens). 47 type Spyglass struct { 48 // JobAgent contains information about the current jobs in deck 49 JobAgent *jobs.JobAgent 50 51 // ConfigAgent contains information about the prow configuration 52 ConfigAgent configAgent 53 54 *GCSArtifactFetcher 55 *PodLogArtifactFetcher 56 } 57 58 // This interface matches config.Agent and exists for the purpose of unit tests. 59 type configAgent interface { 60 Config() *config.Config 61 } 62 63 // LensRequest holds data sent by a view 64 type LensRequest struct { 65 Source string `json:"src"` 66 Artifacts []string `json:"artifacts"` 67 } 68 69 // New constructs a Spyglass object from a JobAgent, a config.Agent, and a storage Client. 70 func New(ja *jobs.JobAgent, conf configAgent, c *storage.Client) *Spyglass { 71 return &Spyglass{ 72 JobAgent: ja, 73 ConfigAgent: conf, 74 PodLogArtifactFetcher: NewPodLogArtifactFetcher(ja), 75 GCSArtifactFetcher: NewGCSArtifactFetcher(c), 76 } 77 } 78 79 // Lenses gets all views of all artifact files matching each regexp with a registered lens 80 func (s *Spyglass) Lenses(matchCache map[string][]string) []lenses.Lens { 81 ls := []lenses.Lens{} 82 for lensName, matches := range matchCache { 83 if len(matches) == 0 { 84 continue 85 } 86 lens, err := lenses.GetLens(lensName) 87 if err != nil { 88 logrus.WithField("lensName", lens).WithError(err).Error("Could not find artifact lens") 89 } else { 90 ls = append(ls, lens) 91 } 92 } 93 // Make sure lenses are rendered in order by ascending priority 94 sort.Slice(ls, func(i, j int) bool { 95 iname := ls[i].Name() 96 jname := ls[j].Name() 97 pi := ls[i].Priority() 98 pj := ls[j].Priority() 99 if pi == pj { 100 return iname < jname 101 } 102 return pi < pj 103 }) 104 return ls 105 } 106 107 // JobPath returns a link to the GCS directory for the job specified in src 108 func (s *Spyglass) JobPath(src string) (string, error) { 109 src = strings.TrimSuffix(src, "/") 110 keyType, key, err := splitSrc(src) 111 if err != nil { 112 return "", fmt.Errorf("error parsing src: %v", src) 113 } 114 split := strings.Split(key, "/") 115 switch keyType { 116 case gcsKeyType: 117 if len(split) < 4 { 118 return "", fmt.Errorf("invalid key %s: expected <bucket-name>/<log-type>/.../<job-name>/<build-id>", key) 119 } 120 // see https://github.com/kubernetes/test-infra/tree/master/gubernator 121 bktName := split[0] 122 logType := split[1] 123 jobName := split[len(split)-2] 124 if logType == "logs" { 125 return path.Dir(key), nil 126 } else if logType == "pr-logs" { 127 return path.Join(bktName, "pr-logs/directory", jobName), nil 128 } 129 return "", fmt.Errorf("unrecognized GCS key: %s", key) 130 case prowKeyType: 131 if len(split) < 2 { 132 return "", fmt.Errorf("invalid key %s: expected <job-name>/<build-id>", key) 133 } 134 jobName := split[0] 135 buildID := split[1] 136 job, err := s.jobAgent.GetProwJob(jobName, buildID) 137 if err != nil { 138 return "", fmt.Errorf("failed to get prow job from src %q: %v", key, err) 139 } 140 if job.Spec.DecorationConfig == nil { 141 return "", fmt.Errorf("failed to locate GCS upload bucket for %s: job is undecorated", jobName) 142 } 143 if job.Spec.DecorationConfig.GCSConfiguration == nil { 144 return "", fmt.Errorf("failed to locate GCS upload bucket for %s: missing GCS configuration", jobName) 145 } 146 bktName := job.Spec.DecorationConfig.GCSConfiguration.Bucket 147 if job.Spec.Type == kube.PresubmitJob { 148 return path.Join(bktName, "pr-logs/directory", jobName), nil 149 } 150 return path.Join(bktName, "logs", jobName), nil 151 default: 152 return "", fmt.Errorf("unrecognized key type for src: %v", src) 153 } 154 }