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

     1  package engine
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/docker/docker/pkg/ioutils"
    14  	"github.com/docker/docker/utils"
    15  )
    16  
    17  // Installer is a standard interface for objects which can "install" themselves
    18  // on an engine by registering handlers.
    19  // This can be used as an entrypoint for external plugins etc.
    20  type Installer interface {
    21  	Install(*Engine) error
    22  }
    23  
    24  type Handler func(*Job) Status
    25  
    26  var globalHandlers map[string]Handler
    27  
    28  func init() {
    29  	globalHandlers = make(map[string]Handler)
    30  }
    31  
    32  func Register(name string, handler Handler) error {
    33  	_, exists := globalHandlers[name]
    34  	if exists {
    35  		return fmt.Errorf("Can't overwrite global handler for command %s", name)
    36  	}
    37  	globalHandlers[name] = handler
    38  	return nil
    39  }
    40  
    41  func unregister(name string) {
    42  	delete(globalHandlers, name)
    43  }
    44  
    45  // The Engine is the core of Docker.
    46  // It acts as a store for *containers*, and allows manipulation of these
    47  // containers by executing *jobs*.
    48  type Engine struct {
    49  	handlers   map[string]Handler
    50  	catchall   Handler
    51  	hack       Hack // data for temporary hackery (see hack.go)
    52  	id         string
    53  	Stdout     io.Writer
    54  	Stderr     io.Writer
    55  	Stdin      io.Reader
    56  	Logging    bool
    57  	tasks      sync.WaitGroup
    58  	l          sync.RWMutex // lock for shutdown
    59  	shutdown   bool
    60  	onShutdown []func() // shutdown handlers
    61  }
    62  
    63  func (eng *Engine) Register(name string, handler Handler) error {
    64  	_, exists := eng.handlers[name]
    65  	if exists {
    66  		return fmt.Errorf("Can't overwrite handler for command %s", name)
    67  	}
    68  	eng.handlers[name] = handler
    69  	return nil
    70  }
    71  
    72  func (eng *Engine) RegisterCatchall(catchall Handler) {
    73  	eng.catchall = catchall
    74  }
    75  
    76  // New initializes a new engine.
    77  func New() *Engine {
    78  	eng := &Engine{
    79  		handlers: make(map[string]Handler),
    80  		id:       utils.RandomString(),
    81  		Stdout:   os.Stdout,
    82  		Stderr:   os.Stderr,
    83  		Stdin:    os.Stdin,
    84  		Logging:  true,
    85  	}
    86  	eng.Register("commands", func(job *Job) Status {
    87  		for _, name := range eng.commands() {
    88  			job.Printf("%s\n", name)
    89  		}
    90  		return StatusOK
    91  	})
    92  	// Copy existing global handlers
    93  	for k, v := range globalHandlers {
    94  		eng.handlers[k] = v
    95  	}
    96  	return eng
    97  }
    98  
    99  func (eng *Engine) String() string {
   100  	return fmt.Sprintf("%s", eng.id[:8])
   101  }
   102  
   103  // Commands returns a list of all currently registered commands,
   104  // sorted alphabetically.
   105  func (eng *Engine) commands() []string {
   106  	names := make([]string, 0, len(eng.handlers))
   107  	for name := range eng.handlers {
   108  		names = append(names, name)
   109  	}
   110  	sort.Strings(names)
   111  	return names
   112  }
   113  
   114  // Job creates a new job which can later be executed.
   115  // This function mimics `Command` from the standard os/exec package.
   116  func (eng *Engine) Job(name string, args ...string) *Job {
   117  	job := &Job{
   118  		Eng:     eng,
   119  		Name:    name,
   120  		Args:    args,
   121  		Stdin:   NewInput(),
   122  		Stdout:  NewOutput(),
   123  		Stderr:  NewOutput(),
   124  		env:     &Env{},
   125  		closeIO: true,
   126  	}
   127  	if eng.Logging {
   128  		job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr))
   129  	}
   130  
   131  	// Catchall is shadowed by specific Register.
   132  	if handler, exists := eng.handlers[name]; exists {
   133  		job.handler = handler
   134  	} else if eng.catchall != nil && name != "" {
   135  		// empty job names are illegal, catchall or not.
   136  		job.handler = eng.catchall
   137  	}
   138  	return job
   139  }
   140  
   141  // OnShutdown registers a new callback to be called by Shutdown.
   142  // This is typically used by services to perform cleanup.
   143  func (eng *Engine) OnShutdown(h func()) {
   144  	eng.l.Lock()
   145  	eng.onShutdown = append(eng.onShutdown, h)
   146  	eng.l.Unlock()
   147  }
   148  
   149  // Shutdown permanently shuts down eng as follows:
   150  // - It refuses all new jobs, permanently.
   151  // - It waits for all active jobs to complete (with no timeout)
   152  // - It calls all shutdown handlers concurrently (if any)
   153  // - It returns when all handlers complete, or after 15 seconds,
   154  //	whichever happens first.
   155  func (eng *Engine) Shutdown() {
   156  	eng.l.Lock()
   157  	if eng.shutdown {
   158  		eng.l.Unlock()
   159  		return
   160  	}
   161  	eng.shutdown = true
   162  	eng.l.Unlock()
   163  	// We don't need to protect the rest with a lock, to allow
   164  	// for other calls to immediately fail with "shutdown" instead
   165  	// of hanging for 15 seconds.
   166  	// This requires all concurrent calls to check for shutdown, otherwise
   167  	// it might cause a race.
   168  
   169  	// Wait for all jobs to complete.
   170  	// Timeout after 5 seconds.
   171  	tasksDone := make(chan struct{})
   172  	go func() {
   173  		eng.tasks.Wait()
   174  		close(tasksDone)
   175  	}()
   176  	select {
   177  	case <-time.After(time.Second * 5):
   178  	case <-tasksDone:
   179  	}
   180  
   181  	// Call shutdown handlers, if any.
   182  	// Timeout after 10 seconds.
   183  	var wg sync.WaitGroup
   184  	for _, h := range eng.onShutdown {
   185  		wg.Add(1)
   186  		go func(h func()) {
   187  			defer wg.Done()
   188  			h()
   189  		}(h)
   190  	}
   191  	done := make(chan struct{})
   192  	go func() {
   193  		wg.Wait()
   194  		close(done)
   195  	}()
   196  	select {
   197  	case <-time.After(time.Second * 10):
   198  	case <-done:
   199  	}
   200  	return
   201  }
   202  
   203  // IsShutdown returns true if the engine is in the process
   204  // of shutting down, or already shut down.
   205  // Otherwise it returns false.
   206  func (eng *Engine) IsShutdown() bool {
   207  	eng.l.RLock()
   208  	defer eng.l.RUnlock()
   209  	return eng.shutdown
   210  }
   211  
   212  // ParseJob creates a new job from a text description using a shell-like syntax.
   213  //
   214  // The following syntax is used to parse `input`:
   215  //
   216  // * Words are separated using standard whitespaces as separators.
   217  // * Quotes and backslashes are not interpreted.
   218  // * Words of the form 'KEY=[VALUE]' are added to the job environment.
   219  // * All other words are added to the job arguments.
   220  //
   221  // For example:
   222  //
   223  // job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world")
   224  //
   225  // The resulting job will have:
   226  //	job.Args={"echo", "hello", "world"}
   227  //	job.Env={"VERBOSE":"1", "TEST":"true"}
   228  //
   229  func (eng *Engine) ParseJob(input string) (*Job, error) {
   230  	// FIXME: use a full-featured command parser
   231  	scanner := bufio.NewScanner(strings.NewReader(input))
   232  	scanner.Split(bufio.ScanWords)
   233  	var (
   234  		cmd []string
   235  		env Env
   236  	)
   237  	for scanner.Scan() {
   238  		word := scanner.Text()
   239  		kv := strings.SplitN(word, "=", 2)
   240  		if len(kv) == 2 {
   241  			env.Set(kv[0], kv[1])
   242  		} else {
   243  			cmd = append(cmd, word)
   244  		}
   245  	}
   246  	if len(cmd) == 0 {
   247  		return nil, fmt.Errorf("empty command: '%s'", input)
   248  	}
   249  	job := eng.Job(cmd[0], cmd[1:]...)
   250  	job.Env().Init(&env)
   251  	return job, nil
   252  }