Skip to content

Commit

Permalink
Added _x_tapis_tracking_id
Browse files Browse the repository at this point in the history
  • Loading branch information
NotChristianGarcia committed Nov 16, 2024
1 parent b5cf4dd commit 0fe830e
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 2 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
All notable changes to this project will be documented in this file.


## 1.7.1 - 2024-11-15
### Added
- Adding support for _x_tapis_tracking_id variable with validation. This sends the header when users call tapipy operations and specify the value in the call.

### Changed
- No change.

### Removed
- No change.


## 1.7.0 - 2024-09-13
### Added
- Poetry lock update
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "tapipy"
version = "1.7.0"
version = "1.7.1"
description = "Python lib for interacting with an instance of the Tapis API Framework"
license = "BSD-4-Clause"
authors = ["Joe Stubbs <jstubbs@tacc.utexas.edu>"]
Expand Down
2 changes: 1 addition & 1 deletion tapipy/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.7.0'
__version__ = '1.7.1'
51 changes: 51 additions & 0 deletions tapipy/tapis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,57 @@ def __call__(self, **kwargs):
raise errors.InvalidInputError(
msg="The headers argument, if passed, must be a dictionary-like object.")

# if X-Tapis-Tracking_ID (regardless of case) is in the headers we need to set tracking_id for validation
tracking_id = None
for k, v in headers.items():
if k.lower() == 'x-tapis-tracking-id' or k.lower() == 'x_tapis_tracking_id':
tracking_id = headers.pop(k)
break
if '_x_tapis_tracking_id' in kwargs:
if tracking_id:
raise errors.InvalidInputError(msg="The _x_tapis_tracking_id argument and the X-Tapis-Tracking-ID header cannot both be set.")
else:
tracking_id = kwargs.pop('_x_tapis_tracking_id')

# tracking_id header needs to be passed through to __call__ headers for splunk audit trails
if tracking_id:
try:
if not isinstance(tracking_id, str):
raise errors.InvalidInputError(
msg="The _x_tapis_tracking_id argument, if passed, must be a string.")

if not tracking_id.isascii():
raise errors.InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed be a single period, followed by an ASCII universally unique identifier string. Must be an entirely ASCII string.")

if len(tracking_id) > 126:
raise errors.InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed be a single period, followed by an ASCII universally unique identifier string. Must be less than 126 characters.")

# only one . in string
if tracking_id.count('.') != 1:
raise errors.InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed be a single period, followed by an ASCII universally unique identifier string. count('.') != 1.")

# ensure doesn't start or end with .
if tracking_id.startswith('.') or tracking_id.endswith('.'):
raise errors.InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed be a single period, followed by an ASCII universally unique identifier string. Cannot start or end with '.'.")

tracking_namespace, tracking_unique_identifier = tracking_id.split('.')
# check namespace is alphanumeric + underscores
if not all(c.isalnum() or c == '_' for c in tracking_namespace):
raise errors.InvalidInputError(msg="Error: tracking_namespace contains invalid characters. Alphanumeric + underscores only.")

# check tracking_unique_identifier is alphanumeric + hyphens
if not all(c.isalnum() or c == '-' for c in tracking_unique_identifier):
raise errors.InvalidInputError(msg="Error: tracking_unique_identifier contains invalid characters. Alphanumeric + hyphens only.")

headers.update({'X-Tapis-Tracking-ID': tracking_id})
except ValueError:
raise errors.InvalidInputError(
msg=f"_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed be a single period, followed by an ASCII universally unique identifier string. Got x_tapis_tracking_id: {tracking_id}")

# construct the data -
data = None
files = None
Expand Down
70 changes: 70 additions & 0 deletions tests/tapipy-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import subprocess
import pytest
from tapipy.tapis import Tapis, TapisResult
from tapipy.errors import InvalidInputError


BASE_URL = os.getenv("base_url", "https://dev.develop.tapis.io")
Expand All @@ -24,6 +25,7 @@ def client():
# -----------------------------------------------------
# Tests to check parsing of different result structures -
# -----------------------------------------------------

def test_tapisresult_list_simple():
result = ['a', 1, 'b', True, None, 3.14159, b'some bytes']
tr = TapisResult(result)
Expand Down Expand Up @@ -308,6 +310,74 @@ def test_debug_flag_tenants(client):
assert hasattr(debug.request, 'url')
assert hasattr(debug.response, 'content')

# ----------------
# tracking_id tests - Confluence: Proposal for File Provenance Auditing
# ----------------

def validate_tracking_id(tracking_id):
if not isinstance(tracking_id, str):
raise InvalidInputError(
msg="The _x_tapis_tracking_id argument, if passed, must be a string.")

if not tracking_id.isascii():
raise InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed by a single period, followed by an ASCII universally unique identifier string. Must be an entirely ASCII string.")

if len(tracking_id) > 126:
raise InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed by a single period, followed by an ASCII universally unique identifier string. Must be less than 126 characters.")

if tracking_id.count('.') != 1:
raise InvalidInputError(
msg="_x_tapis_tracking_id validation error. <namespace>.<unique identifier>. namespace is a non-empty, ASCII string of alphanumeric characters and underscores, followed by a single period, followed by an ASCII universally unique identifier string. count('.') != 1.")

tracking_namespace, tracking_id = tracking_id.split('.')
if not all(c.isalnum() or c == '_' for c in tracking_namespace):
raise InvalidInputError(msg="Error: tracking_namespace contains invalid characters. Alphanumeric + underscores only.")

if not all(c.isalnum() or c == '-' for c in tracking_id):
raise InvalidInputError(msg="Error: tracking_id contains invalid characters. Alphanumeric + hyphens only.")

def test_tracking_id_validation(client):
with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id=True) # Not a string

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.iden.tifier") # More than one period

result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifier") # Should work

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifier-with-non-ascii-字符") # Non-ASCII characters

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifier" * 10) # Length > 126

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespaceidentifier") # No period

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace..identifier") # More than one period

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifi.er") # More than one period

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifi_er") # id only allowed alphanumeric and hyphens after .

result = client.tenants.list_tenants(_x_tapis_tracking_id="names_ace.identifi-er") # namespace only allowed alphanumeric and underscores (This is proper)

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace!@#.identifier") # Invalid characters in namespace

with pytest.raises(InvalidInputError):
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifier!@#") # Invalid characters in identifier

# Valid case
try:
result = client.tenants.list_tenants(_x_tapis_tracking_id="namespace.identifier")
except InvalidInputError:
pytest.fail("validate_tracking_id() raised InvalidInputError unexpectedly!")

# -----------------------
# Tapipy import timing test -
Expand Down

0 comments on commit 0fe830e

Please sign in to comment.