github.com/raychaser/docker@v1.5.0/engine/job.go (about)

     1  package engine
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"strings"
     8  	"time"
     9  
    10  	log "github.com/Sirupsen/logrus"
    11  )
    12  
    13  // A job is the fundamental unit of work in the docker engine.
    14  // Everything docker can do should eventually be exposed as a job.
    15  // For example: execute a process in a container, create a new container,
    16  // download an archive from the internet, serve the http api, etc.
    17  //
    18  // The job API is designed after unix processes: a job has a name, arguments,
    19  // environment variables, standard streams for input, output and error, and
    20  // an exit status which can indicate success (0) or error (anything else).
    21  //
    22  // For status, 0 indicates success, and any other integers indicates an error.
    23  // This allows for richer error reporting.
    24  //
    25  type Job struct {
    26  	Eng     *Engine
    27  	Name    string
    28  	Args    []string
    29  	env     *Env
    30  	Stdout  *Output
    31  	Stderr  *Output
    32  	Stdin   *Input
    33  	handler Handler
    34  	status  Status
    35  	end     time.Time
    36  	closeIO bool
    37  }
    38  
    39  type Status int
    40  
    41  const (
    42  	StatusOK       Status = 0
    43  	StatusErr      Status = 1
    44  	StatusNotFound Status = 127
    45  )
    46  
    47  // Run executes the job and blocks until the job completes.
    48  // If the job returns a failure status, an error is returned
    49  // which includes the status.
    50  func (job *Job) Run() error {
    51  	if job.Eng.IsShutdown() && !job.GetenvBool("overrideShutdown") {
    52  		return fmt.Errorf("engine is shutdown")
    53  	}
    54  	// FIXME: this is a temporary workaround to avoid Engine.Shutdown
    55  	// waiting 5 seconds for server/api.ServeApi to complete (which it never will)
    56  	// everytime the daemon is cleanly restarted.
    57  	// The permanent fix is to implement Job.Stop and Job.OnStop so that
    58  	// ServeApi can cooperate and terminate cleanly.
    59  	if job.Name != "serveapi" {
    60  		job.Eng.l.Lock()
    61  		job.Eng.tasks.Add(1)
    62  		job.Eng.l.Unlock()
    63  		defer job.Eng.tasks.Done()
    64  	}
    65  	// FIXME: make this thread-safe
    66  	// FIXME: implement wait
    67  	if !job.end.IsZero() {
    68  		return fmt.Errorf("%s: job has already completed", job.Name)
    69  	}
    70  	// Log beginning and end of the job
    71  	if job.Eng.Logging {
    72  		log.Infof("+job %s", job.CallString())
    73  		defer func() {
    74  			log.Infof("-job %s%s", job.CallString(), job.StatusString())
    75  		}()
    76  	}
    77  	var errorMessage = bytes.NewBuffer(nil)
    78  	job.Stderr.Add(errorMessage)
    79  	if job.handler == nil {
    80  		job.Errorf("%s: command not found", job.Name)
    81  		job.status = 127
    82  	} else {
    83  		job.status = job.handler(job)
    84  		job.end = time.Now()
    85  	}
    86  	if job.closeIO {
    87  		// Wait for all background tasks to complete
    88  		if err := job.Stdout.Close(); err != nil {
    89  			return err
    90  		}
    91  		if err := job.Stderr.Close(); err != nil {
    92  			return err
    93  		}
    94  		if err := job.Stdin.Close(); err != nil {
    95  			return err
    96  		}
    97  	}
    98  	if job.status != 0 {
    99  		return fmt.Errorf("%s", Tail(errorMessage, 1))
   100  	}
   101  
   102  	return nil
   103  }
   104  
   105  func (job *Job) CallString() string {
   106  	return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
   107  }
   108  
   109  func (job *Job) StatusString() string {
   110  	// If the job hasn't completed, status string is empty
   111  	if job.end.IsZero() {
   112  		return ""
   113  	}
   114  	var okerr string
   115  	if job.status == StatusOK {
   116  		okerr = "OK"
   117  	} else {
   118  		okerr = "ERR"
   119  	}
   120  	return fmt.Sprintf(" = %s (%d)", okerr, job.status)
   121  }
   122  
   123  // String returns a human-readable description of `job`
   124  func (job *Job) String() string {
   125  	return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
   126  }
   127  
   128  func (job *Job) Env() *Env {
   129  	return job.env
   130  }
   131  
   132  func (job *Job) EnvExists(key string) (value bool) {
   133  	return job.env.Exists(key)
   134  }
   135  
   136  func (job *Job) Getenv(key string) (value string) {
   137  	return job.env.Get(key)
   138  }
   139  
   140  func (job *Job) GetenvBool(key string) (value bool) {
   141  	return job.env.GetBool(key)
   142  }
   143  
   144  func (job *Job) SetenvBool(key string, value bool) {
   145  	job.env.SetBool(key, value)
   146  }
   147  
   148  func (job *Job) GetenvSubEnv(key string) *Env {
   149  	return job.env.GetSubEnv(key)
   150  }
   151  
   152  func (job *Job) SetenvSubEnv(key string, value *Env) error {
   153  	return job.env.SetSubEnv(key, value)
   154  }
   155  
   156  func (job *Job) GetenvInt64(key string) int64 {
   157  	return job.env.GetInt64(key)
   158  }
   159  
   160  func (job *Job) GetenvInt(key string) int {
   161  	return job.env.GetInt(key)
   162  }
   163  
   164  func (job *Job) SetenvInt64(key string, value int64) {
   165  	job.env.SetInt64(key, value)
   166  }
   167  
   168  func (job *Job) SetenvInt(key string, value int) {
   169  	job.env.SetInt(key, value)
   170  }
   171  
   172  // Returns nil if key not found
   173  func (job *Job) GetenvList(key string) []string {
   174  	return job.env.GetList(key)
   175  }
   176  
   177  func (job *Job) GetenvJson(key string, iface interface{}) error {
   178  	return job.env.GetJson(key, iface)
   179  }
   180  
   181  func (job *Job) SetenvJson(key string, value interface{}) error {
   182  	return job.env.SetJson(key, value)
   183  }
   184  
   185  func (job *Job) SetenvList(key string, value []string) error {
   186  	return job.env.SetJson(key, value)
   187  }
   188  
   189  func (job *Job) Setenv(key, value string) {
   190  	job.env.Set(key, value)
   191  }
   192  
   193  // DecodeEnv decodes `src` as a json dictionary, and adds
   194  // each decoded key-value pair to the environment.
   195  //
   196  // If `src` cannot be decoded as a json dictionary, an error
   197  // is returned.
   198  func (job *Job) DecodeEnv(src io.Reader) error {
   199  	return job.env.Decode(src)
   200  }
   201  
   202  func (job *Job) EncodeEnv(dst io.Writer) error {
   203  	return job.env.Encode(dst)
   204  }
   205  
   206  func (job *Job) ImportEnv(src interface{}) (err error) {
   207  	return job.env.Import(src)
   208  }
   209  
   210  func (job *Job) Environ() map[string]string {
   211  	return job.env.Map()
   212  }
   213  
   214  func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
   215  	prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
   216  	return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
   217  }
   218  
   219  func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
   220  	return fmt.Fprintf(job.Stdout, format, args...)
   221  }
   222  
   223  func (job *Job) Errorf(format string, args ...interface{}) Status {
   224  	if format[len(format)-1] != '\n' {
   225  		format = format + "\n"
   226  	}
   227  	fmt.Fprintf(job.Stderr, format, args...)
   228  	return StatusErr
   229  }
   230  
   231  func (job *Job) Error(err error) Status {
   232  	fmt.Fprintf(job.Stderr, "%s\n", err)
   233  	return StatusErr
   234  }
   235  
   236  func (job *Job) StatusCode() int {
   237  	return int(job.status)
   238  }
   239  
   240  func (job *Job) SetCloseIO(val bool) {
   241  	job.closeIO = val
   242  }