engine.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package engine
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "sort"
  8. "strings"
  9. "github.com/dotcloud/docker/utils"
  10. )
  11. // Installer is a standard interface for objects which can "install" themselves
  12. // on an engine by registering handlers.
  13. // This can be used as an entrypoint for external plugins etc.
  14. type Installer interface {
  15. Install(*Engine) error
  16. }
  17. type Handler func(*Job) Status
  18. var globalHandlers map[string]Handler
  19. func init() {
  20. globalHandlers = make(map[string]Handler)
  21. }
  22. func Register(name string, handler Handler) error {
  23. _, exists := globalHandlers[name]
  24. if exists {
  25. return fmt.Errorf("Can't overwrite global handler for command %s", name)
  26. }
  27. globalHandlers[name] = handler
  28. return nil
  29. }
  30. func unregister(name string) {
  31. delete(globalHandlers, name)
  32. }
  33. // The Engine is the core of Docker.
  34. // It acts as a store for *containers*, and allows manipulation of these
  35. // containers by executing *jobs*.
  36. type Engine struct {
  37. handlers map[string]Handler
  38. catchall Handler
  39. hack Hack // data for temporary hackery (see hack.go)
  40. id string
  41. Stdout io.Writer
  42. Stderr io.Writer
  43. Stdin io.Reader
  44. Logging bool
  45. }
  46. func (eng *Engine) Register(name string, handler Handler) error {
  47. _, exists := eng.handlers[name]
  48. if exists {
  49. return fmt.Errorf("Can't overwrite handler for command %s", name)
  50. }
  51. eng.handlers[name] = handler
  52. return nil
  53. }
  54. func (eng *Engine) RegisterCatchall(catchall Handler) {
  55. eng.catchall = catchall
  56. }
  57. // New initializes a new engine.
  58. func New() *Engine {
  59. eng := &Engine{
  60. handlers: make(map[string]Handler),
  61. id: utils.RandomString(),
  62. Stdout: os.Stdout,
  63. Stderr: os.Stderr,
  64. Stdin: os.Stdin,
  65. Logging: true,
  66. }
  67. eng.Register("commands", func(job *Job) Status {
  68. for _, name := range eng.commands() {
  69. job.Printf("%s\n", name)
  70. }
  71. return StatusOK
  72. })
  73. // Copy existing global handlers
  74. for k, v := range globalHandlers {
  75. eng.handlers[k] = v
  76. }
  77. return eng
  78. }
  79. func (eng *Engine) String() string {
  80. return fmt.Sprintf("%s", eng.id[:8])
  81. }
  82. // Commands returns a list of all currently registered commands,
  83. // sorted alphabetically.
  84. func (eng *Engine) commands() []string {
  85. names := make([]string, 0, len(eng.handlers))
  86. for name := range eng.handlers {
  87. names = append(names, name)
  88. }
  89. sort.Strings(names)
  90. return names
  91. }
  92. // Job creates a new job which can later be executed.
  93. // This function mimics `Command` from the standard os/exec package.
  94. func (eng *Engine) Job(name string, args ...string) *Job {
  95. job := &Job{
  96. Eng: eng,
  97. Name: name,
  98. Args: args,
  99. Stdin: NewInput(),
  100. Stdout: NewOutput(),
  101. Stderr: NewOutput(),
  102. env: &Env{},
  103. }
  104. if eng.Logging {
  105. job.Stderr.Add(utils.NopWriteCloser(eng.Stderr))
  106. }
  107. // Catchall is shadowed by specific Register.
  108. if handler, exists := eng.handlers[name]; exists {
  109. job.handler = handler
  110. } else if eng.catchall != nil && name != "" {
  111. // empty job names are illegal, catchall or not.
  112. job.handler = eng.catchall
  113. }
  114. return job
  115. }
  116. // ParseJob creates a new job from a text description using a shell-like syntax.
  117. //
  118. // The following syntax is used to parse `input`:
  119. //
  120. // * Words are separated using standard whitespaces as separators.
  121. // * Quotes and backslashes are not interpreted.
  122. // * Words of the form 'KEY=[VALUE]' are added to the job environment.
  123. // * All other words are added to the job arguments.
  124. //
  125. // For example:
  126. //
  127. // job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world")
  128. //
  129. // The resulting job will have:
  130. // job.Args={"echo", "hello", "world"}
  131. // job.Env={"VERBOSE":"1", "TEST":"true"}
  132. //
  133. func (eng *Engine) ParseJob(input string) (*Job, error) {
  134. // FIXME: use a full-featured command parser
  135. scanner := bufio.NewScanner(strings.NewReader(input))
  136. scanner.Split(bufio.ScanWords)
  137. var (
  138. cmd []string
  139. env Env
  140. )
  141. for scanner.Scan() {
  142. word := scanner.Text()
  143. kv := strings.SplitN(word, "=", 2)
  144. if len(kv) == 2 {
  145. env.Set(kv[0], kv[1])
  146. } else {
  147. cmd = append(cmd, word)
  148. }
  149. }
  150. if len(cmd) == 0 {
  151. return nil, fmt.Errorf("empty command: '%s'", input)
  152. }
  153. job := eng.Job(cmd[0], cmd[1:]...)
  154. job.Env().Init(&env)
  155. return job, nil
  156. }
  157. func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
  158. if !eng.Logging {
  159. return 0, nil
  160. }
  161. prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
  162. return fmt.Fprintf(eng.Stderr, prefixedFormat, args...)
  163. }