Skip to content

Commit

Permalink
add --format flag to 'token add' and make the same flag visible for '…
Browse files Browse the repository at this point in the history
…token ls'
  • Loading branch information
capnspacehook committed May 11, 2022
1 parent b76add8 commit 7534d1c
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 23 deletions.
12 changes: 12 additions & 0 deletions docs/pages/setup/reference/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1009,6 +1009,7 @@ $ tctl tokens add --type=TYPE [<flags>]
| `--type` | none | `proxy`, `auth`, `trusted_cluster`, `node`, `db`, `kube`, `app`, `windowsdesktop` | Type of token to add |
| `--value` | none | **string** token value | Value of token to add |
| `--ttl` | 1h | relative duration like 5s, 2m, or 3h | Set expiration time for token |
| `--format` | none | `text`, `json`, `yaml` | Output format |

#### Global flags

Expand Down Expand Up @@ -1051,6 +1052,17 @@ List node and user invitation tokens:
$ tctl tokens ls [<flags>]
```

#### Flags

| Name | Default Value(s) | Allowed Value(s) | Description |
| - | - | - | - |
| `--format` | none | `text`, `json`, `yaml` | Output format |

#### Global flags

These flags are available for all commands `--debug, --config` . Run
`tctl help <subcommand>` or see the [Global Flags section](#tctl-global-flags).

#### Example

```code
Expand Down
45 changes: 45 additions & 0 deletions tool/tctl/common/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,56 @@ func runResourceCommand(t *testing.T, fc *config.FileConfig, args []string, opts
return &stdoutBuff, nil
}

func runTokensCommand(t *testing.T, fc *config.FileConfig, args []string, opts ...optionsFunc) (*bytes.Buffer, error) {
var options options
for _, v := range opts {
v(&options)
}

var stdoutBuff bytes.Buffer
command := &TokensCommand{
stdout: &stdoutBuff,
}
cfg := service.MakeDefaultConfig()

app := utils.InitCLIParser("tctl", GlobalHelpString)
command.Initialize(app, cfg)

args = append([]string{"tokens"}, args...)
selectedCmd, err := app.Parse(args)
require.NoError(t, err)

var ccf GlobalCLIFlags
ccf.ConfigString = mustGetBase64EncFileConfig(t, fc)
ccf.Insecure = options.Insecure

clientConfig, err := applyConfig(&ccf, cfg)
require.NoError(t, err)

if options.CertPool != nil {
clientConfig.TLS.RootCAs = options.CertPool
}

client, err := authclient.Connect(context.Background(), clientConfig)
require.NoError(t, err)

_, err = command.TryRun(selectedCmd, client)
if err != nil {
return nil, err
}
return &stdoutBuff, nil
}

func mustDecodeJSON(t *testing.T, r io.Reader, i interface{}) {
err := json.NewDecoder(r).Decode(i)
require.NoError(t, err)
}

func mustDecodeYAML(t *testing.T, r io.Reader, i interface{}) {
err := yaml.NewDecoder(r).Decode(i)
require.NoError(t, err)
}

func mustGetFreeLocalListenerAddr(t *testing.T) string {
l, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
Expand Down
98 changes: 76 additions & 22 deletions tool/tctl/common/token_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strings"
"time"

"github.com/ghodss/yaml"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/asciitable"
Expand All @@ -39,8 +41,8 @@ import (
"github.com/gravitational/trace"
)

// TokenCommand implements `tctl token` group of commands
type TokenCommand struct {
// TokensCommand implements `tctl tokens` group of commands
type TokensCommand struct {
config *service.Config

// format is the output format, e.g. text or json
Expand Down Expand Up @@ -81,14 +83,19 @@ type TokenCommand struct {

// tokenList is used to view all tokens that Teleport knows about.
tokenList *kingpin.CmdClause

// stdout allows to switch the standard output source. Used in tests.
stdout io.Writer
}

// Initialize allows TokenCommand to plug itself into the CLI parser
func (c *TokenCommand) Initialize(app *kingpin.Application, config *service.Config) {
func (c *TokensCommand) Initialize(app *kingpin.Application, config *service.Config) {
c.config = config

tokens := app.Command("tokens", "List or revoke invitation tokens")

formats := []string{teleport.Text, teleport.JSON, teleport.YAML}

// tctl tokens add ..."
c.tokenAdd = tokens.Command("add", "Create a invitation token")
c.tokenAdd.Flag("type", "Type(s) of token to add, e.g. --type=node,app,db").Required().StringVar(&c.tokenType)
Expand All @@ -103,18 +110,23 @@ func (c *TokenCommand) Initialize(app *kingpin.Application, config *service.Conf
c.tokenAdd.Flag("db-name", "Name of the database to add").StringVar(&c.dbName)
c.tokenAdd.Flag("db-protocol", fmt.Sprintf("Database protocol to use. Supported are: %v", defaults.DatabaseProtocols)).StringVar(&c.dbProtocol)
c.tokenAdd.Flag("db-uri", "Address the database is reachable at").StringVar(&c.dbURI)
c.tokenAdd.Flag("format", "Output format, 'text', 'json', or 'yaml'").EnumVar(&c.format, formats...)

// "tctl tokens rm ..."
c.tokenDel = tokens.Command("rm", "Delete/revoke an invitation token").Alias("del")
c.tokenDel.Arg("token", "Token to delete").StringVar(&c.value)

// "tctl tokens ls"
c.tokenList = tokens.Command("ls", "List node and user invitation tokens")
c.tokenList.Flag("format", "Output format, 'text' or 'json'").Hidden().Default(teleport.Text).StringVar(&c.format)
c.tokenList.Flag("format", "Output format, 'text', 'json' or 'yaml'").EnumVar(&c.format, formats...)

if c.stdout == nil {
c.stdout = os.Stdout
}
}

// TryRun takes the CLI command as an argument (like "nodes ls") and executes it.
func (c *TokenCommand) TryRun(cmd string, client auth.ClientI) (match bool, err error) {
func (c *TokensCommand) TryRun(cmd string, client auth.ClientI) (match bool, err error) {
switch cmd {
case c.tokenAdd.FullCommand():
err = c.Add(client)
Expand All @@ -129,7 +141,7 @@ func (c *TokenCommand) TryRun(cmd string, client auth.ClientI) (match bool, err
}

// Add is called to execute "tokens add ..." command.
func (c *TokenCommand) Add(client auth.ClientI) error {
func (c *TokensCommand) Add(client auth.ClientI) error {
// Parse string to see if it's a type of role that Teleport supports.
roles, err := types.ParseTeleportRoles(c.tokenType)
if err != nil {
Expand All @@ -155,6 +167,36 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
return trace.Wrap(err)
}

// Print token information formatted with JSON, YAML, or just print the raw token.
switch c.format {
case teleport.JSON, teleport.YAML:
expires := time.Now().Add(c.ttl)
tokenInfo := map[string]interface{}{
"token": token,
"roles": roles,
"expires": expires,
}

var (
data []byte
err error
)
if c.format == teleport.JSON {
data, err = json.MarshalIndent(tokenInfo, "", " ")
} else {
data, err = yaml.Marshal(tokenInfo)
}
if err != nil {
return trace.Wrap(err)
}
fmt.Fprint(c.stdout, string(data))

return nil
case teleport.Text:
fmt.Fprintln(c.stdout, token)
return nil
}

// Calculate the CA pins for this cluster. The CA pins are used by the
// client to verify the identity of the Auth Server.
localCAResponse, err := client.GetClusterCACert()
Expand Down Expand Up @@ -187,7 +229,7 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
}
appPublicAddr := fmt.Sprintf("%v.%v", c.appName, proxies[0].GetPublicAddr())

return appMessageTemplate.Execute(os.Stdout,
return appMessageTemplate.Execute(c.stdout,
map[string]interface{}{
"token": token,
"minutes": c.ttl.Minutes(),
Expand All @@ -205,7 +247,7 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
if len(proxies) == 0 {
return trace.NotFound("cluster has no proxies")
}
return dbMessageTemplate.Execute(os.Stdout,
return dbMessageTemplate.Execute(c.stdout,
map[string]interface{}{
"token": token,
"minutes": c.ttl.Minutes(),
Expand All @@ -216,7 +258,7 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
"db_uri": c.dbURI,
})
case roles.Include(types.RoleTrustedCluster):
fmt.Printf(trustedClusterMessage,
fmt.Fprintf(c.stdout, trustedClusterMessage,
token,
int(c.ttl.Minutes()))
default:
Expand All @@ -237,7 +279,8 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
authServer = proxies[0].GetPublicAddr()
}
}
return nodeMessageTemplate.Execute(os.Stdout, map[string]interface{}{

return nodeMessageTemplate.Execute(c.stdout, map[string]interface{}{
"token": token,
"roles": strings.ToLower(roles.String()),
"minutes": int(c.ttl.Minutes()),
Expand All @@ -250,34 +293,51 @@ func (c *TokenCommand) Add(client auth.ClientI) error {
}

// Del is called to execute "tokens del ..." command.
func (c *TokenCommand) Del(client auth.ClientI) error {
func (c *TokensCommand) Del(client auth.ClientI) error {
ctx := context.TODO()
if c.value == "" {
return trace.Errorf("Need an argument: token")
}
if err := client.DeleteToken(ctx, c.value); err != nil {
return trace.Wrap(err)
}
fmt.Printf("Token %s has been deleted\n", c.value)
fmt.Fprintf(c.stdout, "Token %s has been deleted\n", c.value)
return nil
}

// List is called to execute "tokens ls" command.
func (c *TokenCommand) List(client auth.ClientI) error {
func (c *TokensCommand) List(client auth.ClientI) error {
ctx := context.TODO()
tokens, err := client.GetTokens(ctx)
if err != nil {
return trace.Wrap(err)
}
if len(tokens) == 0 {
fmt.Println("No active tokens found.")
fmt.Fprintln(c.stdout, "No active tokens found.")
return nil
}

// Sort by expire time.
sort.Slice(tokens, func(i, j int) bool { return tokens[i].Expiry().Unix() < tokens[j].Expiry().Unix() })

if c.format == teleport.Text {
switch c.format {
case teleport.JSON:
data, err := json.MarshalIndent(tokens, "", " ")
if err != nil {
return trace.Wrap(err, "failed to marshal tokens")
}
fmt.Fprint(c.stdout, string(data))
case teleport.YAML:
data, err := yaml.Marshal(tokens)
if err != nil {
return trace.Wrap(err, "failed to marshal tokens")
}
fmt.Fprint(c.stdout, string(data))
case teleport.Text:
for _, token := range tokens {
fmt.Fprintln(c.stdout, token.GetName())
}
default:
tokensView := func() string {
table := asciitable.MakeTable([]string{"Token", "Type", "Labels", "Expiry Time (UTC)"})
now := time.Now()
Expand All @@ -292,13 +352,7 @@ func (c *TokenCommand) List(client auth.ClientI) error {
}
return table.AsBuffer().String()
}
fmt.Print(tokensView())
} else {
data, err := json.MarshalIndent(tokens, "", " ")
if err != nil {
return trace.Wrap(err, "failed to marshal tokens")
}
fmt.Print(string(data))
fmt.Fprint(c.stdout, tokensView())
}
return nil
}
Loading

0 comments on commit 7534d1c

Please sign in to comment.