Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c1429b4
Update to react 18
jjgriff93 Nov 10, 2022
02fb463
Updated all other packages
jjgriff93 Nov 10, 2022
91783d3
Return allowed_user_actions with all airlock api calls
jjgriff93 Nov 14, 2022
fb17d35
Merge branch 'main' into jjgriff93/2764-airlock_ui-support_multiple_r…
jjgriff93 Nov 14, 2022
334b6b0
Fixed migration
jjgriff93 Nov 15, 2022
ed63fa1
Dont show notCreated when loading
jjgriff93 Nov 15, 2022
215fd27
Fix status
jjgriff93 Nov 15, 2022
f448117
Fix api and add other reviewers hint
jjgriff93 Nov 15, 2022
978f7c4
Delete previous resource
jjgriff93 Nov 15, 2022
0f8c552
Merge branch 'main' into jjgriff93/2764-airlock_ui-support_multiple_r…
jjgriff93 Nov 15, 2022
29ba359
Update changelog
jjgriff93 Nov 15, 2022
e85a509
Fix tests
jjgriff93 Nov 16, 2022
9c1b624
Revise mock json object
jjgriff93 Nov 16, 2022
a5da38a
Merge branch 'main' into jjgriff93/2764-airlock_ui-support_multiple_r…
jjgriff93 Nov 18, 2022
a7d3a25
Rename review user resource methods
jjgriff93 Nov 28, 2022
6d35c58
Merge branch 'main' into jjgriff93/2764-airlock_ui-support_multiple_r…
jjgriff93 Nov 28, 2022
40ea599
Update changelog
jjgriff93 Nov 28, 2022
b3157e9
Fix migration test
jjgriff93 Nov 28, 2022
7b5ab24
Fix test review input
jjgriff93 Nov 28, 2022
d9774c4
Fix final test
jjgriff93 Nov 29, 2022
5ba5639
Formatting
jjgriff93 Nov 29, 2022
ae945ed
Add new tests
jjgriff93 Nov 29, 2022
85b1f64
Merge branch 'main' into jjgriff93/2764-airlock_ui-support_multiple_r…
jjgriff93 Nov 29, 2022
dec4561
Merge branch 'main' into jjgriff93/2764-airlock_ui-support_multiple_r…
jjgriff93 Nov 29, 2022
28679bc
Get power state separately
jjgriff93 Nov 29, 2022
2ba6d24
Merge branch 'jjgriff93/2764-airlock_ui-support_multiple_review_vms' …
jjgriff93 Nov 29, 2022
d37da69
Bump api
jjgriff93 Nov 29, 2022
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
## 0.8.0 (Unreleased)

**BREAKING CHANGES & MIGRATIONS**:
* The model for `reviewUserResources` in airlock requests has changed from being a list to a dictionary. A migration has been added to update your existing requests automatically; please make sure you run the migrations as part of updating your API and UI.
* Note that any in-flight requests that have review resources deployed will show `UNKNOWN[i]` for the user key of that resource and in the UI users will be prompted to deploy a new resource. [#2883](https://github.com/microsoft/AzureTRE/pull/2883)

FEATURES:
* Support review VMs for multiple reviewers for each airlock request [#2883](https://github.com/microsoft/AzureTRE/pull/2883)

ENHANCEMENTS:
* Remove Porter's Docker mixin as it's not in use ([#2889](https://github.com/microsoft/AzureTRE/pull/2889))
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.5.21"
__version__ = "0.5.22"
94 changes: 67 additions & 27 deletions api_app/api/routes/airlock.py

Large diffs are not rendered by default.

64 changes: 43 additions & 21 deletions api_app/api/routes/airlock_resource_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from starlette import status

from api.routes.resource_helpers import send_uninstall_message
from models.domain.user_resource import UserResource
from db.repositories.airlock_requests import AirlockRequestRepository
from db.repositories.resource_templates import ResourceTemplateRepository
from db.repositories.user_resources import UserResourceRepository
Expand Down Expand Up @@ -73,8 +74,9 @@ async def update_and_publish_event_airlock_request(
except Exception as e:
logging.error(f'Failed updating airlock_request item {airlock_request}: {e}')
# If the validation failed, the error was not related to the saving itself
if e.status_code == 400:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=strings.AIRLOCK_REQUEST_ILLEGAL_STATUS_CHANGE)
if hasattr(e, 'status_code'):
Comment thread
jjgriff93 marked this conversation as resolved.
if e.status_code == 400:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=strings.AIRLOCK_REQUEST_ILLEGAL_STATUS_CHANGE)
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=strings.STATE_STORE_ENDPOINT_NOT_RESPONDING)

if not new_status:
Expand Down Expand Up @@ -140,39 +142,59 @@ def enrich_requests_with_allowed_actions(requests: List[AirlockRequest], user: U
return enriched_requests


async def delete_review_user_resources(
async def delete_review_user_resource(
user_resource: UserResource,
user_resource_repo: UserResourceRepository,
workspace_service_repo: WorkspaceServiceRepository,
resource_template_repo: ResourceTemplateRepository,
operations_repo: OperationRepository,
user: User) -> Operation:
workspace_service = workspace_service_repo.get_workspace_service_by_id(workspace_id=user_resource.workspaceId,
service_id=user_resource.parentWorkspaceServiceId)

resource_template = resource_template_repo.get_template_by_name_and_version(
user_resource.templateName,
user_resource.templateVersion,
ResourceType.UserResource,
workspace_service.templateName)

logging.info(f"Deleting user resource {user_resource.id} in workspace service {workspace_service.id}")
operation = await send_uninstall_message(
resource=user_resource,
resource_repo=user_resource_repo,
operations_repo=operations_repo,
resource_type=ResourceType.UserResource,
resource_template_repo=resource_template_repo,
user=user,
resource_template=resource_template)
logging.info(f"Started operation {operation}")
return operation


async def delete_all_review_user_resources(
airlock_request: AirlockRequest,
user_resource_repo: UserResourceRepository,
workspace_service_repo: WorkspaceServiceRepository,
resource_template_repo: ResourceTemplateRepository,
operations_repo: OperationRepository,
user: User) -> List[Operation]:
operations: List[Operation] = []
for review_ur in airlock_request.reviewUserResources:
for review_ur in airlock_request.reviewUserResources.values():
user_resource = user_resource_repo.get_user_resource_by_id(
workspace_id=review_ur.workspaceId,
service_id=review_ur.workspaceServiceId,
resource_id=review_ur.userResourceId
)

workspace_service = workspace_service_repo.get_workspace_service_by_id(workspace_id=user_resource.workspaceId, service_id=user_resource.parentWorkspaceServiceId)

resource_template = resource_template_repo.get_template_by_name_and_version(
user_resource.templateName,
user_resource.templateVersion,
ResourceType.UserResource,
workspace_service.templateName)

logging.info(f"Deleting user resource {user_resource.id} in workspace service {workspace_service.id}")
operations.append(await send_uninstall_message(
resource=user_resource,
resource_repo=user_resource_repo,
operations_repo=operations_repo,
resource_type=ResourceType.UserResource,
operation = await delete_review_user_resource(
user_resource=user_resource,
user_resource_repo=user_resource_repo,
workspace_service_repo=workspace_service_repo,
resource_template_repo=resource_template_repo,
user=user,
resource_template=resource_template))
logging.info(f"Started operation {operations[-1]}")
operations_repo=operations_repo,
user=user
)
operations.append(operation)

logging.info(f"Started {len(operations)} operations on deleting user resources")
return operations
4 changes: 4 additions & 0 deletions api_app/api/routes/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ async def migrate_database(resources_repo=Depends(get_repository(ResourceReposit
num_updated = airlock_migration.add_created_by_and_rename_in_history()
migrations.append(Migration(issueNumber="2779", status=f'Renamed fields & updated {num_updated} airlock requests with createdBy'))

logging.info("PR 2883 - Support multiple reviewer VMs per Airlock request")
num_updated = airlock_migration.change_review_resources_to_dict()
migrations.append(Migration(issueNumber="XXXX", status=f'Updated {num_updated} airlock requests with new reviewUserResources format'))

return MigrationOutList(migrations=migrations)
except Exception as e:
logging.error("Failed to migrate database: %s", e, exc_info=True)
Expand Down
18 changes: 17 additions & 1 deletion api_app/db/migrations/airlock.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ def add_created_by_and_rename_in_history(self) -> int:
request['createdBy'] = request['updatedBy']

self.update_item_dict(request)
num_updated = num_updated + 1
num_updated += 1

return num_updated

def change_review_resources_to_dict(self) -> int:
num_updated = 0
for request in self.container.read_all_items():
# Only migrate if airlockReviewResources property present and is a list
if 'reviewUserResources' in request:
if type(request['reviewUserResources']) == list:
updated_review_resources = {}
for i, resource in enumerate(request['reviewUserResources']):
updated_review_resources['UNKNOWN' + str(i)] = resource

request['reviewUserResources'] = updated_review_resources
self.update_item_dict(request)
num_updated += 1

return num_updated
11 changes: 5 additions & 6 deletions api_app/db/repositories/airlock_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ def update_airlock_request(
request_files=request_files,
status_message=status_message,
airlock_review=airlock_review,
review_user_resource=review_user_resource)
review_user_resource=review_user_resource,
updated_by=updated_by)
try:
db_response = self.update_airlock_request_item(original_request, updated_request, updated_by, {"previousStatus": original_request.status})
except CosmosAccessConditionFailedError:
Expand Down Expand Up @@ -187,7 +188,8 @@ def _build_updated_request(
request_files: List[AirlockFile] = None,
status_message: str = None,
airlock_review: AirlockReview = None,
review_user_resource: AirlockReviewUserResource = None) -> AirlockRequest:
review_user_resource: AirlockReviewUserResource = None,
updated_by: User = None) -> AirlockRequest:
updated_request = copy.deepcopy(original_request)

if new_status is not None:
Expand All @@ -207,10 +209,7 @@ def _build_updated_request(
updated_request.reviews.append(airlock_review)

if review_user_resource is not None:
if updated_request.reviewUserResources is None:
updated_request.reviewUserResources = [review_user_resource]
else:
updated_request.reviewUserResources.append(review_user_resource)
updated_request.reviewUserResources[updated_by.id] = review_user_resource

return updated_request

Expand Down
7 changes: 4 additions & 3 deletions api_app/models/domain/airlock_request.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from typing import List
from enum import Enum
from typing import List, Dict

from models.domain.azuretremodel import AzureTREModel
from pydantic import Field, validator
from pydantic.schema import Optional
from resources import strings
from models.domain.azuretremodel import AzureTREModel


class AirlockRequestStatus(str, Enum):
Expand Down Expand Up @@ -95,7 +96,7 @@ class AirlockRequest(AzureTREModel):
statusMessage: Optional[str] = Field(title="Optional - contains additional information about the current status.")
reviews: Optional[List[AirlockReview]]
etag: Optional[str] = Field(title="_etag", alias="_etag")
reviewUserResources: List[AirlockReviewUserResource] = Field([], title="User resources created for Airlock Reviews")
reviewUserResources: Dict[str, AirlockReviewUserResource] = Field({}, title="User resources created for Airlock Reviews")

# SQL API CosmosDB saves ETag as an escaped string: https://github.com/microsoft/AzureTRE/issues/1931
@validator("etag", pre=True)
Expand Down
4 changes: 2 additions & 2 deletions api_app/models/domain/user_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

class UserResource(Resource):
"""
Workspace service request
User resource
"""
workspaceId: str = Field("", title="Workspace ID", description="Service target Workspace id")
ownerId: str = Field("", title="Owner of the user resource")
parentWorkspaceServiceId: str = Field("", title="Parent Workspace Service ID", description="Service target Workspace Service id")
azureStatus: dict = Field({}, title="Azure Status", description="Azure status, varies per user resoruce")
azureStatus: dict = Field({}, title="Azure Status", description="Azure status, varies per user resource")
resourceType = ResourceType.UserResource
2 changes: 1 addition & 1 deletion api_app/models/schemas/airlock_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class AirlockRequestInResponse(BaseModel):
class Config:
schema_extra = {
"example": {
"airlock_request": get_sample_airlock_request("933ad738-7265-4b5f-9eae-a1a62928772e", "121e921f-a4aa-44b3-90a9-e8da030495ef")
"airlockRequest": get_sample_airlock_request("933ad738-7265-4b5f-9eae-a1a62928772e", "121e921f-a4aa-44b3-90a9-e8da030495ef")
}
}

Expand Down
1 change: 0 additions & 1 deletion api_app/service_bus/resource_request_sender.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import json


from db.repositories.resources import ResourceRepository
from db.repositories.resource_templates import ResourceTemplateRepository
from service_bus.helpers import send_deployment_message, update_resource_for_step
Expand Down
Loading