Migrated 'users' and 'nodes' CLI commands to the new model

This commit is contained in:
Ev Kontsevoy 2017-07-25 22:13:02 -07:00
parent 8ad748b86d
commit 432cb34c91
5 changed files with 174 additions and 55 deletions

View file

@ -155,7 +155,7 @@ type serverCollection struct {
func (s *serverCollection) writeText(w io.Writer) error {
t := goterm.NewTable(0, 10, 5, ' ', 0)
printHeader(t, []string{"Hostname", "Name", "Address", "Labels"})
printHeader(t, []string{"Hostname", "UUID", "Address", "Labels"})
if len(s.servers) == 0 {
_, err := io.WriteString(w, t.String())
return trace.Wrap(err)

View file

@ -23,8 +23,10 @@ import (
"strings"
"time"
"github.com/gravitational/kingpin"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/trace"
)
@ -44,6 +46,42 @@ type NodeCommand struct {
ttl time.Duration
// namespace is node namespace
namespace string
// CLI subcommands (clauses)
nodeAdd *kingpin.CmdClause
nodeList *kingpin.CmdClause
}
// Initialize allows NodeCommand to plug itself into the CLI parser
func (c *NodeCommand) Initialize(app *kingpin.Application, config *service.Config) {
c.config = config
// add node command
nodes := app.Command("nodes", "Issue invites for other nodes to join the cluster")
c.nodeAdd = nodes.Command("add", "Generate a node invitation token")
c.nodeAdd.Flag("roles", "Comma-separated list of roles for the new node to assume [node]").Default("node").StringVar(&c.roles)
c.nodeAdd.Flag("ttl", "Time to live for a generated token").Default(defaults.ProvisioningTokenTTL.String()).DurationVar(&c.ttl)
c.nodeAdd.Flag("count", "add count tokens and output JSON with the list").Hidden().Default("1").IntVar(&c.count)
c.nodeAdd.Flag("format", "output format, 'text' or 'json'").Hidden().Default("text").StringVar(&c.format)
c.nodeAdd.Alias(AddNodeHelp)
c.nodeList = nodes.Command("ls", "List all active SSH nodes within the cluster")
c.nodeList.Flag("namespace", "Namespace of the nodes").Default(defaults.Namespace).StringVar(&c.namespace)
c.nodeList.Alias(ListNodesHelp)
}
// TryRun takes the CLI command as an argument (like "nodes ls") and executes it.
func (c *NodeCommand) TryRun(cmd string, client *auth.TunClient) (match bool, err error) {
switch cmd {
case c.nodeAdd.FullCommand():
err = c.Invite(client)
case c.nodeList.FullCommand():
err = c.ListActive(client)
default:
return false, nil
}
return true, trace.Wrap(err)
}
// Invite generates a token which can be used to add another SSH node
@ -83,7 +121,6 @@ func (u *NodeCommand) Invite(client *auth.TunClient) error {
}
fmt.Printf(" - This invitation token will expire in %d minutes\n", int(u.ttl.Minutes()))
fmt.Printf(" - %v must be reachable from the new node, see --advertise-ip server flag\n", authServers[0].GetAddr())
fmt.Printf(` - For tokens of type "trustedcluster", tctl needs to be used to create a TrustedCluster resource. See the Admin Guide for more details.`)
} else {
out, err := json.Marshal(tokens)
if err != nil {

View file

@ -31,16 +31,98 @@ import (
"github.com/Sirupsen/logrus"
"github.com/buger/goterm"
"github.com/gravitational/kingpin"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
)
type CLIConfig struct {
// GlobalCLIFlags keeps the CLI flags that apply to all tctl commands
type GlobalCLIFlags struct {
Debug bool
ConfigFile string
ConfigString string
}
// CLICommand interface must be implemented by every CLI command
//
// This allows OSS and Enterprise Teleport editions to plug their own
// implementations of different CLI commands into the common execution
// framework
//
type CLICommand interface {
// Initialize allows a caller-defined command to plug itself into CLI
// argument parsing
Initialize(*kingpin.Application, *service.Config)
// TryRun is executed after the CLI parsing is done. The command must
// determine if selectedCommand belongs to it and return match=true
TryRun(selectedCommand string, c *auth.TunClient) (match bool, err error)
}
func Run2(distribution string, commands []CLICommand) {
utils.InitLogger(utils.LoggingForCLI, logrus.WarnLevel)
// app is the command line parser
app := utils.InitCLIParser("tctl", GlobalHelpString)
// cfg (teleport auth server configuration) is going to be shared by all
// commands
cfg := service.MakeDefaultConfig()
// each command will add itself to the CLI parser:
for i := range commands {
commands[i].Initialize(app, cfg)
}
// these global flags apply to all commands
var ccf GlobalCLIFlags
app.Flag("debug", "Enable verbose logging to stderr").
Short('d').
BoolVar(&ccf.Debug)
app.Flag("config", fmt.Sprintf("Path to a configuration file [%v]", defaults.ConfigFilePath)).
Short('c').
ExistingFileVar(&ccf.ConfigFile)
app.Flag("config-string",
"Base64 encoded configuration string").Hidden().Envar(defaults.ConfigEnvar).StringVar(&ccf.ConfigString)
// "version" command is always available:
ver := app.Command("version", "Print the version.")
app.HelpFlag.Short('h')
// parse CLI commands+flags:
selectedCmd, err := app.Parse(os.Args[1:])
if err != nil {
utils.FatalError(err)
}
// "version" command?
if selectedCmd == ver.FullCommand() {
utils.PrintVersion(distribution)
return
}
// configure all commands with Teleport configuration (they share 'cfg')
applyConfig(&ccf, cfg)
// connect to the auth sever:
client, err := connectToAuthService(cfg)
if err != nil {
utils.FatalError(err)
}
// execute whatever is selected:
var match bool
for _, c := range commands {
match, err = c.TryRun(selectedCmd, client)
if err != nil {
utils.FatalError(err)
}
if match {
break
}
}
}
// Run() is the same as 'make'. It helps to share the code between different
// "distributions" like OSS or Enterprise
//
@ -51,8 +133,6 @@ func Run(distribution string) {
// generate default tctl configuration:
cfg := service.MakeDefaultConfig()
cmdUsers := UserCommand{config: cfg}
cmdNodes := NodeCommand{config: cfg}
cmdAuth := AuthCommand{config: cfg}
cmdTokens := TokenCommand{config: cfg}
cmdGet := GetCommand{config: cfg}
@ -60,7 +140,7 @@ func Run(distribution string) {
cmdDelete := DeleteCommand{config: cfg}
// define global flags:
var ccf CLIConfig
var ccf GlobalCLIFlags
app.Flag("debug", "Enable verbose logging to stderr").
Short('d').
BoolVar(&ccf.Debug)
@ -74,20 +154,6 @@ func Run(distribution string) {
ver := app.Command("version", "Print the version.")
app.HelpFlag.Short('h')
// user add command:
users := app.Command("users", "Manage users logins")
userAdd := users.Command("add", "Generate an invitation token and print the signup URL")
userAdd.Arg("login", "Teleport user login").Required().StringVar(&cmdUsers.login)
userAdd.Arg("local-logins", "Local UNIX users this account can log in as [login]").
Default("").StringVar(&cmdUsers.allowedLogins)
userAdd.Alias(AddUserHelp)
userUpdate := users.Command("update", "Update properties for existing user").Hidden()
userUpdate.Arg("login", "Teleport user login").Required().StringVar(&cmdUsers.login)
userUpdate.Flag("set-roles", "Roles to assign to this user").
Default("").StringVar(&cmdUsers.roles)
delete := app.Command("del", "Delete resources").Hidden()
delete.Arg("resource", "Resource to delete").SetValue(&cmdDelete.ref)
@ -102,26 +168,6 @@ func Run(distribution string) {
create := app.Command("create", "Create or update a resource").Hidden()
create.Flag("filename", "resource definition file").Short('f').StringVar(&cmdCreate.filename)
// list users command
userList := users.Command("ls", "List all user accounts")
// delete user command
userDelete := users.Command("del", "Deletes user accounts")
userDelete.Arg("logins", "Comma-separated list of user logins to delete").
Required().StringVar(&cmdUsers.login)
// add node command
nodes := app.Command("nodes", "Issue invites for other nodes to join the cluster")
nodeAdd := nodes.Command("add", "Generate an invitation token. Use it to add a new node to the Teleport cluster")
nodeAdd.Flag("roles", "Comma-separated list of roles for the new node to assume [node]").Default("node").StringVar(&cmdNodes.roles)
nodeAdd.Flag("ttl", "Time to live for a generated token").Default(defaults.ProvisioningTokenTTL.String()).DurationVar(&cmdNodes.ttl)
nodeAdd.Flag("count", "add count tokens and output JSON with the list").Hidden().Default("1").IntVar(&cmdNodes.count)
nodeAdd.Flag("format", "output format, 'text' or 'json'").Hidden().Default("text").StringVar(&cmdNodes.format)
nodeAdd.Alias(AddNodeHelp)
nodeList := nodes.Command("ls", "List all active SSH nodes within the cluster")
nodeList.Flag("namespace", "Namespace of the nodes").Default(defaults.Namespace).StringVar(&cmdNodes.namespace)
nodeList.Alias(ListNodesHelp)
// operations on invitation tokens
tokens := app.Command("tokens", "List or revoke invitation tokens")
tokenList := tokens.Command("ls", "List node and user invitation tokens")
@ -184,18 +230,7 @@ func Run(distribution string) {
err = cmdCreate.Create(client)
case delete.FullCommand():
err = cmdDelete.Delete(client)
case userAdd.FullCommand():
err = cmdUsers.Add(client)
case userList.FullCommand():
err = cmdUsers.List(client)
case userUpdate.FullCommand():
err = cmdUsers.Update(client)
case userDelete.FullCommand():
err = cmdUsers.Delete(client)
case nodeAdd.FullCommand():
err = cmdNodes.Invite(client)
case nodeList.FullCommand():
err = cmdNodes.ListActive(client)
case authExport.FullCommand():
err = cmdAuth.ExportAuthorities(client)
case tokenList.FullCommand():
@ -256,7 +291,7 @@ func connectToAuthService(cfg *service.Config) (client *auth.TunClient, err erro
// applyConfig takes configuration values from the config file and applies
// them to 'service.Config' object
func applyConfig(ccf *CLIConfig, cfg *service.Config) error {
func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) error {
// load /etc/teleport.yaml and apply it's values:
fileConf, err := config.ReadConfigFile(ccf.ConfigFile)
if err != nil {

View file

@ -21,7 +21,7 @@ const (
AddUserHelp = `Notes:
1. tctl will generate a signup token and give you a URL to share with a user.
He will have to configure the mandatory 2nd facto auth and select a password.
A user will have to complete account creation by visiting the URL.
2. A Teleport user account is not the same as a local UNIX users on SSH nodes.
You must assign a list of allowed local users for every Teleport login.
@ -30,7 +30,7 @@ Examples:
> tctl users add joe admin,nginx
This creates a Teleport identity 'joe' who can login as 'admin' or 'nginx'
This creates a Teleport account 'joe' who can login as 'admin' or 'nginx'
to any SSH node connected to this auth server.
> tctl users add joe

View file

@ -23,6 +23,7 @@ import (
"strconv"
"strings"
"github.com/gravitational/kingpin"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/service"
@ -32,12 +33,58 @@ import (
)
// UserCommand implements `tctl users` set of commands
// It implements CLICommand interface
type UserCommand struct {
config *service.Config
login string
allowedLogins string
roles string
identities []string
userAdd *kingpin.CmdClause
userUpdate *kingpin.CmdClause
userList *kingpin.CmdClause
userDelete *kingpin.CmdClause
}
// Initialize allows UserCommand to plug itself into the CLI parser
func (u *UserCommand) Initialize(app *kingpin.Application, config *service.Config) {
u.config = config
users := app.Command("users", "Manage user accounts")
u.userAdd = users.Command("add", "Generate a user invitation token")
u.userAdd.Arg("account", "Teleport user account name").Required().StringVar(&u.login)
u.userAdd.Arg("local-logins", "Local UNIX users this account can log in as [login]").
Default("").StringVar(&u.allowedLogins)
u.userAdd.Alias(AddUserHelp)
u.userUpdate = users.Command("update", "Update properties for existing user").Hidden()
u.userUpdate.Arg("login", "Teleport user login").Required().StringVar(&u.login)
u.userUpdate.Flag("set-roles", "Roles to assign to this user").
Default("").StringVar(&u.roles)
u.userList = users.Command("ls", "List all user accounts")
u.userDelete = users.Command("rm", "Deletes user accounts").Alias("del")
u.userDelete.Arg("logins", "Comma-separated list of user logins to delete").
Required().StringVar(&u.login)
}
// TryRun takes the CLI command as an argument (like "users add") and executes it.
func (u *UserCommand) TryRun(cmd string, client *auth.TunClient) (match bool, err error) {
switch cmd {
case u.userAdd.FullCommand():
err = u.Add(client)
case u.userUpdate.FullCommand():
err = u.Update(client)
case u.userList.FullCommand():
err = u.List(client)
case u.userDelete.FullCommand():
err = u.Delete(client)
default:
return false, nil
}
return true, trace.Wrap(err)
}
// Add creates a new sign-up token and prints a token URL to stdout.