blob: bd4a8b901571f84c11d21a57755230b2dde42325 [file] [log] [blame]
// Copyright 2018 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package frontend
import (
"fmt"
"net/http"
"strconv"
"strings"
bb "go.chromium.org/luci/buildbucket"
buildbucketpb "go.chromium.org/luci/buildbucket/proto"
bbv1 "go.chromium.org/luci/common/api/buildbucket/buildbucket/v1"
"go.chromium.org/luci/common/data/strpair"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/milo/buildsource/buildbucket"
"go.chromium.org/luci/milo/common"
"go.chromium.org/luci/milo/frontend/ui"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/templates"
)
// handleDevBuild renders a canned build for development.
func handleDevBuild(c *router.Context) error {
name := c.Params.ByName("name")
b, err := buildbucket.GetTestBuild(c.Context, "../../buildsource/buildbucket", name)
if err != nil {
return err
}
return renderBuild(c, &ui.BuildPage{Build: *b}, nil)
}
// handleLUCIBuild renders a LUCI build.
func handleLUCIBuild(c *router.Context) error {
bucket := c.Params.ByName("bucket")
buildername := c.Params.ByName("builder")
numberOrId := c.Params.ByName("numberOrId")
if _, v2Bucket := bb.BucketNameToV2(bucket); v2Bucket != "" {
// Params bucket is a v1 bucket, so call the legacy endpoint.
return handleLUCIBuildLegacy(c, bucket, buildername, numberOrId)
}
// TODO(hinoka): Once v2 is default, redirect v1 bucketnames to v2 bucketname URLs.
br := buildbucketpb.GetBuildRequest{}
if strings.HasPrefix(numberOrId, "b") {
id, err := strconv.ParseInt(numberOrId[1:], 10, 64)
if err != nil {
return errors.Annotate(err, "bad build id").Tag(common.CodeParameterError).Err()
}
br.Id = int64(id)
} else {
number, err := strconv.Atoi(numberOrId)
if err != nil {
return errors.Annotate(err, "bad build number").Tag(common.CodeParameterError).Err()
}
br.BuildNumber = int32(number)
br.Builder = &buildbucketpb.BuilderID{
Project: c.Params.ByName("project"),
Bucket: bucket,
Builder: buildername,
}
}
bp, err := buildbucket.GetBuildPage(c, br)
return renderBuild(c, bp, err)
}
// renderBuild is a shortcut for rendering build or returning err if it is not nil.
func renderBuild(c *router.Context, bp *ui.BuildPage, err error) error {
if err != nil {
return err
}
bp.StepDisplayPref = getStepDisplayPrefCookie(c)
templates.MustRender(c.Context, c.Writer, "pages/build.html", templates.Args{
"BuildPage": bp,
})
return nil
}
// redirectLUCIBuild redirects to a canonical build URL
// e.g. to /p/{project}/builders/{bucket}/{builder}/{number or id}.
func redirectLUCIBuild(c *router.Context) error {
idStr := c.Params.ByName("id")
// Verify it is an int64.
if _, err := strconv.ParseInt(idStr, 10, 64); err != nil {
return errors.Annotate(err, "invalid id").Tag(common.CodeParameterError).Err()
}
build, err := buildbucket.GetRawBuild(c.Context, idStr)
if err != nil {
return err
}
// If the build has a number, redirect to a URL with it.
builder := ""
u := *c.Request.URL
for _, t := range build.Tags {
switch k, v := strpair.Parse(t); k {
case bbv1.TagBuildAddress:
_, project, bucket, builder, number, _ := bbv1.ParseBuildAddress(v)
if number > 0 {
u.Path = fmt.Sprintf("/p/%s/builders/%s/%s/%d", project, bucket, builder, number)
http.Redirect(c.Writer, c.Request, u.String(), http.StatusMovedPermanently)
return nil
}
case bbv1.TagBuilder:
builder = v
}
}
if builder == "" {
return errors.Reason("build %s does not have a builder", idStr).Tag(common.CodeParameterError).Err()
}
u.Path = fmt.Sprintf("/p/%s/builders/%s/%s/b%d", build.Project, build.Bucket, builder, build.Id)
http.Redirect(c.Writer, c.Request, u.String(), http.StatusMovedPermanently)
return nil
}
func getStepDisplayPrefCookie(c *router.Context) ui.StepDisplayPref {
switch cookie, err := c.Request.Cookie("stepDisplayPref"); err {
case nil:
return ui.StepDisplayPref(cookie.Value)
case http.ErrNoCookie:
return ui.StepDisplayDefault
default:
logging.WithError(err).Errorf(c.Context, "failed to read stepDisplayPref cookie")
return ui.StepDisplayDefault
}
}