Skip to content

Commit 2d647d6

Browse files
committed
Refactor HarnessApiClient to require harness_token for authentication in harness mode, removing support for apikey. Update README and tests to reflect changes in authentication requirements and usage.
1 parent a6b6441 commit 2d647d6

File tree

6 files changed

+83
-128
lines changed

6 files changed

+83
-128
lines changed

CHANGES.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ CHANGELOG
33

44
This file tracks the version history and changes made to the Split/Harness FME Python API Client library.
55

6-
3.5.8 (December 5, 2025)
6+
3.5.8 (December 10, 2025)
77
-------------------------
88
Features:
99
- Added optional org_identifier and project_identifier support for Harness microclients
@@ -12,7 +12,10 @@ Features:
1212
- When not provided, they are completely omitted from URLs rather than being passed as empty strings
1313
- Applies to all Harness microclients: harness_project, harness_user, harness_group,
1414
harness_apikey, service_account, token, role, resource_group, and role_assignment
15-
Added filterType support to the group endpoint
15+
16+
- Added filterType support to the group endpoint in order to allow fitlering by resourcegroups
17+
- Update the client instantiation to warn when using the legacy bearer auth along with the x-api-key auth
18+
1619

1720
3.5.7 (November 26, 2025)
1821
-------------------------

README.md

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,17 @@ Full documentation on this Python wrapper is available in [this link](https://he
88

99
## Using in Harness Mode
1010

11-
Starting with version 3.5.0, the Split API client supports operating in "harness mode" to interact with both Split and Harness APIs. This is required for usage in environments that have been migrated to Harness and want to use the new features. Existing API keys will continue to work with the non-deprecated endpoints after migration, but new Harness Tokens will be required for Harness mode.
11+
Starting with version 3.5.0, the Split API client supports operating in "harness mode" to interact with both Split and Harness APIs. This is required for usage in environments that have been migrated to Harness and want to use the new features.
1212

1313
For detailed information about Harness API endpoints, please refer to the [official Harness API documentation](https://apidocs.harness.io/).
1414

1515
### Authentication in Harness Mode
1616

17-
The client supports multiple authentication scenarios:
17+
**Important:** Harness mode requires a `harness_token` (Harness API key). Split API keys (`apikey`) are **not supported** in harness mode.
1818

19-
1. Harness-specific endpoints always use the 'x-api-key' header format
20-
2. Split endpoints will use the 'x-api-key' header when using the harness_token
21-
3. Split endpoints will use the normal 'Authorization' header when using the apikey
22-
4. If both harness_token and apikey are provided, the client will use the harness_token for Harness endpoints and the apikey for Split endpoints
19+
If your account has been migrated to Harness, you should create a new Harness API key to use with this client. The `harness_token` is used for both Split API endpoints and Harness-specific endpoints via the `x-api-key` header.
20+
21+
For non-migrated accounts that need to use Split API keys with Bearer authentication, use the standard (non-harness) mode instead.
2322

2423
### Base URLs and Endpoints
2524

@@ -44,22 +43,14 @@ To use the client in harness mode:
4443
```python
4544
from splitapiclient.main import get_client
4645

47-
# Option 1: Use harness_token for Harness endpoints and apikey for Split endpoints
46+
# Basic harness mode setup with required harness_token
4847
client = get_client({
4948
'harness_mode': True,
50-
'harness_token': 'YOUR_HARNESS_TOKEN', # Used for Harness-specific endpoints
51-
'apikey': 'YOUR_SPLIT_API_KEY', # Used for existing Split endpoints
49+
'harness_token': 'YOUR_HARNESS_TOKEN', # Required: Harness API key
5250
'account_identifier': 'YOUR_HARNESS_ACCOUNT_ID' # Required for Harness operations
5351
})
5452

55-
# Option 2: Use harness_token for all operations (if apikey is not provided)
56-
client = get_client({
57-
'harness_mode': True,
58-
'harness_token': 'YOUR_HARNESS_TOKEN', # Used for both Harness and Split endpoints
59-
'account_identifier': 'YOUR_HARNESS_ACCOUNT_ID'
60-
})
61-
62-
# Option 3: Include optional org_identifier and project_identifier
53+
# Include optional org_identifier and project_identifier
6354
client = get_client({
6455
'harness_mode': True,
6556
'harness_token': 'YOUR_HARNESS_TOKEN',

splitapiclient/main/harness_apiclient.py

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import absolute_import, division, print_function, \
22
unicode_literals
3+
import warnings
34
from splitapiclient.main.apiclient import BaseApiClient
45
from splitapiclient.http_clients.harness_client import HarnessHttpClient
56
from splitapiclient.util.exceptions import InsufficientConfigArgumentsException
@@ -45,10 +46,12 @@ def __init__(self, config):
4546
Class constructor.
4647
4748
:param config: Dictionary containing options required to instantiate
48-
the API client. Should have AT LEAST one of the following keys:
49-
- 'apikey': Split API key for authentication with Split endpoints
50-
- 'harness_token': Harness authentication token used for x-api-key header with Harness endpoints
51-
If harness_token is not provided, apikey will be used for all operations
49+
the API client. Required keys:
50+
- 'harness_token': Harness authentication token used for x-api-key header
51+
This token is used for both Split API and Harness API endpoints.
52+
Note: Split API keys (apikey) are not supported in harness mode.
53+
Migrated accounts should create new Harness API keys.
54+
Optional keys:
5255
- 'base_url': Base url where the Split API is hosted (optional, defaults to Split URL)
5356
- 'base_url_v3': Base url where the Split API v3 is hosted (optional, defaults to Split URL)
5457
- 'harness_base_url': Base url where the Harness API is hosted (optional, defaults to Harness URL)
@@ -74,32 +77,28 @@ def __init__(self, config):
7477
else:
7578
self._harness_base_url = self.BASE_HARNESS_URL
7679

77-
# Check if at least one authentication method is provided
78-
if 'apikey' not in config and 'harness_token' not in config:
80+
# Require harness_token in harness mode
81+
if 'harness_token' not in config:
7982
raise InsufficientConfigArgumentsException(
80-
'At least one of the following keys must be present in the config dict for harness mode: apikey, harness_token'
83+
'harness_token is required in harness mode. Split API keys (apikey) are not supported by this client. '
84+
'Please create a Harness API key for your migrated account and use the harness_token for authentication.'
8185
)
8286

83-
# Set up authentication tokens
84-
self._apikey = config.get('apikey')
85-
self._harness_token = config.get('harness_token')
86-
87-
# If harness_token is not provided, use apikey for all operations
88-
# If apikey is not provided, use harness_token for all operations
89-
split_auth_token = self._apikey if self._apikey else self._harness_token
90-
harness_auth_token = self._harness_token if self._harness_token else self._apikey
87+
# Use harness_token for all operations
88+
self._harness_token = config['harness_token']
89+
auth_token = self._harness_token
9190

9291
# Store the account identifier, org identifier, and project identifier
9392
self._account_identifier = config.get('account_identifier')
9493
self._org_identifier = config.get('org_identifier')
9594
self._project_identifier = config.get('project_identifier')
9695

97-
# Create HTTP clients for Split endpoints
98-
split_http_client = HarnessHttpClient(self._base_url, split_auth_token)
99-
split_http_clientv3 = HarnessHttpClient(self._base_url_v3, split_auth_token)
96+
# Create HTTP clients - use same token for both Split and Harness endpoints
97+
split_http_client = HarnessHttpClient(self._base_url, auth_token)
98+
split_http_clientv3 = HarnessHttpClient(self._base_url_v3, auth_token)
10099

101100
# Create HTTP client for Harness endpoints
102-
harness_http_client = HarnessHttpClient(self._harness_base_url, harness_auth_token)
101+
harness_http_client = HarnessHttpClient(self._harness_base_url, auth_token)
103102

104103
# Standard microclients using Split endpoints
105104
self._environment_client = EnvironmentMicroClient(split_http_client)

splitapiclient/tests/main/test_harness_apiclient_resources.py

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def test_harness_resource_properties(self, mocker):
2424

2525
# Create a HarnessApiClient with minimal config
2626
client = HarnessApiClient({
27-
'apikey': 'test-apikey',
2827
'harness_token': 'test-harness-token'
2928
})
3029

@@ -49,7 +48,6 @@ def test_harness_resource_operations(self, mocker):
4948

5049
# Create a HarnessApiClient with minimal config
5150
client = HarnessApiClient({
52-
'apikey': 'test-apikey',
5351
'harness_token': 'test-harness-token',
5452
'account_identifier': 'test-account-identifier'
5553
})
@@ -446,7 +444,6 @@ def test_harness_pagination(self, mocker):
446444
# Create a HarnessApiClient with mocked HTTP client
447445
client = HarnessApiClient({
448446
'harness_token': 'test-harness-token',
449-
'apikey': 'test-apikey',
450447
'base_url': 'test-host',
451448
'harness_base_url': 'test-harness-host',
452449
'account_identifier': 'test-account-identifier'
@@ -534,50 +531,51 @@ def test_harness_pagination(self, mocker):
534531

535532
def test_harness_authentication_modes(self, mocker):
536533
'''
537-
Test that the HarnessApiClient properly handles different authentication modes
534+
Test that the HarnessApiClient properly handles authentication requirements
538535
'''
539536
# Mock the HTTP client initialization to avoid actual HTTP requests
540537
mocker.patch('splitapiclient.http_clients.harness_client.HarnessHttpClient.__init__', return_value=None)
541538

542-
# Test with both apikey and harness_token
543-
client1 = HarnessApiClient({
544-
'apikey': 'test-apikey',
539+
# Test with harness_token - should work
540+
client = HarnessApiClient({
545541
'harness_token': 'test-harness-token'
546542
})
547543

548-
# Verify that the HTTP client was initialized correctly for client1
544+
# Verify that the HTTP client was initialized correctly
549545
from splitapiclient.http_clients.harness_client import HarnessHttpClient
550546

551-
# For client1, harness_token should be used for Harness endpoints
552-
harness_client1_calls = [
547+
# harness_token should be used for all endpoints (both Harness and Split)
548+
harness_client_calls = [
553549
call for call in HarnessHttpClient.__init__.call_args_list
554-
if call[0][0] == 'https://app.harness.io/' and call[0][1] == 'test-harness-token'
550+
if call[0][1] == 'test-harness-token'
555551
]
556-
assert len(harness_client1_calls) > 0
557-
558-
# Reset the mock before creating client2
559-
HarnessHttpClient.__init__.reset_mock()
552+
# Should be called 3 times: Split v2, Split v3, and Harness endpoints
553+
assert len(harness_client_calls) == 3
554+
555+
# Verify that the client has all the Harness resource properties
556+
assert hasattr(client, 'token')
557+
assert hasattr(client, 'harness_apikey')
558+
assert hasattr(client, 'service_account')
559+
assert hasattr(client, 'harness_user')
560+
assert hasattr(client, 'harness_group')
561+
assert hasattr(client, 'role')
562+
assert hasattr(client, 'resource_group')
563+
assert hasattr(client, 'role_assignment')
564+
assert hasattr(client, 'harness_project')
560565

561-
# Test with only apikey
562-
client2 = HarnessApiClient({
563-
'apikey': 'test-apikey'
564-
})
566+
def test_harness_mode_requires_harness_token(self, mocker):
567+
'''
568+
Test that harness_token is required and apikey alone raises an error
569+
'''
570+
from splitapiclient.util.exceptions import InsufficientConfigArgumentsException
565571

566-
# Verify that both clients have all the Harness resource properties
567-
for client in [client1, client2]:
568-
assert hasattr(client, 'token')
569-
assert hasattr(client, 'harness_apikey')
570-
assert hasattr(client, 'service_account')
571-
assert hasattr(client, 'harness_user')
572-
assert hasattr(client, 'harness_group')
573-
assert hasattr(client, 'role')
574-
assert hasattr(client, 'resource_group')
575-
assert hasattr(client, 'role_assignment')
576-
assert hasattr(client, 'harness_project')
577-
578-
# For client2, apikey should be used for Harness endpoints
579-
harness_client2_calls = [
580-
call for call in HarnessHttpClient.__init__.call_args_list
581-
if call[0][0] == 'https://app.harness.io/' and call[0][1] == 'test-apikey'
582-
]
583-
assert len(harness_client2_calls) > 0
572+
# Mock the HTTP client initialization to avoid actual HTTP requests
573+
mocker.patch('splitapiclient.http_clients.harness_client.HarnessHttpClient.__init__', return_value=None)
574+
575+
# Test with only apikey - should raise an error
576+
with pytest.raises(InsufficientConfigArgumentsException) as excinfo:
577+
HarnessApiClient({
578+
'apikey': 'test-apikey'
579+
})
580+
581+
assert 'harness_token is required' in str(excinfo.value)

splitapiclient/tests/microclients/harness/deprecated_endpoints_test.py

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def test_workspace_endpoints_deprecated_in_harness_mode(self, mocker):
2424

2525
# Create a HarnessApiClient
2626
client = HarnessApiClient({
27-
'apikey': 'abc',
2827
'harness_token': 'abc'
2928
})
3029

@@ -138,7 +137,7 @@ class TestAuthenticationInHarnessMode:
138137

139138
def test_harness_token_used_for_harness_endpoints(self, mocker):
140139
"""
141-
Test that harness_token is used for Harness endpoints and apikey for Split endpoints
140+
Test that harness_token is used for all endpoints (both Harness and Split)
142141
"""
143142
# Create a custom HTTP client class for testing
144143
class TestHttpClient(HarnessHttpClient):
@@ -147,13 +146,7 @@ def __init__(self, baseurl, auth_token):
147146
self.auth_token = auth_token
148147
# Initialize with empty config
149148
self.config = {'base_args': {}}
150-
151-
# For Harness HTTP client, set x-api-key in base_args
152-
if 'harness' in baseurl:
153-
self.config['base_args'] = {'x-api-key': auth_token}
154-
else:
155-
# For Split HTTP client, we'll check Authorization header in make_request
156-
pass
149+
self.config['base_args'] = {'x-api-key': auth_token}
157150

158151
def make_request(self, endpoint, body=None, **kwargs):
159152
# Just return a successful response without making actual requests
@@ -168,57 +161,28 @@ def make_request(self, endpoint, body=None, **kwargs):
168161
mocker.patch('splitapiclient.http_clients.harness_client.HarnessHttpClient.make_request',
169162
TestHttpClient.make_request)
170163

171-
# Create client with both harness_token and apikey
164+
# Create client with harness_token only
172165
client = HarnessApiClient({
173-
'harness_token': 'harness_token_value',
174-
'apikey': 'api_key_value'
166+
'harness_token': 'harness_token_value'
175167
})
176168

177-
# Check that the Harness HTTP client was initialized with harness_token
169+
# Check that all HTTP clients were initialized with harness_token
178170
assert client._token_client._http_client.auth_token == 'harness_token_value'
179-
180-
# Check that the Split HTTP client was initialized with apikey
181-
assert client._split_client._http_client.auth_token == 'api_key_value'
171+
assert client._split_client._http_client.auth_token == 'harness_token_value'
182172

183173
def test_apikey_fallback_when_no_harness_token(self, mocker):
184174
"""
185-
Test that apikey is used for all operations when harness_token is not provided
175+
Test that harness_token is required and apikey alone raises an error
186176
"""
187-
# Create a custom HTTP client class for testing
188-
class TestHttpClient(HarnessHttpClient):
189-
def __init__(self, baseurl, auth_token):
190-
self.baseurl = baseurl
191-
self.auth_token = auth_token
192-
# Initialize with empty config
193-
self.config = {'base_args': {}}
194-
195-
# For Harness HTTP client, set x-api-key in base_args
196-
if 'harness' in baseurl:
197-
self.config['base_args'] = {'x-api-key': auth_token}
198-
else:
199-
# For Split HTTP client, we'll check Authorization header in make_request
200-
pass
201-
202-
def make_request(self, endpoint, body=None, **kwargs):
203-
# Just return a successful response without making actual requests
204-
mock_response = mocker.Mock()
205-
mock_response.status_code = 200
206-
mock_response.text = '{}'
207-
return {}
177+
from splitapiclient.util.exceptions import InsufficientConfigArgumentsException
208178

209-
# Patch the HarnessHttpClient constructor to use our test class
210-
mocker.patch('splitapiclient.http_clients.harness_client.HarnessHttpClient.__init__',
211-
TestHttpClient.__init__)
212-
mocker.patch('splitapiclient.http_clients.harness_client.HarnessHttpClient.make_request',
213-
TestHttpClient.make_request)
214-
215-
# Create client with only apikey
216-
client = HarnessApiClient({
217-
'apikey': 'api_key_value'
218-
})
179+
# Patch the HarnessHttpClient constructor to avoid actual HTTP requests
180+
mocker.patch('splitapiclient.http_clients.harness_client.HarnessHttpClient.__init__', return_value=None)
219181

220-
# Check that the Harness HTTP client was initialized with apikey as fallback
221-
assert client._token_client._http_client.auth_token == 'api_key_value'
182+
# Create client with only apikey should raise an error
183+
with pytest.raises(InsufficientConfigArgumentsException) as excinfo:
184+
client = HarnessApiClient({
185+
'apikey': 'api_key_value'
186+
})
222187

223-
# Check that the Split HTTP client was initialized with apikey
224-
assert client._split_client._http_client.auth_token == 'api_key_value'
188+
assert 'harness_token is required' in str(excinfo.value)

splitapiclient/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '3.5.8rc2'
1+
__version__ = '3.5.8'

0 commit comments

Comments
 (0)