Add machine-readable output functionality to ports command

This commit is contained in:
Mislav Marohnić 2021-08-12 14:35:49 +02:00
parent 7a9d1fc331
commit db95f2f71f
4 changed files with 93 additions and 22 deletions

View file

@ -3,6 +3,8 @@ package output
import (
"encoding/json"
"io"
"strings"
"unicode"
)
type jsonwriter struct {
@ -19,7 +21,7 @@ func (j *jsonwriter) SetHeader(cols []string) {
func (j *jsonwriter) Append(values []string) {
row := make(map[string]string)
for i, v := range values {
row[j.cols[i]] = v
row[camelize(j.cols[i])] = v
}
j.data = append(j.data, row)
}
@ -31,3 +33,23 @@ func (j *jsonwriter) Render() {
}
_ = enc.Encode(j.data)
}
func camelize(s string) string {
var b strings.Builder
capitalizeNext := false
for i, r := range s {
if r == ' ' {
capitalizeNext = true
continue
}
if capitalizeNext {
b.WriteRune(unicode.ToUpper(r))
capitalizeNext = false
} else if i == 0 {
b.WriteRune(unicode.ToLower(r))
} else {
b.WriteRune(r)
}
}
return b.String()
}

View file

@ -15,9 +15,7 @@ type Table interface {
}
func NewTable(w io.Writer, asJSON bool) Table {
f, ok := w.(*os.File)
isTTY := ok && term.IsTerminal(int(f.Fd()))
isTTY := isTTY(w)
if asJSON {
return &jsonwriter{w: w, pretty: isTTY}
}
@ -26,3 +24,8 @@ func NewTable(w io.Writer, asJSON bool) Table {
}
return &tabwriter{w: w}
}
func isTTY(w io.Writer) bool {
f, ok := w.(*os.File)
return ok && term.IsTerminal(int(f.Fd()))
}

45
cmd/ghcs/output/logger.go Normal file
View file

@ -0,0 +1,45 @@
package output
import (
"fmt"
"io"
)
func NewLogger(stdout, stderr io.Writer, disabled bool) *Logger {
return &Logger{
out: stdout,
errout: stderr,
enabled: !disabled && isTTY(stdout),
}
}
type Logger struct {
out io.Writer
errout io.Writer
enabled bool
}
func (l *Logger) Print(v ...interface{}) (int, error) {
if !l.enabled {
return 0, nil
}
return fmt.Fprint(l.out, v...)
}
func (l *Logger) Println(v ...interface{}) (int, error) {
if !l.enabled {
return 0, nil
}
return fmt.Fprintln(l.out, v...)
}
func (l *Logger) Printf(f string, v ...interface{}) (int, error) {
if !l.enabled {
return 0, nil
}
return fmt.Fprintf(l.out, f, v...)
}
func (l *Logger) Errorf(f string, v ...interface{}) (int, error) {
return fmt.Fprintf(l.errout, f, v...)
}

View file

@ -10,25 +10,36 @@ import (
"strings"
"github.com/github/ghcs/api"
"github.com/github/ghcs/cmd/ghcs/output"
"github.com/github/ghcs/internal/codespaces"
"github.com/github/go-liveshare"
"github.com/muhammadmuzzammil1998/jsonc"
"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
)
type PortsOptions struct {
CodespaceName string
AsJSON bool
}
func NewPortsCmd() *cobra.Command {
opts := &PortsOptions{}
portsCmd := &cobra.Command{
Use: "ports",
Short: "Forward ports from a GitHub Codespace.",
RunE: func(cmd *cobra.Command, args []string) error {
return Ports()
return Ports(opts)
},
}
portsCmd.Flags().StringVarP(&opts.CodespaceName, "name", "n", "", "Name of Codespace to use")
portsCmd.Flags().BoolVar(&opts.AsJSON, "json", false, "Output as JSON")
portsCmd.AddCommand(NewPortsPublicCmd())
portsCmd.AddCommand(NewPortsPrivateCmd())
portsCmd.AddCommand(NewPortsForwardCmd())
return portsCmd
}
@ -36,16 +47,17 @@ func init() {
rootCmd.AddCommand(NewPortsCmd())
}
func Ports() error {
func Ports(opts *PortsOptions) error {
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
ctx := context.Background()
log := output.NewLogger(os.Stdout, os.Stderr, opts.AsJSON)
user, err := apiClient.GetUser(ctx)
if err != nil {
return fmt.Errorf("error getting user: %v", err)
}
codespace, err := codespaces.ChooseCodespace(ctx, apiClient, user)
codespace, token, err := codespaces.GetOrChooseCodespace(ctx, apiClient, user, opts.CodespaceName)
if err != nil {
if err == codespaces.ErrNoCodespaces {
fmt.Println(err.Error())
@ -56,33 +68,23 @@ func Ports() error {
devContainerCh := getDevContainer(ctx, apiClient, codespace)
token, err := apiClient.GetCodespaceToken(ctx, user.Login, codespace.Name)
if err != nil {
return fmt.Errorf("error getting codespace token: %v", err)
}
liveShareClient, err := codespaces.ConnectToLiveshare(ctx, apiClient, token, codespace)
if err != nil {
return fmt.Errorf("error connecting to liveshare: %v", err)
}
fmt.Println("Loading ports...")
log.Println("Loading ports...")
ports, err := getPorts(ctx, liveShareClient)
if err != nil {
return fmt.Errorf("error getting ports: %v", err)
}
if len(ports) == 0 {
fmt.Println("This codespace has no open ports")
return nil
}
devContainerResult := <-devContainerCh
if devContainerResult.Err != nil {
fmt.Printf("Failed to get port names: %v\n", devContainerResult.Err.Error())
_, _ = log.Errorf("Failed to get port names: %v\n", devContainerResult.Err.Error())
}
table := tablewriter.NewWriter(os.Stdout)
table := output.NewTable(os.Stdout, opts.AsJSON)
table.SetHeader([]string{"Label", "Source Port", "Destination Port", "Public", "Browse URL"})
for _, port := range ports {
sourcePort := strconv.Itoa(port.SourcePort)
@ -104,7 +106,6 @@ func Ports() error {
table.Render()
return nil
}
func getPorts(ctx context.Context, lsclient *liveshare.Client) (liveshare.Ports, error) {