Skip to content
Merged
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
14 changes: 14 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ CHANGELOG

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

3.5.8 (December 10, 2025)
-------------------------
Features:
- Added optional org_identifier and project_identifier support for Harness microclients
- These parameters can be set at client initialization or passed to individual method calls
- Parameters are only included in API request URLs when they are set (not None)
- When not provided, they are completely omitted from URLs rather than being passed as empty strings
- Applies to all Harness microclients: harness_project, harness_user, harness_group,
harness_apikey, service_account, token, role, resource_group, and role_assignment

- Added filterType support to the group endpoint in order to allow fitlering by resourcegroups
- Update the client instantiation to warn when using the legacy bearer auth along with the x-api-key auth


3.5.7 (November 26, 2025)
-------------------------
Bug Fixes:
Expand Down
101 changes: 80 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,17 @@ Full documentation on this Python wrapper is available in [this link](https://he

## Using in Harness Mode

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.
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. Operating not in harness mode will work as it did before with existing Split bearer tokens, but the deprecated endpoints mentioned below will not function.

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

### Authentication in Harness Mode

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

1. Harness-specific endpoints always use the 'x-api-key' header format
2. Split endpoints will use the 'x-api-key' header when using the harness_token
3. Split endpoints will use the normal 'Authorization' header when using the apikey
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
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.

For non-migrated accounts that need to use Split API keys with Bearer authentication, use the standard (non-harness) mode instead.

### Base URLs and Endpoints

Expand All @@ -44,19 +43,20 @@ To use the client in harness mode:
```python
from splitapiclient.main import get_client

# Option 1: Use harness_token for Harness endpoints and apikey for Split endpoints
# Basic harness mode setup with required harness_token
client = get_client({
'harness_mode': True,
'harness_token': 'YOUR_HARNESS_TOKEN', # Used for Harness-specific endpoints
'apikey': 'YOUR_SPLIT_API_KEY', # Used for existing Split endpoints
'harness_token': 'YOUR_HARNESS_TOKEN', # Required: Harness API key
'account_identifier': 'YOUR_HARNESS_ACCOUNT_ID' # Required for Harness operations
})

# Option 2: Use harness_token for all operations (if apikey is not provided)
# Include optional org_identifier and project_identifier
client = get_client({
'harness_mode': True,
'harness_token': 'YOUR_HARNESS_TOKEN', # Used for both Harness and Split endpoints
'account_identifier': 'YOUR_HARNESS_ACCOUNT_ID'
'harness_token': 'YOUR_HARNESS_TOKEN',
'account_identifier': 'YOUR_HARNESS_ACCOUNT_ID',
'org_identifier': 'YOUR_ORG_ID', # Optional: organization identifier
'project_identifier': 'YOUR_PROJECT_ID' # Optional: project identifier
})
```

Expand Down Expand Up @@ -97,15 +97,30 @@ Basic example:
# Account identifier is required for all Harness operations
account_id = 'YOUR_ACCOUNT_IDENTIFIER'

# List all tokens
# List all tokens (org_identifier and project_identifier are optional)
tokens = client.token.list(account_id)
for token in tokens:
print(f"Token: {token.name}, ID: {token.id}")

# List service accounts
service_accounts = client.service_account.list(account_id)
# List service accounts with org and project identifiers
org_id = 'YOUR_ORG_IDENTIFIER'
project_id = 'YOUR_PROJECT_IDENTIFIER'
service_accounts = client.service_account.list(account_id, org_identifier=org_id, project_identifier=project_id)
for sa in service_accounts:
print(f"Service Account: {sa.name}, ID: {sa.id}")

# If org_identifier and project_identifier are set at client initialization, you can omit them
client = get_client({
'harness_mode': True,
'harness_token': 'YOUR_HARNESS_TOKEN',
'account_identifier': account_id,
'org_identifier': org_id,
'project_identifier': project_id
})

# Now you can call methods without specifying identifiers
service_accounts = client.service_account.list() # Uses default identifiers
projects = client.harness_project.list() # Uses default identifiers
```

For most creation, update, and delete endpoints for harness specific resources, you will need to pass through the JSON body directly.
Expand All @@ -130,24 +145,68 @@ new_sa = client.service_account.create(sa_data, account_id)
client.harness_user.add_user_to_groups(user.id, [group.id], account_id)
```

### Harness Groups

The `harness_group.list()` method supports an optional `filterType` parameter to filter groups:

```python
# List all groups (default behavior)
all_groups = client.harness_group.list()

# List groups with filterType to exclude inherited groups
groups = client.harness_group.list(filterType='EXCLUDE_INHERITED_GROUPS')

# List groups with filterType and other optional parameters
groups = client.harness_group.list(
account_identifier='YOUR_ACCOUNT_ID',
org_identifier='YOUR_ORG_ID',
project_identifier='YOUR_PROJECT_ID',
filterType='INCLUDE_INHERITED_GROUPS'
)
```

**Note:** The `filterType` parameter is optional. When not provided (or set to `None`), it will be omitted from the API request. Valid values depend on the Harness API specification.


For detailed examples and API specifications for each resource, please refer to the [Harness API documentation](https://apidocs.harness.io/).

### Setting Default Account Identifier
### Setting Default Identifiers

To avoid specifying the account identifier with every request:
To avoid specifying identifiers with every request, you can set default values when creating the client:

```python
# Set default account identifier when creating the client
# Set default identifiers when creating the client
client = get_client({
'harness_mode': True,
'harness_token': 'YOUR_HARNESS_TOKEN',
'account_identifier': 'YOUR_ACCOUNT_IDENTIFIER'
'account_identifier': 'YOUR_ACCOUNT_IDENTIFIER', # Required
'org_identifier': 'YOUR_ORG_IDENTIFIER', # Optional
'project_identifier': 'YOUR_PROJECT_IDENTIFIER' # Optional
})

# Now you can make calls without specifying account_identifier in each request
# Now you can make calls without specifying identifiers in each request
tokens = client.token.list() # account_identifier is automatically included
projects = client.harness_project.list() # account_identifier is automatically included
projects = client.harness_project.list() # account_identifier and org_identifier are automatically included (project_identifier is not used for projects endpoint)
```

**Note on Optional Identifiers:**
- `account_identifier` is **required** for all Harness operations
- `org_identifier` and `project_identifier` are **optional** and will be omitted from API requests if not provided
- If `org_identifier` or `project_identifier` are not set, they will not appear in the URL at all (not even as empty parameters)
- **Important:** The `harness_project` microclient does **not** support `project_identifier` as a query parameter. The projects endpoint only uses `org_identifier` (and `account_identifier`). Other microclients (service_account, token, role, etc.) do support `project_identifier`.
- You can override default identifiers by passing them as parameters to individual method calls:

```python
# Override default identifiers for a specific request
# Note: project_identifier is not used for harness_project endpoints
projects = client.harness_project.list(
account_identifier='DIFFERENT_ACCOUNT_ID',
org_identifier='DIFFERENT_ORG_ID'
)

# Use default identifiers but override only org_identifier
# Note: project_identifier is not used for harness_project endpoints
projects = client.harness_project.list(org_identifier='DIFFERENT_ORG_ID')
```

## Quick Setup
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "splitapiclient"
version = "3.5.7"
version = "3.5.8"
license = "Apache-2.0"
description = "This Python Library provides full support for Split REST Admin API, allow creating, deleting and editing Environments, Splits, Split Definitions, Segments, Segment Keys, Users, Groups, API Keys, Change Requests, Attributes and Identities"
classifiers = [
Expand Down
61 changes: 32 additions & 29 deletions splitapiclient/main/harness_apiclient.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import, division, print_function, \
unicode_literals
import warnings
from splitapiclient.main.apiclient import BaseApiClient
from splitapiclient.http_clients.harness_client import HarnessHttpClient
from splitapiclient.util.exceptions import InsufficientConfigArgumentsException
Expand Down Expand Up @@ -45,14 +46,18 @@ def __init__(self, config):
Class constructor.

:param config: Dictionary containing options required to instantiate
the API client. Should have AT LEAST one of the following keys:
- 'apikey': Split API key for authentication with Split endpoints
- 'harness_token': Harness authentication token used for x-api-key header with Harness endpoints
If harness_token is not provided, apikey will be used for all operations
the API client. Required keys:
- 'harness_token': Harness authentication token used for x-api-key header
This token is used for both Split API and Harness API endpoints.
Note: Split API keys (apikey) are not supported in harness mode.
Migrated accounts should create new Harness API keys.
Optional keys:
- 'base_url': Base url where the Split API is hosted (optional, defaults to Split URL)
- 'base_url_v3': Base url where the Split API v3 is hosted (optional, defaults to Split URL)
- 'harness_base_url': Base url where the Harness API is hosted (optional, defaults to Harness URL)
- 'account_identifier': Harness account identifier to use for all Harness operations (optional)
- 'org_identifier': Harness organization identifier to use for all Harness operations (optional)
- 'project_identifier': Harness project identifier to use for all Harness operations (optional)
'''
# Set up Split API base URLs for existing endpoints
if 'base_url' in config:
Expand All @@ -72,30 +77,28 @@ def __init__(self, config):
else:
self._harness_base_url = self.BASE_HARNESS_URL

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

# Set up authentication tokens
self._apikey = config.get('apikey')
self._harness_token = config.get('harness_token')
# Use harness_token for all operations
self._harness_token = config['harness_token']
auth_token = self._harness_token

# If harness_token is not provided, use apikey for all operations
# If apikey is not provided, use harness_token for all operations
split_auth_token = self._apikey if self._apikey else self._harness_token
harness_auth_token = self._harness_token if self._harness_token else self._apikey

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

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

# Create HTTP client for Harness endpoints
harness_http_client = HarnessHttpClient(self._harness_base_url, harness_auth_token)
harness_http_client = HarnessHttpClient(self._harness_base_url, auth_token)

# Standard microclients using Split endpoints
self._environment_client = EnvironmentMicroClient(split_http_client)
Expand All @@ -114,15 +117,15 @@ def __init__(self, config):
self._flag_set_client = FlagSetMicroClient(split_http_clientv3)

# Harness-specific microclients using Harness endpoints
self._token_client = TokenMicroClient(harness_http_client, self._account_identifier)
self._harness_apikey_client = HarnessApiKeyMicroClient(harness_http_client, self._account_identifier)
self._service_account_client = ServiceAccountMicroClient(harness_http_client, self._account_identifier)
self._harness_user_client = HarnessUserMicroClient(harness_http_client, self._account_identifier)
self._harness_group_client = HarnessGroupMicroClient(harness_http_client, self._account_identifier)
self._role_client = RoleMicroClient(harness_http_client, self._account_identifier)
self._resource_group_client = ResourceGroupMicroClient(harness_http_client, self._account_identifier)
self._role_assignment_client = RoleAssignmentMicroClient(harness_http_client, self._account_identifier)
self._harness_project_client = HarnessProjectMicroClient(harness_http_client, self._account_identifier)
self._token_client = TokenMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._harness_apikey_client = HarnessApiKeyMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._service_account_client = ServiceAccountMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._harness_user_client = HarnessUserMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._harness_group_client = HarnessGroupMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._role_client = RoleMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._resource_group_client = ResourceGroupMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._role_assignment_client = RoleAssignmentMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)
self._harness_project_client = HarnessProjectMicroClient(harness_http_client, self._account_identifier, self._org_identifier, self._project_identifier)

@property
def traffic_types(self):
Expand Down
Loading