Skip to content

Commit

Permalink
Add feature: harp console
Browse files Browse the repository at this point in the history
  • Loading branch information
bom-d-van committed Jan 1, 2016
1 parent e24d596 commit 14a7ce4
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 4 deletions.
22 changes: 22 additions & 0 deletions README.md
Expand Up @@ -77,6 +77,11 @@ harp -h

# Run arbitrary Go program wihtout harp.json
harp -server app@test.com:8022 -t run app.go # with default -goos=linux -goarch=amd64

# Run shell commands or execute scripts with harp console (alias: sh, shell)
harp -s prod console
harp -s prod console < my-shell-script
harp -s prod console <<<(ls)
```

## Common Trouble Shootings
Expand Down Expand Up @@ -153,6 +158,8 @@ example:
}
```

## Usages

### How to specify server or server sets:

Using the configuration above as example, server set means the key in `Servers` object value, i.e. `prod`, `dev`.
Expand Down Expand Up @@ -425,3 +432,18 @@ harp xc
```

Note: before using `harp xc`, you need to enable cross compilation by installing go from source, details could be found here: https://golang.org/doc/install/source

### Console (sh, shell)

Run shell commands or execute scripts with harp console (alias: sh, shell)

```
# start a repl to execute shell commands
harp -s prod console
# run a script
harp -s prod console < my-shell-script
# another example
harp -s prod console <<<(ls)
```
80 changes: 80 additions & 0 deletions console.go
@@ -0,0 +1,80 @@
package main

import (
"fmt"
"io/ioutil"
"os"
"strings"
"sync"

"github.com/chzyer/readline"
)

func startConsole(servers []*Server) {
rl, err := readline.New("> ")
if err != nil {
panic(err)
}
defer rl.Close()

type output struct {
serv *Server
output string
}

isTerminal := readline.IsTerminal(int(os.Stdin.Fd()))
for {
var line string
var err error
if isTerminal {
line, err = rl.Readline()
} else {
lineb, er := ioutil.ReadAll(os.Stdin)
line, err = string(lineb), er
}
if err != nil { // io.EOF
break
}

outputc := make(chan output)
var mutex sync.Mutex
var count int
var wg sync.WaitGroup
wg.Add(1)
go func() {
for {
mutex.Lock()
output := <-outputc
prompt := output.serv.prompt()
if isTerminal {
prompt += " " + line
}
if count > 0 && strings.TrimSpace(output.output) != "" {
prompt = "\n" + prompt
}
fmt.Println(prompt)
if strings.TrimSpace(output.output) != "" {
fmt.Printf(output.output)
}
if count++; count >= len(servers) {
break
}
mutex.Unlock()
}
wg.Done()
}()

for _, serv := range servers {
wg.Add(1)
go func(serv *Server) {
outputc <- output{serv: serv, output: serv.exec(line)}
wg.Done()
}(serv)
}
wg.Wait()

if !isTerminal {
return
}
}
}
17 changes: 15 additions & 2 deletions harp.go
Expand Up @@ -108,6 +108,9 @@ type App struct {
DeployScript string
RestartScript string
MigrationScript string

// TODO
// Hooks struct{}
}

type Tasks []string
Expand All @@ -127,6 +130,7 @@ func (t *FlagStrings) Set(s string) error {
}

var (
// option is a global control center, keeping flags in one place.
option = struct {
configPath string

Expand Down Expand Up @@ -160,11 +164,18 @@ var (
deploy string

tasks Tasks
hand bool
hand bool // hand flag indicates migration are executed but only deployed on servers

cli bool
// cli bool

// transient flag allow running go program without the presence of harp.json
transient bool

hook struct {
before, after string
}

docker bool
}{}

migrations []Migration
Expand Down Expand Up @@ -309,6 +320,8 @@ func main() {
}
case "cross-compile", "xc":
initXC()
case "console", "shell", "sh":
startConsole(servers)
default:
fmt.Println("unknown command:", args[0])
os.Exit(1)
Expand Down
36 changes: 35 additions & 1 deletion server.go
Expand Up @@ -22,7 +22,9 @@ import (
type Server struct {
ID string

Envs map[string]string
Envs map[string]string
// Args []string // TODO: support

Home string
GoPath string
LogDir string
Expand All @@ -32,6 +34,9 @@ type Server struct {
Host string
Port string

// TODO
Password string

Set string // aka, Type

client *ssh.Client
Expand Down Expand Up @@ -258,6 +263,7 @@ func (s *Server) restartScript() (script string) {
}
args := strings.Join(app.Args, " ")
script += fmt.Sprintf("cd %s/src/%s\n", s.GoPath, app.ImportPath)
// env=val nohup $GOPATH/bin/$app arg1 >> $log 2&1 &
script += fmt.Sprintf("%s nohup %s/bin/%s %s >> %s 2>&1 &\n", envs, s.GoPath, app.Name, args, log)
script += fmt.Sprintf("echo $! > %s\n", pid)
script += "cd " + s.Home
Expand Down Expand Up @@ -437,6 +443,25 @@ func (s *Server) getSession() *ssh.Session {
return session
}

func (s *Server) exec(cmd string) string {
if s.client == nil {
s.initClient()
}

session, err := s.client.NewSession()
if err != nil {
// fmt.Printf("%s: %s\n", s, err)
return err.Error()
}

output, err := session.CombinedOutput(cmd)
if err != nil {
output = append([]byte(err.Error()+"\n"), output...)
}
session.Close()
return string(output)
}

// name@host:port
func (s Server) String() string {
return fmt.Sprintf("%s@%s%s", s.User, s.Host, s.Port)
Expand Down Expand Up @@ -545,3 +570,12 @@ func (s *Server) diffFiles() string {

return diff
}

func (s *Server) prompt() string {
whoami := strings.TrimSpace(s.exec("whoami"))
hostname := strings.TrimSpace(s.exec("hostname"))
if whoami == "" || hostname == "" {
return fmt.Sprintf("%s:%s$", s, s.Home)
}
return fmt.Sprintf("%s@%s:%s$", whoami, hostname, s.Home)
}
2 changes: 1 addition & 1 deletion version.go
@@ -1,3 +1,3 @@
package main

const version = 15
const version = 16

0 comments on commit 14a7ce4

Please sign in to comment.