grim/hgkeeper

overhaul how commands are found/run

2019-07-23, Gary Kramlich
eb233ca9b428
Parents 713e642eb9bc
Children 76eb73c033fc
overhaul how commands are found/run
--- a/access/access.go Tue Jul 23 15:30:21 2019 -0500
+++ b/access/access.go Tue Jul 23 16:08:13 2019 -0500
@@ -38,7 +38,7 @@
repoPath string
- global Acl
+ Global Acl
groups map[string][]string
patterns map[string]Acl
@@ -73,7 +73,7 @@
return err
}
- a.global = data.Global
+ a.Global = data.Global
a.groups = data.Groups
a.patterns = data.Patterns
@@ -83,7 +83,7 @@
func (a *Access) findUsers() []string {
users := map[string]bool{}
- for _, name := range a.global.Users() {
+ for _, name := range a.Global.Users() {
// don't add groups to the users list
if _, found := a.groups[name]; found {
continue
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/commands.go Tue Jul 23 16:08:13 2019 -0500
@@ -0,0 +1,62 @@
+package commands
+
+import (
+ "fmt"
+
+ "github.com/alecthomas/kong"
+ "github.com/kballard/go-shellquote"
+ "golang.org/x/crypto/ssh"
+
+ "bitbucket.org/rw_grim/hgkeeper/access"
+)
+
+type cli struct {
+ Hg struct {
+ Repo string `kong:"flag,short='R'"`
+ Serve struct {
+ Stdio bool `kong:"flag,name='stdio'"`
+ } `kong:"cmd"`
+ Init struct {
+ Repo string `kong:"arg"`
+ } `kong:"cmd"`
+ } `kong:"cmd"`
+}
+
+type Command interface {
+ Run(conn ssh.Channel, serverConn *ssh.ServerConn, req *ssh.Request) error
+ CheckAccess(access access.Access, username string) bool
+}
+
+func parse(cmd string) (cli, string, error) {
+ values := cli{}
+
+ args, err := shellquote.Split(cmd)
+ if err != nil {
+ return values, "", err
+ }
+
+ parser := kong.Must(&values)
+
+ ctx, err := parser.Parse(args)
+ if err != nil {
+ return values, "", err
+ }
+
+ return values, ctx.Command(), nil
+}
+
+func Find(cmd, reposPath string) (Command, error) {
+ values, pcmd, err := parse(cmd)
+ if err != nil {
+ return nil, err
+ }
+
+ switch pcmd {
+ case "hg serve":
+ return NewServe(reposPath, values.Hg.Repo), nil
+ case "hg init <repo>":
+ return NewInit(reposPath, values.Hg.Init.Repo), nil
+ default:
+ return nil, fmt.Errorf("unknown command %s", cmd)
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/commands_test.go Tue Jul 23 16:08:13 2019 -0500
@@ -0,0 +1,39 @@
+package commands
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type testCase struct {
+ input string
+ expected Command
+}
+
+func TestRepoFromCommand(t *testing.T) {
+ assert := assert.New(t)
+
+ cases := []testCase{
+ {
+ "hg serve --stdio",
+ NewServe("repos", ""),
+ },
+ {
+ "hg -R foo serve --stdio",
+ NewServe("repos", "foo"),
+ },
+ {
+ "hg -R foo/bar serve --stdio",
+ NewServe("repos", "foo/bar"),
+ },
+ }
+
+ for _, testCase := range cases {
+ cmd, err := Find(testCase.input, "repos")
+ assert.Nil(err)
+ assert.NotNil(cmd)
+
+ assert.Equal(cmd, testCase.expected)
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/init.go Tue Jul 23 16:08:13 2019 -0500
@@ -0,0 +1,29 @@
+package commands
+
+import (
+ "golang.org/x/crypto/ssh"
+ "path/filepath"
+
+ "bitbucket.org/rw_grim/hgkeeper/access"
+ "bitbucket.org/rw_grim/hgkeeper/hg"
+)
+
+type Init struct {
+ repoPath string
+ repoName string
+}
+
+func NewInit(reposPath, repoName string) *Init {
+ return &Init{
+ repoPath: filepath.Join(reposPath, repoName),
+ repoName: repoName,
+ }
+}
+
+func (i *Init) Run(conn ssh.Channel, serverConn *ssh.ServerConn, req *ssh.Request) error {
+ return run(hg.Init(i.repoPath), conn, serverConn, req)
+}
+
+func (i *Init) CheckAccess(access access.Access, username string) bool {
+ return access.Global.CanInit(username)
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/run.go Tue Jul 23 16:08:13 2019 -0500
@@ -0,0 +1,78 @@
+package commands
+
+import (
+ "io"
+ "os/exec"
+ "strings"
+ "sync"
+
+ log "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh"
+)
+
+func run(cmd *exec.Cmd, conn ssh.Channel, serverConn *ssh.ServerConn, req *ssh.Request) error {
+ teardown := func() {
+ conn.Close()
+
+ if err := cmd.Wait(); err != nil {
+ log.Warnf(
+ "%s command %q failed: %v",
+ serverConn.RemoteAddr(),
+ strings.Join(cmd.Args, " "),
+ err,
+ )
+
+ req.Reply(false, nil)
+
+ return
+ }
+
+ log.Debugf(
+ "%s command %s finished",
+ serverConn.RemoteAddr(),
+ strings.Join(cmd.Args, " "),
+ )
+ req.Reply(true, nil)
+ }
+
+ stdinWriter, err := cmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+
+ stdoutReader, err := cmd.StdoutPipe()
+ if err != nil {
+ return err
+ }
+
+ stderrReader, err := cmd.StderrPipe()
+ if err != nil {
+ return err
+ }
+
+ var once sync.Once
+
+ // now wire up stdin/stdout/stderr
+ go func() {
+ io.Copy(stdinWriter, conn)
+ once.Do(teardown)
+ }()
+
+ go func() {
+ io.Copy(conn, stdoutReader)
+ once.Do(teardown)
+ }()
+
+ go func() {
+ io.Copy(conn.Stderr(), stderrReader)
+ once.Do(teardown)
+ }()
+
+ if err := cmd.Start(); err != nil {
+ req.Reply(false, nil)
+ once.Do(teardown)
+ return err
+ }
+
+ return nil
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/serve.go Tue Jul 23 16:08:13 2019 -0500
@@ -0,0 +1,30 @@
+package commands
+
+import (
+ "path/filepath"
+
+ "golang.org/x/crypto/ssh"
+
+ "bitbucket.org/rw_grim/hgkeeper/access"
+ "bitbucket.org/rw_grim/hgkeeper/hg"
+)
+
+type Serve struct {
+ repoPath string
+ repoName string
+}
+
+func NewServe(reposPath, repoName string) *Serve {
+ return &Serve{
+ repoPath: filepath.Join(reposPath, repoName),
+ repoName: repoName,
+ }
+}
+
+func (s *Serve) Run(conn ssh.Channel, serverConn *ssh.ServerConn, req *ssh.Request) error {
+ return run(hg.Serve(s.repoPath), conn, serverConn, req)
+}
+
+func (s *Serve) CheckAccess(access access.Access, username string) bool {
+ return access.Global.CanRead(username)
+}
--- a/ssh/command.go Tue Jul 23 15:30:21 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,148 +0,0 @@
-package ssh
-
-import (
- "fmt"
- "io"
- "os/exec"
- "path/filepath"
- "strings"
- "sync"
-
- "github.com/alecthomas/kong"
- "github.com/kballard/go-shellquote"
- log "github.com/sirupsen/logrus"
- "golang.org/x/crypto/ssh"
-
- "bitbucket.org/rw_grim/hgkeeper/hg"
-)
-
-type cli struct {
- Hg struct {
- Repo string `kong:"flag,short='R'"`
- Serve struct {
- Stdio bool `kong:"flag,name='stdio'"`
- } `kong:"cmd"`
- Init struct {
- Repo string `kong:"arg"`
- } `kong:"cmd"`
- } `kong:"cmd"`
-}
-
-type Command struct {
- path string
- cmd *exec.Cmd
-}
-
-func NewCommand(path string, cmd *exec.Cmd) *Command {
- return &Command{
- path: path,
- cmd: cmd,
- }
-}
-
-func (c *Command) run(conn ssh.Channel, serverConn *ssh.ServerConn, req *ssh.Request) error {
- teardown := func() {
- conn.Close()
-
- if err := c.cmd.Wait(); err != nil {
- log.Warnf(
- "%s command %q failed: %v",
- serverConn.RemoteAddr(),
- strings.Join(c.cmd.Args, " "),
- err,
- )
-
- req.Reply(false, nil)
-
- return
- }
-
- log.Debugf(
- "%s command %s finished",
- serverConn.RemoteAddr(),
- strings.Join(c.cmd.Args, " "),
- )
- req.Reply(true, nil)
- }
-
- stdinWriter, err := c.cmd.StdinPipe()
- if err != nil {
- return err
- }
-
- stdoutReader, err := c.cmd.StdoutPipe()
- if err != nil {
- return err
- }
-
- stderrReader, err := c.cmd.StderrPipe()
- if err != nil {
- return err
- }
-
- var once sync.Once
-
- // now wire up stdin/stdout/stderr
- go func() {
- io.Copy(stdinWriter, conn)
- once.Do(teardown)
- }()
-
- go func() {
- io.Copy(conn, stdoutReader)
- once.Do(teardown)
- }()
-
- go func() {
- io.Copy(conn.Stderr(), stderrReader)
- once.Do(teardown)
- }()
-
- if err := c.cmd.Start(); err != nil {
- req.Reply(false, nil)
- once.Do(teardown)
- return err
- }
-
- return nil
-}
-
-func parseCommand(cmd string) (cli, string, error) {
- values := cli{}
-
- args, err := shellquote.Split(cmd)
- if err != nil {
- return values, "", err
- }
-
- parser := kong.Must(&values)
-
- ctx, err := parser.Parse(args)
- if err != nil {
- return values, "", err
- }
-
- return values, ctx.Command(), nil
-}
-
-func findCommand(cmd, reposPath string) (*Command, error) {
- values, pcmd, err := parseCommand(cmd)
- if err != nil {
- return nil, err
- }
-
- switch pcmd {
- case "hg serve":
- return NewCommand(
- values.Hg.Repo,
- hg.Serve(filepath.Join(reposPath, values.Hg.Repo)),
- ), nil
- case "hg init <repo>":
- return NewCommand(
- values.Hg.Init.Repo,
- hg.Init(filepath.Join(reposPath, values.Hg.Init.Repo)),
- ), nil
- default:
- return nil, fmt.Errorf("unknown command %s", cmd)
- }
-}
--- a/ssh/command_test.go Tue Jul 23 15:30:21 2019 -0500
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-package ssh
-
-import (
- "os/exec"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-type testCase struct {
- input string
- expected *exec.Cmd
-}
-
-func TestRepoFromCommand(t *testing.T) {
- assert := assert.New(t)
-
- cases := []testCase{
- {
- "hg serve --stdio",
- &exec.Cmd{
- Path: "hg",
- Args: []string{"hg", "-R", "repos", "serve", "--stdio"},
- },
- },
- {
- "hg -R foo serve --stdio",
- &exec.Cmd{
- Path: "hg",
- Args: []string{"hg", "-R", "repos/foo", "serve", "--stdio"},
- },
- },
- {
- "hg -R foo/bar serve --stdio",
- &exec.Cmd{
- Path: "hg",
- Args: []string{"hg", "-R", "repos/foo/bar", "serve", "--stdio"},
- },
- },
- }
-
- for _, testCase := range cases {
- cmd, err := findCommand(testCase.input, "repos")
- assert.Nil(err)
- assert.NotNil(cmd)
-
- assert.Equal(cmd.cmd.Args, testCase.expected.Args)
- }
-}