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