| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package swarming | 5 package swarming |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "errors" |
| 8 "net/http" | 9 "net/http" |
| 9 "os" | 10 "os" |
| 10 | 11 |
| 11 "github.com/julienschmidt/httprouter" | |
| 12 "golang.org/x/net/context" | 12 "golang.org/x/net/context" |
| 13 "google.golang.org/api/googleapi" | 13 "google.golang.org/api/googleapi" |
| 14 | 14 |
| 15 » "github.com/luci/luci-go/milo/appengine/settings" | 15 » "github.com/luci/luci-go/milo/appengine/common" |
| 16 » "github.com/luci/luci-go/milo/common/miloerror" | 16 » "github.com/luci/luci-go/server/router" |
| 17 "github.com/luci/luci-go/server/templates" | 17 "github.com/luci/luci-go/server/templates" |
| 18 ) | 18 ) |
| 19 | 19 |
| 20 const ( | 20 const ( |
| 21 defaultSwarmingServer = "chromium-swarm.appspot.com" | 21 defaultSwarmingServer = "chromium-swarm.appspot.com" |
| 22 defaultSwarmingDevServer = "chromium-swarm-dev.appspot.com" | 22 defaultSwarmingDevServer = "chromium-swarm-dev.appspot.com" |
| 23 ) | 23 ) |
| 24 | 24 |
| 25 func getSwarmingHost(r *http.Request) string { | 25 func getSwarmingHost(r *http.Request) string { |
| 26 server := r.FormValue("server") | 26 server := r.FormValue("server") |
| 27 switch server { | 27 switch server { |
| 28 case "": | 28 case "": |
| 29 return defaultSwarmingServer | 29 return defaultSwarmingServer |
| 30 case "dev": | 30 case "dev": |
| 31 return defaultSwarmingDevServer | 31 return defaultSwarmingDevServer |
| 32 default: | 32 default: |
| 33 return server | 33 return server |
| 34 } | 34 } |
| 35 } | 35 } |
| 36 | 36 |
| 37 var errUnrecognizedHost = errors.New("Unregistered Swarming Host") |
| 38 |
| 37 func getSwarmingService(c context.Context, host string) (swarmingService, error)
{ | 39 func getSwarmingService(c context.Context, host string) (swarmingService, error)
{ |
| 38 switch host { | 40 switch host { |
| 39 // TODO(hinoka): configure this mapping in luci-config | 41 // TODO(hinoka): configure this mapping in luci-config |
| 40 case defaultSwarmingServer, defaultSwarmingDevServer, | 42 case defaultSwarmingServer, defaultSwarmingDevServer, |
| 41 "cast-swarming.appspot.com", | 43 "cast-swarming.appspot.com", |
| 42 "touch-swarming.appspot.com": | 44 "touch-swarming.appspot.com": |
| 43 return newProdService(c, host) | 45 return newProdService(c, host) |
| 44 | 46 |
| 45 default: | 47 default: |
| 46 » » return nil, &miloerror.Error{ | 48 » » return nil, errUnrecognizedHost |
| 47 » » » Message: "unregistered Swarming host", | |
| 48 » » » Code: http.StatusNotFound, | |
| 49 » » } | |
| 50 } | 49 } |
| 51 } | 50 } |
| 52 | 51 |
| 53 // Log is for fetching logs from swarming. | |
| 54 type Log struct{} | |
| 55 | |
| 56 // Build is for deciphering recipe builds from swarming based off of logs. | 52 // Build is for deciphering recipe builds from swarming based off of logs. |
| 57 type Build struct { | 53 type Build struct { |
| 58 // bl is the buildLoader to use. A zero value is suitable for production
, but | 54 // bl is the buildLoader to use. A zero value is suitable for production
, but |
| 59 // this can be overridden for testing. | 55 // this can be overridden for testing. |
| 60 bl buildLoader | 56 bl buildLoader |
| 61 } | 57 } |
| 62 | 58 |
| 63 // GetTemplateName for Log returns the template name for log pages. | 59 // LogHandler writes the build log to the given response writer. |
| 64 func (l Log) GetTemplateName(t settings.Theme) string { | 60 func LogHandler(c *router.Context) { |
| 65 » return "log.html" | 61 » id := c.Params.ByName("id") |
| 62 » if id == "" { |
| 63 » » common.ErrorPage(c, http.StatusBadRequest, "No id") |
| 64 » » return |
| 65 » } |
| 66 » logname := c.Params.ByName("logname") |
| 67 » if logname == "" { |
| 68 » » common.ErrorPage(c, http.StatusBadRequest, "No log name") |
| 69 » } |
| 70 |
| 71 » sf, err := getSwarmingService(c.Context, getSwarmingHost(c.Request)) |
| 72 » if err != nil { |
| 73 » » common.ErrorPage(c, errCode(err), err.Error()) |
| 74 » » return |
| 75 » } |
| 76 |
| 77 » log, closed, err := swarmingBuildLogImpl(c.Context, sf, id, logname) |
| 78 » if err != nil { |
| 79 » » common.ErrorPage(c, errCode(err), err.Error()) |
| 80 » » return |
| 81 » } |
| 82 |
| 83 » templates.MustRender(c.Context, c.Writer, "pages/log.html", templates.Ar
gs{ |
| 84 » » "Log": log, |
| 85 » » "Closed": closed, |
| 86 » }) |
| 66 } | 87 } |
| 67 | 88 |
| 68 // Render writes the build log to the given response writer. | 89 func BuildHandler(c *router.Context) { |
| 69 func (l Log) Render(c context.Context, r *http.Request, p httprouter.Params) (*t
emplates.Args, error) { | 90 » (Build{}).Render(c) |
| 70 » id := p.ByName("id") | |
| 71 » if id == "" { | |
| 72 » » return nil, &miloerror.Error{ | |
| 73 » » » Message: "No id", | |
| 74 » » » Code: http.StatusBadRequest, | |
| 75 » » } | |
| 76 » } | |
| 77 » logname := p.ByName("logname") | |
| 78 » if logname == "" { | |
| 79 » » return nil, &miloerror.Error{ | |
| 80 » » » Message: "No log name", | |
| 81 » » » Code: http.StatusBadRequest, | |
| 82 » » } | |
| 83 » } | |
| 84 | |
| 85 » sf, err := getSwarmingService(c, getSwarmingHost(r)) | |
| 86 » if err != nil { | |
| 87 » » return nil, convertErr(err) | |
| 88 » } | |
| 89 | |
| 90 » log, closed, err := swarmingBuildLogImpl(c, sf, id, logname) | |
| 91 » if err != nil { | |
| 92 » » return nil, convertErr(err) | |
| 93 » } | |
| 94 | |
| 95 » args := &templates.Args{ | |
| 96 » » "Log": log, | |
| 97 » » "Closed": closed, | |
| 98 » } | |
| 99 » return args, nil | |
| 100 } | |
| 101 | |
| 102 // GetTemplateName for Build returns the template name for build pages. | |
| 103 func (b Build) GetTemplateName(t settings.Theme) string { | |
| 104 » return "build.html" | |
| 105 } | 91 } |
| 106 | 92 |
| 107 // Render renders both the build page and the log. | 93 // Render renders both the build page and the log. |
| 108 func (b Build) Render(c context.Context, r *http.Request, p httprouter.Params) (
*templates.Args, error) { | 94 func (b Build) Render(c *router.Context) { |
| 109 // Get the swarming ID | 95 // Get the swarming ID |
| 110 » id := p.ByName("id") | 96 » id := c.Params.ByName("id") |
| 111 if id == "" { | 97 if id == "" { |
| 112 » » return nil, &miloerror.Error{ | 98 » » common.ErrorPage(c, http.StatusBadRequest, "No id") |
| 113 » » » Message: "No id", | 99 » » return |
| 114 » » » Code: http.StatusBadRequest, | |
| 115 » » } | |
| 116 } | 100 } |
| 117 | 101 |
| 118 » sf, err := getSwarmingService(c, getSwarmingHost(r)) | 102 » sf, err := getSwarmingService(c.Context, getSwarmingHost(c.Request)) |
| 119 if err != nil { | 103 if err != nil { |
| 120 » » return nil, convertErr(err) | 104 » » common.ErrorPage(c, errCode(err), err.Error()) |
| 105 » » return |
| 121 } | 106 } |
| 122 | 107 |
| 123 » result, err := b.bl.swarmingBuildImpl(c, sf, r.URL.String(), id) | 108 » result, err := b.bl.swarmingBuildImpl(c.Context, sf, c.Request.URL.Strin
g(), id) |
| 124 if err != nil { | 109 if err != nil { |
| 125 » » return nil, convertErr(err) | 110 » » common.ErrorPage(c, errCode(err), err.Error()) |
| 111 » » return |
| 126 } | 112 } |
| 127 | 113 |
| 128 » // Render into the template | 114 » templates.MustRender(c.Context, c.Writer, "pages/build.html", templates.
Args{ |
| 129 » args := &templates.Args{ | |
| 130 "Build": result, | 115 "Build": result, |
| 131 » } | 116 » }) |
| 132 » return args, nil | |
| 133 } | 117 } |
| 134 | 118 |
| 135 func convertErr(err error) error { | 119 // errCode resolves recognized errors into proper http response codes. |
| 120 func errCode(err error) int { |
| 121 » if err == errUnrecognizedHost { |
| 122 » » return http.StatusBadRequest |
| 123 » } |
| 136 if isAPINotFound(err) || os.IsNotExist(err) { | 124 if isAPINotFound(err) || os.IsNotExist(err) { |
| 137 » » return &miloerror.Error{ | 125 » » return http.StatusNotFound |
| 138 » » » Message: err.Error(), | |
| 139 » » » Code: http.StatusNotFound, | |
| 140 » » } | |
| 141 } | 126 } |
| 142 » return err | 127 » return http.StatusInternalServerError |
| 143 } | 128 } |
| 144 | 129 |
| 145 // isAPINotFound returns true if err is a HTTP 404 API response. | 130 // isAPINotFound returns true if err is a HTTP 404 API response. |
| 146 func isAPINotFound(err error) bool { | 131 func isAPINotFound(err error) bool { |
| 147 if apiErr, ok := err.(*googleapi.Error); ok && apiErr.Code == http.Statu
sNotFound { | 132 if apiErr, ok := err.(*googleapi.Error); ok && apiErr.Code == http.Statu
sNotFound { |
| 148 return true | 133 return true |
| 149 } | 134 } |
| 150 | |
| 151 return false | 135 return false |
| 152 } | 136 } |
| OLD | NEW |