Refactor get_kubeconfig.go.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2020-09-21 15:42:54 -05:00
parent 541336b997
commit 381fd51e13
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D
2 changed files with 261 additions and 708 deletions

View File

@ -27,51 +27,40 @@ import (
"go.pinniped.dev/internal/here" "go.pinniped.dev/internal/here"
) )
const (
getKubeConfigCmdTokenFlagName = "token"
getKubeConfigCmdKubeconfigFlagName = "kubeconfig"
getKubeConfigCmdKubeconfigContextFlagName = "kubeconfig-context"
getKubeConfigCmdPinnipedNamespaceFlagName = "pinniped-namespace"
)
//nolint: gochecknoinits //nolint: gochecknoinits
func init() { func init() {
rootCmd.AddCommand(newGetKubeConfigCmd(os.Args, os.Stdout, os.Stderr).cmd) rootCmd.AddCommand(newGetKubeConfigCommand().Command())
}
type getKubeConfigFlags struct {
token string
kubeconfig string
contextOverride string
namespace string
} }
type getKubeConfigCommand struct { type getKubeConfigCommand struct {
// runFunc is called by the cobra.Command.Run hook. It is included here for flags getKubeConfigFlags
// testability. // Test mocking points
runFunc func( getPathToSelf func() (string, error)
stdout, stderr io.Writer, kubeClientCreator func(restConfig *rest.Config) (pinnipedclientset.Interface, error)
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 { func newGetKubeConfigCommand() *getKubeConfigCommand {
c := &getKubeConfigCommand{ return &getKubeConfigCommand{
runFunc: runGetKubeConfig, flags: getKubeConfigFlags{
} namespace: "pinniped",
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,
)
}, },
getPathToSelf: os.Executable,
kubeClientCreator: func(restConfig *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedclientset.NewForConfig(restConfig)
},
}
}
func (c *getKubeConfigCommand) Command() *cobra.Command {
cmd := &cobra.Command{
RunE: c.run,
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",
@ -93,94 +82,40 @@ func newGetKubeConfigCmd(args []string, stdout, stderr io.Writer) *getKubeConfig
kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods kubectl --kubeconfig $HOME/mycluster-kubeconfig get pods
`), `),
} }
cmd.Flags().StringVar(&c.flags.token, "token", "", "Credential to include in the resulting kubeconfig output (Required)")
c.cmd.SetArgs(args) err := cmd.MarkFlagRequired("token")
c.cmd.SetOut(stdout)
c.cmd.SetErr(stderr)
c.cmd.Flags().StringP(
getKubeConfigCmdTokenFlagName,
"",
"",
"Credential to include in the resulting kubeconfig output (Required)",
)
err := c.cmd.MarkFlagRequired(getKubeConfigCmdTokenFlagName)
if err != nil { if err != nil {
panic(err) panic(err)
} }
cmd.Flags().StringVar(&c.flags.kubeconfig, "kubeconfig", c.flags.kubeconfig, "Path to the kubeconfig file")
c.cmd.Flags().StringP( cmd.Flags().StringVar(&c.flags.contextOverride, "kubeconfig-context", c.flags.contextOverride, "Kubeconfig context override")
getKubeConfigCmdKubeconfigFlagName, cmd.Flags().StringVar(&c.flags.namespace, "pinniped-namespace", c.flags.namespace, "Namespace in which Pinniped was installed")
"", return cmd
"",
"Path to the kubeconfig file",
)
c.cmd.Flags().StringP(
getKubeConfigCmdKubeconfigContextFlagName,
"",
"",
"Kubeconfig context override",
)
c.cmd.Flags().StringP(
getKubeConfigCmdPinnipedNamespaceFlagName,
"",
"pinniped",
"Namespace in which Pinniped was installed",
)
return c
} }
func runGetKubeConfig( func (c *getKubeConfigCommand) run(cmd *cobra.Command, args []string) error {
stdout, stderr io.Writer, fullPathToSelf, err := c.getPathToSelf()
token, kubeconfigPathOverride, currentContextOverride, pinnipedInstallationNamespace string,
) {
err := getKubeConfig(
stdout,
stderr,
token,
kubeconfigPathOverride,
currentContextOverride,
pinnipedInstallationNamespace,
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedclientset.NewForConfig(restConfig)
},
)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
os.Exit(1)
}
}
func getKubeConfig(
outputWriter io.Writer,
warningsWriter io.Writer,
token string,
kubeconfigPathOverride string,
currentContextNameOverride string,
pinnipedInstallationNamespace string,
kubeClientCreator func(restConfig *rest.Config) (pinnipedclientset.Interface, error),
) error {
if token == "" {
return constable.Error("--" + getKubeConfigCmdTokenFlagName + " flag value cannot be empty")
}
fullPathToSelf, err := os.Executable()
if err != nil { if err != nil {
return fmt.Errorf("could not find path to self: %w", err) return fmt.Errorf("could not find path to self: %w", err)
} }
clientConfig := newClientConfig(kubeconfigPathOverride, currentContextNameOverride) clientConfig := newClientConfig(c.flags.kubeconfig, c.flags.contextOverride)
currentKubeConfig, err := clientConfig.RawConfig() currentKubeConfig, err := clientConfig.RawConfig()
if err != nil { if err != nil {
return err return err
} }
credentialIssuerConfig, err := fetchPinnipedCredentialIssuerConfig(clientConfig, kubeClientCreator, pinnipedInstallationNamespace) restConfig, err := clientConfig.ClientConfig()
if err != nil {
return err
}
clientset, err := c.kubeClientCreator(restConfig)
if err != nil {
return err
}
credentialIssuerConfig, err := fetchPinnipedCredentialIssuerConfig(clientset, c.flags.namespace)
if err != nil { if err != nil {
return err return err
} }
@ -189,19 +124,19 @@ func getKubeConfig(
return constable.Error(`CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`) return constable.Error(`CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`)
} }
v1Cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, currentContextNameOverride) v1Cluster, err := copyCurrentClusterFromExistingKubeConfig(currentKubeConfig, c.flags.contextOverride)
if err != nil { if err != nil {
return err return err
} }
err = issueWarningForNonMatchingServerOrCA(v1Cluster, credentialIssuerConfig, warningsWriter) err = issueWarningForNonMatchingServerOrCA(v1Cluster, credentialIssuerConfig, cmd.ErrOrStderr())
if err != nil { if err != nil {
return err return err
} }
config := newPinnipedKubeconfig(v1Cluster, fullPathToSelf, token, pinnipedInstallationNamespace) config := newPinnipedKubeconfig(v1Cluster, fullPathToSelf, c.flags.token, c.flags.namespace)
err = writeConfigAsYAML(outputWriter, config) err = writeConfigAsYAML(cmd.OutOrStdout(), config)
if err != nil { if err != nil {
return err return err
} }
@ -224,16 +159,7 @@ func issueWarningForNonMatchingServerOrCA(v1Cluster v1.Cluster, credentialIssuer
return nil return nil
} }
func fetchPinnipedCredentialIssuerConfig(clientConfig clientcmd.ClientConfig, kubeClientCreator func(restConfig *rest.Config) (pinnipedclientset.Interface, error), pinnipedInstallationNamespace string) (*configv1alpha1.CredentialIssuerConfig, error) { func fetchPinnipedCredentialIssuerConfig(clientset pinnipedclientset.Interface, pinnipedInstallationNamespace string) (*configv1alpha1.CredentialIssuerConfig, error) {
restConfig, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
clientset, err := kubeClientCreator(restConfig)
if err != nil {
return nil, err
}
ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20) ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*20)
defer cancelFunc() defer cancelFunc()

View File

@ -7,16 +7,15 @@ import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io" "strings"
"os"
"testing" "testing"
"github.com/sclevine/spec" "github.com/spf13/cobra"
"github.com/sclevine/spec/report"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
coretesting "k8s.io/client-go/testing"
configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1" configv1alpha1 "go.pinniped.dev/generated/1.19/apis/config/v1alpha1"
pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned" pinnipedclientset "go.pinniped.dev/generated/1.19/client/clientset/versioned"
@ -68,149 +67,60 @@ var (
) )
func TestNewGetKubeConfigCmd(t *testing.T) { func TestNewGetKubeConfigCmd(t *testing.T) {
spec.Run(t, "newGetKubeConfigCmd", func(t *testing.T, when spec.G, it spec.S) { t.Parallel()
var r *require.Assertions tests := []struct {
var stdout, stderr *bytes.Buffer name string
args []string
wantError bool
wantStdout string
wantStderr string
}{
{
name: "help flag passed",
args: []string{"--help"},
wantStdout: knownGoodHelpForGetKubeConfig,
},
{
name: "missing required flag",
args: []string{},
wantError: true,
wantStdout: `Error: required flag(s) "token" not set` + "\n" + knownGoodUsageForGetKubeConfig,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
cmd := newGetKubeConfigCommand().Command()
require.NotNil(t, cmd)
it.Before(func() { var stdout, stderr bytes.Buffer
r = require.New(t) cmd.SetOut(&stdout)
cmd.SetErr(&stderr)
stdout, stderr = bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) cmd.SetArgs(tt.args)
err := cmd.Execute()
if tt.wantError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, tt.wantStdout, stdout.String(), "unexpected stdout")
require.Equal(t, tt.wantStderr, stderr.String(), "unexpected stderr")
}) })
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()) type expectedKubeconfigYAML struct {
r.True(runFuncCalled) clusterCAData string
r.Empty(stdout.String()) clusterServer string
r.Empty(stderr.String()) command string
}) token string
pinnipedEndpoint string
it("requires the 'token' flag", func() { pinnipedCABundle string
args := []string{ namespace 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` func (e expectedKubeconfigYAML) String() string {
r.EqualError(c.cmd.Execute(), errorMessage)
r.False(runFuncCalled)
output := "Error: " + errorMessage + "\n" + knownGoodUsageForGetKubeConfig
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 + "\n" + knownGoodUsageForGetKubeConfig
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(knownGoodHelpForGetKubeConfig, stdout.String())
r.Empty(stderr.String())
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
}
func expectedKubeconfigYAML(
clusterCAData,
clusterServer,
command,
// nolint: unparam // Pass in the token even if it is always the same in practice
token,
pinnipedEndpoint,
pinnipedCABundle,
// nolint: unparam // Pass in the namespace even if it is always the same in practice
namespace string,
) string {
return here.Docf(` return here.Docf(`
apiVersion: v1 apiVersion: v1
clusters: clusters:
@ -246,16 +156,10 @@ func expectedKubeconfigYAML(
installHint: |- installHint: |-
The Pinniped CLI is required to authenticate to the current cluster. The Pinniped CLI is required to authenticate to the current cluster.
For more information, please visit https://pinniped.dev For more information, please visit https://pinniped.dev
`, clusterCAData, clusterServer, command, pinnipedEndpoint, pinnipedCABundle, namespace, token) `, e.clusterCAData, e.clusterServer, e.command, e.pinnipedEndpoint, e.pinnipedCABundle, e.namespace, e.token)
} }
func newCredentialIssuerConfig( func newCredentialIssuerConfig(name, namespace, server, certificateAuthorityData string) *configv1alpha1.CredentialIssuerConfig {
name,
//nolint: unparam // Pass in the namespace even if it is always the same in practice
namespace,
server,
certificateAuthorityData string,
) *configv1alpha1.CredentialIssuerConfig {
return &configv1alpha1.CredentialIssuerConfig{ return &configv1alpha1.CredentialIssuerConfig{
TypeMeta: metav1.TypeMeta{ TypeMeta: metav1.TypeMeta{
Kind: "CredentialIssuerConfig", Kind: "CredentialIssuerConfig",
@ -274,439 +178,162 @@ func newCredentialIssuerConfig(
} }
} }
func TestGetKubeConfig(t *testing.T) { func TestRun(t *testing.T) {
spec.Run(t, "cmd.getKubeConfig", func(t *testing.T, when spec.G, it spec.S) { t.Parallel()
var r *require.Assertions tests := []struct {
var outputBuffer *bytes.Buffer name string
var warningsBuffer *bytes.Buffer mocks func(*getKubeConfigCommand)
var fullPathToSelf string wantError string
var pinnipedClient *pinnipedfake.Clientset wantStdout string
const installationNamespace = "some-namespace" wantStderr string
}{
it.Before(func() { {
r = require.New(t) name: "failure to get path to self",
mocks: func(cmd *getKubeConfigCommand) {
outputBuffer = new(bytes.Buffer) cmd.getPathToSelf = func() (string, error) {
warningsBuffer = new(bytes.Buffer) return "", fmt.Errorf("some error getting path to self")
}
var err error },
fullPathToSelf, err = os.Executable() wantError: "could not find path to self: some error getting path to self",
r.NoError(err) },
{
pinnipedClient = pinnipedfake.NewSimpleClientset() name: "kubeconfig does not exist",
}) mocks: func(cmd *getKubeConfigCommand) {
cmd.flags.kubeconfig = "./testdata/does-not-exist.yaml"
when("the CredentialIssuerConfig is found on the cluster with a configuration that matches the existing kubeconfig", func() { },
it.Before(func() { wantError: "stat ./testdata/does-not-exist.yaml: no such file or directory",
r.NoError(pinnipedClient.Tracker().Add( },
newCredentialIssuerConfig( {
"some-cic-name", name: "fail to get client",
installationNamespace, mocks: func(cmd *getKubeConfigCommand) {
"https://fake-server-url-value", cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
"fake-certificate-authority-data-value", return nil, fmt.Errorf("some error configuring clientset")
), }
)) },
}) wantError: "some error configuring clientset",
},
it("writes the kubeconfig to the given writer", func() { {
kubeClientCreatorFuncWasCalled := false name: "fail to get CredentialIssuerConfigs",
err := getKubeConfig(outputBuffer, mocks: func(cmd *getKubeConfigCommand) {
warningsBuffer, clientset := pinnipedfake.NewSimpleClientset()
"some-token", clientset.PrependReactor("*", "*", func(_ coretesting.Action) (bool, runtime.Object, error) {
"./testdata/kubeconfig.yaml", return true, nil, fmt.Errorf("some error getting CredentialIssuerConfigs")
"", })
installationNamespace, cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return clientset, nil
kubeClientCreatorFuncWasCalled = true }
r.Equal("https://fake-server-url-value", restConfig.Host) },
r.Equal("fake-certificate-authority-data-value", string(restConfig.CAData)) wantError: "some error getting CredentialIssuerConfigs",
return pinnipedClient, nil },
}, {
) name: "zero CredentialIssuerConfigs found",
r.NoError(err) mocks: func(cmd *getKubeConfigCommand) {
r.True(kubeClientCreatorFuncWasCalled) cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
return pinnipedfake.NewSimpleClientset(
r.Empty(warningsBuffer.String()) newCredentialIssuerConfig("pinniped-config-1", "not-the-test-namespace", "", ""),
r.Equal(expectedKubeconfigYAML( ), nil
base64.StdEncoding.EncodeToString([]byte("fake-certificate-authority-data-value")), }
"https://fake-server-url-value", },
fullPathToSelf, wantError: `No CredentialIssuerConfig was found in namespace "test-namespace". Is Pinniped installed on this cluster in namespace "test-namespace"?`,
"some-token", },
"https://fake-server-url-value", {
"fake-certificate-authority-data-value", name: "multiple CredentialIssuerConfigs found",
installationNamespace, mocks: func(cmd *getKubeConfigCommand) {
), outputBuffer.String()) cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
}) return pinnipedfake.NewSimpleClientset(
newCredentialIssuerConfig("pinniped-config-1", "test-namespace", "", ""),
when("the currentContextOverride is used to specify a context other than the default context", func() { newCredentialIssuerConfig("pinniped-config-2", "test-namespace", "", ""),
it.Before(func() { ), nil
// update the Server and CertificateAuthorityData to make them match the other kubeconfig context }
r.NoError(pinnipedClient.Tracker().Update( },
schema.GroupVersionResource{ wantError: `More than one CredentialIssuerConfig was found in namespace "test-namespace"`,
Group: configv1alpha1.GroupName, },
Version: configv1alpha1.SchemeGroupVersion.Version, {
Resource: "credentialissuerconfigs", name: "CredentialIssuerConfig missing KubeConfigInfo",
}, mocks: func(cmd *getKubeConfigCommand) {
newCredentialIssuerConfig( cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "", "")
"some-cic-name", cic.Status.KubeConfigInfo = nil
installationNamespace, cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
"https://some-other-fake-server-url-value", return pinnipedfake.NewSimpleClientset(cic), nil
"some-other-fake-certificate-authority-data-value", }
), },
installationNamespace, wantError: `CredentialIssuerConfig "pinniped-config" was missing KubeConfigInfo`,
)) },
}) {
name: "KubeConfigInfo has invalid base64",
when("that context exists", func() { mocks: func(cmd *getKubeConfigCommand) {
it("writes the kubeconfig to the given writer using the specified context", func() { cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "https://example.com", "")
kubeClientCreatorFuncWasCalled := false cic.Status.KubeConfigInfo.CertificateAuthorityData = "invalid-base64-test-ca"
err := getKubeConfig(outputBuffer, cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
warningsBuffer, return pinnipedfake.NewSimpleClientset(cic), nil
"some-token", }
"./testdata/kubeconfig.yaml", },
"some-other-context", wantError: `illegal base64 data at input byte 7`,
installationNamespace, },
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { {
kubeClientCreatorFuncWasCalled = true name: "success using remote CA data",
r.Equal("https://some-other-fake-server-url-value", restConfig.Host) mocks: func(cmd *getKubeConfigCommand) {
r.Equal("some-other-fake-certificate-authority-data-value", string(restConfig.CAData)) cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "https://fake-server-url-value", "fake-certificate-authority-data-value")
return pinnipedClient, nil cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
}, return pinnipedfake.NewSimpleClientset(cic), nil
) }
r.NoError(err) },
r.True(kubeClientCreatorFuncWasCalled) wantStdout: expectedKubeconfigYAML{
clusterCAData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
r.Empty(warningsBuffer.String()) clusterServer: "https://fake-server-url-value",
r.Equal(expectedKubeconfigYAML( command: "/path/to/pinniped",
base64.StdEncoding.EncodeToString([]byte("some-other-fake-certificate-authority-data-value")), token: "test-token",
"https://some-other-fake-server-url-value", pinnipedEndpoint: "https://fake-server-url-value",
fullPathToSelf, pinnipedCABundle: "fake-certificate-authority-data-value",
"some-token", namespace: "test-namespace",
"https://some-other-fake-server-url-value", }.String(),
"some-other-fake-certificate-authority-data-value", },
installationNamespace, {
), outputBuffer.String()) name: "success using local CA data",
}) mocks: func(cmd *getKubeConfigCommand) {
}) cic := newCredentialIssuerConfig("pinniped-config", "test-namespace", "https://example.com", "test-ca")
cmd.kubeClientCreator = func(_ *rest.Config) (pinnipedclientset.Interface, error) {
when("that context does not exist the in the current kubeconfig", func() { return pinnipedfake.NewSimpleClientset(cic), nil
it("returns an error", func() { }
err := getKubeConfig(outputBuffer, },
warningsBuffer, wantStderr: `WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuerConfig on the cluster. Using local kubeconfig values.`,
"some-token", wantStdout: expectedKubeconfigYAML{
"./testdata/kubeconfig.yaml", clusterCAData: "ZmFrZS1jZXJ0aWZpY2F0ZS1hdXRob3JpdHktZGF0YS12YWx1ZQ==",
"this-context-name-does-not-exist-in-kubeconfig.yaml", clusterServer: "https://fake-server-url-value",
installationNamespace, command: "/path/to/pinniped",
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return pinnipedClient, nil }, token: "test-token",
) pinnipedEndpoint: "https://fake-server-url-value",
r.EqualError(err, `context "this-context-name-does-not-exist-in-kubeconfig.yaml" does not exist`) pinnipedCABundle: "fake-certificate-authority-data-value",
r.Empty(warningsBuffer.String()) namespace: "test-namespace",
r.Empty(outputBuffer.String()) }.String(),
}) },
}) }
}) for _, tt := range tests {
tt := tt
when("the token passed in is empty", func() { t.Run(tt.name, func(t *testing.T) {
it("returns an error", func() { t.Parallel()
err := getKubeConfig(outputBuffer,
warningsBuffer, // Start with a default getKubeConfigCommand, set some defaults, then apply any mocks.
"", c := newGetKubeConfigCommand()
"./testdata/kubeconfig.yaml", c.flags.token = "test-token"
"", c.flags.namespace = "test-namespace"
installationNamespace, c.getPathToSelf = func() (string, error) { return "/path/to/pinniped", nil }
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return pinnipedClient, nil }, c.flags.kubeconfig = "./testdata/kubeconfig.yaml"
) tt.mocks(c)
r.EqualError(err, "--token flag value cannot be empty")
r.Empty(warningsBuffer.String()) cmd := &cobra.Command{}
r.Empty(outputBuffer.String()) var stdout, stderr bytes.Buffer
}) cmd.SetOut(&stdout)
}) cmd.SetErr(&stderr)
cmd.SetArgs([]string{})
when("the kubeconfig path passed refers to a file that does not exist", func() { err := c.run(cmd, []string{})
it("returns an error", func() { if tt.wantError != "" {
err := getKubeConfig(outputBuffer, require.EqualError(t, err, tt.wantError)
warningsBuffer, } else {
"some-token", require.NoError(t, err)
"./testdata/this-file-does-not-exist.yaml", }
"", require.Equal(t, strings.TrimSpace(tt.wantStdout), strings.TrimSpace(stdout.String()), "unexpected stdout")
installationNamespace, require.Equal(t, strings.TrimSpace(tt.wantStderr), strings.TrimSpace(stderr.String()), "unexpected stderr")
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) { return pinnipedClient, nil }, })
) }
r.EqualError(err, "stat ./testdata/this-file-does-not-exist.yaml: no such file or directory")
r.Empty(warningsBuffer.String())
r.Empty(outputBuffer.String())
})
})
when("the kubeconfig path parameter is empty", func() {
it.Before(func() {
// Note that this is technically polluting other parallel tests in this file, but other tests
// are always specifying the kubeconfigPathOverride parameter, so they're not actually looking
// at the value of this environment variable.
r.NoError(os.Setenv("KUBECONFIG", "./testdata/kubeconfig.yaml"))
})
it.After(func() {
r.NoError(os.Unsetenv("KUBECONFIG"))
})
it("falls back to using the KUBECONFIG env var to find the kubeconfig file", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"",
"",
installationNamespace,
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.NoError(err)
r.True(kubeClientCreatorFuncWasCalled)
r.Empty(warningsBuffer.String())
r.Equal(expectedKubeconfigYAML(
base64.StdEncoding.EncodeToString([]byte("fake-certificate-authority-data-value")),
"https://fake-server-url-value",
fullPathToSelf,
"some-token",
"https://fake-server-url-value",
"fake-certificate-authority-data-value",
installationNamespace,
), outputBuffer.String())
})
})
when("the wrong pinniped namespace is passed in", func() {
it("returns an error", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
"this-is-the-wrong-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.EqualError(err, `No CredentialIssuerConfig was found in namespace "this-is-the-wrong-namespace". Is Pinniped installed on this cluster in namespace "this-is-the-wrong-namespace"?`)
r.True(kubeClientCreatorFuncWasCalled)
})
})
when("there is more than one CredentialIssuerConfig is found on the cluster", func() {
it.Before(func() {
r.NoError(pinnipedClient.Tracker().Add(
newCredentialIssuerConfig(
"another-cic-name",
installationNamespace,
"https://fake-server-url-value",
"fake-certificate-authority-data-value",
),
))
})
it("returns an error", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
installationNamespace,
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.EqualError(err, `More than one CredentialIssuerConfig was found in namespace "some-namespace"`)
r.True(kubeClientCreatorFuncWasCalled)
})
})
})
when("the CredentialIssuerConfig is found on the cluster with a configuration that does not match the existing kubeconfig", func() {
when("the Server doesn't match", func() {
it.Before(func() {
r.NoError(pinnipedClient.Tracker().Add(
newCredentialIssuerConfig(
"some-cic-name",
installationNamespace,
"non-matching-pinniped-server-url",
"fake-certificate-authority-data-value",
),
))
})
it("writes the kubeconfig to the given writer using the values found in the local kubeconfig and issues a warning", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
installationNamespace,
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.NoError(err)
r.True(kubeClientCreatorFuncWasCalled)
r.Equal(
"WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuerConfig on the cluster. Using local kubeconfig values.\n",
warningsBuffer.String(),
)
r.Equal(expectedKubeconfigYAML(
base64.StdEncoding.EncodeToString([]byte("fake-certificate-authority-data-value")),
"https://fake-server-url-value",
fullPathToSelf,
"some-token",
"https://fake-server-url-value",
"fake-certificate-authority-data-value",
installationNamespace,
), outputBuffer.String())
})
})
when("the CA doesn't match", func() {
it.Before(func() {
r.NoError(pinnipedClient.Tracker().Add(
newCredentialIssuerConfig(
"some-cic-name",
installationNamespace,
"https://fake-server-url-value",
"non-matching-certificate-authority-data-value",
),
))
})
it("writes the kubeconfig to the given writer using the values found in the local kubeconfig and issues a warning", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
installationNamespace,
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.NoError(err)
r.True(kubeClientCreatorFuncWasCalled)
r.Equal(
"WARNING: Server and certificate authority did not match between local kubeconfig and Pinniped's CredentialIssuerConfig on the cluster. Using local kubeconfig values.\n",
warningsBuffer.String(),
)
r.Equal(expectedKubeconfigYAML(
base64.StdEncoding.EncodeToString([]byte("fake-certificate-authority-data-value")),
"https://fake-server-url-value",
fullPathToSelf,
"some-token",
"https://fake-server-url-value",
"fake-certificate-authority-data-value",
installationNamespace,
), outputBuffer.String())
})
})
})
when("the CredentialIssuerConfig is found on the cluster with an empty KubeConfigInfo", func() {
it.Before(func() {
r.NoError(pinnipedClient.Tracker().Add(
&configv1alpha1.CredentialIssuerConfig{
TypeMeta: metav1.TypeMeta{
Kind: "CredentialIssuerConfig",
APIVersion: configv1alpha1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "pinniped-config",
Namespace: installationNamespace,
},
Status: configv1alpha1.CredentialIssuerConfigStatus{},
},
))
})
it("returns an error", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
installationNamespace,
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() {
it("returns an error", func() {
kubeClientCreatorFuncWasCalled := false
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
installationNamespace,
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, `No CredentialIssuerConfig was found in namespace "some-namespace". Is Pinniped installed on this cluster in namespace "some-namespace"?`)
r.Empty(warningsBuffer.String())
r.Empty(outputBuffer.String())
})
})
when("there is an error while getting the CredentialIssuerConfig from the cluster", func() {
it("returns an error", func() {
err := getKubeConfig(outputBuffer,
warningsBuffer,
"some-token",
"./testdata/kubeconfig.yaml",
"",
installationNamespace,
func(restConfig *rest.Config) (pinnipedclientset.Interface, error) {
return nil, fmt.Errorf("some error getting CredentialIssuerConfig")
},
)
r.EqualError(err, "some error getting CredentialIssuerConfig")
r.Empty(warningsBuffer.String())
r.Empty(outputBuffer.String())
})
})
}, spec.Parallel(), spec.Report(report.Terminal{}))
} }