@@ -14,6 +14,7 @@ import (
1414
1515 "github.com/Sirupsen/logrus"
1616 "github.com/docker/docker/pkg/jsonmessage"
17+ "github.com/docker/docker/pkg/promise"
1718 "github.com/docker/docker/pkg/stdcopy"
1819 "github.com/docker/docker/pkg/term"
1920 "github.com/docker/docker/reference"
@@ -145,7 +146,7 @@ func (c *Container) Recreate(imageName string) (*types.Container, error) {
145146 return nil , err
146147 }
147148
148- newContainer , err := c .createContainer (imageName , info .ID )
149+ newContainer , err := c .createContainer (imageName , info .ID , nil )
149150 if err != nil {
150151 return nil , err
151152 }
@@ -168,13 +169,20 @@ func (c *Container) Recreate(imageName string) (*types.Container, error) {
168169// to notify the container has been created. If the container already exists, does
169170// nothing.
170171func (c * Container ) Create (imageName string ) (* types.Container , error ) {
172+ return c .CreateWithOverride (imageName , nil )
173+ }
174+
175+ // CreateWithOverride create container and override parts of the config to
176+ // allow special situations to override the config generated from the compose
177+ // file
178+ func (c * Container ) CreateWithOverride (imageName string , configOverride * project.ServiceConfig ) (* types.Container , error ) {
171179 container , err := c .findExisting ()
172180 if err != nil {
173181 return nil , err
174182 }
175183
176184 if container == nil {
177- container , err = c .createContainer (imageName , "" )
185+ container , err = c .createContainer (imageName , "" , configOverride )
178186 if err != nil {
179187 return nil , err
180188 }
@@ -274,6 +282,126 @@ func (c *Container) IsRunning() (bool, error) {
274282 return info .State .Running , nil
275283}
276284
285+ // Run creates, start and attach to the container based on the image name,
286+ // the specified configuration.
287+ // It will always create a new container.
288+ func (c * Container ) Run (imageName string , configOverride * project.ServiceConfig ) (int , error ) {
289+ var (
290+ errCh chan error
291+ out , stderr io.Writer
292+ in io.ReadCloser
293+ )
294+
295+ container , err := c .createContainer (imageName , "" , configOverride )
296+ if err != nil {
297+ return - 1 , err
298+ }
299+
300+ info , err := c .client .ContainerInspect (context .Background (), container .ID )
301+ if err != nil {
302+ return - 1 , err
303+ }
304+
305+ if configOverride .StdinOpen {
306+ in = os .Stdin
307+ }
308+ if configOverride .Tty {
309+ out = os .Stdout
310+ }
311+ if configOverride .Tty {
312+ stderr = os .Stderr
313+ }
314+
315+ options := types.ContainerAttachOptions {
316+ ContainerID : container .ID ,
317+ Stream : true ,
318+ Stdin : configOverride .StdinOpen ,
319+ Stdout : configOverride .Tty ,
320+ Stderr : configOverride .Tty ,
321+ }
322+
323+ resp , err := c .client .ContainerAttach (context .Background (), options )
324+ if err != nil {
325+ return - 1 , err
326+ }
327+
328+ // set raw terminal
329+ inFd , _ := term .GetFdInfo (in )
330+ state , err := term .SetRawTerminal (inFd )
331+ if err != nil {
332+ return - 1 , err
333+ }
334+ // restore raw terminal
335+ defer term .RestoreTerminal (inFd , state )
336+ // holdHijackedConnection (in goroutine)
337+ errCh = promise .Go (func () error {
338+ return holdHijackedConnection (configOverride .Tty , in , out , stderr , resp )
339+ })
340+
341+ if err := c .client .ContainerStart (context .Background (), container .ID ); err != nil {
342+ return - 1 , err
343+ }
344+
345+ if err := <- errCh ; err != nil {
346+ logrus .Debugf ("Error hijack: %s" , err )
347+ return - 1 , err
348+ }
349+
350+ info , err = c .client .ContainerInspect (context .Background (), container .ID )
351+ if err != nil {
352+ return - 1 , err
353+ }
354+
355+ return info .State .ExitCode , nil
356+ }
357+
358+ func holdHijackedConnection (tty bool , inputStream io.ReadCloser , outputStream , errorStream io.Writer , resp types.HijackedResponse ) error {
359+ var err error
360+ receiveStdout := make (chan error , 1 )
361+ if outputStream != nil || errorStream != nil {
362+ go func () {
363+ // When TTY is ON, use regular copy
364+ if tty && outputStream != nil {
365+ _ , err = io .Copy (outputStream , resp .Reader )
366+ } else {
367+ _ , err = stdcopy .StdCopy (outputStream , errorStream , resp .Reader )
368+ }
369+ logrus .Debugf ("[hijack] End of stdout" )
370+ receiveStdout <- err
371+ }()
372+ }
373+
374+ stdinDone := make (chan struct {})
375+ go func () {
376+ if inputStream != nil {
377+ io .Copy (resp .Conn , inputStream )
378+ logrus .Debugf ("[hijack] End of stdin" )
379+ }
380+
381+ if err := resp .CloseWrite (); err != nil {
382+ logrus .Debugf ("Couldn't send EOF: %s" , err )
383+ }
384+ close (stdinDone )
385+ }()
386+
387+ select {
388+ case err := <- receiveStdout :
389+ if err != nil {
390+ logrus .Debugf ("Error receiveStdout: %s" , err )
391+ return err
392+ }
393+ case <- stdinDone :
394+ if outputStream != nil || errorStream != nil {
395+ if err := <- receiveStdout ; err != nil {
396+ logrus .Debugf ("Error receiveStdout: %s" , err )
397+ return err
398+ }
399+ }
400+ }
401+
402+ return nil
403+ }
404+
277405// Up creates and start the container based on the image name and send an event
278406// to notify the container has been created. If the container exists but is stopped
279407// it tries to start it.
@@ -297,20 +425,25 @@ func (c *Container) Up(imageName string) error {
297425 }
298426
299427 if ! info .State .Running {
300- logrus .WithFields (logrus.Fields {"container.ID" : container .ID , "c.name" : c .name }).Debug ("Starting container" )
301- if err = c .client .ContainerStart (context .Background (), container .ID ); err != nil {
302- logrus .WithFields (logrus.Fields {"container.ID" : container .ID , "c.name" : c .name }).Debug ("Failed to start container" )
303- return err
304- }
305-
306- c .service .context .Project .Notify (project .EventContainerStarted , c .service .Name (), map [string ]string {
307- "name" : c .Name (),
308- })
428+ c .Start (container )
309429 }
310430
311431 return nil
312432}
313433
434+ // Start the specified container with the specified host config
435+ func (c * Container ) Start (container * types.Container ) error {
436+ logrus .WithFields (logrus.Fields {"container.ID" : container .ID , "c.name" : c .name }).Debug ("Starting container" )
437+ if err := c .client .ContainerStart (context .Background (), container .ID ); err != nil {
438+ logrus .WithFields (logrus.Fields {"container.ID" : container .ID , "c.name" : c .name }).Debug ("Failed to start container" )
439+ return err
440+ }
441+ c .service .context .Project .Notify (project .EventContainerStarted , c .service .Name (), map [string ]string {
442+ "name" : c .Name (),
443+ })
444+ return nil
445+ }
446+
314447// OutOfSync checks if the container is out of sync with the service definition.
315448// It looks if the the service hash container label is the same as the computed one.
316449func (c * Container ) OutOfSync (imageName string ) (bool , error ) {
@@ -356,7 +489,13 @@ func volumeBinds(volumes map[string]struct{}, container *types.ContainerJSON) []
356489 return result
357490}
358491
359- func (c * Container ) createContainer (imageName , oldContainer string ) (* types.Container , error ) {
492+ func (c * Container ) createContainer (imageName , oldContainer string , configOverride * project.ServiceConfig ) (* types.Container , error ) {
493+ serviceConfig := c .service .serviceConfig
494+ if configOverride != nil {
495+ serviceConfig .Command = configOverride .Command
496+ serviceConfig .Tty = configOverride .Tty
497+ serviceConfig .StdinOpen = configOverride .StdinOpen
498+ }
360499 configWrapper , err := ConvertToAPI (c .service )
361500 if err != nil {
362501 return nil , err
0 commit comments