Skip to content

Commit

Permalink
Merge pull request #470 from UW-GAC/feature/requester-pays-audit
Browse files Browse the repository at this point in the history
Add requester pays field and auditing checks
  • Loading branch information
amstilp authored Mar 9, 2024
2 parents 5649d3d + 9a966b3 commit b30e2ce
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Add customizeable `get_extra_detail_context_data` method for workspace adapters. Users can override this method to provide additional context data on a workspace detail page.
* Add filtering by workspace type to the admin interface.
* Set max_length for `ManagedGroup` and `Workspace` models to match what AnVIL allows.
* Track requester pays status for `Workspace` objects.

## 0.21.0 (2023-12-04)

Expand Down
2 changes: 1 addition & 1 deletion anvil_consortium_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.22.0.dev2"
__version__ = "0.22.0.dev3"
7 changes: 5 additions & 2 deletions anvil_consortium_manager/anvil_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def list_workspaces(self, fields=None):
else:
return self.auth_session.get(url, 200)

def get_workspace(self, workspace_namespace, workspace_name):
def get_workspace(self, workspace_namespace, workspace_name, fields=None):
"""Get information about a specific workspace on AnVIL.
Calls the Rawls /api/workspaces/{workspace_namespace}/{workspace_name} GET method.
Expand All @@ -250,7 +250,10 @@ def get_workspace(self, workspace_namespace, workspace_name):
requests.Response
"""
url = self.rawls_entry_point + "/api/workspaces/" + workspace_namespace + "/" + workspace_name
return self.auth_session.get(url, 200)
if fields:
return self.auth_session.get(url, 200, params={"fields": fields})
else:
return self.auth_session.get(url, 200)

def create_workspace(self, workspace_namespace, workspace_name, authorization_domains=[]):
"""Create a workspace on AnVIL.
Expand Down
14 changes: 13 additions & 1 deletion anvil_consortium_manager/audit/audit.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,14 +363,18 @@ class WorkspaceAudit(AnVILAudit):
ERROR_DIFFERENT_LOCK = "Workspace lock status does not match on AnVIL"
"""Error when the workspace.is_locked status does not match the lock status on AnVIL."""

ERROR_DIFFERENT_REQUESTER_PAYS = "Workspace bucket requester_pays status does not match on AnVIL"
"""Error when the workspace.is_locked status does not match the lock status on AnVIL."""

def run_audit(self):
"""Run an audit on Workspaces in the app."""
# Check the list of workspaces.
fields = [
"workspace.namespace",
"workspace.name",
"workspace.authorizationDomain",
"workspace.isLocked,accessLevel",
"workspace.isLocked",
"accessLevel",
]
response = AnVILAPIClient().list_workspaces(fields=",".join(fields))
workspaces_on_anvil = response.json()
Expand Down Expand Up @@ -408,6 +412,14 @@ def run_audit(self):
# Check lock status.
if workspace.is_locked != workspace_details["workspace"]["isLocked"]:
model_instance_result.add_error(self.ERROR_DIFFERENT_LOCK)
# Check is_requester_pays status. Unfortunately we have to make a separate API call.
response = AnVILAPIClient().get_workspace(
workspace.billing_project.name,
workspace.name,
fields=["bucketOptions"],
)
if workspace.is_requester_pays != response.json()["bucketOptions"]["requesterPays"]:
model_instance_result.add_error(self.ERROR_DIFFERENT_REQUESTER_PAYS)

self.add_result(model_instance_result)

Expand Down
1 change: 1 addition & 0 deletions anvil_consortium_manager/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class Meta:
"billing_project",
"name",
"authorization_domains",
"is_requester_pays",
"note",
)
widgets = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 5.0 on 2024-03-08 00:23

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('anvil_consortium_manager', '0016_max_length_limits'),
]

operations = [
migrations.AddField(
model_name='historicalworkspace',
name='is_requester_pays',
field=models.BooleanField(default=False, help_text='Indicator of whether the workspace is set to requester pays.'),
),
migrations.AddField(
model_name='workspace',
name='is_requester_pays',
field=models.BooleanField(default=False, help_text='Indicator of whether the workspace is set to requester pays.'),
),
]
6 changes: 5 additions & 1 deletion anvil_consortium_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,11 @@ class Workspace(TimeStampedModel):
help_text="Indicator of whether the workspace is locked or not.",
default=False,
)

is_requester_pays = models.BooleanField(
verbose_name="Requester pays",
help_text="Indicator of whether the workspace is set to requester pays.",
default=False,
)
history = HistoricalRecords()

class Meta:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@
></span>
</span>

{% if object.is_requester_pays %}
<span class="badge bg-warning text-dark"><span class="fa-solid fa-circle-dollar-to-slot me-2"></span>Requester pays
<span class="ms-2 fa-solid fa-circle-question"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
data-bs-title="You must select a billing project when listing files or using data in this workspace."
></span>
</span>
{% endif %}

{% if has_access or user.is_superuser %}
<a class="badge bg-light text-dark btn btn-light" href="{{ object.get_anvil_url }}" target="_blank" role="button">
<span class="fa-solid fa-arrow-up-right-from-square mx-1"></span>
Expand Down
Loading

0 comments on commit b30e2ce

Please sign in to comment.