go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/luciexe/legacy/annotee/executor/executor.go (about) 1 // Copyright 2015 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 executor 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "io" 22 "os/exec" 23 24 "github.com/golang/protobuf/proto" 25 log "go.chromium.org/luci/common/logging" 26 "go.chromium.org/luci/common/system/exitcode" 27 "go.chromium.org/luci/logdog/common/types" 28 "go.chromium.org/luci/luciexe/legacy/annotee" 29 "go.chromium.org/luci/luciexe/legacy/annotee/annotation" 30 ) 31 32 // Executor bootstraps an application, running its output through a Processor. 33 type Executor struct { 34 // Options are the set of Annotee options to use. 35 Options annotee.Options 36 37 // Stdin, if not nil, will be used as standard input for the bootstrapped 38 // process. 39 Stdin io.Reader 40 41 // TeeStdout, if not nil, is a Writer where bootstrapped process standard 42 // output will be tee'd. 43 TeeStdout io.Writer 44 // TeeStderr, if not nil, is a Writer where bootstrapped process standard 45 // error will be tee'd. 46 TeeStderr io.Writer 47 48 executed bool 49 returnCode int 50 51 // step is the serialized milo.Step protobuf taken from the end of the 52 // Processor at execution finish. 53 step []byte 54 } 55 56 // Run executes the bootstrapped process, blocking until it completes. 57 func (e *Executor) Run(ctx context.Context, command []string) error { 58 // Clear any previous state. 59 e.executed = false 60 e.returnCode = 0 61 e.step = nil 62 63 if len(command) == 0 { 64 return errors.New("no command") 65 } 66 67 ctx, cancelFunc := context.WithCancel(ctx) 68 defer cancelFunc() 69 cmd := exec.CommandContext(ctx, command[0], command[1:]...) 70 71 // STDOUT 72 stdoutRC, err := cmd.StdoutPipe() 73 if err != nil { 74 return fmt.Errorf("failed to create STDOUT pipe: %s", err) 75 } 76 defer stdoutRC.Close() 77 stdout := e.configStream(stdoutRC, annotee.STDOUT, e.TeeStdout, true) 78 79 stderrRC, err := cmd.StderrPipe() 80 if err != nil { 81 return fmt.Errorf("failed to create STDERR pipe: %s", err) 82 } 83 defer stderrRC.Close() 84 stderr := e.configStream(stderrRC, annotee.STDERR, e.TeeStderr, false) 85 86 // Start our process. 87 if err := cmd.Start(); err != nil { 88 return fmt.Errorf("failed to start bootstrapped process: %s", err) 89 } 90 91 // Cleanup the process on exit, and record its status and return code. 92 defer func() { 93 if err := cmd.Wait(); err != nil { 94 var ok bool 95 if e.returnCode, ok = exitcode.Get(err); ok { 96 e.executed = true 97 } else { 98 log.WithError(err).Errorf(ctx, "Failed to Wait() for bootstrapped process.") 99 } 100 } else { 101 e.returnCode = 0 102 e.executed = true 103 } 104 }() 105 106 // Probe our execution information. 107 options := e.Options 108 if options.Execution == nil { 109 options.Execution = annotation.ProbeExecution(command, nil, "") 110 } 111 112 // Configure our Processor. 113 streams := []*annotee.Stream{ 114 stdout, 115 stderr, 116 } 117 118 // Process the bootstrapped I/O. We explicitly defer a Finish here to ensure 119 // that we clean up any internal streams if our Processor fails/panics. 120 // 121 // If we fail to process the I/O, terminate the bootstrapped process 122 // immediately, since it may otherwise block forever on I/O. 123 proc := annotee.New(ctx, options) 124 defer proc.Finish() 125 126 if err := proc.RunStreams(streams); err != nil { 127 return fmt.Errorf("failed to process bootstrapped I/O: %v", err) 128 } 129 130 // Finish and record our annotation steps on completion. 131 if e.step, err = proto.Marshal(proc.Finish().RootStep().Proto()); err != nil { 132 log.WithError(err).Errorf(ctx, "Failed to Marshal final Step protobuf on completion.") 133 return err 134 } 135 return nil 136 } 137 138 // Step returns the root Step protobuf from the latest run. 139 func (e *Executor) Step() []byte { return e.step } 140 141 // ReturnCode returns the executed process' return code. 142 // 143 // If the process hasn't completed its execution (see Executed), then this will 144 // return 0. 145 func (e *Executor) ReturnCode() int { 146 return e.returnCode 147 } 148 149 // Executed returns true if the bootstrapped process' execution completed 150 // successfully. This is independent of the return value, and can be used to 151 // differentiate execution errors from process errors. 152 func (e *Executor) Executed() bool { 153 return e.executed 154 } 155 156 func (e *Executor) configStream(r io.Reader, name types.StreamName, tee io.Writer, emitAll bool) *annotee.Stream { 157 s := &annotee.Stream{ 158 Reader: r, 159 Name: name, 160 Tee: tee, 161 Alias: "stdio", 162 Annotate: true, 163 EmitAllLink: emitAll, 164 } 165 return s 166 }