Path: blob/develop/tests/functional/eks/test_update_kubeconfig.py
1567 views
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.1#2# Licensed under the Apache License, Version 2.0 (the "License"). You3# may not use this file except in compliance with the License. A copy of4# the License is located at5#6# http://aws.amazon.com/apache2.0/7#8# or in the "license" file accompanying this file. This file is9# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF10# ANY KIND, either express or implied. See the License for the specific11# language governing permissions and limitations under the License.1213import os14import sys15import glob16import yaml17import logging18import botocore19import tempfile20import shutil21import re22from argparse import Namespace2324from botocore.session import get_session2526from awscli.testutils import mock, unittest, capture_output27from awscli.customizations.eks.update_kubeconfig import UpdateKubeconfigCommand28from awscli.customizations.eks.exceptions import EKSClusterError29from awscli.customizations.eks.kubeconfig import (Kubeconfig,30KubeconfigCorruptedError,31KubeconfigInaccessableError)32from tests.functional.eks.test_util import (describe_cluster_response,33describe_cluster_creating_response,34get_testdata,35assume_role_response)3637def sanitize_output(output):38"""39Trims output and removes all lines after a line starting with warning.40A line will only start with warning if it is the start of a41"not installed" warning, which should be ignored when comparing output.42"""43to_return = ""44for line in output.splitlines():45if bool(re.match('warning', line.strip(), re.I)):46return to_return.strip()47else:48to_return += line49to_return += '\n'50return to_return.strip()5152def build_environment(entries):53""" Build an environment variable from a list of strings. """54return os.path.pathsep.join(entries)5556class TestUpdateKubeconfig(unittest.TestCase):57def setUp(self):58self.create_client_patch = mock.patch(59'botocore.session.Session.create_client'60)6162self.mock_create_client = self.create_client_patch.start()63self.session = get_session()6465self.client = mock.Mock()66self.client.describe_cluster.return_value = describe_cluster_response()67self.mock_create_client.return_value = self.client6869# Set up the sts_client_mock70self.sts_client_mock = mock.Mock()71self.sts_client_mock.assume_role.return_value = assume_role_response()7273# Ensure the mock_create_client correctly returns the appropriate mock74self.mock_create_client.side_effect = lambda service_name, **kwargs: (75self.sts_client_mock if service_name == "sts" else self.client76)7778self.command = UpdateKubeconfigCommand(self.session)79self.maxDiff = None8081def tearDown(self):82self.create_client_patch.stop()8384def assert_output(self, captured, file):85"""86Compares the captured output with the testdata named file87For approximate equality.88"""89with open(get_testdata(file)) as f:90self.assertMultiLineEqual(91sanitize_output(captured.stdout.getvalue()),92f.read().strip()93)9495def _get_temp_config(self, config):96"""97Helper to access a temp config generated by initialize_tempfiles.98"""99return os.path.join(self._temp_directory, config)100101def initialize_tempfiles(self, files):102"""103Initializes a directory of tempfiles containing copies of each testdata104file listed in files.105Returns the absolute path of the containing directory.106107:param files: A list of filenames found in testdata108:type files: list109"""110self._temp_directory = tempfile.mkdtemp()111self.addCleanup(shutil.rmtree, self._temp_directory)112if files is not None:113for file in files:114shutil.copy2(get_testdata(file),115self._get_temp_config(file))116return self._temp_directory117118119def build_temp_environment_variable(self, configs):120"""121Generate a string which is an environment variable122containing the paths for each temp file corresponding to configs123124:param configs: The names of the configs in testdata125to put in the environment variable126:type configs: list127"""128return build_environment([self._get_temp_config(config)129for config in configs])130131def assert_config_state(self, config_name, correct_output_name):132"""133Asserts that the temp config named config_name has the same content134as the testdata named correct_output_name.135Should be called after initialize_tempfiles.136137:param config_name: The filename (not the path) of the tempfile138to compare139:type config_name: str140141:param correct_output_name: The filename (not the path) of the testdata142to compare143:type correct_output_name: str144"""145with open(self._get_temp_config(config_name)) as file1:146with open(get_testdata(correct_output_name)) as file2:147self.assertMultiLineEqual(file1.read().strip(),148file2.read().strip())149150151def assert_cmd_dry(self, passed_config,152env_variable_configs,153default_config=os.path.join(".kube", "config")):154"""155Run update-kubeconfig using dry-run,156assert_cmd_dry runs directly referencing the testdata directory,157since dry_run won't write to file158The KUBECONFIG environment variable will be set to contain the configs159listed in env_variable_configs (regardless of whether they exist).160The default path will be set to default_config161Returns the captured output162163:param passed_config: A filename to be passed to --kubeconfig164:type passed_config: string165166:param env_variable_configs: A list of filenames to put in KUBECONFIG167:type env_variable_configs: list or None168169:param default config: A config to be the default path170:type default_config: string171172:returns: The captured output173:rtype: CapturedOutput174"""175env_variable = self.build_temp_environment_variable(176env_variable_configs177)178args = ["--name", "ExampleCluster", "--dry-run"]179if passed_config is not None:180args += ["--kubeconfig", get_testdata(passed_config)]181182with capture_output() as captured:183with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):184with mock.patch(185"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",186get_testdata(default_config)):187self.command(args, None)188189self.mock_create_client.assert_called_once_with('eks')190self.client \191.describe_cluster.assert_called_once_with(name='ExampleCluster')192193return captured194195def assert_cmd(self, configs, passed_config,196env_variable_configs,197default_config=os.path.join(".kube", "config"),198verbose=False):199"""200Run update-kubeconfig in a temp directory,201This directory will have copies of all testdata files whose names202are listed in configs.203The KUBECONFIG environment variable will be set to contain the configs204listed in env_variable_configs (regardless of whether they exist).205The default path will be set to default_config206207:param configs: A list of filenames to copy into the temp directory208:type configs: list209210:param passed_config: A filename to be passed to --kubeconfig211:type passed_config: string or None212213:param env_variable_configs: A list of filenames to put in KUBECONFIG214:type env_variable_configs: list215216:param default config: A config to be the default path217:type default_config: string218"""219self.initialize_tempfiles(configs)220env_variable = self.build_temp_environment_variable(221env_variable_configs222)223args = ["--name", "ExampleCluster"]224if passed_config is not None:225args += ["--kubeconfig", self._get_temp_config(passed_config)]226if verbose:227args += ["--verbose"]228229with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):230with mock.patch(231"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",232self._get_temp_config(default_config)):233self.command(args, None)234235self.mock_create_client.assert_called_once_with('eks')236self.client\237.describe_cluster.assert_called_once_with(name='ExampleCluster')238239def test_dry_run_new(self):240passed = "new_config"241environment = []242243captured_output = self.assert_cmd_dry(passed, environment)244self.assert_output(captured_output, 'output_single')245246def test_dry_run_existing(self):247passed = "valid_existing"248environment = []249250captured_output = self.assert_cmd_dry(passed, environment)251self.assert_output(captured_output, 'output_combined')252253def test_dry_run_empty(self):254passed = "valid_empty_config"255environment = []256257captured_output = self.assert_cmd_dry(passed, environment)258self.assert_output(captured_output, 'output_single')259260def test_dry_run_corrupted(self):261passed = "invalid_string_clusters"262environment = []263264with self.assertRaises(KubeconfigCorruptedError):265captured_output = self.assert_cmd_dry(passed, environment)266267def test_write_new(self):268configs = []269passed = "new_config"270environment = []271272self.assert_cmd(configs, passed, environment)273self.assert_config_state("new_config", "output_single")274275def test_use_environment(self):276configs = ['invalid_string_clusters',277'valid_empty_existing',278'valid_existing']279passed = None280environment = ['does_not_exist',281'invalid_string_clusters',282'valid_empty_existing',283'valid_existing']284285self.assert_cmd(configs, passed, environment)286self.assert_config_state("does_not_exist", "output_single")287288def test_use_default(self):289configs = ["valid_existing"]290passed = None291environment = []292default = "valid_existing"293294self.assert_cmd(configs, passed, environment, default, verbose=True)295self.assert_config_state("valid_existing", "output_combined")296297def test_all_corrupted(self):298configs = ["invalid_string_cluster_entry",299"invalid_string_contexts",300"invalid_text"]301passed = None302environment = ["invalid_string_cluster_entry",303"invalid_string_contexts",304"invalid_text"]305306with self.assertRaises(KubeconfigCorruptedError):307self.assert_cmd(configs, passed, environment)308309def test_all_but_one_corrupted(self):310configs = ["valid_existing",311"invalid_string_cluster_entry",312"invalid_string_contexts",313"invalid_text"]314passed = None315environment = ["valid_existing",316"invalid_string_cluster_entry",317"invalid_string_contexts",318"invalid_text"]319320self.assert_cmd(configs, passed, environment)321self.assert_config_state("valid_existing", 'output_combined')322323def test_corrupted_and_missing(self):324configs = ["invalid_string_clusters",325"invalid_string_users"]326passed = None327environment = ["invalid_string_clusters",328"does_not_exist",329"does_not_exist2",330"invalid_string_users"]331332with self.assertRaises(KubeconfigCorruptedError):333self.assert_cmd(configs, passed, environment)334335def test_one_corrupted_environment(self):336configs = ["invalid_string_clusters"]337passed = None338environment = ["invalid_string_clusters"]339340with self.assertRaises(KubeconfigCorruptedError):341self.assert_cmd(configs, passed, environment)342343def test_environmemt_empty_elements(self):344configs = ["valid_existing"]345346self.initialize_tempfiles(configs)347env_variable = build_environment([348"",349self._get_temp_config("valid_existing")350])351args = ["--name", "ExampleCluster"]352353with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):354with mock.patch(355"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",356self._get_temp_config("default_temp")):357self.command(args, None)358359self.mock_create_client.assert_called_once_with('eks')360self.client\361.describe_cluster.assert_called_once_with(name='ExampleCluster')362self.assert_config_state("valid_existing", "output_combined")363364def test_environmemt_all_empty(self):365configs = ["valid_existing"]366367self.initialize_tempfiles(configs)368env_variable = build_environment(["", ""," ", "\t",""])369args = ["--name", "ExampleCluster"]370371with mock.patch.dict(os.environ, {'KUBECONFIG': env_variable}):372with mock.patch(373"awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH",374self._get_temp_config("default_temp")):375self.command(args, None)376377self.mock_create_client.assert_called_once_with('eks')378self.client\379.describe_cluster.assert_called_once_with(name='ExampleCluster')380self.assert_config_state("default_temp", "output_single")381382def test_default_path_directory(self):383configs = []384passed = None385environment = []386# Default will be the temp directory once _get_temp_config is called387default = ""388389with self.assertRaises(KubeconfigInaccessableError):390self.assert_cmd(configs, passed, environment, default)391392def test_update_existing(self):393configs = ["valid_old_data"]394passed = "valid_old_data"395environment = []396397self.assert_cmd(configs, passed, environment)398self.assert_config_state("valid_old_data", "output_combined")399400def test_update_existing_environment(self):401configs = ["valid_old_data"]402passed = None403environment = ["valid_old_data",404"output_combined",405"output_single"]406407self.assert_cmd(configs, passed, environment)408self.assert_config_state("valid_old_data", "output_combined")409410def test_cluster_creating(self):411configs = ["output_combined"]412passed = "output_combined"413environment = []414self.client.describe_cluster =\415mock.Mock(return_value=describe_cluster_creating_response())416with self.assertRaises(EKSClusterError):417self.assert_cmd(configs, passed, environment)418419def test_kubeconfig_order(self):420configs = ["valid_changed_ordering"]421passed = "valid_changed_ordering"422environment = []423424self.assert_cmd(configs, passed, environment)425self.assert_config_state("valid_changed_ordering", "output_combined_changed_ordering")426427def test_update_old_api_version(self):428configs = ["valid_old_api_version"]429passed = "valid_old_api_version"430environment = []431432self.assert_cmd(configs, passed, environment)433self.assert_config_state("valid_old_api_version", "valid_old_api_version_updated")434435def test_assume_role(self):436"""437Test that assume_role_arn is handled correctly when provided.438"""439configs = ["valid_existing"]440self.initialize_tempfiles(configs)441442# Include the --assume-role-arn argument443args = [444"--name", "ExampleCluster",445"--assume-role-arn", "arn:aws:iam::123456789012:role/test-role"446]447448# Mock environment variables and paths449kubeconfig_path = self._get_temp_config("valid_existing")450default_path = self._get_temp_config("default_temp")451452with mock.patch.dict(os.environ, {'KUBECONFIG': kubeconfig_path}):453with mock.patch("awscli.customizations.eks.update_kubeconfig.DEFAULT_PATH", default_path):454self.command(args, None)455456# Verify that assume_role was called with the correct parameters457self.sts_client_mock.assume_role.assert_called_once_with(458RoleArn="arn:aws:iam::123456789012:role/test-role",459RoleSessionName="EKSDescribeClusterSession"460)461462# Verify that the EKS client was created with the assumed credentials463self.mock_create_client.assert_any_call(464"eks",465aws_access_key_id="test-access-key",466aws_secret_access_key="test-secret-key",467aws_session_token="test-session-token"468)469470# Verify that the cluster was described471self.client.describe_cluster.assert_called_once_with(name="ExampleCluster")472473# Assert the configuration state474self.assert_config_state("valid_existing", "output_combined")475476def test_no_assume_role(self):477"""478Test that assume_role_arn is not used when not provided.479"""480configs = ["valid_existing"]481passed = "valid_existing"482environment = []483484self.client.describe_cluster = mock.Mock(return_value=describe_cluster_response())485self.assert_cmd(configs, passed, environment)486487# Verify that assume_role was not called488self.mock_create_client.assert_called_once_with("eks")489self.client.describe_cluster.assert_called_once_with(name="ExampleCluster")490491492