2022-04-11 18:38:26 +02:00
|
|
|
# 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.
|
|
|
|
"""
|
|
|
|
Metadata DAG common functions
|
|
|
|
"""
|
|
|
|
import json
|
2022-11-30 08:30:45 +01:00
|
|
|
import uuid
|
2022-04-20 09:14:27 +02:00
|
|
|
from datetime import datetime, timedelta
|
2022-11-30 08:30:45 +01:00
|
|
|
from functools import partial
|
2024-05-16 15:33:42 +02:00
|
|
|
from typing import Callable, Optional, Union
|
2022-04-11 18:38:26 +02:00
|
|
|
|
|
|
|
from airflow import DAG
|
2022-07-28 14:46:25 +02:00
|
|
|
from openmetadata_managed_apis.api.utils import clean_dag_id
|
2022-11-09 13:00:22 +05:30
|
|
|
from pydantic import ValidationError
|
|
|
|
from requests.utils import quote
|
2022-04-11 18:38:26 +02:00
|
|
|
|
2024-09-11 13:36:53 +05:30
|
|
|
from metadata.generated.schema.entity.services.apiService import ApiService
|
2022-06-02 20:40:53 +02:00
|
|
|
from metadata.generated.schema.entity.services.dashboardService import DashboardService
|
|
|
|
from metadata.generated.schema.entity.services.databaseService import DatabaseService
|
|
|
|
from metadata.generated.schema.entity.services.messagingService import MessagingService
|
2022-11-16 11:11:54 +05:30
|
|
|
from metadata.generated.schema.entity.services.metadataService import MetadataService
|
2022-06-28 14:58:38 +02:00
|
|
|
from metadata.generated.schema.entity.services.mlmodelService import MlModelService
|
2022-06-02 20:40:53 +02:00
|
|
|
from metadata.generated.schema.entity.services.pipelineService import PipelineService
|
2023-09-08 12:40:48 +05:30
|
|
|
from metadata.generated.schema.entity.services.searchService import SearchService
|
2023-04-12 11:44:46 +02:00
|
|
|
from metadata.generated.schema.entity.services.storageService import StorageService
|
2023-11-13 08:58:38 +01:00
|
|
|
from metadata.generated.schema.metadataIngestion.application import (
|
|
|
|
OpenMetadataApplicationConfig,
|
|
|
|
)
|
2024-06-07 04:36:17 +02:00
|
|
|
from metadata.generated.schema.type.basic import Timestamp, Uuid
|
2022-06-02 20:40:53 +02:00
|
|
|
from metadata.ingestion.ometa.ometa_api import OpenMetadata
|
2024-05-16 15:33:42 +02:00
|
|
|
from metadata.utils import fqn
|
2022-04-11 18:38:26 +02:00
|
|
|
|
2024-01-05 18:14:26 +05:30
|
|
|
# pylint: disable=ungrouped-imports
|
2022-04-11 18:38:26 +02:00
|
|
|
try:
|
|
|
|
from airflow.operators.python import PythonOperator
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
from airflow.operators.python_operator import PythonOperator
|
|
|
|
|
2023-01-23 16:28:17 +01:00
|
|
|
from openmetadata_managed_apis.utils.logger import set_operator_logger, workflow_logger
|
2022-11-09 13:00:22 +05:30
|
|
|
from openmetadata_managed_apis.utils.parser import (
|
|
|
|
parse_service_connection,
|
|
|
|
parse_validation_err,
|
|
|
|
)
|
2022-07-19 14:51:44 +02:00
|
|
|
|
2022-04-11 18:38:26 +02:00
|
|
|
from metadata.generated.schema.entity.services.ingestionPipelines.ingestionPipeline import (
|
|
|
|
IngestionPipeline,
|
2022-11-03 14:37:26 +05:30
|
|
|
PipelineState,
|
2022-04-11 18:38:26 +02:00
|
|
|
)
|
|
|
|
from metadata.generated.schema.metadataIngestion.workflow import (
|
2022-04-29 09:40:05 +02:00
|
|
|
LogLevels,
|
2022-04-11 18:38:26 +02:00
|
|
|
OpenMetadataWorkflowConfig,
|
|
|
|
)
|
2022-06-02 20:40:53 +02:00
|
|
|
from metadata.generated.schema.metadataIngestion.workflow import (
|
|
|
|
Source as WorkflowSource,
|
|
|
|
)
|
|
|
|
from metadata.generated.schema.metadataIngestion.workflow import WorkflowConfig
|
2022-11-09 13:00:22 +05:30
|
|
|
from metadata.ingestion.api.parser import (
|
|
|
|
InvalidWorkflowException,
|
|
|
|
ParsingConfigurationError,
|
|
|
|
)
|
|
|
|
from metadata.ingestion.ometa.utils import model_str
|
2023-08-30 15:49:42 +02:00
|
|
|
from metadata.workflow.metadata import MetadataWorkflow
|
2022-11-09 13:00:22 +05:30
|
|
|
|
|
|
|
logger = workflow_logger()
|
2022-04-11 18:38:26 +02:00
|
|
|
|
2023-09-08 12:40:48 +05:30
|
|
|
ENTITY_CLASS_MAP = {
|
2024-09-11 13:36:53 +05:30
|
|
|
"apiService": ApiService,
|
2023-09-08 12:40:48 +05:30
|
|
|
"databaseService": DatabaseService,
|
|
|
|
"pipelineService": PipelineService,
|
|
|
|
"dashboardService": DashboardService,
|
|
|
|
"messagingService": MessagingService,
|
|
|
|
"mlmodelService": MlModelService,
|
|
|
|
"metadataService": MetadataService,
|
|
|
|
"storageService": StorageService,
|
|
|
|
"searchService": SearchService,
|
|
|
|
}
|
|
|
|
|
2022-04-11 18:38:26 +02:00
|
|
|
|
2022-08-26 07:29:38 +02:00
|
|
|
class InvalidServiceException(Exception):
|
2022-12-21 11:01:54 +01:00
|
|
|
"""
|
|
|
|
The service type we received is not supported
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
class GetServiceException(Exception):
|
2022-08-26 07:29:38 +02:00
|
|
|
"""
|
|
|
|
Exception to be thrown when couldn't fetch the service from server
|
|
|
|
"""
|
|
|
|
|
2022-12-21 11:01:54 +01:00
|
|
|
def __init__(self, service_type: str, service_name: str):
|
|
|
|
self.message = (
|
2023-06-27 12:32:08 +02:00
|
|
|
f"Could not get service from type [{service_type}]. This means that the"
|
2022-12-21 11:01:54 +01:00
|
|
|
" OpenMetadata client running in the Airflow host had issues getting"
|
2023-06-27 12:32:08 +02:00
|
|
|
f" the service [{service_name}]. Make sure the ingestion-bot JWT token"
|
|
|
|
" is valid and that the Workflow is deployed with the latest one. If this error"
|
|
|
|
" persists, recreate the JWT token and redeploy the Workflow."
|
2022-12-21 11:01:54 +01:00
|
|
|
)
|
|
|
|
super().__init__(self.message)
|
|
|
|
|
2022-08-26 07:29:38 +02:00
|
|
|
|
|
|
|
class ClientInitializationError(Exception):
|
|
|
|
"""
|
|
|
|
Exception to be thrown when couldn't initialize the Openmetadata Client
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
2022-06-02 20:40:53 +02:00
|
|
|
def build_source(ingestion_pipeline: IngestionPipeline) -> WorkflowSource:
|
|
|
|
"""
|
|
|
|
Use the service EntityReference to build the Source.
|
|
|
|
Building the source dynamically helps us to not store any
|
|
|
|
sensitive info.
|
|
|
|
:param ingestion_pipeline: With the service ref
|
|
|
|
:return: WorkflowSource
|
|
|
|
"""
|
|
|
|
|
2022-08-26 07:29:38 +02:00
|
|
|
try:
|
|
|
|
metadata = OpenMetadata(config=ingestion_pipeline.openMetadataServerConnection)
|
2023-08-21 11:35:36 +02:00
|
|
|
|
|
|
|
# check we can access OM server
|
|
|
|
metadata.health_check()
|
2022-08-26 07:29:38 +02:00
|
|
|
except Exception as exc:
|
2022-12-21 11:01:54 +01:00
|
|
|
raise ClientInitializationError(
|
|
|
|
f"Failed to initialize the OpenMetadata client due to: {exc}."
|
|
|
|
" Make sure that the Airflow host can reach the OpenMetadata"
|
|
|
|
f" server running at {ingestion_pipeline.openMetadataServerConnection.hostPort}"
|
|
|
|
" and that the client and server are in the same version."
|
|
|
|
)
|
2022-06-02 20:40:53 +02:00
|
|
|
|
|
|
|
service_type = ingestion_pipeline.service.type
|
|
|
|
|
2023-09-08 12:40:48 +05:30
|
|
|
entity_class = ENTITY_CLASS_MAP.get(service_type)
|
2022-11-09 13:00:22 +05:30
|
|
|
try:
|
2023-06-23 06:40:32 +02:00
|
|
|
if service_type == "testSuite":
|
|
|
|
return WorkflowSource(
|
|
|
|
type=service_type,
|
|
|
|
serviceName=ingestion_pipeline.service.name,
|
|
|
|
sourceConfig=ingestion_pipeline.sourceConfig,
|
2024-01-05 18:14:26 +05:30
|
|
|
# retrieved from the test suite workflow using the `sourceConfig.config.entityFullyQualifiedName`
|
|
|
|
serviceConnection=None,
|
2023-06-23 06:40:32 +02:00
|
|
|
)
|
|
|
|
|
2023-09-08 12:40:48 +05:30
|
|
|
if entity_class is None:
|
2022-11-09 13:00:22 +05:30
|
|
|
raise InvalidServiceException(f"Invalid Service Type: {service_type}")
|
2023-09-08 12:40:48 +05:30
|
|
|
|
|
|
|
service = metadata.get_by_name(
|
|
|
|
entity=entity_class,
|
|
|
|
fqn=ingestion_pipeline.service.name,
|
|
|
|
nullable=False,
|
|
|
|
)
|
|
|
|
|
2022-11-09 13:00:22 +05:30
|
|
|
except ValidationError as original_error:
|
|
|
|
try:
|
|
|
|
resp = metadata.client.get(
|
|
|
|
f"{metadata.get_suffix(entity_class)}/name/{quote(model_str(ingestion_pipeline.service.name), safe='')}"
|
|
|
|
)
|
|
|
|
parse_service_connection(resp)
|
|
|
|
except (ValidationError, InvalidWorkflowException) as scoped_error:
|
|
|
|
if isinstance(scoped_error, ValidationError):
|
|
|
|
# Let's catch validations of internal Workflow models, not the Workflow itself
|
|
|
|
object_error = (
|
|
|
|
scoped_error.model.__name__
|
|
|
|
if scoped_error.model is not None
|
|
|
|
else "workflow"
|
|
|
|
)
|
|
|
|
raise ParsingConfigurationError(
|
|
|
|
f"We encountered an error parsing the configuration of your {object_error}.\n"
|
|
|
|
f"{parse_validation_err(scoped_error)}"
|
|
|
|
)
|
|
|
|
raise scoped_error
|
|
|
|
raise ParsingConfigurationError(
|
|
|
|
f"We encountered an error parsing the configuration of your workflow.\n"
|
|
|
|
f"{parse_validation_err(original_error)}"
|
2022-06-28 14:58:38 +02:00
|
|
|
)
|
2022-06-02 20:40:53 +02:00
|
|
|
|
|
|
|
if not service:
|
2022-12-21 11:01:54 +01:00
|
|
|
raise GetServiceException(service_type, ingestion_pipeline.service.name)
|
2022-06-02 20:40:53 +02:00
|
|
|
|
|
|
|
return WorkflowSource(
|
|
|
|
type=service.serviceType.value.lower(),
|
2024-06-05 21:18:37 +02:00
|
|
|
serviceName=service.name.root,
|
2022-06-02 20:40:53 +02:00
|
|
|
serviceConnection=service.connection,
|
|
|
|
sourceConfig=ingestion_pipeline.sourceConfig,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2022-04-11 18:38:26 +02:00
|
|
|
def metadata_ingestion_workflow(workflow_config: OpenMetadataWorkflowConfig):
|
|
|
|
"""
|
|
|
|
Task that creates and runs the ingestion workflow.
|
|
|
|
|
|
|
|
The workflow_config gets cooked form the incoming
|
2022-04-21 17:53:29 +02:00
|
|
|
ingestionPipeline.
|
2022-04-11 18:38:26 +02:00
|
|
|
|
|
|
|
This is the callable used to create the PythonOperator
|
|
|
|
"""
|
2023-01-23 16:28:17 +01:00
|
|
|
|
|
|
|
set_operator_logger(workflow_config)
|
|
|
|
|
2024-06-14 14:08:59 +05:30
|
|
|
config = json.loads(workflow_config.model_dump_json(exclude_defaults=False))
|
2023-08-30 15:49:42 +02:00
|
|
|
workflow = MetadataWorkflow.create(config)
|
2022-08-25 10:01:28 +02:00
|
|
|
|
2022-11-30 08:30:45 +01:00
|
|
|
workflow.execute()
|
|
|
|
workflow.raise_from_status()
|
2024-07-29 09:20:34 +02:00
|
|
|
workflow.print_status()
|
2022-11-30 08:30:45 +01:00
|
|
|
workflow.stop()
|
2022-10-26 11:18:08 +02:00
|
|
|
|
|
|
|
|
2022-04-28 12:20:03 +02:00
|
|
|
def build_workflow_config_property(
|
|
|
|
ingestion_pipeline: IngestionPipeline,
|
|
|
|
) -> WorkflowConfig:
|
|
|
|
"""
|
|
|
|
Prepare the workflow config with logLevels and openMetadataServerConfig
|
|
|
|
:param ingestion_pipeline: Received payload from REST
|
|
|
|
:return: WorkflowConfig
|
|
|
|
"""
|
|
|
|
return WorkflowConfig(
|
2022-04-29 09:40:05 +02:00
|
|
|
loggerLevel=ingestion_pipeline.loggerLevel or LogLevels.INFO,
|
2022-04-28 12:20:03 +02:00
|
|
|
openMetadataServerConfig=ingestion_pipeline.openMetadataServerConnection,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-05-16 15:33:42 +02:00
|
|
|
def clean_name_tag(tag: str) -> Optional[str]:
|
|
|
|
"""
|
2024-05-17 07:31:06 +02:00
|
|
|
Clean the tag to be used in Airflow.
|
|
|
|
Airflow supports 100 characters. We'll keep just 90
|
|
|
|
since we add prefixes on the tags
|
2024-05-16 15:33:42 +02:00
|
|
|
"""
|
|
|
|
if not tag:
|
|
|
|
return None
|
|
|
|
try:
|
2024-05-17 07:31:06 +02:00
|
|
|
return fqn.split(tag)[-1][:90]
|
2024-05-16 15:33:42 +02:00
|
|
|
except Exception as exc:
|
|
|
|
logger.warning("Error cleaning tag: %s", exc)
|
2024-05-17 07:31:06 +02:00
|
|
|
return tag[:90]
|
2024-05-16 15:33:42 +02:00
|
|
|
|
|
|
|
|
2022-04-20 09:14:27 +02:00
|
|
|
def build_dag_configs(ingestion_pipeline: IngestionPipeline) -> dict:
|
|
|
|
"""
|
|
|
|
Prepare kwargs to send to DAG
|
|
|
|
:param ingestion_pipeline: pipeline configs
|
|
|
|
:return: dict to use as kwargs
|
|
|
|
"""
|
2024-06-07 04:36:17 +02:00
|
|
|
|
|
|
|
if ingestion_pipeline.airflowConfig.startDate:
|
|
|
|
start_date = ingestion_pipeline.airflowConfig.startDate.root
|
|
|
|
else:
|
|
|
|
start_date = datetime.now() - timedelta(days=1)
|
|
|
|
|
2022-04-20 09:14:27 +02:00
|
|
|
return {
|
2024-06-05 21:18:37 +02:00
|
|
|
"dag_id": clean_dag_id(ingestion_pipeline.name.root),
|
|
|
|
"description": ingestion_pipeline.description.root
|
2023-05-03 11:37:35 +02:00
|
|
|
if ingestion_pipeline.description is not None
|
|
|
|
else None,
|
2024-06-07 04:36:17 +02:00
|
|
|
"start_date": start_date,
|
2024-06-05 21:18:37 +02:00
|
|
|
"end_date": ingestion_pipeline.airflowConfig.endDate.root
|
2022-06-15 00:06:46 +05:30
|
|
|
if ingestion_pipeline.airflowConfig.endDate
|
|
|
|
else None,
|
2022-04-20 09:14:27 +02:00
|
|
|
"concurrency": ingestion_pipeline.airflowConfig.concurrency,
|
|
|
|
"max_active_runs": ingestion_pipeline.airflowConfig.maxActiveRuns,
|
|
|
|
"default_view": ingestion_pipeline.airflowConfig.workflowDefaultView,
|
|
|
|
"orientation": ingestion_pipeline.airflowConfig.workflowDefaultViewOrientation,
|
|
|
|
"dagrun_timeout": timedelta(ingestion_pipeline.airflowConfig.workflowTimeout)
|
|
|
|
if ingestion_pipeline.airflowConfig.workflowTimeout
|
|
|
|
else None,
|
|
|
|
"is_paused_upon_creation": ingestion_pipeline.airflowConfig.pausePipeline
|
|
|
|
or False,
|
|
|
|
"catchup": ingestion_pipeline.airflowConfig.pipelineCatchup or False,
|
|
|
|
"schedule_interval": ingestion_pipeline.airflowConfig.scheduleInterval,
|
2022-12-05 21:00:46 +01:00
|
|
|
"tags": [
|
|
|
|
"OpenMetadata",
|
2024-05-16 15:33:42 +02:00
|
|
|
clean_name_tag(ingestion_pipeline.displayName)
|
2024-06-05 21:18:37 +02:00
|
|
|
or clean_name_tag(ingestion_pipeline.name.root),
|
2024-05-17 07:31:06 +02:00
|
|
|
f"type:{ingestion_pipeline.pipelineType.value}",
|
|
|
|
f"service:{clean_name_tag(ingestion_pipeline.service.name)}",
|
2022-12-05 21:00:46 +01:00
|
|
|
],
|
2022-04-20 09:14:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-26 17:44:35 +01:00
|
|
|
def send_failed_status_callback(workflow_config: OpenMetadataWorkflowConfig, *_, **__):
|
2022-11-30 08:30:45 +01:00
|
|
|
"""
|
|
|
|
Airflow on_failure_callback to update workflow status if something unexpected
|
|
|
|
happens or if the DAG is externally killed.
|
2022-12-09 10:42:24 +01:00
|
|
|
|
|
|
|
We don't want to initialize the full workflow as it might be failing
|
|
|
|
on the `__init__` call as well. We'll manually prepare the status sending
|
|
|
|
logic.
|
|
|
|
|
|
|
|
In this callback we just care about:
|
|
|
|
- instantiating the ometa client
|
|
|
|
- getting the IngestionPipeline FQN
|
|
|
|
- if exists, update with `Failed` status
|
|
|
|
|
|
|
|
Here the workflow_config is already properly shaped, otherwise
|
|
|
|
the DAG deployment would fail.
|
|
|
|
|
|
|
|
More info on context variables here
|
|
|
|
https://airflow.apache.org/docs/apache-airflow/stable/templates-ref.html#templates-variables
|
2022-11-30 08:30:45 +01:00
|
|
|
"""
|
|
|
|
logger.info("Sending failed status from callback...")
|
2022-12-09 10:42:24 +01:00
|
|
|
|
|
|
|
metadata_config = workflow_config.workflowConfig.openMetadataServerConfig
|
|
|
|
metadata = OpenMetadata(config=metadata_config)
|
|
|
|
|
|
|
|
if workflow_config.ingestionPipelineFQN:
|
|
|
|
logger.info(
|
|
|
|
f"Sending status to Ingestion Pipeline {workflow_config.ingestionPipelineFQN}"
|
|
|
|
)
|
|
|
|
|
|
|
|
pipeline_status = metadata.get_pipeline_status(
|
2024-06-14 14:08:59 +05:30
|
|
|
workflow_config.ingestionPipelineFQN,
|
|
|
|
str(workflow_config.pipelineRunId.root),
|
2022-12-09 10:42:24 +01:00
|
|
|
)
|
2024-06-07 04:36:17 +02:00
|
|
|
pipeline_status.endDate = Timestamp(int(datetime.now().timestamp() * 1000))
|
2022-12-09 10:42:24 +01:00
|
|
|
pipeline_status.pipelineState = PipelineState.failed
|
|
|
|
|
|
|
|
metadata.create_or_update_pipeline_status(
|
|
|
|
workflow_config.ingestionPipelineFQN, pipeline_status
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
logger.info(
|
|
|
|
"Workflow config does not have ingestionPipelineFQN informed. We won't update the status."
|
|
|
|
)
|
2022-11-30 08:30:45 +01:00
|
|
|
|
|
|
|
|
2024-01-05 18:14:26 +05:30
|
|
|
class CustomPythonOperator(PythonOperator):
|
|
|
|
def on_kill(self) -> None:
|
|
|
|
"""
|
|
|
|
Override this method to clean up subprocesses when a task instance
|
|
|
|
gets killed. Any use of the threading, subprocess or multiprocessing
|
|
|
|
module within an operator needs to be cleaned up, or it will leave
|
|
|
|
ghost processes behind.
|
|
|
|
"""
|
|
|
|
workflow_config = self.op_kwargs.get("workflow_config")
|
|
|
|
if workflow_config:
|
2024-01-26 17:44:35 +01:00
|
|
|
send_failed_status_callback(workflow_config)
|
2024-01-05 18:14:26 +05:30
|
|
|
|
|
|
|
|
2022-04-21 17:53:29 +02:00
|
|
|
def build_dag(
|
2022-04-11 18:38:26 +02:00
|
|
|
task_name: str,
|
|
|
|
ingestion_pipeline: IngestionPipeline,
|
2023-11-13 08:58:38 +01:00
|
|
|
workflow_config: Union[OpenMetadataWorkflowConfig, OpenMetadataApplicationConfig],
|
2022-04-21 17:53:29 +02:00
|
|
|
workflow_fn: Callable,
|
2022-04-11 18:38:26 +02:00
|
|
|
) -> DAG:
|
|
|
|
"""
|
|
|
|
Build a simple metadata workflow DAG
|
|
|
|
"""
|
|
|
|
|
2022-04-20 09:14:27 +02:00
|
|
|
with DAG(**build_dag_configs(ingestion_pipeline)) as dag:
|
2022-11-30 08:30:45 +01:00
|
|
|
# Initialize with random UUID4. Will be used by the callback instead of
|
|
|
|
# generating it inside the Workflow itself.
|
2024-06-07 04:36:17 +02:00
|
|
|
workflow_config.pipelineRunId = Uuid(uuid.uuid4())
|
2022-11-30 08:30:45 +01:00
|
|
|
|
2024-01-05 18:14:26 +05:30
|
|
|
CustomPythonOperator(
|
2022-04-11 18:38:26 +02:00
|
|
|
task_id=task_name,
|
2022-04-21 17:53:29 +02:00
|
|
|
python_callable=workflow_fn,
|
2022-04-11 18:38:26 +02:00
|
|
|
op_kwargs={"workflow_config": workflow_config},
|
2022-11-30 08:30:45 +01:00
|
|
|
# There's no need to retry if we have had an error. Wait until the next schedule or manual rerun.
|
2023-08-23 11:22:54 +02:00
|
|
|
retries=ingestion_pipeline.airflowConfig.retries or 0,
|
2022-11-30 08:30:45 +01:00
|
|
|
# each DAG will call its own OpenMetadataWorkflowConfig
|
2024-01-26 17:44:35 +01:00
|
|
|
on_failure_callback=partial(send_failed_status_callback, workflow_config),
|
2022-12-05 21:00:46 +01:00
|
|
|
# Add tag and ownership to easily identify DAGs generated by OM
|
2024-07-29 23:06:39 -07:00
|
|
|
owner=ingestion_pipeline.owners.root[0].name
|
|
|
|
if (ingestion_pipeline.owners and ingestion_pipeline.owners.root)
|
2022-12-05 21:00:46 +01:00
|
|
|
else "openmetadata",
|
2022-04-11 18:38:26 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return dag
|