Skip to content
Merged
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
2 changes: 1 addition & 1 deletion git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func Test_CurrentBranch(t *testing.T) {

result, err := CurrentBranch()
if err != nil {
t.Errorf("got unexpected error: %w", err)
t.Errorf("got unexpected error: %v", err)
}
if result != v.Expected {
t.Errorf("unexpected branch name: %s instead of %s", result, v.Expected)
Expand Down
86 changes: 43 additions & 43 deletions pkg/cmd/codespace/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ func newPortsCmd(app *App) *cobra.Command {
portsCmd.PersistentFlags().StringVarP(&codespace, "codespace", "c", "", "Name of the codespace")
portsCmd.Flags().BoolVar(&asJSON, "json", false, "Output as JSON")

portsCmd.AddCommand(newPortsPublicCmd(app))
portsCmd.AddCommand(newPortsPrivateCmd(app))
portsCmd.AddCommand(newPortsForwardCmd(app))
portsCmd.AddCommand(newPortsPrivacyCmd(app))

return portsCmd
}
Expand Down Expand Up @@ -79,7 +78,7 @@ func (a *App) ListPorts(ctx context.Context, codespaceName string, asJSON bool)
}

table := output.NewTable(os.Stdout, asJSON)
table.SetHeader([]string{"Label", "Port", "Public", "Browse URL"})
table.SetHeader([]string{"Label", "Port", "Privacy", "Browse URL"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called Visibility ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josebalius either that or we change the sub command to "privacy". Do you have a preference?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah my initial suggestion was to use privacy. The VS Code UI calls this Privacy and I think we should match them, but I think we should then match our command to the column. Wdyt?
Screen Shot 2021-10-13 at 12 00 34 PM

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oddly I think the URL params use visibility e.g. https://auth.githubpreview.dev/authenticate-codespace/codespace-name?cid=<id>&path=<port>&port=4000&visibility=private. I can chat with the VSCode team about their use of that term.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josebalius sounds good 👍🏼 I just changed it to be called privacy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asciimike they are definitely being used interchangeably a bit. For example, the VSCode session server also calls it privacy: https://github.com/marwan-at-work/cli-1/blob/2ce3ea6cede0dc645c4e07c2c6a3fc0ad0f5f66c/pkg/liveshare/session.go#L74

From what I can see, "privacy" is more widely used and visible to the user than visibility. But it might be worth making the query param you mentioned above "privacy" as well on the github side.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for _, port := range ports {
sourcePort := strconv.Itoa(port.SourcePort)
var portName string
Expand All @@ -92,7 +91,7 @@ func (a *App) ListPorts(ctx context.Context, codespaceName string, asJSON bool)
table.Append([]string{
portName,
sourcePort,
strings.ToUpper(strconv.FormatBool(port.IsPublic)),
port.Privacy,
fmt.Sprintf("https://%s-%s.githubpreview.dev/", codespace.Name, sourcePort),
})
}
Expand Down Expand Up @@ -145,47 +144,33 @@ func getDevContainer(ctx context.Context, apiClient apiClient, codespace *api.Co
return ch
}

// newPortsPublicCmd returns a Cobra "ports public" subcommand, which makes a given port public.
func newPortsPublicCmd(app *App) *cobra.Command {
func newPortsPrivacyCmd(app *App) *cobra.Command {
return &cobra.Command{
Use: "public <port>",
Short: "Mark port as public",
Args: cobra.ExactArgs(1),
Use: "privacy <port:public|private|org>...",
Short: "Change the privacy of the forwarded port",
Example: "gh codespace ports privacy 80:org 3000:private 8000:public",
Args: cobra.ArbitraryArgs,
RunE: func(cmd *cobra.Command, args []string) error {
codespace, err := cmd.Flags().GetString("codespace")
if err != nil {
// should only happen if flag is not defined
// or if the flag is not of string type
// since it's a persistent flag that we control it should never happen
return fmt.Errorf("get codespace flag: %w", err)
if len(args) == 0 {
return fmt.Errorf("at least one port privacy argument is required")
}

return app.UpdatePortVisibility(cmd.Context(), codespace, args[0], true)
},
}
}

// newPortsPrivateCmd returns a Cobra "ports private" subcommand, which makes a given port private.
func newPortsPrivateCmd(app *App) *cobra.Command {
return &cobra.Command{
Use: "private <port>",
Short: "Mark port as private",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
codespace, err := cmd.Flags().GetString("codespace")
if err != nil {
// should only happen if flag is not defined
// or if the flag is not of string type
// since it's a persistent flag that we control it should never happen
return fmt.Errorf("get codespace flag: %w", err)
}

return app.UpdatePortVisibility(cmd.Context(), codespace, args[0], false)
return app.UpdatePortPrivacy(cmd.Context(), codespace, args)
},
}
}

func (a *App) UpdatePortVisibility(ctx context.Context, codespaceName, sourcePort string, public bool) (err error) {
func (a *App) UpdatePortPrivacy(ctx context.Context, codespaceName string, args []string) (err error) {
ports, err := a.parsePortPrivacies(args)
if err != nil {
return fmt.Errorf("error parsing port arguments: %w", err)
}
codespace, err := getOrChooseCodespace(ctx, a.apiClient, codespaceName)
if err != nil {
if err == errNoCodespaces {
Expand All @@ -200,24 +185,39 @@ func (a *App) UpdatePortVisibility(ctx context.Context, codespaceName, sourcePor
}
defer safeClose(session, &err)

port, err := strconv.Atoi(sourcePort)
if err != nil {
return fmt.Errorf("error reading port number: %w", err)
}

if err := session.UpdateSharedVisibility(ctx, port, public); err != nil {
return fmt.Errorf("error update port to public: %w", err)
}
for _, port := range ports {
if err := session.UpdateSharedServerPrivacy(ctx, port.number, port.privacy); err != nil {
return fmt.Errorf("error update port to public: %w", err)
}

state := "PUBLIC"
if !public {
state = "PRIVATE"
a.logger.Printf("Port %d is now %s scoped.\n", port.number, port.privacy)
}
a.logger.Printf("Port %s is now %s.\n", sourcePort, state)

return nil
}

type portPrivacy struct {
number int
privacy string
}

func (a *App) parsePortPrivacies(args []string) ([]portPrivacy, error) {
ports := make([]portPrivacy, 0, len(args))
for _, a := range args {
fields := strings.Split(a, ":")
if len(fields) != 2 {
return nil, fmt.Errorf("invalid port privacy format for %q", a)
}
portStr, privacy := fields[0], fields[1]
portNumber, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid port number: %w", err)
}
ports = append(ports, portPrivacy{portNumber, privacy})
}
return ports, nil
}

// NewPortsForwardCmd returns a Cobra "ports forward" subcommand, which forwards a set of
// port pairs from the codespace to localhost.
func newPortsForwardCmd(app *App) *cobra.Command {
Expand Down
6 changes: 3 additions & 3 deletions pkg/liveshare/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestConnect(t *testing.T) {
livesharetest.WithRelaySAS(opts.RelaySAS),
)
if err != nil {
t.Errorf("error creating Live Share server: %w", err)
t.Errorf("error creating Live Share server: %v", err)
}
defer server.Close()
opts.RelayEndpoint = "sb" + strings.TrimPrefix(server.URL(), "https")
Expand All @@ -65,10 +65,10 @@ func TestConnect(t *testing.T) {

select {
case err := <-server.Err():
t.Errorf("error from server: %w", err)
t.Errorf("error from server: %v", err)
case err := <-done:
if err != nil {
t.Errorf("error from client: %w", err)
t.Errorf("error from client: %v", err)
}
}
}
8 changes: 4 additions & 4 deletions pkg/liveshare/port_forwarder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestNewPortForwarder(t *testing.T) {
testServer, session, err := makeMockSession()
if err != nil {
t.Errorf("create mock client: %w", err)
t.Errorf("create mock client: %v", err)
}
defer testServer.Close()
pf := NewPortForwarder(session, "ssh", 80, false)
Expand All @@ -42,7 +42,7 @@ func TestPortForwarderStart(t *testing.T) {
livesharetest.WithStream("stream-id", stream),
)
if err != nil {
t.Errorf("create mock session: %w", err)
t.Errorf("create mock session: %v", err)
}
defer testServer.Close()

Expand Down Expand Up @@ -86,10 +86,10 @@ func TestPortForwarderStart(t *testing.T) {

select {
case err := <-testServer.Err():
t.Errorf("error from server: %w", err)
t.Errorf("error from server: %v", err)
case err := <-done:
if err != nil {
t.Errorf("error from client: %w", err)
t.Errorf("error from client: %v", err)
}
}
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/liveshare/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type Port struct {
IsPublic bool `json:"isPublic"`
IsTCPServerConnectionEstablished bool `json:"isTCPServerConnectionEstablished"`
HasTLSHandshakePassed bool `json:"hasTLSHandshakePassed"`
Privacy string `json:"privacy"`
}

// startSharing tells the Live Share host to start sharing the specified port from the container.
Expand All @@ -67,10 +68,10 @@ func (s *Session) GetSharedServers(ctx context.Context) ([]*Port, error) {
return response, nil
}

// UpdateSharedVisibility controls port permissions and whether it can be accessed publicly
// via the Browse URL
func (s *Session) UpdateSharedVisibility(ctx context.Context, port int, public bool) error {
if err := s.rpc.do(ctx, "serverSharing.updateSharedServerVisibility", []interface{}{port, public}, nil); err != nil {
// UpdateSharedServerPrivacy controls port permissions and visibility scopes for who can access its URLs
// in the browser.
func (s *Session) UpdateSharedServerPrivacy(ctx context.Context, port int, visibility string) error {
if err := s.rpc.do(ctx, "serverSharing.updateSharedServerPrivacy", []interface{}{port, visibility}, nil); err != nil {
return err
}

Expand Down
34 changes: 17 additions & 17 deletions pkg/liveshare/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func TestServerStartSharing(t *testing.T) {
defer testServer.Close() //nolint:staticcheck // httptest.Server does not return errors on Close()

if err != nil {
t.Errorf("error creating mock session: %w", err)
t.Errorf("error creating mock session: %v", err)
}
ctx := context.Background()

Expand All @@ -100,10 +100,10 @@ func TestServerStartSharing(t *testing.T) {

select {
case err := <-testServer.Err():
t.Errorf("error from server: %w", err)
t.Errorf("error from server: %v", err)
case err := <-done:
if err != nil {
t.Errorf("error from client: %w", err)
t.Errorf("error from client: %v", err)
}
}
}
Expand All @@ -121,7 +121,7 @@ func TestServerGetSharedServers(t *testing.T) {
livesharetest.WithService("serverSharing.getSharedServers", getSharedServers),
)
if err != nil {
t.Errorf("error creating mock session: %w", err)
t.Errorf("error creating mock session: %v", err)
}
defer testServer.Close()
ctx := context.Background()
Expand All @@ -148,15 +148,15 @@ func TestServerGetSharedServers(t *testing.T) {

select {
case err := <-testServer.Err():
t.Errorf("error from server: %w", err)
t.Errorf("error from server: %v", err)
case err := <-done:
if err != nil {
t.Errorf("error from client: %w", err)
t.Errorf("error from client: %v", err)
}
}
}

func TestServerUpdateSharedVisibility(t *testing.T) {
func TestServerUpdateSharedServerPrivacy(t *testing.T) {
updateSharedVisibility := func(rpcReq *jsonrpc2.Request) (interface{}, error) {
var req []interface{}
if err := json.Unmarshal(*rpcReq.Params, &req); err != nil {
Expand All @@ -172,33 +172,33 @@ func TestServerUpdateSharedVisibility(t *testing.T) {
} else {
return nil, errors.New("port param is not a float64")
}
if public, ok := req[1].(bool); ok {
if public != true {
return nil, errors.New("pulic param is not expected value")
if privacy, ok := req[1].(string); ok {
if privacy != "public" {
return nil, fmt.Errorf("expected privacy param to be public but got %q", privacy)
}
} else {
return nil, errors.New("public param is not a bool")
return nil, fmt.Errorf("expected privacy param to be a bool but go %T", req[1])
}
return nil, nil
}
testServer, session, err := makeMockSession(
livesharetest.WithService("serverSharing.updateSharedServerVisibility", updateSharedVisibility),
livesharetest.WithService("serverSharing.updateSharedServerPrivacy", updateSharedVisibility),
)
if err != nil {
t.Errorf("creating mock session: %w", err)
t.Errorf("creating mock session: %v", err)
}
defer testServer.Close()
ctx := context.Background()
done := make(chan error)
go func() {
done <- session.UpdateSharedVisibility(ctx, 80, true)
done <- session.UpdateSharedServerPrivacy(ctx, 80, "public")
}()
select {
case err := <-testServer.Err():
t.Errorf("error from server: %w", err)
t.Errorf("error from server: %v", err)
case err := <-done:
if err != nil {
t.Errorf("error from client: %w", err)
t.Errorf("error from client: %v", err)
}
}
}
Expand All @@ -214,7 +214,7 @@ func TestInvalidHostKey(t *testing.T) {
}
testServer, err := livesharetest.NewServer(opts...)
if err != nil {
t.Errorf("error creating server: %w", err)
t.Errorf("error creating server: %v", err)
}
_, err = Connect(context.Background(), Options{
SessionID: "session-id",
Expand Down