Skip to content

Commit

Permalink
feat: rewrite tunnel interface, add list, reconnect underlying expose…
Browse files Browse the repository at this point in the history
… tunnel (#30)
  • Loading branch information
jaredallard authored Dec 8, 2020
1 parent 405e55e commit 03771f8
Show file tree
Hide file tree
Showing 27 changed files with 1,787 additions and 1,063 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ replacements/

# Build stuff
dist/

# debugger
__debug_bin
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ builds:
- main: ./cmd/localizer
id: &name localizer
binary: *name
ldflags:
- '-w -s -X "main.Version=v{{ .Version }}"'
goos:
- linux
- darwin
Expand Down
348 changes: 295 additions & 53 deletions api/v1/v1.pb.go

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions api/v1/v1.proto
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ message ExposeServiceRequest {
repeated string port_map = 3;
}

message ListRequest {}

message StopExposeRequest {
string namespace = 1;
string service = 2;
Expand All @@ -32,7 +34,22 @@ message ConsoleResponse {
string message = 2;
}

message ListService {
string namespace = 1;
string name = 2;
string status = 3;
string endpoint = 4;
string status_reason = 5;
string ip = 6;
repeated string ports = 7;
}

message ListResponse {
repeated ListService services = 1;
}

service LocalizerService {
rpc ExposeService(ExposeServiceRequest) returns (stream ConsoleResponse) {}
rpc StopExpose(StopExposeRequest) returns (stream ConsoleResponse) {}
rpc List(ListRequest) returns (ListResponse) {}
}
39 changes: 38 additions & 1 deletion api/v1/v1_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 100 additions & 0 deletions cmd/localizer/expose.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package main

import (
"context"
"fmt"
"io"
"os"
"strings"
"time"

apiv1 "github.com/jaredallard/localizer/api/v1"
"github.com/jaredallard/localizer/internal/server"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"
)

func NewExposeCommand(log logrus.FieldLogger) *cli.Command {
return &cli.Command{
Name: "expose",
Description: "Expose ports for a given service to Kubernetes",
Usage: "expose <namespace/service>",
Flags: []cli.Flag{
&cli.StringSliceFlag{
Name: "map",
Usage: "Map a local port to a remote port, i.e --map 80:8080 will bind what is normally :8080 to :80 locally",
},
&cli.BoolFlag{
Name: "stop",
Usage: "stop exposing a service",
},
},
// TODO: multiple service support before this gets released
Action: func(c *cli.Context) error {
split := strings.Split(c.Args().First(), "/")
if len(split) != 2 {
return fmt.Errorf("invalid service, expected namespace/name")
}

serviceNamespace := split[0]
serviceName := split[1]

if _, err := os.Stat(server.SocketPath); os.IsNotExist(err) {
return fmt.Errorf("localizer daemon not running (run localizer by itself?)")
}

ctx, cancel := context.WithTimeout(c.Context, 30*time.Second)
defer cancel()

log.Info("connecting to localizer daemon")
conn, err := grpc.DialContext(ctx, "unix://"+server.SocketPath,
grpc.WithBlock(), grpc.WithInsecure())
if err != nil {
return errors.Wrap(err, "failed to talk to localizer daemon")
}

client := apiv1.NewLocalizerServiceClient(conn)

var stream apiv1.LocalizerService_ExposeServiceClient
if c.Bool("stop") {
log.Info("sending stop expose request to daemon")
stream, err = client.StopExpose(ctx, &apiv1.StopExposeRequest{
Namespace: serviceNamespace,
Service: serviceName,
})
} else {
log.Info("sending expose request to daemon")
stream, err = client.ExposeService(ctx, &apiv1.ExposeServiceRequest{
PortMap: c.StringSlice("map"),
Namespace: serviceNamespace,
Service: serviceName,
})
}
if err != nil {
return err
}

for {
res, err := stream.Recv()
if err == io.EOF {
return nil
} else if err != nil {
return err
}

logger := log.Info
switch res.Level {
case apiv1.ConsoleLevel_CONSOLE_LEVEL_INFO, apiv1.ConsoleLevel_CONSOLE_LEVEL_UNSPECIFIED:
case apiv1.ConsoleLevel_CONSOLE_LEVEL_WARN:
logger = log.Warn
case apiv1.ConsoleLevel_CONSOLE_LEVEL_ERROR:
logger = log.Error
}

logger(res.Message)
}
},
}
}
74 changes: 74 additions & 0 deletions cmd/localizer/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

import (
"context"
"fmt"
"os"
"sort"
"strings"
"text/tabwriter"
"time"

apiv1 "github.com/jaredallard/localizer/api/v1"
"github.com/jaredallard/localizer/internal/server"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"
)

func NewListCommand(_ logrus.FieldLogger) *cli.Command {
return &cli.Command{
Name: "list",
Description: "list all port-forwarded services and their status(es)",
Usage: "list",
Action: func(c *cli.Context) error {
if _, err := os.Stat(server.SocketPath); os.IsNotExist(err) {
return fmt.Errorf("localizer daemon not running (run localizer by itself?)")
}

ctx, cancel := context.WithTimeout(c.Context, 30*time.Second)
defer cancel()

conn, err := grpc.DialContext(ctx, "unix://"+server.SocketPath,
grpc.WithBlock(), grpc.WithInsecure())
if err != nil {
return errors.Wrap(err, "failed to talk to localizer daemon")
}

client := apiv1.NewLocalizerServiceClient(conn)
resp, err := client.List(ctx, &apiv1.ListRequest{})
if err != nil {
return err
}

w := tabwriter.NewWriter(os.Stdout, 10, 0, 3, ' ', 0)
defer w.Flush()

fmt.Fprintf(w, "NAMESPACE\tNAME\tSTATUS\tREASON\tENDPOINT\tIP ADDRESS\tPORT(S)\t\n")

// sort by namespace and then by name
sort.Slice(resp.Services, func(i, j int) bool {
return resp.Services[i].Namespace < resp.Services[j].Namespace
})
sort.Slice(resp.Services, func(i, j int) bool {
return resp.Services[i].Name < resp.Services[j].Name
})

for _, s := range resp.Services {
status := strings.ToUpper(s.Status[:1]) + s.Status[1:]
ip := s.Ip
if ip == "" {
ip = "None"
}

fmt.Fprintf(w,
"%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
s.Namespace, s.Name, status, s.StatusReason, s.Endpoint, ip, strings.Join(s.Ports, ","),
)
}

return nil
},
}
}
Loading

0 comments on commit 03771f8

Please sign in to comment.