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 }