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  }