go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/internal/buildsource/rawpresentation/logDogBuild.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package rawpresentation 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "time" 22 23 "github.com/golang/protobuf/ptypes" 24 25 annopb "go.chromium.org/luci/luciexe/legacy/annotee/proto" 26 "go.chromium.org/luci/milo/frontend/ui" 27 "go.chromium.org/luci/milo/internal/model/milostatus" 28 ) 29 30 // URLBuilder constructs URLs for various link types. 31 type URLBuilder interface { 32 // LinkURL returns the URL associated with the supplied Link. 33 // 34 // If no URL could be built for that Link, nil will be returned. 35 BuildLink(l *annopb.AnnotationLink) *ui.Link 36 } 37 38 // miloBuildStep converts a logdog/annopb step to a BuildComponent struct. 39 // buildCompletedTime must be zero if build did not complete yet. 40 func miloBuildStep(c context.Context, ub URLBuilder, anno *annopb.Step, includeChildren bool) ui.BuildComponent { 41 42 comp := ui.BuildComponent{ 43 Label: ui.NewLink(anno.Name, "", anno.Name), 44 } 45 switch anno.Status { 46 case annopb.Status_RUNNING: 47 comp.Status = milostatus.Running 48 49 case annopb.Status_SUCCESS: 50 comp.Status = milostatus.Success 51 52 case annopb.Status_FAILURE: 53 if fd := anno.GetFailureDetails(); fd != nil { 54 switch fd.Type { 55 case annopb.FailureDetails_EXCEPTION, annopb.FailureDetails_INFRA: 56 comp.Status = milostatus.InfraFailure 57 58 case annopb.FailureDetails_EXPIRED: 59 comp.Status = milostatus.Expired 60 61 default: 62 comp.Status = milostatus.Failure 63 } 64 65 if fd.Text != "" { 66 comp.Text = append(comp.Text, fd.Text) 67 } 68 } else { 69 comp.Status = milostatus.Failure 70 } 71 72 case annopb.Status_PENDING: 73 comp.Status = milostatus.NotRun 74 75 // Missing the case of waiting on unfinished dependency... 76 default: 77 comp.Status = milostatus.NotRun 78 } 79 80 // Main link is a link to the stdout. 81 var stdoutLink *annopb.AnnotationLink 82 if anno.StdoutStream != nil { 83 stdoutLink = &annopb.AnnotationLink{ 84 Label: "stdout", 85 Value: &annopb.AnnotationLink_LogdogStream{ 86 LogdogStream: anno.StdoutStream, 87 }, 88 } 89 } 90 91 if anno.Link != nil { 92 comp.MainLink = ui.LinkSet{ub.BuildLink(anno.Link)} 93 94 // If we also have a STDOUT stream, add it to our OtherLinks. 95 if stdoutLink != nil { 96 anno.OtherLinks = append([]*annopb.AnnotationLink{stdoutLink}, anno.OtherLinks...) 97 } 98 } else if stdoutLink != nil { 99 comp.MainLink = ui.LinkSet{ub.BuildLink(stdoutLink)} 100 } 101 102 // Add STDERR link, if available. 103 if anno.StderrStream != nil { 104 anno.OtherLinks = append(anno.OtherLinks, &annopb.AnnotationLink{ 105 Label: "stderr", 106 Value: &annopb.AnnotationLink_LogdogStream{ 107 LogdogStream: anno.StderrStream, 108 }, 109 }) 110 } 111 112 // Sub link is for one link per log that isn't stdout. 113 for _, link := range anno.GetOtherLinks() { 114 if l := ub.BuildLink(link); l != nil { 115 comp.SubLink = append(comp.SubLink, ui.LinkSet{l}) 116 } 117 } 118 119 // This should always be a step. 120 comp.Type = ui.StepLegacy 121 122 // Timestamps 123 var start, end time.Time 124 if t, err := ptypes.Timestamp(anno.Started); err == nil { 125 start = t 126 } 127 if t, err := ptypes.Timestamp(anno.Ended); err == nil { 128 end = t 129 } 130 comp.ExecutionTime = ui.NewInterval(c, start, end) 131 132 // This should be the exact same thing. 133 comp.Text = append(comp.Text, anno.Text...) 134 135 if !includeChildren { 136 return comp 137 } 138 139 ss := anno.GetSubstep() 140 comp.Children = make([]*ui.BuildComponent, 0, len(ss)) 141 142 // Process nested steps. 143 for _, substep := range ss { 144 var subanno *annopb.Step 145 switch s := substep.GetSubstep().(type) { 146 case *annopb.Step_Substep_Step: 147 subanno = s.Step 148 case *annopb.Step_Substep_AnnotationStream: 149 panic("Non-inline substeps not supported") 150 default: 151 panic(fmt.Errorf("unknown type %v", s)) 152 } 153 subcomp := miloBuildStep(c, ub, subanno, includeChildren) 154 comp.Children = append(comp.Children, &subcomp) 155 } 156 157 return comp 158 } 159 160 func addPropGroups(groups *[]*ui.PropertyGroup, bs *ui.BuildComponent, anno *annopb.Step) { 161 propGroup := &ui.PropertyGroup{GroupName: bs.Label.Label} 162 for _, prop := range anno.Property { 163 propGroup.Property = append(propGroup.Property, &ui.Property{ 164 Key: prop.Name, 165 Value: prop.Value, 166 }) 167 } 168 *groups = append(*groups, propGroup) 169 170 for _, child := range bs.Children { 171 addPropGroups(groups, child, anno) 172 } 173 } 174 175 // SubStepsToUI converts a slice of annotation substeps to ui.BuildComponent and 176 // slice of ui.PropertyGroups. 177 func SubStepsToUI(c context.Context, ub URLBuilder, substeps []*annopb.Step_Substep) ([]*ui.BuildComponent, []*ui.PropertyGroup) { 178 components := make([]*ui.BuildComponent, 0, len(substeps)) 179 propGroups := make([]*ui.PropertyGroup, 0, len(substeps)+1) // This is the max number or property groups. 180 for _, substepContainer := range substeps { 181 anno := substepContainer.GetStep() 182 if anno == nil { 183 // TODO: We ignore non-embedded substeps for now. 184 continue 185 } 186 187 bs := miloBuildStep(c, ub, anno, true) 188 components = append(components, &bs) 189 addPropGroups(&propGroups, &bs, anno) 190 } 191 192 return components, propGroups 193 } 194 195 // AddLogDogToBuild takes a set of logdog streams and populate a milo build. 196 // build.Summary.Finished must be set. 197 func AddLogDogToBuild( 198 c context.Context, ub URLBuilder, mainAnno *annopb.Step, build *ui.MiloBuildLegacy) { 199 200 // Now fill in each of the step components. 201 // TODO(hinoka): This is totes cacheable. 202 build.Summary = miloBuildStep(c, ub, mainAnno, false) 203 build.Components, build.PropertyGroup = SubStepsToUI(c, ub, mainAnno.Substep) 204 205 // Take care of properties 206 propGroup := &ui.PropertyGroup{GroupName: "Main"} 207 for _, prop := range mainAnno.Property { 208 propGroup.Property = append(propGroup.Property, &ui.Property{ 209 Key: prop.Name, 210 Value: prop.Value, 211 }) 212 } 213 build.PropertyGroup = append(build.PropertyGroup, propGroup) 214 215 // Build a property map so we can extract revision properties. 216 propMap := map[string]string{} 217 for _, pg := range build.PropertyGroup { 218 for _, p := range pg.Property { 219 propMap[p.Key] = p.Value 220 } 221 } 222 // HACK(hinoka,iannucci): Extract revision out of properties. This should use 223 // source manifests instead. 224 jrev, ok := propMap["got_revision"] 225 if !ok { 226 jrev, ok = propMap["revision"] 227 } 228 if ok { 229 // got_revision/revision are json strings, so it looks like "aaaaaabbcc123..." 230 var rev string 231 err := json.Unmarshal([]byte(jrev), &rev) 232 if err == nil { 233 if build.Trigger == nil { 234 build.Trigger = &ui.Trigger{} 235 } 236 build.Trigger.Revision = ui.NewLink( 237 rev, fmt.Sprintf("https://crrev.com/%s", rev), fmt.Sprintf("revision %s", rev)) 238 } 239 } 240 }