Add machine-readable output functionality to ports command
This commit is contained in:
parent
7a9d1fc331
commit
db95f2f71f
4 changed files with 93 additions and 22 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
45
cmd/ghcs/output/logger.go
Normal 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...)
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue