Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 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 buildbot | 5 package buildbot |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "encoding/json" | 8 "encoding/json" |
| 9 "errors" | 9 "errors" |
| 10 "fmt" | 10 "fmt" |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 32 Master: master, | 32 Master: master, |
| 33 Buildername: builder, | 33 Buildername: builder, |
| 34 Number: buildNum, | 34 Number: buildNum, |
| 35 } | 35 } |
| 36 | 36 |
| 37 err := datastore.Get(c, result) | 37 err := datastore.Get(c, result) |
| 38 err = checkAccess(c, err, result.Internal) | 38 err = checkAccess(c, err, result.Internal) |
| 39 if err == errMasterNotFound { | 39 if err == errMasterNotFound { |
| 40 err = errBuildNotFound | 40 err = errBuildNotFound |
| 41 } | 41 } |
| 42 | |
| 42 return result, err | 43 return result, err |
| 43 } | 44 } |
| 44 | 45 |
| 45 // result2Status translates a buildbot result integer into a resp.Status. | 46 // result2Status translates a buildbot result integer into a resp.Status. |
| 46 func result2Status(s *int) (status resp.Status) { | 47 func result2Status(s *int) (status resp.Status) { |
| 47 if s == nil { | 48 if s == nil { |
| 48 return resp.Running | 49 return resp.Running |
| 49 } | 50 } |
| 50 switch *s { | 51 switch *s { |
| 51 case 0: | 52 case 0: |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 174 } | 175 } |
| 175 | 176 |
| 176 return sum | 177 return sum |
| 177 } | 178 } |
| 178 | 179 |
| 179 var rLineBreak = regexp.MustCompile("<br */?>") | 180 var rLineBreak = regexp.MustCompile("<br */?>") |
| 180 | 181 |
| 181 // components takes a full buildbot build struct and extract step info from all | 182 // components takes a full buildbot build struct and extract step info from all |
| 182 // of the steps and returns it as a list of milo Build Components. | 183 // of the steps and returns it as a list of milo Build Components. |
| 183 func components(b *buildbotBuild) (result []*resp.BuildComponent) { | 184 func components(b *buildbotBuild) (result []*resp.BuildComponent) { |
| 184 » for i, step := range b.Steps { | 185 » for _, step := range b.Steps { |
| 185 if step.Hidden == true { | 186 if step.Hidden == true { |
| 186 continue | 187 continue |
| 187 } | 188 } |
| 188 bc := &resp.BuildComponent{ | 189 bc := &resp.BuildComponent{ |
| 189 Label: step.Name, | 190 Label: step.Name, |
| 190 } | 191 } |
| 191 // Step text sometimes contains <br>, which we want to parse int o new lines. | 192 // Step text sometimes contains <br>, which we want to parse int o new lines. |
| 192 for _, t := range step.Text { | 193 for _, t := range step.Text { |
| 193 for _, line := range rLineBreak.Split(t, -1) { | 194 for _, line := range rLineBreak.Split(t, -1) { |
| 194 bc.Text = append(bc.Text, line) | 195 bc.Text = append(bc.Text, line) |
| 195 } | 196 } |
| 196 } | 197 } |
| 197 | 198 |
| 198 // Figure out the status. | 199 // Figure out the status. |
| 199 if !step.IsStarted { | 200 if !step.IsStarted { |
| 200 bc.Status = resp.NotRun | 201 bc.Status = resp.NotRun |
| 201 } else if !step.IsFinished { | 202 } else if !step.IsFinished { |
| 202 bc.Status = resp.Running | 203 bc.Status = resp.Running |
| 203 } else { | 204 } else { |
| 204 if len(step.Results) > 0 { | 205 if len(step.Results) > 0 { |
| 205 status := int(step.Results[0].(float64)) | 206 status := int(step.Results[0].(float64)) |
| 206 bc.Status = result2Status(&status) | 207 bc.Status = result2Status(&status) |
| 207 } else { | 208 } else { |
| 208 bc.Status = resp.Success | 209 bc.Status = resp.Success |
| 209 } | 210 } |
| 210 } | 211 } |
| 211 | 212 |
| 212 // Now, link to the logs. | |
| 213 // | |
| 214 // Any given log may have an alias link. There may also be alise s that are | |
| 215 // not attached to logs (unusual, but allowed), so we need to tr ack which | |
| 216 // aliases are unreferenced and add them as immediate links. | |
| 217 // | |
| 218 // Additionally, if the build is LogDog-only ("log_location" is "logdog://") | |
| 219 // then we want to render *only* the alias for steps with aliase s. | |
| 220 isLogDogOnly := false | |
| 221 if loc, ok := b.getPropertyValue("log_location").(string); ok && strings.HasPrefix(loc, "logdog://") { | |
| 222 isLogDogOnly = true | |
| 223 } | |
| 224 remainingAliases := stringset.New(len(step.Aliases)) | 213 remainingAliases := stringset.New(len(step.Aliases)) |
| 225 for linkAnchor := range step.Aliases { | 214 for linkAnchor := range step.Aliases { |
| 226 remainingAliases.Add(linkAnchor) | 215 remainingAliases.Add(linkAnchor) |
| 227 } | 216 } |
| 228 | 217 |
| 229 getLinksWithAliases := func(logLink *resp.Link, isLog bool) resp .LinkSet { | 218 getLinksWithAliases := func(logLink *resp.Link, isLog bool) resp .LinkSet { |
| 230 // Generate alias links. | 219 // Generate alias links. |
| 231 var aliases resp.LinkSet | 220 var aliases resp.LinkSet |
| 232 if remainingAliases.Del(logLink.Label) { | 221 if remainingAliases.Del(logLink.Label) { |
| 233 stepAliases := step.Aliases[logLink.Label] | 222 stepAliases := step.Aliases[logLink.Label] |
| 234 aliases = make(resp.LinkSet, len(stepAliases)) | 223 aliases = make(resp.LinkSet, len(stepAliases)) |
| 235 for i, alias := range stepAliases { | 224 for i, alias := range stepAliases { |
| 236 aliases[i] = alias.toLink() | 225 aliases[i] = alias.toLink() |
| 237 } | 226 } |
| 238 } | 227 } |
| 239 | 228 |
| 240 var links resp.LinkSet | |
| 241 if isLogDogOnly && len(aliases) > 0 && !(isLog && i == 0 && logLink.Label == "stdio") { | |
| 242 // LogDog only and there are viable aliases. Fea ture them instead of the | |
| 243 // log link. | |
| 244 // | |
| 245 // We exclude the top-level "stdio" link here be cause there isn't a | |
| 246 // LogDog equivalent of the raw step output. | |
| 247 // | |
| 248 // Any link named "logdog" (Annotee cosmetic imp lementation detail) will | |
| 249 // inherit the name of the build link. | |
| 250 for _, a := range aliases { | |
| 251 if a.Label == "logdog" { | |
| 252 a.Label = logLink.Label | |
| 253 } | |
| 254 } | |
| 255 return aliases | |
| 256 } | |
| 257 | |
| 258 // Step log link takes primary, with aliases as secondar y. | 229 // Step log link takes primary, with aliases as secondar y. |
| 259 » » » links = make(resp.LinkSet, 1, 1+len(aliases)) | 230 » » » links := make(resp.LinkSet, 1, 1+len(aliases)) |
| 260 links[0] = logLink | 231 links[0] = logLink |
| 261 | 232 |
| 262 for _, a := range aliases { | 233 for _, a := range aliases { |
| 263 a.Alias = true | 234 a.Alias = true |
| 264 } | 235 } |
| 265 return append(links, aliases...) | 236 return append(links, aliases...) |
| 266 } | 237 } |
| 267 | 238 |
| 268 for _, l := range step.Logs { | 239 for _, l := range step.Logs { |
| 269 logLink := resp.Link{ | 240 logLink := resp.Link{ |
| (...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 515 var err error | 486 var err error |
| 516 if master == "debug" { | 487 if master == "debug" { |
| 517 b, err = getDebugBuild(c, builder, buildNum) | 488 b, err = getDebugBuild(c, builder, buildNum) |
| 518 } else { | 489 } else { |
| 519 b, err = getBuild(c, master, builder, buildNum) | 490 b, err = getBuild(c, master, builder, buildNum) |
| 520 } | 491 } |
| 521 if err != nil { | 492 if err != nil { |
| 522 return nil, err | 493 return nil, err |
| 523 } | 494 } |
| 524 | 495 |
| 496 // Prepare the build for rendering. | |
| 497 postProcessBuild(b) | |
|
hinoka
2017/03/27 20:14:35
I like
b = postProcessBuild(b)
to make it crystal
dnj
2017/03/27 21:31:33
Talked offline, making "updatePostProcessBuild".
| |
| 498 | |
| 525 // TODO(hinoka): Do all fields concurrently. | 499 // TODO(hinoka): Do all fields concurrently. |
| 526 return &resp.MiloBuild{ | 500 return &resp.MiloBuild{ |
| 527 SourceStamp: sourcestamp(c, b), | 501 SourceStamp: sourcestamp(c, b), |
| 528 Summary: summary(c, b), | 502 Summary: summary(c, b), |
| 529 Components: components(b), | 503 Components: components(b), |
| 530 PropertyGroup: properties(b), | 504 PropertyGroup: properties(b), |
| 531 Blame: blame(b), | 505 Blame: blame(b), |
| 532 }, nil | 506 }, nil |
| 533 } | 507 } |
| 508 | |
| 509 // postProcessBuild transforms a build from its raw JSON format into the format | |
| 510 // that should be presented to users. | |
|
hinoka
2017/03/27 20:14:35
Add to comment:
// This does the following:
// 1.
dnj
2017/03/27 21:31:33
Done.
| |
| 511 func postProcessBuild(b *buildbotBuild) { | |
| 512 // If this is a LogDog-only build, we want to promote the LogDog links, which | |
| 513 // come in the form of aliases, to first-class build log and links. | |
| 514 if loc, ok := b.getPropertyValue("log_location").(string); ok && strings .HasPrefix(loc, "logdog://") { | |
| 515 for sidx := range b.Steps { | |
|
hinoka
2017/03/27 20:14:35
nit: just "i" is fine.
dnj
2017/03/27 21:31:33
I want to use the same variable name as in "promot
hinoka
2017/03/27 22:04:26
Acknowledged.
| |
| 516 promoteLogDogOnlyLinksForStep(b, sidx, &b.Steps[sidx]) | |
|
hinoka
2017/03/27 20:14:35
I think:
b.Steps[i] = logDogOnlyStep(...)
is a cle
dnj
2017/03/27 21:31:33
That notation implies that a correct implementatio
| |
| 517 } | |
| 518 } | |
| 519 } | |
| 520 | |
| 521 // postProcessBuildPromoteLogDogOnly updates the links in a BuildBot build to | |
| 522 // promote LogDog links. | |
| 523 // | |
| 524 // A build's links come in one of three forms: | |
| 525 // - Log Links, which link directly to BuildBot build logs. | |
| 526 // - URL Links, which are named links to arbitrary URLs. | |
| 527 // - Aliases, which attach to the label in one of the other types of links and | |
| 528 // augment it with additional named links. | |
| 529 // | |
| 530 // LogDog uses aliases exclusively to attach LogDog logs to other links. When | |
| 531 // the build is LogDog-only, though, the original links are actually junk. What | |
| 532 // we want to do is remove the original junk links and replace them with their | |
| 533 // alias counterparts, so that the "natural" BuildBot links are actually LogDog | |
| 534 // links. | |
| 535 func promoteLogDogOnlyLinksForStep(b *buildbotBuild, sidx int, s *buildbotStep) { | |
|
hinoka
2017/03/27 20:14:35
how about logDogOnlyStep(...)
Make sure comment f
dnj
2017/03/27 21:31:33
Chatted offline, made "promoteLogDogOnlyLinksForBu
| |
| 536 type stepLog struct { | |
| 537 label string | |
| 538 link string | |
| 539 } | |
| 540 | |
| 541 remainingAliases := stringset.New(len(s.Aliases)) | |
| 542 for linkAnchor := range s.Aliases { | |
| 543 remainingAliases.Add(linkAnchor) | |
| 544 } | |
| 545 | |
| 546 maybePromoteAliases := func(sl *stepLog, isLog bool) []*stepLog { | |
| 547 // As a special case, if this is the first step ("steps" in Buil dBot), we | |
| 548 // will refrain from promoting aliases for "stdio", since "stdio " represents | |
| 549 // the raw BuildBot logs. | |
| 550 if isLog && sidx == 0 && sl.label == "stdio" { | |
|
hinoka
2017/03/27 20:14:35
This looks like the only place sidx is used. Cons
dnj
2017/03/27 21:31:33
Replaced with "isInitialStep" boolean.
| |
| 551 // No aliases, don't modify this log. | |
| 552 return []*stepLog{sl} | |
| 553 } | |
| 554 | |
| 555 // If there are no aliases, we should obviously not promote them . This will | |
| 556 // be the case for pre-LogDog steps such as build setup. | |
| 557 aliases := s.Aliases[sl.label] | |
| 558 if len(aliases) == 0 { | |
| 559 return []*stepLog{sl} | |
| 560 } | |
| 561 | |
| 562 // We have chosen to promote the aliases. Therefore, we will not include | |
| 563 // them as aliases in the modified step. | |
| 564 remainingAliases.Del(sl.label) | |
| 565 | |
| 566 result := make([]*stepLog, len(aliases)) | |
| 567 for i, alias := range aliases { | |
| 568 aliasStepLog := stepLog{alias.Text, alias.URL} | |
| 569 | |
| 570 // Any link named "logdog" (Annotee cosmetic implementat ion detail) will | |
| 571 // inherit the name of the original log. | |
| 572 if !isLog { | |
| 573 if aliasStepLog.label == "logdog" { | |
| 574 aliasStepLog.label = sl.label | |
| 575 } | |
| 576 } | |
| 577 | |
| 578 result[i] = &aliasStepLog | |
| 579 } | |
| 580 return result | |
| 581 } | |
| 582 | |
| 583 // Update step logs. | |
| 584 newLogs := make([][]string, 0, len(s.Logs)) | |
| 585 for _, l := range s.Logs { | |
| 586 for _, res := range maybePromoteAliases(&stepLog{l[0], l[1]}, tr ue) { | |
| 587 newLogs = append(newLogs, []string{res.label, res.link}) | |
| 588 } | |
| 589 } | |
| 590 s.Logs = newLogs | |
| 591 | |
| 592 // Update step URLs. | |
| 593 newURLs := make(map[string]string, len(s.Urls)) | |
| 594 for label, link := range s.Urls { | |
| 595 urlLinks := maybePromoteAliases(&stepLog{label, link}, false) | |
| 596 if len(urlLinks) > 0 { | |
| 597 // Use the last URL link, since our URL map can only tol erate one link. | |
| 598 // The expected case here is that len(urlLinks) == 1, th ough, but it's | |
| 599 // possible that multiple aliases can be included for a single URL, so | |
| 600 // we need to handle that. | |
| 601 newValue := urlLinks[len(urlLinks)-1] | |
| 602 newURLs[newValue.label] = newValue.link | |
| 603 } else { | |
| 604 newURLs[label] = link | |
| 605 } | |
| 606 } | |
| 607 s.Urls = newURLs | |
| 608 | |
| 609 // Preserve any aliases that haven't been promoted. | |
| 610 var newAliases map[string][]*buildbotLinkAlias | |
| 611 if l := remainingAliases.Len(); l > 0 { | |
| 612 newAliases = make(map[string][]*buildbotLinkAlias, l) | |
| 613 remainingAliases.Iter(func(v string) bool { | |
| 614 newAliases[v] = s.Aliases[v] | |
| 615 return true | |
| 616 }) | |
| 617 } | |
| 618 s.Aliases = newAliases | |
| 619 } | |
| OLD | NEW |