Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
ENHANCEMENTS:
* Specify default_outbound_access_enabled = false setting for all subnets ([#4757](https://github.com/microsoft/AzureTRE/pull/4757))
* Pin all GitHub Actions workflow steps to full commit SHAs to prevent supply chain attacks plus update to latest releases ([#4886](https://github.com/microsoft/AzureTRE/pull/4886))
* Pass `isEnabled` in the Service Bus resource request message payload and expose it as the `is_enabled` Porter parameter, enabling bundle authors to stop or deallocate Azure resources on disable ([#4889](https://github.com/microsoft/AzureTRE/issues/4889))

## (0.28.0) (March 2, 2026)
**BREAKING CHANGES**
Expand Down
2 changes: 1 addition & 1 deletion api_app/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.25.15"
__version__ = "0.25.16"
3 changes: 2 additions & 1 deletion api_app/models/domain/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ def get_resource_request_message_payload(self, operation_id: str, step_id: str,
"id": self.id,
"name": self.templateName,
"version": self.templateVersion,
"parameters": self.properties
"parameters": self.properties,
"isEnabled": self.isEnabled
}

if self.resourceType == ResourceType.WorkspaceService:
Expand Down
9 changes: 9 additions & 0 deletions api_app/tests_ma/test_models/test_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,12 @@ def test_workspace_service_get_resource_request_message_payload_augments_payload
message_payload = workspace_service.get_resource_request_message_payload(OPERATION_ID, STEP_ID, RequestAction.Install)

assert message_payload["workspaceId"] == workspace_id


@pytest.mark.parametrize('is_enabled', [True, False])
def test_get_resource_request_message_payload_includes_is_enabled(is_enabled):
resource = Resource(templateName="", templateVersion="", isEnabled=is_enabled, etag="", properties={}, id="1234", resourceType=ResourceType.Workspace, resourcePath="test")

message_payload = resource.get_resource_request_message_payload(OPERATION_ID, STEP_ID, RequestAction.Install)

assert message_payload["isEnabled"] == is_enabled
16 changes: 16 additions & 0 deletions docs/tre-workspace-authors/authoring-workspace-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,22 @@ The required actions are the main two of CNAB spec:
* `install` - Deploys/repairs the workspace Azure resources, and must be **idempotent**
* `uninstall` - Tears down (deletes) the Azure resources of the workspace and its services

## Handling resource enable/disable

When a resource is enabled or disabled via the TRE API, the framework runs the `upgrade` action on the Porter bundle and passes the current value of `isEnabled` as the `is_enabled` parameter. Bundle authors can use this parameter to stop, deallocate, or remove Azure resources when a resource is disabled, and to restore them when re-enabled.

To opt in, declare the `is_enabled` parameter in your bundle's `porter.yaml`:

```yaml
parameters:
- name: is_enabled
type: boolean
default: true
description: "Indicates whether the resource is enabled. When false, cost-incurring Azure resources should be stopped or deleted."
```

Then reference it in your `upgrade` action and pass it through to Terraform (or any other provisioner) to handle lifecycle accordingly. It is up to the bundle author to decide the exact behaviour — for example, a VM bundle might delete the VM (but keep its disks) when `is_enabled` is `false`.

## Workspace service bundle manifests

Workspace service bundles are generated in the same way as workspace bundles.
Expand Down
2 changes: 1 addition & 1 deletion resource_processor/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.13.3"
__version__ = "0.13.4"
2 changes: 2 additions & 0 deletions resource_processor/helpers/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ def get_special_porter_param_value(config, parameter_name: str, msg_body):
return msg_body.get("parentWorkspaceServiceId") # not included in all messages
if parameter_name == "owner_id":
return msg_body.get("ownerId") # not included in all messages
if parameter_name == "is_enabled":
return msg_body.get("isEnabled")
if (value := config["bundle_params"].get(parameter_name.lower())) is not None:
return value
# Parameters that relate to the cloud type
Expand Down
6 changes: 5 additions & 1 deletion resource_processor/tests_rp/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ def test_get_special_porter_param_value():
msg_body = {
"workspaceId": "ws-123",
"parentWorkspaceServiceId": "parent-123",
"ownerId": "owner-123"
"ownerId": "owner-123",
"isEnabled": False
}

# Test ACR name extraction
Expand All @@ -320,6 +321,9 @@ def test_get_special_porter_param_value():
# Test owner ID
assert get_special_porter_param_value(config, "owner_id", msg_body) == "owner-123"

# Test is_enabled
assert get_special_porter_param_value(config, "is_enabled", msg_body) is False

# Test bundle params
assert get_special_porter_param_value(config, "custom_param", msg_body) == "custom_value"

Expand Down