github.com/cpuid/libcompose@v0.4.0/cli/app/app.go (about)

     1  package app
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/signal"
     7  	"strconv"
     8  	"strings"
     9  	"syscall"
    10  
    11  	"golang.org/x/net/context"
    12  
    13  	"github.com/Sirupsen/logrus"
    14  	"github.com/docker/libcompose/project"
    15  	"github.com/docker/libcompose/project/options"
    16  	"github.com/docker/libcompose/version"
    17  	"github.com/urfave/cli"
    18  )
    19  
    20  // ProjectAction is an adapter to allow the use of ordinary functions as libcompose actions.
    21  // Any function that has the appropriate signature can be register as an action on a codegansta/cli command.
    22  //
    23  // cli.Command{
    24  //		Name:   "ps",
    25  //		Usage:  "List containers",
    26  //		Action: app.WithProject(factory, app.ProjectPs),
    27  //	}
    28  type ProjectAction func(project project.APIProject, c *cli.Context) error
    29  
    30  // BeforeApp is an action that is executed before any cli command.
    31  func BeforeApp(c *cli.Context) error {
    32  	if c.GlobalBool("verbose") {
    33  		logrus.SetLevel(logrus.DebugLevel)
    34  	}
    35  
    36  	if version.ShowWarning() {
    37  		logrus.Warning("Note: This is an experimental alternate implementation of the Compose CLI (https://github.com/docker/compose)")
    38  	}
    39  	return nil
    40  }
    41  
    42  // WithProject is a helper function to create a cli.Command action with a ProjectFactory.
    43  func WithProject(factory ProjectFactory, action ProjectAction) func(context *cli.Context) error {
    44  	return func(context *cli.Context) error {
    45  		p, err := factory.Create(context)
    46  		if err != nil {
    47  			logrus.Fatalf("Failed to read project: %v", err)
    48  		}
    49  		return action(p, context)
    50  	}
    51  }
    52  
    53  // ProjectPs lists the containers.
    54  func ProjectPs(p project.APIProject, c *cli.Context) error {
    55  	qFlag := c.Bool("q")
    56  	allInfo, err := p.Ps(context.Background(), c.Args()...)
    57  	if err != nil {
    58  		return cli.NewExitError(err.Error(), 1)
    59  	}
    60  	columns := []string{"Name", "Command", "State", "Ports"}
    61  	if qFlag {
    62  		columns = []string{"Id"}
    63  	}
    64  	os.Stdout.WriteString(allInfo.String(columns, !qFlag))
    65  	return nil
    66  }
    67  
    68  // ProjectPort prints the public port for a port binding.
    69  func ProjectPort(p project.APIProject, c *cli.Context) error {
    70  	if len(c.Args()) != 2 {
    71  		return cli.NewExitError("Please pass arguments in the form: SERVICE PORT", 1)
    72  	}
    73  
    74  	index := c.Int("index")
    75  	protocol := c.String("protocol")
    76  	serviceName := c.Args()[0]
    77  	privatePort := c.Args()[1]
    78  
    79  	port, err := p.Port(context.Background(), index, protocol, serviceName, privatePort)
    80  	if err != nil {
    81  		return cli.NewExitError(err.Error(), 1)
    82  	}
    83  	fmt.Println(port)
    84  	return nil
    85  }
    86  
    87  // ProjectStop stops all services.
    88  func ProjectStop(p project.APIProject, c *cli.Context) error {
    89  	err := p.Stop(context.Background(), c.Int("timeout"), c.Args()...)
    90  	if err != nil {
    91  		return cli.NewExitError(err.Error(), 1)
    92  	}
    93  	return nil
    94  }
    95  
    96  // ProjectDown brings all services down (stops and clean containers).
    97  func ProjectDown(p project.APIProject, c *cli.Context) error {
    98  	options := options.Down{
    99  		RemoveVolume:  c.Bool("volumes"),
   100  		RemoveImages:  options.ImageType(c.String("rmi")),
   101  		RemoveOrphans: c.Bool("remove-orphans"),
   102  	}
   103  	err := p.Down(context.Background(), options, c.Args()...)
   104  	if err != nil {
   105  		return cli.NewExitError(err.Error(), 1)
   106  	}
   107  	return nil
   108  }
   109  
   110  // ProjectBuild builds or rebuilds services.
   111  func ProjectBuild(p project.APIProject, c *cli.Context) error {
   112  	config := options.Build{
   113  		NoCache:     c.Bool("no-cache"),
   114  		ForceRemove: c.Bool("force-rm"),
   115  		Pull:        c.Bool("pull"),
   116  	}
   117  	err := p.Build(context.Background(), config, c.Args()...)
   118  	if err != nil {
   119  		return cli.NewExitError(err.Error(), 1)
   120  	}
   121  	return nil
   122  }
   123  
   124  // ProjectCreate creates all services but do not start them.
   125  func ProjectCreate(p project.APIProject, c *cli.Context) error {
   126  	options := options.Create{
   127  		NoRecreate:    c.Bool("no-recreate"),
   128  		ForceRecreate: c.Bool("force-recreate"),
   129  		NoBuild:       c.Bool("no-build"),
   130  	}
   131  	err := p.Create(context.Background(), options, c.Args()...)
   132  	if err != nil {
   133  		return cli.NewExitError(err.Error(), 1)
   134  	}
   135  	return nil
   136  }
   137  
   138  // ProjectUp brings all services up.
   139  func ProjectUp(p project.APIProject, c *cli.Context) error {
   140  	options := options.Up{
   141  		Create: options.Create{
   142  			NoRecreate:    c.Bool("no-recreate"),
   143  			ForceRecreate: c.Bool("force-recreate"),
   144  			NoBuild:       c.Bool("no-build"),
   145  			ForceBuild:    c.Bool("build"),
   146  		},
   147  	}
   148  	ctx, cancelFun := context.WithCancel(context.Background())
   149  	err := p.Up(ctx, options, c.Args()...)
   150  	if err != nil {
   151  		return cli.NewExitError(err.Error(), 1)
   152  	}
   153  	if !c.Bool("d") {
   154  		signalChan := make(chan os.Signal, 1)
   155  		cleanupDone := make(chan bool)
   156  		signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
   157  		errChan := make(chan error)
   158  		go func() {
   159  			errChan <- p.Log(ctx, true, c.Args()...)
   160  		}()
   161  		go func() {
   162  			select {
   163  			case <-signalChan:
   164  				fmt.Printf("\nGracefully stopping...\n")
   165  				cancelFun()
   166  				ProjectStop(p, c)
   167  				cleanupDone <- true
   168  			case err := <-errChan:
   169  				if err != nil {
   170  					logrus.Fatal(err)
   171  				}
   172  				cleanupDone <- true
   173  			}
   174  		}()
   175  		<-cleanupDone
   176  		return nil
   177  	}
   178  	return nil
   179  }
   180  
   181  // ProjectRun runs a given command within a service's container.
   182  func ProjectRun(p project.APIProject, c *cli.Context) error {
   183  	if len(c.Args()) == 0 {
   184  		logrus.Fatal("No service specified")
   185  	}
   186  
   187  	serviceName := c.Args()[0]
   188  	commandParts := c.Args()[1:]
   189  
   190  	options := options.Run{
   191  		Detached: c.Bool("d"),
   192  	}
   193  
   194  	exitCode, err := p.Run(context.Background(), serviceName, commandParts, options)
   195  	if err != nil {
   196  		return cli.NewExitError(err.Error(), 1)
   197  	}
   198  	return cli.NewExitError("", exitCode)
   199  }
   200  
   201  // ProjectStart starts services.
   202  func ProjectStart(p project.APIProject, c *cli.Context) error {
   203  	err := p.Start(context.Background(), c.Args()...)
   204  	if err != nil {
   205  		return cli.NewExitError(err.Error(), 1)
   206  	}
   207  	return nil
   208  }
   209  
   210  // ProjectRestart restarts services.
   211  func ProjectRestart(p project.APIProject, c *cli.Context) error {
   212  	err := p.Restart(context.Background(), c.Int("timeout"), c.Args()...)
   213  	if err != nil {
   214  		return cli.NewExitError(err.Error(), 1)
   215  	}
   216  	return nil
   217  }
   218  
   219  // ProjectLog gets services logs.
   220  func ProjectLog(p project.APIProject, c *cli.Context) error {
   221  	err := p.Log(context.Background(), c.Bool("follow"), c.Args()...)
   222  	if err != nil {
   223  		return cli.NewExitError(err.Error(), 1)
   224  	}
   225  	return nil
   226  }
   227  
   228  // ProjectPull pulls images for services.
   229  func ProjectPull(p project.APIProject, c *cli.Context) error {
   230  	err := p.Pull(context.Background(), c.Args()...)
   231  	if err != nil && !c.Bool("ignore-pull-failures") {
   232  		return cli.NewExitError(err.Error(), 1)
   233  	}
   234  	return nil
   235  }
   236  
   237  // ProjectDelete deletes services.
   238  func ProjectDelete(p project.APIProject, c *cli.Context) error {
   239  	options := options.Delete{
   240  		RemoveVolume: c.Bool("v"),
   241  	}
   242  	if !c.Bool("force") {
   243  		stoppedContainers, err := p.Containers(context.Background(), project.Filter{
   244  			State: project.Stopped,
   245  		}, c.Args()...)
   246  		if err != nil {
   247  			return cli.NewExitError(err.Error(), 1)
   248  		}
   249  		if len(stoppedContainers) == 0 {
   250  			fmt.Println("No stopped containers")
   251  			return nil
   252  		}
   253  		fmt.Printf("Going to remove %v\nAre you sure? [yN]\n", strings.Join(stoppedContainers, ", "))
   254  		var answer string
   255  		_, err = fmt.Scanln(&answer)
   256  		if err != nil {
   257  			return cli.NewExitError(err.Error(), 1)
   258  		}
   259  		if answer != "y" && answer != "Y" {
   260  			return nil
   261  		}
   262  	}
   263  	err := p.Delete(context.Background(), options, c.Args()...)
   264  	if err != nil {
   265  		return cli.NewExitError(err.Error(), 1)
   266  	}
   267  	return nil
   268  }
   269  
   270  // ProjectKill forces stop service containers.
   271  func ProjectKill(p project.APIProject, c *cli.Context) error {
   272  	err := p.Kill(context.Background(), c.String("signal"), c.Args()...)
   273  	if err != nil {
   274  		return cli.NewExitError(err.Error(), 1)
   275  	}
   276  	return nil
   277  }
   278  
   279  // ProjectConfig validates and print the compose file.
   280  func ProjectConfig(p project.APIProject, c *cli.Context) error {
   281  	yaml, err := p.Config()
   282  	if err != nil {
   283  		return cli.NewExitError(err.Error(), 1)
   284  	}
   285  	if !c.Bool("quiet") {
   286  		fmt.Println(yaml)
   287  	}
   288  	return nil
   289  }
   290  
   291  // ProjectPause pauses service containers.
   292  func ProjectPause(p project.APIProject, c *cli.Context) error {
   293  	err := p.Pause(context.Background(), c.Args()...)
   294  	if err != nil {
   295  		return cli.NewExitError(err.Error(), 1)
   296  	}
   297  	return nil
   298  }
   299  
   300  // ProjectUnpause unpauses service containers.
   301  func ProjectUnpause(p project.APIProject, c *cli.Context) error {
   302  	err := p.Unpause(context.Background(), c.Args()...)
   303  	if err != nil {
   304  		return cli.NewExitError(err.Error(), 1)
   305  	}
   306  	return nil
   307  }
   308  
   309  // ProjectScale scales services.
   310  func ProjectScale(p project.APIProject, c *cli.Context) error {
   311  	servicesScale := map[string]int{}
   312  	for _, arg := range c.Args() {
   313  		kv := strings.SplitN(arg, "=", 2)
   314  		if len(kv) != 2 {
   315  			return cli.NewExitError(fmt.Sprintf("Invalid scale parameter: %s", arg), 2)
   316  		}
   317  
   318  		name := kv[0]
   319  
   320  		count, err := strconv.Atoi(kv[1])
   321  		if err != nil {
   322  			return cli.NewExitError(fmt.Sprintf("Invalid scale parameter: %v", err), 2)
   323  		}
   324  
   325  		servicesScale[name] = count
   326  	}
   327  
   328  	err := p.Scale(context.Background(), c.Int("timeout"), servicesScale)
   329  	if err != nil {
   330  		return cli.NewExitError(err.Error(), 0)
   331  	}
   332  	return nil
   333  }