github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/cmd/deck/main.go (about) 1 /* 2 Copyright 2016 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 main 18 19 import ( 20 "bufio" 21 "bytes" 22 "encoding/json" 23 "flag" 24 "fmt" 25 "io" 26 "io/ioutil" 27 "net/http" 28 "net/url" 29 "regexp" 30 "strconv" 31 32 "github.com/NYTimes/gziphandler" 33 "github.com/ghodss/yaml" 34 "github.com/sirupsen/logrus" 35 36 "k8s.io/test-infra/prow/config" 37 "k8s.io/test-infra/prow/jenkins" 38 "k8s.io/test-infra/prow/kube" 39 "k8s.io/test-infra/prow/pjutil" 40 ) 41 42 var ( 43 configPath = flag.String("config-path", "/etc/config/config", "Path to config.yaml.") 44 buildCluster = flag.String("build-cluster", "", "Path to file containing a YAML-marshalled kube.Cluster object. If empty, uses the local cluster.") 45 46 jenkinsURL = flag.String("jenkins-url", "", "Jenkins URL") 47 jenkinsUserName = flag.String("jenkins-user", "jenkins-trigger", "Jenkins username") 48 jenkinsTokenFile = flag.String("jenkins-token-file", "", "Path to the file containing the Jenkins API token.") 49 jenkinsBearerTokenFile = flag.String("jenkins-bearer-token-file", "", "Path to the file containing the Jenkins API bearer token.") 50 ) 51 52 // Matches letters, numbers, hyphens, and underscores. 53 var objReg = regexp.MustCompile(`^[\w-]+$`) 54 55 func main() { 56 flag.Parse() 57 logrus.SetFormatter(&logrus.JSONFormatter{}) 58 59 configAgent := &config.Agent{} 60 if err := configAgent.Start(*configPath); err != nil { 61 logrus.WithError(err).Fatal("Error starting config agent.") 62 } 63 64 kc, err := kube.NewClientInCluster(configAgent.Config().ProwJobNamespace) 65 if err != nil { 66 logrus.WithError(err).Fatal("Error getting client.") 67 } 68 var pkc *kube.Client 69 if *buildCluster == "" { 70 pkc = kc.Namespace(configAgent.Config().PodNamespace) 71 } else { 72 pkc, err = kube.NewClientFromFile(*buildCluster, configAgent.Config().PodNamespace) 73 if err != nil { 74 logrus.WithError(err).Fatal("Error getting kube client to build cluster.") 75 } 76 } 77 78 ac := &jenkins.AuthConfig{} 79 var jc *jenkins.Client 80 if *jenkinsURL != "" { 81 if *jenkinsTokenFile != "" { 82 if token, err := loadToken(*jenkinsTokenFile); err != nil { 83 logrus.WithError(err).Fatalf("Could not read token file.") 84 } else { 85 ac.Basic = &jenkins.BasicAuthConfig{ 86 User: *jenkinsUserName, 87 Token: token, 88 } 89 } 90 } else if *jenkinsBearerTokenFile != "" { 91 if token, err := loadToken(*jenkinsBearerTokenFile); err != nil { 92 logrus.WithError(err).Fatalf("Could not read token file.") 93 } else { 94 ac.BearerToken = &jenkins.BearerTokenAuthConfig{ 95 Token: token, 96 } 97 } 98 } else { 99 logrus.Fatal("An auth token for basic or bearer token auth must be supplied.") 100 } 101 jc = jenkins.NewClient(*jenkinsURL, ac) 102 } 103 104 ja := &JobAgent{ 105 kc: kc, 106 pkc: pkc, 107 jc: jc, 108 } 109 ja.Start() 110 111 http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir("/static")))) 112 http.Handle("/data.js", gziphandler.GzipHandler(handleData(ja))) 113 http.Handle("/log", gziphandler.GzipHandler(handleLog(ja))) 114 http.Handle("/rerun", gziphandler.GzipHandler(handleRerun(kc))) 115 116 logrus.WithError(http.ListenAndServe(":8080", nil)).Fatal("ListenAndServe returned.") 117 } 118 119 func loadToken(file string) (string, error) { 120 raw, err := ioutil.ReadFile(file) 121 if err != nil { 122 return "", err 123 } 124 return string(bytes.TrimSpace(raw)), nil 125 } 126 127 func handleData(ja *JobAgent) http.HandlerFunc { 128 return func(w http.ResponseWriter, r *http.Request) { 129 w.Header().Set("Cache-Control", "no-cache") 130 jobs := ja.Jobs() 131 jd, err := json.Marshal(jobs) 132 if err != nil { 133 logrus.WithError(err).Error("Error marshaling jobs.") 134 jd = []byte("[]") 135 } 136 // If we have a "var" query, then write out "var value = {...};". 137 // Otherwise, just write out the JSON. 138 if v := r.URL.Query().Get("var"); v != "" { 139 fmt.Fprintf(w, "var %s = %s;", v, string(jd)) 140 } else { 141 fmt.Fprintf(w, string(jd)) 142 } 143 } 144 } 145 146 type logClient interface { 147 GetJobLog(job, id string) ([]byte, error) 148 // Add ability to stream logs with options enabled. This call is used to follow logs 149 // using kubernetes client API. All other options on the Kubernetes log api can 150 // also be enabled. 151 GetJobLogStream(job, id string, options map[string]string) (io.ReadCloser, error) 152 } 153 154 func httpChunking(log io.ReadCloser, w http.ResponseWriter) { 155 flusher, ok := w.(http.Flusher) 156 if !ok { 157 logrus.Warning("Error getting flusher.") 158 } 159 reader := bufio.NewReader(log) 160 for { 161 line, err := reader.ReadBytes('\n') 162 if err != nil { 163 // TODO(rmmh): The log stops streaming after 30s. 164 // This seems to be an apiserver limitation-- investigate? 165 // logrus.WithError(err).Error("chunk failed to read!") 166 break 167 } 168 w.Write(line) 169 if flusher != nil { 170 flusher.Flush() 171 } 172 } 173 } 174 175 func getOptions(values url.Values) map[string]string { 176 options := make(map[string]string) 177 for k, v := range values { 178 if k != "pod" && k != "job" && k != "id" { 179 options[k] = v[0] 180 } 181 } 182 return options 183 } 184 185 // TODO(spxtr): Cache, rate limit. 186 func handleLog(lc logClient) http.HandlerFunc { 187 return func(w http.ResponseWriter, r *http.Request) { 188 w.Header().Set("Cache-Control", "no-cache") 189 w.Header().Set("Access-Control-Allow-Origin", "*") 190 job := r.URL.Query().Get("job") 191 id := r.URL.Query().Get("id") 192 stream := r.URL.Query().Get("follow") 193 var logStreamRequested bool 194 if ok, _ := strconv.ParseBool(stream); ok { 195 // get http chunked responses to the client 196 w.Header().Set("Connection", "Keep-Alive") 197 w.Header().Set("Transfer-Encoding", "chunked") 198 logStreamRequested = true 199 } 200 if job != "" && id != "" { 201 if !objReg.MatchString(job) { 202 http.Error(w, "Invalid job query", http.StatusBadRequest) 203 return 204 } 205 if !objReg.MatchString(id) { 206 http.Error(w, "Invalid ID query", http.StatusBadRequest) 207 return 208 } 209 if !logStreamRequested { 210 log, err := lc.GetJobLog(job, id) 211 if err != nil { 212 http.Error(w, fmt.Sprintf("Log not found: %v", err), http.StatusNotFound) 213 logrus.WithError(err).Warning("Error returned.") 214 return 215 } 216 if _, err = w.Write(log); err != nil { 217 logrus.WithError(err).Warning("Error writing log.") 218 } 219 } else { 220 //run http chunking 221 options := getOptions(r.URL.Query()) 222 log, err := lc.GetJobLogStream(job, id, options) 223 if err != nil { 224 http.Error(w, fmt.Sprintf("Log stream caused: %v", err), http.StatusNotFound) 225 logrus.WithError(err).Warning("Error returned.") 226 return 227 } 228 httpChunking(log, w) 229 } 230 } else { 231 http.Error(w, "Missing job and ID query", http.StatusBadRequest) 232 return 233 } 234 } 235 } 236 237 type pjClient interface { 238 GetProwJob(string) (kube.ProwJob, error) 239 } 240 241 func handleRerun(kc pjClient) http.HandlerFunc { 242 return func(w http.ResponseWriter, r *http.Request) { 243 name := r.URL.Query().Get("prowjob") 244 if !objReg.MatchString(name) { 245 http.Error(w, "Invalid ProwJob query", http.StatusBadRequest) 246 return 247 } 248 pj, err := kc.GetProwJob(name) 249 if err != nil { 250 http.Error(w, fmt.Sprintf("ProwJob not found: %v", err), http.StatusNotFound) 251 logrus.WithError(err).Warning("Error returned.") 252 return 253 } 254 pjutil := pjutil.NewProwJob(pj.Spec) 255 b, err := yaml.Marshal(&pjutil) 256 if err != nil { 257 http.Error(w, fmt.Sprintf("Error marshaling: %v", err), http.StatusInternalServerError) 258 logrus.WithError(err).Error("Error marshaling jobs.") 259 return 260 } 261 if _, err := w.Write(b); err != nil { 262 logrus.WithError(err).Error("Error writing log.") 263 } 264 } 265 }