Repository: kubernetes-client/python

Issue:
Client doesn't implement provideClusterInfo
**What you expected to happen**:
A Python script provided with a kubeconfig that contains a user exec section to get a client certificate should connect to the cluster.

The kubeconfig contains this section:
```
users:
- name: my-garden-cluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1
      args:
      - get-client-certificate
      command: kubectl-gardenlogin
      env: null
      installHint: Follow the instructions on https://github.com/gardener/gardenlogin#installation
        to install gardenlogin
      interactiveMode: IfAvailable
      provideClusterInfo: true
```

Running a Kubernetes Python script with this kubeconfig produces the following error:

```
Error: failed to complete command options: ExecCredential does not contain cluster information
Usage:
  gardenlogin get-client-certificate [flags]
```



Discussion:
This looks to me like the exec itself is not working correctly.  What happens when you run `kubectl-gardenlogin get-client-certificate` in your shell? If that doesn't work, this won't work either.

Does this kubeconfig file work when you run it with `kubectl`?
Thanks for your response. "kubectl-gardenlogin" produces valid certificates in that shell. This is how I kind of worked around this issue: exec get-certificate with python.subprocess and use the certs with the client object. It also works with kubectl (and k9s for instance). It works as follows:

- get kubeconfig from Gardener 
- gardenctl config set-garden Gardenname --kubeconfig Downloadedkubeconfig
- eval $(gardenctl kubectl-env bash) # this sets KUBECONFIG to some temporary file. This is the kubeconfig that contains the above exec configuration with kubectl-gardenlogin
- gardenctl target --garden Gardenname --project GardenerProject --shoot ShootName

after that I can get the certs with 
- kubectl-gardenlogin get-client-certificate --name ShootName --namespace Garden-Projectname--garden-cluster-identity Gardenname

After that, when I run "kubectl get ns", it works, but the corresponding Python client sample errors with: "Error: failed to complete command options: ExecCredential does not contain cluster information". Which is true, because the ExecCredential delivered by get-certificate contains only kind, apiVersion, spec (interactive=false) and status (with expirationTimestamp, clientCertificateData and clientKeyData). Changing the value of provideClusterInfo in the kubeconfig doesn't seem to change anything.

If you look into the code of [base/config/exec_provider](https://github.com/kubernetes-client/python/blob/f414832bb05b946eb1758df12f08806b44dd315e/kubernetes/base/config/exec_provider.py#L34) there's no mention of a cluster or clusterinfo as it is in the Go client.
Ok, I think I see the issue.

I believe that the message is coming from `gardenlogin` and it's refering to the ExecCredential which is sent into `gardenlogin` as input.

If you look at the code here: https://github.com/kubernetes-client/python/blob/master/kubernetes/base/config/exec_provider.py#L65 we definitely don't set the cluster info.

The root of the problem is that we don't implement the `provideClusterInfo` as described here:

https://kubernetes.io/docs/reference/config-api/client-authentication.v1/#client-authentication-k8s-io-v1-ExecCredentialSpec
The relevant code in Go is here:
https://github.com/kubernetes/client-go/blob/3dc7fd5f4c1d8afaf5924c461eae2ab27db0045a/plugin/pkg/client/auth/exec/exec.go#L424



Patch:
diff --git a/kubernetes/base/config/exec_provider.py b/kubernetes/base/config/exec_provider.py
index 10b73dbe1e..317a566955 100644
--- a/kubernetes/base/config/exec_provider.py
+++ b/kubernetes/base/config/exec_provider.py
@@ -31,7 +31,7 @@ class ExecProvider(object):
     * caching
     """
 
-    def __init__(self, exec_config, cwd):
+    def __init__(self, exec_config, cwd, cluster=None):
         """
         exec_config must be of type ConfigNode because we depend on
         safe_get(self, key) to correctly handle optional exec provider
@@ -53,7 +53,10 @@ def __init__(self, exec_config, cwd):
                 value = item['value']
                 additional_vars[name] = value
             self.env.update(additional_vars)
-
+        if exec_config.safe_get('provideClusterInfo'):
+            self.cluster = cluster
+        else:
+            self.cluster = None
         self.cwd = cwd or None
 
     def run(self, previous_response=None):
@@ -67,6 +70,9 @@ def run(self, previous_response=None):
         }
         if previous_response:
             kubernetes_exec_info['spec']['response'] = previous_response
+        if self.cluster:
+            kubernetes_exec_info['spec']['cluster'] = self.cluster
+
         self.env['KUBERNETES_EXEC_INFO'] = json.dumps(kubernetes_exec_info)
         process = subprocess.Popen(
             self.args,
diff --git a/kubernetes/base/config/kube_config.py b/kubernetes/base/config/kube_config.py
index 09cda8bda0..7077955ca6 100644
--- a/kubernetes/base/config/kube_config.py
+++ b/kubernetes/base/config/kube_config.py
@@ -487,7 +487,7 @@ def _load_from_exec_plugin(self):
             return
         try:
             base_path = self._get_base_path(self._cluster.path)
-            status = ExecProvider(self._user['exec'], base_path).run()
+            status = ExecProvider(self._user['exec'], base_path, self._cluster).run()
             if 'token' in status:
                 self.token = "Bearer %s" % status['token']
             elif 'clientCertificateData' in status:


Test patch:
diff --git a/kubernetes/base/config/exec_provider_test.py b/kubernetes/base/config/exec_provider_test.py
index de8109499f..9ff62d12e2 100644
--- a/kubernetes/base/config/exec_provider_test.py
+++ b/kubernetes/base/config/exec_provider_test.py
@@ -12,6 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import json
 import os
 import unittest
 
@@ -31,6 +32,13 @@ def setUp(self):
             'apiVersion': 'client.authentication.k8s.io/v1beta1',
             'env': None
         })
+        self.input_with_cluster = ConfigNode('test', {
+            'command': 'aws-iam-authenticator',
+            'args': ['token', '-i', 'dummy'],
+            'apiVersion': 'client.authentication.k8s.io/v1beta1',
+            'provideClusterInfo': True,
+            'env': None
+        })
         self.output_ok = """
         {
             "apiVersion": "client.authentication.k8s.io/v1beta1",
@@ -162,6 +170,19 @@ def test_ok_no_console_attached(self, mock):
             self.assertTrue(isinstance(result, dict))
             self.assertTrue('token' in result)
 
+    @mock.patch('subprocess.Popen')
+    def test_with_cluster_info(self, mock):
+        instance = mock.return_value
+        instance.wait.return_value = 0
+        instance.communicate.return_value = (self.output_ok, '')
+        ep = ExecProvider(self.input_with_cluster, None, {'server': 'name.company.com'})
+        result = ep.run()
+        self.assertTrue(isinstance(result, dict))
+        self.assertTrue('token' in result)
+
+        obj = json.loads(mock.call_args.kwargs['env']['KUBERNETES_EXEC_INFO'])
+        self.assertEqual(obj['spec']['cluster']['server'], 'name.company.com')
+
 
 if __name__ == '__main__':
     unittest.main()