Skip to content

Commit

Permalink
implement process entrypoint and run cmd
Browse files Browse the repository at this point in the history
 ### process entrypoint

`entrypoint` is an optional array of strings which can be set on the process config
to override default shell (and shellArg) used to run the `command`

When `entrypoint` is set and is non-empty, `command` can also be omitted.

This is useful if one want's to:
1. use a different shell per process
2. run any other executable directly, without a shell wrapper

 ### `run` cmd

Equivalent of [docker-compose run](https://docs.docker.com/engine/reference/commandline/compose_run/)
which can be used to run a chosen process in the foreground (with std(in|out|err) attached),
while it's dependencies are ran in the background (no logs printed to stdio).

Deps can be disabled via --no-deps flag.

Additional arguments can be passed to the process after `--` separator, for example:

```sh
process-compose run main -- arg1 arg2
```

The separator is necessary in order to distinguish process-compose flags from flags
meant to be passed to the process.

While the process is running, the logs of the deps can be inspected using the
usual command:

```
process-compose process logs dep
```

The process being ran will have `availability.exit_on_end` set to `true` and
manually setting it to `false` will have no effect.
  • Loading branch information
adrian-gierakowski committed Oct 5, 2023
1 parent 5d7d23d commit 29dba80
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 33 deletions.
1 change: 1 addition & 0 deletions src/app/commander_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ type Commander interface {
StderrPipe() (io.ReadCloser, error)
Stop(int, bool) error
SetCmdArgs()
AttachIo()
}
64 changes: 47 additions & 17 deletions src/app/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,22 @@ type Process struct {
liveProber *health.Prober
readyProber *health.Prober
shellConfig command.ShellConfig
}

func NewProcess(globalEnv []string, logger pclog.PcLogger, procConf *types.ProcessConfig, processState *types.ProcessState, procLog *pclog.ProcessLogBuffer, shellConfig command.ShellConfig) *Process {
printLogs bool
isMain bool
extraArgs []string
}

func NewProcess(
globalEnv []string,
logger pclog.PcLogger,
procConf *types.ProcessConfig,
processState *types.ProcessState,
procLog *pclog.ProcessLogBuffer,
shellConfig command.ShellConfig,
printLogs bool,
isMain bool,
extraArgs []string,
) *Process {
colNumeric := rand.Intn(int(color.FgHiWhite)-int(color.FgHiBlack)) + int(color.FgHiBlack)

proc := &Process{
Expand All @@ -71,6 +84,9 @@ func NewProcess(globalEnv []string, logger pclog.PcLogger, procConf *types.Proce
shellConfig: shellConfig,
procStateChan: make(chan string, 1),
procReadyChan: make(chan string, 1),
printLogs: printLogs,
isMain: isMain,
extraArgs: extraArgs,
}

proc.procReadyCtx, proc.readyCancelFn = context.WithCancel(context.Background())
Expand All @@ -85,7 +101,7 @@ func (p *Process) run() int {
}

if err := p.validateProcess(); err != nil {
log.Error().Err(err).Msgf("Failed to run command %s for process %s", p.getCommand(), p.getName())
log.Error().Err(err).Msgf(`Failed to run command ["%v"] for process %s`, strings.Join(p.getCommand(), `" "`), p.getName())
p.onProcessEnd(types.ProcessStateError)
return 1
}
Expand All @@ -94,7 +110,7 @@ func (p *Process) run() int {
for {
err := p.setStateAndRun(p.getStartingStateName(), p.getProcessStarter())
if err != nil {
log.Error().Err(err).Msgf("Failed to run command %s for process %s", p.getCommand(), p.getName())
log.Error().Err(err).Msgf(`Failed to run command ["%v"] for process %s`, strings.Join(p.getCommand(), `" "`), p.getName())
p.logBuffer.Write(err.Error())
p.onProcessEnd(types.ProcessStateError)
return 1
Expand Down Expand Up @@ -144,16 +160,23 @@ func (p *Process) run() int {

func (p *Process) getProcessStarter() func() error {
return func() error {
p.command = command.BuildCommandShellArg(p.shellConfig, p.getCommand())
p.command = command.BuildCommand(
p.procConf.Executable,
append(p.procConf.Args, p.extraArgs...),
)
p.command.SetEnv(p.getProcessEnvironment())
p.command.SetDir(p.procConf.WorkingDir)
p.command.SetCmdArgs()
stdout, _ := p.command.StdoutPipe()
stderr, _ := p.command.StderrPipe()
go p.handleOutput(stdout, p.handleInfo)
go p.handleOutput(stderr, p.handleError)
//stdin, _ := p.command.StdinPipe()
//go p.handleInput(stdin)

if p.isMain {
p.command.AttachIo()
} else {
p.command.SetCmdArgs()
stdout, _ := p.command.StdoutPipe()
stderr, _ := p.command.StderrPipe()
go p.handleOutput(stdout, p.handleInfo)
go p.handleOutput(stderr, p.handleError)
}

return p.command.Start()
}
}
Expand Down Expand Up @@ -339,8 +362,11 @@ func (p *Process) getNameWithSmartReplica() string {
return p.procConf.Name
}

func (p *Process) getCommand() string {
return p.procConf.Command
func (p *Process) getCommand() []string {
return append(
[]string{(*p.procConf).Executable},
append(p.procConf.Args, p.extraArgs...)...,
)
}

func (p *Process) updateProcState() {
Expand Down Expand Up @@ -394,13 +420,17 @@ func (p *Process) handleOutput(pipe io.ReadCloser, handler func(message string))

func (p *Process) handleInfo(message string) {
p.logger.Info(message, p.getName(), p.procConf.ReplicaNum)
fmt.Printf("[%s\t] %s\n", p.procColor(p.getName()), message)
if p.printLogs {
fmt.Printf("[%s\t] %s\n", p.procColor(p.getName()), message)
}
p.logBuffer.Write(message)
}

func (p *Process) handleError(message string) {
p.logger.Error(message, p.getName(), p.procConf.ReplicaNum)
fmt.Printf("[%s\t] %s\n", p.procColor(p.getName()), p.redColor(message))
if p.printLogs {
fmt.Printf("[%s\t] %s\n", p.procColor(p.getName()), p.redColor(message))
}
p.logBuffer.Write(message)
}

Expand Down
32 changes: 30 additions & 2 deletions src/app/project_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type ProjectRunner struct {
logger pclog.PcLogger
waitGroup sync.WaitGroup
exitCode int
mainProcess string
mainProcessArgs []string
}

func (p *ProjectRunner) GetLexicographicProcessNames() ([]string, error) {
Expand Down Expand Up @@ -83,7 +85,25 @@ func (p *ProjectRunner) runProcess(config *types.ProcessConfig) {
procLog = pclog.NewLogBuffer(0)
}
procState, _ := p.GetProcessState(config.ReplicaName)
process := NewProcess(p.project.Environment, procLogger, config, procState, procLog, *p.project.ShellConfig)
isMain := config.Name == p.mainProcess
hasMain := p.mainProcess != ""
printLogs := !hasMain
extraArgs := []string{}
if isMain {
extraArgs = p.mainProcessArgs
config.RestartPolicy.ExitOnEnd = true
}
process := NewProcess(
p.project.Environment,
procLogger,
config,
procState,
procLog,
*p.project.ShellConfig,
printLogs,
isMain,
extraArgs,
)
p.addRunningProcess(process)
p.waitGroup.Add(1)
go func() {
Expand Down Expand Up @@ -560,10 +580,18 @@ func (p *ProjectRunner) GetProject() *types.Project {
return p.project
}

func NewProjectRunner(project *types.Project, processesToRun []string, noDeps bool) (*ProjectRunner, error) {
func NewProjectRunner(
project *types.Project,
processesToRun []string,
noDeps bool,
mainProcess string,
mainProcessArgs []string,
) (*ProjectRunner, error) {

runner := &ProjectRunner{
project: project,
mainProcess: mainProcess,
mainProcessArgs: mainProcessArgs,
}

var err error
Expand Down
10 changes: 5 additions & 5 deletions src/app/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func TestSystem_TestFixtures(t *testing.T) {
t.Errorf(err.Error())
return
}
runner, err := NewProjectRunner(project, []string{}, false)
runner, err := NewProjectRunner(project, []string{}, false, "", []string{})
if err != nil {
t.Errorf(err.Error())
return
Expand All @@ -48,7 +48,7 @@ func TestSystem_TestComposeWithLog(t *testing.T) {
t.Errorf(err.Error())
return
}
runner, err := NewProjectRunner(project, []string{}, false)
runner, err := NewProjectRunner(project, []string{}, false, "", []string{})
if err != nil {
t.Errorf(err.Error())
return
Expand Down Expand Up @@ -81,7 +81,7 @@ func TestSystem_TestComposeChain(t *testing.T) {
t.Errorf(err.Error())
return
}
runner, err := NewProjectRunner(project, []string{}, false)
runner, err := NewProjectRunner(project, []string{}, false, "", []string{})
if err != nil {
t.Errorf(err.Error())
return
Expand Down Expand Up @@ -117,7 +117,7 @@ func TestSystem_TestComposeChainExit(t *testing.T) {
t.Errorf(err.Error())
return
}
runner, err := NewProjectRunner(project, []string{}, false)
runner, err := NewProjectRunner(project, []string{}, false, "", []string{})
if err != nil {
t.Errorf(err.Error())
return
Expand Down Expand Up @@ -162,7 +162,7 @@ func TestSystem_TestComposeScale(t *testing.T) {
t.Errorf(err.Error())
return
}
runner, err := NewProjectRunner(project, []string{}, false)
runner, err := NewProjectRunner(project, []string{}, false, "", []string{})
if err != nil {
t.Errorf(err.Error())
return
Expand Down
4 changes: 2 additions & 2 deletions src/cmd/project_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"time"
)

func getProjectRunner(process []string, noDeps bool) *app.ProjectRunner {
func getProjectRunner(process []string, noDeps bool, mainProcess string, mainProcessArgs []string) *app.ProjectRunner {
if *pcFlags.HideDisabled {
opts.AddAdmitter(&admitter.DisabledProcAdmitter{})
}
Expand All @@ -23,7 +23,7 @@ func getProjectRunner(process []string, noDeps bool) *app.ProjectRunner {
log.Fatal().Msg(err.Error())
}

runner, err := app.NewProjectRunner(project, process, noDeps)
runner, err := app.NewProjectRunner(project, process, noDeps, mainProcess, mainProcessArgs)
if err != nil {
fmt.Println(err)
log.Fatal().Msg(err.Error())
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func run(cmd *cobra.Command, args []string) {
defer func() {
_ = logFile.Close()
}()
runner := getProjectRunner([]string{}, false)
runner := getProjectRunner([]string{}, false, "", []string{})
api.StartHttpServer(!*pcFlags.Headless, *pcFlags.PortNum, runner)
runProject(runner)
}
Expand Down
54 changes: 54 additions & 0 deletions src/cmd/run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmd

import (
"fmt"
"os"
"github.com/f1bonacc1/process-compose/src/api"
"github.com/spf13/cobra"
)

// runCmd represents the up command
var runCmd = &cobra.Command{
Use: "run PROCESS [flags] -- [process_args]",
Short: "Run PROCESS in the foreground, and it's dependencies in the background",
Long: `Run selected process with std(in|out|err) attached, while other processes run in the background.
Command line arguments, provided after --, are passed to the PROCESS.`,
Args: cobra.MinimumNArgs(1),
// Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
*pcFlags.Headless = false

processName := args[0]

if len(args) > 1 {
argsLenAtDash := cmd.ArgsLenAtDash()
if argsLenAtDash != 1 {
message := "Extra positional arguments provided! To pass args to PROCESS, separate them from process-compose arguments with: --"
fmt.Println(message)
os.Exit(1)
}
args = args[argsLenAtDash:]
} else {
// Clease args as they will contain the processName
args = []string{}
}

runner := getProjectRunner(
[]string{processName},
*pcFlags.NoDependencies,
processName,
args,
)

api.StartHttpServer(false, *pcFlags.PortNum, runner)
runProject(runner)
},
}

func init() {
rootCmd.AddCommand(runCmd)

runCmd.Flags().BoolVarP(pcFlags.NoDependencies, "no-deps", "", *pcFlags.NoDependencies, "don't start dependent processes")
runCmd.Flags().AddFlag(rootCmd.Flags().Lookup("config"))

}
2 changes: 1 addition & 1 deletion src/cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var upCmd = &cobra.Command{
If one or more process names are passed as arguments,
will start them and their dependencies only`,
Run: func(cmd *cobra.Command, args []string) {
runner := getProjectRunner(args, *pcFlags.NoDependencies)
runner := getProjectRunner(args, *pcFlags.NoDependencies, "", []string{})
api.StartHttpServer(!*pcFlags.Headless, *pcFlags.PortNum, runner)
runProject(runner)
},
Expand Down
5 changes: 3 additions & 2 deletions src/command/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
"runtime"
)

func BuildCommandShellArg(shell ShellConfig, cmd string) *CmdWrapper {

func BuildCommand(cmd string, args []string) *CmdWrapper {
return &CmdWrapper{
cmd: exec.Command(shell.ShellCommand, shell.ShellArgument, cmd),
cmd: exec.Command(cmd, args...),
}
//return NewMockCommand()
}
Expand Down
7 changes: 7 additions & 0 deletions src/command/command_wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package command

import (
"io"
"os"
"os/exec"
)

Expand Down Expand Up @@ -37,6 +38,12 @@ func (c *CmdWrapper) StderrPipe() (io.ReadCloser, error) {
return c.cmd.StderrPipe()
}

func (c *CmdWrapper) AttachIo() () {
c.cmd.Stdin = os.Stdin
c.cmd.Stdout = os.Stdout
c.cmd.Stderr = os.Stderr
}

func (c *CmdWrapper) SetEnv(env []string) {
c.cmd.Env = env
}
Expand Down
6 changes: 4 additions & 2 deletions src/tui/proc-info-form.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ func (pv *pcView) createProcInfoForm(info *types.ProcessConfig, ports *types.Pro
f.SetFieldTextColor(tcell.ColorBlack)
f.SetButtonsAlign(tview.AlignCenter)
f.SetTitle("Process " + info.Name + " Info")
addStringIfNotEmpty("Command:", info.Command, f)
addStringIfNotEmpty("Entrypoint:", strings.Join(*info.Entrypoint, " "), f)
if info.Command != nil {
addStringIfNotEmpty("Command:", *info.Command, f)
}
addStringIfNotEmpty("Working Directory:", info.WorkingDir, f)
addStringIfNotEmpty("Log Location:", info.LogLocation, f)
f.AddInputField("Replica:", fmt.Sprintf("%d/%d", info.ReplicaNum+1, info.Replicas), 0, nil, nil)
addDropDownIfNotEmpty("Environment:", info.Environment, f)
addCSVIfNotEmpty("Depends On:", mapKeysToSlice(info.DependsOn), f)
if ports != nil {
Expand Down
5 changes: 4 additions & 1 deletion src/types/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ type ProcessConfig struct {
Name string
Disabled bool `yaml:"disabled,omitempty"`
IsDaemon bool `yaml:"is_daemon,omitempty"`
Command string `yaml:"command"`
Command *string `yaml:"command,omitempty"`
Entrypoint *[]string `yaml:"entrypoint,omitempty"`
LogLocation string `yaml:"log_location,omitempty"`
Environment Environment `yaml:"environment,omitempty"`
RestartPolicy RestartPolicyConfig `yaml:"availability,omitempty"`
Expand All @@ -30,6 +31,8 @@ type ProcessConfig struct {
Extensions map[string]interface{} `yaml:",inline"`
ReplicaNum int
ReplicaName string
Executable string
Args []string
}

func (p *ProcessConfig) GetDependencies() []string {
Expand Down
Loading

0 comments on commit 29dba80

Please sign in to comment.