diff --git a/aws_lambda_builders/workflows/dotnet_clipackage/actions.py b/aws_lambda_builders/workflows/dotnet_clipackage/actions.py index 2f9f31601..6e07546f7 100644 --- a/aws_lambda_builders/workflows/dotnet_clipackage/actions.py +++ b/aws_lambda_builders/workflows/dotnet_clipackage/actions.py @@ -28,9 +28,21 @@ class GlobalToolInstallAction(BaseAction): DESCRIPTION = "Install or update the Amazon.Lambda.Tools .NET Core Global Tool." PURPOSE = Purpose.COMPILE_SOURCE - def __init__(self, subprocess_dotnet): + def __init__(self, subprocess_dotnet, runtime=None): super(GlobalToolInstallAction, self).__init__() self.subprocess_dotnet = subprocess_dotnet + self.runtime = runtime + + def _existing_tool_available(self): + """ + Returns True if a working Amazon.Lambda.Tools is already available on the PATH + (e.g. pre-installed in the SAM build image). + """ + try: + self.subprocess_dotnet.run(["lambda", "help"]) + return True + except DotnetCLIExecutionError: + return False def execute(self): # run Amazon.Lambda.Tools update in sync block in case build is triggered in parallel @@ -42,6 +54,15 @@ def execute(self): LOG.info("Skipping to update Amazon.Lambda.Tools install/update, since it is updated recently") return + # Amazon.Lambda.Tools 7.0.0 dropped support for dotnet6 (EOL runtime), so + # installing/updating to latest breaks dotnet6 builds. If a working tool is + # already available (e.g. pre-installed in the SAM build image), use it as is. + # Deliberately not setting __tools_installed here so other runtimes in the + # same process still install/update to latest as before. + if self.runtime == "dotnet6" and self._existing_tool_available(): + LOG.info("Skipping Amazon.Lambda.Tools install/update for dotnet6; using the pre-installed version") + return + try: LOG.debug("Installing Amazon.Lambda.Tools Global Tool") self.subprocess_dotnet.run(["tool", "install", "-g", "Amazon.Lambda.Tools", "--ignore-failed-sources"]) diff --git a/aws_lambda_builders/workflows/dotnet_clipackage/workflow.py b/aws_lambda_builders/workflows/dotnet_clipackage/workflow.py index 9a5e5f49a..19ddc9b4e 100644 --- a/aws_lambda_builders/workflows/dotnet_clipackage/workflow.py +++ b/aws_lambda_builders/workflows/dotnet_clipackage/workflow.py @@ -29,7 +29,7 @@ def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtim options = kwargs["options"] if "options" in kwargs else {} subprocess_dotnetcli = SubprocessDotnetCLI(os_utils=OSUtils()) - dotnetcli_install = GlobalToolInstallAction(subprocess_dotnet=subprocess_dotnetcli) + dotnetcli_install = GlobalToolInstallAction(subprocess_dotnet=subprocess_dotnetcli, runtime=runtime) dotnetcli_deployment = RunPackageAction( source_dir, diff --git a/tests/unit/workflows/dotnet_clipackage/test_actions.py b/tests/unit/workflows/dotnet_clipackage/test_actions.py index c9e54a518..5a12a0a7f 100644 --- a/tests/unit/workflows/dotnet_clipackage/test_actions.py +++ b/tests/unit/workflows/dotnet_clipackage/test_actions.py @@ -62,6 +62,43 @@ def test_global_tool_parallel(self): ["tool", "install", "-g", "Amazon.Lambda.Tools", "--ignore-failed-sources"] ) + def test_dotnet6_skips_install_when_tool_preinstalled(self): + # `dotnet lambda help` succeeds -> a working tool is already available (e.g. SAM build image) + action = GlobalToolInstallAction(self.subprocess_dotnet, runtime="dotnet6") + action.execute() + self.subprocess_dotnet.run.assert_called_once_with(["lambda", "help"]) + + def test_dotnet6_installs_when_no_tool_preinstalled(self): + # `dotnet lambda help` fails -> no tool available, fall back to normal install + self.subprocess_dotnet.run.side_effect = [DotnetCLIExecutionError(message="No tool"), None] + action = GlobalToolInstallAction(self.subprocess_dotnet, runtime="dotnet6") + action.execute() + self.subprocess_dotnet.run.assert_any_call(["lambda", "help"]) + self.subprocess_dotnet.run.assert_any_call( + ["tool", "install", "-g", "Amazon.Lambda.Tools", "--ignore-failed-sources"] + ) + + def test_other_runtimes_do_not_probe_for_preinstalled_tool(self): + action = GlobalToolInstallAction(self.subprocess_dotnet, runtime="dotnet8") + action.execute() + self.subprocess_dotnet.run.assert_called_once_with( + ["tool", "install", "-g", "Amazon.Lambda.Tools", "--ignore-failed-sources"] + ) + + def test_dotnet8_after_dotnet6_skip_still_installs(self): + # dotnet6 skips because a working tool is pre-installed, but it must NOT mark the + # tool as installed for the process - a subsequent dotnet8 build still installs + # or updates to latest as before + dotnet6_action = GlobalToolInstallAction(self.subprocess_dotnet, runtime="dotnet6") + dotnet6_action.execute() + self.subprocess_dotnet.reset_mock() + + dotnet8_action = GlobalToolInstallAction(self.subprocess_dotnet, runtime="dotnet8") + dotnet8_action.execute() + self.subprocess_dotnet.run.assert_called_once_with( + ["tool", "install", "-g", "Amazon.Lambda.Tools", "--ignore-failed-sources"] + ) + class TestRunPackageAction(TestCase): @patch("aws_lambda_builders.workflows.dotnet_clipackage.dotnetcli.SubprocessDotnetCLI")