mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-21 23:48:47 +00:00
parent
f0049853ec
commit
c9a017d8db
@ -8,6 +8,11 @@ source:
|
|||||||
password: password
|
password: password
|
||||||
securityToken: securityToken
|
securityToken: securityToken
|
||||||
sobjectName: sobjectName
|
sobjectName: sobjectName
|
||||||
|
# sslConfig:
|
||||||
|
# caCertificate: |
|
||||||
|
# -----BEGIN CERTIFICATE-----
|
||||||
|
# sample caCertificateData
|
||||||
|
# -----END CERTIFICATE-----
|
||||||
# salesforceApiVersion: 42.0
|
# salesforceApiVersion: 42.0
|
||||||
# salesforceDomain: login
|
# salesforceDomain: login
|
||||||
sourceConfig:
|
sourceConfig:
|
||||||
|
@ -14,7 +14,7 @@ Source connection handler
|
|||||||
"""
|
"""
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from simple_salesforce import Salesforce
|
from simple_salesforce.api import Salesforce
|
||||||
from sqlalchemy.engine import Engine
|
from sqlalchemy.engine import Engine
|
||||||
|
|
||||||
from metadata.generated.schema.entity.automations.workflow import (
|
from metadata.generated.schema.entity.automations.workflow import (
|
||||||
@ -32,11 +32,12 @@ def get_connection(connection: SalesforceConnection) -> Engine:
|
|||||||
Create connection
|
Create connection
|
||||||
"""
|
"""
|
||||||
return Salesforce(
|
return Salesforce(
|
||||||
connection.username,
|
username=connection.username,
|
||||||
password=connection.password.get_secret_value(),
|
password=connection.password.get_secret_value(),
|
||||||
security_token=connection.securityToken.get_secret_value(),
|
security_token=connection.securityToken.get_secret_value(),
|
||||||
domain=connection.salesforceDomain,
|
domain=connection.salesforceDomain,
|
||||||
version=connection.salesforceApiVersion,
|
version=connection.salesforceApiVersion,
|
||||||
|
**connection.connectionArguments.root if connection.connectionArguments else {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ from metadata.utils import fqn
|
|||||||
from metadata.utils.constants import DEFAULT_DATABASE
|
from metadata.utils.constants import DEFAULT_DATABASE
|
||||||
from metadata.utils.filters import filter_by_table
|
from metadata.utils.filters import filter_by_table
|
||||||
from metadata.utils.logger import ingestion_logger
|
from metadata.utils.logger import ingestion_logger
|
||||||
|
from metadata.utils.ssl_manager import SSLManager, check_ssl_and_init
|
||||||
|
|
||||||
logger = ingestion_logger()
|
logger = ingestion_logger()
|
||||||
|
|
||||||
@ -77,6 +78,11 @@ class SalesforceSource(DatabaseServiceSource):
|
|||||||
)
|
)
|
||||||
self.metadata = metadata
|
self.metadata = metadata
|
||||||
self.service_connection = self.config.serviceConnection.root.config
|
self.service_connection = self.config.serviceConnection.root.config
|
||||||
|
self.ssl_manager: SSLManager = check_ssl_and_init(self.service_connection)
|
||||||
|
if self.ssl_manager:
|
||||||
|
self.service_connection = self.ssl_manager.setup_ssl(
|
||||||
|
self.service_connection
|
||||||
|
)
|
||||||
self.client = get_connection(self.service_connection)
|
self.client = get_connection(self.service_connection)
|
||||||
self.table_constraints = None
|
self.table_constraints = None
|
||||||
self.database_source_state = set()
|
self.database_source_state = set()
|
||||||
|
@ -39,11 +39,15 @@ from metadata.generated.schema.entity.services.connections.database.postgresConn
|
|||||||
from metadata.generated.schema.entity.services.connections.database.redshiftConnection import (
|
from metadata.generated.schema.entity.services.connections.database.redshiftConnection import (
|
||||||
RedshiftConnection,
|
RedshiftConnection,
|
||||||
)
|
)
|
||||||
|
from metadata.generated.schema.entity.services.connections.database.salesforceConnection import (
|
||||||
|
SalesforceConnection,
|
||||||
|
)
|
||||||
from metadata.generated.schema.entity.services.connections.messaging.kafkaConnection import (
|
from metadata.generated.schema.entity.services.connections.messaging.kafkaConnection import (
|
||||||
KafkaConnection,
|
KafkaConnection,
|
||||||
)
|
)
|
||||||
from metadata.generated.schema.security.ssl import verifySSLConfig
|
from metadata.generated.schema.security.ssl import verifySSLConfig
|
||||||
from metadata.ingestion.connections.builders import init_empty_connection_arguments
|
from metadata.ingestion.connections.builders import init_empty_connection_arguments
|
||||||
|
from metadata.ingestion.models.custom_pydantic import CustomSecretStr
|
||||||
from metadata.ingestion.source.connections import get_connection
|
from metadata.ingestion.source.connections import get_connection
|
||||||
from metadata.utils.logger import utils_logger
|
from metadata.utils.logger import utils_logger
|
||||||
|
|
||||||
@ -126,6 +130,25 @@ class SSLManager:
|
|||||||
)
|
)
|
||||||
return connection
|
return connection
|
||||||
|
|
||||||
|
@setup_ssl.register(SalesforceConnection)
|
||||||
|
def _(self, connection):
|
||||||
|
import requests # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
connection: SalesforceConnection = cast(SalesforceConnection, connection)
|
||||||
|
connection.connectionArguments = (
|
||||||
|
connection.connectionArguments or init_empty_connection_arguments()
|
||||||
|
)
|
||||||
|
session = requests.Session()
|
||||||
|
if self.ca_file_path:
|
||||||
|
session.verify = self.ca_file_path
|
||||||
|
if self.cert_file_path and self.key_file_path:
|
||||||
|
session.cert = (self.cert_file_path, self.key_file_path)
|
||||||
|
connection.connectionArguments.root = (
|
||||||
|
connection.connectionArguments.root or {}
|
||||||
|
) # to satisfy mypy
|
||||||
|
connection.connectionArguments.root["session"] = session
|
||||||
|
return connection
|
||||||
|
|
||||||
@setup_ssl.register(QlikSenseConnection)
|
@setup_ssl.register(QlikSenseConnection)
|
||||||
def _(self, connection):
|
def _(self, connection):
|
||||||
return {
|
return {
|
||||||
@ -147,7 +170,22 @@ class SSLManager:
|
|||||||
|
|
||||||
|
|
||||||
@singledispatch
|
@singledispatch
|
||||||
def check_ssl_and_init(_):
|
def check_ssl_and_init(_) -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@check_ssl_and_init.register(cls=SalesforceConnection)
|
||||||
|
def _(connection) -> Union[SSLManager, None]:
|
||||||
|
service_connection = cast(SalesforceConnection, connection)
|
||||||
|
ssl: Optional[verifySSLConfig.SslConfig] = service_connection.sslConfig
|
||||||
|
if ssl and ssl.root.caCertificate:
|
||||||
|
ssl_dict: dict[str, Union[CustomSecretStr, None]] = {
|
||||||
|
"ca": ssl.root.caCertificate
|
||||||
|
}
|
||||||
|
if (ssl.root.sslCertificate) and (ssl.root.sslKey):
|
||||||
|
ssl_dict["cert"] = ssl.root.sslCertificate
|
||||||
|
ssl_dict["key"] = ssl.root.sslKey
|
||||||
|
return SSLManager(**ssl_dict)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -182,6 +220,7 @@ def _(connection):
|
|||||||
|
|
||||||
def get_ssl_connection(service_config):
|
def get_ssl_connection(service_config):
|
||||||
try:
|
try:
|
||||||
|
# To be cleaned up as part of https://github.com/open-metadata/OpenMetadata/issues/15913
|
||||||
ssl_manager: SSLManager = check_ssl_and_init(service_config)
|
ssl_manager: SSLManager = check_ssl_and_init(service_config)
|
||||||
if ssl_manager:
|
if ssl_manager:
|
||||||
service_config = ssl_manager.setup_ssl(service_config)
|
service_config = ssl_manager.setup_ssl(service_config)
|
||||||
|
@ -433,7 +433,7 @@ class SalesforceUnitTest(TestCase):
|
|||||||
@patch(
|
@patch(
|
||||||
"metadata.ingestion.source.database.salesforce.metadata.SalesforceSource.test_connection"
|
"metadata.ingestion.source.database.salesforce.metadata.SalesforceSource.test_connection"
|
||||||
)
|
)
|
||||||
@patch("simple_salesforce.Salesforce")
|
@patch("simple_salesforce.api.Salesforce")
|
||||||
def __init__(self, methodName, salesforce, test_connection) -> None:
|
def __init__(self, methodName, salesforce, test_connection) -> None:
|
||||||
super().__init__(methodName)
|
super().__init__(methodName)
|
||||||
test_connection.return_value = False
|
test_connection.return_value = False
|
||||||
@ -461,3 +461,41 @@ class SalesforceUnitTest(TestCase):
|
|||||||
SALESFORCE_FIELDS[i]["type"].upper()
|
SALESFORCE_FIELDS[i]["type"].upper()
|
||||||
)
|
)
|
||||||
assert result == EXPECTED_COLUMN_TYPE[i]
|
assert result == EXPECTED_COLUMN_TYPE[i]
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
"metadata.ingestion.source.database.salesforce.metadata.SalesforceSource.test_connection"
|
||||||
|
)
|
||||||
|
@patch("simple_salesforce.api.Salesforce")
|
||||||
|
def test_check_ssl(self, salesforce, test_connection) -> None:
|
||||||
|
mock_salesforce_config["source"]["serviceConnection"]["config"]["sslConfig"] = {
|
||||||
|
"caCertificate": """
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
sample caCertificateData
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_salesforce_config["source"]["serviceConnection"]["config"]["sslConfig"][
|
||||||
|
"sslKey"
|
||||||
|
] = """
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
sample caCertificateData
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
"""
|
||||||
|
mock_salesforce_config["source"]["serviceConnection"]["config"]["sslConfig"][
|
||||||
|
"sslCertificate"
|
||||||
|
] = """
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
sample sslCertificateData
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
test_connection.return_value = False
|
||||||
|
self.config = OpenMetadataWorkflowConfig.model_validate(mock_salesforce_config)
|
||||||
|
self.salesforce_source = SalesforceSource.create(
|
||||||
|
mock_salesforce_config["source"],
|
||||||
|
self.config.workflowConfig.openMetadataServerConfig,
|
||||||
|
)
|
||||||
|
self.assertTrue(self.salesforce_source.ssl_manager.ca_file_path)
|
||||||
|
self.assertTrue(self.salesforce_source.ssl_manager.cert_file_path)
|
||||||
|
self.assertTrue(self.salesforce_source.ssl_manager.key_file_path)
|
||||||
|
@ -53,6 +53,10 @@ These are the permissions you will require to fetch the metadata from Salesforce
|
|||||||
- **Salesforce Domain**: When connecting to Salesforce, you can specify the domain to use for accessing the platform. The common domains include `login` and `test`, and you can also utilize Salesforce My Domain.
|
- **Salesforce Domain**: When connecting to Salesforce, you can specify the domain to use for accessing the platform. The common domains include `login` and `test`, and you can also utilize Salesforce My Domain.
|
||||||
By default, the domain `login` is used for accessing Salesforce.
|
By default, the domain `login` is used for accessing Salesforce.
|
||||||
|
|
||||||
|
**SSL Configuration**
|
||||||
|
|
||||||
|
In order to integrate SSL in the Metadata Ingestion Config, the user will have to add the SSL config under sslConfig which is placed in the source.
|
||||||
|
|
||||||
{% partial file="/v1.5/connectors/database/advanced-configuration.md" /%}
|
{% partial file="/v1.5/connectors/database/advanced-configuration.md" /%}
|
||||||
|
|
||||||
{% /extraContent %}
|
{% /extraContent %}
|
||||||
|
@ -67,6 +67,7 @@ public final class ClassConverterFactory {
|
|||||||
Map.entry(SupersetConnection.class, new SupersetConnectionClassConverter()),
|
Map.entry(SupersetConnection.class, new SupersetConnectionClassConverter()),
|
||||||
Map.entry(SSOAuthMechanism.class, new SSOAuthMechanismClassConverter()),
|
Map.entry(SSOAuthMechanism.class, new SSOAuthMechanismClassConverter()),
|
||||||
Map.entry(TableauConnection.class, new TableauConnectionClassConverter()),
|
Map.entry(TableauConnection.class, new TableauConnectionClassConverter()),
|
||||||
|
Map.entry(SalesforceConnection.class, new SalesforceConnectorClassConverter()),
|
||||||
Map.entry(
|
Map.entry(
|
||||||
TestServiceConnectionRequest.class,
|
TestServiceConnectionRequest.class,
|
||||||
new TestServiceConnectionRequestClassConverter()),
|
new TestServiceConnectionRequestClassConverter()),
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 Collate
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.openmetadata.service.secrets.converter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.openmetadata.schema.security.ssl.ValidateSSLClientConfig;
|
||||||
|
import org.openmetadata.schema.services.connections.database.SalesforceConnection;
|
||||||
|
import org.openmetadata.service.util.JsonUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter class to get an `Salesforce` object.
|
||||||
|
*/
|
||||||
|
public class SalesforceConnectorClassConverter extends ClassConverter {
|
||||||
|
|
||||||
|
private static final List<Class<?>> SSL_SOURCE_CLASS = List.of(ValidateSSLClientConfig.class);
|
||||||
|
|
||||||
|
public SalesforceConnectorClassConverter() {
|
||||||
|
super(SalesforceConnection.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object convert(Object object) {
|
||||||
|
SalesforceConnection salesforceConnection =
|
||||||
|
(SalesforceConnection) JsonUtils.convertValue(object, this.clazz);
|
||||||
|
|
||||||
|
// Convert the `sslConfig` field to the appropriate class
|
||||||
|
tryToConvert(salesforceConnection.getSslConfig(), SSL_SOURCE_CLASS)
|
||||||
|
.ifPresent(obj -> salesforceConnection.setSslConfig((ValidateSSLClientConfig) obj));
|
||||||
|
|
||||||
|
return salesforceConnection;
|
||||||
|
}
|
||||||
|
}
|
@ -17,11 +17,12 @@ import org.openmetadata.schema.services.connections.dashboard.SupersetConnection
|
|||||||
import org.openmetadata.schema.services.connections.dashboard.TableauConnection;
|
import org.openmetadata.schema.services.connections.dashboard.TableauConnection;
|
||||||
import org.openmetadata.schema.services.connections.database.BigQueryConnection;
|
import org.openmetadata.schema.services.connections.database.BigQueryConnection;
|
||||||
import org.openmetadata.schema.services.connections.database.DatalakeConnection;
|
import org.openmetadata.schema.services.connections.database.DatalakeConnection;
|
||||||
|
import org.openmetadata.schema.services.connections.database.IcebergConnection;
|
||||||
import org.openmetadata.schema.services.connections.database.MysqlConnection;
|
import org.openmetadata.schema.services.connections.database.MysqlConnection;
|
||||||
import org.openmetadata.schema.services.connections.database.PostgresConnection;
|
import org.openmetadata.schema.services.connections.database.PostgresConnection;
|
||||||
|
import org.openmetadata.schema.services.connections.database.SalesforceConnection;
|
||||||
import org.openmetadata.schema.services.connections.database.TrinoConnection;
|
import org.openmetadata.schema.services.connections.database.TrinoConnection;
|
||||||
import org.openmetadata.schema.services.connections.database.datalake.GCSConfig;
|
import org.openmetadata.schema.services.connections.database.datalake.GCSConfig;
|
||||||
import org.openmetadata.schema.services.connections.metadata.OpenMetadataConnection;
|
|
||||||
import org.openmetadata.schema.services.connections.pipeline.AirflowConnection;
|
import org.openmetadata.schema.services.connections.pipeline.AirflowConnection;
|
||||||
import org.openmetadata.schema.services.connections.search.ElasticSearchConnection;
|
import org.openmetadata.schema.services.connections.search.ElasticSearchConnection;
|
||||||
import org.openmetadata.schema.services.connections.storage.GCSConnection;
|
import org.openmetadata.schema.services.connections.storage.GCSConnection;
|
||||||
@ -42,14 +43,15 @@ public class ClassConverterFactoryTest {
|
|||||||
GCSConnection.class,
|
GCSConnection.class,
|
||||||
ElasticSearchConnection.class,
|
ElasticSearchConnection.class,
|
||||||
LookerConnection.class,
|
LookerConnection.class,
|
||||||
OpenMetadataConnection.class,
|
|
||||||
SSOAuthMechanism.class,
|
SSOAuthMechanism.class,
|
||||||
SupersetConnection.class,
|
SupersetConnection.class,
|
||||||
GCPCredentials.class,
|
GCPCredentials.class,
|
||||||
TableauConnection.class,
|
TableauConnection.class,
|
||||||
TestServiceConnectionRequest.class,
|
TestServiceConnectionRequest.class,
|
||||||
TrinoConnection.class,
|
TrinoConnection.class,
|
||||||
Workflow.class
|
Workflow.class,
|
||||||
|
SalesforceConnection.class,
|
||||||
|
IcebergConnection.class,
|
||||||
})
|
})
|
||||||
void testClassConverterIsSet(Class<?> clazz) {
|
void testClassConverterIsSet(Class<?> clazz) {
|
||||||
assertFalse(
|
assertFalse(
|
||||||
@ -58,6 +60,6 @@ public class ClassConverterFactoryTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testClassConvertedMapIsNotModified() {
|
void testClassConvertedMapIsNotModified() {
|
||||||
assertEquals(20, ClassConverterFactory.getConverterMap().size());
|
assertEquals(26, ClassConverterFactory.getConverterMap().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"default": "login"
|
"default": "login"
|
||||||
},
|
},
|
||||||
|
"sslConfig": {
|
||||||
|
"title": "SSL Configuration",
|
||||||
|
"description": "SSL Configuration details.",
|
||||||
|
"$ref": "../../../../security/ssl/verifySSLConfig.json#/definitions/sslConfig"
|
||||||
|
},
|
||||||
"connectionOptions": {
|
"connectionOptions": {
|
||||||
"title": "Connection Options",
|
"title": "Connection Options",
|
||||||
"$ref": "../connectionBasicType.json#/definitions/connectionOptions"
|
"$ref": "../connectionBasicType.json#/definitions/connectionOptions"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# Salesforce
|
# Salesforce
|
||||||
|
|
||||||
In this section, we provide guides and references to use the Salesforce connector.
|
In this section, we provide guides and references to use the Salesforce connector.
|
||||||
@ -67,6 +68,13 @@ By default, the domain `login` is used for accessing Salesforce.
|
|||||||
|
|
||||||
$$
|
$$
|
||||||
|
|
||||||
|
|
||||||
|
$$section
|
||||||
|
### SSL CA $(id="caCertificate")
|
||||||
|
The CA certificate used for SSL validation to connect to Salesforce.
|
||||||
|
$$
|
||||||
|
|
||||||
|
|
||||||
$$section
|
$$section
|
||||||
### Connection Options $(id="connectionOptions")
|
### Connection Options $(id="connectionOptions")
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user