forked from toolshed/abra
		
	| @ -29,5 +29,6 @@ var AppCommand = cli.Command{ | ||||
| 		appVolumeCommand, | ||||
| 		appVersionCommand, | ||||
| 		appErrorsCommand, | ||||
| 		appCmdCommand, | ||||
| 	}, | ||||
| } | ||||
|  | ||||
							
								
								
									
										214
									
								
								cli/app/cmd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								cli/app/cmd.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,214 @@ | ||||
| package app | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path" | ||||
| 	"strings" | ||||
|  | ||||
| 	"coopcloud.tech/abra/cli/internal" | ||||
| 	"coopcloud.tech/abra/pkg/autocomplete" | ||||
| 	"coopcloud.tech/abra/pkg/client" | ||||
| 	"coopcloud.tech/abra/pkg/config" | ||||
| 	containerPkg "coopcloud.tech/abra/pkg/container" | ||||
| 	"coopcloud.tech/abra/pkg/formatter" | ||||
| 	"coopcloud.tech/abra/pkg/upstream/container" | ||||
| 	"github.com/docker/cli/cli/command" | ||||
| 	"github.com/docker/docker/api/types" | ||||
| 	"github.com/docker/docker/api/types/filters" | ||||
| 	"github.com/docker/docker/pkg/archive" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/urfave/cli" | ||||
| ) | ||||
|  | ||||
| var localCmd bool | ||||
| var localCmdFlag = &cli.BoolFlag{ | ||||
| 	Name:        "local, l", | ||||
| 	Usage:       "Run command locally", | ||||
| 	Destination: &localCmd, | ||||
| } | ||||
|  | ||||
| var appCmdCommand = cli.Command{ | ||||
| 	Name:    "command", | ||||
| 	Aliases: []string{"cmd"}, | ||||
| 	Usage:   "Run app commands", | ||||
| 	Description: ` | ||||
| This command runs app specific commands. | ||||
|  | ||||
| These commands are bash functions, defined in the abra.sh of the recipe itself. | ||||
| They can be run within the context of a service (e.g. app) or locally on your | ||||
| work station by passing "--local". Arguments can be passed into these functions | ||||
| using the "-- <args>" syntax. | ||||
|  | ||||
| Example: | ||||
|  | ||||
|   abra app cmd example.com app create_user -- me@example.com | ||||
| `, | ||||
| 	ArgsUsage: "<domain> [<service>] <command>", | ||||
| 	Flags: []cli.Flag{ | ||||
| 		internal.DebugFlag, | ||||
| 		localCmdFlag, | ||||
| 	}, | ||||
| 	BashComplete: autocomplete.AppNameComplete, | ||||
| 	Before:       internal.SubCommandBefore, | ||||
| 	Action: func(c *cli.Context) error { | ||||
| 		app := internal.ValidateApp(c) | ||||
|  | ||||
| 		if len(c.Args()) <= 2 && !localCmd { | ||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("missing <service>/<command>? did you mean to pass --local?")) | ||||
| 		} | ||||
|  | ||||
| 		if len(c.Args()) > 2 && localCmd { | ||||
| 			internal.ShowSubcommandHelpAndError(c, errors.New("cannot specify <service> and --local together")) | ||||
| 		} | ||||
|  | ||||
| 		abraSh := path.Join(config.RECIPES_DIR, app.Recipe, "abra.sh") | ||||
| 		if _, err := os.Stat(abraSh); err != nil { | ||||
| 			if os.IsNotExist(err) { | ||||
| 				logrus.Fatalf("%s does not exist for %s?", abraSh, app.Name) | ||||
| 			} | ||||
| 			logrus.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if localCmd { | ||||
| 			cmdName := c.Args().Get(1) | ||||
| 			if err := ensureCommand(abraSh, app.Recipe, cmdName); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			logrus.Debugf("--local detected, running %s on local work station", cmdName) | ||||
|  | ||||
| 			sourceAndExec := fmt.Sprintf("TARGET=local; APP_NAME=%s; . %s; %s", app.StackName(), abraSh, cmdName) | ||||
| 			cmd := exec.Command("/bin/sh", "-c", sourceAndExec) | ||||
| 			if err := internal.RunCmd(cmd); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} else { | ||||
| 			targetServiceName := c.Args().Get(1) | ||||
|  | ||||
| 			cmdName := c.Args().Get(2) | ||||
| 			if err := ensureCommand(abraSh, app.Recipe, cmdName); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			serviceNames, err := config.GetAppServiceNames(app.Name) | ||||
| 			if err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			matchingServiceName := false | ||||
| 			for _, serviceName := range serviceNames { | ||||
| 				if serviceName == targetServiceName { | ||||
| 					matchingServiceName = true | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if !matchingServiceName { | ||||
| 				logrus.Fatalf("no service %s for %s?", targetServiceName, app.Name) | ||||
| 			} | ||||
|  | ||||
| 			logrus.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName) | ||||
|  | ||||
| 			var parsedCmdArgs string | ||||
| 			var cmdArgsIdx int | ||||
| 			var hasCmdArgs bool | ||||
| 			for idx, arg := range c.Args() { | ||||
| 				if arg == "--" { | ||||
| 					cmdArgsIdx = idx | ||||
| 					hasCmdArgs = true | ||||
| 				} | ||||
|  | ||||
| 				if hasCmdArgs && idx > cmdArgsIdx { | ||||
| 					parsedCmdArgs += fmt.Sprintf("%s ", c.Args().Get(idx)) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if hasCmdArgs { | ||||
| 				logrus.Debugf("parsed following command arguments: %s", parsedCmdArgs) | ||||
| 			} else { | ||||
| 				logrus.Debug("did not detect any command arguments") | ||||
| 			} | ||||
|  | ||||
| 			if err := runCmdRemote(app, abraSh, targetServiceName, cmdName, parsedCmdArgs); err != nil { | ||||
| 				logrus.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return nil | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| func ensureCommand(abraSh, recipeName, execCmd string) error { | ||||
| 	bytes, err := ioutil.ReadFile(abraSh) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if !strings.Contains(string(bytes), execCmd) { | ||||
| 		return fmt.Errorf("%s doesn't have a %s function", recipeName, execCmd) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func runCmdRemote(app config.App, abraSh, serviceName, cmdName, cmdArgs string) error { | ||||
| 	cl, err := client.New(app.Server) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	filters := filters.NewArgs() | ||||
| 	filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName)) | ||||
|  | ||||
| 	targetContainer, err := containerPkg.GetContainer(context.Background(), cl, filters, true) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	logrus.Debugf("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server) | ||||
|  | ||||
| 	toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip} | ||||
| 	content, err := archive.TarWithOptions(abraSh, toTarOpts) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false} | ||||
| 	if err := cl.CopyToContainer(context.Background(), targetContainer.ID, "/tmp", content, copyOpts); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	var cmd []string | ||||
| 	if cmdArgs != "" { | ||||
| 		cmd = []string{"/bin/sh", "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; . /tmp/abra.sh; %s %s", serviceName, app.StackName(), cmdName, cmdArgs)} | ||||
| 	} else { | ||||
| 		cmd = []string{"/bin/sh", "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; . /tmp/abra.sh; %s", serviceName, app.StackName(), cmdName)} | ||||
| 	} | ||||
|  | ||||
| 	logrus.Debugf("running command: %s", strings.Join(cmd, " ")) | ||||
|  | ||||
| 	execCreateOpts := types.ExecConfig{ | ||||
| 		AttachStderr: true, | ||||
| 		AttachStdin:  true, | ||||
| 		AttachStdout: true, | ||||
| 		Cmd:          cmd, | ||||
| 		Detach:       false, | ||||
| 		Tty:          true, | ||||
| 	} | ||||
|  | ||||
| 	// FIXME: avoid instantiating a new CLI | ||||
| 	dcli, err := command.NewDockerCli() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
		Reference in New Issue
	
	Block a user