go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/legacy/cmd/run_annotations/main.go (about) 1 // Copyright 2019 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 // Command run_annotations is a LUCI executable that wraps a command that 16 // produces @@@ANNOTATIONS@@@, and converts annotations to build message. 17 package main 18 19 import ( 20 "context" 21 "os" 22 "os/exec" 23 "sync" 24 "time" 25 26 "github.com/golang/protobuf/proto" 27 28 pb "go.chromium.org/luci/buildbucket/proto" 29 "go.chromium.org/luci/common/errors" 30 "go.chromium.org/luci/logdog/client/butlerlib/bootstrap" 31 "go.chromium.org/luci/luciexe/exe" 32 "go.chromium.org/luci/luciexe/legacy/annotee" 33 "go.chromium.org/luci/luciexe/legacy/annotee/annotation" 34 annopb "go.chromium.org/luci/luciexe/legacy/annotee/proto" 35 ) 36 37 const warningMsg = `WARNING: This step is launched using deprecated allow_subannotation feature 38 that exists in the legacy @@@annotator@@@ protocol. Although a replacement 39 is built using the latest luciexe protocol for backwards compatibility, 40 please consider using 'step.presentation' object to mutate the step/build 41 state instead in the recipe. 42 43 NOTE: The original stdout/stderr log for this step is available at 44 annotation.stdout/annotation.stderr log stream. 45 ` 46 47 func check(err error) { 48 if err != nil { 49 panic(err) 50 } 51 } 52 53 func main() { 54 exe.Run(func(ctx context.Context, build *pb.Build, userArgs []string, sendBuild exe.BuildSender) error { 55 os.Stderr.WriteString(warningMsg) 56 if len(userArgs) == 0 { 57 return errors.New("No arguments were provided") 58 } 59 60 cwd, err := os.Getwd() 61 check(err) 62 63 // Start the subprocess. 64 cmd := exec.CommandContext(ctx, userArgs[0], userArgs[1:]...) 65 stdout, err := cmd.StdoutPipe() 66 check(err) 67 stderr, err := cmd.StderrPipe() 68 check(err) 69 err = cmd.Start() 70 check(errors.Annotate(err, "failed to start subprocess").Err()) 71 72 ldBootstrap, err := bootstrap.Get() 73 check(err) 74 75 var buildMU sync.Mutex 76 sendAnnotations := func(ann *annopb.Step) { 77 latest, err := annotee.ConvertRootStep(ctx, ann) 78 check(errors.Annotate(err, "failed to convert an annotation root step to a build").Err()) 79 80 buildMU.Lock() 81 defer buildMU.Unlock() 82 updateBaseBuild(build, latest) 83 sendBuild() 84 } 85 86 // Run STDOUT/STDERR streams through the processor. 87 // Run the process' output streams through Annotee. This will block until 88 // they are all consumed. 89 processor := annotee.New(ctx, annotee.Options{ 90 Client: ldBootstrap.Client, 91 Execution: annotation.ProbeExecution(userArgs, os.Environ(), cwd), 92 MetadataUpdateInterval: 30 * time.Second, 93 Offline: false, 94 CloseSteps: true, 95 AnnotationUpdated: func(annBytes []byte) { 96 ann := &annopb.Step{} 97 check(errors.Annotate(proto.Unmarshal(annBytes, ann), "failed to parse annotation proto").Err()) 98 sendAnnotations(ann) 99 }, 100 }) 101 streams := []*annotee.Stream{ 102 { 103 Reader: stdout, 104 Name: annotee.STDOUT, 105 Annotate: true, 106 }, 107 { 108 Reader: stderr, 109 Name: annotee.STDERR, 110 Annotate: true, 111 }, 112 } 113 err = processor.RunStreams(streams) 114 check(errors.Annotate(err, "failed to process annotations").Err()) 115 116 // Wait for the subprocess to exit. 117 switch err := cmd.Wait().(type) { 118 case *exec.ExitError: 119 case nil: 120 default: 121 check(errors.Annotate(err, "failed to wait for the subprocess to exit").Err()) 122 } 123 124 // Send the final state. 125 sendAnnotations(processor.Finish().RootStep().Proto()) 126 return nil 127 }) 128 } 129 130 func updateBaseBuild(base, latest *pb.Build) { 131 base.Status = latest.Status 132 base.StartTime = latest.StartTime 133 base.EndTime = latest.EndTime 134 base.SummaryMarkdown = latest.SummaryMarkdown 135 base.Output = latest.Output 136 for _, log := range base.GetOutput().GetLogs() { 137 // Rename the annotation's std logs so that it won't conflict with the 138 // std log of this command/luciexe when merging. More specifically, 139 // recipe engine will try to merge all logs in output into the step 140 // logs which already opens up stdout and stderr log. 141 if log.Name == "stdout" || log.Name == "stderr" { 142 log.Name = "annotation." + log.Name 143 } 144 } 145 base.Steps = latest.Steps 146 }