This is an automated email from the ASF dual-hosted git repository.
dockerzhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git
The following commit(s) were added to refs/heads/master by this push:
new 89c965b4ae [INLONG-10409][Dashboard] Support installing agents by SSH
key-based auth (#10636)
89c965b4ae is described below
commit 89c965b4aea0f7432066b55360f154d514148138
Author: qingliu <[email protected]>
AuthorDate: Wed Jul 17 20:50:22 2024 +0800
[INLONG-10409][Dashboard] Support installing agents by SSH key-based auth
(#10636)
Co-authored-by: Charles Zhang <[email protected]>
---
inlong-dashboard/src/ui/locales/cn.json | 3 +
inlong-dashboard/src/ui/locales/en.json | 3 +
.../src/ui/pages/Clusters/NodeEditModal.tsx | 95 +++++++++++++++++++++-
.../service/cluster/InlongClusterService.java | 8 ++
.../service/cluster/InlongClusterServiceImpl.java | 13 +++
.../web/controller/InlongClusterController.java | 6 ++
6 files changed, 124 insertions(+), 4 deletions(-)
diff --git a/inlong-dashboard/src/ui/locales/cn.json
b/inlong-dashboard/src/ui/locales/cn.json
index 5e0fb31416..6ff457fe97 100644
--- a/inlong-dashboard/src/ui/locales/cn.json
+++ b/inlong-dashboard/src/ui/locales/cn.json
@@ -772,9 +772,12 @@
"pages.Clusters.Node.IsInstall": "安装方式",
"pages.Clusters.Node.ManualInstall": "手动安装",
"pages.Clusters.Node.SSHInstall": "SSH 安装",
+ "pages.Clusters.Node.IdentifyType": "认证方式",
"pages.Clusters.Node.Username": "SSH 用户名",
"pages.Clusters.Node.Password": "SSH 密码",
+ "pages.Clusters.Node.SSHKey": "SSH 密钥",
"pages.Clusters.Node.SSHPort": "SSH 端口",
+ "pages.Clusters.Node.SSHKeyHelper": "请将公钥上传至 Agent 节点的
~/.ssh/authorized_keys 文件中",
"pages.Clusters.Node.Status": "状态",
"pages.Clusters.Node.Status.Normal": "正常",
"pages.Clusters.Node.Status.Timeout": "心跳超时",
diff --git a/inlong-dashboard/src/ui/locales/en.json
b/inlong-dashboard/src/ui/locales/en.json
index 165e6deec6..8d05262a88 100644
--- a/inlong-dashboard/src/ui/locales/en.json
+++ b/inlong-dashboard/src/ui/locales/en.json
@@ -772,9 +772,12 @@
"pages.Clusters.Node.IsInstall": "Installation",
"pages.Clusters.Node.ManualInstall": "Manual",
"pages.Clusters.Node.SSHInstall": "SSH",
+ "pages.Clusters.Node.IdentifyType": "Identify Type",
"pages.Clusters.Node.Username": "SSH Username",
+ "pages.Clusters.Node.SSHKey": "SSH Key",
"pages.Clusters.Node.Password": "SSH Password",
"pages.Clusters.Node.SSHPort": "SSH Port",
+ "pages.Clusters.Node.SSHKeyHelper": "Please upload the public key to the
~/.ssh/authorized_keys file of the Agent node",
"pages.Clusters.Node.Status": "Status",
"pages.Clusters.Node.Status.Normal": "Normal",
"pages.Clusters.Node.Status.Timeout": "Timeout",
diff --git a/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
b/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
index a8a070f3aa..5a16e0d771 100644
--- a/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
+++ b/inlong-dashboard/src/ui/pages/Clusters/NodeEditModal.tsx
@@ -17,9 +17,9 @@
* under the License.
*/
-import React, { useMemo } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import i18n from '@/i18n';
-import { Modal, message } from 'antd';
+import { Modal, message, Button } from 'antd';
import { ModalProps } from 'antd/es/modal';
import FormGenerator, { useForm } from '@/ui/components/FormGenerator';
import { useRequest, useUpdateEffect } from '@/ui/hooks';
@@ -34,6 +34,7 @@ export interface NodeEditModalProps extends ModalProps {
const NodeEditModal: React.FC<NodeEditModalProps> = ({ id, type, clusterId,
...modalProps }) => {
const [form] = useForm();
+ const [isInstall, setInstallType] = useState(false);
const { data: savedData, run: getData } = useRequest(
id => ({
@@ -103,6 +104,34 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id,
type, clusterId, ...m
},
);
+ const { data: sshKeys, run: getSSHKeys } = useRequest(
+ () => ({
+ url: '/cluster/node/getManagerSSHPublicKey',
+ method: 'GET',
+ }),
+ {
+ manual: true,
+ onSuccess: result => {
+ form.setFieldValue('sshKey', result);
+ },
+ },
+ );
+
+ const testSSHConnection = async () => {
+ const values = await form.validateFields();
+ const submitData = {
+ ...values,
+ type,
+ parentId: savedData?.parentId || clusterId,
+ };
+ await request({
+ url: '/cluster/node/testSSHConnection',
+ method: 'POST',
+ data: submitData,
+ });
+ message.success(i18n.t('basic.ConnectionSuccess'));
+ };
+
useUpdateEffect(() => {
if (modalProps.open) {
// open
@@ -212,6 +241,9 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id,
type, clusterId, ...m
hidden: type !== 'AGENT',
rules: [{ required: true }],
props: {
+ onChange: ({ target: { value } }) => {
+ setInstallType(value);
+ },
options: [
{
label: i18n.t('pages.Clusters.Node.ManualInstall'),
@@ -224,6 +256,32 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id,
type, clusterId, ...m
],
},
},
+ {
+ type: 'radio',
+ label: i18n.t('pages.Clusters.Node.IdentifyType'),
+ name: 'identifyType',
+ initialValue: 'password',
+ hidden: type !== 'AGENT',
+ visible: values => values?.isInstall,
+ rules: [{ required: true }],
+ props: {
+ onChange: ({ target: { value } }) => {
+ if (value === 'sshKey' && !form.getFieldValue('sshKey')) {
+ getSSHKeys();
+ }
+ },
+ options: [
+ {
+ label: i18n.t('pages.Clusters.Node.Password'),
+ value: 'password',
+ },
+ {
+ label: i18n.t('pages.Clusters.Node.SSHKey'),
+ value: 'sshKey',
+ },
+ ],
+ },
+ },
{
type: 'input',
label: i18n.t('pages.Clusters.Node.Username'),
@@ -238,7 +296,20 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id,
type, clusterId, ...m
name: 'password',
rules: [{ required: true }],
hidden: type !== 'AGENT',
- visible: values => values?.isInstall,
+ visible: values => values?.isInstall && values?.identifyType ===
'password',
+ },
+ {
+ type: 'textarea',
+ label: i18n.t('pages.Clusters.Node.SSHKey'),
+ tooltip: i18n.t('pages.Clusters.Node.SSHKeyHelper'),
+ name: 'sshKey',
+ rules: [{ required: true }],
+ hidden: type !== 'AGENT',
+ visible: values => values?.isInstall && values?.identifyType ===
'sshKey',
+ props: {
+ readOnly: true,
+ autoSize: true,
+ },
},
{
type: 'input',
@@ -285,7 +356,23 @@ const NodeEditModal: React.FC<NodeEditModalProps> = ({ id,
type, clusterId, ...m
}, []);
return (
- <Modal {...modalProps} title={i18n.t('pages.Clusters.Node.Name')}
onOk={onOk}>
+ <Modal
+ {...modalProps}
+ title={i18n.t('pages.Clusters.Node.Name')}
+ footer={[
+ <Button key="cancel" onClick={e => modalProps.onCancel(e)}>
+ {i18n.t('basic.Cancel')}
+ </Button>,
+ <Button key="save" type="primary" onClick={onOk}>
+ {i18n.t('basic.Save')}
+ </Button>,
+ isInstall && (
+ <Button key="run" type="primary" onClick={testSSHConnection}>
+ {i18n.t('pages.Nodes.TestConnection')}
+ </Button>
+ ),
+ ]}
+ >
<FormGenerator content={content} form={form} useMaxWidth />
</Modal>
);
diff --git
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterService.java
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterService.java
index 22282ba67e..00a13e0804 100644
---
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterService.java
+++
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterService.java
@@ -293,6 +293,14 @@ public interface InlongClusterService {
*/
String getManagerSSHPublicKey();
+ /**
+ * Test whether the SSH connection can be successfully established using
the provided SSH information.
+ *
+ * @param request connection request
+ * @return true or false
+ */
+ Boolean testSSHConnection(ClusterNodeRequest request);
+
/**
* Query data proxy nodes by the given inlong group id and protocol type
*
diff --git
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
index 5be7ffd3b1..e5912bd93f 100644
---
a/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
+++
b/inlong-manager/manager-service/src/main/java/org/apache/inlong/manager/service/cluster/InlongClusterServiceImpl.java
@@ -61,6 +61,7 @@ import
org.apache.inlong.manager.pojo.cluster.ClusterTagResponse;
import org.apache.inlong.manager.pojo.cluster.TenantClusterTagInfo;
import org.apache.inlong.manager.pojo.cluster.TenantClusterTagPageRequest;
import org.apache.inlong.manager.pojo.cluster.TenantClusterTagRequest;
+import org.apache.inlong.manager.pojo.cluster.agent.AgentClusterNodeRequest;
import
org.apache.inlong.manager.pojo.cluster.dataproxy.DataProxyClusterNodeDTO;
import org.apache.inlong.manager.pojo.cluster.pulsar.PulsarClusterDTO;
import org.apache.inlong.manager.pojo.common.PageResult;
@@ -78,6 +79,7 @@ import
org.apache.inlong.manager.service.cluster.node.InlongClusterNodeInstallOp
import
org.apache.inlong.manager.service.cluster.node.InlongClusterNodeOperator;
import
org.apache.inlong.manager.service.cluster.node.InlongClusterNodeOperatorFactory;
import org.apache.inlong.manager.service.cmd.CommandExecutor;
+import org.apache.inlong.manager.service.cmd.CommandResult;
import org.apache.inlong.manager.service.repository.DataProxyConfigRepository;
import org.apache.inlong.manager.service.tenant.InlongTenantService;
import org.apache.inlong.manager.service.user.InlongRoleService;
@@ -891,6 +893,17 @@ public class InlongClusterServiceImpl implements
InlongClusterService {
}
}
+ @Override
+ public Boolean testSSHConnection(ClusterNodeRequest request) {
+ AgentClusterNodeRequest nodeRequest = (AgentClusterNodeRequest)
request;
+ try {
+ CommandResult commandResult =
commandExecutor.execRemote(nodeRequest, "ls");
+ return commandResult.getCode() == 0;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public DataProxyNodeResponse getDataProxyNodes(String groupId, String
protocolType) {
LOGGER.debug("begin to get data proxy nodes for groupId={},
protocol={}", groupId, protocolType);
diff --git
a/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
b/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
index da6bc39253..e66d4fb833 100644
---
a/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
+++
b/inlong-manager/manager-web/src/main/java/org/apache/inlong/manager/web/controller/InlongClusterController.java
@@ -303,6 +303,12 @@ public class InlongClusterController {
return Response.success(clusterService.getManagerSSHPublicKey());
}
+ @PostMapping("/cluster/node/testSSHConnection")
+ @ApiOperation(value = "Test SSH connection for inlong cluster node")
+ public Response<Boolean> testSSHConnection(@RequestBody ClusterNodeRequest
request) {
+ return Response.success(clusterService.testSSHConnection(request));
+ }
+
@PostMapping("/cluster/testConnection")
@ApiOperation(value = "Test connection for inlong cluster")
public Response<Boolean> testConnection(@Validated @RequestBody
ClusterRequest request) {