Update token-create command
This commit is contained in:
parent
c14629cb5d
commit
ad28d1d82a
|
@ -3,174 +3,250 @@ package command
|
|||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/helper/flag-kv"
|
||||
"github.com/hashicorp/vault/helper/flag-slice"
|
||||
"github.com/hashicorp/vault/meta"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/posener/complete"
|
||||
)
|
||||
|
||||
// Ensure we are implementing the right interfaces.
|
||||
var _ cli.Command = (*TokenCreateCommand)(nil)
|
||||
var _ cli.CommandAutocomplete = (*TokenCreateCommand)(nil)
|
||||
|
||||
// TokenCreateCommand is a Command that mounts a new mount.
|
||||
type TokenCreateCommand struct {
|
||||
meta.Meta
|
||||
}
|
||||
*BaseCommand
|
||||
|
||||
func (c *TokenCreateCommand) Run(args []string) int {
|
||||
var format string
|
||||
var id, displayName, lease, ttl, explicitMaxTTL, period, role string
|
||||
var orphan, noDefaultPolicy, renewable bool
|
||||
var metadata map[string]string
|
||||
var numUses int
|
||||
var policies []string
|
||||
flags := c.Meta.FlagSet("mount", meta.FlagSetDefault)
|
||||
flags.StringVar(&format, "format", "table", "")
|
||||
flags.StringVar(&displayName, "display-name", "", "")
|
||||
flags.StringVar(&id, "id", "", "")
|
||||
flags.StringVar(&lease, "lease", "", "")
|
||||
flags.StringVar(&ttl, "ttl", "", "")
|
||||
flags.StringVar(&explicitMaxTTL, "explicit-max-ttl", "", "")
|
||||
flags.StringVar(&period, "period", "", "")
|
||||
flags.StringVar(&role, "role", "", "")
|
||||
flags.BoolVar(&orphan, "orphan", false, "")
|
||||
flags.BoolVar(&renewable, "renewable", true, "")
|
||||
flags.BoolVar(&noDefaultPolicy, "no-default-policy", false, "")
|
||||
flags.IntVar(&numUses, "use-limit", 0, "")
|
||||
flags.Var((*kvFlag.Flag)(&metadata), "metadata", "")
|
||||
flags.Var((*sliceflag.StringFlag)(&policies), "policy", "")
|
||||
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return 1
|
||||
}
|
||||
flagID string
|
||||
flagDisplayName string
|
||||
flagTTL time.Duration
|
||||
flagExplicitMaxTTL time.Duration
|
||||
flagPeriod time.Duration
|
||||
flagRenewable bool
|
||||
flagOrphan bool
|
||||
flagNoDefaultPolicy bool
|
||||
flagUseLimit int
|
||||
flagRole string
|
||||
flagMetadata map[string]string
|
||||
flagPolicies []string
|
||||
|
||||
args = flags.Args()
|
||||
if len(args) != 0 {
|
||||
flags.Usage()
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"\ntoken-create expects no arguments"))
|
||||
return 1
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error initializing client: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
if ttl == "" {
|
||||
ttl = lease
|
||||
}
|
||||
|
||||
tcr := &api.TokenCreateRequest{
|
||||
ID: id,
|
||||
Policies: policies,
|
||||
Metadata: metadata,
|
||||
TTL: ttl,
|
||||
NoParent: orphan,
|
||||
NoDefaultPolicy: noDefaultPolicy,
|
||||
DisplayName: displayName,
|
||||
NumUses: numUses,
|
||||
Renewable: new(bool),
|
||||
ExplicitMaxTTL: explicitMaxTTL,
|
||||
Period: period,
|
||||
}
|
||||
*tcr.Renewable = renewable
|
||||
|
||||
var secret *api.Secret
|
||||
if role != "" {
|
||||
secret, err = client.Auth().Token().CreateWithRole(tcr, role)
|
||||
} else {
|
||||
secret, err = client.Auth().Token().Create(tcr)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"Error creating token: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
return OutputSecret(c.Ui, format, secret)
|
||||
// Deprecated flags
|
||||
flagLease time.Duration
|
||||
}
|
||||
|
||||
func (c *TokenCreateCommand) Synopsis() string {
|
||||
return "Create a new auth token"
|
||||
return "Creates a new token"
|
||||
}
|
||||
|
||||
func (c *TokenCreateCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: vault token-create [options]
|
||||
|
||||
Create a new auth token.
|
||||
Creates a new token that can be used for authentication. This token will be
|
||||
created as a child of the currently authenticated token. The generated token
|
||||
will inherit all policies and permissions of the currently authenticated
|
||||
token unless you explicitly define a subset list policies to assign to the
|
||||
token.
|
||||
|
||||
This command creates a new token that can be used for authentication.
|
||||
This token will be created as a child of your token. The created token
|
||||
will inherit your policies, or can be assigned a subset of your policies.
|
||||
|
||||
A lease can also be associated with the token. If a lease is not associated
|
||||
with the token, then it cannot be renewed. If a lease is associated with
|
||||
A ttl can also be associated with the token. If a ttl is not associated
|
||||
with the token, then it cannot be renewed. If a ttl is associated with
|
||||
the token, it will expire after that amount of time unless it is renewed.
|
||||
|
||||
Metadata associated with the token (specified with "-metadata") is
|
||||
written to the audit log when the token is used.
|
||||
Metadata associated with the token (specified with "-metadata") is written
|
||||
to the audit log when the token is used.
|
||||
|
||||
If a role is specified, the role may override parameters specified here.
|
||||
|
||||
General Options:
|
||||
` + meta.GeneralOptionsUsage() + `
|
||||
Token Options:
|
||||
` + c.Flags().Help()
|
||||
|
||||
-id="7699125c-d8...." The token value that clients will use to authenticate
|
||||
with Vault. If not provided this defaults to a 36
|
||||
character UUID. A root token is required to specify
|
||||
the ID of a token.
|
||||
|
||||
-display-name="name" A display name to associate with this token. This
|
||||
is a non-security sensitive value used to help
|
||||
identify created secrets, i.e. prefixes.
|
||||
|
||||
-ttl="1h" Initial TTL to associate with the token; renewals can
|
||||
extend this value.
|
||||
|
||||
-explicit-max-ttl="1h" An explicit maximum lifetime for the token. Unlike
|
||||
normal token TTLs, which can be renewed up until the
|
||||
maximum TTL set on the auth/token mount or the system
|
||||
configuration file, this lifetime is a hard limit set
|
||||
on the token itself and cannot be exceeded.
|
||||
|
||||
-period="1h" If specified, the token will be periodic; it will
|
||||
have no maximum TTL (unless an "explicit-max-ttl" is
|
||||
also set) but every renewal will use the given
|
||||
period. Requires a root/sudo token to use.
|
||||
|
||||
-renewable=true Whether or not the token is renewable to extend its
|
||||
TTL up to Vault's configured maximum TTL for tokens.
|
||||
This defaults to true; set to false to disable
|
||||
renewal of this token.
|
||||
|
||||
-metadata="key=value" Metadata to associate with the token. This shows
|
||||
up in the audit log. This can be specified multiple
|
||||
times.
|
||||
|
||||
-orphan If specified, the token will have no parent. This
|
||||
prevents the new token from being revoked with
|
||||
your token. Requires a root/sudo token to use.
|
||||
|
||||
-no-default-policy If specified, the token will not have the "default"
|
||||
policy included in its policy set.
|
||||
|
||||
-policy="name" Policy to associate with this token. This can be
|
||||
specified multiple times.
|
||||
|
||||
-use-limit=5 The number of times this token can be used until
|
||||
it is automatically revoked.
|
||||
|
||||
-format=table The format for output. By default it is a whitespace-
|
||||
delimited table. This can also be json or yaml.
|
||||
|
||||
-role=name If set, the token will be created against the named
|
||||
role. The role may override other parameters. This
|
||||
requires the client to have permissions on the
|
||||
appropriate endpoint (auth/token/create/<name>).
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *TokenCreateCommand) Flags() *FlagSets {
|
||||
set := c.flagSet(FlagSetHTTP | FlagSetOutputField | FlagSetOutputFormat)
|
||||
|
||||
f := set.NewFlagSet("Command Options")
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "id",
|
||||
Target: &c.flagID,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Value for the token. By default, this is an auto-generated 36 " +
|
||||
"character UUID. Specifying this value requires sudo permissions.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "display-name",
|
||||
Target: &c.flagDisplayName,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Name to associate with this token. This is a non-sensitive value " +
|
||||
"that can be used to help identify created secrets (e.g. prefixes).",
|
||||
})
|
||||
|
||||
f.DurationVar(&DurationVar{
|
||||
Name: "ttl",
|
||||
Target: &c.flagTTL,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Initial TTL to associate with the token. Token renewals may be " +
|
||||
"able to extend beyond this value, depending on the configured maximum" +
|
||||
"TTLs. This is specified as a numeric string with suffix like \"30s\" " +
|
||||
"or \"5m\".",
|
||||
})
|
||||
|
||||
f.DurationVar(&DurationVar{
|
||||
Name: "explicit-max-ttl",
|
||||
Target: &c.flagExplicitMaxTTL,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Explicit maximum lifetime for the token. Unlike normal TTLs, the " +
|
||||
"maximum TTL is a hard limit and cannot be exceeded. This is specified " +
|
||||
"as a numeric string with suffix like \"30s\" or \"5m\".",
|
||||
})
|
||||
|
||||
f.DurationVar(&DurationVar{
|
||||
Name: "period",
|
||||
Target: &c.flagPeriod,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "If specified, every renewal will use the given period. Periodic " +
|
||||
"tokens do not expire (unless -explicit-max-ttl is also provided). " +
|
||||
"Setting this value requires sudo permissions. This is specified as a " +
|
||||
"numeric string with suffix like \"30s\" or \"5m\".",
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "renewable",
|
||||
Target: &c.flagRenewable,
|
||||
Default: true,
|
||||
Usage: "Allow the token to be renewed up to it's maximum TTL.",
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "orphan",
|
||||
Target: &c.flagOrphan,
|
||||
Default: false,
|
||||
Usage: "Create the token with no parent. This prevents the token from " +
|
||||
"being revoked when the token which created it expires. Setting this " +
|
||||
"value requires sudo permissions.",
|
||||
})
|
||||
|
||||
f.BoolVar(&BoolVar{
|
||||
Name: "no-default-policy",
|
||||
Target: &c.flagNoDefaultPolicy,
|
||||
Default: false,
|
||||
Usage: "Detach the \"default\" policy from the policy set for this " +
|
||||
"token.",
|
||||
})
|
||||
|
||||
f.IntVar(&IntVar{
|
||||
Name: "use-limit",
|
||||
Target: &c.flagUseLimit,
|
||||
Default: 0,
|
||||
Usage: "Number of times this token can be used. After the last use, the " +
|
||||
"token is automatically revoked. By default, tokens can be used an " +
|
||||
"unlimited number of times until their expiration.",
|
||||
})
|
||||
|
||||
f.StringVar(&StringVar{
|
||||
Name: "role",
|
||||
Target: &c.flagRole,
|
||||
Default: "",
|
||||
Usage: "Name of the role to create the token against. Specifying -role " +
|
||||
"may override other arguments. The locally authenticated Vault token " +
|
||||
"must have permission for \"auth/token/create/<role>\".",
|
||||
})
|
||||
|
||||
f.StringMapVar(&StringMapVar{
|
||||
Name: "metadata",
|
||||
Target: &c.flagMetadata,
|
||||
Completion: complete.PredictAnything,
|
||||
Usage: "Arbitary key=value metadata to associate with the token. " +
|
||||
"This metadata will show in the audit log when the token is used. " +
|
||||
"This can be specified multiple times to add multiple pieces of " +
|
||||
"metadata.",
|
||||
})
|
||||
|
||||
f.StringSliceVar(&StringSliceVar{
|
||||
Name: "policy",
|
||||
Target: &c.flagPolicies,
|
||||
Completion: c.PredictVaultPolicies(),
|
||||
Usage: "Name of a policy to associate with this token. This can be " +
|
||||
"specified multiple times to attach multiple policies.",
|
||||
})
|
||||
|
||||
// Deprecated flags
|
||||
// TODO: remove in 0.9.0
|
||||
f.DurationVar(&DurationVar{
|
||||
Name: "lease", // prefer -ttl
|
||||
Target: &c.flagLease,
|
||||
Default: 0,
|
||||
Hidden: true,
|
||||
})
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (c *TokenCreateCommand) AutocompleteArgs() complete.Predictor {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TokenCreateCommand) AutocompleteFlags() complete.Flags {
|
||||
return c.Flags().Completions()
|
||||
}
|
||||
|
||||
func (c *TokenCreateCommand) Run(args []string) int {
|
||||
f := c.Flags()
|
||||
|
||||
if err := f.Parse(args); err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 1
|
||||
}
|
||||
|
||||
args = f.Args()
|
||||
if len(args) > 0 {
|
||||
c.UI.Error(fmt.Sprintf("Too many arguments (expected 0, got %d)", len(args)))
|
||||
return 1
|
||||
}
|
||||
|
||||
// TODO: remove in 0.9.0
|
||||
if c.flagLease != 0 {
|
||||
c.UI.Warn("The -lease flag is deprecated. Please use -ttl instead.")
|
||||
c.flagTTL = c.flagLease
|
||||
}
|
||||
|
||||
client, err := c.Client()
|
||||
if err != nil {
|
||||
c.UI.Error(err.Error())
|
||||
return 2
|
||||
}
|
||||
|
||||
tcr := &api.TokenCreateRequest{
|
||||
ID: c.flagID,
|
||||
Policies: c.flagPolicies,
|
||||
Metadata: c.flagMetadata,
|
||||
TTL: c.flagTTL.String(),
|
||||
NoParent: c.flagOrphan,
|
||||
NoDefaultPolicy: c.flagNoDefaultPolicy,
|
||||
DisplayName: c.flagDisplayName,
|
||||
NumUses: c.flagUseLimit,
|
||||
Renewable: &c.flagRenewable,
|
||||
ExplicitMaxTTL: c.flagExplicitMaxTTL.String(),
|
||||
Period: c.flagPeriod.String(),
|
||||
}
|
||||
|
||||
var secret *api.Secret
|
||||
if c.flagRole != "" {
|
||||
secret, err = client.Auth().Token().CreateWithRole(tcr, c.flagRole)
|
||||
} else {
|
||||
secret, err = client.Auth().Token().Create(tcr)
|
||||
}
|
||||
if err != nil {
|
||||
c.UI.Error(fmt.Sprintf("Error creating token: %s", err))
|
||||
return 2
|
||||
}
|
||||
|
||||
if c.flagField != "" {
|
||||
return PrintRawField(c.UI, secret, c.flagField)
|
||||
}
|
||||
|
||||
return OutputSecret(c.UI, c.flagFormat, secret)
|
||||
}
|
||||
|
|
|
@ -1,38 +1,243 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/meta"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestTokenCreate(t *testing.T) {
|
||||
core, _, token := vault.TestCoreUnsealed(t)
|
||||
ln, addr := http.TestServer(t, core)
|
||||
defer ln.Close()
|
||||
func testTokenCreateCommand(tb testing.TB) (*cli.MockUi, *TokenCreateCommand) {
|
||||
tb.Helper()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &TokenCreateCommand{
|
||||
Meta: meta.Meta{
|
||||
ClientToken: token,
|
||||
Ui: ui,
|
||||
ui := cli.NewMockUi()
|
||||
return ui, &TokenCreateCommand{
|
||||
BaseCommand: &BaseCommand{
|
||||
UI: ui,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenCreateCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
args []string
|
||||
out string
|
||||
code int
|
||||
}{
|
||||
{
|
||||
"too_many_args",
|
||||
[]string{"abcd1234"},
|
||||
"Too many arguments",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"default",
|
||||
nil,
|
||||
"token",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"metadata",
|
||||
[]string{"-metadata", "foo=bar", "-metadata", "zip=zap"},
|
||||
"token",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"policies",
|
||||
[]string{"-policy", "foo", "-policy", "bar"},
|
||||
"token",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"field",
|
||||
[]string{
|
||||
"-field", "token_renewable",
|
||||
},
|
||||
"false",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"field_not_found",
|
||||
[]string{
|
||||
"-field", "not-a-real-field",
|
||||
},
|
||||
"not present in secret",
|
||||
1,
|
||||
},
|
||||
{
|
||||
"format",
|
||||
[]string{
|
||||
"-format", "json",
|
||||
},
|
||||
"{",
|
||||
0,
|
||||
},
|
||||
{
|
||||
"format_bad",
|
||||
[]string{
|
||||
"-format", "nope-not-real",
|
||||
},
|
||||
"Invalid output format",
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-address", addr,
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
t.Run("validations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Ensure we get lease info
|
||||
output := ui.OutputWriter.String()
|
||||
if !strings.Contains(output, "token_duration") {
|
||||
t.Fatalf("bad: %#v", output)
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testTokenCreateCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run(tc.args)
|
||||
if code != tc.code {
|
||||
t.Errorf("expected %d to be %d", code, tc.code)
|
||||
}
|
||||
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, tc.out) {
|
||||
t.Errorf("expected %q to contain %q", combined, tc.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("default", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testTokenCreateCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-field", "token",
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
token := strings.TrimSpace(ui.OutputWriter.String())
|
||||
secret, err := client.Auth().Token().Lookup(token)
|
||||
if secret == nil || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("metadata", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testTokenCreateCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-metadata", "foo=bar",
|
||||
"-metadata", "zip=zap",
|
||||
"-field", "token",
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
token := strings.TrimSpace(ui.OutputWriter.String())
|
||||
secret, err := client.Auth().Token().Lookup(token)
|
||||
if secret == nil || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
meta, ok := secret.Data["meta"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("missing meta: %#v", secret)
|
||||
}
|
||||
if _, ok := meta["foo"]; !ok {
|
||||
t.Errorf("missing meta.foo: %#v", meta)
|
||||
}
|
||||
if _, ok := meta["zip"]; !ok {
|
||||
t.Errorf("missing meta.bar: %#v", meta)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("policies", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServer(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testTokenCreateCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{
|
||||
"-policy", "foo",
|
||||
"-policy", "bar",
|
||||
"-field", "token",
|
||||
})
|
||||
if exp := 0; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
token := strings.TrimSpace(ui.OutputWriter.String())
|
||||
secret, err := client.Auth().Token().Lookup(token)
|
||||
if secret == nil || err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
raw, ok := secret.Data["policies"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("missing policies: %#v", secret)
|
||||
}
|
||||
|
||||
policies := make([]string, len(raw))
|
||||
for i := range raw {
|
||||
policies[i] = raw[i].(string)
|
||||
}
|
||||
|
||||
expected := []string{"bar", "default", "foo"}
|
||||
if !reflect.DeepEqual(policies, expected) {
|
||||
t.Errorf("expected %q to be %q", policies, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("communication_failure", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, closer := testVaultServerBad(t)
|
||||
defer closer()
|
||||
|
||||
ui, cmd := testTokenCreateCommand(t)
|
||||
cmd.client = client
|
||||
|
||||
code := cmd.Run([]string{})
|
||||
if exp := 2; code != exp {
|
||||
t.Errorf("expected %d to be %d", code, exp)
|
||||
}
|
||||
|
||||
expected := "Error creating token: "
|
||||
combined := ui.OutputWriter.String() + ui.ErrorWriter.String()
|
||||
if !strings.Contains(combined, expected) {
|
||||
t.Errorf("expected %q to contain %q", combined, expected)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no_tabs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, cmd := testTokenCreateCommand(t)
|
||||
assertNoTabs(t, cmd)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue