github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/spyglass/podlogartifact.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 18 19 import ( 20 "bytes" 21 "errors" 22 "fmt" 23 "io" 24 "net/url" 25 "strings" 26 27 "k8s.io/test-infra/prow/kube" 28 "k8s.io/test-infra/prow/spyglass/lenses" 29 ) 30 31 type jobAgent interface { 32 GetProwJob(job string, id string) (kube.ProwJob, error) 33 GetJobLog(job string, id string) ([]byte, error) 34 GetJobLogTail(job string, id string, n int64) ([]byte, error) 35 } 36 37 // PodLogArtifact holds data for reading from a specific pod log 38 type PodLogArtifact struct { 39 name string 40 buildID string 41 sizeLimit int64 42 jobAgent 43 } 44 45 var ( 46 errInsufficientJobInfo = errors.New("insufficient job information provided") 47 errInvalidSizeLimit = errors.New("sizeLimit must be a 64-bit integer greater than 0") 48 ) 49 50 // NewPodLogArtifact creates a new PodLogArtifact 51 func NewPodLogArtifact(jobName string, buildID string, sizeLimit int64, ja jobAgent) (*PodLogArtifact, error) { 52 if jobName == "" { 53 return nil, errInsufficientJobInfo 54 } 55 if buildID == "" { 56 return nil, errInsufficientJobInfo 57 } 58 if sizeLimit < 0 { 59 return nil, errInvalidSizeLimit 60 } 61 return &PodLogArtifact{ 62 name: jobName, 63 buildID: buildID, 64 sizeLimit: sizeLimit, 65 jobAgent: ja, 66 }, nil 67 } 68 69 // CanonicalLink returns a link to where pod logs are streamed 70 func (a *PodLogArtifact) CanonicalLink() string { 71 q := url.Values{ 72 "job": []string{a.name}, 73 "id": []string{a.buildID}, 74 } 75 u := url.URL{ 76 Path: "/log", 77 RawQuery: q.Encode(), 78 } 79 return u.String() 80 } 81 82 // JobPath gets the path within the job for the pod log. Always returns build-log.txt. 83 // This is because the pod log becomes the build log after the job artifact uploads 84 // are complete, which should be used instead of the pod log. 85 func (a *PodLogArtifact) JobPath() string { 86 return "build-log.txt" 87 } 88 89 // ReadAt implements reading a range of bytes from the pod logs endpoint 90 func (a *PodLogArtifact) ReadAt(p []byte, off int64) (n int, err error) { 91 logs, err := a.jobAgent.GetJobLog(a.name, a.buildID) 92 if err != nil { 93 return 0, fmt.Errorf("error getting pod log: %v", err) 94 } 95 r := bytes.NewReader(logs) 96 readBytes, err := r.ReadAt(p, off) 97 if err == io.EOF { 98 return readBytes, io.EOF 99 } 100 if err != nil { 101 return 0, fmt.Errorf("error reading pod logs: %v", err) 102 } 103 return readBytes, nil 104 } 105 106 // ReadAll reads all available pod logs, failing if they are too large 107 func (a *PodLogArtifact) ReadAll() ([]byte, error) { 108 size, err := a.Size() 109 if err != nil { 110 return nil, fmt.Errorf("error getting pod log size: %v", err) 111 } 112 if size > a.sizeLimit { 113 return nil, lenses.ErrFileTooLarge 114 } 115 logs, err := a.jobAgent.GetJobLog(a.name, a.buildID) 116 if err != nil { 117 return nil, fmt.Errorf("error getting pod log: %v", err) 118 } 119 return logs, nil 120 } 121 122 // ReadAtMost reads at most n bytes 123 func (a *PodLogArtifact) ReadAtMost(n int64) ([]byte, error) { 124 logs, err := a.jobAgent.GetJobLog(a.name, a.buildID) 125 if err != nil { 126 return nil, fmt.Errorf("error getting pod log: %v", err) 127 } 128 reader := bytes.NewReader(logs) 129 var byteCount int64 130 var p []byte 131 for byteCount < n { 132 b, err := reader.ReadByte() 133 if err == io.EOF { 134 return p, io.EOF 135 } 136 if err != nil { 137 return nil, fmt.Errorf("error reading pod log: %v", err) 138 } 139 p = append(p, b) 140 byteCount++ 141 } 142 return p, nil 143 } 144 145 // ReadTail reads the last n bytes of the pod log 146 func (a *PodLogArtifact) ReadTail(n int64) ([]byte, error) { 147 logs, err := a.jobAgent.GetJobLogTail(a.name, a.buildID, n) 148 if err != nil { 149 return nil, fmt.Errorf("error getting pod log tail: %v", err) 150 } 151 size := int64(len(logs)) 152 var off int64 153 if n > size { 154 off = 0 155 } else { 156 off = size - n 157 } 158 p := make([]byte, n) 159 readBytes, err := bytes.NewReader(logs).ReadAt(p, off) 160 if err != nil && err != io.EOF { 161 return nil, fmt.Errorf("error reading pod log tail: %v", err) 162 } 163 return p[:readBytes], nil 164 } 165 166 // Size gets the size of the pod log. Note: this function makes the same network call as reading the entire file. 167 func (a *PodLogArtifact) Size() (int64, error) { 168 logs, err := a.jobAgent.GetJobLog(a.name, a.buildID) 169 if err != nil { 170 return 0, fmt.Errorf("error getting size of pod log: %v", err) 171 } 172 return int64(len(logs)), nil 173 174 } 175 176 // isProwJobSource returns true if the provided string is a valid Prowjob source and false otherwise 177 func isProwJobSource(src string) bool { 178 return strings.HasPrefix(src, "prowjob/") 179 }