diff --git a/data_safe_haven/commands/sre.py b/data_safe_haven/commands/sre.py index 8c3e0b5cdc..2cccfb90d0 100644 --- a/data_safe_haven/commands/sre.py +++ b/data_safe_haven/commands/sre.py @@ -50,6 +50,7 @@ def deploy( pulumi_config = DSHPulumiConfig.from_remote_or_create( context, encrypted_key=None, projects={} ) + sre_config = SREConfig.from_remote_by_name(context, name) # Check whether current IP address is authorised to take administrator actions @@ -68,6 +69,7 @@ def deploy( pulumi_config=pulumi_config, create_project=True, ) + # Set Azure options stack.add_option( "azure-native:location", sre_config.azure.location, replace=False diff --git a/data_safe_haven/infrastructure/project_manager.py b/data_safe_haven/infrastructure/project_manager.py index eca352b28b..5153a74a54 100644 --- a/data_safe_haven/infrastructure/project_manager.py +++ b/data_safe_haven/infrastructure/project_manager.py @@ -119,27 +119,45 @@ def stack(self) -> automation.Stack: """Load the Pulumi stack, creating if needed.""" if not self._stack: self.logger.debug(f"Creating/loading stack [green]{self.stack_name}[/].") + # Note: `create_or_select_stack` is not used here because we need to know whether the stack exists or not. + # There is no way to check if a stack exists other than trying to `create_stack` or `select_stack` and catching the error. + # `create_or_select_stack` never generates an error, and doesn't tell us whether the stack was created or selected. + # When creating a stack, it generates a new encryption key rather than using the project's key, so we need to set the key + # manually after creating the stack. try: - self._stack = automation.create_or_select_stack( + self._stack = automation.select_stack( opts=automation.LocalWorkspaceOptions( env_vars=self.account.env, project_settings=self.project_settings, - secrets_provider=self.context.pulumi_secrets_provider_url, stack_settings={self.stack_name: self.stack_settings}, + secrets_provider=self.context.pulumi_secrets_provider_url, ), program=self.program, project_name=self.project_name, stack_name=self.stack_name, ) - self.logger.info(f"Loaded stack [green]{self.stack_name}[/].") - # Ensure encrypted key is stored in the Pulumi configuration - self.update_dsh_pulumi_encrypted_key(self._stack.workspace) - # Ensure workspace plugins are installed - self.install_plugins(self._stack.workspace) - except automation.CommandError as exc: - self.log_exception(exc) - msg = f"Could not load Pulumi stack {self.stack_name}." - raise DataSafeHavenPulumiError(msg) from exc + except automation.CommandError: + try: + self._stack = automation.create_stack( + opts=automation.LocalWorkspaceOptions( + env_vars=self.account.env, + project_settings=self.project_settings, + secrets_provider=self.context.pulumi_secrets_provider_url, + ), + program=self.program, + project_name=self.project_name, + stack_name=self.stack_name, + ) + self._stack.workspace.save_stack_settings( + self.stack_name, self.stack_settings + ) + except automation.CommandError as exc: + self.log_exception(exc) + msg = f"Could not create Pulumi stack {self.stack_name}." + raise DataSafeHavenPulumiError(msg) from exc + self.logger.info(f"Loaded stack [green]{self.stack_name}[/].") + # Ensure workspace plugins are installed + self.install_plugins(self._stack.workspace) return self._stack def add_option(self, name: str, value: str, *, replace: bool) -> None: @@ -425,16 +443,6 @@ def update_dsh_pulumi_project(self) -> None: } self.pulumi_project.stack_config = all_config_dict - def update_dsh_pulumi_encrypted_key(self, workspace: automation.Workspace) -> None: - """Update encrypted key in the DSHPulumiProject object""" - stack_key = workspace.stack_settings(stack_name=self.stack_name).encrypted_key - - if not self.pulumi_config.encrypted_key: - self.pulumi_config.encrypted_key = stack_key - elif self.pulumi_config.encrypted_key != stack_key: - msg = "Stack encrypted key does not match project encrypted key" - raise DataSafeHavenPulumiError(msg) - class SREProjectManager(ProjectManager): """Interact with an SRE using Pulumi"""