Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions cmd/marionette/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"context"
"log"
"time"

"google.golang.org/grpc"
)

func logInterceptor() grpc.ServerOption {
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
before := time.Now()
resp, err := handler(ctx, req)
after := time.Now()

status := "OK"
if err != nil {
status = "ERROR"
}
log.Printf("%s (%v) %s", info.FullMethod, after.Sub(before), status)

return resp, err
})
}
42 changes: 42 additions & 0 deletions cmd/marionette/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"flag"
"log"
"net"

"google.golang.org/grpc"
"google.golang.org/grpc/reflection"

"github.com/sourcegraph/src-cli/internal/marionette"
)

var (
address string
network string
workspace string
)

func init() {
flag.StringVar(&address, "address", ":50051", "address to bind to")
flag.StringVar(&network, "network", "tcp", "network to listen on")
flag.StringVar(&workspace, "workspace", "", "base path of the workspace")
}

func main() {
flag.Parse()

l, err := net.Listen(network, address)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}

s := grpc.NewServer(logInterceptor())
marionette.RegisterMarionetteServer(s, &server{workspace: workspace})
reflection.Register(s)

log.Printf("listening on %s %s", network, address)
if err := s.Serve(l); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
124 changes: 124 additions & 0 deletions cmd/marionette/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
"context"
"fmt"
"os/exec"
"strings"

"github.com/golang/protobuf/ptypes/empty"
"github.com/sourcegraph/src-cli/internal/campaigns"
"github.com/sourcegraph/src-cli/internal/marionette"
)

type server struct {
marionette.UnimplementedMarionetteServer

workspace string
}

var _ marionette.MarionetteServer = &server{}

func (s *server) Changes(ctx context.Context, req *empty.Empty) (*marionette.ChangesResponse, error) {
if _, err := s.command(ctx, "git", "add", "--all").run(); err != nil {
return nil, err
}

out, err := s.command(ctx, "git", "status", "--porcelain").run()
if err != nil {
return nil, err
}

changes, err := parseGitStatus(out)
if err != nil {
return nil, err
}

return &marionette.ChangesResponse{
Modified: changes.Modified,
Added: changes.Added,
Deleted: changes.Deleted,
Renamed: changes.Renamed,
}, nil
}

func (s *server) Diff(ctx context.Context, req *empty.Empty) (*marionette.DiffResponse, error) {
out, err := s.command(ctx, "git", "diff", "--cached", "--no-prefix", "--binary").run()
if err != nil {
return nil, err
}

return &marionette.DiffResponse{Diff: out}, nil
}

func (s *server) Prepare(ctx context.Context, req *empty.Empty) (*empty.Empty, error) {
if _, err := s.command(ctx, "git", "init").run(); err != nil {
return nil, err
}
if _, err := s.command(ctx, "git", "add", "--force", "--all").run(); err != nil {
return nil, err
}
if _, err := s.command(ctx, "git", "commit", "--quiet", "--all", "--allow-empty", "-m", "src-action-exec").run(); err != nil {
return nil, err
}

return &empty.Empty{}, nil
}

func (s *server) Unzip(ctx context.Context, req *marionette.UnzipRequest) (*empty.Empty, error) {
if _, err := s.command(ctx, "unzip", req.Path).run(); err != nil {
return nil, err
}

return &empty.Empty{}, nil
}

func (s *server) command(ctx context.Context, name string, arg ...string) command {
cmd := exec.CommandContext(ctx, name, arg...)
cmd.Dir = s.workspace

return command{cmd}
}

type command struct {
*exec.Cmd
}

func (c command) run() ([]byte, error) {
// TODO: wrap execution errors in something we can marshal back to the
// client.
return c.CombinedOutput()
}

// TODO: don't duplicate.
func parseGitStatus(out []byte) (campaigns.StepChanges, error) {
result := campaigns.StepChanges{}

stripped := strings.TrimSpace(string(out))
if len(stripped) == 0 {
return result, nil
}

for _, line := range strings.Split(stripped, "\n") {
if len(line) < 4 {
return result, fmt.Errorf("git status line has unrecognized format: %q", line)
}

file := line[3:]

switch line[0] {
case 'M':
result.Modified = append(result.Modified, file)
case 'A':
result.Added = append(result.Added, file)
case 'D':
result.Deleted = append(result.Deleted, file)
case 'R':
files := strings.Split(file, " -> ")
newFile := files[len(files)-1]
result.Renamed = append(result.Renamed, newFile)
}
}

return result, nil
}
2 changes: 1 addition & 1 deletion cmd/src/campaigns_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func newCampaignsApplyFlags(flagSet *flag.FlagSet, cacheDir, tempDir string) *ca
}
flagSet.StringVar(
&caf.workspace, "workspace", defaultWorkspace,
`Workspace mode to use ("bind" or "volume")`,
`Workspace mode to use ("bind", "marionette", or "volume")`,
)

flagSet.BoolVar(verbose, "v", false, "print verbose output")
Expand Down
12 changes: 12 additions & 0 deletions docker/marionette/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM golang:1.15-alpine as builder

RUN apk add --update protoc protobuf-dev

COPY . /src
WORKDIR /src
RUN go generate ./... && go build ./cmd/marionette

FROM sourcegraph/src-campaign-volume-workspace

COPY --from=builder /src/marionette /usr/sbin/marionette
CMD ["/usr/sbin/marionette"]
10 changes: 8 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/dustin/go-humanize v1.0.0
github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101
github.com/gobwas/glob v0.2.3
github.com/golang/protobuf v1.4.3
github.com/google/go-cmp v0.5.2
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.0
Expand All @@ -26,9 +27,14 @@ require (
github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/net v0.0.0-20200625001655-4c5254603344
golang.org/x/net v0.0.0-20201224014010-6772e930b67b
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4
golang.org/x/sys v0.0.0-20210105210732-16f7687f5001
golang.org/x/text v0.3.4 // indirect
google.golang.org/genproto v0.0.0-20210106152847-07624b53cd92 // indirect
google.golang.org/grpc v1.34.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.1
google.golang.org/protobuf v1.25.0
gopkg.in/yaml.v2 v2.3.0
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7
)
Expand Down
Loading