Skip to content

Commit

Permalink
Merge pull request #98 from hashicorp/b-command-escaping
Browse files Browse the repository at this point in the history
Fix panic displaying invoked command
  • Loading branch information
dadgar authored May 31, 2024
2 parents fe33862 + f2b21ff commit 4570a18
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .changelog/98.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
Fix rare panic that could occur on authentication error when running a command that had quoted arguments.
```
40 changes: 29 additions & 11 deletions internal/pkg/cmd/command_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"os"
"slices"
"strconv"
"strings"

"github.com/go-openapi/runtime"
Expand Down Expand Up @@ -107,17 +108,7 @@ func (c *Command) Run(args []string) int {
return 1
} else if errors.As(err, &runtimeErr) && runtimeErr.IsCode(http.StatusUnauthorized) {
// Request failed because of authentication issues.
fmt.Fprintf(io.Err(), "%s %s\n\n",
cs.ErrorLabel(),
heredoc.New(io, heredoc.WithPreserveNewlines()).Mustf(`
Unauthorized request. Re-attempt by first logging out and back in, and then re-run the command.
{{ Bold "$ hcp auth logout" }}
{{ Bold "$ hcp auth login" }}
{{ Bold "$ %s %s" }}
`, c.commandPath(), strings.Join(args, " "),
),
)
fmt.Fprintf(io.Err(), "%s %s\n\n", cs.ErrorLabel(), authErrorHelp(io, c.commandPath(), args))
return 1
}

Expand All @@ -128,6 +119,33 @@ func (c *Command) Run(args []string) int {
return 0
}

// authErrorHelp returns a help message for recovering from authentication errors.
func authErrorHelp(io iostreams.IOStreams, commandPath string, args []string) string {
// Build the original command
command := "$ " + commandPath
for _, a := range args {
// If there are spaces in the argument, quote it.
if strings.Contains(a, " ") {
command += fmt.Sprintf(" %s", strconv.Quote(a))
} else {
command += fmt.Sprintf(" %s", a)
}
}

// Quote the entire command so we can inject it into the template as a
// variable.
command = strconv.Quote(command)

// Render the help message to logout, login, and re-run the command.
return heredoc.New(io, heredoc.WithPreserveNewlines(), heredoc.WithWidth(0)).Mustf(`
Unauthorized request. Re-attempt by first logging out and back in, and then re-run the command.
{{ Bold "$ hcp auth logout" }}
{{ Bold "$ hcp auth login" }}
{{ with $cmd := %s }}{{ Bold $cmd }}{{ end }}
`, command)
}

// helpEntry is used to structure help output with titles.
type helpEntry struct {
Title string
Expand Down
24 changes: 24 additions & 0 deletions internal/pkg/cmd/command_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package cmd

import (
"testing"

"github.com/hashicorp/hcp/internal/pkg/iostreams"
"github.com/stretchr/testify/require"
)

func TestAuthErrorHelp(t *testing.T) {
t.Parallel()
r := require.New(t)
io := iostreams.Test()

commandPath := "hcp example"
args := []string{"simple", "'single-quote'", `escaped \"inner\"`}

// Get the help text
helpText := authErrorHelp(io, commandPath, args)
r.Contains(helpText, `$ hcp example simple 'single-quote' "escaped \\\"inner\\\""`)
}

0 comments on commit 4570a18

Please sign in to comment.