Skip to content

Commit

Permalink
tctl: users add/ls and tokens ls json output
Browse files Browse the repository at this point in the history
  • Loading branch information
Jérémy Clerc authored and klizhentas committed Apr 25, 2019
1 parent 9d2a881 commit b2fd50b
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 25 deletions.
6 changes: 6 additions & 0 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ const (
// JSON means JSON serialization format
JSON = "json"

// YAML means YAML serialization format
YAML = "yaml"

// Text means text serialization format
Text = "text"

// LinuxAdminGID is the ID of the standard adm group on linux
LinuxAdminGID = 4

Expand Down
2 changes: 1 addition & 1 deletion e
Submodule e updated from dfb02d to 5bee05
63 changes: 63 additions & 0 deletions lib/services/invite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
Copyright 2019 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package services

import (
"time"
)

// InviteTokenV3 is an invite token spec format V3
type InviteTokenV3 struct {
// Kind is a resource kind - always resource.
Kind string `json:"kind"`

// SubKind is a resource sub kind
SubKind string `json:"sub_kind,omitempty"`

// Version is a resource version.
Version string `json:"version"`

// Metadata is metadata about the resource.
Metadata Metadata `json:"metadata"`

// Spec is a spec of the invite token
Spec InviteTokenSpecV3 `json:"spec"`
}

// InviteTokenSpecV3 is a spec for invite token
type InviteTokenSpecV3 struct {
// URL is a helper invite token URL
URL string `json:"url"`
}

// NewInviteToken returns a new instance of the invite token
func NewInviteToken(token, signupURL string, expires time.Time) *InviteTokenV3 {
tok := InviteTokenV3{
Kind: KindInviteToken,
Version: V3,
Metadata: Metadata{
Name: token,
},
Spec: InviteTokenSpecV3{
URL: signupURL,
},
}
if !expires.IsZero() {
tok.Metadata.SetExpiry(expires)
}
return &tok
}
3 changes: 3 additions & 0 deletions lib/services/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ const (
// to proxy
KindRemoteCluster = "remote_cluster"

// KindInviteToken is a local user invite token
KindInviteToken = "invite_token"

// KindIdenity is local on disk identity resource
KindIdentity = "identity"

Expand Down
31 changes: 22 additions & 9 deletions tool/tctl/common/token_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package common

import (
"encoding/json"
"fmt"
"sort"
"strings"
Expand All @@ -39,6 +40,9 @@ import (
type TokenCommand struct {
config *service.Config

// format is the output format, e.g. text or json
format string

// tokenType is the type of token. For example, "trusted_cluster".
tokenType string

Expand Down Expand Up @@ -80,6 +84,7 @@ func (c *TokenCommand) Initialize(app *kingpin.Application, config *service.Conf

// "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)
}

// TryRun takes the CLI command as an argument (like "nodes ls") and executes it.
Expand Down Expand Up @@ -178,18 +183,26 @@ func (c *TokenCommand) List(client auth.ClientI) error {
// Sort by expire time.
sort.Slice(tokens, func(i, j int) bool { return tokens[i].Expiry().Unix() < tokens[j].Expiry().Unix() })

tokensView := func() string {
table := asciitable.MakeTable([]string{"Token", "Type", "Expiry Time (UTC)"})
for _, t := range tokens {
expiry := "never"
if t.Expiry().Unix() > 0 {
expiry = t.Expiry().Format(time.RFC822)
if c.format == teleport.Text {
tokensView := func() string {
table := asciitable.MakeTable([]string{"Token", "Type", "Expiry Time (UTC)"})
for _, t := range tokens {
expiry := "never"
if t.Expiry().Unix() > 0 {
expiry = t.Expiry().Format(time.RFC822)
}
table.AddRow([]string{t.GetName(), t.GetRoles().String(), expiry})
}
table.AddRow([]string{t.GetName(), t.GetRoles().String(), expiry})
return table.AsBuffer().String()
}
fmt.Printf(tokensView())
} else {
data, err := json.MarshalIndent(tokens, "", " ")
if err != nil {
return trace.Wrap(err, "failed to marshal tokens")
}
return table.AsBuffer().String()
fmt.Printf(string(data))
}
fmt.Printf(tokensView())
return nil
}

Expand Down
53 changes: 38 additions & 15 deletions tool/tctl/common/user_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package common

import (
"encoding/json"
"fmt"
"strings"
"time"
Expand All @@ -43,6 +44,9 @@ type UserCommand struct {
identities []string
ttl time.Duration

// format is the output format, e.g. text or json
format string

userAdd *kingpin.CmdClause
userUpdate *kingpin.CmdClause
userList *kingpin.CmdClause
Expand All @@ -63,6 +67,7 @@ func (u *UserCommand) Initialize(app *kingpin.Application, config *service.Confi
u.userAdd.Flag("ttl", fmt.Sprintf("Set expiration time for token, default is %v hour, maximum is %v hours",
int(defaults.SignupTokenTTL/time.Hour), int(defaults.MaxSignupTokenTTL/time.Hour))).
Default(fmt.Sprintf("%v", defaults.SignupTokenTTL)).DurationVar(&u.ttl)
u.userAdd.Flag("format", "Output format, 'text' or 'json'").Hidden().Default(teleport.Text).StringVar(&u.format)
u.userAdd.Alias(AddUserHelp)

u.userUpdate = users.Command("update", "Update properties for existing user").Hidden()
Expand All @@ -71,6 +76,7 @@ func (u *UserCommand) Initialize(app *kingpin.Application, config *service.Confi
Default("").StringVar(&u.roles)

u.userList = users.Command("ls", "List all user accounts")
u.userList.Flag("format", "Output format, 'text' or 'json'").Hidden().Default(teleport.Text).StringVar(&u.format)

u.userDelete = users.Command("rm", "Deletes user accounts").Alias("del")
u.userDelete.Arg("logins", "Comma-separated list of user logins to delete").
Expand Down Expand Up @@ -116,16 +122,25 @@ func (u *UserCommand) Add(client auth.ClientI) error {
}

// try to auto-suggest the activation link
u.PrintSignupURL(client, token, u.ttl)
return nil
return u.PrintSignupURL(client, token, u.ttl, u.format)
}

func (u *UserCommand) PrintSignupURL(client auth.ClientI, token string, ttl time.Duration) {
// PrintSignupURL prints signup URL
func (u *UserCommand) PrintSignupURL(client auth.ClientI, token string, ttl time.Duration, format string) error {
signupURL, proxyHost := web.CreateSignupLink(client, token)

fmt.Printf("Signup token has been created and is valid for %v hours. Share this URL with the user:\n%v\n\n",
int(ttl/time.Hour), signupURL)
fmt.Printf("NOTE: Make sure %v points at a Teleport proxy which users can access.\n", proxyHost)
if format == teleport.Text {
fmt.Printf("Signup token has been created and is valid for %v hours. Share this URL with the user:\n%v\n\n",
int(ttl/time.Hour), signupURL)
fmt.Printf("NOTE: Make sure %v points at a Teleport proxy which users can access.\n", proxyHost)
} else {
out, err := json.MarshalIndent(services.NewInviteToken(token, signupURL, time.Now().Add(ttl).UTC()), "", " ")
if err != nil {
return trace.Wrap(err, "failed to marshal signup infos")
}
fmt.Printf(string(out))
}
return nil
}

// Update updates existing user
Expand Down Expand Up @@ -154,16 +169,24 @@ func (u *UserCommand) List(client auth.ClientI) error {
if err != nil {
return trace.Wrap(err)
}
if len(users) == 0 {
fmt.Println("No users found")
return nil
}
t := asciitable.MakeTable([]string{"User", "Allowed logins"})
for _, u := range users {
logins, _ := u.GetTraits()[teleport.TraitLogins]
t.AddRow([]string{u.GetName(), strings.Join(logins, ",")})
if u.format == teleport.Text {
if len(users) == 0 {
fmt.Println("No users found")
return nil
}
t := asciitable.MakeTable([]string{"User", "Allowed logins"})
for _, u := range users {
logins, _ := u.GetTraits()[teleport.TraitLogins]
t.AddRow([]string{u.GetName(), strings.Join(logins, ",")})
}
fmt.Println(t.AsBuffer().String())
} else {
out, err := json.MarshalIndent(users, "", " ")
if err != nil {
return trace.Wrap(err, "failed to marshal users")
}
fmt.Printf(string(out))
}
fmt.Println(t.AsBuffer().String())
return nil
}

Expand Down

0 comments on commit b2fd50b

Please sign in to comment.