Skip to content

Commit 863e65e

Browse files
Akash VermaAkash Verma
authored andcommitted
Added req for sdz and customer name in schema and ts files
1 parent facae97 commit 863e65e

File tree

17 files changed

+193
-86
lines changed

17 files changed

+193
-86
lines changed

ingestion/src/metadata/ingestion/source/database/burstiq/client.py

Lines changed: 48 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
"""
1212
Client to interact with BurstIQ LifeGraph APIs
1313
"""
14-
import base64
15-
import json
1614
import traceback
1715
from datetime import datetime, timedelta
1816
from typing import Any, Dict, List, Optional
@@ -49,34 +47,49 @@ def __init__(self, config: BurstIQConnection):
4947
Args:
5048
config: BurstIQConnection configuration
5149
"""
52-
self.realm_name = config.realmName
53-
self.username = config.username
54-
self.password = config.password.get_secret_value()
55-
self.client_id = getattr(config, "clientId", "burst")
56-
57-
self.auth_server_url = getattr(config, "authServerUrl", AUTH_SERVER_BASE)
50+
self.config = config
5851
self.api_base_url = getattr(config, "apiUrl", API_BASE_URL).rstrip("/")
5952

6053
# Token management
6154
self.access_token: Optional[str] = None
6255
self.token_expires_at: Optional[datetime] = None
6356

64-
# Extract customer and SDZ names from token after authentication
65-
self.customer_name: Optional[str] = None
66-
self.sdz_name: Optional[str] = None
57+
def test_authenticate(self):
58+
"""
59+
Explicitly test authentication with BurstIQ.
60+
This is used during test_connection to validate credentials.
6761
68-
# Authenticate on initialization
62+
Raises:
63+
Exception: If authentication fails
64+
"""
6965
self._authenticate()
7066

7167
def _authenticate(self):
7268
"""Authenticate with BurstIQ and get access token"""
73-
token_url = f"{self.auth_server_url}/realms/{self.realm_name}/protocol/openid-connect/token"
69+
# Get configuration values
70+
realm_name = getattr(self.config, "realmName", None)
71+
username = getattr(self.config, "username", None)
72+
password = getattr(self.config, "password", None)
73+
74+
# Validate required fields
75+
if not realm_name:
76+
raise ValueError("realmName is required for authentication")
77+
if not username:
78+
raise ValueError("username is required for authentication")
79+
if not password:
80+
raise ValueError("password is required for authentication")
81+
82+
auth_server_url = getattr(self.config, "authServerUrl", AUTH_SERVER_BASE)
83+
client_id = getattr(self.config, "clientId", "burst")
84+
token_url = (
85+
f"{auth_server_url}/realms/{realm_name}/protocol/openid-connect/token"
86+
)
7487

7588
payload = {
76-
"client_id": self.client_id,
89+
"client_id": client_id,
7790
"grant_type": "password",
78-
"username": self.username,
79-
"password": self.password,
91+
"username": username,
92+
"password": password.get_secret_value(),
8093
}
8194

8295
headers = {"Content-Type": "application/x-www-form-urlencoded"}
@@ -98,69 +111,34 @@ def _authenticate(self):
98111
seconds=expires_in - 60
99112
) # 60s buffer
100113

101-
# Decode token to extract customer and SDZ names
102-
self._decode_and_extract_claims()
114+
customer_name = getattr(self.config, "biqCustomerName", None)
115+
sdz_name = getattr(self.config, "biqSdzName", None)
103116

104117
logger.info(
105118
f"Authentication successful. Token expires in {expires_in} seconds"
106119
)
107-
logger.info(f"Customer: {self.customer_name}, SDZ: {self.sdz_name}")
120+
if customer_name and sdz_name:
121+
logger.info(f"Customer: {customer_name}, SDZ: {sdz_name}")
108122

109123
except Exception as exc:
110124
logger.error(f"Authentication failed: {exc}")
111125
logger.debug(traceback.format_exc())
112126
raise Exception("Failed to authenticate with BurstIQ") from exc
113127

114-
def _decode_and_extract_claims(self):
115-
"""Decode JWT token and extract customer/SDZ names"""
116-
if not self.access_token:
117-
return
118-
119-
try:
120-
# Decode JWT payload (second part of token)
121-
parts = self.access_token.split(".")
122-
if len(parts) != 3:
123-
logger.warning("Invalid JWT token format")
124-
return
125-
126-
payload = parts[1]
127-
# Add padding if needed
128-
padding = 4 - (len(payload) % 4)
129-
if padding != 4:
130-
payload += "=" * padding
131-
132-
decoded_bytes = base64.urlsafe_b64decode(payload)
133-
decoded_token = json.loads(decoded_bytes)
134-
135-
# Extract customer name from issuer (realm)
136-
iss = decoded_token.get("iss", "")
137-
if "/realms/" in iss:
138-
self.customer_name = iss.split("/realms/")[-1].strip("/")
139-
140-
# Extract SDZ name from audience or resource_access
141-
aud = decoded_token.get("aud")
142-
if aud:
143-
self.sdz_name = aud if isinstance(aud, str) else aud[0]
144-
145-
# If not in aud, check resource_access keys
146-
if not self.sdz_name:
147-
resource_access = decoded_token.get("resource_access", {})
148-
if resource_access:
149-
self.sdz_name = list(resource_access.keys())[0]
150-
151-
except Exception as exc:
152-
logger.warning(f"Failed to decode token: {exc}")
153-
logger.debug(traceback.format_exc())
154-
155128
def _get_auth_header(self) -> Dict[str, str]:
156129
"""
157-
Get authentication headers with current access token
130+
Get authentication headers with current access token.
131+
Authenticates on first call if not already authenticated.
158132
159133
Returns:
160134
Dictionary of headers
161135
"""
136+
# Authenticate if not already done (lazy authentication)
137+
if not self.access_token:
138+
logger.info("No access token found, authenticating...")
139+
self._authenticate()
162140
# Check if token needs refresh
163-
if self.token_expires_at and datetime.now() >= self.token_expires_at:
141+
elif self.token_expires_at and datetime.now() >= self.token_expires_at:
164142
logger.info("Access token expired, re-authenticating...")
165143
self._authenticate()
166144

@@ -170,11 +148,14 @@ def _get_auth_header(self) -> Dict[str, str]:
170148
"Accept": "application/json",
171149
}
172150

173-
# Add BurstIQ-specific headers
174-
if self.customer_name:
175-
headers["biq_customer_name"] = self.customer_name
176-
if self.sdz_name:
177-
headers["biq_sdz_name"] = self.sdz_name
151+
# Add BurstIQ-specific headers from config
152+
customer_name = getattr(self.config, "biqCustomerName", None)
153+
sdz_name = getattr(self.config, "biqSdzName", None)
154+
155+
if customer_name:
156+
headers["biq_customer_name"] = customer_name
157+
if sdz_name:
158+
headers["biq_sdz_name"] = sdz_name
178159

179160
return headers
180161

ingestion/src/metadata/ingestion/source/database/burstiq/connection.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def test_connection(
6666
TestConnectionResult
6767
"""
6868

69+
def test_authenticate():
70+
"""Test authentication with BurstIQ credentials"""
71+
client.test_authenticate()
72+
6973
def test_get_dictionaries():
7074
"""Test fetching dictionaries from BurstIQ"""
7175
dictionaries = client.get_dictionaries(limit=1)
@@ -75,11 +79,12 @@ def test_get_dictionaries():
7579
def test_get_edges():
7680
"""Test fetching edges used for lineage"""
7781
edges = client.get_edges(limit=1)
78-
if not edges:
79-
raise ConnectionError("Failed to fetch edges from BurstIQ")
82+
# Edges might not exist, so don't fail if empty
83+
logger.info(f"Found {len(edges)} edges in BurstIQ")
8084

8185
test_fn = {
82-
"CheckAccess": test_get_dictionaries,
86+
"CheckAccess": test_authenticate,
87+
"GetDictionaries": test_get_dictionaries,
8388
"GetEdges": test_get_edges,
8489
}
8590

ingestion/src/metadata/ingestion/source/database/burstiq/metadata.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -430,17 +430,27 @@ def get_table_constraints(
430430
# Process attributes for foreign keys (reference relationships)
431431
for attribute in dictionary.attributes:
432432
if attribute.referenceDictionaryName:
433-
# This is a foreign key relationship
434-
table_constraints.append(
435-
TableConstraint(
436-
constraintType=ConstraintType.FOREIGN_KEY,
437-
columns=[attribute.name],
438-
referredColumns=[
439-
attribute.name
440-
], # Assume same column name in referenced table
441-
)
433+
# Build FQN for the referred column in the referenced table
434+
referred_col_fqn = fqn.build(
435+
metadata=self.metadata,
436+
entity_type=Table,
437+
service_name=self.context.get().database_service,
438+
database_name=self.context.get().database,
439+
schema_name=self.context.get().database_schema,
440+
table_name=attribute.referenceDictionaryName,
441+
column_name=attribute.name, # Assuming same column name in referenced table
442442
)
443443

444+
# Only add foreign key constraint if FQN was successfully built
445+
if referred_col_fqn:
446+
table_constraints.append(
447+
TableConstraint(
448+
constraintType=ConstraintType.FOREIGN_KEY,
449+
columns=[attribute.name],
450+
referredColumns=[referred_col_fqn],
451+
)
452+
)
453+
444454
return table_constraints if table_constraints else None
445455

446456
def yield_table(

openmetadata-service/src/main/resources/json/data/testConnections/database/burstiq.json

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,22 @@
66
"steps": [
77
{
88
"name": "CheckAccess",
9-
"description": "Validate that we can properly authenticate with BurstIQ using the provided credentials and access the LifeGraph API.",
10-
"errorMessage": "Failed to connect to BurstIQ LifeGraph. Please validate the username, password, and realm name.",
9+
"description": "Validate that we can properly authenticate with BurstIQ using the provided credentials.",
10+
"errorMessage": "Failed to authenticate with BurstIQ. Please validate the username, password, and realm name are correct.",
11+
"shortCircuit": true,
12+
"mandatory": true
13+
},
14+
{
15+
"name": "GetDictionaries",
16+
"description": "Validate that we can fetch dictionaries (tables) from BurstIQ LifeGraph API.",
17+
"errorMessage": "Failed to fetch dictionaries from BurstIQ LifeGraph. Please validate that the user has sufficient privileges to read metadata.",
1118
"shortCircuit": true,
1219
"mandatory": true
1320
},
1421
{
1522
"name": "GetEdges",
16-
"description": "List all the Edges available to the user in the BurstIQ LifeGraph platform.",
17-
"errorMessage": "Failed to fetch Edges. Please validate that the user has sufficient privileges to read metadata from BurstIQ LifeGraph.",
23+
"description": "List all the Edges available to the user in the BurstIQ LifeGraph platform for lineage extraction.",
24+
"errorMessage": "Failed to fetch Edges. Lineage extraction may not be available but basic metadata ingestion will still work.",
1825
"mandatory": false
1926
}
2027
]

openmetadata-spec/src/main/resources/json/schema/entity/services/connections/database/burstIQConnection.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636
"description": "BurstIQ Keycloak realm name (e.g., 'ems' from https://auth.burstiq.com/realms/ems).",
3737
"type": "string"
3838
},
39+
"biqSdzName": {
40+
"title": "BurstIQ SDZ Name",
41+
"description": "BurstIQ Secure Data Zone (SDZ) name for API requests.",
42+
"type": "string"
43+
},
44+
"biqCustomerName": {
45+
"title": "BurstIQ Customer Name",
46+
"description": "BurstIQ customer name for API requests.",
47+
"type": "string"
48+
},
3949
"tableFilterPattern": {
4050
"title": "Table Filter Pattern",
4151
"description": "Regex to only include/exclude dictionaries (tables) that matches the pattern.",
@@ -51,5 +61,5 @@
5161
}
5262
},
5363
"additionalProperties": false,
54-
"required": ["username", "password", "realmName"]
64+
"required": ["username", "password", "realmName", "biqSdzName", "biqCustomerName"]
5565
}

openmetadata-ui/src/main/resources/ui/public/locales/en-US/Database/BurstIQ.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ BurstIQ Keycloak realm name (e.g., 'ems' from https://auth.burstiq.com/realms/em
2525
$$
2626

2727
$$section
28-
### Table Filter Pattern $(id="tableFilterPattern")
29-
Regex to only include/exclude dictionaries (tables) that matches the pattern.
28+
### BurstIQ SDZ Name $(id="biqSdzName")
29+
BurstIQ Secure Data Zone (SDZ) name for API requests.
3030
$$
31+
32+
$$section
33+
### BurstIQ Customer Name $(id="biqCustomerName")
34+
BurstIQ customer name for API requests.
35+
$$
36+

openmetadata-ui/src/main/resources/ui/src/generated/api/automations/createWorkflow.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1500,6 +1500,14 @@ export interface ConfigObject {
15001500
* admin tables will be fetched.
15011501
*/
15021502
includeSystemTables?: boolean;
1503+
/**
1504+
* BurstIQ customer name for API requests.
1505+
*/
1506+
biqCustomerName?: string;
1507+
/**
1508+
* BurstIQ Secure Data Zone (SDZ) name for API requests.
1509+
*/
1510+
biqSdzName?: string;
15031511
/**
15041512
* BurstIQ Keycloak realm name (e.g., 'ems' from https://auth.burstiq.com/realms/ems).
15051513
*/

openmetadata-ui/src/main/resources/ui/src/generated/api/services/createDatabaseService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,14 @@ export interface ConfigObject {
993993
* admin tables will be fetched.
994994
*/
995995
includeSystemTables?: boolean;
996+
/**
997+
* BurstIQ customer name for API requests.
998+
*/
999+
biqCustomerName?: string;
1000+
/**
1001+
* BurstIQ Secure Data Zone (SDZ) name for API requests.
1002+
*/
1003+
biqSdzName?: string;
9961004
/**
9971005
* BurstIQ Keycloak realm name (e.g., 'ems' from https://auth.burstiq.com/realms/ems).
9981006
*/

openmetadata-ui/src/main/resources/ui/src/generated/api/services/ingestionPipelines/createIngestionPipeline.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4178,6 +4178,14 @@ export interface ConfigObject {
41784178
* admin tables will be fetched.
41794179
*/
41804180
includeSystemTables?: boolean;
4181+
/**
4182+
* BurstIQ customer name for API requests.
4183+
*/
4184+
biqCustomerName?: string;
4185+
/**
4186+
* BurstIQ Secure Data Zone (SDZ) name for API requests.
4187+
*/
4188+
biqSdzName?: string;
41814189
/**
41824190
* BurstIQ Keycloak realm name (e.g., 'ems' from https://auth.burstiq.com/realms/ems).
41834191
*/

openmetadata-ui/src/main/resources/ui/src/generated/entity/automations/testServiceConnection.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,6 +1382,14 @@ export interface ConfigObject {
13821382
* admin tables will be fetched.
13831383
*/
13841384
includeSystemTables?: boolean;
1385+
/**
1386+
* BurstIQ customer name for API requests.
1387+
*/
1388+
biqCustomerName?: string;
1389+
/**
1390+
* BurstIQ Secure Data Zone (SDZ) name for API requests.
1391+
*/
1392+
biqSdzName?: string;
13851393
/**
13861394
* BurstIQ Keycloak realm name (e.g., 'ems' from https://auth.burstiq.com/realms/ems).
13871395
*/

0 commit comments

Comments
 (0)