diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 536df1f..dc54f11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,9 +21,9 @@ jobs: redis-version: 6 - name: Set up Go - uses: actions/setup-go@v2 + uses: zmicro-design/action-setup-go@v1 with: - go-version: 1.18 + go-version: v1.20 - name: install deps run: | @@ -31,12 +31,14 @@ jobs: go install golang.org/x/tools/cmd/goimports@latest go install golang.org/x/lint/golint@latest go install github.com/mattn/goveralls@latest + - name: static analysis run: | golint -set_exit_status go vet test -z "$(goimports -l .)" + - name: Test run: goveralls -service=github env: - COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..ef60782 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: release + +on: + push: + tags: + - 'v*' + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: zmicro-design/action-setup-go@v1 + with: + go-version: v1.20 + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v4 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..368f2a0 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,23 @@ +env: + - GO111MODULE=on + +builds: + - env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + # - windows + goarch: + - amd64 + - arm64 + # - arm + # - "386" + goarm: + - "7" + mod_timestamp: "{{ .CommitTimestamp }}" + flags: + - -trimpath + ldflags: + - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{ .CommitDate }} -X main.builtBy=go-zoox + main: ./cmd/command-runner diff --git a/cancel.go b/cancel.go new file mode 100644 index 0000000..f1b166d --- /dev/null +++ b/cancel.go @@ -0,0 +1,6 @@ +package command + +// Cancel cancels the command. +func (c *command) Cancel() error { + return c.engine.Cancel() +} diff --git a/cmd/command-runner/commands/exec.go b/cmd/command-runner/commands/exec.go new file mode 100644 index 0000000..dfaff7d --- /dev/null +++ b/cmd/command-runner/commands/exec.go @@ -0,0 +1,262 @@ +package commands + +import ( + "context" + "fmt" + "io" + "os" + "os/signal" + "syscall" + "time" + + "github.com/eiannone/keyboard" + "github.com/go-zoox/cli" + "github.com/go-zoox/command" + "github.com/go-zoox/command/terminal" + "github.com/go-zoox/fs" + + "golang.org/x/term" +) + +// Exec is the exec command +func Exec(app *cli.MultipleProgram) { + app.Register("exec", &cli.Command{ + Name: "exec", + Usage: "command execute", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "engine", + Usage: "command engine", + Aliases: []string{"e"}, + EnvVars: []string{"ENGINE"}, + Value: "host", + }, + &cli.StringFlag{ + Name: "command", + Usage: "the command", + Aliases: []string{"c"}, + EnvVars: []string{"COMMAND"}, + }, + &cli.StringFlag{ + Name: "workdir", + Usage: "the command workdir", + Aliases: []string{"w"}, + EnvVars: []string{"WORKDIR"}, + Value: fs.CurrentDir(), + }, + &cli.StringFlag{ + Name: "user", + Usage: "the command user", + Aliases: []string{"u"}, + // EnvVars: []string{"WORKDIR"}, + }, + &cli.StringFlag{ + Name: "shell", + Usage: "the command shell", + Aliases: []string{"s"}, + // EnvVars: []string{"SHELL"}, + }, + &cli.StringFlag{ + Name: "image", + Usage: "docker image", + Aliases: []string{"i"}, + EnvVars: []string{"IMAGE"}, + }, + &cli.BoolFlag{ + Name: "tty", + Usage: "Allocate a pseudo-TTY. The default is false, which disables TTY allocation.", + Aliases: []string{"t"}, + EnvVars: []string{"TTY"}, + }, + &cli.Int64Flag{ + Name: "memory", + Usage: `Memory limit, unit: MB`, + Aliases: []string{"m"}, + EnvVars: []string{"MEMORY"}, + }, + &cli.Float64Flag{ + Name: "cpu", + Usage: `CPU limit, unit: core`, + EnvVars: []string{"CPU"}, + }, + &cli.StringFlag{ + Name: "platform", + Usage: `Command platform, available: linux/amd64, linux/arm64`, + Aliases: []string{"p"}, + EnvVars: []string{"PLATFORM"}, + }, + &cli.StringFlag{ + Name: "network", + Usage: `Network name`, + Aliases: []string{"n"}, + EnvVars: []string{"NETWORK"}, + }, + &cli.BoolFlag{ + Name: "disable-network", + Usage: "Disable network visit", + EnvVars: []string{"DISABLE_NETWORK"}, + }, + }, + Action: func(ctx *cli.Context) (err error) { + cmd, err := command.New(context.Background(), &command.Config{ + Engine: ctx.String("engine"), + Command: ctx.String("command"), + WorkDir: ctx.String("workdir"), + User: ctx.String("user"), + Shell: ctx.String("shell"), + Image: ctx.String("image"), + Memory: ctx.Int64("memory"), + CPU: ctx.Float64("cpu"), + Platform: ctx.String("platform"), + Network: ctx.String("network"), + DisableNetwork: ctx.Bool("disable-network"), + }) + if err != nil { + return err + } + + if ctx.Bool("tty") { + term, err := cmd.Terminal() + if err != nil { + return err + } + defer term.Close() + + go func() { + io.Copy(os.Stdout, term) + // _, err := io.Copy(os.Stdout, term) + // if err != nil && err != io.EOF { + // os.Stderr.Write([]byte(err.Error())) + // os.Exit(term.ExitCode()) + // return + // } + }() + + if err := connectKeyboard(term); err != nil { + return err + } + + return nil + } + + return cmd.Run() + }, + }) +} + +func connectKeyboard(t terminal.Terminal) error { + // resize + if err := resizeTerminal(t); err != nil { + return err + } + + // 监听操作系统信号 + sigWinch := make(chan os.Signal, 1) + signal.Notify(sigWinch, syscall.SIGWINCH) + // 启动循环来检测终端窗口大小是否发生变化 + go func() { + for { + select { + case <-sigWinch: + resizeTerminal(t) + default: + time.Sleep(time.Millisecond * 100) + } + } + }() + + if err := keyboard.Open(); err != nil { + return err + } + defer func() { + _ = keyboard.Close() + }() + + for { + char, key, err := keyboard.GetKey() + if err != nil { + return err + } + + // fmt.Printf("You pressed: rune:%q, key %X\r\n", char, key) + if key == keyboard.KeyCtrlD { + break + } + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + + // key == 0 => char + if key == 0 { + _, err = t.Write([]byte{byte(char)}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + } else { + switch key { + case keyboard.KeyF1: + _, err = t.Write([]byte{0x1b, 0x4f, 0x50}) + case keyboard.KeyF2: + _, err = t.Write([]byte{0x1b, 0x4f, 0x51}) + case keyboard.KeyF3: + _, err = t.Write([]byte{0x1b, 0x4f, 0x52}) + case keyboard.KeyF4: + _, err = t.Write([]byte{0x1b, 0x4f, 0x53}) + case keyboard.KeyF5: + _, err = t.Write([]byte{0x1b, 0x5b, 0x31, 0x35, 0x7e}) + case keyboard.KeyF6: + _, err = t.Write([]byte{0x1b, 0x5b, 0x31, 0x37, 0x7e}) + case keyboard.KeyF7: + _, err = t.Write([]byte{0x1b, 0x5b, 0x31, 0x38, 0x7e}) + case keyboard.KeyF8: + _, err = t.Write([]byte{0x1b, 0x5b, 0x31, 0x39, 0x7e}) + case keyboard.KeyF9: + _, err = t.Write([]byte{0x1b, 0x5b, 0x32, 0x30, 0x7e}) + case keyboard.KeyF10: + _, err = t.Write([]byte{0x1b, 0x5b, 0x32, 0x31, 0x7e}) + case keyboard.KeyF11: + _, err = t.Write([]byte{0x1b, 0x5b, 0x32, 0x33, 0x7e}) + case keyboard.KeyF12: + _, err = t.Write([]byte{0x1b, 0x5b, 0x32, 0x34, 0x7e}) + case keyboard.KeyInsert: + _, err = t.Write([]byte{0x1b, 0x5b, 0x32, 0x7e}) + case keyboard.KeyDelete: + _, err = t.Write([]byte{0x1b, 0x5b, 0x33, 0x7e}) + case keyboard.KeyHome: + _, err = t.Write([]byte{0x1b, 0x5b, 0x48}) + case keyboard.KeyEnd: + _, err = t.Write([]byte{0x1b, 0x5b, 0x46}) + case keyboard.KeyPgup: + _, err = t.Write([]byte{0x1b, 0x5b, 0x35, 0x7e}) + case keyboard.KeyPgdn: + _, err = t.Write([]byte{0x1b, 0x5b, 0x36, 0x7e}) + case keyboard.KeyArrowUp: + _, err = t.Write([]byte{0x1b, 0x5b, 0x41}) + case keyboard.KeyArrowDown: + _, err = t.Write([]byte{0x1b, 0x5b, 0x42}) + case keyboard.KeyArrowRight: + _, err = t.Write([]byte{0x1b, 0x5b, 0x43}) + case keyboard.KeyArrowLeft: + _, err = t.Write([]byte{0x1b, 0x5b, 0x44}) + default: + _, err = t.Write([]byte{byte(key)}) + } + + if err != nil { + fmt.Fprintln(os.Stderr, err) + } + } + } + + return nil +} + +func resizeTerminal(t terminal.Terminal) error { + fd := int(os.Stdin.Fd()) + columns, rows, err := term.GetSize(fd) + if err != nil { + return err + } + + return t.Resize(rows, columns) +} diff --git a/cmd/command-runner/main.go b/cmd/command-runner/main.go new file mode 100644 index 0000000..7d1ad3b --- /dev/null +++ b/cmd/command-runner/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "github.com/go-zoox/cli" + "github.com/go-zoox/command" + "github.com/go-zoox/command/cmd/command-runner/commands" +) + +func main() { + app := cli.NewMultipleProgram(&cli.MultipleProgramConfig{ + Name: "command-runner", + Usage: "Powerful command runner", + Version: command.Version, + }) + + commands.Exec(app) + + app.Run() +} diff --git a/command.go b/command.go index 7e00881..96a91d7 100644 --- a/command.go +++ b/command.go @@ -1,86 +1,133 @@ package command import ( - "encoding/json" + "context" "fmt" "io" - "os" - "os/exec" + + "github.com/go-zoox/command/engine" + "github.com/go-zoox/command/engine/docker" + "github.com/go-zoox/command/engine/host" + "github.com/go-zoox/command/terminal" + "github.com/go-zoox/uuid" ) -// Command is better os/exec command -type Command struct { - Script string `json:"content"` - Context string `json:"context"` - Environment map[string]string `json:"environment"` - Shell string `json:"shell"` +// Command is the command runner interface +type Command interface { + Start() error + Wait() error + Cancel() error + // + Run() error // - Stdout io.Writer - Stderr io.Writer + SetStdin(stdin io.Reader) error + SetStdout(stdout io.Writer) error + SetStderr(stderr io.Writer) error // - cmd *exec.Cmd + Terminal() (terminal.Terminal, error) } -// Run runs the command -func (c *Command) Run() error { - environment := os.Environ() +// Config is the command config +type Config struct { + Engine string + Command string + WorkDir string + Environment map[string]string + User string + Shell string + + // engine = docker + Image string + // Memory is the memory limit, unit: MB + Memory int64 + // CPU is the CPU limit, unit: core + CPU float64 + // Platform is the command platform, available: linux/amd64, linux/arm64 + Platform string + // Network is the network name + Network string + // DisableNetwork disables network + DisableNetwork bool + + // Custom Command Runner ID + ID string +} - for k, v := range c.Environment { - environment = append(environment, fmt.Sprintf("%s=%s", k, v)) +// New creates a new command runner. +func New(ctx context.Context, cfg *Config) (cmd Command, err error) { + if cfg.Engine == "" { + cfg.Engine = host.Name } - shell := c.Shell - if shell == "" { - shell = os.Getenv("SHELL") - if shell == "" { - shell = "sh" - } + if cfg.Shell == "" { + cfg.Shell = "/bin/sh" } - cmd := exec.Command(shell, "-c", c.Script) - cmd.Dir = c.Context - cmd.Env = environment - - cmd.Stdout = c.Stdout - if cmd.Stdout == nil { - cmd.Stdout = os.Stdout + if cfg.ID == "" { + cfg.ID = fmt.Sprintf("go-zoox_command_%s", uuid.V4()) } - cmd.Stderr = c.Stderr - if cmd.Stderr == nil { - cmd.Stderr = os.Stderr + environment := map[string]string{ + "GO_ZOOX_COMMAND_ENGINE": cfg.Engine, + "GO_ZOOX_COMMAND_ID": cfg.ID, + "GO_ZOOX_COMMAND_SHELL": cfg.Shell, + "GO_ZOOX_COMMAND_USER": cfg.User, + "GO_ZOOX_COMMAND_WORKDIR": cfg.WorkDir, + "GO_ZOOX_COMMAND_COMMAND": cfg.Command, + "GO_ZOOX_COMMAND_IMAGE": cfg.Image, + "GO_ZOOX_COMMAND_MEMORY": fmt.Sprintf("%d", cfg.Memory), + "GO_ZOOX_COMMAND_CPU": fmt.Sprintf("%f", cfg.CPU), + "GO_ZOOX_COMMAND_PLATFORM": cfg.Platform, + "GO_ZOOX_COMMAND_NETWORK": cfg.Network, + "GO_ZOOX_COMMAND_DISABLE_NETWORK": fmt.Sprintf("%t", cfg.DisableNetwork), } - - c.cmd = cmd - - if err := cmd.Run(); err != nil { - return err + for k, v := range cfg.Environment { + environment[k] = v } - return nil -} - -// Config gets the config of command -func (c *Command) Config() (string, error) { - cfg, err := json.MarshalIndent(c, "", " ") - if err != nil { - return "", err - } - - return string(cfg), nil -} - -// MustConfig gets the config of command -func (c *Command) MustConfig() string { - cfg, err := c.Config() - if err != nil { - return "" + var engine engine.Engine + switch cfg.Engine { + case host.Name: + engine, err = host.New(ctx, &host.Config{ + ID: cfg.ID, + // + Command: cfg.Command, + WorkDir: cfg.WorkDir, + Environment: environment, + User: cfg.User, + Shell: cfg.Shell, + }) + if err != nil { + return nil, err + } + case docker.Name: + engine, err = docker.New(ctx, &docker.Config{ + ID: cfg.ID, + // + Command: cfg.Command, + WorkDir: cfg.WorkDir, + Environment: environment, + User: cfg.User, + Shell: cfg.Shell, + Image: cfg.Image, + Memory: cfg.Memory, + CPU: cfg.CPU, + Platform: cfg.Platform, + Network: cfg.Network, + DisableNetwork: cfg.DisableNetwork, + }) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported command engine: %s", cfg.Engine) } - return cfg + return &command{ + engine: engine, + }, nil } -// ExitCode gets the exit code of process -func (c *Command) ExitCode() int { - return c.cmd.ProcessState.ExitCode() +type command struct { + engine engine.Engine } diff --git a/command_test.go b/command_test.go index 63ba0ee..7e2794a 100644 --- a/command_test.go +++ b/command_test.go @@ -1,125 +1,29 @@ package command import ( - "io" + "context" + "strings" "testing" - - "github.com/go-zoox/testify" ) -func TestCommand(t *testing.T) { - cmd := &Command{ - Script: `echo "hello world"`, +func TestNew(t *testing.T) { + cfg := &Config{ + Command: "echo hello world", } - cfg := `{ - "content": "echo \"hello world\"", - "context": "", - "environment": null, - "shell": "" -}` - - testify.Equal(t, cfg, cmd.MustConfig()) - - // cmd.Run() -} - -func TestCommandWithContext(t *testing.T) { - cmd := &Command{ - Script: `echo "PWD: $PWD"`, - Context: `/tmp`, + cmd, err := New(context.Background(), cfg) + if err != nil { + t.Fatalf("failed to create command: %v", err) } - cfg := `{ - "content": "echo \"PWD: $PWD\"", - "context": "/tmp", - "environment": null, - "shell": "" -}` - - testify.Equal(t, cfg, cmd.MustConfig()) - - // cmd.Run() -} - -func TestCommandWithEnvironment(t *testing.T) { - cmd := &Command{ - Script: `echo "PWD: $PWD"`, - Context: `/tmp`, - Environment: map[string]string{ - "FOO": "BAR", - "FOO1": "NAR1", - }, - } - - cfg := `{ - "content": "echo \"PWD: $PWD\"", - "context": "/tmp", - "environment": { - "FOO": "BAR", - "FOO1": "NAR1" - }, - "shell": "" -}` - - testify.Equal(t, cfg, cmd.MustConfig()) - - // cmd.Run() -} + buf := &strings.Builder{} + cmd.SetStdout(buf) -func TestCommandWithShell(t *testing.T) { - cmd := &Command{ - Script: `echo "PWD: $PWD"`, - Context: `/tmp`, - Environment: map[string]string{ - "FOO": "BAR", - "FOO1": "NAR1", - }, - Shell: "/bin/bash", + if err := cmd.Run(); err != nil { + t.Fatalf("failed to start command: %v", err) } - cfg := `{ - "content": "echo \"PWD: $PWD\"", - "context": "/tmp", - "environment": { - "FOO": "BAR", - "FOO1": "NAR1" - }, - "shell": "/bin/bash" -}` - - testify.Equal(t, cfg, cmd.MustConfig()) - - // cmd.Run() -} - -func TestCommandWithMultilines(t *testing.T) { - output := &Output{} - cmd := &Command{ - Script: ` -echo 1 -echo 2 -echo 3 -`, - Stdout: output, - Stderr: output, + if v := buf.String(); v != "hello world\n" { + t.Fatalf("expected %q, got %q", "hello world\n", v) } - - testify.Assert(t, cmd.Run() == nil) - testify.Equal(t, output.String(), "1\n2\n3\n") -} - -type Output struct { - io.Writer - - data []byte -} - -func (o *Output) Write(b []byte) (n int, err error) { - o.data = append(o.data, b...) - return len(b), nil -} - -func (o *Output) String() string { - return string(o.data) } diff --git a/engine/docker/cancel.go b/engine/docker/cancel.go new file mode 100644 index 0000000..8a264f3 --- /dev/null +++ b/engine/docker/cancel.go @@ -0,0 +1,12 @@ +package docker + +import ( + "github.com/docker/docker/api/types" +) + +// Cancel cancels the command. +func (d *docker) Cancel() error { + return d.client.ContainerRemove(d.ctx, d.container.ID, types.ContainerRemoveOptions{ + Force: true, + }) +} diff --git a/engine/docker/config.go b/engine/docker/config.go new file mode 100644 index 0000000..311555b --- /dev/null +++ b/engine/docker/config.go @@ -0,0 +1,26 @@ +package docker + +// Config is the configuration for a Docker engine. +type Config struct { + Command string + Environment map[string]string + WorkDir string + User string + Shell string + + // engine = docker + Image string + // Memory is the memory limit, unit: MB + Memory int64 + // CPU is the CPU limit, unit: core + CPU float64 + // Platform is the command platform, available: linux/amd64, linux/arm64 + Platform string + // Network is the network name + Network string + // DisableNetwork disables network + DisableNetwork bool + + // Custom Command Runner ID + ID string +} diff --git a/engine/docker/create.go b/engine/docker/create.go new file mode 100644 index 0000000..ab7b26e --- /dev/null +++ b/engine/docker/create.go @@ -0,0 +1,118 @@ +package docker + +import ( + "fmt" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" + "github.com/go-zoox/core-utils/cast" + "github.com/go-zoox/core-utils/strings" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// create creates a container. +func (d *docker) create() (err error) { + if d.cfg.Command != "" { + d.args = append(d.args, "-c", d.cfg.Command) + } + + for k, v := range d.cfg.Environment { + d.env = append(d.env, fmt.Sprintf("%s=%s", k, v)) + } + + d.client, err = client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + + cfg := &container.Config{ + Hostname: "go-zoox", + Image: d.cfg.Image, + Cmd: append([]string{d.cfg.Shell}, d.args...), + User: d.cfg.User, + WorkingDir: d.cfg.WorkDir, + Env: d.env, + Tty: true, + OpenStdin: true, + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + StdinOnce: true, + } + + hostCfg := &container.HostConfig{ + Resources: container.Resources{ + // Memory: d.cfg.Memory, + // CPUPeriod: 100000, + // CPUQuota: cast.ToInt64(100000 * d.cfg.CPU), + }, + Mounts: []mount.Mount{ + // { + // Type: mount.TypeBind, + // Source: d.cfg.WorkDir, + // Target: d.cfg.WorkDir, + // ReadOnly: false, + // }, + }, + // NetworkMode: "none", + } + if d.cfg.Memory != 0 { + hostCfg.Resources.Memory = d.cfg.Memory * 1024 * 1024 + } + if d.cfg.DisableNetwork { + hostCfg.NetworkMode = "none" + } + + if d.cfg.CPU != 0 { + hostCfg.Resources.CPUPeriod = 100000 + hostCfg.Resources.CPUQuota = cast.ToInt64(float64(hostCfg.Resources.CPUPeriod) * d.cfg.CPU) + } + if d.cfg.WorkDir != "" { + hostCfg.Mounts = append(hostCfg.Mounts, mount.Mount{ + Type: mount.TypeBind, + Source: d.cfg.WorkDir, + Target: d.cfg.WorkDir, + ReadOnly: false, + }) + } + + networkCfg := &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{}, + } + if d.cfg.Network != "" { + networkIns, err := d.client.NetworkInspect(d.ctx, d.cfg.Network, types.NetworkInspectOptions{}) + if err != nil { + return err + } + + networkCfg.EndpointsConfig[d.cfg.Network] = &network.EndpointSettings{ + NetworkID: networkIns.ID, + } + } + + platformCfg := &ocispec.Platform{ + OS: "linux", + Architecture: "amd64", + } + if d.cfg.Platform != "" { + switch d.cfg.Platform { + case "linux/amd64", "linux/arm64": + default: + return fmt.Errorf("invalid platform: %s, available: linux/amd64, linux/arm64", d.cfg.Platform) + } + + osArch := strings.Split(d.cfg.Platform, "/") + platformCfg.OS = osArch[0] + platformCfg.Architecture = osArch[1] + } + + d.container, err = d.client.ContainerCreate(d.ctx, cfg, hostCfg, networkCfg, platformCfg, d.cfg.ID) + if err != nil { + return err + } + + return nil +} diff --git a/engine/docker/docker.go b/engine/docker/docker.go new file mode 100644 index 0000000..5c626c0 --- /dev/null +++ b/engine/docker/docker.go @@ -0,0 +1,64 @@ +package docker + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/go-zoox/command/engine" + "github.com/go-zoox/uuid" +) + +// Name is the name of the engine. +const Name = "docker" + +type docker struct { + cfg *Config + // + args []string + env []string + // + ctx context.Context + // + client *client.Client + // + container container.CreateResponse + + // + stdin io.Reader + stdout io.Writer + stderr io.Writer +} + +// New creates a new docker engine. +func New(ctx context.Context, cfg *Config) (engine.Engine, error) { + if cfg.Image == "" { + cfg.Image = "whatwewant/zmicro:v1" + } + + if cfg.Shell == "" { + cfg.Shell = "/bin/sh" + } + + if cfg.ID == "" { + cfg.ID = fmt.Sprintf("go-zoox_command_%s", uuid.V4()) + } + + d := &docker{ + ctx: ctx, + cfg: cfg, + // + stdin: os.Stdin, + stdout: os.Stdout, + stderr: os.Stderr, + } + + if err := d.create(); err != nil { + return nil, err + } + + return d, nil +} diff --git a/engine/docker/io.go b/engine/docker/io.go new file mode 100644 index 0000000..cd94e4d --- /dev/null +++ b/engine/docker/io.go @@ -0,0 +1,21 @@ +package docker + +import "io" + +// SetStdin sets the stdin for the Docker engine. +func (d *docker) SetStdin(stdin io.Reader) error { + d.stdin = stdin + return nil +} + +// SetStdout sets the stdout for the Docker engine. +func (d *docker) SetStdout(stdout io.Writer) error { + d.stdout = stdout + return nil +} + +// SetStderr sets the stderr for the Docker engine. +func (d *docker) SetStderr(stderr io.Writer) error { + d.stderr = stderr + return nil +} diff --git a/engine/docker/start.go b/engine/docker/start.go new file mode 100644 index 0000000..0e2aec9 --- /dev/null +++ b/engine/docker/start.go @@ -0,0 +1,61 @@ +package docker + +import ( + "io" + "net" + + "github.com/docker/docker/api/types" +) + +// Start starts the command. +func (d *docker) Start() error { + stream, err := d.client.ContainerAttach(d.ctx, d.container.ID, types.ContainerAttachOptions{ + Stream: true, + Stdin: true, + Stdout: true, + Stderr: true, + // Logs: true, + }) + if err != nil { + return err + } + + if err := applyStdin(stream.Conn, d.stdin); err != nil { + return nil + } + + if err := applyStdout(stream.Conn, d.stdout); err != nil { + return nil + } + + if err := applyStderr(stream.Conn, d.stderr); err != nil { + return nil + } + + err = d.client.ContainerStart(d.ctx, d.container.ID, types.ContainerStartOptions{}) + if err != nil { + return err + } + + return nil +} + +func applyStdin(conn net.Conn, stdin io.Reader) error { + if stdin != nil { + go io.Copy(conn, stdin) + } + + return nil +} + +func applyStdout(conn net.Conn, stdout io.Writer) error { + if stdout != nil { + go io.Copy(stdout, conn) + } + + return nil +} + +func applyStderr(conn net.Conn, stderr io.Writer) error { + return nil +} diff --git a/engine/docker/terminal.go b/engine/docker/terminal.go new file mode 100644 index 0000000..d1e8bb2 --- /dev/null +++ b/engine/docker/terminal.go @@ -0,0 +1,116 @@ +package docker + +import ( + "context" + "fmt" + "io" + "net" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + dockerClient "github.com/docker/docker/client" + "github.com/go-zoox/command/terminal" +) + +// Terminal returns a terminal. +func (d *docker) Terminal() (terminal.Terminal, error) { + stream, err := d.client.ContainerAttach(d.ctx, d.container.ID, types.ContainerAttachOptions{ + Stream: true, + Stdin: true, + Stdout: true, + Stderr: true, + // Logs: true, + }) + if err != nil { + return nil, err + } + + t := &Terminal{ + Ctx: d.ctx, + Client: d.client, + ContainerID: d.container.ID, + Conn: stream.Conn, + } + + err = d.client.ContainerStart(d.ctx, d.container.ID, types.ContainerStartOptions{}) + if err != nil { + return nil, err + } + + return t, nil +} + +// Terminal is a terminal. +type Terminal struct { + Ctx context.Context + Conn net.Conn + // + Client *dockerClient.Client + ContainerID string +} + +// Close closes the terminal. +func (t *Terminal) Close() error { + t.Conn.Close() + + return t.Client.ContainerRemove(t.Ctx, t.ContainerID, types.ContainerRemoveOptions{ + Force: true, + }) +} + +// Read reads from the terminal. +func (t *Terminal) Read(p []byte) (n int, err error) { + return t.Conn.Read(p) +} + +// Write writes to the terminal. +func (t *Terminal) Write(p []byte) (n int, err error) { + return t.Conn.Write(p) +} + +// Resize resizes the terminal. +func (t *Terminal) Resize(rows, cols int) error { + inspect, err := t.Client.ContainerInspect(t.Ctx, t.ContainerID) + if err != nil { + return err + } + + if inspect.State.Status != "running" { + // return fmt.Errorf("container is not running") + return nil + } + + return t.Client.ContainerResize(t.Ctx, t.ContainerID, types.ResizeOptions{ + Height: uint(rows), + Width: uint(cols), + }) +} + +// ExitCode returns the exit code. +func (t *Terminal) ExitCode() int { + inspect, err := t.Client.ContainerInspect(t.Ctx, t.ContainerID) + if err != nil { + return -1 + } + + return inspect.State.ExitCode +} + +// Wait waits for the terminal to exit. +func (t *Terminal) Wait() error { + resultC, errC := t.Client.ContainerWait(t.Ctx, t.ContainerID, container.WaitConditionNotRunning) + select { + case err := <-errC: + if err != nil && err != io.EOF { + return fmt.Errorf("container exit error: %#v", err) + } + + case result := <-resultC: + if result.StatusCode != 0 { + // rt.exitCode = int(result.StatusCode) + return fmt.Errorf("container exited with non-zero status: %d", result.StatusCode) + } + } + + return nil +} diff --git a/engine/docker/wait.go b/engine/docker/wait.go new file mode 100644 index 0000000..46050b6 --- /dev/null +++ b/engine/docker/wait.go @@ -0,0 +1,25 @@ +package docker + +import ( + "fmt" + "io" + + "github.com/docker/docker/api/types/container" +) + +// Wait waits for the command to finish. +func (d *docker) Wait() error { + result, err := d.client.ContainerWait(d.ctx, d.container.ID, container.WaitConditionNotRunning) + select { + case err := <-err: + if err != nil && err != io.EOF { + return fmt.Errorf("container exit error: %#v", err) + } + case result := <-result: + if result.StatusCode != 0 { + return fmt.Errorf("container exited with non-zero status: %d", result.StatusCode) + } + } + + return nil +} diff --git a/engine/engine.go b/engine/engine.go new file mode 100644 index 0000000..159df66 --- /dev/null +++ b/engine/engine.go @@ -0,0 +1,20 @@ +package engine + +import ( + "io" + + "github.com/go-zoox/command/terminal" +) + +// Engine is the interface that an command engine must implement. +type Engine interface { + Start() error + Wait() error + Cancel() error + // + SetStdin(stdin io.Reader) error + SetStdout(stdout io.Writer) error + SetStderr(stderr io.Writer) error + // + Terminal() (terminal.Terminal, error) +} diff --git a/engine/host/cancel.go b/engine/host/cancel.go new file mode 100644 index 0000000..7f0af3f --- /dev/null +++ b/engine/host/cancel.go @@ -0,0 +1,10 @@ +package host + +// Cancel cancels the command. +func (h *host) Cancel() error { + if err := h.cmd.Process.Kill(); err != nil { + return err + } + + return nil +} diff --git a/engine/host/config.go b/engine/host/config.go new file mode 100644 index 0000000..33bfacf --- /dev/null +++ b/engine/host/config.go @@ -0,0 +1,16 @@ +package host + +// Config is the configuration for a host engine. +type Config struct { + Command string + Environment map[string]string + WorkDir string + User string + Shell string + + // + IsHistoryDisabled bool + + // Custom Command Runner ID + ID string +} diff --git a/engine/host/create.go b/engine/host/create.go new file mode 100644 index 0000000..ba7eca1 --- /dev/null +++ b/engine/host/create.go @@ -0,0 +1,102 @@ +package host + +import ( + "errors" + "fmt" + "os" + "os/exec" + "os/user" + "syscall" + + "github.com/go-zoox/logger" + "github.com/spf13/cast" +) + +// Create creates the command. +func (h *host) create() error { + if h.cmd != nil { + return errors.New("command: already created") + } + + args := []string{} + if h.cfg.Command != "" { + args = append(args, "-c", h.cfg.Command) + } + + logger.Debugf("create command: %s %v", h.cfg.Shell, args) + h.cmd = exec.Command(h.cfg.Shell, args...) + + if err := applyEnv(h.cmd, h.cfg.Environment); err != nil { + return err + } + + if err := applyWorkDir(h.cmd, h.cfg.WorkDir); err != nil { + return err + } + + if err := applyUser(h.cmd, h.cfg.User); err != nil { + return err + } + + if err := applyHistory(h.cmd, h.cfg.IsHistoryDisabled); err != nil { + return err + } + + return nil +} + +func applyEnv(cmd *exec.Cmd, environment map[string]string) error { + cmd.Env = append(os.Environ(), "TERM=xterm") + + for k, v := range environment { + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v)) + } + + return nil +} + +func applyWorkDir(cmd *exec.Cmd, workDir string) error { + cmd.Dir = workDir + return nil +} + +func applyUser(cmd *exec.Cmd, username string) error { + if username == "" { + return nil + } + + userX, err := user.Lookup(username) + if err != nil { + return err + } + + logger.Infof("[command] uid=%s gid=%s", userX.Uid, userX.Gid) + + uid := cast.ToInt(userX.Uid) + gid := cast.ToInt(userX.Gid) + + cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr.Credential = &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + } + + cmd.Env = append( + cmd.Env, + "USER="+username, + "HOME="+userX.HomeDir, + "LOGNAME="+username, + "UID="+userX.Uid, + "GID="+userX.Gid, + ) + + return nil +} + +func applyHistory(cmd *exec.Cmd, disable bool) error { + if disable { + cmd.Env = append(cmd.Env, "HISTFILE=/dev/null") + } + + return nil +} diff --git a/engine/host/host.go b/engine/host/host.go new file mode 100644 index 0000000..b8fadfc --- /dev/null +++ b/engine/host/host.go @@ -0,0 +1,49 @@ +package host + +import ( + "context" + "io" + "os" + "os/exec" + + "github.com/go-zoox/command/engine" +) + +// Name is the name of the engine. +const Name = "host" + +type host struct { + ctx context.Context + // + cfg *Config + // + cmd *exec.Cmd + // + + // + stdin io.Reader + stdout io.Writer + stderr io.Writer +} + +// New creates a new host engine. +func New(ctx context.Context, cfg *Config) (engine.Engine, error) { + if cfg.Shell == "" { + cfg.Shell = "/bin/sh" + } + + h := &host{ + ctx: ctx, + cfg: cfg, + // + stdin: os.Stdin, + stdout: os.Stdout, + stderr: os.Stderr, + } + + if err := h.create(); err != nil { + return nil, err + } + + return h, nil +} diff --git a/engine/host/io.go b/engine/host/io.go new file mode 100644 index 0000000..f9debed --- /dev/null +++ b/engine/host/io.go @@ -0,0 +1,21 @@ +package host + +import "io" + +// SetStdin sets the stdin for the command. +func (h *host) SetStdin(stdin io.Reader) error { + h.stdin = stdin + return nil +} + +// SetStdout sets the stdout for the command. +func (h *host) SetStdout(stdout io.Writer) error { + h.stdout = stdout + return nil +} + +// SetStderr sets the stderr for the command. +func (h *host) SetStderr(stderr io.Writer) error { + h.stderr = stderr + return nil +} diff --git a/engine/host/start.go b/engine/host/start.go new file mode 100644 index 0000000..57ee0d8 --- /dev/null +++ b/engine/host/start.go @@ -0,0 +1,42 @@ +package host + +import ( + "io" + "os/exec" +) + +// Start starts the command. +func (h *host) Start() error { + if err := applyStdin(h.cmd, h.stdin); err != nil { + return nil + } + + if err := applyStdout(h.cmd, h.stdout); err != nil { + return nil + } + + if err := applyStderr(h.cmd, h.stderr); err != nil { + return nil + } + + return h.cmd.Start() +} + +func applyStdin(cmd *exec.Cmd, stdin io.Reader) error { + cmd.Stdin = stdin + return nil +} + +func applyStdout(cmd *exec.Cmd, stdout io.Writer) error { + cmd.Stdout = stdout + if cmd.Stderr == nil { + return applyStderr(cmd, stdout) + } + + return nil +} + +func applyStderr(cmd *exec.Cmd, stderr io.Writer) error { + cmd.Stderr = stderr + return nil +} diff --git a/engine/host/terminal.go b/engine/host/terminal.go new file mode 100644 index 0000000..9bc34c4 --- /dev/null +++ b/engine/host/terminal.go @@ -0,0 +1,61 @@ +package host + +import ( + "os" + "os/exec" + + "github.com/creack/pty" + "github.com/go-zoox/command/terminal" +) + +// Name is the name of the engine. +func (h *host) Terminal() (terminal.Terminal, error) { + terminal, err := pty.Start(h.cmd) + if err != nil { + return nil, err + } + + return &Terminal{ + File: terminal, + Cmd: h.cmd, + }, nil +} + +// Terminal is the terminal implementation. +type Terminal struct { + *os.File + Cmd *exec.Cmd +} + +// Close closes the terminal. +func (t *Terminal) Close() error { + return t.File.Close() +} + +// Read reads from the terminal. +func (t *Terminal) Read(p []byte) (n int, err error) { + return t.File.Read(p) +} + +// Write writes to the terminal. +func (t *Terminal) Write(p []byte) (n int, err error) { + return t.File.Write(p) +} + +// Resize resizes the terminal. +func (t *Terminal) Resize(rows, cols int) error { + return pty.Setsize(t.File, &pty.Winsize{ + Rows: uint16(rows), + Cols: uint16(cols), + }) +} + +// ExitCode returns the exit code. +func (t *Terminal) ExitCode() int { + return t.Cmd.ProcessState.ExitCode() +} + +// Wait waits for the terminal to exit. +func (t *Terminal) Wait() error { + return t.Cmd.Wait() +} diff --git a/engine/host/wait.go b/engine/host/wait.go new file mode 100644 index 0000000..ad269d4 --- /dev/null +++ b/engine/host/wait.go @@ -0,0 +1,6 @@ +package host + +// Wait waits for the command to finish. +func (h *host) Wait() error { + return h.cmd.Wait() +} diff --git a/go.mod b/go.mod index 6b0f372..74d9373 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,62 @@ module github.com/go-zoox/command -go 1.18 +go 1.20 -require github.com/go-zoox/testify v1.0.0 +require ( + github.com/creack/pty v1.1.18 + github.com/docker/docker v24.0.6+incompatible + github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 + github.com/go-zoox/cli v1.3.6 + github.com/go-zoox/logger v1.4.6 + github.com/go-zoox/testify v1.0.2 + github.com/go-zoox/uuid v0.0.1 + github.com/spf13/cast v1.5.1 + golang.org/x/term v0.12.0 +) require ( - github.com/go-zoox/core-utils v1.0.4 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-playground/validator/v10 v10.10.0 // indirect + github.com/go-zoox/chalk v1.0.2 // indirect + github.com/go-zoox/config v1.2.10 // indirect + github.com/go-zoox/core-utils v1.2.14 // indirect + github.com/go-zoox/datetime v1.2.2 // indirect + github.com/go-zoox/dotenv v1.2.3 // indirect + github.com/go-zoox/encoding v1.2.1 // indirect + github.com/go-zoox/fs v1.3.13 // indirect + github.com/go-zoox/ini v1.0.4 // indirect + github.com/go-zoox/tag v1.2.3 // indirect + github.com/goccy/go-yaml v1.11.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sevlyar/go-daemon v0.1.6 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 // indirect + github.com/urfave/cli/v2 v2.25.7 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.6.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 97f73d1..df6dc56 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,187 @@ -github.com/go-zoox/core-utils v1.0.4 h1:hkED3IlciYfTYnk7LrO+lBGC/+I1KSjQpJL6+K1v7Rg= -github.com/go-zoox/core-utils v1.0.4/go.mod h1:EknM3KLL6/kagL95wZbWE7mRRcYCG/fHqiJ/EH0ihAs= -github.com/go-zoox/testify v1.0.0 h1:zXuj+JMcudM/dWk8HgMfCKpGYDcyHbTUBGxH35SGubU= -github.com/go-zoox/testify v1.0.0/go.mod h1:6+UZ2gOcwcnUvR5lclGRnLrE3/mLoQMAGExjrZgs3aA= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= +github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-zoox/chalk v1.0.2 h1:DCWft37fogmvqF37JdbGSLg28L/tQeA8u0lMvb62KOg= +github.com/go-zoox/chalk v1.0.2/go.mod h1:z5+qvE9nEJI5uT4px2tyoFa/xxkqf3CUo22KmXLKbNI= +github.com/go-zoox/cli v1.3.6 h1:+A4l9aBBvqO/xXjwZ1PCcX0ue/HZ/Ft0Wp/dDq27iVE= +github.com/go-zoox/cli v1.3.6/go.mod h1:25ox3mVRJdRSyLJLvK8OHYEuXtMtfkTseWkJjlK9kM4= +github.com/go-zoox/config v1.2.10 h1:mebuz6O0N81OXOCwtV+LKOiFuAfZ5wyaGsuzkGSSpf4= +github.com/go-zoox/config v1.2.10/go.mod h1:KnSEhz7AVMqQfznJzgpzFhQfgy8UZYeone/osbvnVUE= +github.com/go-zoox/core-utils v1.2.14 h1:Z6x9QTgfBfhbSOOPEDLqLs9UGBWkXgBpVPlJ4pI68yk= +github.com/go-zoox/core-utils v1.2.14/go.mod h1:raOOwr2l2sJQyjR0Dg33sg0ry4U1/L2eNTuLFRpUXWs= +github.com/go-zoox/datetime v1.2.2 h1:JrI4ekdsvpsenGzrNQAOmobBTYyotaXD3YDXngvCbM4= +github.com/go-zoox/datetime v1.2.2/go.mod h1:qvaCrzjhq/g/gstx4sx06Nl4ll2pLSrkRa9ueLjrZ9A= +github.com/go-zoox/dotenv v1.2.3 h1:9wx4sL2u/FrRLkzoOb7ozYii6NoGsl05KoGdZm1ebIE= +github.com/go-zoox/dotenv v1.2.3/go.mod h1:hhl5WZUxI+DNJN6Zos1y7Nq0jDCXciphswPSYtInqg0= +github.com/go-zoox/encoding v1.2.1 h1:38rQRsfL1f1YHZaqsPaGcNMkPnzatnPlYiHioUh9F4A= +github.com/go-zoox/encoding v1.2.1/go.mod h1:NdcM7Ln73oVW3vJgx3MH4fJknCcdQfq+NgJ0tuCo7tU= +github.com/go-zoox/fs v1.3.13 h1:fe0uvtXCM+9s51z/CnQ5kxB4hBYaK55tkrE9gq0385U= +github.com/go-zoox/fs v1.3.13/go.mod h1:wCM+UQkjFTxNjOOCNlGcN3k9FeXXUwn9bFnpyjOn55c= +github.com/go-zoox/ini v1.0.4 h1:N4mUbAO0juYIRrv3ysjKtpEn/+yQv57eQietsgpkAYQ= +github.com/go-zoox/ini v1.0.4/go.mod h1:SisQneNLb1EBeZ5bA5GnrJd8FNg372hQrPh+gb3IzV4= +github.com/go-zoox/logger v1.4.6 h1:zHUaB6KQ9rD/N3hM0JJ3/JCNdgtedf4mVBBNNSyWCOg= +github.com/go-zoox/logger v1.4.6/go.mod h1:o7ddvv/gMoMa0TomPhHoIz11ZWRbQ92pF6rwYbOY3iQ= +github.com/go-zoox/tag v1.2.3 h1:HDQpRu8rA1xSJt6c+v0O7TfzTjPq5aDtyzW/15aTh94= +github.com/go-zoox/tag v1.2.3/go.mod h1:z9z4iZb/XPE4HwTXJgPIdwgH90c2NysGxIMq9tW+GuU= +github.com/go-zoox/testify v1.0.2 h1:G5sQ3xm0uwCuytnMhgnqZ5BItCt2DN3n2wLBqlIJEWA= +github.com/go-zoox/testify v1.0.2/go.mod h1:L35iVL6xDKDL/TQOTRWyNL4H4nm8bzs6nde5XA7PYnY= +github.com/go-zoox/uuid v0.0.1 h1:txqmDavRTq68gzzqWfJQLorFyUp9a7M2lmq2KcwPGPA= +github.com/go-zoox/uuid v0.0.1/go.mod h1:0/F4LdfLqFdyqOf7aXoiYXRkXHU324JQ5DZEytXYBPM= +github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= +github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sevlyar/go-daemon v0.1.6 h1:EUh1MDjEM4BI109Jign0EaknA2izkOyi0LV3ro3QQGs= +github.com/sevlyar/go-daemon v0.1.6/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4= github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q= +github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= +github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/io.go b/io.go new file mode 100644 index 0000000..4617bab --- /dev/null +++ b/io.go @@ -0,0 +1,18 @@ +package command + +import "io" + +// SetStdin sets the stdin for the command. +func (c *command) SetStdin(stdin io.Reader) error { + return c.engine.SetStdin(stdin) +} + +// SetStdout sets the stdout for the command. +func (c *command) SetStdout(stdout io.Writer) error { + return c.engine.SetStdout(stdout) +} + +// SetStderr sets the stderr for the command. +func (c *command) SetStderr(stderr io.Writer) error { + return c.engine.SetStderr(stderr) +} diff --git a/new.go b/new.go deleted file mode 100644 index 8cdede9..0000000 --- a/new.go +++ /dev/null @@ -1,26 +0,0 @@ -package command - -// NewOptions is the options for new -type NewOptions struct { - Context string - Environment map[string]string - Shell string -} - -// New creates a Command -func New(script string, options ...*NewOptions) *Command { - var context string - var environment map[string]string - var shell = "/bin/sh" - if len(options) > 0 && options[0] != nil { - context = options[0].Context - environment = options[0].Environment - } - - return &Command{ - Script: script, - Context: context, - Environment: environment, - Shell: shell, - } -} diff --git a/run.go b/run.go new file mode 100644 index 0000000..304e324 --- /dev/null +++ b/run.go @@ -0,0 +1,10 @@ +package command + +// Run runs the command. +func (c *command) Run() error { + if err := c.Start(); err != nil { + return err + } + + return c.Wait() +} diff --git a/start.go b/start.go new file mode 100644 index 0000000..1d4d745 --- /dev/null +++ b/start.go @@ -0,0 +1,12 @@ +package command + +import "errors" + +// Start starts to run the command. +func (c *command) Start() error { + if c.engine == nil { + return errors.New("engine not set") + } + + return c.engine.Start() +} diff --git a/terminal.go b/terminal.go new file mode 100644 index 0000000..9391b27 --- /dev/null +++ b/terminal.go @@ -0,0 +1,8 @@ +package command + +import "github.com/go-zoox/command/terminal" + +// Terminal returns a terminal for the command. +func (c *command) Terminal() (terminal.Terminal, error) { + return c.engine.Terminal() +} diff --git a/terminal/terminal.go b/terminal/terminal.go new file mode 100644 index 0000000..b129f8d --- /dev/null +++ b/terminal/terminal.go @@ -0,0 +1,17 @@ +package terminal + +import "io" + +// Terminal is the interface that a terminal must implement. +type Terminal interface { + io.ReadWriteCloser + + // + Resize(rows, cols int) error + + // + ExitCode() int + + // + Wait() error +} diff --git a/wait.go b/wait.go new file mode 100644 index 0000000..b8a270f --- /dev/null +++ b/wait.go @@ -0,0 +1,6 @@ +package command + +// Wait waits for the command to exit. +func (c *command) Wait() error { + return c.engine.Wait() +}