diff --git a/data_safe_haven/administration/users/active_directory_users.py b/data_safe_haven/administration/users/active_directory_users.py index d4aea2038d..6832c40f71 100644 --- a/data_safe_haven/administration/users/active_directory_users.py +++ b/data_safe_haven/administration/users/active_directory_users.py @@ -7,7 +7,7 @@ from data_safe_haven.exceptions import DataSafeHavenActiveDirectoryError from data_safe_haven.external import AzureApi from data_safe_haven.functions import b64encode -from data_safe_haven.infrastructure import PulumiAccount, SHMStackManager +from data_safe_haven.infrastructure import SHMStackManager from data_safe_haven.utility import FileReader, LoggingSingleton from .research_user import ResearchUser @@ -23,7 +23,6 @@ def __init__( **kwargs: Any, ) -> None: super().__init__(*args, **kwargs) - PulumiAccount(config).handle_login() shm_stack = SHMStackManager(config) self.azure_api = AzureApi(config.subscription_name) self.logger = LoggingSingleton() diff --git a/data_safe_haven/administration/users/guacamole_users.py b/data_safe_haven/administration/users/guacamole_users.py index 8a553f3d1d..c8791db8fa 100644 --- a/data_safe_haven/administration/users/guacamole_users.py +++ b/data_safe_haven/administration/users/guacamole_users.py @@ -4,7 +4,7 @@ from data_safe_haven.config import Config from data_safe_haven.external import AzurePostgreSQLDatabase -from data_safe_haven.infrastructure import PulumiAccount, SREStackManager +from data_safe_haven.infrastructure import SREStackManager from .research_user import ResearchUser @@ -12,7 +12,6 @@ class GuacamoleUsers: def __init__(self, config: Config, sre_name: str, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) - PulumiAccount(config).handle_login() sre_stack = SREStackManager(config, sre_name) self.postgres_provisioner = AzurePostgreSQLDatabase( sre_stack.output("remote_desktop")["connection_db_name"], diff --git a/data_safe_haven/commands/deploy_shm.py b/data_safe_haven/commands/deploy_shm.py index 8ca7846918..f23d52afae 100644 --- a/data_safe_haven/commands/deploy_shm.py +++ b/data_safe_haven/commands/deploy_shm.py @@ -3,7 +3,7 @@ from data_safe_haven.exceptions import DataSafeHavenError from data_safe_haven.external import GraphApi from data_safe_haven.functions import password -from data_safe_haven.infrastructure import PulumiAccount, SHMStackManager +from data_safe_haven.infrastructure import SHMStackManager from data_safe_haven.provisioning import SHMProvisioningManager @@ -40,7 +40,6 @@ def deploy_shm( verification_record = graph_api.add_custom_domain(config.shm.fqdn) # Initialise Pulumi stack - PulumiAccount(config).handle_login() stack = SHMStackManager(config) # Set Azure options stack.add_option("azure-native:location", config.azure.location, replace=False) diff --git a/data_safe_haven/commands/deploy_sre.py b/data_safe_haven/commands/deploy_sre.py index 25ac7266ca..7f95227e17 100644 --- a/data_safe_haven/commands/deploy_sre.py +++ b/data_safe_haven/commands/deploy_sre.py @@ -5,11 +5,7 @@ ) from data_safe_haven.external import GraphApi from data_safe_haven.functions import alphanumeric, bcrypt_salt -from data_safe_haven.infrastructure import ( - PulumiAccount, - SHMStackManager, - SREStackManager, -) +from data_safe_haven.infrastructure import SHMStackManager, SREStackManager from data_safe_haven.provisioning import SREProvisioningManager from data_safe_haven.utility import DatabaseSystem, SoftwarePackageCategory @@ -52,7 +48,6 @@ def deploy_sre( ) # Initialise Pulumi stack - PulumiAccount(config).handle_login() shm_stack = SHMStackManager(config) stack = SREStackManager(config, sre_name, graph_api_token=graph_api.token) # Set Azure options diff --git a/data_safe_haven/commands/teardown_shm.py b/data_safe_haven/commands/teardown_shm.py index 61fb15f7f7..c05bf81ca4 100644 --- a/data_safe_haven/commands/teardown_shm.py +++ b/data_safe_haven/commands/teardown_shm.py @@ -4,7 +4,7 @@ DataSafeHavenError, DataSafeHavenInputError, ) -from data_safe_haven.infrastructure import PulumiAccount, SHMStackManager +from data_safe_haven.infrastructure import SHMStackManager def teardown_shm() -> None: @@ -15,7 +15,6 @@ def teardown_shm() -> None: # Remove infrastructure deployed with Pulumi try: - PulumiAccount(config).handle_login() stack = SHMStackManager(config) stack.teardown() except Exception as exc: diff --git a/data_safe_haven/commands/teardown_sre.py b/data_safe_haven/commands/teardown_sre.py index f21ca878c9..6c51ca856a 100644 --- a/data_safe_haven/commands/teardown_sre.py +++ b/data_safe_haven/commands/teardown_sre.py @@ -6,7 +6,7 @@ ) from data_safe_haven.external import GraphApi from data_safe_haven.functions import alphanumeric -from data_safe_haven.infrastructure import PulumiAccount, SREStackManager +from data_safe_haven.infrastructure import SREStackManager def teardown_sre(name: str) -> None: @@ -28,7 +28,6 @@ def teardown_sre(name: str) -> None: # Remove infrastructure deployed with Pulumi try: - PulumiAccount(config).handle_login() stack = SREStackManager(config, sre_name, graph_api_token=graph_api.token) if stack.work_dir.exists(): stack.teardown() diff --git a/data_safe_haven/infrastructure/stack_manager.py b/data_safe_haven/infrastructure/stack_manager.py index 92f26822af..4815a1f574 100644 --- a/data_safe_haven/infrastructure/stack_manager.py +++ b/data_safe_haven/infrastructure/stack_manager.py @@ -2,14 +2,12 @@ import logging import pathlib import shutil -import subprocess import time from contextlib import suppress from importlib import metadata from shutil import which from typing import Any -import typer from pulumi import automation from data_safe_haven.config import Config @@ -26,12 +24,10 @@ class PulumiAccount: def __init__(self, config: Config): self.cfg = config self.env_: dict[str, Any] | None = None - self.logger = LoggingSingleton() path = which("pulumi") if path is None: msg = "Unable to find Pulumi CLI executable in your path.\nPlease ensure that Pulumi is installed" raise DataSafeHavenPulumiError(msg) - self.path = path # Ensure Azure CLI account is correct # This will be needed to populate env @@ -50,69 +46,10 @@ def env(self) -> dict[str, Any]: "AZURE_STORAGE_ACCOUNT": self.cfg.backend.storage_account_name, "AZURE_STORAGE_KEY": str(backend_storage_account_keys[0].value), "AZURE_KEYVAULT_AUTH_VIA_CLI": "true", + "PULUMI_BACKEND_URL": f"azblob://{self.cfg.pulumi.storage_container_name}", } return self.env_ - def confirm(self) -> bool: - """Prompt user to confirm the Pulumi account is correct""" - # Because the who_am_i method requires a stack and workspace, it is difficult to - # do this with a minimal dummy stack which also works with the Azure backend. - # A subprocess call is used here. - try: - result = subprocess.check_output( - [self.path, "whoami", "--verbose"], - stderr=subprocess.PIPE, - encoding="utf8", - env=self.env, - ) - except subprocess.CalledProcessError as exc: - msg = f"Logging into Pulumi failed.\n{exc}\n{result}" - raise DataSafeHavenPulumiError(msg) from exc - - self.logger.info("Confirming Pulumi account details") - for line in result.splitlines(): - self.logger.info(line) - - if typer.confirm("Is this the Pulumi account you expect?"): - self.logger.info("confirmed") - return True - else: - return False - - def login(self) -> None: - """Login to Pulumi.""" - try: - result = subprocess.check_output( - [ - self.path, - "login", - f"azblob://{self.cfg.pulumi.storage_container_name}", - ], - stderr=subprocess.PIPE, - encoding="utf8", - env=self.env, - ) - self.logger.info(result) - except subprocess.CalledProcessError as exc: - msg = f"Logging into Pulumi failed.\n{exc}." - raise DataSafeHavenPulumiError(msg) from exc - - def handle_login(self) -> None: - """Ensure the user is using the DSH Pulumi backend""" - if not self.confirm(): - msg = ( - "Attempting to login to Pulumi account using" - f" container [green]{self.cfg.pulumi.storage_container_name}[/]" - f" in Azure storage account [green]{self.cfg.backend.storage_account_name}[/]" - ) - self.logger.info(msg) - self.login() - if not self.confirm(): - self.logger.error( - "Mismatch between expected Pulumi account and configuration" - ) - raise typer.Exit(1) - class StackManager: """Interact with infrastructure using Pulumi"""