Skip to content

Commit b799abe

Browse files
authored
Merge pull request #565 from atlanhq/APP-5259
APP-5259: Added secure agent support to the `OracleCrawler`
2 parents 2a7872b + 24ddc59 commit b799abe

File tree

7 files changed

+419
-14
lines changed

7 files changed

+419
-14
lines changed

pyatlan/model/packages/base/crawler.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,13 @@ def build_flat_filter(raw_filter: Optional[list]) -> str:
127127
return dumps(to_include)
128128
except (AttributeError, TypeError):
129129
raise ErrorCode.UNABLE_TO_TRANSLATE_FILTERS.exception_with_parameters()
130+
131+
def _add_optional_params(self, params: Dict[str, Optional[Any]]) -> None:
132+
"""
133+
Helper method to add non-None params to `self._parameters`.
134+
135+
:param params: dict of param names and values.
136+
"""
137+
for name, value in params.items():
138+
if value is not None:
139+
self._parameters.append({"name": name, "value": value})

pyatlan/model/packages/oracle_crawler.py

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

3-
from typing import List, Optional
3+
from enum import Enum
4+
from json import dumps
5+
from typing import Any, Dict, List, Optional
46

57
from pyatlan.model.enums import AtlanConnectorType, WorkflowPackage
68
from pyatlan.model.packages.base.crawler import AbstractCrawler
@@ -34,6 +36,26 @@ class OracleCrawler(AbstractCrawler):
3436
"https://docs.oracle.com/sp_common/book-template/ohc-common/img/favicon.ico"
3537
)
3638

39+
class AuthType(str, Enum):
40+
BASIC = "basic"
41+
KERBEROS = "kerberos"
42+
43+
class AwsAuthMethod(str, Enum):
44+
IAM = "iam"
45+
IAM_ASSUME_ROLE = "iam-assume-role"
46+
ACCESS_KEY = "access-key"
47+
48+
class AzureAuthMethod(str, Enum):
49+
MANAGED_IDENTITY = "managed_identity"
50+
SERVICE_PRINCIPAL = "service_principal"
51+
52+
class SecretStore(str, Enum):
53+
SECRET_INJECTION_ENV = "secretinjectionenvironment"
54+
AWS_SECRET_MANAGER = "awssecretmanager"
55+
AZURE_KEY_VAULT = "azurekeyvault"
56+
GCP_SECRET_MANAGER = "gcpsecretmanager"
57+
CUSTOM = "custom"
58+
3759
def __init__(
3860
self,
3961
connection_name: str,
@@ -45,6 +67,7 @@ def __init__(
4567
row_limit: int = 10000,
4668
):
4769
self._advanced_config = False
70+
self._agent_config = False
4871
super().__init__(
4972
connection_name=connection_name,
5073
connection_type=self._CONNECTOR_TYPE,
@@ -95,6 +118,75 @@ def direct(
95118
self._parameters.append(dict(name="extraction-method", value="direct"))
96119
return self
97120

121+
def agent_config(
122+
self,
123+
hostname: str,
124+
default_db_name: str,
125+
sid: str,
126+
agent_name: str,
127+
port: int = 1521,
128+
user_env_var: Optional[str] = None,
129+
password_env_var: Optional[str] = None,
130+
secret_store: SecretStore = SecretStore.CUSTOM,
131+
auth_type: AuthType = AuthType.BASIC,
132+
aws_region: str = "us-east-1",
133+
aws_auth_method: AwsAuthMethod = AwsAuthMethod.IAM,
134+
azure_auth_method: AzureAuthMethod = AzureAuthMethod.MANAGED_IDENTITY,
135+
secret_path: Optional[str] = None,
136+
principal: Optional[str] = None,
137+
azure_vault_name: Optional[str] = None,
138+
agent_custom_config: Optional[Dict[str, Any]] = None,
139+
) -> OracleCrawler:
140+
"""
141+
Configure the agent for Oracle extraction.
142+
143+
:param hostname: host address of the Oracle instance.
144+
:param default_db_name: default database name.
145+
:param sid: SID (system identifier) of the Oracle instance.
146+
:param agent_name: name of the agent.
147+
:param secret_store: secret store to use (e.g AWS, Azure, GCP, etc)
148+
:param port: port number for the Oracle instance. Defaults to `1521`.
149+
:param user_env_var: (optional) environment variable storing the username.
150+
:param password_env_var: (optional) environment variable storing the password.
151+
:param auth_type: authentication type (`basic` or `kerberos`). Defaults to `basic`.
152+
:param aws_region: AWS region where secrets are stored. Defaults to `us-east-1`.
153+
:param aws_auth_method: AWS authentication method (`iam`, `iam-assume-role`, `access-key`). Defaults to `iam`.
154+
:param azure_auth_method: Azure authentication method (`managed_identity` or `service_principal`). Defaults to `managed_identity`.
155+
:param secret_path: (optional) path to the secret in the secret manager.
156+
:param principal: (optional) Kerberos principal (required if using Kerberos authentication).
157+
:param azure_vault_name: (optional) Azure Key Vault name (required if using Azure secret store).
158+
:param agent_custom_config: (optional Custom JSON configuration for the agent.
159+
160+
:returns: crawler, set up to extraction from offline agent.
161+
"""
162+
self._agent_config = True
163+
_agent_dict = {
164+
"host": hostname,
165+
"port": port,
166+
"auth-type": auth_type,
167+
"database": default_db_name,
168+
"extra-service": sid,
169+
"agent-name": agent_name,
170+
"secret-manager": secret_store,
171+
"user-env": user_env_var,
172+
"password-env": password_env_var,
173+
"agent-config": agent_custom_config,
174+
"aws-auth-method": aws_auth_method,
175+
"aws-region": aws_region,
176+
"azure-auth-method": azure_auth_method,
177+
}
178+
if secret_path:
179+
_agent_dict["secret-path"] = secret_path
180+
if principal:
181+
_agent_dict["extra-principal"] = principal
182+
if agent_custom_config:
183+
_agent_dict["agent-config"] = agent_custom_config
184+
if azure_vault_name:
185+
_agent_dict["azure-vault-name"] = azure_vault_name
186+
self._parameters.append(dict(name="extraction-method", value="agent"))
187+
self._parameters.append(dict(name="agent-json", value=dumps(_agent_dict)))
188+
return self
189+
98190
def basic_auth(
99191
self,
100192
username: str,
@@ -133,7 +225,14 @@ def include(self, assets: dict) -> OracleCrawler:
133225
include_assets = assets or {}
134226
to_include = self.build_hierarchical_filter(include_assets)
135227
self._parameters.append(
136-
dict(dict(name="include-filter", value=to_include or "{}"))
228+
dict(
229+
dict(
230+
name="include-filter"
231+
if not self._agent_config
232+
else "include-filter-agent",
233+
value=to_include or "{}",
234+
)
235+
)
137236
)
138237
return self
139238

@@ -148,7 +247,14 @@ def exclude(self, assets: dict) -> OracleCrawler:
148247
"""
149248
exclude_assets = assets or {}
150249
to_exclude = self.build_hierarchical_filter(exclude_assets)
151-
self._parameters.append(dict(name="exclude-filter", value=to_exclude or "{}"))
250+
self._parameters.append(
251+
dict(
252+
name="exclude-filter"
253+
if not self._agent_config
254+
else "exclude-filter-agent",
255+
value=to_exclude or "{}",
256+
)
257+
)
152258
return self
153259

154260
def exclude_regex(self, regex: str) -> OracleCrawler:
@@ -199,9 +305,6 @@ def _set_required_metadata_params(self):
199305
self._parameters.append(
200306
{"name": "credentials-fetch-strategy", "value": "credential_guid"}
201307
)
202-
self._parameters.append(
203-
{"name": "credential-guid", "value": "{{credentialGuid}}"}
204-
)
205308
self._parameters.append(dict(name="publish-mode", value="production"))
206309
self._parameters.append(dict(name="atlas-auth-type", value="internal"))
207310
self._parameters.append(
@@ -218,6 +321,10 @@ def _set_required_metadata_params(self):
218321
),
219322
}
220323
)
324+
if not self._agent_config:
325+
self._parameters.append(
326+
{"name": "credential-guid", "value": "{{credentialGuid}}"}
327+
)
221328

222329
def _get_metadata(self) -> WorkflowMetadata:
223330
self._set_required_metadata_params()

tests/unit/data/package_requests/oracle_crawler_basic.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,6 @@
7474
"name": "credentials-fetch-strategy",
7575
"value": "credential_guid"
7676
},
77-
{
78-
"name": "credential-guid",
79-
"value": "{{credentialGuid}}"
80-
},
8177
{
8278
"name": "publish-mode",
8379
"value": "production"
@@ -93,6 +89,10 @@
9389
{
9490
"name": "connection",
9591
"value": "{\"typeName\": \"Connection\", \"attributes\": {\"qualifiedName\": \"default/oracle/123456\", \"name\": \"test-oracle-conn\", \"adminUsers\": [], \"adminGroups\": [], \"connectorName\": \"oracle\", \"isDiscoverable\": true, \"isEditable\": false, \"adminRoles\": [\"admin-guid-1234\"], \"category\": \"warehouse\", \"allowQuery\": true, \"allowQueryPreview\": true, \"rowLimit\": 10000, \"defaultCredentialGuid\": \"{{credentialGuid}}\", \"sourceLogo\": \"https://docs.oracle.com/sp_common/book-template/ohc-common/img/favicon.ico\"}, \"guid\": \"-1234567890000000000000000\"}"
92+
},
93+
{
94+
"name": "credential-guid",
95+
"value": "{{credentialGuid}}"
9696
}
9797
]
9898
},
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
{
2+
"metadata": {
3+
"annotations": {
4+
"orchestration.atlan.com/allowSchedule": "true",
5+
"orchestration.atlan.com/categories": "warehouse,crawler",
6+
"orchestration.atlan.com/dependentPackage": "",
7+
"orchestration.atlan.com/docsUrl": "https://ask.atlan.com/hc/en-us/articles/6849958872861",
8+
"orchestration.atlan.com/emoji": "🚀",
9+
"orchestration.atlan.com/icon": "https://docs.oracle.com/sp_common/book-template/ohc-common/img/favicon.ico",
10+
"orchestration.atlan.com/logo": "https://docs.oracle.com/sp_common/book-template/ohc-common/img/favicon.ico",
11+
"orchestration.atlan.com/marketplaceLink": "https://packages.atlan.com/-/web/detail/@atlan/oracle",
12+
"orchestration.atlan.com/name": "Oracle Assets",
13+
"package.argoproj.io/author": "Atlan",
14+
"package.argoproj.io/description": "Package to crawl Oracle assets and publish to Atlan for discovery",
15+
"package.argoproj.io/homepage": "https://packages.atlan.com/-/web/detail/@atlan/oracle",
16+
"package.argoproj.io/keywords": "[\"oracle\",\"warehouse\",\"connector\",\"crawler\"]",
17+
"package.argoproj.io/name": "@atlan/oracle",
18+
"package.argoproj.io/registry": "https://packages.atlan.com",
19+
"package.argoproj.io/repository": "git+https://github.com/atlanhq/marketplace-packages.git",
20+
"package.argoproj.io/support": "[email protected]",
21+
"orchestration.atlan.com/atlanName": "atlan-oracle-default-oracle-123456"
22+
},
23+
"labels": {
24+
"orchestration.atlan.com/certified": "true",
25+
"orchestration.atlan.com/source": "oracle",
26+
"orchestration.atlan.com/sourceCategory": "warehouse",
27+
"orchestration.atlan.com/type": "connector",
28+
"orchestration.atlan.com/verified": "true",
29+
"package.argoproj.io/installer": "argopm",
30+
"package.argoproj.io/name": "a-t-ratlans-l-a-s-horacle",
31+
"package.argoproj.io/registry": "httpsc-o-l-o-ns-l-a-s-hs-l-a-s-hpackages.atlan.com",
32+
"orchestration.atlan.com/default-oracle-123456": "true",
33+
"orchestration.atlan.com/atlan-ui": "true"
34+
},
35+
"name": "atlan-oracle-123456",
36+
"namespace": "default"
37+
},
38+
"spec": {
39+
"entrypoint": "main",
40+
"templates": [
41+
{
42+
"name": "main",
43+
"dag": {
44+
"tasks": [
45+
{
46+
"name": "run",
47+
"arguments": {
48+
"parameters": [
49+
{
50+
"name": "extraction-method",
51+
"value": "agent"
52+
},
53+
{
54+
"name": "agent-json",
55+
"value": "{\"host\": \"test.oracle.com\", \"port\": 1234, \"auth-type\": \"basic\", \"database\": \"test-db\", \"extra-service\": \"test-sid\", \"agent-name\": \"test-agent\", \"secret-manager\": \"awssecretmanager\", \"user-env\": null, \"password-env\": null, \"agent-config\": {\"test\": \"config\"}, \"aws-auth-method\": \"iam-assume-role\", \"aws-region\": \"us-east-1\", \"azure-auth-method\": \"managed_identity\", \"secret-path\": \"some/test/path\"}"
56+
},
57+
{
58+
"name": "include-filter-agent",
59+
"value": "{\"^t1$\": [\"^t11$\", \"^t12$\", \"^t13$\"]}"
60+
},
61+
{
62+
"name": "exclude-filter-agent",
63+
"value": "{\"^t2$\": [\"^t21$\", \"^t22$\", \"^t23$\"]}"
64+
},
65+
{
66+
"name": "use-jdbc-internal-methods",
67+
"value": "true"
68+
},
69+
{
70+
"name": "use-source-schema-filtering",
71+
"value": "false"
72+
},
73+
{
74+
"name": "credentials-fetch-strategy",
75+
"value": "credential_guid"
76+
},
77+
{
78+
"name": "publish-mode",
79+
"value": "production"
80+
},
81+
{
82+
"name": "atlas-auth-type",
83+
"value": "internal"
84+
},
85+
{
86+
"name": "advanced-config-strategy",
87+
"value": "custom"
88+
},
89+
{
90+
"name": "connection",
91+
"value": "{\"typeName\": \"Connection\", \"attributes\": {\"qualifiedName\": \"default/oracle/123456\", \"name\": \"test-oracle-conn\", \"adminUsers\": [], \"adminGroups\": [], \"connectorName\": \"oracle\", \"isDiscoverable\": true, \"isEditable\": false, \"adminRoles\": [\"admin-guid-1234\"], \"category\": \"warehouse\", \"allowQuery\": true, \"allowQueryPreview\": true, \"rowLimit\": 10000, \"defaultCredentialGuid\": \"{{credentialGuid}}\", \"sourceLogo\": \"https://docs.oracle.com/sp_common/book-template/ohc-common/img/favicon.ico\"}, \"guid\": \"-1234567890000000000000000\"}"
92+
}
93+
]
94+
},
95+
"templateRef": {
96+
"name": "atlan-oracle",
97+
"template": "main",
98+
"clusterScope": true
99+
}
100+
}
101+
]
102+
}
103+
}
104+
],
105+
"workflowMetadata": {
106+
"annotations": {
107+
"package.argoproj.io/name": "@atlan/oracle"
108+
}
109+
}
110+
},
111+
"payload": []
112+
}

0 commit comments

Comments
 (0)