Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tctl refactoring #1168

Merged
merged 4 commits into from
Jul 28, 2017
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
5 changes: 4 additions & 1 deletion lib/client/keyagent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package client
import (
"fmt"
"io/ioutil"
"math/rand"
"net"
"os"
"path/filepath"
Expand Down Expand Up @@ -286,7 +287,9 @@ func startDebugAgent() error {
errorC := make(chan error)

go func() {
socketpath := filepath.Join(os.TempDir(), fmt.Sprintf("teleport-%d.socket", os.Getpid()))
socketpath := filepath.Join(os.TempDir(),
fmt.Sprintf("teleport-%d-%d.socket", os.Getpid(), rand.Uint32()))

systemAgent := agent.NewKeyring()

listener, err := net.Listen("unix", socketpath)
Expand Down
48 changes: 47 additions & 1 deletion tool/tctl/common/auth_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"time"

"github.com/gravitational/kingpin"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/auth/native"
"github.com/gravitational/teleport/lib/client"
Expand All @@ -33,6 +34,51 @@ type AuthCommand struct {
outputFormat client.IdentityFileFormat
compatVersion string
compatibility string

authGenerate *kingpin.CmdClause
authExport *kingpin.CmdClause
authSign *kingpin.CmdClause
}

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

// operations with authorities
auth := app.Command("auth", "Operations with user and host certificate authorities (CAs)").Hidden()
a.authExport = auth.Command("export", "Export public cluster (CA) keys to stdout")
a.authExport.Flag("keys", "if set, will print private keys").BoolVar(&a.exportPrivateKeys)
a.authExport.Flag("fingerprint", "filter authority by fingerprint").StringVar(&a.exportAuthorityFingerprint)
a.authExport.Flag("compat", "export cerfiticates compatible with specific version of Teleport").StringVar(&a.compatVersion)
a.authExport.Flag("type", "certificate type: 'user' or 'host'").StringVar(&a.authType)

a.authGenerate = auth.Command("gen", "Generate a new SSH keypair").Hidden()
a.authGenerate.Flag("pub-key", "path to the public key").Required().StringVar(&a.genPubPath)
a.authGenerate.Flag("priv-key", "path to the private key").Required().StringVar(&a.genPrivPath)

a.authSign = auth.Command("sign", "Create an identity file(s) for a given user")
a.authSign.Flag("user", "Teleport user name").Required().StringVar(&a.genUser)
a.authSign.Flag("out", "identity output").Short('o').StringVar(&a.output)
a.authSign.Flag("format", "identity format: 'file' (default) or 'dir'").Default(string(client.DefaultIdentityFormat)).StringVar((*string)(&a.outputFormat))
a.authSign.Flag("ttl", "TTL (time to live) for the generated certificate").Default(fmt.Sprintf("%v", defaults.CertDuration)).DurationVar(&a.genTTL)
a.authSign.Flag("compat", "OpenSSH compatibility flag").StringVar(&a.compatibility)
}

// TryRun takes the CLI command as an argument (like "auth gen") and executes it
// or returns match=false if 'cmd' does not belong to it
func (a *AuthCommand) TryRun(cmd string, client *auth.TunClient) (match bool, err error) {
switch cmd {
case a.authGenerate.FullCommand():
err = a.GenerateKeys()
case a.authExport.FullCommand():
err = a.ExportAuthorities(client)
case a.authSign.FullCommand():
err = a.GenerateAndSignKeys(client)

default:
return false, nil
}
return true, trace.Wrap(err)
}

// ExportAuthorities outputs the list of authorities in OpenSSH compatible formats
Expand Down Expand Up @@ -213,7 +259,7 @@ func hostCAFormat(ca services.CertAuthority, keyBytes []byte, client *auth.TunCl
"type": []string{string(ca.GetType())},
}

roles, err := services.FetchRoles(ca.GetRoles(), client)
roles, err := services.FetchRoles(ca.GetRoles(), client, nil)
if err != nil {
return "", trace.Wrap(err)
}
Expand Down
2 changes: 1 addition & 1 deletion tool/tctl/common/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
59 changes: 48 additions & 11 deletions tool/tctl/common/node_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -44,22 +46,58 @@ 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
// to a cluster
func (u *NodeCommand) Invite(client *auth.TunClient) error {
if u.count < 1 {
return trace.BadParameter("count should be > 0, got %v", u.count)
func (c *NodeCommand) Invite(client *auth.TunClient) error {
if c.count < 1 {
return trace.BadParameter("count should be > 0, got %v", c.count)
}
// parse --roles flag
roles, err := teleport.ParseRoles(u.roles)
roles, err := teleport.ParseRoles(c.roles)
if err != nil {
return trace.Wrap(err)
}
var tokens []string
for i := 0; i < u.count; i++ {
token, err := client.GenerateToken(roles, u.ttl)
for i := 0; i < c.count; i++ {
token, err := client.GenerateToken(roles, c.ttl)
if err != nil {
return trace.Wrap(err)
}
Expand All @@ -75,15 +113,14 @@ func (u *NodeCommand) Invite(client *auth.TunClient) error {
}

// output format swtich:
if u.format == "text" {
if c.format == "text" {
for _, token := range tokens {
fmt.Printf(
"The invite token: %v\nRun this on the new node to join the cluster:\n> teleport start --roles=%s --token=%v --auth-server=%v\n\nPlease note:\n",
token, strings.ToLower(roles.String()), token, authServers[0].GetAddr())
}
fmt.Printf(" - This invitation token will expire in %d minutes\n", int(u.ttl.Minutes()))
fmt.Printf(" - This invitation token will expire in %d minutes\n", int(c.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 {
Expand All @@ -96,8 +133,8 @@ func (u *NodeCommand) Invite(client *auth.TunClient) error {

// ListActive retreives the list of nodes who recently sent heartbeats to
// to a cluster and prints it to stdout
func (u *NodeCommand) ListActive(client *auth.TunClient) error {
nodes, err := client.GetNodes(u.namespace)
func (c *NodeCommand) ListActive(client *auth.TunClient) error {
nodes, err := client.GetNodes(c.namespace)
if err != nil {
return trace.Wrap(err)
}
Expand Down
62 changes: 48 additions & 14 deletions tool/tctl/common/resource_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,72 @@ import (
"io/ioutil"
"os"

"github.com/gravitational/kingpin"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/service"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/trace"
kyaml "k8s.io/client-go/1.4/pkg/util/yaml"
)

// GetCommand implements `tctl get` command
type GetCommand struct {
// ResourceCommand implements `tctl get/create/list` commands for manipulating
// Teleport resources
type ResourceCommand struct {
config *service.Config
ref services.Ref
format string
namespace string
withSecrets bool
}

// GetCommand implements `tctl create` command
type CreateCommand struct {
config *service.Config
// filename is the name of the resource, used for 'create'
filename string

// CLI subcommands:
delete *kingpin.CmdClause
get *kingpin.CmdClause
create *kingpin.CmdClause
}

// GetCommand implements `tctl delete` command
type DeleteCommand struct {
config *service.Config
ref services.Ref
// Initialize allows ResourceCommand to plug itself into the CLI parser
func (g *ResourceCommand) Initialize(app *kingpin.Application, config *service.Config) {
g.config = config

g.create = app.Command("create", "Create or update a resource")
g.create.Flag("filename", "resource definition file").Short('f').StringVar(&g.filename)

g.delete = app.Command("rm", "Delete a resource").Alias("del")
g.delete.Arg("resource", "Resource to delete").SetValue(&g.ref)

g.get = app.Command("get", "Print a resource")
g.get.Arg("resource", "Resource type and name").SetValue(&g.ref)
g.get.Flag("format", "Format output type, one of 'yaml', 'json' or 'text'").Default(formatText).StringVar(&g.format)
g.get.Flag("namespace", "Namespace of the resources").Hidden().Default(defaults.Namespace).StringVar(&g.namespace)
g.get.Flag("with-secrets", "Include secrets in resources like certificate authorities or OIDC connectors").Default("false").BoolVar(&g.withSecrets)
}

// TryRun takes the CLI command as an argument (like "auth gen") and executes it
// or returns match=false if 'cmd' does not belong to it
func (g *ResourceCommand) TryRun(cmd string, client *auth.TunClient) (match bool, err error) {
switch cmd {

case g.get.FullCommand():
err = g.Get(client)
case g.create.FullCommand():
err = g.Create(client)
case g.delete.FullCommand():
err = g.Delete(client)

default:
return false, nil
}
return true, trace.Wrap(err)
}

// Get prints one or many resources of a certain type
func (g *GetCommand) Get(client *auth.TunClient) error {
func (g *ResourceCommand) Get(client *auth.TunClient) error {
collection, err := g.getCollection(client)
if err != nil {
return trace.Wrap(err)
Expand All @@ -72,7 +106,7 @@ func (g *GetCommand) Get(client *auth.TunClient) error {
}

// Create updates or insterts one or many resources
func (u *CreateCommand) Create(client *auth.TunClient) error {
func (u *ResourceCommand) Create(client *auth.TunClient) error {
var reader io.ReadCloser
var err error
if u.filename != "" {
Expand Down Expand Up @@ -205,7 +239,7 @@ func (u *CreateCommand) Create(client *auth.TunClient) error {
}

// Delete deletes resource by name
func (d *DeleteCommand) Delete(client *auth.TunClient) error {
func (d *ResourceCommand) Delete(client *auth.TunClient) error {
if d.ref.Kind == "" {
return trace.BadParameter("provide full resource name to delete e.g. roles/example")
}
Expand Down Expand Up @@ -258,7 +292,7 @@ func (d *DeleteCommand) Delete(client *auth.TunClient) error {
return nil
}

func (g *GetCommand) getCollection(client auth.ClientI) (collection, error) {
func (g *ResourceCommand) getCollection(client auth.ClientI) (collection, error) {
if g.ref.Kind == "" {
return nil, trace.BadParameter("specify resource to list, e.g. 'tctl get roles'")
}
Expand Down
Loading