unit tests should not depend on real KUBECONFIG env var value to pass

This commit is contained in:
Ryan Richard
2024-08-19 15:02:10 -07:00
parent d2f00d832e
commit 21ee90ae41
4 changed files with 110 additions and 64 deletions

View File

@@ -36,6 +36,7 @@ import (
)
type kubeconfigDeps struct {
getenv func(key string) string
getPathToSelf func() (string, error)
getClientset getConciergeClientsetFunc
log plog.MinLogger
@@ -43,6 +44,7 @@ type kubeconfigDeps struct {
func kubeconfigRealDeps() kubeconfigDeps {
return kubeconfigDeps{
getenv: os.Getenv,
getPathToSelf: os.Executable,
getClientset: getRealConciergeClientset,
log: plog.New(),
@@ -156,7 +158,7 @@ func kubeconfigCommand(deps kubeconfigDeps) *cobra.Command {
),
)
f.StringVar(&flags.oidc.upstreamIDPFlow, "upstream-identity-provider-flow", "", fmt.Sprintf("The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. '%s', '%s')", idpdiscoveryv1alpha1.IDPFlowCLIPassword, idpdiscoveryv1alpha1.IDPFlowBrowserAuthcode))
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
f.StringVar(&flags.kubeconfigPath, "kubeconfig", deps.getenv("KUBECONFIG"), "Path to kubeconfig file")
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
f.BoolVar(&flags.skipValidate, "skip-validation", false, "Skip final validation of the kubeconfig (default: false)")
f.DurationVar(&flags.timeout, "timeout", 10*time.Minute, "Timeout for autodiscovery and validation")

View File

@@ -97,6 +97,47 @@ func TestGetKubeconfig(t *testing.T) {
}`, issuerURL)
}
helpOutputFormatString := here.Doc(`
Generate a Pinniped-based kubeconfig for a cluster
Usage:
kubeconfig [flags]
Flags:
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
--concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
--credential-cache string Path to cluster-specific credentials cache
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
-h, --help help for kubeconfig
--install-hint string This text is shown to the user when the pinniped CLI is not installed. (default "The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli for more details")
--kubeconfig string Path to kubeconfig file%s
--kubeconfig-context string Kubeconfig context name (default: current active context)
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience,username,groups])
--oidc-session-cache string Path to OpenID Connect session cache file
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
-o, --output string Output file path (default: stdout)
--pinniped-cli-path string Full path or executable name for the Pinniped CLI binary to be embedded in the resulting kubeconfig output (e.g. 'pinniped') (default: full path of the binary used to execute this command)
--skip-validation Skip final validation of the kubeconfig (default: false)
--static-token string Instead of doing an OIDC-based login, specify a static token
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
--upstream-identity-provider-flow string The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. 'cli_password', 'browser_authcode')
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap', 'activedirectory', 'github')
`)
tests := []struct {
name string
args func(string, string) []string
@@ -120,46 +161,17 @@ func TestGetKubeconfig(t *testing.T) {
name: "help flag passed",
args: func(issuerCABundle string, issuerURL string) []string { return []string{"--help"} },
wantStdout: func(issuerCABundle string, issuerURL string) string {
return here.Doc(`
Generate a Pinniped-based kubeconfig for a cluster
Usage:
kubeconfig [flags]
Flags:
--concierge-api-group-suffix string Concierge API group suffix (default "pinniped.dev")
--concierge-authenticator-name string Concierge authenticator name (default: autodiscover)
--concierge-authenticator-type string Concierge authenticator type (e.g., 'webhook', 'jwt') (default: autodiscover)
--concierge-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated) to use when connecting to the Concierge
--concierge-credential-issuer string Concierge CredentialIssuer object to use for autodiscovery (default: autodiscover)
--concierge-endpoint string API base for the Concierge endpoint
--concierge-mode mode Concierge mode of operation (default TokenCredentialRequestAPI)
--concierge-skip-wait Skip waiting for any pending Concierge strategies to become ready (default: false)
--credential-cache string Path to cluster-specific credentials cache
--generated-name-suffix string Suffix to append to generated cluster, context, user kubeconfig entries (default "-pinniped")
-h, --help help for kubeconfig
--install-hint string This text is shown to the user when the pinniped CLI is not installed. (default "The pinniped CLI does not appear to be installed. See https://get.pinniped.dev/cli for more details")
--kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context)
--no-concierge Generate a configuration which does not use the Concierge, but sends the credential to the cluster directly
--oidc-ca-bundle path Path to TLS certificate authority bundle (PEM format, optional, can be repeated)
--oidc-client-id string OpenID Connect client ID (default: autodiscover) (default "pinniped-cli")
--oidc-issuer string OpenID Connect issuer URL (default: autodiscover)
--oidc-listen-port uint16 TCP port for localhost listener (authorization code flow only)
--oidc-request-audience string Request a token with an alternate audience using RFC8693 token exchange
--oidc-scopes strings OpenID Connect scopes to request during login (default [offline_access,openid,pinniped:request-audience,username,groups])
--oidc-session-cache string Path to OpenID Connect session cache file
--oidc-skip-browser During OpenID Connect login, skip opening the browser (just print the URL)
-o, --output string Output file path (default: stdout)
--pinniped-cli-path string Full path or executable name for the Pinniped CLI binary to be embedded in the resulting kubeconfig output (e.g. 'pinniped') (default: full path of the binary used to execute this command)
--skip-validation Skip final validation of the kubeconfig (default: false)
--static-token string Instead of doing an OIDC-based login, specify a static token
--static-token-env string Instead of doing an OIDC-based login, read a static token from the environment
--timeout duration Timeout for autodiscovery and validation (default 10m0s)
--upstream-identity-provider-flow string The type of client flow to use with the upstream identity provider during login with a Supervisor (e.g. 'cli_password', 'browser_authcode')
--upstream-identity-provider-name string The name of the upstream identity provider used during login with a Supervisor
--upstream-identity-provider-type string The type of the upstream identity provider used during login with a Supervisor (e.g. 'oidc', 'ldap', 'activedirectory', 'github')
`)
return fmt.Sprintf(helpOutputFormatString, "")
},
},
{
name: "help flag passed with KUBECONFIG env var set",
env: map[string]string{
"KUBECONFIG": "/path/to/kubeconfig",
},
args: func(issuerCABundle string, issuerURL string) []string { return []string{"--help"} },
wantStdout: func(issuerCABundle string, issuerURL string) string {
return fmt.Sprintf(helpOutputFormatString, ` (default "/path/to/kubeconfig")`)
},
},
{
@@ -3237,6 +3249,9 @@ func TestGetKubeconfig(t *testing.T) {
var log bytes.Buffer
cmd := kubeconfigCommand(kubeconfigDeps{
getenv: func(key string) string {
return tt.env[key]
},
getPathToSelf: func() (string, error) {
if tt.getPathToSelfErr != nil {
return "", tt.getPathToSelfErr

View File

@@ -24,9 +24,21 @@ import (
"go.pinniped.dev/internal/here"
)
type whoamiDeps struct {
getenv func(key string) string
getClientset getConciergeClientsetFunc
}
func whoamiRealDeps() whoamiDeps {
return whoamiDeps{
getenv: os.Getenv,
getClientset: getRealConciergeClientset,
}
}
//nolint:gochecknoinits
func init() {
rootCmd.AddCommand(newWhoamiCommand(getRealConciergeClientset))
rootCmd.AddCommand(newWhoamiCommand(whoamiRealDeps()))
}
type whoamiFlags struct {
@@ -44,7 +56,7 @@ type clusterInfo struct {
url string
}
func newWhoamiCommand(getClientset getConciergeClientsetFunc) *cobra.Command {
func newWhoamiCommand(deps whoamiDeps) *cobra.Command {
cmd := &cobra.Command{
Args: cobra.NoArgs, // do not accept positional arguments for this command
Use: "whoami",
@@ -56,21 +68,21 @@ func newWhoamiCommand(getClientset getConciergeClientsetFunc) *cobra.Command {
// flags
f := cmd.Flags()
f.StringVarP(&flags.outputFormat, "output", "o", "text", "Output format (e.g., 'yaml', 'json', 'text')")
f.StringVar(&flags.kubeconfigPath, "kubeconfig", os.Getenv("KUBECONFIG"), "Path to kubeconfig file")
f.StringVar(&flags.kubeconfigPath, "kubeconfig", deps.getenv("KUBECONFIG"), "Path to kubeconfig file")
f.StringVar(&flags.kubeconfigContextOverride, "kubeconfig-context", "", "Kubeconfig context name (default: current active context)")
f.StringVar(&flags.apiGroupSuffix, "api-group-suffix", groupsuffix.PinnipedDefaultSuffix, "Concierge API group suffix")
f.DurationVar(&flags.timeout, "timeout", 0, "Timeout for the WhoAmI API request (default: 0, meaning no timeout)")
cmd.RunE = func(cmd *cobra.Command, _ []string) error {
return runWhoami(cmd.OutOrStdout(), getClientset, flags)
return runWhoami(cmd.OutOrStdout(), deps, flags)
}
return cmd
}
func runWhoami(output io.Writer, getClientset getConciergeClientsetFunc, flags *whoamiFlags) error {
func runWhoami(output io.Writer, deps whoamiDeps, flags *whoamiFlags) error {
clientConfig := newClientConfig(flags.kubeconfigPath, flags.kubeconfigContextOverride)
clientset, err := getClientset(clientConfig, flags.apiGroupSuffix)
clientset, err := deps.getClientset(clientConfig, flags.apiGroupSuffix)
if err != nil {
return fmt.Errorf("could not configure Kubernetes client: %w", err)
}

View File

@@ -5,6 +5,7 @@ package cmd
import (
"bytes"
"fmt"
"testing"
"github.com/stretchr/testify/require"
@@ -21,9 +22,25 @@ import (
)
func TestWhoami(t *testing.T) {
helpOutputFormatString := here.Doc(`
Print information about the current user
Usage:
whoami [flags]
Flags:
--api-group-suffix string Concierge API group suffix (default "pinniped.dev")
-h, --help help for whoami
--kubeconfig string Path to kubeconfig file%s
--kubeconfig-context string Kubeconfig context name (default: current active context)
-o, --output string Output format (e.g., 'yaml', 'json', 'text') (default "text")
--timeout duration Timeout for the WhoAmI API request (default: 0, meaning no timeout)
`)
tests := []struct {
name string
args []string
env map[string]string
groupsOverride []string
gettingClientsetErr error
callingAPIErr error
@@ -31,22 +48,17 @@ func TestWhoami(t *testing.T) {
wantStdout, wantStderr string
}{
{
name: "help flag",
args: []string{"--help"},
wantStdout: here.Doc(`
Print information about the current user
Usage:
whoami [flags]
Flags:
--api-group-suffix string Concierge API group suffix (default "pinniped.dev")
-h, --help help for whoami
--kubeconfig string Path to kubeconfig file
--kubeconfig-context string Kubeconfig context name (default: current active context)
-o, --output string Output format (e.g., 'yaml', 'json', 'text') (default "text")
--timeout duration Timeout for the WhoAmI API request (default: 0, meaning no timeout)
`),
name: "help flag passed",
args: []string{"--help"},
wantStdout: fmt.Sprintf(helpOutputFormatString, ""),
},
{
name: "help flag passed with KUBECONFIG env var set",
env: map[string]string{
"KUBECONFIG": "/path/to/kubeconfig",
},
args: []string{"--help"},
wantStdout: fmt.Sprintf(helpOutputFormatString, ` (default "/path/to/kubeconfig")`),
},
{
name: "text output",
@@ -306,7 +318,12 @@ func TestWhoami(t *testing.T) {
})
return clientset, nil
}
cmd := newWhoamiCommand(getClientset)
cmd := newWhoamiCommand(whoamiDeps{
getenv: func(key string) string {
return test.env[key]
},
getClientset: getClientset,
})
stdout, stderr := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{})
cmd.SetOut(stdout)