Add help/usage units for CLI exchange-credential subcommand

This commit is contained in:
Ryan Richard 2020-09-15 09:05:40 -07:00
parent 831df90c93
commit 4ced58b5b7
3 changed files with 166 additions and 44 deletions

View File

@ -23,19 +23,39 @@ import (
//nolint: gochecknoinits
func init() {
exchangeCredentialCmd := &cobra.Command{
Run: runExchangeCredential,
rootCmd.AddCommand(newExchangeCredentialCmd(os.Args, os.Stdout, os.Stderr).cmd)
}
type exchangeCredentialCommand struct {
// runFunc is called by the cobra.Command.Run hook. It is included here for
// testability.
runFunc func(stdout, stderr io.Writer)
// cmd is the cobra.Command for this CLI command. It is included here for
// testability.
cmd *cobra.Command
}
func newExchangeCredentialCmd(args []string, stdout, stderr io.Writer) *exchangeCredentialCommand {
c := &exchangeCredentialCommand{
runFunc: runExchangeCredential,
}
c.cmd = &cobra.Command{
Run: func(cmd *cobra.Command, _ []string) {
c.runFunc(stdout, stderr)
},
Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "exchange-credential",
Short: "Exchange a credential for a cluster-specific access credential",
Long: here.Doc(`
Exchange a credential which proves your identity for a time-limited,
cluster-specific access credential.
Designed to be conveniently used as an credential plugin for kubectl.
See the help message for 'pinniped get-kubeconfig' for more
information about setting up a kubeconfig file using Pinniped.
Requires all of the following environment variables, which are
typically set in the kubeconfig:
- PINNIPED_TOKEN: the token to send to Pinniped for exchange
@ -43,13 +63,17 @@ func init() {
Pinniped's HTTPS endpoint
- PINNIPED_K8S_API_ENDPOINT: the URL for the Pinniped credential
exchange API
For more information about credential plugins in general, see
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
`),
}
rootCmd.AddCommand(exchangeCredentialCmd)
c.cmd.SetArgs(args)
c.cmd.SetOut(stdout)
c.cmd.SetErr(stderr)
return c
}
type envGetter func(string) (string, bool)
@ -57,8 +81,8 @@ type tokenExchanger func(ctx context.Context, token, caBundle, apiEndpoint strin
const ErrMissingEnvVar = constable.Error("failed to get credential: environment variable not set")
func runExchangeCredential(_ *cobra.Command, _ []string) {
err := exchangeCredential(os.LookupEnv, client.ExchangeToken, os.Stdout, 30*time.Second)
func runExchangeCredential(stdout, _ io.Writer) {
err := exchangeCredential(os.LookupEnv, client.ExchangeToken, stdout, 30*time.Second)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error())
os.Exit(1)

View File

@ -9,6 +9,7 @@ import (
"bytes"
"context"
"fmt"
"io"
"testing"
"time"
@ -18,9 +19,105 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
"github.com/suzerain-io/pinniped/internal/here"
"github.com/suzerain-io/pinniped/internal/testutil"
)
var (
knownGoodUsageForExchangeCredential = here.Doc(`
Usage:
exchange-credential [flags]
Flags:
-h, --help help for exchange-credential
`)
knownGoodHelpForExchangeCredential = here.Doc(`
Exchange a credential which proves your identity for a time-limited,
cluster-specific access credential.
Designed to be conveniently used as an credential plugin for kubectl.
See the help message for 'pinniped get-kubeconfig' for more
information about setting up a kubeconfig file using Pinniped.
Requires all of the following environment variables, which are
typically set in the kubeconfig:
- PINNIPED_TOKEN: the token to send to Pinniped for exchange
- PINNIPED_CA_BUNDLE: the CA bundle to trust when calling
Pinniped's HTTPS endpoint
- PINNIPED_K8S_API_ENDPOINT: the URL for the Pinniped credential
exchange API
For more information about credential plugins in general, see
https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins
Usage:
exchange-credential [flags]
Flags:
-h, --help help for exchange-credential
`)
)
func TestNewCredentialExchangeCmd(t *testing.T) {
spec.Run(t, "newCredentialExchangeCmd", func(t *testing.T, when spec.G, it spec.S) {
var r *require.Assertions
var stdout, stderr *bytes.Buffer
it.Before(func() {
r = require.New(t)
stdout, stderr = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
})
it("calls runFunc and does not print usage or help when correct arguments and flags are used", func() {
c := newExchangeCredentialCmd([]string{}, stdout, stderr)
runFuncCalled := false
c.runFunc = func(out, err io.Writer) {
runFuncCalled = true
}
r.NoError(c.cmd.Execute())
r.True(runFuncCalled)
r.Empty(stdout.String())
r.Empty(stderr.String())
})
it("fails when args are passed", func() {
c := newExchangeCredentialCmd([]string{"some-arg"}, stdout, stderr)
runFuncCalled := false
c.runFunc = func(out, err io.Writer) {
runFuncCalled = true
}
errorMessage := `unknown command "some-arg" for "exchange-credential"`
r.EqualError(c.cmd.Execute(), errorMessage)
r.False(runFuncCalled)
output := "Error: " + errorMessage + "\n" + knownGoodUsageForExchangeCredential
r.Equal(output, stdout.String())
r.Empty(stderr.String())
})
it("prints a nice help message", func() {
c := newExchangeCredentialCmd([]string{"--help"}, stdout, stderr)
runFuncCalled := false
c.runFunc = func(out, err io.Writer) {
runFuncCalled = true
}
r.NoError(c.cmd.Execute())
r.False(runFuncCalled)
r.Equal(knownGoodHelpForExchangeCredential, stdout.String())
r.Empty(stderr.String())
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}
func TestExchangeCredential(t *testing.T) {
spec.Run(t, "cmd.exchangeCredential", func(t *testing.T, when spec.G, it spec.S) {
var r *require.Assertions

View File

@ -26,46 +26,47 @@ import (
"github.com/suzerain-io/pinniped/internal/here"
)
const (
knownGoodUsage = `
Usage:
get-kubeconfig [flags]
var (
knownGoodUsageForGetKubeConfig = here.Doc(`
Usage:
get-kubeconfig [flags]
Flags:
-h, --help help for get-kubeconfig
--kubeconfig string Path to the kubeconfig file
--kubeconfig-context string Kubeconfig context override
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
--token string Credential to include in the resulting kubeconfig output (Required)
Flags:
-h, --help help for get-kubeconfig
--kubeconfig string Path to the kubeconfig file
--kubeconfig-context string Kubeconfig context override
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
--token string Credential to include in the resulting kubeconfig output (Required)
`
`)
knownGoodHelp = `Print a kubeconfig for authenticating into a cluster via Pinniped.
knownGoodHelpForGetKubeConfig = here.Doc(`
Print a kubeconfig for authenticating into a cluster via Pinniped.
Requires admin-like access to the cluster using the current
kubeconfig context in order to access Pinniped's metadata.
The current kubeconfig is found similar to how kubectl finds it:
using the value of the --kubeconfig option, or if that is not
specified then from the value of the KUBECONFIG environment
variable, or if that is not specified then it defaults to
.kube/config in your home directory.
Requires admin-like access to the cluster using the current
kubeconfig context in order to access Pinniped's metadata.
The current kubeconfig is found similar to how kubectl finds it:
using the value of the --kubeconfig option, or if that is not
specified then from the value of the KUBECONFIG environment
variable, or if that is not specified then it defaults to
.kube/config in your home directory.
Prints a kubeconfig which is suitable to access the cluster using
Pinniped as the authentication mechanism. This kubeconfig output
can be saved to a file and used with future kubectl commands, e.g.:
pinniped get-kubeconfig --token $MY_TOKEN > $HOME/mycluster-kubeconfig
kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods
Prints a kubeconfig which is suitable to access the cluster using
Pinniped as the authentication mechanism. This kubeconfig output
can be saved to a file and used with future kubectl commands, e.g.:
pinniped get-kubeconfig --token $MY_TOKEN > $HOME/mycluster-kubeconfig
kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods
Usage:
get-kubeconfig [flags]
Usage:
get-kubeconfig [flags]
Flags:
-h, --help help for get-kubeconfig
--kubeconfig string Path to the kubeconfig file
--kubeconfig-context string Kubeconfig context override
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
--token string Credential to include in the resulting kubeconfig output (Required)
`
Flags:
-h, --help help for get-kubeconfig
--kubeconfig string Path to the kubeconfig file
--kubeconfig-context string Kubeconfig context override
--pinniped-namespace string Namespace in which Pinniped was installed (default "pinniped")
--token string Credential to include in the resulting kubeconfig output (Required)
`)
)
func TestNewGetKubeConfigCmd(t *testing.T) {
@ -126,7 +127,7 @@ func TestNewGetKubeConfigCmd(t *testing.T) {
r.EqualError(c.cmd.Execute(), errorMessage)
r.False(runFuncCalled)
output := "Error: " + errorMessage + knownGoodUsage
output := "Error: " + errorMessage + "\n" + knownGoodUsageForGetKubeConfig
r.Equal(output, stdout.String())
r.Empty(stderr.String())
})
@ -174,7 +175,7 @@ func TestNewGetKubeConfigCmd(t *testing.T) {
r.EqualError(c.cmd.Execute(), errorMessage)
r.False(runFuncCalled)
output := "Error: " + errorMessage + knownGoodUsage
output := "Error: " + errorMessage + "\n" + knownGoodUsageForGetKubeConfig
r.Equal(output, stdout.String())
r.Empty(stderr.String())
})
@ -195,7 +196,7 @@ func TestNewGetKubeConfigCmd(t *testing.T) {
r.NoError(c.cmd.Execute())
r.False(runFuncCalled)
r.Equal(knownGoodHelp, stdout.String())
r.Equal(knownGoodHelpForGetKubeConfig, stdout.String())
r.Empty(stderr.String())
})
}, spec.Parallel(), spec.Report(report.Terminal{}))