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  }