Skip to content

Commit 1a6b498

Browse files
micahhauslerjustindho
authored andcommitted
Deprecate Kubernetes client API version v1alpha1
Kubernetes has deprecated v1alpha1, v1beta1 has been available since Kubernetes v1.11 (kubernetes/kubernetes#64482), and EKS currently supports Kubernetes versions v1.16 through v1.21. This is a breaking change for clients running versions v1.10 and older, which haven't been supported by EKS since September 2019. "aws eks get-token" now respects the KUBERNETES_EXEC_INFO environment variable and conservatively falls back to v1alpha1, which is supported by Kubernetes versions 1.10 through 1.22 (released upstream August 2021, to be released by EKS in Q4 2021). It also now supports "v1beta1" and "v1". "aws eks update-kubeconfig" now writes "v1beta1" in the kubeconfig which will be supported by Kubernetes until 1.29 (aproximately December 2023). At or around that date, we can change the default version written to kubeconfigs to "v1" Signed-off-by: Micah Hausler <mhausler@amazon.com>
1 parent e564532 commit 1a6b498

26 files changed

Lines changed: 272 additions & 41 deletions

awscli/customizations/eks/get_token.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import base64
1414
import botocore
1515
import json
16+
import os
17+
import sys
1618

1719
from datetime import datetime, timedelta
1820
from botocore.signers import RequestSigner
@@ -74,7 +76,7 @@ def _run_main(self, parsed_args, parsed_globals):
7476

7577
full_object = {
7678
"kind": "ExecCredential",
77-
"apiVersion": "client.authentication.k8s.io/v1alpha1",
79+
"apiVersion": self.discover_api_version(),
7880
"spec": {},
7981
"status": {
8082
"expirationTimestamp": token_expiration,
@@ -86,6 +88,72 @@ def _run_main(self, parsed_args, parsed_globals):
8688
uni_print('\n')
8789
return 0
8890

91+
def discover_api_version(self):
92+
"""
93+
Parses the KUBERNETES_EXEC_INFO environment variable and returns the API
94+
version. If the environment variable is empty, malformed, or invalid,
95+
return the v1alpha1 response and print an message to stderr.
96+
97+
If the v1alpha1 API is specified explicitly, a message is printed to
98+
stderr with instructions to update.
99+
100+
:return: The client authentication API version
101+
:rtype: string
102+
"""
103+
alpha_api = "client.authentication.k8s.io/v1alpha1"
104+
beta_api = "client.authentication.k8s.io/v1beta1"
105+
v1_api = "client.authentication.k8s.io/v1"
106+
# At the time Kubernetes v1.29 is released upstream (aprox Dec 2023),
107+
# "v1beta1" will be removed. At or around that time, EKS will likely
108+
# support v1.22 through v1.28, in which client API version "v1beta1"
109+
# will be supported by all EKS versions.
110+
fallback_api_version = alpha_api
111+
112+
error_prefixes = {
113+
"error": "Error parsing",
114+
"empty": "Empty",
115+
}
116+
117+
error_msg_tpl = ("{0} KUBERNETES_EXEC_INFO, defaulting "
118+
"to {1}. This is likely a bug in your Kubernetes "
119+
"client. Please update your Kubernetes client.")
120+
unrecognized_msg = ("Unrecognized API version in KUBERNETES_EXEC_INFO, defaulting "
121+
"to {0}. This is likely due to an outdated AWS CLI."
122+
" Please update your AWS CLI.".format(fallback_api_version))
123+
deprecation_msg_tpl = ("Kubeconfig user entry is using deprecated API "
124+
"version {0}. Run 'aws eks update-kubeconfig' to update")
125+
126+
exec_info_raw = os.environ.get("KUBERNETES_EXEC_INFO", "")
127+
if len(exec_info_raw) == 0:
128+
# All kube clients should be setting this, we'll return the fallback and write an error
129+
sys.stderr.write(error_msg_tpl.format(error_prefixes["empty"], fallback_api_version))
130+
sys.stderr.write("\n")
131+
sys.stderr.flush()
132+
return fallback_api_version
133+
try:
134+
exec_info = json.loads(exec_info_raw)
135+
except json.JSONDecodeError as e:
136+
# The environment variable was malformed
137+
sys.stderr.write(error_msg_tpl.format(error_prefixes["error"], fallback_api_version))
138+
sys.stderr.write("\n")
139+
sys.stderr.flush()
140+
return fallback_api_version
141+
142+
api_version_raw = exec_info.get("apiVersion")
143+
if api_version_raw == v1_api:
144+
return v1_api
145+
if api_version_raw == beta_api:
146+
return beta_api
147+
if api_version_raw == alpha_api:
148+
sys.stderr.write(deprecation_msg_tpl.format(alpha_api))
149+
sys.stderr.write("\n")
150+
sys.stderr.flush()
151+
return alpha_api
152+
153+
sys.stderr.write(unrecognized_msg)
154+
sys.stderr.write("\n")
155+
sys.stderr.flush()
156+
return fallback_api_version
89157

90158
class TokenGenerator(object):
91159
def __init__(self, sts_client):

awscli/customizations/eks/update_kubeconfig.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@
3131

3232
DEFAULT_PATH = os.path.expanduser("~/.kube/config")
3333

34-
# Use the endpoint for kubernetes 1.10
35-
# To get the most recent endpoint we will need to
36-
# Do a check on the cluster's version number
37-
API_VERSION = "client.authentication.k8s.io/v1alpha1"
34+
# At the time EKS no longer supports Kubernetes v1.21 (probably ~Dec 2023),
35+
# this can be safely changed to default to writing "v1"
36+
API_VERSION = "client.authentication.k8s.io/v1beta1"
3837

3938
class UpdateKubeconfigCommand(BasicCommand):
4039
NAME = 'update-kubeconfig'
@@ -306,7 +305,7 @@ def get_user_entry(self):
306305
"--cluster-name",
307306
self._cluster_name,
308307
]),
309-
("command", "aws")
308+
("command", "aws"),
310309
]))
311310
]))
312311
])

awscli/examples/eks/get-token.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Output::
1010

1111
{
1212
"kind": "ExecCredential",
13-
"apiVersion": "client.authentication.k8s.io/v1alpha1",
13+
"apiVersion": "client.authentication.k8s.io/v1beta1",
1414
"spec": {},
1515
"status": {
1616
"expirationTimestamp": "2019-08-14T18:44:27Z",

tests/functional/eks/test_get_token.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import base64
1414
from datetime import datetime
1515
import json
16+
import os
1617

1718
from awscli.testutils import mock
1819
from awscli.testutils import BaseAWSCommandParamsTest
@@ -143,3 +144,105 @@ def test_url_different_partition(self):
143144
expected_endpoint='sts.cn-north-1.amazonaws.com.cn',
144145
expected_signing_region='cn-north-1'
145146
)
147+
148+
def test_api_version_discovery_deprecated(self):
149+
os.environ["KUBERNETES_EXEC_INFO"] = '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1alpha1","spec":{"interactive":true}}'
150+
cmd = 'eks get-token --cluster-name %s' % self.cluster_name
151+
stdout, stderr, _ = self.run_cmd(cmd)
152+
response = json.loads(stdout)
153+
154+
self.assertEqual(
155+
response["apiVersion"],
156+
"client.authentication.k8s.io/v1alpha1",
157+
)
158+
159+
self.assertEqual(
160+
stderr,
161+
("Kubeconfig user entry is using deprecated API "
162+
"version client.authentication.k8s.io/v1alpha1. Run 'aws eks update-kubeconfig' to update\n")
163+
)
164+
165+
del os.environ["KUBERNETES_EXEC_INFO"]
166+
167+
def test_api_version_discovery_malformed(self):
168+
os.environ["KUBERNETES_EXEC_INFO"] = '{{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1alpha1","spec":{"interactive":true}}'
169+
cmd = 'eks get-token --cluster-name %s' % self.cluster_name
170+
stdout, stderr, _ = self.run_cmd(cmd)
171+
response = json.loads(stdout)
172+
173+
self.assertEqual(
174+
response["apiVersion"],
175+
"client.authentication.k8s.io/v1alpha1",
176+
)
177+
178+
self.assertEqual(
179+
stderr,
180+
("Error parsing KUBERNETES_EXEC_INFO, defaulting to client.authentication.k8s.io/v1alpha1. "
181+
"This is likely a bug in your Kubernetes client. Please update your Kubernetes client.\n")
182+
)
183+
184+
del os.environ["KUBERNETES_EXEC_INFO"]
185+
186+
def test_api_version_discovery_empty(self):
187+
cmd = 'eks get-token --cluster-name %s' % self.cluster_name
188+
stdout, stderr, _ = self.run_cmd(cmd)
189+
response = json.loads(stdout)
190+
191+
self.assertEqual(
192+
response["apiVersion"],
193+
"client.authentication.k8s.io/v1alpha1",
194+
)
195+
196+
self.assertEqual(
197+
stderr,
198+
("Empty KUBERNETES_EXEC_INFO, defaulting to client.authentication.k8s.io/v1alpha1. "
199+
"This is likely a bug in your Kubernetes client. Please update your Kubernetes client.\n")
200+
)
201+
def test_api_version_discovery_v1(self):
202+
os.environ["KUBERNETES_EXEC_INFO"] = '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1","spec":{"interactive":true}}'
203+
cmd = 'eks get-token --cluster-name %s' % self.cluster_name
204+
stdout, stderr, _ = self.run_cmd(cmd)
205+
response = json.loads(stdout)
206+
207+
self.assertEqual(
208+
response["apiVersion"],
209+
"client.authentication.k8s.io/v1",
210+
)
211+
212+
self.assertEqual(stderr, "")
213+
214+
del os.environ["KUBERNETES_EXEC_INFO"]
215+
216+
def test_api_version_discovery_v1beta1(self):
217+
os.environ["KUBERNETES_EXEC_INFO"] = '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v1beta1","spec":{"interactive":true}}'
218+
cmd = 'eks get-token --cluster-name %s' % self.cluster_name
219+
stdout, stderr, _ = self.run_cmd(cmd)
220+
response = json.loads(stdout)
221+
222+
self.assertEqual(
223+
response["apiVersion"],
224+
"client.authentication.k8s.io/v1beta1",
225+
)
226+
227+
self.assertEqual(stderr, "")
228+
229+
del os.environ["KUBERNETES_EXEC_INFO"]
230+
231+
def test_api_version_discovery_unknown(self):
232+
os.environ["KUBERNETES_EXEC_INFO"] = '{"kind":"ExecCredential","apiVersion":"client.authentication.k8s.io/v2","spec":{"interactive":true}}'
233+
cmd = 'eks get-token --cluster-name %s' % self.cluster_name
234+
stdout, stderr, _ = self.run_cmd(cmd)
235+
response = json.loads(stdout)
236+
237+
self.assertEqual(
238+
response["apiVersion"],
239+
"client.authentication.k8s.io/v1alpha1",
240+
)
241+
242+
self.assertEqual(
243+
stderr,
244+
("Unrecognized API version in KUBERNETES_EXEC_INFO, defaulting to client.authentication.k8s.io/v1alpha1. "
245+
"This is likely due to an outdated AWS CLI. Please update your AWS CLI.\n")
246+
)
247+
248+
del os.environ["KUBERNETES_EXEC_INFO"]

tests/functional/eks/test_kubeconfig.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def _clone_config(self, config):
9595
"""
9696
old_path = os.path.abspath(get_testdata(config))
9797
new_path = os.path.join(self._temp_directory, config)
98-
shutil.copy2(old_path,
98+
shutil.copy2(old_path,
9999
new_path)
100100
return new_path
101101

@@ -119,25 +119,25 @@ def test_load_simple(self):
119119
])
120120
loaded_config = self._loader.load_kubeconfig(simple_path)
121121
self.assertEqual(loaded_config.content, content)
122-
self._validator.validate_config.called_with(Kubeconfig(simple_path,
122+
self._validator.validate_config.called_with(Kubeconfig(simple_path,
123123
content))
124124

125125
def test_load_noexist(self):
126126
no_exist_path = os.path.join(self._temp_directory,
127127
"this_does_not_exist")
128128
loaded_config = self._loader.load_kubeconfig(no_exist_path)
129-
self.assertEqual(loaded_config.content,
129+
self.assertEqual(loaded_config.content,
130130
_get_new_kubeconfig_content())
131131
self._validator.validate_config.called_with(
132132
Kubeconfig(no_exist_path, _get_new_kubeconfig_content()))
133133

134134
def test_load_empty(self):
135135
empty_path = self._clone_config("valid_empty_existing")
136136
loaded_config = self._loader.load_kubeconfig(empty_path)
137-
self.assertEqual(loaded_config.content,
137+
self.assertEqual(loaded_config.content,
138138
_get_new_kubeconfig_content())
139139
self._validator.validate_config.called_with(
140-
Kubeconfig(empty_path,
140+
Kubeconfig(empty_path,
141141
_get_new_kubeconfig_content()))
142142

143143
def test_load_directory(self):

tests/functional/eks/test_update_kubeconfig.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ def setUp(self):
5858
self.create_client_patch = mock.patch(
5959
'botocore.session.Session.create_client'
6060
)
61-
61+
6262
self.mock_create_client = self.create_client_patch.start()
6363
self.session = get_session()
6464

6565
self.client = mock.Mock()
6666
self.client.describe_cluster.return_value = describe_cluster_response()
6767
self.mock_create_client.return_value = self.client
68-
68+
6969
self.command = UpdateKubeconfigCommand(self.session)
7070
self.maxDiff = None
7171

@@ -102,7 +102,7 @@ def initialize_tempfiles(self, files):
102102
self.addCleanup(shutil.rmtree, self._temp_directory)
103103
if files is not None:
104104
for file in files:
105-
shutil.copy2(get_testdata(file),
105+
shutil.copy2(get_testdata(file),
106106
self._get_temp_config(file))
107107
return self._temp_directory
108108

@@ -116,7 +116,7 @@ def build_temp_environment_variable(self, configs):
116116
to put in the environment variable
117117
:type configs: list
118118
"""
119-
return build_environment([self._get_temp_config(config)
119+
return build_environment([self._get_temp_config(config)
120120
for config in configs])
121121

122122
def assert_config_state(self, config_name, correct_output_name):
@@ -125,7 +125,7 @@ def assert_config_state(self, config_name, correct_output_name):
125125
as the testdata named correct_output_name.
126126
Should be called after initialize_tempfiles.
127127
128-
:param config_name: The filename (not the path) of the tempfile
128+
:param config_name: The filename (not the path) of the tempfile
129129
to compare
130130
:type config_name: str
131131
@@ -189,7 +189,7 @@ def assert_cmd(self, configs, passed_config,
189189
verbose=False):
190190
"""
191191
Run update-kubeconfig in a temp directory,
192-
This directory will have copies of all testdata files whose names
192+
This directory will have copies of all testdata files whose names
193193
are listed in configs.
194194
The KUBECONFIG environment variable will be set to contain the configs
195195
listed in env_variable_configs (regardless of whether they exist).
@@ -384,7 +384,7 @@ def test_update_existing(self):
384384
configs = ["valid_old_data"]
385385
passed = "valid_old_data"
386386
environment = []
387-
387+
388388
self.assert_cmd(configs, passed, environment)
389389
self.assert_config_state("valid_old_data", "output_combined")
390390

@@ -394,7 +394,7 @@ def test_update_existing_environment(self):
394394
environment = ["valid_old_data",
395395
"output_combined",
396396
"output_single"]
397-
397+
398398
self.assert_cmd(configs, passed, environment)
399399
self.assert_config_state("valid_old_data", "output_combined")
400400

@@ -415,3 +415,10 @@ def test_kubeconfig_order(self):
415415
self.assert_cmd(configs, passed, environment)
416416
self.assert_config_state("valid_changed_ordering", "output_combined_changed_ordering")
417417

418+
def test_update_old_api_version(self):
419+
configs = ["valid_old_api_version"]
420+
passed = "valid_old_api_version"
421+
environment = []
422+
423+
self.assert_cmd(configs, passed, environment)
424+
self.assert_config_state("valid_old_api_version", "valid_old_api_version_updated")

tests/functional/eks/testdata/invalid_string_cluster_entry

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ users:
1313
- name: arn:aws:eks:us-west-2:111222333444:cluster/Existing
1414
user:
1515
exec:
16-
apiVersion: client.authentication.k8s.io/v1alpha1
16+
apiVersion: client.authentication.k8s.io/v1beta1
1717
args:
1818
- --region
1919
- us-west-2

tests/functional/eks/testdata/invalid_string_clusters

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ users:
1212
- name: arn:aws:eks:us-west-2:111222333444:cluster/Existing
1313
user:
1414
exec:
15-
apiVersion: client.authentication.k8s.io/v1alpha1
15+
apiVersion: client.authentication.k8s.io/v1beta1
1616
args:
1717
- --region
1818
- us-west-2

tests/functional/eks/testdata/invalid_string_context_entry

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ users:
1313
- name: arn:aws:eks:us-west-2:111222333444:cluster/Existing
1414
user:
1515
exec:
16-
apiVersion: client.authentication.k8s.io/v1alpha1
16+
apiVersion: client.authentication.k8s.io/v1beta1
1717
args:
1818
- --region
1919
- us-west-2

tests/functional/eks/testdata/invalid_string_contexts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ users:
1212
- name: arn:aws:eks:us-west-2:111222333444:cluster/Existing
1313
user:
1414
exec:
15-
apiVersion: client.authentication.k8s.io/v1alpha1
15+
apiVersion: client.authentication.k8s.io/v1beta1
1616
args:
1717
- --region
1818
- us-west-2

0 commit comments

Comments
 (0)