Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/functional/eks/test_update_kubeconfig.py
1567 views
1
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
14
import os
15
import sys
16
import glob
17
import yaml
18
import logging
19
import botocore
20
import tempfile
21
import shutil
22
import re
23
from argparse import Namespace
24
25
from botocore.session import get_session
26
27
from awscli.testutils import mock, unittest, capture_output
28
from awscli.customizations.eks.update_kubeconfig import UpdateKubeconfigCommand
29
from awscli.customizations.eks.exceptions import EKSClusterError
30
from awscli.customizations.eks.kubeconfig import (Kubeconfig,
31
KubeconfigCorruptedError,
32
KubeconfigInaccessableError)
33
from tests.functional.eks.test_util import (describe_cluster_response,
34
describe_cluster_creating_response,
35
get_testdata,
36
assume_role_response)
37
38
def sanitize_output(output):
39
"""
40
Trims output and removes all lines after a line starting with warning.
41
A line will only start with warning if it is the start of a
42
"not installed" warning, which should be ignored when comparing output.
43
"""
44
to_return = ""
45
for line in output.splitlines():
46
if bool(re.match('warning', line.strip(), re.I)):
47
return to_return.strip()
48
else:
49
to_return += line
50
to_return += '\n'
51
return to_return.strip()
52
53
def build_environment(entries):
54
""" Build an environment variable from a list of strings. """
55
return os.path.pathsep.join(entries)
56
57
class TestUpdateKubeconfig(unittest.TestCase):
58
def setUp(self):
59
self.create_client_patch = mock.patch(
60
'botocore.session.Session.create_client'
61
)
62
63
self.mock_create_client = self.create_client_patch.start()
64
self.session = get_session()
65
66
self.client = mock.Mock()
67
self.client.describe_cluster.return_value = describe_cluster_response()
68
self.mock_create_client.return_value = self.client
69
70
# Set up the sts_client_mock
71
self.sts_client_mock = mock.Mock()
72
self.sts_client_mock.assume_role.return_value = assume_role_response()
73
74
# Ensure the mock_create_client correctly returns the appropriate mock
75
self.mock_create_client.side_effect = lambda service_name, **kwargs: (
76
self.sts_client_mock if service_name == "sts" else self.client
77
)
78
79
self.command = UpdateKubeconfigCommand(self.session)
80
self.maxDiff = None
81
82
def tearDown(self):
83
self.create_client_patch.stop()
84
85
def assert_output(self, captured, file):
86
"""
87
Compares the captured output with the testdata named file
88
For approximate equality.
89
"""
90
with open(get_testdata(file)) as f:
91
self.assertMultiLineEqual(
92
sanitize_output(captured.stdout.getvalue()),
93
f.read().strip()
94
)
95
96
def _get_temp_config(self, config):
97
"""
98
Helper to access a temp config generated by initialize_tempfiles.
99
"""
100
return os.path.join(self._temp_directory, config)
101
102
def initialize_tempfiles(self, files):
103
"""
104
Initializes a directory of tempfiles containing copies of each testdata
105
file listed in files.
106
Returns the absolute path of the containing directory.
107
108
:param files: A list of filenames found in testdata
109
:type files: list
110
"""
111
self._temp_directory = tempfile.mkdtemp()
112
self.addCleanup(shutil.rmtree, self._temp_directory)
113
if files is not None:
114
for file in files:
115
shutil.copy2(get_testdata(file),
116
self._get_temp_config(file))
117
return self._temp_directory
118
119
120
def build_temp_environment_variable(self, configs):
121
"""
122
Generate a string which is an environment variable
123
containing the paths for each temp file corresponding to configs
124
125
:param configs: The names of the configs in testdata
126
to put in the environment variable
127
:type configs: list
128
"""
129
return build_environment([self._get_temp_config(config)
130
for config in configs])
131
132
def assert_config_state(self, config_name, correct_output_name):
133
"""
134
Asserts that the temp config named config_name has the same content
135
as the testdata named correct_output_name.
136
Should be called after initialize_tempfiles.
137
138
:param config_name: The filename (not the path) of the tempfile
139
to compare
140
:type config_name: str
141
142
:param correct_output_name: The filename (not the path) of the testdata
143
to compare
144
:type correct_output_name: str
145
"""
146
with open(self._get_temp_config(config_name)) as file1:
147
with open(get_testdata(correct_output_name)) as file2:
148
self.assertMultiLineEqual(file1.read().strip(),
149
file2.read().strip())
150
151
152
def assert_cmd_dry(self, passed_config,
153
env_variable_configs,
154
default_config=os.path.join(".kube", "config")):
155
"""
156
Run update-kubeconfig using dry-run,
157
assert_cmd_dry runs directly referencing the testdata directory,
158
since dry_run won't write to file
159
The KUBECONFIG environment variable will be set to contain the configs
160
listed in env_variable_configs (regardless of whether they exist).
161
The default path will be set to default_config
162
Returns the captured output
163
164
:param passed_config: A filename to be passed to --kubeconfig
165
:type passed_config: string
166
167
:param env_variable_configs: A list of filenames to put in KUBECONFIG
168
:type env_variable_configs: list or None
169
170
:param default config: A config to be the default path
171
:type default_config: string
172
173
:returns: The captured output
174
:rtype: CapturedOutput
175
"""
176
env_variable = self.build_temp_environment_variable(
177
env_variable_configs
178
)
179
args = ["--name", "ExampleCluster", "--dry-run"]
180
if passed_config is not None:
181
args += ["--kubeconfig", get_testdata(passed_config)]
182
183
with capture_output() as captured:
184
with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):
185
with mock.patch(
186
"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",
187
get_testdata(default_config)):
188
self.command(args, None)
189
190
self.mock_create_client.assert_called_once_with('eks')
191
self.client \
192
.describe_cluster.assert_called_once_with(name='ExampleCluster')
193
194
return captured
195
196
def assert_cmd(self, configs, passed_config,
197
env_variable_configs,
198
default_config=os.path.join(".kube", "config"),
199
verbose=False):
200
"""
201
Run update-kubeconfig in a temp directory,
202
This directory will have copies of all testdata files whose names
203
are listed in configs.
204
The KUBECONFIG environment variable will be set to contain the configs
205
listed in env_variable_configs (regardless of whether they exist).
206
The default path will be set to default_config
207
208
:param configs: A list of filenames to copy into the temp directory
209
:type configs: list
210
211
:param passed_config: A filename to be passed to --kubeconfig
212
:type passed_config: string or None
213
214
:param env_variable_configs: A list of filenames to put in KUBECONFIG
215
:type env_variable_configs: list
216
217
:param default config: A config to be the default path
218
:type default_config: string
219
"""
220
self.initialize_tempfiles(configs)
221
env_variable = self.build_temp_environment_variable(
222
env_variable_configs
223
)
224
args = ["--name", "ExampleCluster"]
225
if passed_config is not None:
226
args += ["--kubeconfig", self._get_temp_config(passed_config)]
227
if verbose:
228
args += ["--verbose"]
229
230
with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):
231
with mock.patch(
232
"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",
233
self._get_temp_config(default_config)):
234
self.command(args, None)
235
236
self.mock_create_client.assert_called_once_with('eks')
237
self.client\
238
.describe_cluster.assert_called_once_with(name='ExampleCluster')
239
240
def test_dry_run_new(self):
241
passed = "new_config"
242
environment = []
243
244
captured_output = self.assert_cmd_dry(passed, environment)
245
self.assert_output(captured_output, 'output_single')
246
247
def test_dry_run_existing(self):
248
passed = "valid_existing"
249
environment = []
250
251
captured_output = self.assert_cmd_dry(passed, environment)
252
self.assert_output(captured_output, 'output_combined')
253
254
def test_dry_run_empty(self):
255
passed = "valid_empty_config"
256
environment = []
257
258
captured_output = self.assert_cmd_dry(passed, environment)
259
self.assert_output(captured_output, 'output_single')
260
261
def test_dry_run_corrupted(self):
262
passed = "invalid_string_clusters"
263
environment = []
264
265
with self.assertRaises(KubeconfigCorruptedError):
266
captured_output = self.assert_cmd_dry(passed, environment)
267
268
def test_write_new(self):
269
configs = []
270
passed = "new_config"
271
environment = []
272
273
self.assert_cmd(configs, passed, environment)
274
self.assert_config_state("new_config", "output_single")
275
276
def test_use_environment(self):
277
configs = ['invalid_string_clusters',
278
'valid_empty_existing',
279
'valid_existing']
280
passed = None
281
environment = ['does_not_exist',
282
'invalid_string_clusters',
283
'valid_empty_existing',
284
'valid_existing']
285
286
self.assert_cmd(configs, passed, environment)
287
self.assert_config_state("does_not_exist", "output_single")
288
289
def test_use_default(self):
290
configs = ["valid_existing"]
291
passed = None
292
environment = []
293
default = "valid_existing"
294
295
self.assert_cmd(configs, passed, environment, default, verbose=True)
296
self.assert_config_state("valid_existing", "output_combined")
297
298
def test_all_corrupted(self):
299
configs = ["invalid_string_cluster_entry",
300
"invalid_string_contexts",
301
"invalid_text"]
302
passed = None
303
environment = ["invalid_string_cluster_entry",
304
"invalid_string_contexts",
305
"invalid_text"]
306
307
with self.assertRaises(KubeconfigCorruptedError):
308
self.assert_cmd(configs, passed, environment)
309
310
def test_all_but_one_corrupted(self):
311
configs = ["valid_existing",
312
"invalid_string_cluster_entry",
313
"invalid_string_contexts",
314
"invalid_text"]
315
passed = None
316
environment = ["valid_existing",
317
"invalid_string_cluster_entry",
318
"invalid_string_contexts",
319
"invalid_text"]
320
321
self.assert_cmd(configs, passed, environment)
322
self.assert_config_state("valid_existing", 'output_combined')
323
324
def test_corrupted_and_missing(self):
325
configs = ["invalid_string_clusters",
326
"invalid_string_users"]
327
passed = None
328
environment = ["invalid_string_clusters",
329
"does_not_exist",
330
"does_not_exist2",
331
"invalid_string_users"]
332
333
with self.assertRaises(KubeconfigCorruptedError):
334
self.assert_cmd(configs, passed, environment)
335
336
def test_one_corrupted_environment(self):
337
configs = ["invalid_string_clusters"]
338
passed = None
339
environment = ["invalid_string_clusters"]
340
341
with self.assertRaises(KubeconfigCorruptedError):
342
self.assert_cmd(configs, passed, environment)
343
344
def test_environmemt_empty_elements(self):
345
configs = ["valid_existing"]
346
347
self.initialize_tempfiles(configs)
348
env_variable = build_environment([
349
"",
350
self._get_temp_config("valid_existing")
351
])
352
args = ["--name", "ExampleCluster"]
353
354
with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):
355
with mock.patch(
356
"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",
357
self._get_temp_config("default_temp")):
358
self.command(args, None)
359
360
self.mock_create_client.assert_called_once_with('eks')
361
self.client\
362
.describe_cluster.assert_called_once_with(name='ExampleCluster')
363
self.assert_config_state("valid_existing", "output_combined")
364
365
def test_environmemt_all_empty(self):
366
configs = ["valid_existing"]
367
368
self.initialize_tempfiles(configs)
369
env_variable = build_environment(["", ""," ", "\t",""])
370
args = ["--name", "ExampleCluster"]
371
372
with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):
373
with mock.patch(
374
"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",
375
self._get_temp_config("default_temp")):
376
self.command(args, None)
377
378
self.mock_create_client.assert_called_once_with('eks')
379
self.client\
380
.describe_cluster.assert_called_once_with(name='ExampleCluster')
381
self.assert_config_state("default_temp", "output_single")
382
383
def test_default_path_directory(self):
384
configs = []
385
passed = None
386
environment = []
387
# Default will be the temp directory once _get_temp_config is called
388
default = ""
389
390
with self.assertRaises(KubeconfigInaccessableError):
391
self.assert_cmd(configs, passed, environment, default)
392
393
def test_update_existing(self):
394
configs = ["valid_old_data"]
395
passed = "valid_old_data"
396
environment = []
397
398
self.assert_cmd(configs, passed, environment)
399
self.assert_config_state("valid_old_data", "output_combined")
400
401
def test_update_existing_environment(self):
402
configs = ["valid_old_data"]
403
passed = None
404
environment = ["valid_old_data",
405
"output_combined",
406
"output_single"]
407
408
self.assert_cmd(configs, passed, environment)
409
self.assert_config_state("valid_old_data", "output_combined")
410
411
def test_cluster_creating(self):
412
configs = ["output_combined"]
413
passed = "output_combined"
414
environment = []
415
self.client.describe_cluster =\
416
mock.Mock(return_value=describe_cluster_creating_response())
417
with self.assertRaises(EKSClusterError):
418
self.assert_cmd(configs, passed, environment)
419
420
def test_kubeconfig_order(self):
421
configs = ["valid_changed_ordering"]
422
passed = "valid_changed_ordering"
423
environment = []
424
425
self.assert_cmd(configs, passed, environment)
426
self.assert_config_state("valid_changed_ordering", "output_combined_changed_ordering")
427
428
def test_update_old_api_version(self):
429
configs = ["valid_old_api_version"]
430
passed = "valid_old_api_version"
431
environment = []
432
433
self.assert_cmd(configs, passed, environment)
434
self.assert_config_state("valid_old_api_version", "valid_old_api_version_updated")
435
436
def test_assume_role(self):
437
"""
438
Test that assume_role_arn is handled correctly when provided.
439
"""
440
configs = ["valid_existing"]
441
self.initialize_tempfiles(configs)
442
443
# Include the --assume-role-arn argument
444
args = [
445
"--name", "ExampleCluster",
446
"--assume-role-arn", "arn:aws:iam::123456789012:role/test-role"
447
]
448
449
# Mock environment variables and paths
450
kubeconfig_path = self._get_temp_config("valid_existing")
451
default_path = self._get_temp_config("default_temp")
452
453
with mock.patch.dict(os.environ, {'KUBECONFIG': kubeconfig_path}):
454
with mock.patch("awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH", default_path):
455
self.command(args, None)
456
457
# Verify that assume_role was called with the correct parameters
458
self.sts_client_mock.assume_role.assert_called_once_with(
459
RoleArn="arn:aws:iam::123456789012:role/test-role",
460
RoleSessionName="EKSDescribeClusterSession"
461
)
462
463
# Verify that the EKS client was created with the assumed credentials
464
self.mock_create_client.assert_any_call(
465
"eks",
466
aws_access_key_id="test-access-key",
467
aws_secret_access_key="test-secret-key",
468
aws_session_token="test-session-token"
469
)
470
471
# Verify that the cluster was described
472
self.client.describe_cluster.assert_called_once_with(name="ExampleCluster")
473
474
# Assert the configuration state
475
self.assert_config_state("valid_existing", "output_combined")
476
477
def test_no_assume_role(self):
478
"""
479
Test that assume_role_arn is not used when not provided.
480
"""
481
configs = ["valid_existing"]
482
passed = "valid_existing"
483
environment = []
484
485
self.client.describe_cluster = mock.Mock(return_value=describe_cluster_response())
486
self.assert_cmd(configs, passed, environment)
487
488
# Verify that assume_role was not called
489
self.mock_create_client.assert_called_once_with("eks")
490
self.client.describe_cluster.assert_called_once_with(name="ExampleCluster")
491
492