Google cloud platform 我们如何在GCP composer环境(1.10.6)中使用SFTPToGCSOperator?

Google cloud platform 我们如何在GCP composer环境(1.10.6)中使用SFTPToGCSOperator?,google-cloud-platform,airflow,google-cloud-composer,airflow-operator,Google Cloud Platform,Airflow,Google Cloud Composer,Airflow Operator,这里我想在GCP的作曲家环境(1.10.6)中使用SFTPToGCSOperator。我知道有一个限制,因为操作员只出现在最新版本的气流中,而不出现在composer最新版本1.10.6中 看到引用了吗- 我找到了operator的替代方案,并创建了一个插件类,但我再次面临SFTPOOK类的问题,现在我使用的是旧版本的SFTPOOK类 见以下参考资料- 从afflow.contrib.hooks.sftp\u hook导入SFTPHook 我已经创建了一个插件类,后来它被导入到我的DAG脚本

这里我想在GCP的作曲家环境(1.10.6)中使用SFTPToGCSOperator。我知道有一个限制,因为操作员只出现在最新版本的气流中,而不出现在composer最新版本1.10.6中

看到引用了吗-

我找到了operator的替代方案,并创建了一个插件类,但我再次面临SFTPOOK类的问题,现在我使用的是旧版本的SFTPOOK类

见以下参考资料-

从afflow.contrib.hooks.sftp\u hook导入SFTPHook

我已经创建了一个插件类,后来它被导入到我的DAG脚本中。只有在移动一个文件时,它才能正常工作,在这种情况下,我们需要传递扩展名为的完整文件路径

请参考下面的示例(在这个场景中工作正常)

除此之外,如果我们使用通配符,我的意思是如果我们想从目录中移动所有文件,我得到的get_tree_map方法的错误

请参见下面的DAG代码

import os

from airflow import models
from airflow.models import Variable
from PluginSFTPToGCSOperator import SFTPToGCSOperator
#from airflow.contrib.operators.sftp_to_gcs import SFTPToGCSOperator
from airflow.utils.dates import days_ago

default_args = {"start_date": days_ago(1)}

DIR_path = "/main_dir/sub_dir/"
BUCKET_SRC = "test-gcp-bucket"

with models.DAG(
    "dag_sftp_to_gcs", default_args=default_args, schedule_interval=None
) as dag:

    copy_sftp_to_gcs = SFTPToGCSOperator(
        task_id="t_sftp_to_gcs",
        sftp_conn_id="test_sftp_conn",
        gcp_conn_id="google_cloud_default",
        source_path=os.path.join(DIR_path, "*.gz"),
        destination_bucket=BUCKET_SRC,
    )

    copy_sftp_to_gcs
这里我们在DAG脚本中使用通配符*,请参见下面的插件类

import os
from tempfile import NamedTemporaryFile
from typing import Optional, Union

from airflow.plugins_manager import AirflowPlugin
from airflow import AirflowException
from airflow.contrib.hooks.gcs_hook import GoogleCloudStorageHook
from airflow.models import BaseOperator
from airflow.contrib.hooks.sftp_hook import SFTPHook
from airflow.utils.decorators import apply_defaults

WILDCARD = "*"

class SFTPToGCSOperator(BaseOperator):

    template_fields = ("source_path", "destination_path", "destination_bucket")

    @apply_defaults
    def __init__(
        self,
        source_path: str,
        destination_bucket: str = "destination_bucket",
        destination_path: Optional[str] = None,
        gcp_conn_id: str = "google_cloud_default",
        sftp_conn_id: str = "sftp_conn_plugin",
        delegate_to: Optional[str] = None,
        mime_type: str = "application/octet-stream",
        gzip: bool = False,
        move_object: bool = False,
        *args,
        **kwargs
    ) -> None:
        super().__init__(*args, **kwargs)

        self.source_path = source_path
        self.destination_path = self._set_destination_path(destination_path)
        print('destination_bucket : ',destination_bucket)
        self.destination_bucket = destination_bucket
        self.gcp_conn_id = gcp_conn_id
        self.mime_type = mime_type
        self.delegate_to = delegate_to
        self.gzip = gzip
        self.sftp_conn_id = sftp_conn_id
        self.move_object = move_object

    def execute(self, context):
        print("inside execute")
        gcs_hook = GoogleCloudStorageHook(
            google_cloud_storage_conn_id=self.gcp_conn_id, delegate_to=self.delegate_to
        )

        sftp_hook = SFTPHook(self.sftp_conn_id)

        if WILDCARD in self.source_path:
            total_wildcards = self.source_path.count(WILDCARD)
            if total_wildcards > 1:
                raise AirflowException(
                    "Only one wildcard '*' is allowed in source_path parameter. "
                    "Found {} in {}.".format(total_wildcards, self.source_path)
                )
            print('self.source_path : ',self.source_path)
            prefix, delimiter = self.source_path.split(WILDCARD, 1)
            print('prefix : ',prefix)
            base_path = os.path.dirname(prefix)
            print('base_path : ',base_path)
            files, _, _ = sftp_hook.get_tree_map(
                base_path, prefix=prefix, delimiter=delimiter
            )

            for file in files:
                destination_path = file.replace(base_path, self.destination_path, 1)
                self._copy_single_object(gcs_hook, sftp_hook, file, destination_path)

          else:
            destination_object = (
                self.destination_path
                if self.destination_path
                else self.source_path.rsplit("/", 1)[1]
            )
            self._copy_single_object(
                gcs_hook, sftp_hook, self.source_path, destination_object

            )

    def _copy_single_object(
        self,
        gcs_hook: GoogleCloudStorageHook,
        sftp_hook: SFTPHook,
        source_path: str,
        destination_object: str,
    ) -> None:
    """
    Helper function to copy single object.
    """
        self.log.info(
            "Executing copy of %s to gs://%s/%s",
            source_path,
            self.destination_bucket,
            destination_object,
        )

        with NamedTemporaryFile("w") as tmp:
            sftp_hook.retrieve_file(source_path, tmp.name)
            print('before upload self det object : ',self.destination_bucket)
            gcs_hook.upload(
                self.destination_bucket,
                destination_object,
                tmp.name,
                self.mime_type,
            )

        if self.move_object:
            self.log.info("Executing delete of %s", source_path)
            sftp_hook.delete_file(source_path)


    @staticmethod
    def _set_destination_path(path: Union[str, None]) -> str:
        if path is not None:
            return path.lstrip("/") if path.startswith("/") else path
        return ""


    @staticmethod
    def _set_bucket_name(name: str) -> str:
        bucket = name if not name.startswith("gs://") else name[5:]
        return bucket.strip("/")

class SFTPToGCSOperatorPlugin(AirflowPlugin):
    name = "SFTPToGCSOperatorPlugin"
    operators = [SFTPToGCSOperator]
所以我在我的DAG脚本中导入这个插件类,当我们使用文件名时,它非常好,因为代码在else条件中

但当我们使用通配符时,我们在if条件中有光标,我得到了get_tree_map方法的错误

见下文错误-

ERROR - 'SFTPHook' object has no attribute 'get_tree_map'
我发现此错误的原因此方法本身在composer(气流1.10.6)中不存在-

此方法出现在最新版本的airflow中

现在,我应该尝试什么,这个方法或者这个操作符类有没有其他选择

有人知道有没有解决办法吗

提前谢谢


请忽略stackoverflow中的输入错误或缩进错误。在我的代码中没有缩进错误。

要在Airflow 1.10.6版上的Google Cloud Composer中使用
SFTPToGCSOperator
,我们需要创建一个插件,并通过将操作符/挂钩代码复制到一个文件中来“黑客”Airflow,以启用
SFTPToGCSOperator
使用Airflow 1.10.10版中的代码

最新的Airflow版本有一个新的
Airflow.providers
目录,该目录在早期版本中不存在。这就是您看到以下错误的原因:
没有名为afflow.providers的模块
。我所做的所有更改如下所述:

我准备的工作插件,你可以下载。在使用它之前,我们必须在Cloud Composer环境中安装以下PyPI库:
pysftp
paramiko
sshtunnel

  • 我复制了完整的
    SFTPToGCSOperator
    ,它从792行开始。您可以看到此运算符使用
    gcs

    来自avirflow.providers.google.cloud.hooks.gcs导入gcs

    这也需要复制到插件-从193行开始

  • 然后,
    gchake
    继承自
    GoogleBaseHook
    类,我们可以将其更改为在Airflow 1.10.6版本中可访问的
    GoogleCloudBaseHook
    ,并导入它:

    来自afflow.contrib.hooks.gcp\u api\u base\u hook导入GoogleCloudBaseHook

  • 最后,需要将
    SFTPHook
    导入插件-从第39行开始,该插件继承自
    SSHHook
    类,我们可以通过更改import语句使用1.10.6版本中的一个插件:

    来自afflow.contrib.hooks.ssh\u hook导入SSHHook

  • 在文件末尾,您可以找到插件的定义:

  • 需要创建插件,因为Airflow 1.10.6版本(最新的Cloud Composer)中目前没有Airflow内置操作符。您可以密切关注Cloud Composer,以了解最新版本的Airflow何时可用

    我希望您能发现上述信息有用。

    “提供商”软件包仅在Airflow 2.0中提供,而Cloud Composer中尚未提供该软件包(在我写这篇文章时,最新可用的Airflow映像是今天上午发布的1.10.14)

    但您可以导入后端口软件包,让您在早期版本1.10.*中享受这些新软件包

    My requirements.txt:

    apache-airflow-backport-providers-ssh==2020.10.29
    apache-airflow-backport-providers-sftp==2020.10.29
    pysftp>=0.2.9
    paramiko>=2.6.0
    sshtunnel<0.2,>=0.1.4
    
    apache airflow后端口提供程序ssh==2020.10.29 apache airflow后端口提供商sftp==2020.10.29 PYSFP>=0.2.9 帕拉米科>=2.6.0 sshtunnel=0.1.4 您可以从控制台直接在Composer环境中导入PyPi包

    有了这些依赖项,我就可以使用最新的
    aiffair.providers.ssh.operators.ssh.SSHOperator
    (以前的
    aiffair.contrib.operators.ssh\u operator.SSHOperator
    )和新的
    aiffair.providers.google.cloud.transfers.gcs\u-to\u-sftp.GCSToSFTPOperator
    (在
    contrib
    操作符中没有等价物)


    享受吧

    嗨!你能尝试用SFTPHook()创建另一个插件吗?然后,您需要在SFTPToGCSOperator插件文件中更改“from afflow.contrib.hooks.sftp_hook import SFTPHook”。创建插件的步骤是什么,DAG中的“PluginSFTPToGCSOperator”这个名称指定了什么?您好@muscat,谢谢您的回复,我的插件类名是PluginSFTPToGCSOperator.py,正如您所说,我已经创建了一个带有新导入的nre插件文件,但我得到了错误。。。。断开的插件:[/home/airfolution/gcs/plugins/PluginSFTPToGCSOperator.py]没有名为“airfolution.providers”的模块,我已经用工作插件添加了答案。我希望你会觉得它有用。这一切都是关于从存储库复制代码。我还找到了名为“airflow.providers”的模块错误的解决方案。您好@muscat,感谢您的宝贵意见。我试过你的插件代码。但仍然面临问题-“gcshake”对象没有属性“client”_info@BhageshArora,您在何处定义“client_info”?Hello@Alexandre Moraes,在这个插件代码中-您可以看到行号。277@BhageshArora我没有经历过你提到的错误
    class SFTPToGCSOperatorPlugin(AirflowPlugin):
        name = "SFTPToGCSOperatorPlugin"
        operators = [SFTPToGCSOperator]
    
    apache-airflow-backport-providers-ssh==2020.10.29
    apache-airflow-backport-providers-sftp==2020.10.29
    pysftp>=0.2.9
    paramiko>=2.6.0
    sshtunnel<0.2,>=0.1.4