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. | |
|
hinoka
2017/03/27 22:04:26
s/Prepare/Modify/
dnj
2017/03/27 22:20:16
Done.
| |
| 497 updatePostProcessBuild(b) | |
| 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 // updatePostProcessBuild transforms a build from its raw JSON format into the | |
| 510 // format that should be presented to users. | |
| 511 // | |
| 512 // Post-processing includes: | |
| 513 // - If the build is LogDog-only, promotes aliases (LogDog links) to | |
| 514 // first-class links in the build. | |
| 515 func updatePostProcessBuild(b *buildbotBuild) { | |
| 516 // If this is a LogDog-only build, we want to promote the LogDog links. | |
| 517 if loc, ok := b.getPropertyValue("log_location").(string); ok && strings .HasPrefix(loc, "logdog://") { | |
| 518 for sidx := range b.Steps { | |
| 519 promoteLogDogOnlyLinksForBuildStep(&b.Steps[sidx], sidx == 0) | |
| 520 } | |
| 521 } | |
| 522 } | |
| 523 | |
| 524 // promoteLogDogOnlyLinksForBuildStep updates the links in a BuildBot build to | |
|
hinoka
2017/03/27 22:04:26
in a BuildBot step, not build
dnj
2017/03/27 22:20:16
Done.
| |
| 525 // promote LogDog links. | |
| 526 // | |
| 527 // A build's links come in one of three forms: | |
| 528 // - Log Links, which link directly to BuildBot build logs. | |
| 529 // - URL Links, which are named links to arbitrary URLs. | |
| 530 // - Aliases, which attach to the label in one of the other types of links and | |
| 531 // augment it with additional named links. | |
| 532 // | |
| 533 // LogDog uses aliases exclusively to attach LogDog logs to other links. When | |
| 534 // the build is LogDog-only, though, the original links are actually junk. What | |
| 535 // we want to do is remove the original junk links and replace them with their | |
| 536 // alias counterparts, so that the "natural" BuildBot links are actually LogDog | |
| 537 // links. | |
| 538 func promoteLogDogOnlyLinksForBuildStep(s *buildbotStep, isInitialStep bool) { | |
|
hinoka
2017/03/27 22:04:26
nit: this is really long and. how about just prom
dnj
2017/03/27 22:20:16
Done.
| |
| 539 type stepLog struct { | |
| 540 label string | |
| 541 link string | |
|
hinoka
2017/03/27 22:04:26
s/link/URL/
dnj
2017/03/27 22:20:16
Done.
| |
| 542 } | |
| 543 | |
| 544 remainingAliases := stringset.New(len(s.Aliases)) | |
| 545 for linkAnchor := range s.Aliases { | |
| 546 remainingAliases.Add(linkAnchor) | |
| 547 } | |
| 548 | |
| 549 maybePromoteAliases := func(sl *stepLog, isLog bool) []*stepLog { | |
| 550 // As a special case, if this is the first step ("steps" in Buil dBot), we | |
| 551 // will refrain from promoting aliases for "stdio", since "stdio " represents | |
| 552 // the raw BuildBot logs. | |
| 553 if isLog && isInitialStep && sl.label == "stdio" { | |
| 554 // No aliases, don't modify this log. | |
| 555 return []*stepLog{sl} | |
| 556 } | |
| 557 | |
| 558 // If there are no aliases, we should obviously not promote them . This will | |
| 559 // be the case for pre-LogDog steps such as build setup. | |
| 560 aliases := s.Aliases[sl.label] | |
| 561 if len(aliases) == 0 { | |
| 562 return []*stepLog{sl} | |
| 563 } | |
| 564 | |
| 565 // We have chosen to promote the aliases. Therefore, we will not include | |
| 566 // them as aliases in the modified step. | |
| 567 remainingAliases.Del(sl.label) | |
| 568 | |
| 569 result := make([]*stepLog, len(aliases)) | |
| 570 for i, alias := range aliases { | |
| 571 aliasStepLog := stepLog{alias.Text, alias.URL} | |
| 572 | |
| 573 // Any link named "logdog" (Annotee cosmetic implementat ion detail) will | |
| 574 // inherit the name of the original log. | |
| 575 if !isLog { | |
| 576 if aliasStepLog.label == "logdog" { | |
| 577 aliasStepLog.label = sl.label | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 result[i] = &aliasStepLog | |
| 582 } | |
| 583 return result | |
| 584 } | |
| 585 | |
| 586 // Update step logs. | |
| 587 newLogs := make([][]string, 0, len(s.Logs)) | |
| 588 for _, l := range s.Logs { | |
| 589 for _, res := range maybePromoteAliases(&stepLog{l[0], l[1]}, tr ue) { | |
| 590 newLogs = append(newLogs, []string{res.label, res.link}) | |
| 591 } | |
| 592 } | |
| 593 s.Logs = newLogs | |
| 594 | |
| 595 // Update step URLs. | |
| 596 newURLs := make(map[string]string, len(s.Urls)) | |
| 597 for label, link := range s.Urls { | |
| 598 urlLinks := maybePromoteAliases(&stepLog{label, link}, false) | |
| 599 if len(urlLinks) > 0 { | |
| 600 // Use the last URL link, since our URL map can only tol erate one link. | |
| 601 // The expected case here is that len(urlLinks) == 1, th ough, but it's | |
| 602 // possible that multiple aliases can be included for a single URL, so | |
| 603 // we need to handle that. | |
| 604 newValue := urlLinks[len(urlLinks)-1] | |
| 605 newURLs[newValue.label] = newValue.link | |
| 606 } else { | |
| 607 newURLs[label] = link | |
| 608 } | |
| 609 } | |
| 610 s.Urls = newURLs | |
| 611 | |
| 612 // Preserve any aliases that haven't been promoted. | |
| 613 var newAliases map[string][]*buildbotLinkAlias | |
| 614 if l := remainingAliases.Len(); l > 0 { | |
|
hinoka
2017/03/27 22:04:26
How about just
newAliases := make(map[string][]*b
dnj
2017/03/27 22:20:16
I like to not allocate a map at all (not even one
| |
| 615 newAliases = make(map[string][]*buildbotLinkAlias, l) | |
| 616 remainingAliases.Iter(func(v string) bool { | |
| 617 newAliases[v] = s.Aliases[v] | |
| 618 return true | |
| 619 }) | |
| 620 } | |
| 621 s.Aliases = newAliases | |
| 622 } | |
| OLD | NEW |