github.com/mattevans/edward@v1.9.2/runner/runner.go (about) 1 package runner 2 3 import ( 4 "io" 5 "log" 6 "os" 7 "os/signal" 8 "sync" 9 "time" 10 11 "github.com/kylelemons/godebug/pretty" 12 "github.com/pkg/errors" 13 "github.com/mattevans/edward/home" 14 "github.com/mattevans/edward/instance" 15 "github.com/mattevans/edward/instance/processes" 16 "github.com/mattevans/edward/services" 17 ) 18 19 // Runner provides state and functions for running a given service 20 type Runner struct { 21 Service *services.ServiceConfig 22 backendRunner services.Runner 23 DirConfig *home.EdwardConfiguration 24 25 logFile *os.File 26 27 commandWait sync.WaitGroup 28 NoWatch bool 29 WorkingDir string 30 31 status instance.Status 32 33 instanceId string 34 35 standardLog *Log 36 errorLog *Log 37 38 shutdownChan chan struct{} 39 } 40 41 func NewRunner( 42 cfg services.OperationConfig, 43 service *services.ServiceConfig, 44 dirConfig *home.EdwardConfiguration, 45 noWatch bool, 46 workingDir string, 47 ) (*Runner, error) { 48 r := &Runner{ 49 Service: service, 50 DirConfig: dirConfig, 51 NoWatch: noWatch, 52 WorkingDir: workingDir, 53 } 54 var err error 55 r.backendRunner, err = services.GetRunner(cfg, service) 56 if err != nil { 57 return nil, errors.WithStack(err) 58 } 59 return r, nil 60 } 61 62 func (r *Runner) Run(args []string) error { 63 r.updateServiceState(instance.StateStarting) 64 65 // Allow shutdown through signals 66 r.configureSignals() 67 68 log.Printf("Signals configured") 69 70 r.shutdownChan = make(chan struct{}) 71 72 r.status = instance.Status{ 73 StartTime: time.Now(), 74 } 75 76 if r.WorkingDir != "" { 77 err := os.Chdir(r.WorkingDir) 78 if err != nil { 79 r.updateServiceState(instance.StateDied) 80 return errors.WithStack(err) 81 } 82 } 83 84 log.Printf("Service config: %s", pretty.Sprint(r.Service)) 85 86 // Set the instance id 87 command, err := instance.Load(r.DirConfig, &processes.Processes{}, r.Service, services.ContextOverride{}) 88 if err != nil { 89 log.Printf("Could not get service command: %v\n", err) 90 } 91 r.instanceId = command.InstanceId 92 93 err = r.configureLogs() 94 if err != nil { 95 r.updateServiceState(instance.StateDied) 96 return errors.WithStack(err) 97 } 98 99 statusTick := time.NewTicker(10 * time.Second) 100 defer func() { 101 if statusTick != nil { 102 statusTick.Stop() 103 } 104 }() 105 go func() { 106 for _ = range statusTick.C { 107 r.updateStatusDetail() 108 } 109 }() 110 111 r.commandWait.Add(1) 112 113 err = r.startService() 114 if err != nil { 115 return errors.WithStack(err) 116 } 117 118 r.updateStatusDetail() 119 r.updateServiceState(instance.StateRunning) 120 121 closeWatchers := r.configureWatch() 122 if closeWatchers != nil { 123 defer closeWatchers() 124 } 125 126 r.commandWait.Wait() 127 128 // Wait for shutdown. 129 // If the service stopped and an interrupt was not sent, do not set the "DIED" state. 130 select { 131 case <-r.shutdownChan: 132 r.updateServiceState(instance.StateStopped) 133 log.Printf("Service stopped\n") 134 return nil 135 default: 136 r.updateServiceState(instance.StateDied) 137 statusTick.Stop() 138 statusTick = nil 139 } 140 141 return nil 142 } 143 144 func (r *Runner) updateServiceState(newState instance.State) { 145 r.status.State = newState 146 err := instance.SaveStatusForService(r.Service, r.instanceId, r.status, r.DirConfig.StateDir) 147 if err != nil { 148 log.Printf("could not save state: %v", err) 149 } 150 } 151 152 func (r *Runner) updateStatusDetail() { 153 r.status.StdoutLines = r.standardLog.Len() 154 r.status.StderrLines = r.errorLog.Len() 155 156 backendStatus, err := r.backendRunner.Status() 157 if err != nil { 158 log.Printf("could not save state: %v", err) 159 return 160 } 161 r.status.Ports = backendStatus.Ports 162 r.status.MemoryInfo = backendStatus.MemoryInfo 163 164 dir := r.DirConfig.StateDir 165 err = instance.SaveStatusForService(r.Service, r.instanceId, r.status, dir) 166 if err != nil { 167 log.Printf("could not save state: %v", err) 168 } 169 } 170 171 func (r *Runner) configureLogs() error { 172 logLocation := r.Service.GetRunLog(r.DirConfig.LogDir) 173 os.Remove(logLocation) 174 175 var err error 176 r.logFile, err = os.Create(logLocation) 177 if err != nil { 178 return errors.WithStack(err) 179 } 180 181 // Tee the logs to stdout and the service log file 182 log.SetOutput(io.MultiWriter( 183 os.Stdout, 184 &Log{ 185 file: r.logFile, 186 name: r.Service.Name, 187 stream: "messages", 188 }, 189 )) 190 log.SetPrefix("Edward> ") 191 return nil 192 } 193 194 func (r *Runner) configureSignals() { 195 signalChan := make(chan os.Signal, 1) 196 signal.Notify(signalChan, os.Interrupt) 197 go func() { 198 for range signalChan { 199 log.Printf("Received interrupt\n") 200 err := r.stopService() 201 if err != nil { 202 log.Printf("Could not stop service: %v", err) 203 } 204 close(r.shutdownChan) 205 } 206 }() 207 } 208 209 func (r *Runner) configureWatch() func() { 210 if !r.NoWatch { 211 closeWatchers, err := BeginWatch(r.DirConfig, r.Service, r.restartService) 212 if err != nil { 213 log.Printf("Could not enable auto-restart: %v\n", err) 214 return nil 215 } 216 if closeWatchers != nil { 217 log.Printf("Auto-restart enabled. This service will restart when files in its watch directories are edited.\nThis can be disabled using the --no-watch flag.\n") 218 } 219 return closeWatchers 220 } 221 return nil 222 } 223 224 func (r *Runner) restartService() error { 225 log.Printf("Restarting service\n") 226 227 // Increment the counter to prevent exiting unexpectedly 228 r.commandWait.Add(1) 229 230 err := r.stopService() 231 if err != nil { 232 return errors.WithStack(err) 233 } 234 err = r.startService() 235 if err != nil { 236 return errors.WithStack(err) 237 } 238 return nil 239 } 240 241 func (r *Runner) stopService() error { 242 wd, err := os.Getwd() 243 if err != nil { 244 return errors.WithStack(err) 245 } 246 247 var scriptErr error 248 var scriptOutput []byte 249 250 c, err := instance.Load(r.DirConfig, &processes.Processes{}, r.Service, services.ContextOverride{}) 251 if err != nil { 252 return errors.WithStack(err) 253 } 254 255 scriptOutput, scriptErr = r.backendRunner.Stop(wd, c.Getenv) 256 if scriptErr != nil { 257 log.Printf("Stop failed:%v\n%v\n", scriptErr, string(scriptOutput)) 258 return errors.WithStack(err) 259 } 260 return nil 261 } 262 263 func (r *Runner) startService() error { 264 log.Printf("Service starting\n") 265 266 r.standardLog = &Log{ 267 file: r.logFile, 268 name: r.Service.Name, 269 stream: "stdout", 270 } 271 r.errorLog = &Log{ 272 file: r.logFile, 273 name: r.Service.Name, 274 stream: "stderr", 275 } 276 277 err := r.backendRunner.Start(r.standardLog, r.errorLog) 278 if err != nil { 279 return errors.WithStack(err) 280 } 281 go func() { 282 r.backendRunner.Wait() 283 r.commandWait.Done() 284 }() 285 return nil 286 }