Repository: argoproj/argo (go)

Issue:
CLI unusable without KUBECONFIG
It should be possible to use the CLI without having a valid KUBECONFIG, by setting ARGO_SERVER and ARGO_TOKEN, but this is not the case :(

```
FATA[0000] invalid configuration: no configuration has been provided
```

Pull request description:
fix(cli)!: Enable CLI to work without kube config. Closes #3383, #2793
Checklist:

* [x] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this is a chore.
* [x] The title of the PR is (a) [conventional](https://www.conventionalcommits.org/en/v1.0.0/), (b) states what changed, and (c) suffixes the related issues number. E.g. `"fix(controller): Updates such and such. Fixes #1234"`.  
* [x] I've signed the CLA.
* [x] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [x] My builds are green. Try syncing with master if they are not. 
* [x] My organization is added to [USERS.md](https://github.com/argoproj/argo/blob/master/USERS.md).


Patch:
diff --git a/cmd/argo/commands/client/conn.go b/cmd/argo/commands/client/conn.go
index 4719c8eae..8f19fdd44 100644
--- a/cmd/argo/commands/client/conn.go
+++ b/cmd/argo/commands/client/conn.go
@@ -50,7 +50,7 @@ func NewAPIClient() (context.Context, apiclient.Client) {
 			AuthSupplier: func() string {
 				return GetAuthString()
 			},
-			ClientConfig: GetConfig(),
+			ClientConfigSupplier: func() clientcmd.ClientConfig { return GetConfig() },
 		})
 	if err != nil {
 		log.Fatal(err)
@@ -59,6 +59,10 @@ func NewAPIClient() (context.Context, apiclient.Client) {
 }
 
 func Namespace() string {
+	namespace, ok := os.LookupEnv("ARGO_NAMESPACE")
+	if ok {
+		return namespace
+	}
 	namespace, _, err := GetConfig().Namespace()
 	if err != nil {
 		log.Fatal(err)
@@ -67,6 +71,10 @@ func Namespace() string {
 }
 
 func GetAuthString() string {
+	token, ok := os.LookupEnv("ARGO_TOKEN")
+	if ok {
+		return token
+	}
 	restConfig, err := GetConfig().ClientConfig()
 	if err != nil {
 		log.Fatal(err)
diff --git a/pkg/apiclient/apiclient.go b/pkg/apiclient/apiclient.go
index dbbf71f95..0b93ecaab 100644
--- a/pkg/apiclient/apiclient.go
+++ b/pkg/apiclient/apiclient.go
@@ -29,7 +29,9 @@ type Opts struct {
 	ArgoServerOpts ArgoServerOpts
 	InstanceID     string
 	AuthSupplier   func() string
-	ClientConfig   clientcmd.ClientConfig
+	// DEPRECATED: use `ClientConfigSupplier`
+	ClientConfig         clientcmd.ClientConfig
+	ClientConfigSupplier func() clientcmd.ClientConfig
 }
 
 // DEPRECATED: use NewClientFromOpts
@@ -37,7 +39,9 @@ func NewClient(argoServer string, authSupplier func() string, clientConfig clien
 	return NewClientFromOpts(Opts{
 		ArgoServerOpts: ArgoServerOpts{URL: argoServer},
 		AuthSupplier:   authSupplier,
-		ClientConfig:   clientConfig,
+		ClientConfigSupplier: func() clientcmd.ClientConfig {
+			return clientConfig
+		},
 	})
 }
 
@@ -49,6 +53,9 @@ func NewClientFromOpts(opts Opts) (context.Context, Client, error) {
 	if opts.ArgoServerOpts.URL != "" {
 		return newArgoServerClient(opts.ArgoServerOpts, opts.AuthSupplier())
 	} else {
+		if opts.ClientConfigSupplier != nil {
+			opts.ClientConfig = opts.ClientConfigSupplier()
+		}
 		return newArgoKubeClient(opts.ClientConfig, instanceid.NewService(opts.InstanceID))
 	}
 }
diff --git a/util/kubeconfig/kubeconfig.go b/util/kubeconfig/kubeconfig.go
index db40b77e8..32d75cfc4 100644
--- a/util/kubeconfig/kubeconfig.go
+++ b/util/kubeconfig/kubeconfig.go
@@ -85,6 +85,7 @@ func GetBearerRestConfig(token string) (*restclient.Config, error) {
 
 //Return the AuthString include Auth type(Basic or Bearer)
 func GetAuthString(in *restclient.Config, explicitKubeConfigPath string) (string, error) {
+
 	//Checking Basic Auth
 	if in.Username != "" {
 		token, err := GetBasicAuthToken(in)
@@ -111,10 +112,6 @@ func GetBearerToken(in *restclient.Config, explicitKubeConfigPath string) (strin
 		return in.BearerToken, nil
 	}
 
-	if token := getEnvToken(); token != "" {
-		return token, nil
-	}
-
 	if in == nil {
 		return "", errors.Errorf("RestClient can't be nil")
 	}
@@ -157,11 +154,6 @@ func GetBearerToken(in *restclient.Config, explicitKubeConfigPath string) (strin
 	return "", errors.Errorf("could not find a token")
 }
 
-// Get the Auth token from environment variable
-func getEnvToken() string {
-	return os.Getenv("ARGO_TOKEN")
-}
-
 func encodeBasicAuthToken(username, password string) string {
 	auth := username + ":" + password
 	return base64.StdEncoding.EncodeToString([]byte(auth))


Test patch:
diff --git a/cmd/argo/commands/client/conn_test.go b/cmd/argo/commands/client/conn_test.go
new file mode 100644
index 000000000..ec9c41079
--- /dev/null
+++ b/cmd/argo/commands/client/conn_test.go
@@ -0,0 +1,21 @@
+package client
+
+import (
+	"os"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetAuthString(t *testing.T) {
+	_ = os.Setenv("ARGO_TOKEN", "my-token")
+	defer func() { _ = os.Unsetenv("ARGO_TOKEN") }()
+	assert.Equal(t, "my-token", GetAuthString())
+
+}
+
+func TestNamespace(t *testing.T) {
+	_ = os.Setenv("ARGO_NAMESPACE", "my-ns")
+	defer func() { _ = os.Unsetenv("ARGO_NAMESPACE") }()
+	assert.Equal(t, "my-ns", Namespace())
+}
diff --git a/test/e2e/cli_with_server_test.go b/test/e2e/cli_with_server_test.go
index 8c01d6f51..2c722b028 100644
--- a/test/e2e/cli_with_server_test.go
+++ b/test/e2e/cli_with_server_test.go
@@ -18,6 +18,7 @@ import (
 
 type CLIWithServerSuite struct {
 	CLISuite
+	kubeConfig string
 }
 
 func (s *CLIWithServerSuite) BeforeTest(suiteName, testName string) {
@@ -27,33 +28,33 @@ func (s *CLIWithServerSuite) BeforeTest(suiteName, testName string) {
 	_ = os.Setenv("ARGO_SERVER", "localhost:2746")
 	_ = os.Setenv("ARGO_SECURE", "true")
 	_ = os.Setenv("ARGO_INSECURE_SKIP_VERIFY", "true")
-	_ = os.Setenv("ARGO_TOKEN", token)
+	_ = os.Setenv("ARGO_TOKEN", "Bearer "+token)
+	// we should not need this to run any tests
+	s.kubeConfig = os.Getenv("KUBECONFIG")
+
 }
 
 func (s *CLIWithServerSuite) AfterTest(suiteName, testName string) {
-	s.CLISuite.AfterTest(suiteName, testName)
+	_ = os.Setenv("KUBECONFIG", s.kubeConfig)
 	_ = os.Unsetenv("ARGO_SERVER")
 	_ = os.Unsetenv("ARGO_SECURE")
 	_ = os.Unsetenv("ARGO_INSECURE_SKIP_VERIFY")
 	_ = os.Unsetenv("ARGO_TOKEN")
+	s.CLISuite.AfterTest(suiteName, testName)
 }
 
 func (s *CLISuite) TestAuthToken() {
 	s.Given().RunCli([]string{"auth", "token"}, func(t *testing.T, output string, err error) {
 		assert.NoError(t, err)
-		var authString, token string
-		token = s.GetBasicAuthToken()
-		if token == "" {
-			token, err = s.GetServiceAccountToken()
-			assert.NoError(t, err)
-			authString = "Bearer " + token
-		} else {
-			authString = "Basic " + token
-		}
-		assert.Equal(t, authString, strings.TrimSpace(output))
+		assert.NotEmpty(t, output)
 	})
 }
 
+func (s *CLIWithServerSuite) TestTokenArg() {
+	// we mark this test as skipped because it does not make any sense when only using server
+	s.T().SkipNow()
+}
+
 func (s *CLIWithServerSuite) TestVersion() {
 	s.Run("Default", func() {
 		s.Given().
diff --git a/test/e2e/fixtures/e2e_suite.go b/test/e2e/fixtures/e2e_suite.go
index 93d43b95b..806293629 100644
--- a/test/e2e/fixtures/e2e_suite.go
+++ b/test/e2e/fixtures/e2e_suite.go
@@ -37,7 +37,6 @@ const Label = "argo-e2e"
 const LabelCron = Label + "-cron"
 
 var imageTag string
-var k3d bool
 
 func init() {
 	gitBranch, err := runCli("git", "rev-parse", "--abbrev-ref=loose", "HEAD")
@@ -57,12 +56,7 @@ func init() {
 		imageTag = parts[len(parts)-2]
 	}
 	imageTag = strings.ReplaceAll(imageTag, "/", "-")
-	context, err := runCli("kubectl", "config", "current-context")
-	if err != nil {
-		panic(err)
-	}
-	k3d = strings.TrimSpace(context) == "k3s-default"
-	log.WithFields(log.Fields{"imageTag": imageTag, "k3d": k3d}).Info()
+	log.WithFields(log.Fields{"imageTag": imageTag}).Info()
 }
 
 type E2ESuite struct {
diff --git a/util/kubeconfig/kubeconfig_test.go b/util/kubeconfig/kubeconfig_test.go
index cca4fa0c4..824352a63 100644
--- a/util/kubeconfig/kubeconfig_test.go
+++ b/util/kubeconfig/kubeconfig_test.go
@@ -7,12 +7,9 @@ import (
 	"testing"
 
 	"github.com/stretchr/testify/assert"
-	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 )
 
-const bearerToken = "bearertoken"
-
 const config = `
 apiVersion: v1
 clusters:
@@ -65,20 +62,3 @@ func Test_BasicAuthString(t *testing.T) {
 
 	})
 }
-
-func Test_BearerAuthString(t *testing.T) {
-
-	restConfig := rest.Config{}
-
-	t.Run("Bearer Auth", func(t *testing.T) {
-		os.Setenv("ARGO_TOKEN", bearerToken)
-		authString, err := GetAuthString(&restConfig, "")
-		assert.NoError(t, err)
-		assert.True(t, IsBearerAuthScheme(authString))
-		config, err := GetRestConfig(authString)
-		assert.NoError(t, err)
-		if assert.NoError(t, err) {
-			assert.Equal(t, bearerToken, config.BearerToken)
-		}
-	})
-}
diff --git a/util/kubeconfig/token_test.go b/util/kubeconfig/token_test.go
deleted file mode 100644
index 6ad621e07..000000000
--- a/util/kubeconfig/token_test.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package kubeconfig
-
-import (
-	"os"
-	"testing"
-
-	"k8s.io/client-go/tools/clientcmd"
-
-	"github.com/stretchr/testify/assert"
-)
-
-func Test_getDefaultTokenVersion(t *testing.T) {
-
-	t.Run("No token", func(t *testing.T) {
-		restConfig, err := clientcmd.DefaultClientConfig.ClientConfig()
-		assert.NoError(t, err)
-		restConfig.BearerToken = ""
-		envToken := os.Getenv("ARGO_TOKEN")
-		os.Unsetenv("ARGO_TOKEN")
-		defer os.Setenv("ARGO_TOKEN", envToken)
-		_, err = GetBearerToken(restConfig, "")
-		assert.Error(t, err)
-	})
-	t.Run("Existing token", func(t *testing.T) {
-		restConfig, err := clientcmd.DefaultClientConfig.ClientConfig()
-		assert.NoError(t, err)
-		restConfig.BearerToken = "test12"
-		envToken := os.Getenv("ARGO_TOKEN")
-		os.Setenv("ARGO_TOKEN", "test")
-		defer os.Setenv("ARGO_TOKEN", envToken)
-		token, err := GetBearerToken(restConfig, "")
-		assert.NoError(t, err)
-		assert.Equal(t, "test12", token)
-	})
-	t.Run("Env token", func(t *testing.T) {
-		restConfig, err := clientcmd.DefaultClientConfig.ClientConfig()
-		assert.NoError(t, err)
-		restConfig.BearerToken = ""
-		envToken := os.Getenv("ARGO_TOKEN")
-		os.Setenv("ARGO_TOKEN", "test")
-		defer os.Setenv("ARGO_TOKEN", envToken)
-		token, err := GetBearerToken(restConfig, "")
-		assert.NoError(t, err)
-		assert.Equal(t, "test", token)
-	})
-}