diff --git a/tests/unit/test_container_to_args_secrets.py b/tests/unit/test_container_to_args_secrets.py index b82f0a3..55225f2 100644 --- a/tests/unit/test_container_to_args_secrets.py +++ b/tests/unit/test_container_to_args_secrets.py @@ -1,7 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 +import os import unittest +from parameterized import parameterized + from podman_compose import container_to_args from .test_container_to_args import create_compose_mock @@ -89,3 +92,317 @@ async def test_pass_secret_as_env_variable_no_external(self): with self.assertRaises(ValueError) as context: await container_to_args(c, cnt) self.assertIn('ERROR: unparsable secret: ', str(context.exception)) + + @parameterized.expand([ + ( + "secret_no_name", + {"my_secret": "my_secret_name", "external": "true"}, + {}, # must have a name + ), + ( + "no_secret_name_in_declared_secrets", + {}, # must have a name + { + "source": "my_secret_name", + }, + ), + ( + "secret_name_does_not_match_declared_secrets_name", + { + "wrong_name": "my_secret_name", + }, + { + "source": "name", # secret name must match the one in declared_secrets + }, + ), + ( + "secret_name_empty_string", + {"": "my_secret_name"}, + { + "source": "", # can not be empty string + }, + ), + ]) + async def test_secret_name(self, test_name, declared_secrets, add_to_minimal_container): + c = create_compose_mock() + c.declared_secrets = declared_secrets + + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [add_to_minimal_container] + + with self.assertRaises(ValueError) as context: + await container_to_args(c, cnt) + self.assertIn('ERROR: undeclared secret: ', str(context.exception)) + + async def test_secret_string_no_external_name_in_declared_secrets(self): + c = create_compose_mock() + c.declared_secrets = {"my_secret_name": {"external": "true"}} + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + "my_secret_name", + ] + args = await container_to_args(c, cnt) + self.assertEqual( + args, + [ + "--name=project_name_service_name1", + "-d", + "--network=bridge", + "--network-alias=service_name", + "--secret", + "my_secret_name", + "busybox", + ], + ) + + async def test_secret_string_options_external_name_in_declared_secrets(self): + c = create_compose_mock() + c.declared_secrets = { + "my_secret_name": { + "external": "true", + "name": "my_secret_name", + } + } + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + { + "source": "my_secret_name", + "target": "my_secret_name", + "uid": "103", + "gid": "103", + "mode": "400", + } + ] + + with self.assertLogs() as cm: + args = await container_to_args(c, cnt) + self.assertEqual(len(cm.output), 1) + self.assertEqual('That is un-supported and a no-op and is ignored.' in cm.output[0], True) + self.assertEqual('my_secret_name' in cm.output[0], True) + + self.assertEqual( + args, + [ + "--name=project_name_service_name1", + "-d", + "--network=bridge", + "--network-alias=service_name", + "--secret", + "my_secret_name,uid=103,gid=103,mode=400", + "busybox", + ], + ) + + async def test_secret_string_external_name_in_declared_secrets_does_not_match_secret(self): + c = create_compose_mock() + c.declared_secrets = { + "my_secret_name": { + "external": "true", + "name": "wrong_secret_name", + } + } + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + "my_secret_name", + ] + + with self.assertRaises(ValueError) as context: + await container_to_args(c, cnt) + self.assertIn('ERROR: Custom name/target reference ', str(context.exception)) + + async def test_secret_target_does_not_match_secret_name_secret_type_not_env(self): + c = create_compose_mock() + c.declared_secrets = { + "my_secret_name": { + "external": "true", + } + } + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + { + "source": "my_secret_name", + "target": "does_not_equal_secret_name", + "type": "does_not_equal_env", + } + ] + + with self.assertRaises(ValueError) as context: + await container_to_args(c, cnt) + self.assertIn('ERROR: Custom name/target reference ', str(context.exception)) + + async def test_secret_target_does_not_match_secret_name_secret_type_env(self): + c = create_compose_mock() + c.declared_secrets = { + "my_secret_name": { + "external": "true", + } + } + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + {"source": "my_secret_name", "target": "does_not_equal_secret_name", "type": "env"} + ] + + args = await container_to_args(c, cnt) + self.assertEqual( + args, + [ + "--name=project_name_service_name1", + "-d", + "--network=bridge", + "--network-alias=service_name", + "--secret", + "my_secret_name,type=env,target=does_not_equal_secret_name", + "busybox", + ], + ) + + async def test_secret_target_matches_secret_name_secret_type_not_env(self): + c = create_compose_mock() + c.declared_secrets = { + "my_secret_name": { + "external": "true", + } + } + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + {"source": "my_secret_name", "target": "my_secret_name", "type": "does_not_equal_env"} + ] + + with self.assertLogs() as cm: + args = await container_to_args(c, cnt) + self.assertEqual(len(cm.output), 1) + self.assertEqual('That is un-supported and a no-op and is ignored.' in cm.output[0], True) + self.assertEqual('my_secret_name' in cm.output[0], True) + + self.assertEqual( + args, + [ + "--name=project_name_service_name1", + "-d", + "--network=bridge", + "--network-alias=service_name", + "--secret", + "my_secret_name,type=does_not_equal_env", + "busybox", + ], + ) + + @parameterized.expand([ + ( + "no_secret_target", + { + "file_secret": { + "file": "./my_secret", + } + }, + "file_secret", + "/test_dirname/my_secret:/run/secrets/file_secret:ro,rprivate,rbind", + ), + ( + "custom_target_name", + { + "file_secret": { + "file": "./my_secret", + } + }, + { + "source": "file_secret", + "target": "custom_name", + }, + "/test_dirname/my_secret:/run/secrets/custom_name:ro,rprivate,rbind", + ), + ( + "no_custom_target_name", + { + "file_secret": { + "file": "./my_secret", + } + }, + { + "source": "file_secret", + }, + "/test_dirname/my_secret:/run/secrets/file_secret:ro,rprivate,rbind", + ), + ( + "custom_location", + { + "file_secret": { + "file": "./my_secret", + } + }, + { + "source": "file_secret", + "target": "/etc/custom_location", + }, + "/test_dirname/my_secret:/etc/custom_location:ro,rprivate,rbind", + ), + ]) + async def test_file_secret( + self, test_name, declared_secrets, add_to_minimal_container, volume_ref + ): + c = create_compose_mock() + c.declared_secrets = declared_secrets + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [add_to_minimal_container] + args = await container_to_args(c, cnt) + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + self.assertEqual( + args, + [ + "--name=project_name_service_name1", + "-d", + "--network=bridge", + "--network-alias=service_name", + "--volume", + base_dir + volume_ref, + "busybox", + ], + ) + + async def test_file_secret_unused_params_warning(self): + c = create_compose_mock() + c.declared_secrets = { + "file_secret": { + "file": "./my_secret", + } + } + cnt = get_minimal_container() + cnt["_service"] = "test-service" + cnt["secrets"] = [ + { + "source": "file_secret", + "target": "unused_params_warning", + "uid": "103", + "gid": "103", + "mode": "400", + } + ] + + base_dir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + with self.assertLogs() as cm: + args = await container_to_args(c, cnt) + self.assertEqual(len(cm.output), 1) + self.assertEqual('with uid, gid, or mode.' in cm.output[0], True) + self.assertEqual('unused_params_warning' in cm.output[0], True) + + self.assertEqual( + args, + [ + "--name=project_name_service_name1", + "-d", + "--network=bridge", + "--network-alias=service_name", + "--volume", + base_dir + + "/test_dirname/my_secret:/run/secrets/unused_params_warning:ro,rprivate,rbind", + "busybox", + ], + )