Skip to content

Commit

Permalink
tctl changes (polish for 2.3)
Browse files Browse the repository at this point in the history
This commit refs #1137

- tctl get user/joe now works (as reported in #1247)
- tctl create/rm roles changes
- added synonyms for various resources
- made YAML the default output for tctl get
- added better help + examples for tctl get
- edited error messages
- minor refactoring
- added the system of "command plugins" which allows enterprise version
  of tctl to introduce different behavior to OSS commands
  • Loading branch information
kontsevoy committed Sep 4, 2017
1 parent d070696 commit a7db6d7
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 86 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ goinstall:
github.com/gravitational/teleport/tool/teleport \
github.com/gravitational/teleport/tool/tctl


#
# make install will installs system-wide teleport
#
Expand Down
2 changes: 1 addition & 1 deletion e
Submodule e updated from c46146 to 7b682b
10 changes: 5 additions & 5 deletions lib/services/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,27 +361,27 @@ func ParseShortcut(in string) (string, error) {
return "", trace.BadParameter("missing resource name")
}
switch strings.ToLower(in) {
case "roles":
case "role", "roles":
return KindRole, nil
case "namespaces", "ns":
return KindNamespace, nil
case "auth_servers", "auth":
return KindAuthServer, nil
case "proxies":
return KindProxy, nil
case "nodes":
case "nodes", "node":
return KindNode, nil
case "oidc":
return KindOIDCConnector, nil
case "saml":
return KindSAMLConnector, nil
case "users":
case "user", "users":
return KindUser, nil
case "cert_authorities", "cas":
return KindCertAuthority, nil
case "reverse_tunnels", "rts":
return KindReverseTunnel, nil
case "trusted_cluster", "tc":
case "trusted_cluster", "tc", "cluster", "clusters":
return KindTrustedCluster, nil
case "cluster_authentication_preferences", "cap":
return KindClusterAuthPreference, nil
Expand Down Expand Up @@ -441,5 +441,5 @@ func (r *Ref) Set(v string) error {
}

func (r *Ref) String() string {
return fmt.Sprintf("%v/%v", r.Kind, r.Name)
return fmt.Sprintf("%s/%s", r.Kind, r.Name)
}
2 changes: 1 addition & 1 deletion tool/tctl/common/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
"github.com/ghodss/yaml"
)

type collection interface {
type ResourceCollection interface {
writeText(w io.Writer) error
writeJSON(w io.Writer) error
writeYAML(w io.Writer) error
Expand Down
147 changes: 68 additions & 79 deletions tool/tctl/common/resource_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

"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"
Expand All @@ -33,6 +32,13 @@ import (
kyaml "k8s.io/apimachinery/pkg/util/yaml"
)

// ResourceCommandImpl allows to customize the implementation of certain
// subcommands of the resource command
type ResourceCommandImpl struct {
Delete func(services.Ref, *auth.TunClient) error
Create func(*services.UnknownResource, *auth.TunClient) error
}

// ResourceCommand implements `tctl get/create/list` commands for manipulating
// Teleport resources
type ResourceCommand struct {
Expand All @@ -49,8 +55,21 @@ type ResourceCommand struct {
delete *kingpin.CmdClause
get *kingpin.CmdClause
create *kingpin.CmdClause

// customized implementation
Impl *ResourceCommandImpl
}

const getHelp = `Examples:
$ tctl get clusters : prints the list of all trusted clusters
$ tctl get cluster/east : prints the trusted cluster 'east'
Same as above, but using JSON output:
$ tctl get clusters --format=json
`

// Initialize allows ResourceCommand to plug itself into the CLI parser
func (g *ResourceCommand) Initialize(app *kingpin.Application, config *service.Config) {
g.config = config
Expand All @@ -61,25 +80,28 @@ func (g *ResourceCommand) Initialize(app *kingpin.Application, config *service.C
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 = app.Command("get", "Print a YAML declaration of various Teleport resources")
g.get.Arg("resource", "Resource spec: 'type/[name]'").SetValue(&g.ref)
g.get.Flag("format", "Output format: 'yaml' or 'json'").Default(formatYAML).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)

g.get.Alias(getHelp)
}

// 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 {

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

default:
return false, nil
}
Expand All @@ -92,6 +114,7 @@ func (g *ResourceCommand) Get(client *auth.TunClient) error {
if err != nil {
return trace.Wrap(err)
}

switch g.format {
case formatYAML:
return collection.writeYAML(os.Stdout)
Expand Down Expand Up @@ -154,24 +177,6 @@ func (u *ResourceCommand) Create(client *auth.TunClient) error {
return trace.Wrap(err)
}
fmt.Printf("created OIDC connector: %v\n", conn.GetName())
case services.KindReverseTunnel:
tun, err := services.GetReverseTunnelMarshaler().UnmarshalReverseTunnel(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
if err := client.UpsertReverseTunnel(tun); err != nil {
return trace.Wrap(err)
}
fmt.Printf("created reverse tunnel: %v\n", tun.GetName())
case services.KindCertAuthority:
ca, err := services.GetCertAuthorityMarshaler().UnmarshalCertAuthority(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
if err := client.UpsertCertAuthority(ca); err != nil {
return trace.Wrap(err)
}
fmt.Printf("created cert authority: %v \n", ca.GetName())
case services.KindUser:
user, err := services.GetUserMarshaler().UnmarshalUser(raw.Raw)
if err != nil {
Expand All @@ -181,28 +186,6 @@ func (u *ResourceCommand) Create(client *auth.TunClient) error {
return trace.Wrap(err)
}
fmt.Printf("created user: %v\n", user.GetName())
case services.KindRole:
role, err := services.GetRoleMarshaler().UnmarshalRole(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
err = role.CheckAndSetDefaults()
if err != nil {
return trace.Wrap(err)
}
if err := client.UpsertRole(role, backend.Forever); err != nil {
return trace.Wrap(err)
}
fmt.Printf("created role: %v\n", role.GetName())
case services.KindNamespace:
ns, err := services.UnmarshalNamespace(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
if err := client.UpsertNamespace(*ns); err != nil {
return trace.Wrap(err)
}
fmt.Printf("created namespace: %v\n", ns.Metadata.Name)
case services.KindTrustedCluster:
tc, err := services.GetTrustedClusterMarshaler().Unmarshal(raw.Raw)
if err != nil {
Expand All @@ -215,33 +198,38 @@ func (u *ResourceCommand) Create(client *auth.TunClient) error {
case "":
return trace.BadParameter("missing resource kind")
default:
return trace.BadParameter("%q is not supported", raw.Kind)
// customized creation:
if u.Impl != nil && u.Impl.Create != nil {
err := u.Impl.Create(&raw, client)
if err != nil {
return trace.Wrap(err)
}
} else {
return trace.BadParameter("creating resources of type %q is not supported", raw.Kind)
}
}
}
}

// Delete deletes resource by name
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")
}
if d.ref.Name == "" {
return trace.BadParameter("provide full resource name to delete e.g. roles/example")
func (d *ResourceCommand) Delete(client *auth.TunClient) (err error) {
if d.ref.Kind == "" || d.ref.Name == "" {
return trace.BadParameter("provide a full resource name to delete, for example:\n$ tctl rm cluster/east\n")
}

switch d.ref.Kind {
case services.KindUser:
if err := client.DeleteUser(d.ref.Name); err != nil {
if err = client.DeleteUser(d.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("user %v has been deleted\n", d.ref.Name)
case services.KindSAMLConnector:
if err := client.DeleteSAMLConnector(d.ref.Name); err != nil {
if err = client.DeleteSAMLConnector(d.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("SAML Connector %v has been deleted\n", d.ref.Name)
case services.KindOIDCConnector:
if err := client.DeleteOIDCConnector(d.ref.Name); err != nil {
if err = client.DeleteOIDCConnector(d.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("OIDC Connector %v has been deleted\n", d.ref.Name)
Expand All @@ -250,35 +238,43 @@ func (d *ResourceCommand) Delete(client *auth.TunClient) error {
return trace.Wrap(err)
}
fmt.Printf("reverse tunnel %v has been deleted\n", d.ref.Name)
case services.KindRole:
if err := client.DeleteRole(d.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("role %v has been deleted\n", d.ref.Name)
case services.KindNamespace:
if err := client.DeleteNamespace(d.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("namespace %v has been deleted\n", d.ref.Name)
case services.KindTrustedCluster:
if err := client.DeleteTrustedCluster(d.ref.Name); err != nil {
if err = client.DeleteTrustedCluster(d.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("trusted cluster %q has been deleted\n", d.ref.Name)
case "":
return trace.BadParameter("missing resource kind")
default:
return trace.BadParameter("%q is not supported", d.ref.Kind)
if d.Impl != nil && d.Impl.Delete != nil {
return trace.Wrap(d.Impl.Delete(d.ref, client))
}
return trace.BadParameter("deleting resoruces of type %q is not supported", d.ref.Kind)
}

return nil
}

func (g *ResourceCommand) getCollection(client auth.ClientI) (collection, error) {
func (g *ResourceCommand) getCollection(client auth.ClientI) (c ResourceCollection, err error) {
if g.ref.Kind == "" {
return nil, trace.BadParameter("specify resource to list, e.g. 'tctl get roles'")
}
switch g.ref.Kind {
// load user(s)
case services.KindUser:
var users services.Users
// just one?
if !g.ref.IsEmtpy() {
user, err := client.GetUser(g.ref.Name)
if err != nil {
return nil, trace.Wrap(err)
}
users = services.Users{user}
// all of them?
} else {
users, err = client.GetUsers()
if err != nil {
return nil, trace.Wrap(err)
}
}
return &userCollection{users: users}, nil
case services.KindSAMLConnector:
connectors, err := client.GetSAMLConnectors(g.withSecrets)
if err != nil {
Expand Down Expand Up @@ -308,12 +304,6 @@ func (g *ResourceCommand) getCollection(client auth.ClientI) (collection, error)
}
userAuthorities = append(userAuthorities, hostAuthorities...)
return &authorityCollection{cas: userAuthorities}, nil
case services.KindUser:
users, err := client.GetUsers()
if err != nil {
return nil, trace.Wrap(err)
}
return &userCollection{users: users}, nil
case services.KindNode:
nodes, err := client.GetNodes(g.namespace)
if err != nil {
Expand Down Expand Up @@ -372,7 +362,6 @@ func (g *ResourceCommand) getCollection(client auth.ClientI) (collection, error)
}
return &trustedClusterCollection{trustedClusters: []services.TrustedCluster{trustedCluster}}, nil
}

return nil, trace.BadParameter("'%v' is not supported", g.ref.Kind)
}

Expand Down

0 comments on commit a7db6d7

Please sign in to comment.