311 lines
7.8 KiB
Go
311 lines
7.8 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/github/ghcs/api"
|
|
"github.com/github/ghcs/internal/codespaces"
|
|
"github.com/github/go-liveshare"
|
|
"github.com/muhammadmuzzammil1998/jsonc"
|
|
"github.com/olekukonko/tablewriter"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func NewPortsCmd() *cobra.Command {
|
|
portsCmd := &cobra.Command{
|
|
Use: "ports",
|
|
Short: "ports",
|
|
Long: "ports",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return Ports()
|
|
},
|
|
}
|
|
|
|
portsCmd.AddCommand(NewPortsPublicCmd())
|
|
portsCmd.AddCommand(NewPortsPrivateCmd())
|
|
portsCmd.AddCommand(NewPortsForwardCmd())
|
|
return portsCmd
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(NewPortsCmd())
|
|
}
|
|
|
|
func Ports() error {
|
|
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
|
ctx := context.Background()
|
|
|
|
user, err := apiClient.GetUser(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting user: %v", err)
|
|
}
|
|
|
|
codespace, err := codespaces.ChooseCodespace(ctx, apiClient, user)
|
|
if err != nil {
|
|
if err == codespaces.ErrNoCodespaces {
|
|
fmt.Println(err.Error())
|
|
return nil
|
|
}
|
|
return fmt.Errorf("error choosing codespace: %v", err)
|
|
}
|
|
|
|
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...")
|
|
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())
|
|
}
|
|
|
|
table := tablewriter.NewWriter(os.Stdout)
|
|
table.SetHeader([]string{"Label", "Source Port", "Destination Port", "Public", "Browse URL"})
|
|
for _, port := range ports {
|
|
sourcePort := strconv.Itoa(port.SourcePort)
|
|
var portName string
|
|
if devContainerResult.DevContainer != nil {
|
|
if attributes, ok := devContainerResult.DevContainer.PortAttributes[sourcePort]; ok {
|
|
portName = attributes.Label
|
|
}
|
|
}
|
|
|
|
table.Append([]string{
|
|
portName,
|
|
sourcePort,
|
|
strconv.Itoa(port.DestinationPort),
|
|
strings.ToUpper(strconv.FormatBool(port.IsPublic)),
|
|
fmt.Sprintf("https://%s-%s.githubpreview.dev/", codespace.Name, sourcePort),
|
|
})
|
|
}
|
|
table.Render()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
func getPorts(ctx context.Context, liveShareClient *liveshare.Client) (liveshare.Ports, error) {
|
|
server, err := liveShareClient.NewServer()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating server: %v", err)
|
|
}
|
|
|
|
ports, err := server.GetSharedServers(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting shared servers: %v", err)
|
|
}
|
|
|
|
return ports, nil
|
|
}
|
|
|
|
type devContainerResult struct {
|
|
DevContainer *devContainer
|
|
Err error
|
|
}
|
|
|
|
type devContainer struct {
|
|
PortAttributes map[string]portAttribute `json:"portsAttributes"`
|
|
}
|
|
|
|
type portAttribute struct {
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
func getDevContainer(ctx context.Context, apiClient *api.API, codespace *api.Codespace) <-chan devContainerResult {
|
|
ch := make(chan devContainerResult)
|
|
go func() {
|
|
contents, err := apiClient.GetCodespaceRepositoryContents(ctx, codespace, ".devcontainer/devcontainer.json")
|
|
if err != nil {
|
|
ch <- devContainerResult{nil, fmt.Errorf("error getting content: %v", err)}
|
|
return
|
|
}
|
|
|
|
if contents == nil {
|
|
ch <- devContainerResult{nil, nil}
|
|
return
|
|
}
|
|
|
|
convertedJSON := jsonc.ToJSON(contents)
|
|
if !jsonc.Valid(convertedJSON) {
|
|
ch <- devContainerResult{nil, errors.New("failed to convert json to standard json")}
|
|
return
|
|
}
|
|
|
|
var container devContainer
|
|
if err := json.Unmarshal(convertedJSON, &container); err != nil {
|
|
ch <- devContainerResult{nil, fmt.Errorf("error unmarshaling: %v", err)}
|
|
return
|
|
}
|
|
|
|
ch <- devContainerResult{&container, nil}
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
func NewPortsPublicCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "public",
|
|
Short: "public",
|
|
Long: "public",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) < 2 {
|
|
return errors.New("[codespace_name] [source] port number are required.")
|
|
}
|
|
|
|
return updatePortVisibility(args[0], args[1], true)
|
|
},
|
|
}
|
|
}
|
|
|
|
func NewPortsPrivateCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "private",
|
|
Short: "private",
|
|
Long: "private",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) < 2 {
|
|
return errors.New("[codespace_name] [source] port number are required.")
|
|
}
|
|
|
|
return updatePortVisibility(args[0], args[1], false)
|
|
},
|
|
}
|
|
}
|
|
|
|
func updatePortVisibility(codespaceName, sourcePort string, public bool) error {
|
|
ctx := context.Background()
|
|
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
|
|
|
user, err := apiClient.GetUser(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting user: %v", err)
|
|
}
|
|
|
|
token, err := apiClient.GetCodespaceToken(ctx, user.Login, codespaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting codespace token: %v", err)
|
|
}
|
|
|
|
codespace, err := apiClient.GetCodespace(ctx, token, user.Login, codespaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting codespace: %v", err)
|
|
}
|
|
|
|
liveShareClient, err := codespaces.ConnectToLiveshare(ctx, apiClient, token, codespace)
|
|
if err != nil {
|
|
return fmt.Errorf("error connecting to liveshare: %v", err)
|
|
}
|
|
|
|
server, err := liveShareClient.NewServer()
|
|
if err != nil {
|
|
return fmt.Errorf("error creating server: %v", err)
|
|
}
|
|
|
|
port, err := strconv.Atoi(sourcePort)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading port number: %v", err)
|
|
}
|
|
|
|
if err := server.UpdateSharedVisibility(ctx, port, public); err != nil {
|
|
return fmt.Errorf("error update port to public: %v", err)
|
|
}
|
|
|
|
state := "PUBLIC"
|
|
if public == false {
|
|
state = "PRIVATE"
|
|
}
|
|
|
|
fmt.Println(fmt.Sprintf("Port %s is now %s.", sourcePort, state))
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewPortsForwardCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "forward",
|
|
Short: "forward",
|
|
Long: "forward",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) < 3 {
|
|
return errors.New("[codespace_name] [source] [dst] port number are required.")
|
|
}
|
|
return forwardPort(args[0], args[1], args[2])
|
|
},
|
|
}
|
|
}
|
|
|
|
func forwardPort(codespaceName, sourcePort, destPort string) error {
|
|
ctx := context.Background()
|
|
apiClient := api.New(os.Getenv("GITHUB_TOKEN"))
|
|
|
|
user, err := apiClient.GetUser(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting user: %v", err)
|
|
}
|
|
|
|
token, err := apiClient.GetCodespaceToken(ctx, user.Login, codespaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting codespace token: %v", err)
|
|
}
|
|
|
|
codespace, err := apiClient.GetCodespace(ctx, token, user.Login, codespaceName)
|
|
if err != nil {
|
|
return fmt.Errorf("error getting codespace: %v", err)
|
|
}
|
|
|
|
liveShareClient, err := codespaces.ConnectToLiveshare(ctx, apiClient, token, codespace)
|
|
if err != nil {
|
|
return fmt.Errorf("error connecting to liveshare: %v", err)
|
|
}
|
|
|
|
server, err := liveShareClient.NewServer()
|
|
if err != nil {
|
|
return fmt.Errorf("error creating server: %v", err)
|
|
}
|
|
|
|
sourcePortInt, err := strconv.Atoi(sourcePort)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading source port: %v", err)
|
|
}
|
|
|
|
dstPortInt, err := strconv.Atoi(destPort)
|
|
if err != nil {
|
|
return fmt.Errorf("error reading destination port: %v", err)
|
|
}
|
|
|
|
if err := server.StartSharing(ctx, "share-"+sourcePort, sourcePortInt); err != nil {
|
|
return fmt.Errorf("error sharing source port: %v", err)
|
|
}
|
|
|
|
fmt.Println("Forwarding port: " + sourcePort + " -> " + destPort)
|
|
portForwarder := liveshare.NewLocalPortForwarder(liveShareClient, server, dstPortInt)
|
|
if err := portForwarder.Start(ctx); err != nil {
|
|
return fmt.Errorf("error forwarding port: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|