Apache NiFi中已经有一些处理器用于执行命令,例如ExecuteProcess和ExecuteStreamCommand。
这些允许通过使用各种参数(例如建立SSH会话时要执行的远程命令)调用操作系统的“ssh”命令来执行远程脚本。
通常这是通过编写带有许多ssh命令的bash脚本(或者在末尾使用一长串远程命令的单个ssh命令)来完成的。
作为一种不同的方法,您可以使用ExecuteScript或InvokeScriptedProcessor以及第三方库来使用SSH执行远程命令。
对于这篇文章,我将使用Groovy作为脚本语言和Sshoogr,这是一个非常方便的Groovy SSH DSL。
Sshoogr可以通过SDK Manager安装或下载(例如从Maven Central)。
关键是将所有库(Sshoogr本身及其依赖项)放在某个目录中。
然后指向ExecuteScript的Module Directory属性中的目录,这些JAR将可用于Groovy脚本。
为了使这个例子相当灵活,我选择了一些约定。
首先,要执行的命令应该在传入流文件的各行上。
接下来,配置参数(例如主机名,用户名,密码和端口)将作为动态属性添加到ExecuteScript。
请注意,在这种情况下,password属性不敏感;
如果需要,请使用InvokeScriptedProcessor并显式添加属性(将密码指定为敏感密码)。
为了使Sshoogr工作(至少在本例中),预计远程节点的RSA密钥位于NiFi用户的〜/ .ssh / known_hosts文件中。
由于操作系统和其他差异,有时SSH连接会因严格的主机密钥检查而失败,因此在脚本中我们将在Sshoogr中禁用它。
现在到脚本。
我们从需要输入流文件的标准代码开始,然后使用session.read()将每行放入名为命令的列表中:
def flowFile = session.get()
if(!flowFile) return
def commands = []
// Read in one command per line
session.read(flowFile, {inputStream ->
inputStream.eachLine { line -> commands << line }
} as InputStreamCallback)
然后我们禁用严格的主机密钥检查:
options.trustUnknownHosts = true
然后我们使用Sshoogr DSL执行流文件中的命令:
def result = null
remoteSession {
host = sshHostname.value
username = sshUsername.value
password = sshPassword.value
port = Integer.parseInt(sshPort.value)
result = exec(showOutput: true, failOnError: false, command: commands.join('\n'))
}
这里我们使用前面提到的ExecuteScript动态属性(sshHostname,sshUsername,sshPassword,sshPort)。
在帖子的后面,下面的屏幕截图显示了它们在添加到ExecuteScript配置后。
exec()方法是Sshoogr的强者。
在这种情况下,我们需要输出命令(放入传出的流文件)。
此外,我将“failOnError”设置为false,因此将尝试所有命令,但您可能希望这是真的,这样如果命令失败,将不会尝试以下任何命令。
此示例中的最后一个参数是“command”。
如果你不使用exec()的“命名参数”(又名Map)版本而只想执行命令列表(例如没有设置“showOutput”),那么exec()将采用一个Collection:
exec(commands)
在我们的例子中,exec()方法会将“command”参数转换为字符串,因此我们需要将其转换为逐行字符串,而不是仅仅传入List“命令”,所以我们
使用join(’\ n’)。
脚本的下一部分是覆盖传入的流文件,或者如果我们可以创建一个新的原始文件作为父文件。
如果使用ExecuteScript并创建新的流文件,请务必删除原始文件。
如果使用InvokeScriptedProcessor,您可以定义“原始”关系并将传入流文件路由到该关系。
flowFile = session.write(flowFile, { outputStream ->
result?.output?.eachLine { line ->
outputStream.write(line.bytes)
outputStream.write('\n'.bytes)
}
} as OutputStreamCallback)
最后,使用result.exitStatus,我们确定是否将流文件路由到成功或失败:
session.transfer(flowFile, result?.exitStatus ? REL_FAILURE : REL_SUCCESS)
此示例的完整脚本如下:
import static com.aestasit.infrastructure.ssh.DefaultSsh.*
def flowFile = session.get()
if(!flowFile) return
def commands = []
// Read in one command per line
session.read(flowFile, {inputStream ->
inputStream.eachLine { line -> commands << line }
} as InputStreamCallback)
options.trustUnknownHosts = true
def result = null
remoteSession {
host = sshHostname.value
username = sshUsername.value
password = sshPassword.value
port = Integer.parseInt(sshPort.value)
result = exec(showOutput: true, failOnError: false, command: commands.join('\n'))
}
flowFile = session.write(flowFile, { outputStream ->
result?.output?.eachLine { line ->
outputStream.write(line.bytes)
outputStream.write('\n'.bytes)
}
} as OutputStreamCallback)
session.transfer(flowFile, result?.exitStatus ? REL_FAILURE : REL_SUCCESS)
现在转到ExecuteScript配置:
在这里,我连接到我的Hortonworks数据平台(HDP)Sandbox以运行Hadoop命令等,显示默认设置,您可以看到我已将脚本粘贴到Script Body中,而我的Module Directory指向我的Sshoogr下载文件夹。
我将此添加到生成流文件的简单流中,使用命令填充它,远程运行它们,然后记录会话返回的输出:
在本例中,我运行了以下两个命令:
hadoop fs -ls /user
echo "Hello World!" > here_i_am.txt
在日志中(在包含有效负载的LogAttribute运行之后),我看到以下内容:
在沙盒上,我看到文件已经创建了预期的内容:
如您所见,这篇文章描述了如何使用带Groovy和Sshoogr的ExecuteScript来执行来自流文件的远程命令。
我创建了模板的Gist,但它是使用Apache NiFi 1.0的beta版本创建的,因此它可能无法加载到早期版本(0.x)。
然而所有的成分都在这篇文章中,所以应该很容易创建自己的流程,请告诉我如何/如果它适合你:)