def _setUp(self, subfolder): session = boto3.Session() pill = placebo.attach(session, data_path=os.path.join(self.PLACEBO_PATH, subfolder)) pill.playback() self.ssm_client = session.client('ssm') SSMParameter.set_ssm_client(self.ssm_client)
def test_with_valid_client(self): """ Test invalid client (without required methods) """ # pylint: disable=unused-argument,no-self-use class MyValidClient(object): """ This client has all the required methods """ def get_parameters(self, *args, **kwargs): """ Mock method """ return { 'InvalidParameters': [], 'Parameters': [ { "Type": "String", "Name": "my_param", "Value": "abc123", "Version": 1 }, ], } def get_parameters_by_path(self, *args, **kwargs): """ Mock method """ return { "Parameters": [ { "Type": "String", "Name": "/foo/bar/1", "Value": "abc123", "Version": 1 }, { "Type": "String", "Name": "/foo/bar/2", "Value": "abc123", "Version": 1 }, ] } client = MyValidClient() SSMParameter.set_ssm_client(client) param = SSMParameter("my_param") self.assertEqual(param.value, self.PARAM_VALUE) group = SSMParameterGroup() param = group.parameter("my_param") self.assertEqual(param.value, self.PARAM_VALUE) params = group.parameters("/foo/bar/") self.assertEqual(len(params), 2) for param in params: self.assertEqual(param.value, self.PARAM_VALUE)
def test_with_placebo(self): """ Test that set_ssm_client works fine with Placebo """ session = boto3.Session() pill = placebo.attach(session, data_path=self.PLACEBO_PATH) pill.playback() client = session.client('ssm') SSMParameter.set_ssm_client(client) param = SSMParameter("my_param") self.assertEqual(param.value, self.PARAM_VALUE)
def test_should_refresh(self): # without max age cache = SSMParameter("my_param") self.assertFalse(cache._should_refresh()) # with max age and no data cache = SSMParameter("my_param", max_age=10) self.assertTrue(cache._should_refresh()) # with max age and last refreshed date OK cache._last_refresh_time = datetime.utcnow() self.assertFalse(cache._should_refresh()) # with max age and last refreshed date KO cache._last_refresh_time = datetime.utcnow() - timedelta(seconds=20) self.assertTrue(cache._should_refresh())
def test_with_illegal_client(self): """ Test invalid client (without required methods) """ with self.assertRaises(TypeError): SSMParameter.set_ssm_client(42) # pylint: disable=too-few-public-methods class MyInvalidClient(object): """ This client only has get_parameters """ def get_parameters(self): """ Empty method """ with self.assertRaises(TypeError): client = MyInvalidClient() SSMParameter.set_ssm_client(client)
def test_versions_invalid(self): """ Test invalid version """ name = "my_param" with self.assertRaises(InvalidVersionError): SSMParameter("%s:hello" % name) with self.assertRaises(InvalidVersionError): SSMParameter("%s:0" % name) with self.assertRaises(InvalidVersionError): SSMParameter("%s:-1" % name) with self.assertRaises(InvalidVersionError): SSMParameter("%s:" % name)
def setUp(self): """ Create params/groups for each test """ names = ["my_param", "my_grouped_param"] self._create_params(names) self.cache = SSMParameter("my_param") self.group = SSMParameterGroup() self.grouped_param = self.group.parameter("my_grouped_param")
def test_creation(self): # single string cache = SSMParameter("my_param") self.assertTrue(cache._with_decryption) self.assertIsNone(cache._max_age) self.assertIsNone(cache._last_refresh_time) # invalid params with self.assertRaises(TypeError): SSMParameter() with self.assertRaises(ValueError): SSMParameter(None) group = SSMParameterGroup() parameter = group.parameter("my_param") with self.assertRaises(TypeError): group.parameter()
def get_config(config_key): """ Retrieve the configuration from the SSM parameter store The config always returns a tuple (value, rate) value: requested configuration rate: the injection probability (default 1 --> 100%) How to use:: >>> import os >>> from chaos_lib import get_config >>> os.environ['FAILURE_INJECTION_PARAM'] = 'chaoslambda.config' >>> get_config('delay') (400, 1) >>> get_config('exception_msg') ('I really failed seriously', 1) >>> get_config('error_code') (404, 1) """ param = SSMParameter(os.environ['FAILURE_INJECTION_PARAM']) try: value = json.loads(param.value) if not value["isEnabled"]: return 0, 0 return value[config_key], value.get('rate', 1) except InvalidParameterError as ex: # key does not exist in SSM raise InvalidParameterError("{} is not a valid SSM config".format(ex)) except KeyError as ex: # not a valid Key in the SSM configuration raise KeyError("key {} not valid or found in SSM config".format(ex))
def test_creation(self): """ Test regular creation """ # single string param = SSMParameter("my_param") self.assertTrue(param._with_decryption) self.assertIsNone(param._max_age) self.assertIsNone(param._last_refresh_time) # invalid params with self.assertRaises(TypeError): SSMParameter() # pylint: disable=no-value-for-parameter with self.assertRaises(ValueError): SSMParameter(None) group = SSMParameterGroup() param = group.parameter("my_param") with self.assertRaises(TypeError): group.parameter() # pylint: disable=no-value-for-parameter
def test_creation(self): # single string cache = SSMParameter("my_param") self.assertEqual(1, len(cache._names)) self.assertTrue(cache._with_decryption) self.assertIsNone(cache._max_age) self.assertIsNone(cache._last_refresh_time) # list of params cache = SSMParameter(["my_param_1", "my_param_2"]) self.assertEqual(2, len(cache._names)) # invalid params with self.assertRaises(ValueError): SSMParameter() with self.assertRaises(ValueError): SSMParameter(None) with self.assertRaises(ValueError): SSMParameter([])
def test_main_lambda_handler(self): cache = SSMParameter("my_param") def lambda_handler(event, context): secret_value = cache.value() return 'Hello from Lambda with secret %s' % secret_value lambda_handler(None, None)
def test_string_list(self): """ Test StringList expiration """ param = SSMParameter("my_params_list") values = param.value self.assertTrue(isinstance(values, list)) self.assertEqual(len(values), self.PARAM_LIST_COUNT) for value in values: self.assertEqual(value, self.PARAM_VALUE)
def test_main_with_explicit_refresh(self): cache = SSMParameter("my_param") # will not expire class InvalidCredentials(Exception): pass def do_something(): my_value = cache.value() if my_value == self.PARAM_VALUE: raise InvalidCredentials() try: do_something() except InvalidCredentials: # manually update value self._create_params(["my_param"], "new_value") cache.refresh() # force refresh do_something() # won't fail anymore
def test_main_with_explicit_refresh(self): """ Test explicit refresh case """ param = SSMParameter("my_param") # will not expire class InvalidCredentials(Exception): """ Mock exception class """ def do_something(): """ Raise an exception until the value has changed """ my_value = param.value if my_value == self.PARAM_VALUE: raise InvalidCredentials() try: do_something() except InvalidCredentials: # manually update value self._create_params(["my_param"], "new_value") param.refresh() # force refresh do_something() # won't fail anymore
def _setUp(self, class_name, test_name): os.environ['CHAOS_PARAM'] = SSM_CONFIG_FILE session = boto3.Session() dir_name = os.path.join(self.PLACEBO_PATH, class_name, test_name) pill = placebo.attach(session, data_path=os.path.join(self.PLACEBO_PATH, class_name, test_name)) if PLACEBO_MODE == "record": try: os.makedirs(dir_name) except FileExistsError: print("Directory already exists") print("Recording") pill.record() else: pill.playback() self.ssm_client = session.client('ssm') SSMParameter.set_ssm_client(self.ssm_client)
def test_main_lambda_handler(self): """ Test simple AWS Lambda handler """ cache = SSMParameter("my_param") def lambda_handler(event, context): """ Simple Lambda handler that just prints a string """ print(event, context) secret_value = cache.value return 'Hello from Lambda with secret %s' % secret_value return_value = lambda_handler(None, None) expected_value = 'Hello from Lambda with secret %s' % self.PARAM_VALUE self.assertEqual(return_value, expected_value)
def get_config(config_key): param = SSMParameter(os.environ['FAILURE_INJECTION_PARAM']) try: value = json.loads(param.value) if not value["isEnabled"]: return 0, 1 return value[config_key], value.get('rate', 1) except InvalidParameterError as e: # key does not exist in SSM raise InvalidParameterError("{} does not exist in SSM".format(e)) except KeyError as e: # not a valid Key in the SSM configuration raise KeyError( "{} is not a valid Key in the SSM configuration".format(e))
def test_versions_unexisting(self): """ Test non existing version """ method_name = sys._getframe().f_code.co_name self._setUp(method_name) name = method_name self._create_or_update_param(name) param = SSMParameter("%s:10" % name) with self.assertRaises(InvalidParameterError): print(param.value) self._delete_param(name)
def test_select_versions(self): """ Test version selection """ method_name = sys._getframe().f_code.co_name self._setUp(method_name) name = method_name self._create_or_update_param(name) param = SSMParameter("%s:1" % name) self.assertEqual(param.value, self.PARAM_VALUE) self.assertEqual(param.version, 1) # this will update the value and create version 2 self._create_or_update_param(name, self.PARAM_VALUE_V2) param.refresh() self.assertEqual(param.value, self.PARAM_VALUE) self.assertEqual(param.version, 1) self._delete_param(name)
def test_update_versions(self): """ Test version update """ method_name = sys._getframe().f_code.co_name self._setUp(method_name) name = method_name self._create_or_update_param(name) param = SSMParameter(name) self.assertEqual(param.version, 1) self.assertEqual(param.value, self.PARAM_VALUE) # this will update the value and create version 2 self._create_or_update_param(name, self.PARAM_VALUE_V2) param.refresh() # refreshing should give you version 2 self.assertEqual(param.version, 2) self.assertEqual(param.value, self.PARAM_VALUE_V2) self._delete_param(name)
def test_main_with_multiple_params(self): cache = SSMParameter(["my_param_1", "my_param_2", "my_param_3"]) # one by one my_value_1 = cache.value("my_param_1") my_value_2 = cache.value("my_param_2") my_value_3 = cache.value("my_param_3") self.assertEqual(my_value_1, self.PARAM_VALUE) self.assertEqual(my_value_2, self.PARAM_VALUE) self.assertEqual(my_value_3, self.PARAM_VALUE) with self.assertRaises(TypeError): cache.value() # name is required # or all together my_value_1, my_value_2, my_value_3 = cache.values() self.assertEqual(my_value_1, self.PARAM_VALUE) self.assertEqual(my_value_2, self.PARAM_VALUE) self.assertEqual(my_value_3, self.PARAM_VALUE) # or a subset my_value_1, my_value_2 = cache.values(["my_param_1", "my_param_2"]) self.assertEqual(my_value_1, self.PARAM_VALUE) self.assertEqual(my_value_2, self.PARAM_VALUE)
def create(cls, project, stage_name, param_name, param_value, param_type, param_desc=None, overwrite=False, tags={}): param_path = create_param_path(project, stage_name, param_name) if not param_value: return None if param_type not in VALID_PARAM_TYPES: raise ParamCreateError( "Invalid parameter type: {}".format(param_type)) try: tag_list = [{"Key": k, "Value": v} for k, v in tags.items()] param_resp = ssm.put_parameter(Name=param_path, Description=param_desc, Value=param_value, Type=param_type, Overwrite=overwrite) if len(tag_list): tag_resp = ssm.add_tags_to_resource(ResourceType="Parameter", ResourceId=param_path, Tags=tag_list) except ssm.exceptions.ClientError as e: raise ParamCreateError(str(e)) ssm_param = SSMParameter(param_path) param = Param(ssm_param) if not param.exists(): raise ParamCreateError( "Something went wrong creating {}".format(param_path)) return param
def test_main_with_expiration(self): cache = SSMParameter("my_param", max_age=300) # 5 minutes expiration time my_value = cache.value() self.assertEqual(my_value, self.PARAM_VALUE)
def test_main(self): cache = SSMParameter("my_param") my_value = cache.value() self.assertEqual(my_value, self.PARAM_VALUE)
def test_unexisting(self): cache = SSMParameter("my_param_invalid_name") with self.assertRaises(InvalidParam): cache.value()
import boto3 from pathlib import Path from ssm_cache import SSMParameterGroup, SSMParameter, InvalidParameterError VALID_PARAM_TYPES = ["String", "SecureString", "StringList"] # make sure we're using the same client as the SSMParameter objects ssm = boto3.client('ssm') SSMParameter.set_ssm_client(ssm) def get_stages(project): project_path = (Path("/") / project).as_posix() group = SSMParameterGroup(base_path=project_path) param_paths = [Path(x.full_name) for x in group.parameters("/")] stage_names = set(p.parts[2] for p in param_paths) return [Stage(project, x) for x in stage_names] def create_param_path(project, stage_name, param_name): return (Path("/") / project / stage_name / param_name).as_posix() class ParameterNotFound(Exception): pass class ParamSchemaValidationError(Exception): def __init__(self, message=None, errors=[]): super(ParamSchemaValidationError, self).__init__(message) self.errors = errors
def test_not_configured(self): cache = SSMParameter(["param_1", "param_2"]) with self.assertRaises(InvalidParam): cache.value("param_3")
def test_main_without_encryption(self): cache = SSMParameter("my_param", with_decryption=False) my_value = cache.value() self.assertEqual(my_value, self.PARAM_VALUE)
def setUp(self): names = ["my_param", "my_grouped_param"] self._create_params(names) self.cache = SSMParameter("my_param") self.group = SSMParameterGroup() self.grouped_param = self.group.parameter("my_grouped_param")