cmd/pinniped/cmd: add get-kubeconfig cli tests
Signed-off-by: Andrew Keesler <akeesler@vmware.com>
This commit is contained in:
parent
4379d2772c
commit
879d847ffb
@ -26,6 +26,7 @@ import (
|
|||||||
|
|
||||||
"github.com/suzerain-io/pinniped/generated/1.19/apis/crdpinniped/v1alpha1"
|
"github.com/suzerain-io/pinniped/generated/1.19/apis/crdpinniped/v1alpha1"
|
||||||
pinnipedclientset "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned"
|
pinnipedclientset "github.com/suzerain-io/pinniped/generated/1.19/client/clientset/versioned"
|
||||||
|
"github.com/suzerain-io/pinniped/internal/constable"
|
||||||
"github.com/suzerain-io/pinniped/internal/controller/issuerconfig"
|
"github.com/suzerain-io/pinniped/internal/controller/issuerconfig"
|
||||||
"github.com/suzerain-io/pinniped/internal/here"
|
"github.com/suzerain-io/pinniped/internal/here"
|
||||||
)
|
)
|
||||||
@ -39,8 +40,42 @@ const (
|
|||||||
|
|
||||||
//nolint: gochecknoinits
|
//nolint: gochecknoinits
|
||||||
func init() {
|
func init() {
|
||||||
getKubeConfigCmd := &cobra.Command{
|
rootCmd.AddCommand(newGetKubeConfigCmd(os.Args, os.Stdout, os.Stderr).cmd)
|
||||||
Run: runGetKubeConfig,
|
}
|
||||||
|
|
||||||
|
type getKubeConfigCommand struct {
|
||||||
|
// runFunc is called by the cobra.Command.Run hook. It is included here for
|
||||||
|
// testability.
|
||||||
|
runFunc func(
|
||||||
|
stdout, stderr io.Writer,
|
||||||
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
|
)
|
||||||
|
|
||||||
|
// cmd is the cobra.Command for this CLI command. It is included here for
|
||||||
|
// testability.
|
||||||
|
cmd *cobra.Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGetKubeConfigCmd(args []string, stdout, stderr io.Writer) *getKubeConfigCommand {
|
||||||
|
c := &getKubeConfigCommand{
|
||||||
|
runFunc: runGetKubeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.cmd = &cobra.Command{
|
||||||
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
|
token := cmd.Flag(getKubeConfigCmdTokenFlagName).Value.String()
|
||||||
|
kubeconfigPathOverride := cmd.Flag(getKubeConfigCmdKubeconfigFlagName).Value.String()
|
||||||
|
currentContextOverride := cmd.Flag(getKubeConfigCmdKubeconfigContextFlagName).Value.String()
|
||||||
|
pinnipedInstallationNamespace := cmd.Flag(getKubeConfigCmdPinnipedNamespaceFlagName).Value.String()
|
||||||
|
c.runFunc(
|
||||||
|
stdout,
|
||||||
|
stderr,
|
||||||
|
token,
|
||||||
|
kubeconfigPathOverride,
|
||||||
|
currentContextOverride,
|
||||||
|
pinnipedInstallationNamespace,
|
||||||
|
)
|
||||||
|
},
|
||||||
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
Args: cobra.NoArgs, // do not accept positional arguments for this command
|
||||||
Use: "get-kubeconfig",
|
Use: "get-kubeconfig",
|
||||||
Short: "Print a kubeconfig for authenticating into a cluster via Pinniped",
|
Short: "Print a kubeconfig for authenticating into a cluster via Pinniped",
|
||||||
@ -63,50 +98,52 @@ func init() {
|
|||||||
`),
|
`),
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(getKubeConfigCmd)
|
c.cmd.SetArgs(args)
|
||||||
|
c.cmd.SetOut(stdout)
|
||||||
|
c.cmd.SetErr(stderr)
|
||||||
|
|
||||||
getKubeConfigCmd.Flags().StringP(
|
c.cmd.Flags().StringP(
|
||||||
getKubeConfigCmdTokenFlagName,
|
getKubeConfigCmdTokenFlagName,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"Credential to include in the resulting kubeconfig output (Required)",
|
"Credential to include in the resulting kubeconfig output (Required)",
|
||||||
)
|
)
|
||||||
err := getKubeConfigCmd.MarkFlagRequired(getKubeConfigCmdTokenFlagName)
|
err := c.cmd.MarkFlagRequired(getKubeConfigCmdTokenFlagName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
getKubeConfigCmd.Flags().StringP(
|
c.cmd.Flags().StringP(
|
||||||
getKubeConfigCmdKubeconfigFlagName,
|
getKubeConfigCmdKubeconfigFlagName,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"Path to the kubeconfig file",
|
"Path to the kubeconfig file",
|
||||||
)
|
)
|
||||||
|
|
||||||
getKubeConfigCmd.Flags().StringP(
|
c.cmd.Flags().StringP(
|
||||||
getKubeConfigCmdKubeconfigContextFlagName,
|
getKubeConfigCmdKubeconfigContextFlagName,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
"Kubeconfig context override",
|
"Kubeconfig context override",
|
||||||
)
|
)
|
||||||
|
|
||||||
getKubeConfigCmd.Flags().StringP(
|
c.cmd.Flags().StringP(
|
||||||
getKubeConfigCmdPinnipedNamespaceFlagName,
|
getKubeConfigCmdPinnipedNamespaceFlagName,
|
||||||
"",
|
"",
|
||||||
"pinniped",
|
"pinniped",
|
||||||
"Namespace in which Pinniped was installed",
|
"Namespace in which Pinniped was installed",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGetKubeConfig(cmd *cobra.Command, _ []string) {
|
func runGetKubeConfig(
|
||||||
token := cmd.Flag(getKubeConfigCmdTokenFlagName).Value.String()
|
stdout, stderr io.Writer,
|
||||||
kubeconfigPathOverride := cmd.Flag(getKubeConfigCmdKubeconfigFlagName).Value.String()
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
currentContextOverride := cmd.Flag(getKubeConfigCmdKubeconfigContextFlagName).Value.String()
|
) {
|
||||||
pinnipedInstallationNamespace := cmd.Flag(getKubeConfigCmdPinnipedNamespaceFlagName).Value.String()
|
|
||||||
|
|
||||||
err := getKubeConfig(
|
err := getKubeConfig(
|
||||||
os.Stdout,
|
stdout,
|
||||||
os.Stderr,
|
stderr,
|
||||||
token,
|
token,
|
||||||
kubeconfigPathOverride,
|
kubeconfigPathOverride,
|
||||||
currentContextOverride,
|
currentContextOverride,
|
||||||
@ -152,13 +189,15 @@ func getKubeConfig(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if credentialIssuerConfig.Status.KubeConfigInfo == nil {
|
||||||
|
return constable.Error(`CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`)
|
||||||
|
}
|
||||||
|
|
||||||
v1Cluster, err := copyCurrentClusterFromExistingKubeConfig(err, currentKubeConfig, currentContextNameOverride)
|
v1Cluster, err := copyCurrentClusterFromExistingKubeConfig(err, currentKubeConfig, currentContextNameOverride)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO handle when credentialIssuerConfig has no Status or no KubeConfigInfo
|
|
||||||
|
|
||||||
err = issueWarningForNonMatchingServerOrCA(v1Cluster, credentialIssuerConfig, warningsWriter)
|
err = issueWarningForNonMatchingServerOrCA(v1Cluster, credentialIssuerConfig, warningsWriter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -25,7 +26,180 @@ import (
|
|||||||
"github.com/suzerain-io/pinniped/internal/here"
|
"github.com/suzerain-io/pinniped/internal/here"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO write a test for the help message and command line flags similar to server_test.go
|
const (
|
||||||
|
knownGoodUsage = `
|
||||||
|
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)
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
knownGoodHelp = `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.
|
||||||
|
|
||||||
|
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]
|
||||||
|
|
||||||
|
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) {
|
||||||
|
spec.Run(t, "newGetKubeConfigCmd", 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("passes all flags to runFunc", func() {
|
||||||
|
args := []string{
|
||||||
|
"--token", "some-token",
|
||||||
|
"--kubeconfig", "some-kubeconfig",
|
||||||
|
"--kubeconfig-context", "some-kubeconfig-context",
|
||||||
|
"--pinniped-namespace", "some-pinniped-namespace",
|
||||||
|
}
|
||||||
|
c := newGetKubeConfigCmd(args, stdout, stderr)
|
||||||
|
|
||||||
|
runFuncCalled := false
|
||||||
|
c.runFunc = func(
|
||||||
|
out, err io.Writer,
|
||||||
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
|
) {
|
||||||
|
runFuncCalled = true
|
||||||
|
r.Equal("some-token", token)
|
||||||
|
r.Equal("some-kubeconfig", kubeconfigPathOverride)
|
||||||
|
r.Equal("some-kubeconfig-context", currentContextOverride)
|
||||||
|
r.Equal("some-pinniped-namespace", pinnipedInstallationNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.NoError(c.cmd.Execute())
|
||||||
|
r.True(runFuncCalled)
|
||||||
|
r.Empty(stdout.String())
|
||||||
|
r.Empty(stderr.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("requires the 'token' flag", func() {
|
||||||
|
args := []string{
|
||||||
|
"--kubeconfig", "some-kubeconfig",
|
||||||
|
"--kubeconfig-context", "some-kubeconfig-context",
|
||||||
|
"--pinniped-namespace", "some-pinniped-namespace",
|
||||||
|
}
|
||||||
|
c := newGetKubeConfigCmd(args, stdout, stderr)
|
||||||
|
|
||||||
|
runFuncCalled := false
|
||||||
|
c.runFunc = func(
|
||||||
|
out, err io.Writer,
|
||||||
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
|
) {
|
||||||
|
runFuncCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage := `required flag(s) "token" not set`
|
||||||
|
r.EqualError(c.cmd.Execute(), errorMessage)
|
||||||
|
r.False(runFuncCalled)
|
||||||
|
|
||||||
|
output := "Error: " + errorMessage + knownGoodUsage
|
||||||
|
r.Equal(output, stdout.String())
|
||||||
|
r.Empty(stderr.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("defaults the flags correctly", func() {
|
||||||
|
args := []string{
|
||||||
|
"--token", "some-token",
|
||||||
|
}
|
||||||
|
c := newGetKubeConfigCmd(args, stdout, stderr)
|
||||||
|
|
||||||
|
runFuncCalled := false
|
||||||
|
c.runFunc = func(
|
||||||
|
out, err io.Writer,
|
||||||
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
|
) {
|
||||||
|
runFuncCalled = true
|
||||||
|
r.Equal("some-token", token)
|
||||||
|
r.Equal("", kubeconfigPathOverride)
|
||||||
|
r.Equal("", currentContextOverride)
|
||||||
|
r.Equal("pinniped", pinnipedInstallationNamespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.NoError(c.cmd.Execute())
|
||||||
|
r.True(runFuncCalled)
|
||||||
|
r.Empty(stdout.String())
|
||||||
|
r.Empty(stderr.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("fails when args are passed", func() {
|
||||||
|
args := []string{
|
||||||
|
"--token", "some-token",
|
||||||
|
"some-arg",
|
||||||
|
}
|
||||||
|
c := newGetKubeConfigCmd(args, stdout, stderr)
|
||||||
|
|
||||||
|
runFuncCalled := false
|
||||||
|
c.runFunc = func(
|
||||||
|
out, err io.Writer,
|
||||||
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
|
) {
|
||||||
|
runFuncCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessage := `unknown command "some-arg" for "get-kubeconfig"`
|
||||||
|
r.EqualError(c.cmd.Execute(), errorMessage)
|
||||||
|
r.False(runFuncCalled)
|
||||||
|
|
||||||
|
output := "Error: " + errorMessage + knownGoodUsage
|
||||||
|
r.Equal(output, stdout.String())
|
||||||
|
r.Empty(stderr.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
it("prints a nice help message", func() {
|
||||||
|
args := []string{
|
||||||
|
"--help",
|
||||||
|
}
|
||||||
|
c := newGetKubeConfigCmd(args, stdout, stderr)
|
||||||
|
|
||||||
|
runFuncCalled := false
|
||||||
|
c.runFunc = func(
|
||||||
|
out, err io.Writer,
|
||||||
|
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
|
||||||
|
) {
|
||||||
|
runFuncCalled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
r.NoError(c.cmd.Execute())
|
||||||
|
r.False(runFuncCalled)
|
||||||
|
r.Equal(knownGoodHelp, stdout.String())
|
||||||
|
r.Empty(stderr.String())
|
||||||
|
})
|
||||||
|
}, spec.Parallel(), spec.Report(report.Terminal{}))
|
||||||
|
}
|
||||||
|
|
||||||
func expectedKubeconfigYAML(clusterCAData, clusterServer, command, token, pinnipedEndpoint, pinnipedCABundle string) string {
|
func expectedKubeconfigYAML(clusterCAData, clusterServer, command, token, pinnipedEndpoint, pinnipedCABundle string) string {
|
||||||
return here.Docf(`
|
return here.Docf(`
|
||||||
@ -383,6 +557,45 @@ func TestGetKubeConfig(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
when("the CredentialIssuerConfig is found on the cluster with an empty KubeConfigInfo", func() {
|
||||||
|
it.Before(func() {
|
||||||
|
r.NoError(pinnipedClient.Tracker().Add(
|
||||||
|
&crdpinnipedv1alpha1.CredentialIssuerConfig{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "CredentialIssuerConfig",
|
||||||
|
APIVersion: crdpinnipedv1alpha1.SchemeGroupVersion.String(),
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "pinniped-config",
|
||||||
|
Namespace: "some-namespace",
|
||||||
|
},
|
||||||
|
Status: crdpinnipedv1alpha1.CredentialIssuerConfigStatus{},
|
||||||
|
},
|
||||||
|
))
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns an error", func() {
|
||||||
|
kubeClientCreatorFuncWasCalled := false
|
||||||
|
err := getKubeConfig(outputBuffer,
|
||||||
|
warningsBuffer,
|
||||||
|
"some-token",
|
||||||
|
"./testdata/kubeconfig.yaml",
|
||||||
|
"",
|
||||||
|
"some-namespace",
|
||||||
|
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) {
|
||||||
|
kubeClientCreatorFuncWasCalled = true
|
||||||
|
r.Equal("https://fake-server-url-value", restConfig.Host)
|
||||||
|
r.Equal("fake-certificate-authority-data-value", string(restConfig.CAData))
|
||||||
|
return pinnipedClient, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.True(kubeClientCreatorFuncWasCalled)
|
||||||
|
r.EqualError(err, `CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`)
|
||||||
|
r.Empty(warningsBuffer.String())
|
||||||
|
r.Empty(outputBuffer.String())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
when("the CredentialIssuerConfig does not exist on the cluster", func() {
|
when("the CredentialIssuerConfig does not exist on the cluster", func() {
|
||||||
it("returns an error", func() {
|
it("returns an error", func() {
|
||||||
kubeClientCreatorFuncWasCalled := false
|
kubeClientCreatorFuncWasCalled := false
|
||||||
|
Loading…
Reference in New Issue
Block a user