mirror of
https://github.com/LamGC/Oracle-Sentry.git
synced 2025-04-30 06:37:42 +00:00
Compare commits
No commits in common. "master" and "v0.0.1-alpha-SNAPSHOT" have entirely different histories.
master
...
v0.0.1-alp
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,4 +31,3 @@ hs_err_pid*
|
|||||||
|
|
||||||
# Idea Editor
|
# Idea Editor
|
||||||
/.idea
|
/.idea
|
||||||
out/
|
|
||||||
|
@ -12,10 +12,6 @@
|
|||||||
|
|
||||||
本程序为脚本提供 SSH 客户端(使用 [apache/Mina-sshd](https://github.com/apache/mina-sshd) ),协助脚本通过 SSH 管理服务器。
|
本程序为脚本提供 SSH 客户端(使用 [apache/Mina-sshd](https://github.com/apache/mina-sshd) ),协助脚本通过 SSH 管理服务器。
|
||||||
|
|
||||||
### 关于更新慢的说明 ###
|
|
||||||
这个项目一直在更新(只有仓库存档了才会说不更),有什么不足的,
|
|
||||||
或者想添加什么功能的可以发 issue,最近本人比较忙,可能没那么及时,但本项目肯定是不会弃坑的 :D
|
|
||||||
|
|
||||||
## Docs ##
|
## Docs ##
|
||||||
[中文(简体)](./docs/cn/Index.md)
|
[中文(简体)](./docs/cn/Index.md)
|
||||||
|
|
||||||
|
45
build.gradle
45
build.gradle
@ -1,14 +1,11 @@
|
|||||||
//file:noinspection GradlePackageUpdate SpringBoot 的版本由插件管理.
|
|
||||||
//file:noinspection GroovyAssignabilityCheck
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.springframework.boot' version '2.5.4'
|
id 'org.springframework.boot' version '2.5.3'
|
||||||
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
|
||||||
id 'jacoco'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'net.lamgc'
|
group 'net.lamgc.oracle'
|
||||||
version '0.2.0'
|
version '0.0.1-alpha-SNAPSHOT'
|
||||||
compileJava.sourceCompatibility = JavaVersion.VERSION_16
|
compileJava.sourceCompatibility = JavaVersion.VERSION_16
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -16,18 +13,17 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
def ociSdkVer = '2.4.0'
|
def ociSdkVer = '2.3.2'
|
||||||
def sshdVer = '2.7.0'
|
def sshdVer = '2.7.0'
|
||||||
def bouncyCastleVer = '1.69'
|
def bouncyCastleVer = '1.69'
|
||||||
|
|
||||||
implementation 'org.springframework.boot:spring-boot-starter'
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
|
||||||
implementation 'org.slf4j:slf4j-api:1.7.32'
|
implementation 'org.slf4j:slf4j-api:1.7.31'
|
||||||
|
|
||||||
implementation "com.oracle.oci.sdk:oci-java-sdk-core:${ociSdkVer}"
|
implementation "com.oracle.oci.sdk:oci-java-sdk-core:${ociSdkVer}"
|
||||||
implementation "com.oracle.oci.sdk:oci-java-sdk-identity:${ociSdkVer}"
|
implementation "com.oracle.oci.sdk:oci-java-sdk-identity:${ociSdkVer}"
|
||||||
implementation "com.oracle.oci.sdk:oci-java-sdk-objectstorage:${ociSdkVer}"
|
|
||||||
|
|
||||||
implementation "org.apache.sshd:sshd-core:${sshdVer}"
|
implementation "org.apache.sshd:sshd-core:${sshdVer}"
|
||||||
implementation "org.apache.sshd:sshd-sftp:${sshdVer}"
|
implementation "org.apache.sshd:sshd-sftp:${sshdVer}"
|
||||||
@ -35,39 +31,16 @@ dependencies {
|
|||||||
implementation "org.bouncycastle:bcpg-jdk15on:${bouncyCastleVer}"
|
implementation "org.bouncycastle:bcpg-jdk15on:${bouncyCastleVer}"
|
||||||
implementation "org.bouncycastle:bcpkix-jdk15on:${bouncyCastleVer}"
|
implementation "org.bouncycastle:bcpkix-jdk15on:${bouncyCastleVer}"
|
||||||
|
|
||||||
implementation 'cglib:cglib:3.3.0'
|
|
||||||
|
|
||||||
implementation "net.i2p.crypto:eddsa:0.3.0"
|
implementation "net.i2p.crypto:eddsa:0.3.0"
|
||||||
|
|
||||||
implementation 'org.codehaus.groovy:groovy-all:3.0.8'
|
implementation 'org.codehaus.groovy:groovy-all:3.0.7'
|
||||||
|
|
||||||
implementation 'com.google.code.gson:gson:2.8.8'
|
implementation 'com.google.code.gson:gson:2.8.7'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
|
||||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
|
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
jacoco {
|
|
||||||
classDumpDir = file("$buildDir/jacoco/classpathDumps")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
javadoc {
|
|
||||||
options.encoding = 'UTF-8'
|
|
||||||
}
|
|
||||||
|
|
||||||
jacoco {
|
|
||||||
reportsDir file("$buildDir/jacoco/reports")
|
|
||||||
}
|
|
||||||
|
|
||||||
jacocoTestReport {
|
|
||||||
reports {
|
|
||||||
xml.enabled true
|
|
||||||
xml.destination file("$buildDir/jacoco/reports.xml")
|
|
||||||
|
|
||||||
html.enabled true
|
|
||||||
html.destination file("$buildDir/jacoco/reports-html/")
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -2,6 +2,3 @@
|
|||||||
|
|
||||||
- [安装并使用](安装并使用.md)
|
- [安装并使用](安装并使用.md)
|
||||||
- [编写脚本](./script/groovy/入门.md)
|
- [编写脚本](./script/groovy/入门.md)
|
||||||
- [注册并使用触发器](./script/groovy/注册并使用触发器.md)
|
|
||||||
- [可用的触发器](./script/groovy/可用的触发器.md)
|
|
||||||
- [开始使用 SSH](./script/groovy/开始使用SSH.md)
|
|
@ -1,12 +1,12 @@
|
|||||||
## Groovy 脚本编写
|
## Groovy 脚本编写
|
||||||
|
|
||||||
### 基本格式
|
### 基本格式
|
||||||
Groovy 语言的脚本基本格式如下:
|
Groovy 语言的脚本基本格式如下:
|
||||||
```groovy
|
```groovy
|
||||||
info {
|
info {
|
||||||
// 脚本信息, 遵循 Java 的 GAV 规则.
|
// 脚本信息, 遵循 Java 的 GAV 规则.
|
||||||
// 脚本英文名
|
// 脚本英文名
|
||||||
name 'demo'
|
artifact 'demo'
|
||||||
// 所属组(如果有域名, 就是自己的域名的倒序, 例如 tieba.baidu.com 就是 com.baidu.tieba)
|
// 所属组(如果有域名, 就是自己的域名的倒序, 例如 tieba.baidu.com 就是 com.baidu.tieba)
|
||||||
// 如果没有, 也可以用 github 的.
|
// 如果没有, 也可以用 github 的.
|
||||||
group 'org.example'
|
group 'org.example'
|
||||||
@ -34,7 +34,6 @@ def formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
|||||||
// 这里注册定时器触发器.
|
// 这里注册定时器触发器.
|
||||||
trigger("timer") {
|
trigger("timer") {
|
||||||
// time 参数指定 Cron 表达式, 像这样设置表达式字符串后就可以了.
|
// time 参数指定 Cron 表达式, 像这样设置表达式字符串后就可以了.
|
||||||
// 比如这里设定一分钟执行一次.
|
|
||||||
time "0 0/1 * * * ? "
|
time "0 0/1 * * * ? "
|
||||||
|
|
||||||
run {
|
run {
|
||||||
@ -46,10 +45,3 @@ trigger("timer") {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Groovy 语言比较贴近 Java,实际开发与 Java 没什么区别(相比于 Java 多了不少语法糖,但还是兼容 Java 的)。
|
Groovy 语言比较贴近 Java,实际开发与 Java 没什么区别(相比于 Java 多了不少语法糖,但还是兼容 Java 的)。
|
||||||
|
|
||||||
## 我能在脚本中使用什么?
|
|
||||||
目前你能使用的东西有:
|
|
||||||
- `HTTP`:一个 HttpClient,允许你访问 Http 资源。
|
|
||||||
- `InstanceManager`:计算实例(就是服务器)管理器,可以获取所有的服务器实例,通过实例对象能获取和操作服务器。
|
|
||||||
|
|
||||||
除此之外,还有 Java 基本库与 Groovy 基本库可供脚本使用。
|
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
## 可用的触发器
|
|
||||||
|
|
||||||
### Once 触发器
|
|
||||||
Once 触发器只会在启动后执行一次,适用于初始化等操作。
|
|
||||||
|
|
||||||
示例:
|
|
||||||
```groovy
|
|
||||||
trigger ("once") {
|
|
||||||
run {
|
|
||||||
// do something...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
参数:
|
|
||||||
- (没有参数)
|
|
||||||
|
|
||||||
`it` 对象:无
|
|
||||||
|
|
||||||
|
|
||||||
### Timer 触发器
|
|
||||||
Timer 触发器通过设定的 Cron 时间表达式,根据时间触发执行,可用于定时检查等操作。
|
|
||||||
|
|
||||||
示例:
|
|
||||||
```groovy
|
|
||||||
trigger ("once") {
|
|
||||||
time "0 0 12 * * ? *"
|
|
||||||
run {
|
|
||||||
// do something...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
参数:
|
|
||||||
- `time`:Cron 时间表达式
|
|
||||||
|
|
||||||
`it` 对象:无
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
|||||||
## 开始使用 SSH
|
|
||||||
哨兵为脚本包装了一个 SSH 客户端,通过 SSH 客户端,脚本可以通过 SSH 连接实例并执行命令、通过 Sftp 访问文件、设置端口转发。
|
|
||||||
|
|
||||||
### 创建一个 SSH 会话
|
|
||||||
SSH 会话不止一个,一个脚本可以创建多个 SSH 会话来同时做不同的事情,也可以多个脚本创建多个会话来做不同事情,会话之间互不干扰。
|
|
||||||
创建会话的方法很简单,只需要这么做:
|
|
||||||
```groovy
|
|
||||||
def session = instance.ssh().createSession()
|
|
||||||
```
|
|
||||||
这样就能创建一个会话了,至于 SSH 的连接认证什么的,只需要交给哨兵完成即可!
|
|
||||||
|
|
||||||
> 注意:会话不保证创建成功,如果创建失败,方法将抛出一个异常。
|
|
||||||
|
|
||||||
### 通过 SSH 执行命令
|
|
||||||
在得到 SSH 会话后,就可以开始执行命令了。
|
|
||||||
首先需要创建一个命令执行会话(虽然本质上是一个通道):
|
|
||||||
```groovy
|
|
||||||
def execSession = session.createExecSession("date")
|
|
||||||
```
|
|
||||||
设定后,我们还需要设置命令的标准输出和标准输入,以方便我们获得命令的输出,和向命令输入内容(比如参数):
|
|
||||||
```groovy
|
|
||||||
// 这里如果不需要获取并处理的话,可以不设置,
|
|
||||||
// 也可以将输出设定为哨兵的标准输出,也是可以的。
|
|
||||||
execSession.setOut(System.out)
|
|
||||||
// 这里也设置为哨兵的标准输入,可以由管理员主动输入内容。
|
|
||||||
execSession.setIn(System.in)
|
|
||||||
```
|
|
||||||
|
|
||||||
最后,调用 `exec()` 方法,执行命令并等待命令运行完成即可。
|
|
||||||
|
|
||||||
如果需要执行命令后自动输入之类的异步操作呢?可以改用 `exec(true)` 进行异步执行,然后使用 `waitFor()` 等待命令执行完成即可。
|
|
||||||
|
|
||||||
命令执行完成后,除了可以检查输出内容来检查程序执行结果外,还可以通过退出代码了解,只需要调用 `exitCode()` 获取退出码即可:
|
|
||||||
```groovy
|
|
||||||
if (execSession.exitCode() == 0) {
|
|
||||||
println "命令执行成功!"
|
|
||||||
} else {
|
|
||||||
println "命令执行失败,退出代码不为 0(退出码:${execSession.exitCode()})"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 完整示例代码
|
|
||||||
```groovy
|
|
||||||
run {
|
|
||||||
def session = instance.ssh().createSession()
|
|
||||||
def execSession = session.createExecSession("date")
|
|
||||||
execSession.setOut(System.out)
|
|
||||||
execSession.setIn(System.in)
|
|
||||||
// 同步执行,并等待命令执行结束。
|
|
||||||
execSession.exec()
|
|
||||||
// 除了上述的 if 判断外,也可以使用 Groovy 的字符串嵌入语法。
|
|
||||||
println "命令执行完成,退出代码:${execSession.exitCode()}"
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,23 +0,0 @@
|
|||||||
## 注册并使用触发器
|
|
||||||
触发器可以在适当的时候执行脚本所注册的运行代码,使用触发器,哨兵可以在某项事件发生时,触发脚本执行某些操作,而无需脚本手动检查。
|
|
||||||
|
|
||||||
### 注册触发器
|
|
||||||
注册触发器的方法非常简单:
|
|
||||||
```groovy
|
|
||||||
trigger("timer") {
|
|
||||||
// timer 触发器的参数 "time", 填写 Cron 时间表达式.
|
|
||||||
time "0 0 12 * * ? *"
|
|
||||||
run {
|
|
||||||
// 这里编写当到达时间时所需执行的动作.
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
其中,`"timer"`是触发器名称,`time` 是触发器参数,不同的触发器有不同的参数,也可能没有参数(比如 Once 触发器),`run` 代码块是每个触发器必须有的,当满足条件时,run 代码块将会被执行。
|
|
||||||
|
|
||||||
run 代码块有个隐藏参数 `it`,如果触发器有参数,将通过该参数传递至 run 代码块,供脚本使用。
|
|
||||||
|
|
||||||
> 注意:每个触发器所能提供的东西并不一样,具体信息见[触发器文档](可用的触发器.md)。
|
|
||||||
|
|
||||||
当注册好了触发器后,只需要等待触发器,在合适的时机触发执行任务即可!
|
|
@ -3,22 +3,22 @@
|
|||||||
### 创建 API 密钥
|
### 创建 API 密钥
|
||||||
前往 [Oracle Cloud](https://cloud.oracle.com),登录后左上角打开菜单,选择“身份和安全”组,在右侧找到“身份”,然后找到“用户”。
|
前往 [Oracle Cloud](https://cloud.oracle.com),登录后左上角打开菜单,选择“身份和安全”组,在右侧找到“身份”,然后找到“用户”。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
然后找到自己的账号(一般用注册邮箱命名),如果先前有创建过帐号,找不到自己的帐号,可以在“用户类型”选择“本地”,会方便查找,找到自己的帐号后点进去。
|
然后找到自己的账号(一般用注册邮箱命名),如果先前有创建过帐号,找不到自己的帐号,可以在“用户类型”选择“本地”,会方便查找,找到自己的帐号后点进去。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
进去后,左下角资源选择“API密钥”,右侧列表选添加 API 密钥,在弹出的窗口中点击“下载私有密钥”,将密钥下载下来妥善保管好(可以先根据需要命名),然后点击“添加”。
|
进去后,左下角资源选择“API密钥”,右侧列表选添加 API 密钥,在弹出的窗口中点击“下载私有密钥”,将密钥下载下来妥善保管好(可以先根据需要命名),然后点击“添加”。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
点击后,会显示一个身份配置模板,将模板复制下来,粘贴到一个文件上,将文件命名为`<自定义名字>.oracle.ini`。
|
点击后,会显示一个身份配置模板,将模板复制下来,粘贴到一个文件上,将文件命名为`<自定义名字>.oracle.ini`。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
粘贴到文件之后,将刚刚保存好的密钥路径粘贴到`key_file`项里,如图所示:
|
粘贴到文件之后,将刚刚保存好的密钥路径粘贴到`key_file`项里,如图所示:
|
||||||

|

|
||||||
|
|
||||||
哨兵支持解析密钥*相对于*配置文件的路径,所以可以填相对路径,方便移动配置文件和密钥文件。
|
哨兵支持解析密钥*相对于*配置文件的路径,所以可以填相对路径,方便移动配置文件和密钥文件。
|
||||||
|
|
||||||
|
@ -1,34 +1,27 @@
|
|||||||
package net.lamgc.oracle.sentry;
|
package net.lamgc.oracle.sentry;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
||||||
import net.lamgc.oracle.sentry.oci.account.OracleAccount;
|
import net.lamgc.oracle.sentry.script.ScriptComponent;
|
||||||
import net.lamgc.oracle.sentry.oci.account.OracleAccountManager;
|
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstanceManager;
|
|
||||||
import net.lamgc.oracle.sentry.script.ScriptComponentExtension;
|
|
||||||
import net.lamgc.oracle.sentry.script.ScriptComponents;
|
|
||||||
import net.lamgc.oracle.sentry.script.ScriptManager;
|
import net.lamgc.oracle.sentry.script.ScriptManager;
|
||||||
|
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
class ApplicationInitiation {
|
public class ApplicationInitiation {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(ApplicationInitiation.class);
|
private final static Logger log = LoggerFactory.getLogger(ApplicationInitiation.class);
|
||||||
|
|
||||||
@ -49,8 +42,8 @@ class ApplicationInitiation {
|
|||||||
private String sshIdentityPath;
|
private String sshIdentityPath;
|
||||||
|
|
||||||
@Bean("oracle.identity.manager")
|
@Bean("oracle.identity.manager")
|
||||||
public OracleAccountManager initialOracleAccountManager() throws IOException {
|
public OracleIdentityManager initialOracleIdentityManager() throws IOException {
|
||||||
OracleAccountManager oracleUserManager = new OracleAccountManager();
|
OracleIdentityManager oracleUserManager = new OracleIdentityManager();
|
||||||
log.info("正在加载 Oracle API 身份配置...");
|
log.info("正在加载 Oracle API 身份配置...");
|
||||||
log.debug("Oracle API 身份配置查找路径: \"{}\", 匹配表达式: {}", identityDirectory, identityFilePattern);
|
log.debug("Oracle API 身份配置查找路径: \"{}\", 匹配表达式: {}", identityDirectory, identityFilePattern);
|
||||||
File identityDir = new File(identityDirectory);
|
File identityDir = new File(identityDirectory);
|
||||||
@ -69,15 +62,15 @@ class ApplicationInitiation {
|
|||||||
|
|
||||||
@Bean("oracle.compute.instance.manager")
|
@Bean("oracle.compute.instance.manager")
|
||||||
@Autowired
|
@Autowired
|
||||||
public ComputeInstanceManager initialComputeInstanceManager(OracleAccountManager accountManager) throws IOException {
|
public ComputeInstanceManager initialComputeInstanceManager(OracleIdentityManager identityManager) throws IOException {
|
||||||
ComputeInstanceManager instanceManager = new ComputeInstanceManager();
|
ComputeInstanceManager instanceManager = new ComputeInstanceManager();
|
||||||
int addTotal = 0;
|
int addTotal = 0;
|
||||||
for (OracleAccount account : accountManager.getAccounts()) {
|
for (AuthenticationDetailsProvider provider : identityManager.getProviders()) {
|
||||||
String identityName = account.name();
|
String identityName = identityManager.getIdentityName(provider.getUserId());
|
||||||
log.info("正在加载用户 {} 所拥有的所有实例...", identityName);
|
log.info("正在加载用户 {} 所拥有的所有实例...", identityName);
|
||||||
int addCount;
|
int addCount;
|
||||||
try {
|
try {
|
||||||
addCount = instanceManager.addComputeInstanceFromUser(account);
|
addCount = instanceManager.addComputeInstanceFromUser(provider);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("加载实例时发生异常.", e);
|
log.error("加载实例时发生异常.", e);
|
||||||
continue;
|
continue;
|
||||||
@ -94,33 +87,17 @@ class ApplicationInitiation {
|
|||||||
|
|
||||||
@Bean("sentry.script.manager")
|
@Bean("sentry.script.manager")
|
||||||
@Autowired
|
@Autowired
|
||||||
public ScriptManager initialScriptManager(ComputeInstanceManager instanceManager, OracleAccountManager accountManager) {
|
public ScriptManager initialScriptManager(ComputeInstanceManager instanceManager) {
|
||||||
ScriptComponents components = new ScriptComponents();
|
ScriptComponent context = new ScriptComponent(new ScriptHttpClient(HttpClientBuilder.create()
|
||||||
components.addComponentObject("InstanceManager", instanceManager);
|
.build()),
|
||||||
components.addComponentObject("AccountManager", accountManager);
|
instanceManager);
|
||||||
|
|
||||||
configureScriptComponentsFromExtension(components);
|
ScriptManager manager = new ScriptManager(new File(scriptsLocation), context);
|
||||||
|
|
||||||
ScriptManager manager = new ScriptManager(new File(scriptsLocation), components);
|
|
||||||
manager.loadScripts();
|
manager.loadScripts();
|
||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void configureScriptComponentsFromExtension(ScriptComponents components) {
|
|
||||||
ServiceLoader<ScriptComponentExtension> extensions = ServiceLoader.load(ScriptComponentExtension.class);
|
|
||||||
for (ScriptComponentExtension extension : extensions) {
|
|
||||||
try {
|
|
||||||
extension.configureScriptComponents(components);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("脚本组件扩展配置组件时发生未捕获异常.(Extension: {})\n{}",
|
|
||||||
extension.getClass().getName(), Throwables.getStackTraceAsString(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@Order(1)
|
|
||||||
@SuppressWarnings({"MismatchedReadAndWriteOfArray", "RedundantOperationOnEmptyContainer"})
|
|
||||||
private void initialEnvironment() throws IOException {
|
private void initialEnvironment() throws IOException {
|
||||||
String[] directors = new String[] {
|
String[] directors = new String[] {
|
||||||
"./config",
|
"./config",
|
||||||
@ -129,7 +106,7 @@ class ApplicationInitiation {
|
|||||||
};
|
};
|
||||||
|
|
||||||
String[] files = new String[] {
|
String[] files = new String[] {
|
||||||
|
sshIdentityPath
|
||||||
};
|
};
|
||||||
|
|
||||||
for (String directory : directors) {
|
for (String directory : directors) {
|
||||||
@ -153,28 +130,4 @@ class ApplicationInitiation {
|
|||||||
}
|
}
|
||||||
log.debug("目录检查完成.");
|
log.debug("目录检查完成.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private void initialConfigurationFile(ApplicationContext context) {
|
|
||||||
File configFile = new File("config/application.yml");
|
|
||||||
if (!configFile.exists()) {
|
|
||||||
Resource resource = context.getResource("application.yml");
|
|
||||||
if (!resource.exists()) {
|
|
||||||
log.error("默认配置初始化失败(包内资源不存在).");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Files.copy(resource.getInputStream(), configFile.toPath());
|
|
||||||
log.info("默认配置文件已初始化完成, 如果调整配置, 可修改配置文件中的相应配置项.");
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("初始化默认配置文件失败!(Path: {})\n{}",
|
|
||||||
configFile.getAbsolutePath(), Throwables.getStackTraceAsString(e));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.debug("配置文件存在, 无需初始化.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,13 @@ import org.springframework.context.annotation.Configuration;
|
|||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@Configuration
|
@Configuration
|
||||||
class ApplicationMain {
|
public class ApplicationMain {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(ApplicationMain.class);
|
private final static Logger log = LoggerFactory.getLogger(ApplicationMain.class);
|
||||||
|
|
||||||
@SuppressWarnings("AlibabaConstantFieldShouldBeUpperCase")
|
@SuppressWarnings("AlibabaConstantFieldShouldBeUpperCase")
|
||||||
private final static Object mainThreadWaiter = new Object();
|
private final static Object mainThreadWaiter = new Object();
|
||||||
|
|
||||||
/**
|
|
||||||
* 程序入口.
|
|
||||||
* @param args 程序参数.
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(ApplicationMain.class, args);
|
SpringApplication.run(ApplicationMain.class, args);
|
||||||
|
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute;
|
package net.lamgc.oracle.sentry;
|
||||||
|
|
||||||
|
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
||||||
|
import com.oracle.bmc.core.ComputeClient;
|
||||||
import com.oracle.bmc.core.model.Instance;
|
import com.oracle.bmc.core.model.Instance;
|
||||||
import com.oracle.bmc.core.requests.ListInstancesRequest;
|
import com.oracle.bmc.core.requests.ListInstancesRequest;
|
||||||
import com.oracle.bmc.core.responses.ListInstancesResponse;
|
import com.oracle.bmc.core.responses.ListInstancesResponse;
|
||||||
|
import com.oracle.bmc.identity.IdentityClient;
|
||||||
import com.oracle.bmc.identity.model.Compartment;
|
import com.oracle.bmc.identity.model.Compartment;
|
||||||
import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
|
import com.oracle.bmc.identity.requests.ListCompartmentsRequest;
|
||||||
import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
|
import com.oracle.bmc.identity.responses.ListCompartmentsResponse;
|
||||||
import net.lamgc.oracle.sentry.oci.account.OracleAccount;
|
import net.lamgc.oracle.sentry.oci.compute.ComputeInstance;
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ssh.SshAuthIdentityProvider;
|
import net.lamgc.oracle.sentry.oci.compute.ssh.SshAuthIdentityProvider;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -24,9 +22,7 @@ import java.util.stream.Collectors;
|
|||||||
* 计算实例管理器.
|
* 计算实例管理器.
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public final class ComputeInstanceManager {
|
public class ComputeInstanceManager {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(ComputeInstanceManager.class);
|
|
||||||
|
|
||||||
private final Map<String, ComputeInstance> instanceMap = new ConcurrentHashMap<>();
|
private final Map<String, ComputeInstance> instanceMap = new ConcurrentHashMap<>();
|
||||||
private SshAuthIdentityProvider sshIdentityProvider;
|
private SshAuthIdentityProvider sshIdentityProvider;
|
||||||
@ -37,16 +33,10 @@ public final class ComputeInstanceManager {
|
|||||||
* @throws IOException 加载时如有异常将直接抛出.
|
* @throws IOException 加载时如有异常将直接抛出.
|
||||||
*/
|
*/
|
||||||
public void initialSshIdentityProvider(File sshIdentityJson) throws IOException {
|
public void initialSshIdentityProvider(File sshIdentityJson) throws IOException {
|
||||||
log.debug("正在初始化 SSH 认证配置提供器...");
|
|
||||||
sshIdentityProvider = new SshAuthIdentityProvider(this, sshIdentityJson);
|
sshIdentityProvider = new SshAuthIdentityProvider(this, sshIdentityJson);
|
||||||
sshIdentityProvider.loadAuthInfo();
|
sshIdentityProvider.loadAuthInfo();
|
||||||
log.debug("SSH 认证配置提供器已初始化完成.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实例 SSH 认证配置提供器.
|
|
||||||
* @return 返回 SSH 认证配置提供器.
|
|
||||||
*/
|
|
||||||
public SshAuthIdentityProvider getSshIdentityProvider() {
|
public SshAuthIdentityProvider getSshIdentityProvider() {
|
||||||
return sshIdentityProvider;
|
return sshIdentityProvider;
|
||||||
}
|
}
|
||||||
@ -70,41 +60,36 @@ public final class ComputeInstanceManager {
|
|||||||
public Set<ComputeInstance> getInstancesByUserId(String userId) {
|
public Set<ComputeInstance> getInstancesByUserId(String userId) {
|
||||||
Objects.requireNonNull(userId);
|
Objects.requireNonNull(userId);
|
||||||
return instanceMap.values().stream()
|
return instanceMap.values().stream()
|
||||||
.filter(computeInstance -> computeInstance.getFromAccount().id().equals(userId))
|
.filter(computeInstance -> computeInstance.getUserId().equals(userId))
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加某一用户的所有计算实例.
|
* 添加某一用户的所有计算实例.
|
||||||
* @param account Oracle 云账号对象.
|
* @param provider 用户身份提供器.
|
||||||
* @return 返回已成功添加的实例数量.
|
* @return 返回已成功添加的实例数量.
|
||||||
* @throws NullPointerException 如果 provider 为 {@code null} 则抛出异常.
|
* @throws NullPointerException 如果 provider 为 {@code null} 则抛出异常.
|
||||||
*/
|
*/
|
||||||
public int addComputeInstanceFromUser(OracleAccount account) {
|
public int addComputeInstanceFromUser(AuthenticationDetailsProvider provider) {
|
||||||
Objects.requireNonNull(account);
|
Objects.requireNonNull(provider);
|
||||||
ListCompartmentsResponse listCompartments = account.clients().identity()
|
IdentityClient identityClient = new IdentityClient(provider);
|
||||||
.listCompartments(ListCompartmentsRequest.builder()
|
ComputeClient computeClient = new ComputeClient(provider);
|
||||||
.compartmentId(account.tenantId())
|
ListCompartmentsResponse listCompartments = identityClient.listCompartments(ListCompartmentsRequest.builder()
|
||||||
.build());
|
.compartmentId(provider.getTenantId())
|
||||||
|
.build());
|
||||||
int addCount = 0;
|
int addCount = 0;
|
||||||
Set<String> compartmentIds = listCompartments.getItems().stream()
|
Set<String> compartmentIds = listCompartments.getItems().stream()
|
||||||
.map(Compartment::getId).collect(Collectors.toSet());
|
.map(Compartment::getId).collect(Collectors.toSet());
|
||||||
compartmentIds.add(account.tenantId());
|
compartmentIds.add(provider.getTenantId());
|
||||||
for (String compartmentId : compartmentIds) {
|
for (String compartmentId : compartmentIds) {
|
||||||
ListInstancesResponse listInstances = account.clients().compute()
|
ListInstancesResponse listInstances = computeClient.listInstances(ListInstancesRequest.builder()
|
||||||
.listInstances(ListInstancesRequest.builder()
|
.compartmentId(compartmentId)
|
||||||
.compartmentId(compartmentId)
|
.build());
|
||||||
.build());
|
|
||||||
for (Instance instance : listInstances.getItems()) {
|
for (Instance instance : listInstances.getItems()) {
|
||||||
if (instance.getLifecycleState() == Instance.LifecycleState.Terminated ||
|
|
||||||
instance.getLifecycleState() == Instance.LifecycleState.Terminating) {
|
|
||||||
log.debug("实例 {} 状态为 {}, 不添加该实例.", instance.getId(), instance.getLifecycleState());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ComputeInstance computeInstance = new ComputeInstance(this, instance.getId(),
|
ComputeInstance computeInstance = new ComputeInstance(this, instance.getId(),
|
||||||
compartmentId, instance.getImageId(), account);
|
provider.getUserId(), compartmentId, instance.getImageId(), provider);
|
||||||
|
|
||||||
addComputeInstance(computeInstance);
|
addComputeInstance(computeInstance);
|
||||||
log.debug("已为用户 {} 添加计算实例: {}", account.id(), instance.getId());
|
|
||||||
addCount ++;
|
addCount ++;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package net.lamgc.oracle.sentry;
|
package net.lamgc.oracle.sentry;
|
||||||
|
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ssh.ConfiguredForwardingFilter;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@ -12,9 +10,6 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component("sentry.constants")
|
@Component("sentry.constants")
|
||||||
public final class Constants {
|
public final class Constants {
|
||||||
|
|
||||||
/**
|
|
||||||
* 本类唯一实例, 请不要进行设置.
|
|
||||||
*/
|
|
||||||
public static Constants instance;
|
public static Constants instance;
|
||||||
|
|
||||||
private Constants() {
|
private Constants() {
|
||||||
@ -25,23 +20,9 @@ public final class Constants {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private String firstConnectionPolicy;
|
private String firstConnectionPolicy;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ConfiguredForwardingFilter forwardingFilter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 SSH 首次连接策略.
|
|
||||||
* @return 返回策略值.
|
|
||||||
*/
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String getFirstConnectionPolicy() {
|
public String getFirstConnectionPolicy() {
|
||||||
return firstConnectionPolicy;
|
return firstConnectionPolicy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取已配置的转发过滤器.
|
|
||||||
* @return 返回转发过滤器.
|
|
||||||
*/
|
|
||||||
public ConfiguredForwardingFilter getForwardingFilter() {
|
|
||||||
return forwardingFilter;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.account;
|
package net.lamgc.oracle.sentry;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.oracle.bmc.ConfigFileReader;
|
import com.oracle.bmc.ConfigFileReader;
|
||||||
import com.oracle.bmc.Region;
|
import com.oracle.bmc.Region;
|
||||||
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
||||||
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
|
import com.oracle.bmc.auth.SimpleAuthenticationDetailsProvider;
|
||||||
import com.oracle.bmc.auth.SimplePrivateKeySupplier;
|
import com.oracle.bmc.auth.SimplePrivateKeySupplier;
|
||||||
import net.lamgc.oracle.sentry.common.OracleBmcExceptionHandler;
|
import com.oracle.bmc.identity.IdentityClient;
|
||||||
import net.lamgc.oracle.sentry.common.retry.ExponentialBackoffDelayer;
|
import com.oracle.bmc.identity.requests.GetUserRequest;
|
||||||
import net.lamgc.oracle.sentry.common.retry.Retryer;
|
import com.oracle.bmc.identity.responses.GetUserResponse;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@ -21,25 +22,30 @@ import java.util.Map;
|
|||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Oracle 云账号管理器.
|
* Oracle 身份管理器.
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public final class OracleAccountManager {
|
public final class OracleIdentityManager {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(OracleAccountManager.class);
|
private final static Logger log = LoggerFactory.getLogger(OracleIdentityManager.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证身份 Map.
|
* 认证身份 Map.
|
||||||
* Key: Identity Id
|
* Key: Identity Id
|
||||||
* Value {@link AuthenticationDetailsProvider}
|
* Value {@link AuthenticationDetailsProvider}
|
||||||
*/
|
*/
|
||||||
private final Map<String, OracleAccount> accountMap = new ConcurrentHashMap<>();
|
private final Map<String, AuthenticationDetailsProvider> identityMap = new ConcurrentHashMap<>();
|
||||||
|
/**
|
||||||
|
* 用户名 Map.
|
||||||
|
* key Identity Id
|
||||||
|
* Value: Username
|
||||||
|
*/
|
||||||
|
private final Map<String, String> identityNameMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从目录扫描匹配的配置文件并加载.
|
* 从目录扫描匹配的配置文件并加载.
|
||||||
@ -59,60 +65,34 @@ public final class OracleAccountManager {
|
|||||||
if (configFiles == null) {
|
if (configFiles == null) {
|
||||||
throw new IOException("Unable to access the specified directory: " + directory.getCanonicalPath());
|
throw new IOException("Unable to access the specified directory: " + directory.getCanonicalPath());
|
||||||
}
|
}
|
||||||
AtomicInteger loadedCount = new AtomicInteger();
|
int loadedCount = 0;
|
||||||
for (File configFile : configFiles) {
|
for (File configFile : configFiles) {
|
||||||
try {
|
try {
|
||||||
Retryer<OracleAccount> retryer = Retryer.builder(() -> {
|
AuthenticationDetailsProvider provider = loadFromConfigFile(configFile);
|
||||||
OracleAccount account = loadFromConfigFile(configFile);
|
loadedCount ++;
|
||||||
if (account == null) {
|
log.info("已成功加载身份配置文件.\n\tUserId: {}\n\tUsername: {}\n\tPath: {}",
|
||||||
return null;
|
provider.getUserId(),
|
||||||
}
|
getIdentityName(provider.getUserId()),
|
||||||
log.info("已成功加载身份配置文件.\n\tUserId: {}\n\tUsername: {}\n\tPath: {}",
|
configFile.getCanonicalPath());
|
||||||
account.id(),
|
} catch (Exception e) {
|
||||||
account.name(),
|
log.error("加载身份配置文件时发生异常.(Path: {})\n{}", configFile.getCanonicalPath(), Throwables.getStackTraceAsString(e));
|
||||||
configFile.getCanonicalPath());
|
|
||||||
return account;
|
|
||||||
})
|
|
||||||
.delayer(new ExponentialBackoffDelayer())
|
|
||||||
.exceptionHandler(new OracleBmcExceptionHandler())
|
|
||||||
.retryNumber(8)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
Future<OracleAccount> future = retryer.executeAsync();
|
|
||||||
future.get(30, TimeUnit.SECONDS);
|
|
||||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
|
||||||
if (e instanceof TimeoutException) {
|
|
||||||
continue;
|
|
||||||
} else if (e instanceof InterruptedException) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error("加载身份配置文件时发生异常.(Path: {})\n{}", configFile.getCanonicalPath(), Throwables.getStackTraceAsString(e.getCause()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return loadedCount.get();
|
return loadedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过配置文件加载身份信息.
|
* 通过配置文件加载身份信息.
|
||||||
* <p> 加载成功后, 将会注册到身份管理器中.
|
|
||||||
* @param identityConfig 身份信息文件.
|
* @param identityConfig 身份信息文件.
|
||||||
* @return 返回已成功加载后, 配置文件对应的身份配置提供器.
|
|
||||||
* @throws IOException 如果读取文件发生问题时将抛出该异常.
|
* @throws IOException 如果读取文件发生问题时将抛出该异常.
|
||||||
*/
|
*/
|
||||||
public OracleAccount loadFromConfigFile(File identityConfig) throws IOException {
|
public AuthenticationDetailsProvider loadFromConfigFile(File identityConfig) throws IOException {
|
||||||
if (!identityConfig.exists()) {
|
if (!identityConfig.exists()) {
|
||||||
throw new FileNotFoundException(identityConfig.getAbsolutePath());
|
throw new FileNotFoundException(identityConfig.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigFileReader.ConfigFile config
|
ConfigFileReader.ConfigFile config
|
||||||
= ConfigFileReader.parse(identityConfig.getAbsolutePath());
|
= ConfigFileReader.parse(identityConfig.getAbsolutePath());
|
||||||
if (!checkIdentityProfileConfig(config)) {
|
|
||||||
log.warn("该配置文件缺少必要信息, 跳过加载.(Path: {})", identityConfig.getCanonicalPath());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
String keyFilePath = config.get("key_file");
|
String keyFilePath = config.get("key_file");
|
||||||
if (keyFilePath.startsWith(".")) {
|
if (keyFilePath.startsWith(".")) {
|
||||||
@ -131,30 +111,38 @@ public final class OracleAccountManager {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 尝试获取身份所属用户名, 以此检查该身份配置是否正确.
|
// 尝试获取身份所属用户名, 以此检查该身份配置是否正确.
|
||||||
OracleAccount oracleAccount = new OracleAccount(provider);
|
String identityName = getIdentityName0(provider);
|
||||||
String accountName = oracleAccount.name();
|
identityNameMap.put(provider.getUserId(), identityName);
|
||||||
if (accountName == null) {
|
identityMap.put(provider.getUserId(), provider);
|
||||||
throw new NullPointerException("Failed to obtain the account name. The identity configuration may be incorrect.");
|
return provider;
|
||||||
}
|
|
||||||
log.debug("已成功通过身份配置获取用户名称: {}", accountName);
|
|
||||||
accountMap.put(oracleAccount.id(), oracleAccount);
|
|
||||||
return oracleAccount;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkIdentityProfileConfig(ConfigFileReader.ConfigFile config) {
|
/**
|
||||||
String[] fields = new String[] {
|
* 获取身份所属用户的名称.
|
||||||
"key_file",
|
* @param provider 身份提供器.
|
||||||
"region",
|
* @return 返回用户名.
|
||||||
"tenancy",
|
*/
|
||||||
"user",
|
private String getIdentityName0(AuthenticationDetailsProvider provider) {
|
||||||
"fingerprint"
|
IdentityClient identityClient = new IdentityClient(provider);
|
||||||
};
|
GetUserResponse user = identityClient.getUser(GetUserRequest.builder()
|
||||||
for (String field : fields) {
|
.userId(provider.getUserId())
|
||||||
if (Strings.isNullOrEmpty(config.get(field))) {
|
.build());
|
||||||
return false;
|
return user.getUser().getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取身份信息所属的用户名.
|
||||||
|
* @param userId 身份信息所属的用户 Id.
|
||||||
|
* @return 返回用户名.
|
||||||
|
* @throws NullPointerException 当 userId 为 {@code null} 时抛出该异常.
|
||||||
|
* @throws NoSuchElementException 指定的 UserId 未找到对应用户名时抛出该异常.
|
||||||
|
*/
|
||||||
|
public String getIdentityName(String userId) {
|
||||||
|
Objects.requireNonNull(userId);
|
||||||
|
if (!identityMap.containsKey(userId)) {
|
||||||
|
throw new NoSuchElementException(userId);
|
||||||
}
|
}
|
||||||
return true;
|
return identityNameMap.get(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -164,20 +152,37 @@ public final class OracleAccountManager {
|
|||||||
* @throws NullPointerException 当 userId 为 {@code null} 时抛出该异常.
|
* @throws NullPointerException 当 userId 为 {@code null} 时抛出该异常.
|
||||||
* @throws NoSuchElementException 指定的 UserId 未找到对应 Provider 时抛出该异常.
|
* @throws NoSuchElementException 指定的 UserId 未找到对应 Provider 时抛出该异常.
|
||||||
*/
|
*/
|
||||||
public OracleAccount getAccountByUserId(String userId) {
|
public AuthenticationDetailsProvider getProviderByUserId(String userId) {
|
||||||
Objects.requireNonNull(userId);
|
Objects.requireNonNull(userId);
|
||||||
if (!accountMap.containsKey(userId)) {
|
if (!identityMap.containsKey(userId)) {
|
||||||
throw new NoSuchElementException(userId);
|
throw new NoSuchElementException(userId);
|
||||||
}
|
}
|
||||||
return accountMap.get(userId);
|
return identityMap.get(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出身份信息.
|
||||||
|
* <p> 不包含私钥.
|
||||||
|
* @return 返回 Json 形式的身份信息数组.
|
||||||
|
*/
|
||||||
|
public JsonArray exportIdentityInfo() {
|
||||||
|
JsonArray identityInfoArray = new JsonArray(identityMap.size());
|
||||||
|
for (AuthenticationDetailsProvider provider : identityMap.values()) {
|
||||||
|
JsonObject identity = new JsonObject();
|
||||||
|
identity.addProperty("UserId", provider.getUserId());
|
||||||
|
identity.addProperty("TenantId", provider.getTenantId());
|
||||||
|
identity.addProperty("Fingerprint", provider.getFingerprint());
|
||||||
|
identityInfoArray.add(identity);
|
||||||
|
}
|
||||||
|
return identityInfoArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有身份提供器.
|
* 获取所有身份提供器.
|
||||||
* @return 返回包含所有身份提供器的集合对象.
|
* @return 返回包含所有身份提供器的集合对象.
|
||||||
*/
|
*/
|
||||||
public Set<OracleAccount> getAccounts() {
|
public Set<AuthenticationDetailsProvider> getProviders() {
|
||||||
return accountMap.values().stream().collect(Collectors.toUnmodifiableSet());
|
return identityMap.values().stream().collect(Collectors.toUnmodifiableSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,23 +1,13 @@
|
|||||||
package net.lamgc.oracle.sentry.common.io;
|
package net.lamgc.oracle.sentry.common;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
/**
|
|
||||||
* 输入流包装器.
|
|
||||||
* <p> 准确来说只是屏蔽了 {@link InputStream#close()} 而已,
|
|
||||||
* 尝试修复 SSH 命令执行会话可能会关闭设置的输入流的问题.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class InputStreamWrapper extends InputStream {
|
public class InputStreamWrapper extends InputStream {
|
||||||
|
|
||||||
private final InputStream source;
|
private final InputStream source;
|
||||||
|
|
||||||
/**
|
|
||||||
* 包装一个输入流.
|
|
||||||
* @param source 输入源.
|
|
||||||
*/
|
|
||||||
public InputStreamWrapper(InputStream source) {
|
public InputStreamWrapper(InputStream source) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
@ -1,48 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 惰性加载器.
|
|
||||||
* <p> 该加载器只会在第一次获取对象时初始化.
|
|
||||||
* <p> 如果不用, 可能会导致内存消耗增加, 尤其是在管理大量帐号的情况下.
|
|
||||||
* <p> 对象在本加载器中为单例, 任何通过同一个加载器获取的对象都是同一个对象.
|
|
||||||
* @author LamGC
|
|
||||||
* @param <T> 对象类型.
|
|
||||||
*/
|
|
||||||
public final class LazyLoader<T> {
|
|
||||||
|
|
||||||
private final Supplier<T> supplier;
|
|
||||||
private final AtomicReference<T> object = new AtomicReference<>(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 构建惰性加载器.
|
|
||||||
* @param supplier 对象提供器.
|
|
||||||
*/
|
|
||||||
public LazyLoader(Supplier<T> supplier) {
|
|
||||||
this.supplier = Objects.requireNonNull(supplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取对象.
|
|
||||||
* <p> 如果是首次调用本方法, 本方法将通过 Supplier 获取一个对象, 缓存对象后返回.
|
|
||||||
* @return 返回惰性加载的对象.
|
|
||||||
*/
|
|
||||||
public T getInstance() {
|
|
||||||
if (object.get() == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (object.get() == null) {
|
|
||||||
T newInstance = supplier.get();
|
|
||||||
if (newInstance == null) {
|
|
||||||
throw new NullPointerException("Supplier is not allowed to return null.");
|
|
||||||
}
|
|
||||||
return object.compareAndSet(null, newInstance) ? newInstance : object.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return object.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common;
|
|
||||||
|
|
||||||
import com.oracle.bmc.model.BmcException;
|
|
||||||
import net.lamgc.oracle.sentry.common.retry.RetryExceptionHandler;
|
|
||||||
|
|
||||||
public class OracleBmcExceptionHandler implements RetryExceptionHandler {
|
|
||||||
@Override
|
|
||||||
public boolean handle(Exception e) {
|
|
||||||
if (e instanceof BmcException bmc) {
|
|
||||||
return bmc.getStatusCode() == -1;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,12 @@
|
|||||||
package net.lamgc.oracle.sentry.common.io;
|
package net.lamgc.oracle.sentry.common;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
|
||||||
/**
|
|
||||||
* 输出流包装器.
|
|
||||||
* <p> 准确来说只是屏蔽了 {@link OutputStream#close()} 而已,
|
|
||||||
* 尝试修复 SSH 命令执行会话可能会关闭设置的输出流的问题.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class OutputStreamWrapper extends OutputStream {
|
public class OutputStreamWrapper extends OutputStream {
|
||||||
|
|
||||||
private final OutputStream target;
|
private final OutputStream target;
|
||||||
|
|
||||||
/**
|
|
||||||
* 包装一个输出流.
|
|
||||||
* @param target 目标输出流.
|
|
||||||
*/
|
|
||||||
public OutputStreamWrapper(OutputStream target) {
|
public OutputStreamWrapper(OutputStream target) {
|
||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
@ -1,53 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.logging;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.Level;
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.filter.Filter;
|
|
||||||
import ch.qos.logback.core.spi.FilterReply;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 级别范围过滤器.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class LevelRangeFilter extends Filter<ILoggingEvent> {
|
|
||||||
|
|
||||||
private Level maxLevel;
|
|
||||||
private Level minLevel;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FilterReply decide(ILoggingEvent event) {
|
|
||||||
int level = event.getLevel().levelInt;
|
|
||||||
if (level > maxLevel.levelInt || level < minLevel.levelInt) {
|
|
||||||
return FilterReply.DENY;
|
|
||||||
}
|
|
||||||
return FilterReply.NEUTRAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置最高等级.
|
|
||||||
* <p> 仅限 XML 配置文件设置.
|
|
||||||
* @param maxLevel 最高等级(包括).
|
|
||||||
*/
|
|
||||||
public void setMaxLevel(String maxLevel) {
|
|
||||||
this.maxLevel = Level.toLevel(maxLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置最低等级.
|
|
||||||
* @param minLevel 允许的最低等级(包括).
|
|
||||||
*/
|
|
||||||
public void setMinLevel(String minLevel) {
|
|
||||||
this.minLevel = Level.toLevel(minLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
if (maxLevel != null && minLevel != null) {
|
|
||||||
if (maxLevel.levelInt < minLevel.levelInt) {
|
|
||||||
throw new IllegalArgumentException("The maximum level cannot be less than the minimum level.");
|
|
||||||
}
|
|
||||||
super.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.logging;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.filter.Filter;
|
|
||||||
import ch.qos.logback.core.spi.FilterReply;
|
|
||||||
import org.slf4j.Marker;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class MarkerFilter extends Filter<ILoggingEvent> {
|
|
||||||
|
|
||||||
private String markerName;
|
|
||||||
private FilterReply onMatch = FilterReply.NEUTRAL;
|
|
||||||
private FilterReply onMismatch = FilterReply.DENY;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FilterReply decide(ILoggingEvent event) {
|
|
||||||
return event.getMarker() != null && event.getMarker().getName().equals(markerName) ? onMatch : onMismatch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置要匹配的 {@link Marker} 名称.
|
|
||||||
* @param markerName Marker 名称.
|
|
||||||
*/
|
|
||||||
public void setMarkerName(String markerName) {
|
|
||||||
this.markerName = markerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 如果匹配, 需要执行的操作.
|
|
||||||
* @see FilterReply
|
|
||||||
* @param onMatch 操作名称.
|
|
||||||
*/
|
|
||||||
public void setOnMatch(String onMatch) {
|
|
||||||
this.onMatch = FilterReply.valueOf(onMatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 如果不匹配, 需要执行的操作.
|
|
||||||
* @see FilterReply
|
|
||||||
* @param onMismatch 操作名称.
|
|
||||||
*/
|
|
||||||
public void setOnMismatch(String onMismatch) {
|
|
||||||
this.onMismatch = FilterReply.valueOf(onMismatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start() {
|
|
||||||
if (markerName != null && onMatch != null && onMismatch != null) {
|
|
||||||
super.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.logging;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
|
||||||
import ch.qos.logback.core.filter.Filter;
|
|
||||||
import ch.qos.logback.core.spi.FilterReply;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class NoMarkerFilter extends Filter<ILoggingEvent> {
|
|
||||||
@Override
|
|
||||||
public FilterReply decide(ILoggingEvent event) {
|
|
||||||
return event.getMarker() != null ? FilterReply.DENY : FilterReply.NEUTRAL;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 断言校验器.
|
|
||||||
* <p> 校验器可帮助 {@link Retryer} 检查执行结果是否符合预期, 如果不符合预期, 那么将会重新执行任务.
|
|
||||||
* @param <R> 执行结果类型.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface AssertionChecker<R> {
|
|
||||||
|
|
||||||
void check(R result) throws RetryAssertException;
|
|
||||||
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指数退避延迟器.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class ExponentialBackoffDelayer implements RetryDelayer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long nextDelayTime(int currentRetryCount) {
|
|
||||||
return (4L << currentRetryCount) * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 固定延迟器.
|
|
||||||
* <p> 永远以指定的延迟重试.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class FixedTimeDelayer implements RetryDelayer {
|
|
||||||
|
|
||||||
private final long delay;
|
|
||||||
|
|
||||||
public FixedTimeDelayer(long time, TimeUnit unit) {
|
|
||||||
this(unit.toMillis(time));
|
|
||||||
}
|
|
||||||
|
|
||||||
public FixedTimeDelayer(long delay) {
|
|
||||||
this.delay = delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long nextDelayTime(int currentRetryCount) {
|
|
||||||
return delay;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 非空检查器.
|
|
||||||
* <p> 当执行结果为 null 时断言失败.
|
|
||||||
* @param <R>
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class NonNullChecker<R> implements AssertionChecker<R> {
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private final static NonNullChecker INSTANCE = new NonNullChecker();
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <R> NonNullChecker<R> getInstance() {
|
|
||||||
return (NonNullChecker<R>) INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NonNullChecker() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void check(Object result) throws RetryAssertException {
|
|
||||||
if (result == null) {
|
|
||||||
throw new RetryAssertException("The execution result is null.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试断言异常.
|
|
||||||
* <p> 当指定条件失败时抛出该异常.
|
|
||||||
* <p> 跑出该异常的原因并非任务执行失败, 而是任务执行的结果与预期不符.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class RetryAssertException extends Exception {
|
|
||||||
|
|
||||||
public RetryAssertException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RetryAssertException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试延迟器.
|
|
||||||
* <p> 用于决定每次重试的间隔时间, 可根据重试次数调整间隔时常, 以避免频繁执行影响性能.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public interface RetryDelayer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取下一次重试延迟时间.
|
|
||||||
* @param currentRetryCount 当前重试次数, 如果第一次重试失败, 则本参数为 0.
|
|
||||||
* @return 返回延迟时间.
|
|
||||||
*/
|
|
||||||
long nextDelayTime(int currentRetryCount);
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface RetryExceptionHandler {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理异常, 并指示是否继续重试.
|
|
||||||
* @param e 异常对象.
|
|
||||||
* @return 如果可以继续重试, 返回 {@code true}.
|
|
||||||
*/
|
|
||||||
boolean handle(Exception e);
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试失败异常.
|
|
||||||
* <p> 该异常是由于某一个任务尝试执行失败过多而抛出, 失败的原因可能是任务执行时抛出异常, 或执行结果与预期不符.
|
|
||||||
*/
|
|
||||||
public final class RetryFailedException extends Exception {
|
|
||||||
|
|
||||||
public RetryFailedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RetryFailedException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RetryFailedException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 可重试任务.
|
|
||||||
* <p> 实现该方法后, 该任务可在 {@link Retryer} 运行, 可用于一些需重试的任务.
|
|
||||||
* @param <R> 执行结果类型.
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface RetryableTask<R> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运行方法.
|
|
||||||
* <p> 当该方法抛出异常, 或经 {@link AssertionChecker} 检查认为结果与预期不符时, 将会被重新运行.
|
|
||||||
* <p> 请注意, 即使任务执行完成, 若 {@link AssertionChecker} 认为结果与预期不符, 任务将会被重新运行, 请注意处理该情况.
|
|
||||||
* @throws Exception 当异常抛出时, 将视为执行失败, 重试器将根据设定自动重新执行.
|
|
||||||
* @return 根据需要可返回.
|
|
||||||
*/
|
|
||||||
R run() throws Exception;
|
|
||||||
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重试器.
|
|
||||||
* <p> 通过重试器, 可以对某一个可能失败的任务做重试, 尽可能确保任务执行成功.
|
|
||||||
* @param <R> 任务结果类型.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class Retryer<R> {
|
|
||||||
|
|
||||||
|
|
||||||
private final static ThreadPoolExecutor ASYNC_EXECUTOR = new ThreadPoolExecutor(
|
|
||||||
1,
|
|
||||||
Math.min(4, Math.max(1, Runtime.getRuntime().availableProcessors() / 2)),
|
|
||||||
10, TimeUnit.SECONDS,
|
|
||||||
new LinkedBlockingDeque<>(),
|
|
||||||
new ThreadFactoryBuilder()
|
|
||||||
.setNameFormat("Thread-Retryer-%d")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
|
|
||||||
private final RetryableTask<R> task;
|
|
||||||
private final int retryNumber;
|
|
||||||
private final RetryDelayer delayer;
|
|
||||||
private final Set<AssertionChecker<R>> checkers = new HashSet<>();
|
|
||||||
private final RetryExceptionHandler handler;
|
|
||||||
|
|
||||||
private Retryer(RetryableTask<R> task, int retryNumber, RetryDelayer delayer, Set<AssertionChecker<R>> checkers, RetryExceptionHandler handler) {
|
|
||||||
this.handler = handler;
|
|
||||||
if (retryNumber < 0) {
|
|
||||||
throw new IllegalArgumentException("The number of retries is not allowed to be negative: " + retryNumber);
|
|
||||||
}
|
|
||||||
this.task = Objects.requireNonNull(task);
|
|
||||||
this.delayer = Objects.requireNonNull(delayer);
|
|
||||||
this.retryNumber = retryNumber;
|
|
||||||
if (checkers != null && !checkers.isEmpty()) {
|
|
||||||
this.checkers.addAll(checkers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("BusyWait")
|
|
||||||
private R execute0() throws Exception {
|
|
||||||
Exception lastException;
|
|
||||||
int currentRetryCount = 0;
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
R result = task.run();
|
|
||||||
checkResult(result);
|
|
||||||
return result;
|
|
||||||
} catch (Exception e) {
|
|
||||||
lastException = e;
|
|
||||||
if (e instanceof InterruptedException) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (handler != null && !handler.handle(e)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (currentRetryCount >= retryNumber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
long delayTime = delayer.nextDelayTime(currentRetryCount);
|
|
||||||
if (delayTime > 0) {
|
|
||||||
try {
|
|
||||||
Thread.sleep(delayTime);
|
|
||||||
} catch (InterruptedException interrupted) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentRetryCount ++;
|
|
||||||
}
|
|
||||||
} while (true);
|
|
||||||
throw lastException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用 {@link AssertionChecker} 检查结果是否符合预期.
|
|
||||||
* <p> 当结果不符合检验器预期时, 检验器将抛出 {@link RetryAssertException} 来表示结果不符预期,
|
|
||||||
* {@link Retryer} 将会重试该任务.
|
|
||||||
* @param result 执行结果.
|
|
||||||
* @throws RetryAssertException 当断言检验器对结果断言失败时抛出该异常.
|
|
||||||
*/
|
|
||||||
private void checkResult(R result) throws RetryAssertException {
|
|
||||||
for (AssertionChecker<R> checker : checkers) {
|
|
||||||
checker.check(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异步执行任务.
|
|
||||||
* <p> 使用线程池执行 task.
|
|
||||||
* @return 返回 Future 对象以跟踪异步执行结果.
|
|
||||||
*/
|
|
||||||
public Future<R> executeAsync() {
|
|
||||||
return ASYNC_EXECUTOR.submit(this::execute0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 同步执行任务.
|
|
||||||
* @return 如果执行完成且成功, 返回执行结果.
|
|
||||||
* @throws RetryFailedException 当重试多次仍失败时抛出该异常.
|
|
||||||
*/
|
|
||||||
public R execute() throws RetryFailedException {
|
|
||||||
Future<R> future = executeAsync();
|
|
||||||
try {
|
|
||||||
return future.get();
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
if (e instanceof ExecutionException exception) {
|
|
||||||
throw new RetryFailedException("Failure after a certain number of attempts.", exception);
|
|
||||||
} else {
|
|
||||||
throw new RetryFailedException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取一个构建器.
|
|
||||||
* @param task 需要重试的任务.
|
|
||||||
* @param <R> 结果类型.
|
|
||||||
* @return 返回新的构造器.
|
|
||||||
*/
|
|
||||||
public static <R> Builder<R> builder(RetryableTask<R> task) {
|
|
||||||
return new Builder<>(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link Retryer} 构造器.
|
|
||||||
* <p> 可通过链式调用快速创建 {@link Retryer}.
|
|
||||||
* @param <R> 任务结果类型.
|
|
||||||
*/
|
|
||||||
public static class Builder<R> {
|
|
||||||
|
|
||||||
private final RetryableTask<R> task;
|
|
||||||
private RetryDelayer delayer = new FixedTimeDelayer(0);
|
|
||||||
private int retryNumber = 0;
|
|
||||||
private final Set<AssertionChecker<R>> checkers = new HashSet<>();
|
|
||||||
private RetryExceptionHandler handler = (e) -> true;
|
|
||||||
|
|
||||||
private Builder(RetryableTask<R> task) {
|
|
||||||
this.task = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Retryer<R> create() {
|
|
||||||
return new Retryer<>(task, retryNumber, delayer, checkers, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<R> delayer(RetryDelayer delayer) {
|
|
||||||
this.delayer = delayer;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<R> retryIfReturnNull() {
|
|
||||||
this.checkers.add(NonNullChecker.getInstance());
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<R> retryNumber(int number) {
|
|
||||||
this.retryNumber = number;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<R> checker(AssertionChecker<R> checker) {
|
|
||||||
checkers.add(checker);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder<R> exceptionHandler(RetryExceptionHandler handler) {
|
|
||||||
this.handler = handler == null ? (e) -> true : handler;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.account;
|
|
||||||
|
|
||||||
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
|
||||||
import com.oracle.bmc.identity.model.RegionSubscription;
|
|
||||||
import com.oracle.bmc.identity.model.User;
|
|
||||||
import com.oracle.bmc.identity.requests.GetUserRequest;
|
|
||||||
import com.oracle.bmc.identity.requests.ListRegionSubscriptionsRequest;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Oracle 云账户.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class OracleAccount {
|
|
||||||
|
|
||||||
private final AuthenticationDetailsProvider provider;
|
|
||||||
private final OracleClients clients;
|
|
||||||
private final User user;
|
|
||||||
|
|
||||||
OracleAccount(AuthenticationDetailsProvider provider) {
|
|
||||||
this.provider = Objects.requireNonNull(provider);
|
|
||||||
this.clients = new OracleClients(provider);
|
|
||||||
this.user = clients.identity().getUser(GetUserRequest.builder()
|
|
||||||
.userId(provider.getUserId())
|
|
||||||
.build()).getUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取帐号 Id.
|
|
||||||
* @return 返回帐号 OCID.
|
|
||||||
*/
|
|
||||||
public String id() {
|
|
||||||
return provider.getUserId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取租户 Id.
|
|
||||||
* <p> 该 Id 同时也是根区间 Id.
|
|
||||||
* @return 返回租户 Id.
|
|
||||||
*/
|
|
||||||
public String tenantId() {
|
|
||||||
return provider.getTenantId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户名.
|
|
||||||
* @return 返回用户名称.
|
|
||||||
*/
|
|
||||||
public String name() {
|
|
||||||
return this.user.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户说明信息.
|
|
||||||
* @return 返回设定的用户说明信息.
|
|
||||||
*/
|
|
||||||
public String description() {
|
|
||||||
return this.user.getDescription();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户订阅的所有地区.
|
|
||||||
* @return 返回已订阅地区列表.
|
|
||||||
*/
|
|
||||||
public List<RegionSubscription> regions() {
|
|
||||||
return this.clients.identity().listRegionSubscriptions(ListRegionSubscriptionsRequest.builder()
|
|
||||||
.tenancyId(this.tenantId())
|
|
||||||
.build()).getItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取帐号主区域.
|
|
||||||
* @return 返回帐号主区域.
|
|
||||||
* @throws NoSuchElementException 当主区域搜索失败时抛出.
|
|
||||||
*/
|
|
||||||
public RegionSubscription mainRegion() {
|
|
||||||
for (RegionSubscription subscription : regions()) {
|
|
||||||
if (subscription.getIsHomeRegion()) {
|
|
||||||
return subscription;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new NoSuchElementException("Primary region not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取该账户所属的 API 客户端集合.
|
|
||||||
* @return 返回该账户所属的甲骨文 API 客户端集.
|
|
||||||
*/
|
|
||||||
public OracleClients clients() {
|
|
||||||
return clients;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (o == null || getClass() != o.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
AuthenticationDetailsProvider thatProvider = ((OracleAccount) o).provider;
|
|
||||||
return provider.getUserId().equals(thatProvider.getUserId()) &&
|
|
||||||
provider.getTenantId().equals(thatProvider.getTenantId()) &&
|
|
||||||
provider.getFingerprint().equals(thatProvider.getFingerprint()) &&
|
|
||||||
provider.getKeyId().equals(thatProvider.getKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(provider.getUserId(), provider.getTenantId(), provider.getFingerprint(), provider.getKeyId());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.account;
|
|
||||||
|
|
||||||
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
|
||||||
import com.oracle.bmc.core.BlockstorageClient;
|
|
||||||
import com.oracle.bmc.core.ComputeClient;
|
|
||||||
import com.oracle.bmc.core.VirtualNetworkClient;
|
|
||||||
import com.oracle.bmc.identity.IdentityClient;
|
|
||||||
import com.oracle.bmc.objectstorage.ObjectStorageClient;
|
|
||||||
import net.lamgc.oracle.sentry.common.LazyLoader;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 甲骨文 SDK 客户端.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class OracleClients {
|
|
||||||
|
|
||||||
private final Map<Class<?>, LazyLoader<?>> LAZY_LOADER_MAP = new ConcurrentHashMap<>();
|
|
||||||
private final AuthenticationDetailsProvider provider;
|
|
||||||
|
|
||||||
OracleClients(AuthenticationDetailsProvider provider) {
|
|
||||||
this.provider = Objects.requireNonNull(provider);
|
|
||||||
initialLazyLoad();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialLazyLoad() {
|
|
||||||
registryLazyLoader(ComputeClient.class, () -> new ComputeClient(provider));
|
|
||||||
registryLazyLoader(VirtualNetworkClient.class, () -> new VirtualNetworkClient(provider));
|
|
||||||
registryLazyLoader(BlockstorageClient.class, () -> new BlockstorageClient(provider));
|
|
||||||
registryLazyLoader(IdentityClient.class, () -> new IdentityClient(provider));
|
|
||||||
registryLazyLoader(ObjectStorageClient.class, () -> new ObjectStorageClient(provider));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取计算类客户端.
|
|
||||||
* @return 获取计算类客户端对象.
|
|
||||||
*/
|
|
||||||
public ComputeClient compute() {
|
|
||||||
return getInstance(ComputeClient.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取网络客户端.
|
|
||||||
* @return 返回 VCN 操作客户端.
|
|
||||||
*/
|
|
||||||
public VirtualNetworkClient network() {
|
|
||||||
return getInstance(VirtualNetworkClient.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取块存储客户端.
|
|
||||||
* <p> 仅限计算实例的存储.
|
|
||||||
* @return 返回块存储客户端.
|
|
||||||
*/
|
|
||||||
public BlockstorageClient blockStorage() {
|
|
||||||
return getInstance(BlockstorageClient.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取身份客户端.
|
|
||||||
* <p> 用于访问身份相关 API.
|
|
||||||
* @return 返回身份客户端.
|
|
||||||
*/
|
|
||||||
public IdentityClient identity() {
|
|
||||||
return getInstance(IdentityClient.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取对象存储客户端.
|
|
||||||
* <p> 不包括计算实例的存储.
|
|
||||||
* @return 获取对象存储客户端.
|
|
||||||
*/
|
|
||||||
public ObjectStorageClient objectStorage() {
|
|
||||||
return getInstance(ObjectStorageClient.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实例.
|
|
||||||
* @param type 实例类.
|
|
||||||
* @param <T> 实例类型.
|
|
||||||
* @return 返回对象.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> T getInstance(Class<?> type) {
|
|
||||||
Objects.requireNonNull(type);
|
|
||||||
if (!LAZY_LOADER_MAP.containsKey(type)) {
|
|
||||||
throw new NoSuchElementException("No lazy loader of this type was found: " + type);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (T) LAZY_LOADER_MAP.get(type).getInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册惰性加载器.
|
|
||||||
* @param type 对象类.
|
|
||||||
* @param supplier 对象提供器.
|
|
||||||
* @param <T> 对象类型.
|
|
||||||
*/
|
|
||||||
private <T> void registryLazyLoader(Class<? extends T> type, Supplier<T> supplier) {
|
|
||||||
Objects.requireNonNull(type);
|
|
||||||
Objects.requireNonNull(supplier);
|
|
||||||
LAZY_LOADER_MAP.put(type, new LazyLoader<>(supplier));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute;
|
|
||||||
|
|
||||||
import com.oracle.bmc.core.model.Image;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 引导镜像.
|
|
||||||
* <p> 创建实例时所指定的引导镜像.
|
|
||||||
* <p> 如果实例经过其他方式重新安装了系统, 则本信息有偏差.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class BootImage {
|
|
||||||
|
|
||||||
private final Image image;
|
|
||||||
|
|
||||||
BootImage(Image image) {
|
|
||||||
this.image = image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取镜像 Id.
|
|
||||||
* <p> 该 Id 可在创建服务器时指定所使用的系统镜像.
|
|
||||||
* @return 返回镜像在 Oracle 的 Id.
|
|
||||||
*/
|
|
||||||
public String getImageId() {
|
|
||||||
return image.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取镜像所在的区域 Id.
|
|
||||||
* @return 返回镜像所在区域的 Id.
|
|
||||||
*/
|
|
||||||
public String getCompartmentId() {
|
|
||||||
return image.getCompartmentId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取镜像系统名称.
|
|
||||||
* <p> 比如 Ubuntu 或者说 CentOS.
|
|
||||||
* @return 返回系统名称(不是计算机名称).
|
|
||||||
*/
|
|
||||||
public String getOS() {
|
|
||||||
return image.getOperatingSystem();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取该镜像基于某一镜像的 Id.
|
|
||||||
* <p> Oracle 提供了方法, 可以通过当前服务器生成新的镜像,
|
|
||||||
* 生成后, 新的镜像就是基于原镜像生成, 该项就不为空.
|
|
||||||
* @return 如果存在, 返回基础镜像 Id, 无基础镜像则返回 {@code null}.
|
|
||||||
*/
|
|
||||||
public String getBaseImageId() {
|
|
||||||
return image.getBaseImageId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 镜像的显示名称.
|
|
||||||
* <p> 获取镜像显示名, 该名称与该镜像系统在官方为 iso 的命名差不多.
|
|
||||||
* @return 获取镜像的显示名称.
|
|
||||||
*/
|
|
||||||
public String getName() {
|
|
||||||
return image.getDisplayName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取镜像大小.
|
|
||||||
* @return 返回镜像大小, 单位为 MiB.
|
|
||||||
*/
|
|
||||||
public Long getSize() {
|
|
||||||
return image.getSizeInMBs();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统版本号.
|
|
||||||
* @return 返回镜像内系统的版本号, 如果版本较旧且服务器更新过系统, 则版本号不是最新的.
|
|
||||||
*/
|
|
||||||
public String getOSVersion() {
|
|
||||||
return image.getOperatingSystemVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取镜像创建时间.
|
|
||||||
* @return 获取镜像创建时间.
|
|
||||||
*/
|
|
||||||
public Date getTimeCreated() {
|
|
||||||
return image.getTimeCreated();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,8 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute;
|
package net.lamgc.oracle.sentry.oci.compute;
|
||||||
|
|
||||||
|
import com.oracle.bmc.auth.AuthenticationDetailsProvider;
|
||||||
import com.oracle.bmc.core.ComputeClient;
|
import com.oracle.bmc.core.ComputeClient;
|
||||||
|
import com.oracle.bmc.core.model.Image;
|
||||||
import com.oracle.bmc.core.model.Instance;
|
import com.oracle.bmc.core.model.Instance;
|
||||||
import com.oracle.bmc.core.requests.GetImageRequest;
|
import com.oracle.bmc.core.requests.GetImageRequest;
|
||||||
import com.oracle.bmc.core.requests.GetInstanceRequest;
|
import com.oracle.bmc.core.requests.GetInstanceRequest;
|
||||||
@ -8,7 +10,7 @@ import com.oracle.bmc.core.requests.InstanceActionRequest;
|
|||||||
import com.oracle.bmc.core.responses.GetImageResponse;
|
import com.oracle.bmc.core.responses.GetImageResponse;
|
||||||
import com.oracle.bmc.core.responses.GetInstanceResponse;
|
import com.oracle.bmc.core.responses.GetInstanceResponse;
|
||||||
import com.oracle.bmc.core.responses.InstanceActionResponse;
|
import com.oracle.bmc.core.responses.InstanceActionResponse;
|
||||||
import net.lamgc.oracle.sentry.oci.account.OracleAccount;
|
import net.lamgc.oracle.sentry.ComputeInstanceManager;
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ssh.InstanceSsh;
|
import net.lamgc.oracle.sentry.oci.compute.ssh.InstanceSsh;
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ssh.SshAuthInfo;
|
import net.lamgc.oracle.sentry.oci.compute.ssh.SshAuthInfo;
|
||||||
|
|
||||||
@ -23,62 +25,54 @@ public final class ComputeInstance {
|
|||||||
private final ComputeInstanceManager instanceManager;
|
private final ComputeInstanceManager instanceManager;
|
||||||
|
|
||||||
private final String instanceId;
|
private final String instanceId;
|
||||||
|
private final String userId;
|
||||||
private final String compartmentId;
|
private final String compartmentId;
|
||||||
private final String imageId;
|
private final String imageId;
|
||||||
|
private final AuthenticationDetailsProvider authProvider;
|
||||||
private final InstanceNetwork network;
|
private final InstanceNetwork network;
|
||||||
private final OracleAccount fromAccount;
|
|
||||||
|
|
||||||
private final ComputeClient computeClient;
|
private final ComputeClient computeClient;
|
||||||
|
|
||||||
/**
|
public ComputeInstance(ComputeInstanceManager instanceManager, String instanceId, String userId,
|
||||||
* 构造一个计算实例对象.
|
String compartmentId, String imageId, AuthenticationDetailsProvider provider) {
|
||||||
* @param instanceManager 实例所属的管理器.
|
|
||||||
* @param instanceId 实例 Id.
|
|
||||||
* @param compartmentId 实例所在区域的 Id.
|
|
||||||
* @param imageId 镜像 Id.
|
|
||||||
* @param fromAccount 所属用户的身份配置提供器.
|
|
||||||
*/
|
|
||||||
public ComputeInstance(ComputeInstanceManager instanceManager, String instanceId,
|
|
||||||
String compartmentId, String imageId, OracleAccount fromAccount) {
|
|
||||||
this.instanceManager = instanceManager;
|
this.instanceManager = instanceManager;
|
||||||
this.instanceId = instanceId;
|
this.instanceId = instanceId;
|
||||||
|
this.userId = userId;
|
||||||
this.compartmentId = compartmentId;
|
this.compartmentId = compartmentId;
|
||||||
this.imageId = imageId;
|
this.imageId = imageId;
|
||||||
this.fromAccount = fromAccount;
|
this.authProvider = provider;
|
||||||
|
|
||||||
|
computeClient = new ComputeClient(provider);
|
||||||
this.network = new InstanceNetwork(this);
|
this.network = new InstanceNetwork(this);
|
||||||
this.computeClient = fromAccount.clients().compute();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实例 Id.
|
|
||||||
* <p> 可通过实例 Id 直接调用 Oracle Cloud SDK, 也可以作为服务器的唯一标识.
|
|
||||||
* @return 返回实例 Id.
|
|
||||||
*/
|
|
||||||
public String getInstanceId() {
|
public String getInstanceId() {
|
||||||
return instanceId;
|
return instanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getUserId() {
|
||||||
* 获取服务器所属区域的 Id.
|
return userId;
|
||||||
* <p> 使用的资源必须要处于同一区域, 例如 IP 资源, 磁盘.
|
}
|
||||||
* @return 返回服务器所属区域的 Id.
|
|
||||||
*/
|
|
||||||
public String getCompartmentId() {
|
public String getCompartmentId() {
|
||||||
return compartmentId;
|
return compartmentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getImageId() {
|
||||||
|
return imageId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取并返回实例镜像信息.
|
* 获取并返回实例镜像信息.
|
||||||
* <p> 可获取系统信息.
|
* <p> 可获取系统信息.
|
||||||
* <p> 如果实例被 dd, 则本信息不准确.
|
* <p> 如果实例被 dd, 则本信息不准确.
|
||||||
* @return 返回实例信息.
|
* @return 返回实例信息.
|
||||||
*/
|
*/
|
||||||
public BootImage getImage() {
|
public Image getImage() {
|
||||||
GetImageResponse image = computeClient.getImage(GetImageRequest.builder()
|
GetImageResponse image = computeClient.getImage(GetImageRequest.builder()
|
||||||
.imageId(imageId)
|
.imageId(imageId)
|
||||||
.build());
|
.build());
|
||||||
return new BootImage(image.getImage());
|
return image.getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,58 +84,37 @@ public final class ComputeInstance {
|
|||||||
return network;
|
return network;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实例的 SSH 客户端.
|
|
||||||
* @return 返回实例 SSH 客户端.
|
|
||||||
*/
|
|
||||||
public InstanceSsh ssh() {
|
public InstanceSsh ssh() {
|
||||||
String instanceState = getInstanceState();
|
Instance.LifecycleState instanceState = getInstanceState();
|
||||||
if (!Instance.LifecycleState.Running.name().equals(instanceState)) {
|
if (instanceState != Instance.LifecycleState.Running) {
|
||||||
throw new IllegalStateException("The state of the current instance cannot connect to SSH: " + instanceState);
|
throw new IllegalStateException("The state of the current instance cannot connect to SSH: " + instanceState);
|
||||||
}
|
}
|
||||||
return new InstanceSsh(this, getSshIdentity());
|
return new InstanceSsh(this, getSshIdentity());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Instance.LifecycleState getInstanceState() {
|
||||||
* 获取实例当前状态.
|
|
||||||
* <p> 实例可有以下状态:
|
|
||||||
* <ul>
|
|
||||||
* <li> Moving: 实例正在转移中;
|
|
||||||
* <li> Provisioning: 实例正在预分配中(正在创建实例);
|
|
||||||
* <li> Running: 实例正在运行中;
|
|
||||||
* <li> Starting: 实例正在启动中;
|
|
||||||
* <li> Stopping: 实例正在停止中;
|
|
||||||
* <li> Stopped: 实例已停止运行;
|
|
||||||
* <li> CreatingImage: 正在通过实例构建镜像;
|
|
||||||
* <li> Terminating: 正在终止实例(正在删除实例);
|
|
||||||
* <li> Terminated: 实例已经终止(已删除实例)
|
|
||||||
* </ul>
|
|
||||||
* @return 返回实例状态.
|
|
||||||
*/
|
|
||||||
public String getInstanceState() {
|
|
||||||
GetInstanceResponse instance = computeClient.getInstance(GetInstanceRequest.builder()
|
GetInstanceResponse instance = computeClient.getInstance(GetInstanceRequest.builder()
|
||||||
.instanceId(instanceId)
|
.instanceId(instanceId)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
return instance.getInstance().getLifecycleState().name();
|
return instance.getInstance().getLifecycleState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 对实例执行操作.
|
* 对实例执行操作.
|
||||||
* @param action 操作类型.
|
* @param action 操作类型.
|
||||||
* @return 如果成功, 返回实例最新状态(返回值意义见 {@link #getInstanceState()} 文档).
|
* @return 如果成功, 返回实例最新状态.
|
||||||
*/
|
*/
|
||||||
public String execAction(InstanceAction action) {
|
public Instance.LifecycleState execAction(InstanceAction action) {
|
||||||
InstanceActionResponse actionResponse = computeClient.instanceAction(InstanceActionRequest.builder()
|
InstanceActionResponse actionResponse = computeClient.instanceAction(InstanceActionRequest.builder()
|
||||||
.instanceId(instanceId)
|
.instanceId(instanceId)
|
||||||
.action(action.getActionValue())
|
.action(action.getActionValue())
|
||||||
.build());
|
.build());
|
||||||
return actionResponse.getInstance().getLifecycleState().name();
|
return actionResponse.getInstance().getLifecycleState();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取实例名称.
|
* 获取实例名称.
|
||||||
* @return 返回实例显示名.
|
|
||||||
*/
|
*/
|
||||||
public String getInstanceName() {
|
public String getInstanceName() {
|
||||||
GetInstanceResponse instance = computeClient.getInstance(GetInstanceRequest.builder()
|
GetInstanceResponse instance = computeClient.getInstance(GetInstanceRequest.builder()
|
||||||
@ -158,6 +131,10 @@ public final class ComputeInstance {
|
|||||||
return computeClient;
|
return computeClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthenticationDetailsProvider getAuthProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
@ -167,29 +144,20 @@ public final class ComputeInstance {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ComputeInstance that = (ComputeInstance) o;
|
ComputeInstance that = (ComputeInstance) o;
|
||||||
return instanceId.equals(that.instanceId) && fromAccount.equals(that.fromAccount) && compartmentId.equals(that.compartmentId);
|
return instanceId.equals(that.instanceId) && userId.equals(that.userId) && compartmentId.equals(that.compartmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(instanceId, fromAccount, compartmentId);
|
return Objects.hash(instanceId, userId, compartmentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 SSH 认证信息.
|
* 获取 SSH 认证信息.
|
||||||
* @return 返回实例 SSH 认证信息.
|
|
||||||
* @throws java.util.NoSuchElementException 如果没有指定配置信息则抛出该异常.
|
|
||||||
*/
|
*/
|
||||||
private SshAuthInfo getSshIdentity() {
|
private SshAuthInfo getSshIdentity() {
|
||||||
return instanceManager.getSshIdentityProvider()
|
return instanceManager.getSshIdentityProvider()
|
||||||
.getAuthInfoByInstanceId(instanceId);
|
.getAuthInfoByInstanceId(instanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实例所属的 Oracle 云帐号对象.
|
|
||||||
* @return 返回实例所属帐号对象.
|
|
||||||
*/
|
|
||||||
public OracleAccount getFromAccount() {
|
|
||||||
return fromAccount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute;
|
package net.lamgc.oracle.sentry.oci.compute;
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例动作.
|
|
||||||
* <p> 可对实例执行的操作.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public enum InstanceAction {
|
public enum InstanceAction {
|
||||||
/**
|
/**
|
||||||
* 启动实例.
|
* 启动实例.
|
||||||
@ -35,10 +30,6 @@ public enum InstanceAction {
|
|||||||
this.actionValue = actionValue;
|
this.actionValue = actionValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取动作的 API 调用值.
|
|
||||||
* @return 返回 API 所规定的对应值.
|
|
||||||
*/
|
|
||||||
public String getActionValue() {
|
public String getActionValue() {
|
||||||
return actionValue;
|
return actionValue;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute;
|
package net.lamgc.oracle.sentry.oci.compute;
|
||||||
|
|
||||||
import com.oracle.bmc.core.VirtualNetworkClient;
|
import com.oracle.bmc.core.VirtualNetworkClient;
|
||||||
import com.oracle.bmc.core.model.Vnic;
|
|
||||||
import com.oracle.bmc.core.model.VnicAttachment;
|
import com.oracle.bmc.core.model.VnicAttachment;
|
||||||
import com.oracle.bmc.core.requests.GetVnicRequest;
|
import com.oracle.bmc.core.requests.GetVnicRequest;
|
||||||
import com.oracle.bmc.core.requests.ListVnicAttachmentsRequest;
|
import com.oracle.bmc.core.requests.ListVnicAttachmentsRequest;
|
||||||
@ -14,7 +13,6 @@ import java.util.NoSuchElementException;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实例网络操作类.
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public class InstanceNetwork {
|
public class InstanceNetwork {
|
||||||
@ -24,7 +22,7 @@ public class InstanceNetwork {
|
|||||||
|
|
||||||
InstanceNetwork(ComputeInstance instance) {
|
InstanceNetwork(ComputeInstance instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.vcnClient = instance.getFromAccount().clients().network();
|
this.vcnClient = new VirtualNetworkClient(this.instance.getAuthProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,24 +55,8 @@ public class InstanceNetwork {
|
|||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
|
|
||||||
return listVnicAttachments.getItems();
|
return listVnicAttachments.getItems().stream().toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取实例的主要 VNIC(虚拟网络接口)
|
|
||||||
* @return 返回实例的主要 VNIC 对象.
|
|
||||||
* @throws NoSuchElementException 当找不到主要 VNIC 时抛出该异常.
|
|
||||||
*/
|
|
||||||
public Vnic getPrimaryVnic() {
|
|
||||||
for (VnicAttachment vnicAttachment : listVnicAttachments()) {
|
|
||||||
GetVnicResponse vnic = vcnClient.getVnic(GetVnicRequest.builder()
|
|
||||||
.vnicId(vnicAttachment.getVnicId())
|
|
||||||
.build());
|
|
||||||
if (vnic.getVnic().getIsPrimary()) {
|
|
||||||
return vnic.getVnic();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new NoSuchElementException("Primary vnic not found.");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
||||||
|
|
||||||
import net.lamgc.oracle.sentry.common.io.InputStreamWrapper;
|
import net.lamgc.oracle.sentry.common.InputStreamWrapper;
|
||||||
import net.lamgc.oracle.sentry.common.io.OutputStreamWrapper;
|
import net.lamgc.oracle.sentry.common.OutputStreamWrapper;
|
||||||
import org.apache.sshd.client.channel.ChannelExec;
|
import org.apache.sshd.client.channel.ChannelExec;
|
||||||
import org.apache.sshd.client.channel.ClientChannelEvent;
|
import org.apache.sshd.client.channel.ClientChannelEvent;
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ public final class CommandExecSession implements Closeable {
|
|||||||
|
|
||||||
private final ChannelExec channelExec;
|
private final ChannelExec channelExec;
|
||||||
|
|
||||||
CommandExecSession(ChannelExec channelExec) {
|
public CommandExecSession(ChannelExec channelExec) {
|
||||||
this.channelExec = channelExec;
|
this.channelExec = channelExec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,11 +54,9 @@ public final class CommandExecSession implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* 等待程序执行完毕.
|
* 等待程序执行完毕.
|
||||||
* @param timeout 超时时间, 0 为无限等待(单位: 毫秒).
|
* @param timeout 超时时间, 0 为无限等待(单位: 毫秒).
|
||||||
* @return 如果在超时时间内返回, 返回 {@code true}, 超时返回 {@code false}.
|
|
||||||
*/
|
*/
|
||||||
public boolean waitFor(long timeout) {
|
public void waitFor(long timeout) {
|
||||||
return !channelExec.waitFor(EnumSet.of(ClientChannelEvent.EXIT_STATUS, ClientChannelEvent.EXIT_SIGNAL), timeout)
|
channelExec.waitFor(EnumSet.of(ClientChannelEvent.EXIT_STATUS, ClientChannelEvent.EXIT_SIGNAL), timeout);
|
||||||
.contains(ClientChannelEvent.TIMEOUT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,7 +71,6 @@ public final class CommandExecSession implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* 设置输入流.
|
* 设置输入流.
|
||||||
* <p> 设置待执行命令的输入流.
|
* <p> 设置待执行命令的输入流.
|
||||||
* @param in 待设置的输入流。
|
|
||||||
*/
|
*/
|
||||||
public void setIn(InputStream in) {
|
public void setIn(InputStream in) {
|
||||||
channelExec.setIn(new InputStreamWrapper(in));
|
channelExec.setIn(new InputStreamWrapper(in));
|
||||||
@ -82,7 +79,6 @@ public final class CommandExecSession implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* 设置标准输出流.
|
* 设置标准输出流.
|
||||||
* <p> 对应待执行命令的 Stdout.
|
* <p> 对应待执行命令的 Stdout.
|
||||||
* @param out 设置标准输出的输出流.
|
|
||||||
*/
|
*/
|
||||||
public void setOut(OutputStream out) {
|
public void setOut(OutputStream out) {
|
||||||
channelExec.setOut(new OutputStreamWrapper(out));
|
channelExec.setOut(new OutputStreamWrapper(out));
|
||||||
@ -91,7 +87,6 @@ public final class CommandExecSession implements Closeable {
|
|||||||
/**
|
/**
|
||||||
* 设置错误输出流.
|
* 设置错误输出流.
|
||||||
* <p> 如果命令使用到, 错误信息会从该输出流输出.
|
* <p> 如果命令使用到, 错误信息会从该输出流输出.
|
||||||
* @param err 设置错误输出的输出流.
|
|
||||||
*/
|
*/
|
||||||
public void setErr(OutputStream err) {
|
public void setErr(OutputStream err) {
|
||||||
channelExec.setErr(new OutputStreamWrapper(err));
|
channelExec.setErr(new OutputStreamWrapper(err));
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|
||||||
|
|
||||||
import org.apache.sshd.common.session.Session;
|
|
||||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
|
||||||
import org.apache.sshd.server.forward.ForwardingFilter;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 已配置转发过滤器.
|
|
||||||
* <p> 根据应用配置, 选择是否允许转发指定类型的流量.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@Component("sentry.script.ssh.forwarding.filter")
|
|
||||||
public class ConfiguredForwardingFilter implements ForwardingFilter {
|
|
||||||
|
|
||||||
@Value("oracle.ssh.forwarding.X11.enable")
|
|
||||||
private String x11Enabled;
|
|
||||||
|
|
||||||
@Value("oracle.ssh.forwarding.tcp.enable")
|
|
||||||
private String tcpEnabled;
|
|
||||||
|
|
||||||
@Value("oracle.ssh.forwarding.agent.enable")
|
|
||||||
private String agentEnabled;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canForwardAgent(Session session, String requestType) {
|
|
||||||
return Boolean.parseBoolean(agentEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canListen(SshdSocketAddress address, Session session) {
|
|
||||||
return Boolean.parseBoolean(tcpEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canConnect(Type type, SshdSocketAddress address, Session session) {
|
|
||||||
return Boolean.parseBoolean(tcpEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canForwardX11(Session session, String requestType) {
|
|
||||||
return Boolean.parseBoolean(x11Enabled);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import net.lamgc.oracle.sentry.Constants;
|
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstance;
|
import net.lamgc.oracle.sentry.oci.compute.ComputeInstance;
|
||||||
import org.apache.sshd.client.SshClient;
|
import org.apache.sshd.client.SshClient;
|
||||||
import org.apache.sshd.client.future.AuthFuture;
|
import org.apache.sshd.client.future.AuthFuture;
|
||||||
@ -17,12 +16,6 @@ import java.util.Objects;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例 SSH 客户端.
|
|
||||||
* <p> 包装并简化了 SSH 会话的创建流程.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class InstanceSsh implements AutoCloseable {
|
public class InstanceSsh implements AutoCloseable {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(InstanceSsh.class);
|
private final static Logger log = LoggerFactory.getLogger(InstanceSsh.class);
|
||||||
@ -31,25 +24,17 @@ public class InstanceSsh implements AutoCloseable {
|
|||||||
private final SshAuthInfo authInfo;
|
private final SshAuthInfo authInfo;
|
||||||
private final SshClient sshClient;
|
private final SshClient sshClient;
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建连接实例用的 SSH 客户端.
|
|
||||||
* @param instance SSH 客户端对应的计算实例.
|
|
||||||
* @param authInfo SSH 认证配置.
|
|
||||||
*/
|
|
||||||
public InstanceSsh(ComputeInstance instance, SshAuthInfo authInfo) {
|
public InstanceSsh(ComputeInstance instance, SshAuthInfo authInfo) {
|
||||||
this.instance = Objects.requireNonNull(instance);
|
this.instance = Objects.requireNonNull(instance);
|
||||||
this.authInfo = Objects.requireNonNull(authInfo);
|
this.authInfo = Objects.requireNonNull(authInfo);
|
||||||
|
|
||||||
sshClient = SshClient.setUpDefaultClient();
|
sshClient = SshClient.setUpDefaultClient();
|
||||||
sshClient.setForwardingFilter(Constants.instance.getForwardingFilter());
|
|
||||||
sshClient.setServerKeyVerifier(new OracleInstanceServerKeyVerifier(instance, authInfo));
|
sshClient.setServerKeyVerifier(new OracleInstanceServerKeyVerifier(instance, authInfo));
|
||||||
if (authInfo instanceof PublicKeyAuthInfo info) {
|
if (authInfo instanceof PublicKeyAuthInfo info) {
|
||||||
FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(info.getPrivateKeyPath().toPath());
|
sshClient.setKeyIdentityProvider(new FileKeyPairProvider(info.getPrivateKeyPath().toPath()));
|
||||||
if (!Strings.isNullOrEmpty(info.getKeyPassword())) {
|
if (!Strings.isNullOrEmpty(info.getKeyPassword())) {
|
||||||
fileKeyPairProvider.setPasswordFinder(FilePasswordProvider.of(info.getKeyPassword()));
|
sshClient.setFilePasswordProvider(FilePasswordProvider.of(info.getKeyPassword()));
|
||||||
log.info("已设置密钥解密密码.");
|
|
||||||
}
|
}
|
||||||
sshClient.setKeyIdentityProvider(fileKeyPairProvider);
|
|
||||||
} else if (authInfo instanceof PasswordAuthInfo info) {
|
} else if (authInfo instanceof PasswordAuthInfo info) {
|
||||||
sshClient.addPasswordIdentity(info.getPassword());
|
sshClient.addPasswordIdentity(info.getPassword());
|
||||||
} else {
|
} else {
|
||||||
@ -58,19 +43,12 @@ public class InstanceSsh implements AutoCloseable {
|
|||||||
sshClient.start();
|
sshClient.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建 SSH 会话.
|
|
||||||
* <p> 允许创建多个 SSH 会话.
|
|
||||||
* @return 返回新的 SSH 会话.
|
|
||||||
* @throws IOException 会话创建失败时将抛出异常.
|
|
||||||
*/
|
|
||||||
public SshSession createSession() throws IOException {
|
public SshSession createSession() throws IOException {
|
||||||
Set<String> instancePublicIps = instance.network().getInstancePublicIp();
|
Set<String> instancePublicIps = instance.network().getInstancePublicIp();
|
||||||
if (instancePublicIps.stream().findFirst().isEmpty()) {
|
if (instancePublicIps.stream().findFirst().isEmpty()) {
|
||||||
throw new IllegalStateException("Instance has no public IP available.");
|
throw new IllegalStateException("Instance has no public IP available.");
|
||||||
}
|
}
|
||||||
String connectUri = "ssh://" + authInfo.getUsername() + "@" +
|
String connectUri = "ssh://" + authInfo.getUsername() + "@" + instancePublicIps.stream().findFirst().get() + ":22";
|
||||||
instancePublicIps.stream().findFirst().get() + ":" + authInfo.getPort();
|
|
||||||
log.info("SSH 正在连接: {}", connectUri);
|
log.info("SSH 正在连接: {}", connectUri);
|
||||||
ConnectFuture connect = sshClient.connect(connectUri);
|
ConnectFuture connect = sshClient.connect(connectUri);
|
||||||
connect.verify();
|
connect.verify();
|
||||||
@ -97,6 +75,7 @@ public class InstanceSsh implements AutoCloseable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
sshClient.stop();
|
sshClient.stop();
|
||||||
|
@ -23,7 +23,7 @@ public class OracleInstanceServerKeyVerifier implements ServerKeyVerifier {
|
|||||||
private final ComputeInstance instance;
|
private final ComputeInstance instance;
|
||||||
private final SshAuthInfo info;
|
private final SshAuthInfo info;
|
||||||
|
|
||||||
OracleInstanceServerKeyVerifier(ComputeInstance instance, SshAuthInfo info) {
|
public OracleInstanceServerKeyVerifier(ComputeInstance instance, SshAuthInfo info) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.info = info;
|
this.info = info;
|
||||||
}
|
}
|
||||||
|
@ -12,18 +12,10 @@ public class PasswordAuthInfo extends SshAuthInfo {
|
|||||||
return AuthType.PASSWORD;
|
return AuthType.PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 SSH 登录密码.
|
|
||||||
* @return 返回登录密码.
|
|
||||||
*/
|
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 SSH 登录密码.
|
|
||||||
* @param password 新的登录密码.
|
|
||||||
*/
|
|
||||||
public void setPassword(String password) {
|
public void setPassword(String password) {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
||||||
|
|
||||||
import java.io.File;
|
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
|
||||||
/**
|
|
||||||
* 公钥登录认证配置.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class PublicKeyAuthInfo extends SshAuthInfo{
|
public class PublicKeyAuthInfo extends SshAuthInfo{
|
||||||
|
|
||||||
private File privateKeyPath;
|
private File privateKeyPath;
|
||||||
@ -16,36 +15,18 @@ public class PublicKeyAuthInfo extends SshAuthInfo{
|
|||||||
return AuthType.PUBLIC_KEY;
|
return AuthType.PUBLIC_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取私钥路径.
|
|
||||||
* <p> 注意: 该路径由 SSH 认证配置文件提供, 不保证私钥的存在.
|
|
||||||
* @return 返回私钥所在路径.
|
|
||||||
*/
|
|
||||||
public File getPrivateKeyPath() {
|
public File getPrivateKeyPath() {
|
||||||
return privateKeyPath;
|
return privateKeyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置私钥路径.
|
|
||||||
* @param privateKeyPath 私钥路径.
|
|
||||||
*/
|
|
||||||
public void setPrivateKeyPath(File privateKeyPath) {
|
public void setPrivateKeyPath(File privateKeyPath) {
|
||||||
this.privateKeyPath = privateKeyPath;
|
this.privateKeyPath = privateKeyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取私钥密码.
|
|
||||||
* @return 如果有, 返回非 {@code null} 值.
|
|
||||||
*/
|
|
||||||
public String getKeyPassword() {
|
public String getKeyPassword() {
|
||||||
return keyPassword;
|
return keyPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置私钥密码.
|
|
||||||
* <p> 如果私钥存在密码但未提供密码, 将无法使用私钥验证会话.
|
|
||||||
* @param keyPassword 私钥密码.
|
|
||||||
*/
|
|
||||||
public void setKeyPassword(String keyPassword) {
|
public void setKeyPassword(String keyPassword) {
|
||||||
this.keyPassword = keyPassword;
|
this.keyPassword = keyPassword;
|
||||||
}
|
}
|
||||||
|
@ -1,280 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|
||||||
|
|
||||||
import net.lamgc.oracle.sentry.common.LazyLoader;
|
|
||||||
import org.apache.sshd.sftp.client.SftpClient;
|
|
||||||
import org.apache.sshd.sftp.common.SftpConstants;
|
|
||||||
import org.apache.sshd.sftp.common.SftpException;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.DirectoryNotEmptyException;
|
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sftp 会话.
|
|
||||||
* <p> 可通过会话访问远程服务器的文件.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class SftpSession implements Closeable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 排除的文件名.
|
|
||||||
*/
|
|
||||||
private final static Set<String> EXCLUDED_FILE_NAMES;
|
|
||||||
|
|
||||||
static {
|
|
||||||
EXCLUDED_FILE_NAMES = new HashSet<>();
|
|
||||||
EXCLUDED_FILE_NAMES.add(".");
|
|
||||||
EXCLUDED_FILE_NAMES.add("..");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final SftpClient sftpClient;
|
|
||||||
private final LazyLoader<String> userHome;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建 Sftp 会话.
|
|
||||||
* @param sftpClient Sftp 客户端.
|
|
||||||
*/
|
|
||||||
SftpSession(SftpClient sftpClient) {
|
|
||||||
this.sftpClient = sftpClient;
|
|
||||||
this.userHome = new LazyLoader<>(() -> {
|
|
||||||
try {
|
|
||||||
CommandExecSession echoHOME = new CommandExecSession(sftpClient.getSession().createExecChannel("echo $HOME"));
|
|
||||||
ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
|
|
||||||
echoHOME.setOut(outBuffer);
|
|
||||||
echoHOME.exec();
|
|
||||||
if (echoHOME.exitCode() != null && echoHOME.exitCode() == 0) {
|
|
||||||
return outBuffer.toString(StandardCharsets.UTF_8).trim();
|
|
||||||
}
|
|
||||||
throw new IOException("Command execution failed.(ExitCode: " + echoHOME.exitCode() + ")");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定文件夹内的所有文件.
|
|
||||||
* @param path 文件夹路径.
|
|
||||||
* @return 返回该目录下所有文件的文件名, 文件名不带路径.
|
|
||||||
* @throws IOException 执行失败时抛出异常.
|
|
||||||
*/
|
|
||||||
public Set<String> listFiles(String path) throws IOException {
|
|
||||||
SftpClient.CloseableHandle handle = sftpClient.openDir(path);
|
|
||||||
Set<String> paths = new HashSet<>();
|
|
||||||
for (SftpClient.DirEntry entry : sftpClient.listDir(handle)) {
|
|
||||||
if (EXCLUDED_FILE_NAMES.contains(entry.getFilename())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
paths.add(entry.getFilename());
|
|
||||||
}
|
|
||||||
sftpClient.close(handle);
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取指定路径的问题.
|
|
||||||
* @param path 文件所在路径.
|
|
||||||
* @return 返回文件输入流.
|
|
||||||
* @throws FileNotFoundException 当文件不存在时抛出该异常.
|
|
||||||
* @throws IOException 当操作执行失败时抛出异常.
|
|
||||||
*/
|
|
||||||
public InputStream read(String path) throws IOException {
|
|
||||||
if (!exists(path)) {
|
|
||||||
throw new FileNotFoundException(path);
|
|
||||||
}
|
|
||||||
return sftpClient.read(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 写入数据到指定目录.
|
|
||||||
* @param path 待写入的路径.
|
|
||||||
* @return 返回数据输出流.
|
|
||||||
* @throws FileNotFoundException 当文件不存在时抛出该异常.
|
|
||||||
* @throws IOException 如果操作失败则抛出异常.
|
|
||||||
*/
|
|
||||||
public OutputStream write(String path) throws IOException {
|
|
||||||
if (!exists(path)) {
|
|
||||||
throw new FileNotFoundException(path);
|
|
||||||
}
|
|
||||||
return sftpClient.write(path, SftpClient.OpenMode.Write);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查指定路径是否存在.
|
|
||||||
* @param path 待检查的路径.
|
|
||||||
* @return 如果存在, 返回 {@code true}, 如果文件不存在, 返回 {@code false}.
|
|
||||||
* @throws IOException 执行失败时抛出.
|
|
||||||
*/
|
|
||||||
public boolean exists(String path) throws IOException {
|
|
||||||
try {
|
|
||||||
return getAttributes(path) != null;
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (e instanceof SftpException sftpException) {
|
|
||||||
if (sftpException.getStatus() == SftpConstants.SSH_FX_NO_SUCH_FILE) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为一个目录.
|
|
||||||
* @param path 待检查路径.
|
|
||||||
* @return 如果是一个目录, 返回 {@code true}.
|
|
||||||
* @throws FileNotFoundException 当路径不存在时抛出该异常.
|
|
||||||
* @throws IOException 如果执行失败则抛出异常.
|
|
||||||
*/
|
|
||||||
public boolean isDirectory(String path) throws IOException {
|
|
||||||
if (!exists(path)) {
|
|
||||||
throw new FileNotFoundException(path);
|
|
||||||
}
|
|
||||||
return getAttributes(path).isDirectory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为一个文件.
|
|
||||||
* @param path 待检查路径.
|
|
||||||
* @return 如果是一个文件, 返回 {@code true}.
|
|
||||||
* @throws FileNotFoundException 当路径不存在时抛出该异常.
|
|
||||||
* @throws IOException 如果执行失败则抛出异常.
|
|
||||||
*/
|
|
||||||
public boolean isFile(String path) throws IOException {
|
|
||||||
if (!exists(path)) {
|
|
||||||
throw new FileNotFoundException(path);
|
|
||||||
}
|
|
||||||
return getAttributes(path).isRegularFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取文件大小.
|
|
||||||
* @param path 待获取的路径.
|
|
||||||
* @return 返回文件大小, 单位 b.
|
|
||||||
* @throws NoSuchFileException 当指定路径不是一个文件(或符号链接)时抛出.
|
|
||||||
* @throws FileNotFoundException 当指定路径不存在时抛出.
|
|
||||||
* @throws IOException 当操作执行失败时抛出.
|
|
||||||
*/
|
|
||||||
public long getFileSize(String path) throws IOException {
|
|
||||||
if (!exists(path)) {
|
|
||||||
throw new FileNotFoundException(path);
|
|
||||||
}
|
|
||||||
SftpClient.Attributes attributes = getAttributes(path);
|
|
||||||
if (!attributes.isRegularFile()) {
|
|
||||||
if (attributes.isSymbolicLink()) {
|
|
||||||
return getFileSize(sftpClient.readLink(path));
|
|
||||||
}
|
|
||||||
throw new NoSuchFileException("Not a file: " + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributes.getSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定路径的属性.
|
|
||||||
* @param path 待获取属性的路径.
|
|
||||||
* @return 返回路径所属属性.
|
|
||||||
* @throws IOException 如果执行失败则抛出异常.
|
|
||||||
*/
|
|
||||||
public SftpClient.Attributes getAttributes(String path) throws IOException {
|
|
||||||
return sftpClient.stat(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建文件夹.
|
|
||||||
* @param path 待创建的目录.
|
|
||||||
* @return 当文件夹已存在时返回 {@code false}, 不存在且创建成功则返回 {@code true}.
|
|
||||||
* @throws IOException 如果操作执行失败则抛出异常.
|
|
||||||
*/
|
|
||||||
public boolean mkdir(String path) throws IOException {
|
|
||||||
if (exists(path)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sftpClient.mkdir(path);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建新文件.
|
|
||||||
* @param path 待创建的文件路径.
|
|
||||||
* @return 当文件已存在时返回 {@code false}, 不存在且创建成功则返回 {@code true}.
|
|
||||||
* @throws IOException 如果操作失败啧抛出异常.
|
|
||||||
*/
|
|
||||||
public boolean createNewFile(String path) throws IOException {
|
|
||||||
if (exists(path)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
SftpClient.CloseableHandle handle = sftpClient.open(path, SftpClient.OpenMode.Create);
|
|
||||||
sftpClient.close(handle);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除指定路径.
|
|
||||||
* <p> 该方法内部做了适配, 可兼容文件与文件夹两种类型.
|
|
||||||
* @param path 待删除的路径.
|
|
||||||
* @throws IOException 如果删除失败, 抛出异常.
|
|
||||||
* @throws FileNotFoundException 当指定路径不存在时抛出该异常.
|
|
||||||
* @throws DirectoryNotEmptyException 当路径为目录且目录不为空时抛出该异常.
|
|
||||||
*/
|
|
||||||
public void delete(String path) throws IOException {
|
|
||||||
if (!exists(path)) {
|
|
||||||
throw new FileNotFoundException(path);
|
|
||||||
}
|
|
||||||
if (isDirectory(path)) {
|
|
||||||
if (!listFiles(path).isEmpty()) {
|
|
||||||
throw new DirectoryNotEmptyException(path);
|
|
||||||
}
|
|
||||||
sftpClient.rmdir(path);
|
|
||||||
} else {
|
|
||||||
sftpClient.remove(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制删除文件夹.
|
|
||||||
* <p> 如果删除文件夹, 将会对文件夹执行遍历删除, 文件夹及子文件夹内的所有内容都会被删除.
|
|
||||||
* @param path 待删除的路径.
|
|
||||||
* @return 如果目标路径为文件夹且删除成功, 返回 {@code true}, 如果是文件, 则不会执行操作并返回 {@code false}.
|
|
||||||
* 该行为是防止文件遭到误删.
|
|
||||||
* @throws IOException 如果操作执行失败, 则抛出异常.
|
|
||||||
*/
|
|
||||||
public boolean forceDeleteDir(String path) throws IOException {
|
|
||||||
if (isDirectory(path)) {
|
|
||||||
for (String filePath : listFiles(path)) {
|
|
||||||
String fullFilePath = path + "/" + filePath;
|
|
||||||
if (isDirectory(fullFilePath)) {
|
|
||||||
forceDeleteDir(fullFilePath);
|
|
||||||
} else {
|
|
||||||
sftpClient.remove(fullFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sftpClient.rmdir(path);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws IOException {
|
|
||||||
sftpClient.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户主目录.
|
|
||||||
* @return 返回用户主目录, 例如: "/root", 末尾无符号.
|
|
||||||
* @throws IOException 如果操作失败, 则抛出异常.
|
|
||||||
*/
|
|
||||||
public String getUserHome() throws IOException {
|
|
||||||
try {
|
|
||||||
return userHome.getInstance();
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
throw (IOException) e.getCause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import com.google.common.reflect.TypeToken;
|
|||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstanceManager;
|
import net.lamgc.oracle.sentry.ComputeInstanceManager;
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstance;
|
import net.lamgc.oracle.sentry.oci.compute.ComputeInstance;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -15,7 +15,6 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.StandardOpenOption;
|
import java.nio.file.StandardOpenOption;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -30,7 +29,7 @@ import java.util.stream.Collectors;
|
|||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public final class SshAuthIdentityProvider implements AutoCloseable {
|
public final class SshAuthIdentityProvider {
|
||||||
|
|
||||||
private final static String DEFAULT_AUTH_KEY = "@default";
|
private final static String DEFAULT_AUTH_KEY = "@default";
|
||||||
private final static Logger log = LoggerFactory.getLogger(SshAuthIdentityProvider.class);
|
private final static Logger log = LoggerFactory.getLogger(SshAuthIdentityProvider.class);
|
||||||
@ -52,11 +51,7 @@ public final class SshAuthIdentityProvider implements AutoCloseable {
|
|||||||
.build());
|
.build());
|
||||||
private final AtomicBoolean needSave = new AtomicBoolean(false);
|
private final AtomicBoolean needSave = new AtomicBoolean(false);
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建 SSH 认证配置提供器.
|
|
||||||
* @param instanceManager 所属实例管理器.
|
|
||||||
* @param identityJson 认证配置文件对象.
|
|
||||||
*/
|
|
||||||
public SshAuthIdentityProvider(ComputeInstanceManager instanceManager, File identityJson) {
|
public SshAuthIdentityProvider(ComputeInstanceManager instanceManager, File identityJson) {
|
||||||
this.instanceManager = instanceManager;
|
this.instanceManager = instanceManager;
|
||||||
this.identityJsonFile = identityJson;
|
this.identityJsonFile = identityJson;
|
||||||
@ -72,14 +67,8 @@ public final class SshAuthIdentityProvider implements AutoCloseable {
|
|||||||
log.warn("本次 SSH 认证配置保存失败.", e);
|
log.warn("本次 SSH 认证配置保存失败.", e);
|
||||||
}
|
}
|
||||||
}, 60, 10, TimeUnit.SECONDS);
|
}, 60, 10, TimeUnit.SECONDS);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread(this::close, "Thread-ProviderAutoSave-Close"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加 SSH 认证配置.
|
|
||||||
* @param instanceId 配置对应的实例 Id.
|
|
||||||
* @param authInfo SSH 认证配置对象.
|
|
||||||
*/
|
|
||||||
public void addSshAuthIdentity(String instanceId, SshAuthInfo authInfo) {
|
public void addSshAuthIdentity(String instanceId, SshAuthInfo authInfo) {
|
||||||
authInfoMap.put(instanceId, authInfo);
|
authInfoMap.put(instanceId, authInfo);
|
||||||
}
|
}
|
||||||
@ -168,13 +157,8 @@ public final class SshAuthIdentityProvider implements AutoCloseable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有不存在 SSH 配置的实例 Id.
|
* 获取所有不存在 SSH 配置的实例 Id.
|
||||||
* @return 返回所有不存在对应 SSH 认证配置的实例 Id.
|
|
||||||
*/
|
*/
|
||||||
private Set<String> checkForMissingInstances() {
|
private Set<String> checkForMissingInstances() {
|
||||||
if (instanceManager == null) {
|
|
||||||
log.info("实例管理器未设置, 跳过检查.");
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
Set<String> instanceIdSet = instanceManager.getComputeInstances().stream()
|
Set<String> instanceIdSet = instanceManager.getComputeInstances().stream()
|
||||||
.map(ComputeInstance::getInstanceId)
|
.map(ComputeInstance::getInstanceId)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
@ -184,8 +168,4 @@ public final class SshAuthIdentityProvider implements AutoCloseable {
|
|||||||
return instanceIdSet;
|
return instanceIdSet;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
scheduledExec.shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,8 +16,11 @@ public abstract class SshAuthInfo {
|
|||||||
private final static Logger log = LoggerFactory.getLogger(SshAuthInfo.class);
|
private final static Logger log = LoggerFactory.getLogger(SshAuthInfo.class);
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
|
/**
|
||||||
|
* 使用 Sha256 计算的密钥指纹.
|
||||||
|
*/
|
||||||
private PublicKey serverKey;
|
private PublicKey serverKey;
|
||||||
private int port;
|
|
||||||
private SshAuthIdentityProvider provider;
|
private SshAuthIdentityProvider provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,28 +29,14 @@ public abstract class SshAuthInfo {
|
|||||||
*/
|
*/
|
||||||
public abstract AuthType getType();
|
public abstract AuthType getType();
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 SSH 登录用户名.
|
|
||||||
* @return 返回 SSH 登录用户名.
|
|
||||||
*/
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取服务器公钥.
|
|
||||||
* <p> 用于认证服务器身份, 在首次登录成功后设置.
|
|
||||||
* @return 如果之前认证成功并保存过, 则不为 {@code null}, 否则需要进行首次连接确认.
|
|
||||||
*/
|
|
||||||
public PublicKey getServerKey() {
|
public PublicKey getServerKey() {
|
||||||
return serverKey;
|
return serverKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置服务器公钥.
|
|
||||||
* <p> 如果本对象有关联的 {@link SshAuthIdentityProvider}, 则会通知 Provider 保存 SSH 认证配置文件.
|
|
||||||
* @param serverKey 服务器公钥.
|
|
||||||
*/
|
|
||||||
public void setServerKey(PublicKey serverKey) {
|
public void setServerKey(PublicKey serverKey) {
|
||||||
this.serverKey = serverKey;
|
this.serverKey = serverKey;
|
||||||
if (this.provider != null) {
|
if (this.provider != null) {
|
||||||
@ -54,43 +44,14 @@ public abstract class SshAuthInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 SSH 登录用户名.
|
|
||||||
* @param username 登录 SSH 的用户名.
|
|
||||||
*/
|
|
||||||
public void setUsername(String username) {
|
public void setUsername(String username) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 SSH 连接端口.
|
|
||||||
* @param port SSH 端口号.
|
|
||||||
*/
|
|
||||||
public void setPort(int port) {
|
|
||||||
this.port = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 SSH 端口号.
|
|
||||||
* @return 返回 SSH 端口号.
|
|
||||||
*/
|
|
||||||
public int getPort() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 SSH 认证配置提供器.
|
|
||||||
* <p> 设置后, 可在首次连接认证通过后, 保存服务器公钥到文件中.
|
|
||||||
* @param provider 所属提供器对象.
|
|
||||||
*/
|
|
||||||
void setProvider(SshAuthIdentityProvider provider) {
|
void setProvider(SshAuthIdentityProvider provider) {
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 认证类型.
|
|
||||||
* <p> 如果没有所需认证类型, 就是没支持.
|
|
||||||
*/
|
|
||||||
public enum AuthType {
|
public enum AuthType {
|
||||||
/**
|
/**
|
||||||
* 密码认证.
|
* 密码认证.
|
||||||
@ -107,10 +68,6 @@ public abstract class SshAuthInfo {
|
|||||||
this.targetClass = targetClass;
|
this.targetClass = targetClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取类型所属的认证配置类.
|
|
||||||
* @return 返回认证配置类.
|
|
||||||
*/
|
|
||||||
public Class<? extends SshAuthInfo> getTargetClass() {
|
public Class<? extends SshAuthInfo> getTargetClass() {
|
||||||
return targetClass;
|
return targetClass;
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ import com.google.gson.*;
|
|||||||
import org.apache.sshd.common.config.keys.KeyUtils;
|
import org.apache.sshd.common.config.keys.KeyUtils;
|
||||||
import org.apache.sshd.common.config.keys.PublicKeyEntry;
|
import org.apache.sshd.common.config.keys.PublicKeyEntry;
|
||||||
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
|
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -25,21 +23,18 @@ import java.util.Collections;
|
|||||||
*/
|
*/
|
||||||
public final class SshAuthInfoSerializer implements JsonSerializer<SshAuthInfo>, JsonDeserializer<SshAuthInfo> {
|
public final class SshAuthInfoSerializer implements JsonSerializer<SshAuthInfo>, JsonDeserializer<SshAuthInfo> {
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(SshAuthInfoSerializer.class);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 本类唯一实例.
|
|
||||||
* <p> 序列化器支持多用.
|
|
||||||
*/
|
|
||||||
public final static SshAuthInfoSerializer INSTANCE = new SshAuthInfoSerializer();
|
public final static SshAuthInfoSerializer INSTANCE = new SshAuthInfoSerializer();
|
||||||
|
|
||||||
private SshAuthInfoSerializer() {}
|
private SshAuthInfoSerializer() {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SshAuthInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
public SshAuthInfo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
if (!json.isJsonObject()) {
|
||||||
|
throw new JsonParseException("It should be a JsonObject");
|
||||||
|
}
|
||||||
JsonObject infoObject = json.getAsJsonObject();
|
JsonObject infoObject = json.getAsJsonObject();
|
||||||
String type = getFieldToStringOrFail(infoObject, "authType");
|
String type = getFieldToStringOrFail(infoObject, "authType");
|
||||||
SshAuthInfo.AuthType authType = getAuthType(type);
|
SshAuthInfo.AuthType authType = SshAuthInfo.AuthType.valueOf(type.toUpperCase());
|
||||||
SshAuthInfo info;
|
SshAuthInfo info;
|
||||||
if (authType == SshAuthInfo.AuthType.PASSWORD) {
|
if (authType == SshAuthInfo.AuthType.PASSWORD) {
|
||||||
PasswordAuthInfo pswAuthInfo = new PasswordAuthInfo();
|
PasswordAuthInfo pswAuthInfo = new PasswordAuthInfo();
|
||||||
@ -50,40 +45,18 @@ public final class SshAuthInfoSerializer implements JsonSerializer<SshAuthInfo>,
|
|||||||
String privateKeyPath = getFieldToStringOrFail(infoObject, "privateKeyPath");
|
String privateKeyPath = getFieldToStringOrFail(infoObject, "privateKeyPath");
|
||||||
File privateKeyFile = new File(privateKeyPath);
|
File privateKeyFile = new File(privateKeyPath);
|
||||||
publicKeyInfo.setPrivateKeyPath(privateKeyFile);
|
publicKeyInfo.setPrivateKeyPath(privateKeyFile);
|
||||||
publicKeyInfo.setKeyPassword(getFieldToString(infoObject, "keyPassword"));
|
|
||||||
info = publicKeyInfo;
|
info = publicKeyInfo;
|
||||||
} else {
|
} else {
|
||||||
throw new JsonParseException("Unsupported authentication type: " + authType);
|
throw new JsonParseException("Unsupported authentication type: " + authType);
|
||||||
}
|
}
|
||||||
info.setUsername(getFieldToStringOrFail(infoObject, "username"));
|
info.setUsername(getFieldToStringOrFail(infoObject, "username"));
|
||||||
String portStr = getFieldToString(infoObject, "port");
|
try {
|
||||||
if (portStr != null) {
|
info.setServerKey(decodeSshPublicKey(
|
||||||
try {
|
infoObject.has("serverKey") && infoObject.get("serverKey").isJsonPrimitive() ?
|
||||||
int port = Integer.parseInt(portStr);
|
infoObject.get("serverKey").getAsString() :
|
||||||
if (checkPortNumber(port)) {
|
null));
|
||||||
info.setPort(port);
|
} catch (GeneralSecurityException | IOException e) {
|
||||||
} else {
|
throw new JsonParseException(e);
|
||||||
log.warn("端口号非法, 将使用默认端口号.(Input: {})", port);
|
|
||||||
info.setPort(22);
|
|
||||||
}
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
log.warn("端口号无法转换成数字, 端口号将使用默认端口号.(Input: {})", portStr);
|
|
||||||
info.setPort(22);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info.setPort(22);
|
|
||||||
}
|
|
||||||
|
|
||||||
String serverKeyStr = getFieldToString(infoObject, "serverKey");
|
|
||||||
if (!Strings.isNullOrEmpty(serverKeyStr)) {
|
|
||||||
try {
|
|
||||||
info.setServerKey(decodeSshPublicKey(serverKeyStr));
|
|
||||||
} catch (GeneralSecurityException | IOException e) {
|
|
||||||
info.setServerKey(null);
|
|
||||||
log.error("解析 ServerKey 时发生错误, 该 ServerKey 将为空.(后续连接需进行首次连接认证.)", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info.setServerKey(null);
|
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@ -91,6 +64,13 @@ public final class SshAuthInfoSerializer implements JsonSerializer<SshAuthInfo>,
|
|||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(SshAuthInfo src, Type typeOfSrc, JsonSerializationContext context) {
|
public JsonElement serialize(SshAuthInfo src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
JsonObject json = new JsonObject();
|
JsonObject json = new JsonObject();
|
||||||
|
json.addProperty("authType", src.getType().toString());
|
||||||
|
json.addProperty("username", src.getUsername());
|
||||||
|
try {
|
||||||
|
json.addProperty("serverKey", encodeSshPublicKey(src.getServerKey()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new JsonParseException(e);
|
||||||
|
}
|
||||||
if (src instanceof PasswordAuthInfo info) {
|
if (src instanceof PasswordAuthInfo info) {
|
||||||
json.addProperty("password", info.getPassword());
|
json.addProperty("password", info.getPassword());
|
||||||
} else if (src instanceof PublicKeyAuthInfo info) {
|
} else if (src instanceof PublicKeyAuthInfo info) {
|
||||||
@ -103,37 +83,21 @@ public final class SshAuthInfoSerializer implements JsonSerializer<SshAuthInfo>,
|
|||||||
} else {
|
} else {
|
||||||
throw new JsonParseException("Unsupported type");
|
throw new JsonParseException("Unsupported type");
|
||||||
}
|
}
|
||||||
|
|
||||||
json.addProperty("authType", src.getType().toString());
|
|
||||||
json.addProperty("username", src.getUsername());
|
|
||||||
json.addProperty("port", src.getPort());
|
|
||||||
if (src.getServerKey() != null) {
|
|
||||||
json.addProperty("serverKey", encodeSshPublicKey(src.getServerKey()));
|
|
||||||
} else {
|
|
||||||
json.add("serverKey", JsonNull.INSTANCE);
|
|
||||||
}
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkPortNumber(int port) {
|
|
||||||
return port >= 0 && port <= 65535;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getFieldToStringOrFail(JsonObject object, String field) {
|
private String getFieldToStringOrFail(JsonObject object, String field) {
|
||||||
if (!object.has(field) || !object.get(field).isJsonPrimitive()) {
|
if (!object.has(field)) {
|
||||||
throw new JsonParseException("Missing field: " + field);
|
throw new JsonParseException("Missing field: " + field);
|
||||||
}
|
}
|
||||||
return object.get(field).getAsString();
|
return object.get(field).getAsString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFieldToString(JsonObject object, String field) {
|
private PublicKey decodeSshPublicKey(String publicKeyString) throws GeneralSecurityException, IOException {
|
||||||
if (!object.has(field) || !object.get(field).isJsonPrimitive()) {
|
if (Strings.isNullOrEmpty(publicKeyString)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return object.get(field).getAsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PublicKey decodeSshPublicKey(String publicKeyString) throws GeneralSecurityException, IOException {
|
|
||||||
String[] strings = publicKeyString.split(" ", 3);
|
String[] strings = publicKeyString.split(" ", 3);
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") PublicKeyEntryDecoder<PublicKey, ?> decoder =
|
@SuppressWarnings("unchecked") PublicKeyEntryDecoder<PublicKey, ?> decoder =
|
||||||
@ -141,23 +105,14 @@ public final class SshAuthInfoSerializer implements JsonSerializer<SshAuthInfo>,
|
|||||||
return decoder.decodePublicKey(null, strings[0], Base64.getDecoder().decode(strings[1]), Collections.emptyMap());
|
return decoder.decodePublicKey(null, strings[0], Base64.getDecoder().decode(strings[1]), Collections.emptyMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String encodeSshPublicKey(PublicKey key) {
|
private String encodeSshPublicKey(PublicKey key) throws IOException {
|
||||||
try {
|
if (key == null) {
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
PublicKeyEntry.appendPublicKeyEntry(builder, key);
|
|
||||||
return builder.toString();
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("ServerKey 编码失败, 下次加载时需要进行首次连接认证.", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private SshAuthInfo.AuthType getAuthType(String type) {
|
|
||||||
try {
|
|
||||||
return SshAuthInfo.AuthType.valueOf(type.toUpperCase());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
PublicKeyEntry.appendPublicKeyEntry(builder, key);
|
||||||
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
||||||
|
|
||||||
import org.apache.sshd.client.session.ClientSession;
|
import org.apache.sshd.client.session.ClientSession;
|
||||||
import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
|
|
||||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
|
||||||
import org.apache.sshd.sftp.client.SftpClientFactory;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -18,11 +15,7 @@ public class SshSession implements Closeable {
|
|||||||
|
|
||||||
private final ClientSession clientSession;
|
private final ClientSession clientSession;
|
||||||
|
|
||||||
/**
|
public SshSession(ClientSession clientSession) {
|
||||||
* 创建新的 SSH 会话.
|
|
||||||
* @param clientSession 原始 SSH 会话.
|
|
||||||
*/
|
|
||||||
SshSession(ClientSession clientSession) {
|
|
||||||
this.clientSession = clientSession;
|
this.clientSession = clientSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,46 +29,6 @@ public class SshSession implements Closeable {
|
|||||||
return new CommandExecSession(clientSession.createExecChannel(command));
|
return new CommandExecSession(clientSession.createExecChannel(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建 Sftp 会话.
|
|
||||||
* <p> 可通过会话操作 Sftp.
|
|
||||||
* @return 返回 Sftp 会话.
|
|
||||||
* @throws IOException 如果创建失败, 将抛出异常.
|
|
||||||
*/
|
|
||||||
public SftpSession createSftpSession() throws IOException {
|
|
||||||
SftpClientFactory factory = SftpClientFactory.instance();
|
|
||||||
return new SftpSession(factory.createSftpClient(clientSession));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建本地 TCP 转发隧道.
|
|
||||||
* <p> 该隧道为方向为 "本地->远端" (本地发起连接转发至远端端口).
|
|
||||||
* @param localPort 本地监听端口.
|
|
||||||
* @param remotePort 远端目标端口.
|
|
||||||
* @return 返回 TCP 转发通道对象, 可获取通道信息和关闭通道.
|
|
||||||
* @throws IOException 当操作失败时抛出该异常.
|
|
||||||
*/
|
|
||||||
public TcpForwardingChannel createLocalTcpForwarding(int localPort, int remotePort) throws IOException {
|
|
||||||
ExplicitPortForwardingTracker tracker = clientSession
|
|
||||||
.createLocalPortForwardingTracker(localPort, new SshdSocketAddress(remotePort));
|
|
||||||
return new TcpForwardingChannel(tracker);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建远端 TCP 转发隧道.
|
|
||||||
* <p> 该隧道为方向为 "本地<-远端" (远端服务器发起连接转发至本地端口).
|
|
||||||
* @param remotePort 远端监听端口号, 该端口为远端服务连接转发的端口号.
|
|
||||||
* @param localPort 本地连接端口号, 该端口为本地服务端的端口号.
|
|
||||||
* @return 返回 Tcp 转发通道对象, 用于管理转发通道.
|
|
||||||
* @throws IOException 当操作失败时抛出异常.
|
|
||||||
*/
|
|
||||||
public TcpForwardingChannel createRemoteTcpForwarding(int remotePort, int localPort) throws IOException {
|
|
||||||
ExplicitPortForwardingTracker tracker =
|
|
||||||
clientSession.createRemotePortForwardingTracker(
|
|
||||||
new SshdSocketAddress(remotePort), new SshdSocketAddress(localPort));
|
|
||||||
return new TcpForwardingChannel(tracker);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关闭 SSH 连接会话, 该连接会话所属的其他会话将会一同被关闭.
|
* 关闭 SSH 连接会话, 该连接会话所属的其他会话将会一同被关闭.
|
||||||
* @throws IOException 关闭失败时抛出异常,
|
* @throws IOException 关闭失败时抛出异常,
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|
||||||
|
|
||||||
import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker;
|
|
||||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TCP 隧道.
|
|
||||||
* <p> 可通过该对象管理隧道.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class TcpForwardingChannel implements AutoCloseable {
|
|
||||||
|
|
||||||
private final ExplicitPortForwardingTracker tracker;
|
|
||||||
|
|
||||||
TcpForwardingChannel(ExplicitPortForwardingTracker tracker) {
|
|
||||||
this.tracker = tracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为本地转发.
|
|
||||||
* @return 如果是本地转发, 则为 {@code true}, 否则返回 {@code false}, 代表远端转发.
|
|
||||||
*/
|
|
||||||
public boolean isLocalForwarding() {
|
|
||||||
return tracker.isLocalForwarding();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 隧道是否已打开.
|
|
||||||
* @return 如果已经打开, 返回 {@code true}, 否则返回 {@code false} 表示已关闭.
|
|
||||||
*/
|
|
||||||
public boolean isOpen() {
|
|
||||||
return tracker.isOpen();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取本地地址.
|
|
||||||
* @return 获取本地连接地址.
|
|
||||||
*/
|
|
||||||
public SshdSocketAddress getLocalAddress() {
|
|
||||||
return tracker.getLocalAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取远端地址.
|
|
||||||
* @return 获取远端地址.
|
|
||||||
*/
|
|
||||||
public SshdSocketAddress getRemoteAddress() {
|
|
||||||
return tracker.getRemoteAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取监听绑定地址.
|
|
||||||
* @return 返回监听绑定地址.
|
|
||||||
*/
|
|
||||||
public SshdSocketAddress getBoundAddress() {
|
|
||||||
return tracker.getBoundAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() throws Exception {
|
|
||||||
tracker.close();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script;
|
|
||||||
|
|
||||||
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
|
||||||
|
|
||||||
public class BuiltinComponentExtension implements ScriptComponentExtension{
|
|
||||||
@Override
|
|
||||||
public void configureScriptComponents(ScriptComponents components) {
|
|
||||||
components.addComponentObject("HTTP", new ScriptHttpClient(HttpClientBuilder.create()
|
|
||||||
.build()));
|
|
||||||
components.addComponentFactory(new ScriptLoggerFactory());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package net.lamgc.oracle.sentry.script;
|
||||||
|
|
||||||
|
import net.lamgc.oracle.sentry.ComputeInstanceManager;
|
||||||
|
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author LamGC
|
||||||
|
*/
|
||||||
|
public final record ScriptComponent(
|
||||||
|
ScriptHttpClient HTTP,
|
||||||
|
ComputeInstanceManager InstanceManager
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 脚本组件扩展.
|
|
||||||
* <p> 通过实现该接口, 可在脚本加载前设置组件对象, 为脚本提供更多功能.
|
|
||||||
* <p> 实现接口后, 需按 SPI 方式添加实现.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public interface ScriptComponentExtension {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置脚本组件.
|
|
||||||
* <p> 在方法中为组件集合添加组件.
|
|
||||||
* @param components 脚本组件集合.
|
|
||||||
*/
|
|
||||||
void configureScriptComponents(final ScriptComponents components);
|
|
||||||
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 脚本组件工厂.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public interface ScriptComponentFactory<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建并获取实例.
|
|
||||||
* @param info 脚本信息.
|
|
||||||
* @return 返回对象.
|
|
||||||
*/
|
|
||||||
T getInstance(ScriptInfo info);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 对象属性名.
|
|
||||||
* @return 返回建议的对象属性名, {@link ScriptLoader} 并不一定遵守.
|
|
||||||
*/
|
|
||||||
String getPropertyName();
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script;
|
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 脚本组件集合.
|
|
||||||
* <p> 存储了脚本可以使用的对象.
|
|
||||||
* <p> 后续可能会改成用 {@link javax.script.Bindings} 之类的.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public final class ScriptComponents {
|
|
||||||
|
|
||||||
private final ScriptComponents parent;
|
|
||||||
private final Map<String, Object> componentObjects = new HashMap<>();
|
|
||||||
private final Set<ScriptComponentFactory<?>> factories = new HashSet<>();
|
|
||||||
|
|
||||||
public ScriptComponents() {
|
|
||||||
this.parent = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ScriptComponents(ScriptComponents parent) {
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addComponentObject(String componentName, Object componentObject) {
|
|
||||||
if (Strings.isNullOrEmpty(componentName)) {
|
|
||||||
throw new NullPointerException("The component name is null or empty.");
|
|
||||||
} else if (componentObject == null) {
|
|
||||||
throw new NullPointerException("ComponentObject is null");
|
|
||||||
} else if (componentObjects.containsKey(componentName)) {
|
|
||||||
throw new IllegalArgumentException("The corresponding object already exists for the component name.");
|
|
||||||
}
|
|
||||||
|
|
||||||
componentObjects.put(componentName, componentObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addComponentFactory(ScriptComponentFactory<?> factory) {
|
|
||||||
Objects.requireNonNull(factory);
|
|
||||||
factories.add(factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> getComponentObjects() {
|
|
||||||
Map<String, Object> componentObjects = this.parent == null ?
|
|
||||||
new HashMap<>() : new HashMap<>(this.parent.getComponentObjects());
|
|
||||||
componentObjects.putAll(this.componentObjects);
|
|
||||||
return Collections.unmodifiableMap(componentObjects);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<ScriptComponentFactory<?>> getScriptComponentFactories() {
|
|
||||||
Set<ScriptComponentFactory<?>> factories = this.parent == null ?
|
|
||||||
new HashSet<>() : new HashSet<>(this.parent.factories);
|
|
||||||
factories.addAll(this.factories);
|
|
||||||
return Collections.unmodifiableSet(factories);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -4,42 +4,29 @@ import java.util.Objects;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 脚本信息.
|
* 脚本信息.
|
||||||
* <p> 脚本信息的 Group, Name 和 Version 遵循 Java 依赖管理的 GAV 坐标规则。
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public class ScriptInfo {
|
public class ScriptInfo {
|
||||||
|
|
||||||
private String group;
|
private String group;
|
||||||
private String name;
|
private String artifact;
|
||||||
private String version;
|
private String version;
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组名.
|
|
||||||
* @return 返回组名.
|
|
||||||
*/
|
|
||||||
public String getGroup() {
|
public String getGroup() {
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public String getArtifact() {
|
||||||
* 获取组名.
|
return artifact;
|
||||||
* @return 返回组名.
|
|
||||||
*/
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取组名.
|
|
||||||
* @return 返回组名.
|
|
||||||
*/
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getGroup() + ":" + getName() + ":" + getVersion();
|
return getGroup() + ":" + getArtifact() + ":" + getVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -51,34 +38,22 @@ public class ScriptInfo {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ScriptInfo that = (ScriptInfo) o;
|
ScriptInfo that = (ScriptInfo) o;
|
||||||
return group.equals(that.group) && name.equals(that.name) && version.equals(that.version);
|
return group.equals(that.group) && artifact.equals(that.artifact) && version.equals(that.version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(group, name, version);
|
return Objects.hash(group, artifact, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置组名.
|
|
||||||
* @param group 新的组名.
|
|
||||||
*/
|
|
||||||
public void setGroup(String group) {
|
public void setGroup(String group) {
|
||||||
this.group = group;
|
this.group = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setArtifact(String artifact) {
|
||||||
* 设置脚本名称.
|
this.artifact = artifact;
|
||||||
* @param name 设置脚本名称.
|
|
||||||
*/
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置版本号.
|
|
||||||
* @param version 脚本版本号.
|
|
||||||
*/
|
|
||||||
public void setVersion(String version) {
|
public void setVersion(String version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ public interface ScriptLoader {
|
|||||||
* @return 返回脚本对象.
|
* @return 返回脚本对象.
|
||||||
* @throws Exception 当 Loader 抛出异常时, 将视为脚本加载失败, 该脚本跳过加载.
|
* @throws Exception 当 Loader 抛出异常时, 将视为脚本加载失败, 该脚本跳过加载.
|
||||||
*/
|
*/
|
||||||
Script loadScript(ScriptComponents context, File scriptFile) throws Exception;
|
Script loadScript(ScriptComponent context, File scriptFile) throws Exception;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取脚本信息.
|
* 获取脚本信息.
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script;
|
|
||||||
|
|
||||||
import net.sf.cglib.proxy.Enhancer;
|
|
||||||
import net.sf.cglib.proxy.MethodInterceptor;
|
|
||||||
import net.sf.cglib.proxy.MethodProxy;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.slf4j.Marker;
|
|
||||||
import org.slf4j.MarkerFactory;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 脚本日志记录器工厂.
|
|
||||||
* <p> 通过 CGLIB 无缝为脚本设置 {@link Marker} 以将脚本日志输出到特定文件中.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class ScriptLoggerFactory implements ScriptComponentFactory<Logger> {
|
|
||||||
|
|
||||||
private final static Marker SCRIPT_MARKER = MarkerFactory.getMarker("Script");
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Logger getInstance(ScriptInfo info) {
|
|
||||||
Logger realLogger = LoggerFactory.getLogger(info.getGroup() + ":" + info.getName());
|
|
||||||
Enhancer enhancer = new Enhancer();
|
|
||||||
enhancer.setSuperclass(Logger.class);
|
|
||||||
enhancer.setCallback(new LoggerProxyImpl(realLogger));
|
|
||||||
return (Logger) enhancer.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPropertyName() {
|
|
||||||
return "Log";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LoggerProxyImpl implements MethodInterceptor {
|
|
||||||
|
|
||||||
private final static Set<String> PROXY_METHOD_NAMES = Set.of(
|
|
||||||
"trace", "debug", "info", "warn", "error",
|
|
||||||
"isTraceEnabled",
|
|
||||||
"isDebugEnabled",
|
|
||||||
"isInfoEnabled",
|
|
||||||
"isWarnEnabled",
|
|
||||||
"isErrorEnabled"
|
|
||||||
);
|
|
||||||
|
|
||||||
private final static ConcurrentHashMap<Method, Method> METHOD_CACHE_MAP =
|
|
||||||
new ConcurrentHashMap<>(PROXY_METHOD_NAMES.size(), 1);
|
|
||||||
|
|
||||||
private final Logger targetLog;
|
|
||||||
private final Class<? extends Logger> logClass;
|
|
||||||
|
|
||||||
public LoggerProxyImpl(Logger targetLog) {
|
|
||||||
this.targetLog = targetLog;
|
|
||||||
logClass = targetLog.getClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
|
|
||||||
if (METHOD_CACHE_MAP.contains(method)) {
|
|
||||||
return METHOD_CACHE_MAP.get(method).invoke(targetLog, insertParameterToArray(args, SCRIPT_MARKER));
|
|
||||||
} else if (PROXY_METHOD_NAMES.contains(method.getName())) {
|
|
||||||
Class<?>[] types = method.getParameterTypes();
|
|
||||||
if (types.length != 0 && !Marker.class.isAssignableFrom(types[0])) {
|
|
||||||
Class<?>[] realMethodParamTypes = insertTypeToArray(types, Marker.class);
|
|
||||||
Method realMethod = logClass.getDeclaredMethod(method.getName(), realMethodParamTypes);
|
|
||||||
METHOD_CACHE_MAP.put(method, realMethod);
|
|
||||||
realMethod.invoke(targetLog, insertParameterToArray(args, SCRIPT_MARKER));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return proxy.invoke(targetLog, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object[] insertParameterToArray(Object[] arr, Object newElement) {
|
|
||||||
if (arr.length == 0) {
|
|
||||||
return new Object[] {newElement};
|
|
||||||
}
|
|
||||||
Object[] newArr = new Object[arr.length + 1];
|
|
||||||
newArr[0] = newElement;
|
|
||||||
System.arraycopy(arr, 0, newArr, 1, arr.length);
|
|
||||||
return newArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Class<?>[] insertTypeToArray(Class<?>[] arr, Class<?> newElement) {
|
|
||||||
if (arr.length == 0) {
|
|
||||||
return new Class<?>[] {newElement};
|
|
||||||
}
|
|
||||||
Class<?>[] newArr = new Class<?>[arr.length + 1];
|
|
||||||
newArr[0] = newElement;
|
|
||||||
System.arraycopy(arr, 0, newArr, 1, arr.length);
|
|
||||||
return newArr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -19,18 +19,13 @@ public final class ScriptManager {
|
|||||||
|
|
||||||
private final Set<ScriptLoader> loaders = new HashSet<>();
|
private final Set<ScriptLoader> loaders = new HashSet<>();
|
||||||
private final File scriptsLocation;
|
private final File scriptsLocation;
|
||||||
private final ScriptComponents context;
|
private final ScriptComponent context;
|
||||||
|
|
||||||
private final Map<ScriptInfo, Script> scripts = new ConcurrentHashMap<>();
|
private final Map<ScriptInfo, Script> scripts = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
/**
|
public ScriptManager(File scriptsLocation, ScriptComponent context) {
|
||||||
* 创建新的脚本管理器.
|
|
||||||
* @param scriptsLocation 脚本加载路径.
|
|
||||||
* @param components 脚本组件.
|
|
||||||
*/
|
|
||||||
public ScriptManager(File scriptsLocation, ScriptComponents components) {
|
|
||||||
this.scriptsLocation = scriptsLocation;
|
this.scriptsLocation = scriptsLocation;
|
||||||
this.context = components;
|
this.context = context;
|
||||||
loadScriptLoaders();
|
loadScriptLoaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,13 +61,6 @@ public final class ScriptManager {
|
|||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ScriptInfo scriptInfo = loader.getScriptInfo(script);
|
|
||||||
if (scriptInfo == null) {
|
|
||||||
log.warn("脚本加载成功, 但加载器没有返回脚本信息, 该脚本已放弃.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
scripts.put(scriptInfo, script);
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("脚本加载时发生异常.(Loader: {}, Path: {})\n{}",
|
log.error("脚本加载时发生异常.(Loader: {}, Path: {})\n{}",
|
||||||
loader.getClass().getName(),
|
loader.getClass().getName(),
|
||||||
@ -80,6 +68,13 @@ public final class ScriptManager {
|
|||||||
Throwables.getStackTraceAsString(e));
|
Throwables.getStackTraceAsString(e));
|
||||||
throw new InvocationTargetException(e);
|
throw new InvocationTargetException(e);
|
||||||
}
|
}
|
||||||
|
ScriptInfo scriptInfo = loader.getScriptInfo(script);
|
||||||
|
if (scriptInfo == null) {
|
||||||
|
log.warn("脚本加载成功, 但加载器没有返回脚本信息, 该脚本已放弃.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
scripts.put(scriptInfo, script);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@ package net.lamgc.oracle.sentry.script.groovy;
|
|||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groovy.lang.DelegatesTo;
|
import groovy.lang.DelegatesTo;
|
||||||
|
import net.lamgc.oracle.sentry.ComputeInstanceManager;
|
||||||
import net.lamgc.oracle.sentry.script.Script;
|
import net.lamgc.oracle.sentry.script.Script;
|
||||||
import net.lamgc.oracle.sentry.script.ScriptInfo;
|
import net.lamgc.oracle.sentry.script.ScriptInfo;
|
||||||
import net.lamgc.oracle.sentry.script.groovy.trigger.GroovyTrigger;
|
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
|
||||||
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
|
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,28 +16,16 @@ import org.codehaus.groovy.runtime.DefaultGroovyMethods;
|
|||||||
public class GroovyDslDelegate implements Script {
|
public class GroovyDslDelegate implements Script {
|
||||||
|
|
||||||
private final GroovyScriptInfo scriptInfo = new GroovyScriptInfo();
|
private final GroovyScriptInfo scriptInfo = new GroovyScriptInfo();
|
||||||
private final GroovyScriptLoader scriptLoader;
|
private final ScriptHttpClient HTTP;
|
||||||
|
private final ComputeInstanceManager InstanceManager;
|
||||||
|
|
||||||
/**
|
public GroovyDslDelegate(ScriptHttpClient httpClient, ComputeInstanceManager instanceManager) {
|
||||||
* 构建一个 DSL Delegate, 并传入可操作对象.
|
HTTP = httpClient;
|
||||||
* @param scriptLoader 该脚本所属的加载器.
|
InstanceManager = instanceManager;
|
||||||
*/
|
|
||||||
public GroovyDslDelegate(GroovyScriptLoader scriptLoader) {
|
|
||||||
this.scriptLoader = scriptLoader;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册触发器.
|
|
||||||
* <p> 注意: 如果脚本尚未初始化完成, 将无法注册触发器, 可通过 {@link #isInitialed()} 检查是否已经完成初始化.
|
|
||||||
* @param triggerName 触发器名称.
|
|
||||||
* @param closure 待执行闭包.
|
|
||||||
*/
|
|
||||||
private void trigger(String triggerName, Closure<?> closure){
|
private void trigger(String triggerName, Closure<?> closure){
|
||||||
if (!scriptLoader.isInitialed(this)) {
|
DefaultGroovyMethods.with(GroovyTriggerProvider.INSTANCE.getTriggerByName(triggerName), closure);
|
||||||
return;
|
|
||||||
}
|
|
||||||
GroovyTrigger trigger = GroovyTriggerProvider.INSTANCE.getTriggerByName(triggerName);
|
|
||||||
DefaultGroovyMethods.with(trigger, closure);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,12 +40,4 @@ public class GroovyDslDelegate implements Script {
|
|||||||
public ScriptInfo getScriptInfo() {
|
public ScriptInfo getScriptInfo() {
|
||||||
return scriptInfo;
|
return scriptInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查脚本当前是否已经初始化完成.
|
|
||||||
* @return 如果脚本已经初始化, 本方法将返回 {@code true}.
|
|
||||||
*/
|
|
||||||
public final boolean isInitialed() {
|
|
||||||
return scriptLoader.isInitialed(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,16 @@ package net.lamgc.oracle.sentry.script.groovy;
|
|||||||
|
|
||||||
import net.lamgc.oracle.sentry.script.ScriptInfo;
|
import net.lamgc.oracle.sentry.script.ScriptInfo;
|
||||||
|
|
||||||
/**
|
|
||||||
* 适配 Groovy 的脚本信息对象.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class GroovyScriptInfo extends ScriptInfo {
|
public class GroovyScriptInfo extends ScriptInfo {
|
||||||
|
|
||||||
/**
|
public void artifact(String artifact) {
|
||||||
* 设置脚本名.
|
super.setArtifact(artifact);
|
||||||
* <p> 不能有空格.
|
|
||||||
* @param name 脚本名.
|
|
||||||
*/
|
|
||||||
public void name(String name) {
|
|
||||||
super.setName(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置组名.
|
|
||||||
* <p> 组名是脚本开发者的域名倒写, 如果你的域名是 example.com,
|
|
||||||
* 那么组名就是 com.example, 没有域名可以用 Github 的,
|
|
||||||
* io.github.[你的 Github 用户名]
|
|
||||||
*
|
|
||||||
* @param group 组名.
|
|
||||||
*/
|
|
||||||
public void group(String group) {
|
public void group(String group) {
|
||||||
super.setGroup(group);
|
super.setGroup(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 脚本版本号.
|
|
||||||
* <p> 遵循 SemVer 版本号规范.
|
|
||||||
* @param version 当前脚本版本号.
|
|
||||||
*/
|
|
||||||
public void version(String version) {
|
public void version(String version) {
|
||||||
super.setVersion(version);
|
super.setVersion(version);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy;
|
package net.lamgc.oracle.sentry.script.groovy;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import groovy.lang.Binding;
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groovy.lang.GroovyClassLoader;
|
||||||
import groovy.util.DelegatingScript;
|
import groovy.util.DelegatingScript;
|
||||||
import net.lamgc.oracle.sentry.script.Script;
|
import net.lamgc.oracle.sentry.script.Script;
|
||||||
import net.lamgc.oracle.sentry.script.ScriptComponents;
|
import net.lamgc.oracle.sentry.script.ScriptComponent;
|
||||||
import net.lamgc.oracle.sentry.script.ScriptInfo;
|
import net.lamgc.oracle.sentry.script.ScriptInfo;
|
||||||
import net.lamgc.oracle.sentry.script.ScriptLoader;
|
import net.lamgc.oracle.sentry.script.ScriptLoader;
|
||||||
import net.lamgc.oracle.sentry.script.ScriptComponentFactory;
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -18,13 +15,10 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groovy 脚本加载器.
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("MapOrSetKeyShouldOverrideHashCodeEquals")
|
@SuppressWarnings("MapOrSetKeyShouldOverrideHashCodeEquals")
|
||||||
@ -33,13 +27,9 @@ public class GroovyScriptLoader implements ScriptLoader {
|
|||||||
private final static Logger log = LoggerFactory.getLogger(GroovyScriptLoader.class);
|
private final static Logger log = LoggerFactory.getLogger(GroovyScriptLoader.class);
|
||||||
|
|
||||||
private final GroovyClassLoader scriptClassLoader;
|
private final GroovyClassLoader scriptClassLoader;
|
||||||
private final Map<Script, ScriptInfo> scriptInfoMap = new ConcurrentHashMap<>();
|
|
||||||
private final Set<Script> initialedScript = new HashSet<>();
|
|
||||||
|
|
||||||
/**
|
private final Map<Script, ScriptInfo> scriptInfoMap = new ConcurrentHashMap<>();
|
||||||
* 构造一个新的脚本加载器.
|
|
||||||
* <p> 每个加载器所使用的 {@link GroovyClassLoader} 实例是不一样的.
|
|
||||||
*/
|
|
||||||
public GroovyScriptLoader() {
|
public GroovyScriptLoader() {
|
||||||
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
|
CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
|
||||||
compilerConfiguration.setScriptBaseClass(DelegatingScript.class.getName());
|
compilerConfiguration.setScriptBaseClass(DelegatingScript.class.getName());
|
||||||
@ -52,7 +42,7 @@ public class GroovyScriptLoader implements ScriptLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Script loadScript(ScriptComponents context, File scriptFile) throws IOException {
|
public Script loadScript(ScriptComponent context, File scriptFile) throws IOException {
|
||||||
Class<?> scriptClass = scriptClassLoader.parseClass(scriptFile);
|
Class<?> scriptClass = scriptClassLoader.parseClass(scriptFile);
|
||||||
if (!DelegatingScript.class.isAssignableFrom(scriptClass)) {
|
if (!DelegatingScript.class.isAssignableFrom(scriptClass)) {
|
||||||
return null;
|
return null;
|
||||||
@ -61,17 +51,10 @@ public class GroovyScriptLoader implements ScriptLoader {
|
|||||||
Constructor<? extends DelegatingScript> constructor =
|
Constructor<? extends DelegatingScript> constructor =
|
||||||
scriptClass.asSubclass(DelegatingScript.class).getConstructor();
|
scriptClass.asSubclass(DelegatingScript.class).getConstructor();
|
||||||
DelegatingScript newScriptObject = constructor.newInstance();
|
DelegatingScript newScriptObject = constructor.newInstance();
|
||||||
GroovyDslDelegate dslDelegate = new GroovyDslDelegate(this);
|
GroovyDslDelegate dslDelegate = new GroovyDslDelegate(context.HTTP(), context.InstanceManager());
|
||||||
newScriptObject.setDelegate(dslDelegate);
|
newScriptObject.setDelegate(dslDelegate);
|
||||||
newScriptObject.run();
|
newScriptObject.run();
|
||||||
ScriptInfo scriptInfo = dslDelegate.getScriptInfo();
|
scriptInfoMap.put(dslDelegate, dslDelegate.getScriptInfo());
|
||||||
if (!checkScriptInfo(scriptInfo)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
initialedScript.add(dslDelegate);
|
|
||||||
newScriptObject.setBinding(createBinding(context, scriptInfo));
|
|
||||||
newScriptObject.run();
|
|
||||||
scriptInfoMap.put(dslDelegate, scriptInfo);
|
|
||||||
return dslDelegate;
|
return dslDelegate;
|
||||||
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
|
||||||
log.error("加载脚本时发生异常.(ScriptPath: {})\n{}", scriptFile.getAbsolutePath(), Throwables.getStackTraceAsString(e));
|
log.error("加载脚本时发生异常.(ScriptPath: {})\n{}", scriptFile.getAbsolutePath(), Throwables.getStackTraceAsString(e));
|
||||||
@ -83,45 +66,4 @@ public class GroovyScriptLoader implements ScriptLoader {
|
|||||||
public ScriptInfo getScriptInfo(Script script) {
|
public ScriptInfo getScriptInfo(Script script) {
|
||||||
return scriptInfoMap.get(script);
|
return scriptInfoMap.get(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查脚本是否已经初始化完毕.
|
|
||||||
* @param script 脚本对象.
|
|
||||||
* @return 如果已初始化完毕, 返回 {@code true}.
|
|
||||||
*/
|
|
||||||
public boolean isInitialed(GroovyDslDelegate script) {
|
|
||||||
return initialedScript.contains(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Binding createBinding(ScriptComponents components, ScriptInfo info) {
|
|
||||||
Binding binding = new Binding();
|
|
||||||
components.getComponentObjects().forEach(binding::setProperty);
|
|
||||||
for (ScriptComponentFactory<?> factory : components.getScriptComponentFactories()) {
|
|
||||||
String componentName = factory.getPropertyName();
|
|
||||||
if (binding.hasVariable(componentName)) {
|
|
||||||
log.warn("脚本组件名发生冲突: 工厂 {} 所给定的组件名已经存在静态组件, 将跳过创建.", factory.getClass());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Object componentObject = factory.getInstance(info);
|
|
||||||
binding.setProperty(componentName, componentObject);
|
|
||||||
}
|
|
||||||
return binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean checkScriptInfo(ScriptInfo info) {
|
|
||||||
if (Strings.isNullOrEmpty(info.getGroup())) {
|
|
||||||
log.warn("脚本信息缺少 {}, 跳过加载.", "Group");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Strings.isNullOrEmpty(info.getName())) {
|
|
||||||
log.warn("脚本信息缺少 {}, 跳过加载.", "Name");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Strings.isNullOrEmpty(info.getVersion())) {
|
|
||||||
log.warn("脚本信息缺少 {}, 跳过加载.", "Version");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,28 +3,23 @@ package net.lamgc.oracle.sentry.script.groovy;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import net.lamgc.oracle.sentry.script.groovy.trigger.GroovyTrigger;
|
import net.lamgc.oracle.sentry.script.groovy.trigger.GroovyTrigger;
|
||||||
import net.lamgc.oracle.sentry.script.groovy.trigger.TriggerName;
|
import net.lamgc.oracle.sentry.script.groovy.trigger.TriggerName;
|
||||||
|
import org.springframework.scheduling.Trigger;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groovy 脚本语言的触发器提供者.
|
|
||||||
* <p> 根据脚本需要创建并注册触发器.
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public class GroovyTriggerProvider {
|
public class GroovyTriggerProvider {
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger Provider 唯一实例.
|
|
||||||
*/
|
|
||||||
public final static GroovyTriggerProvider INSTANCE = new GroovyTriggerProvider();
|
|
||||||
|
|
||||||
|
|
||||||
private final Map<String, ServiceLoader.Provider<GroovyTrigger>> triggerProviderMap = new ConcurrentHashMap<>();
|
private final Map<String, ServiceLoader.Provider<GroovyTrigger>> triggerProviderMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public final static GroovyTriggerProvider INSTANCE = new GroovyTriggerProvider();
|
||||||
|
|
||||||
private GroovyTriggerProvider() {
|
private GroovyTriggerProvider() {
|
||||||
ServiceLoader<GroovyTrigger> loader = ServiceLoader.load(GroovyTrigger.class);
|
ServiceLoader<GroovyTrigger> loader = ServiceLoader.load(GroovyTrigger.class);
|
||||||
loader.stream().iterator().forEachRemaining(triggerProvider -> {
|
loader.stream().iterator().forEachRemaining(triggerProvider -> {
|
||||||
@ -45,18 +40,11 @@ public class GroovyTriggerProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过 Trigger 名称获取新的 Trigger.
|
|
||||||
* @param triggerName Trigger 名称.
|
|
||||||
* @return 返回指定 Trigger 的新实例.
|
|
||||||
* @throws NoSuchElementException 当指定的 Trigger 名称没有对应 Trigger 时抛出该异常.
|
|
||||||
*/
|
|
||||||
public GroovyTrigger getTriggerByName(String triggerName) {
|
public GroovyTrigger getTriggerByName(String triggerName) {
|
||||||
Objects.requireNonNull(triggerName);
|
|
||||||
if (!triggerProviderMap.containsKey(triggerName.toLowerCase())) {
|
if (!triggerProviderMap.containsKey(triggerName.toLowerCase())) {
|
||||||
throw new NoSuchElementException("The specified trigger could not be found: " + triggerName);
|
throw new NoSuchElementException("The specified trigger could not be found: " + triggerName);
|
||||||
}
|
}
|
||||||
return triggerProviderMap.get(triggerName.toLowerCase()).get();
|
return triggerProviderMap.get(triggerName).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.DelegatesTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groovy 脚本的触发器接口.
|
|
||||||
* <p> 实现该接口并添加 {@link TriggerName} 注解后,
|
|
||||||
* 添加到 SPI 实现列表, 即可作为一个 Trigger.
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public interface GroovyTrigger {
|
public interface GroovyTrigger {
|
||||||
@ -15,11 +12,8 @@ public interface GroovyTrigger {
|
|||||||
* <p> 注意, 触发器执行 run 方法不可以阻塞方法返回.
|
* <p> 注意, 触发器执行 run 方法不可以阻塞方法返回.
|
||||||
* @param task 触发器需要执行的任务.
|
* @param task 触发器需要执行的任务.
|
||||||
*/
|
*/
|
||||||
void run(Closure<?> task);
|
void run(Runnable task);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭触发器.
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,286 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
|
||||||
import com.oracle.bmc.core.ComputeClient;
|
|
||||||
import com.oracle.bmc.core.model.Instance;
|
|
||||||
import com.oracle.bmc.core.requests.GetInstanceRequest;
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstanceManager;
|
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstance;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例状态监视触发器.
|
|
||||||
* <p> 通过轮询实例状态, 当状态为指定的状态时将触发回调执行操作.
|
|
||||||
* <p> 吐槽: 换个地方轮询(xs).
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
@TriggerName("InstanceStateWatcher")
|
|
||||||
public class InstanceStateWatchTrigger implements GroovyTrigger {
|
|
||||||
|
|
||||||
private final static Logger log = LoggerFactory.getLogger(InstanceStateWatchTrigger.class);
|
|
||||||
private final static ThreadFactory THREAD_FACTORY = new ThreadFactoryBuilder()
|
|
||||||
.setDaemon(true)
|
|
||||||
.setNameFormat("Thread-InstanceStatePolling-%d")
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final Set<ComputeInstance> instances = new HashSet<>();
|
|
||||||
private final AtomicReference<ComputeInstanceManager> instanceManager = new AtomicReference<>();
|
|
||||||
private final AtomicReference<Thread> pollingThreadReference = new AtomicReference<>();
|
|
||||||
private final Set<Instance.LifecycleState> targetStates = new HashSet<>();
|
|
||||||
private long interval = 5000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置 {@link ComputeInstanceManager}.
|
|
||||||
* <p> 设置计算实例管理器以实现部分功能.
|
|
||||||
* @param instanceManager 实例管理器.
|
|
||||||
*/
|
|
||||||
public void setInstanceManager(ComputeInstanceManager instanceManager) {
|
|
||||||
this.instanceManager.set(instanceManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加所有实例.
|
|
||||||
* <p> 调用前需设置实例管理器.
|
|
||||||
*/
|
|
||||||
public void allInstances() {
|
|
||||||
addInstance(instanceManager.get().getComputeInstances());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加需要监控的实例.
|
|
||||||
* <p> 调用前需设置实例管理器.
|
|
||||||
* @param instanceId 要添加的实例 Id.(注意不是实例名称.)
|
|
||||||
*/
|
|
||||||
public void addInstance(String instanceId) {
|
|
||||||
this.instances.add(instanceManager.get().getComputeInstanceById(instanceId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加需要监控的实例.
|
|
||||||
* @param instance 实例对象.
|
|
||||||
*/
|
|
||||||
public void addInstance(ComputeInstance instance) {
|
|
||||||
this.instances.add(instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加需要监控的实例.
|
|
||||||
* @param instances 实例集合.
|
|
||||||
*/
|
|
||||||
public void addInstance(Set<ComputeInstance> instances) {
|
|
||||||
this.instances.addAll(instances);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置监控间隔.
|
|
||||||
* <p> 该间隔是每轮查询之间的间隔, 并非查询每个实例的间隔. Oracle 对该接口的请求限制很宽松, 且单个帐号的实例有限, 故不再设置查询间隔.
|
|
||||||
* @param interval 间隔时常, 单位: 毫秒.
|
|
||||||
*/
|
|
||||||
public void interval(long interval) {
|
|
||||||
this.interval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 要检查的状态.
|
|
||||||
* <p> 当实例处于指定状态时将触发回调.
|
|
||||||
* @param state 状态名.
|
|
||||||
*/
|
|
||||||
public void state(String state) {
|
|
||||||
targetStates.add(getState(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 要检查的状态.
|
|
||||||
* <p> 当实例处于指定状态时将触发回调.
|
|
||||||
* @param states 状态集合.
|
|
||||||
*/
|
|
||||||
public void state(String[] states) {
|
|
||||||
Set<Instance.LifecycleState> stateSet = new HashSet<>();
|
|
||||||
for (String state : states) {
|
|
||||||
stateSet.add(getState(state));
|
|
||||||
}
|
|
||||||
targetStates.addAll(stateSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加所有状态.
|
|
||||||
* <p> 所有状态都会触发回调函数.
|
|
||||||
*/
|
|
||||||
public void allStates() {
|
|
||||||
targetStates.addAll(Set.of(Instance.LifecycleState.values()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通过状态名获取对应的 {@link Instance.LifecycleState}.
|
|
||||||
* @param state 状态名.
|
|
||||||
* @return 返回对应值.
|
|
||||||
* @throws IllegalArgumentException 当无法获取指定状态对象时抛出该异常.
|
|
||||||
*/
|
|
||||||
private Instance.LifecycleState getState(String state) {
|
|
||||||
try {
|
|
||||||
return LifecycleStateMapping.valueOf(state.toUpperCase()).target;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
try {
|
|
||||||
return Instance.LifecycleState.valueOf(toFirstCharUpperCase(state));
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
throw new IllegalArgumentException("Invalid target state: " + state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void run(final Closure<?> callback) {
|
|
||||||
if (pollingThreadReference.get() != null) {
|
|
||||||
throw new IllegalStateException("Attempting to start multiple check threads repeatedly, which is not allowed.");
|
|
||||||
}
|
|
||||||
if (targetStates.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("The target status has not been set.");
|
|
||||||
}
|
|
||||||
if (instances.isEmpty()) {
|
|
||||||
log.warn("尚未设置任何需监视的实例, 已跳过对触发器的启动.", new IllegalStateException("未设置任何需要监视的实例."));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread newPollingThread = THREAD_FACTORY.newThread(new PollingTask(instances, targetStates, callback, interval));
|
|
||||||
newPollingThread.start();
|
|
||||||
pollingThreadReference.set(newPollingThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public synchronized void shutdown() {
|
|
||||||
if (pollingThreadReference.get() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 合规的,用于停止线程的方法。(笑)(无歧义)
|
|
||||||
Thread pollingThread = pollingThreadReference.get();
|
|
||||||
pollingThread.interrupt();
|
|
||||||
pollingThreadReference.set(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 首字母大写.
|
|
||||||
* @param str 要处理的单词.
|
|
||||||
* @return 如果首字母非大写, 就转换成大写.
|
|
||||||
*/
|
|
||||||
private static String toFirstCharUpperCase(String str) {
|
|
||||||
char firstChar = str.charAt(0);
|
|
||||||
if (!Character.isUpperCase(firstChar)) {
|
|
||||||
return Character.toUpperCase(firstChar) + str.substring(1);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 轮询任务.
|
|
||||||
*/
|
|
||||||
private static class PollingTask implements Runnable {
|
|
||||||
|
|
||||||
private final Set<ComputeInstance> instances;
|
|
||||||
private final Set<Instance.LifecycleState> targetStates;
|
|
||||||
private final Closure<?> callback;
|
|
||||||
private final long interval;
|
|
||||||
private final Map<ComputeInstance, Instance.LifecycleState> lastStateMap = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
private PollingTask(Set<ComputeInstance> instances, Set<Instance.LifecycleState> targetStates,
|
|
||||||
Closure<?> callback, long interval) {
|
|
||||||
this.instances = instances;
|
|
||||||
this.targetStates = targetStates;
|
|
||||||
this.callback = callback;
|
|
||||||
if (interval <= 0) {
|
|
||||||
throw new IllegalArgumentException("Interval cannot be less than or equal to 0.");
|
|
||||||
}
|
|
||||||
this.interval = interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("BusyWait")
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
while (!Thread.interrupted()) {
|
|
||||||
try {
|
|
||||||
// 并非 BusyWait, 而是延迟操作.
|
|
||||||
Thread.sleep(interval);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
log.trace("正在开始新一轮实例状态检查...");
|
|
||||||
for (ComputeInstance instance : instances) {
|
|
||||||
ComputeClient computeClient = instance.getFromAccount().clients().compute();
|
|
||||||
Instance instanceInfo;
|
|
||||||
try {
|
|
||||||
instanceInfo = computeClient.getInstance(GetInstanceRequest.builder()
|
|
||||||
.instanceId(instance.getInstanceId())
|
|
||||||
.build()).getInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (e.getCause() instanceof InterruptedException) {
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.error("获取实例状态失败.", e);
|
|
||||||
} else {
|
|
||||||
log.error("获取实例状态失败(详情请打开 Debug 级别): {}:{}", e.getClass().getName(), e.getMessage());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Instance.LifecycleState lastState = lastStateMap.get(instance);
|
|
||||||
if (targetStates.contains(instanceInfo.getLifecycleState()) &&
|
|
||||||
instanceInfo.getLifecycleState() != lastState) {
|
|
||||||
try {
|
|
||||||
callback.call(new InstanceStateChangeEvent(instance,
|
|
||||||
lastState != null ? lastState.name() : null,
|
|
||||||
instanceInfo.getLifecycleState().name()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("实例状态事件处理时发生未捕获异常.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastStateMap.put(instance, instanceInfo.getLifecycleState());
|
|
||||||
}
|
|
||||||
log.trace("实例状态检查已结束.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一个 Oracle 实例状态枚举的映射枚举类.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings({"AlibabaEnumConstantsMustHaveComment", "unused"})
|
|
||||||
private enum LifecycleStateMapping {
|
|
||||||
MOVING(Instance.LifecycleState.Moving),
|
|
||||||
PROVISIONING(Instance.LifecycleState.Provisioning),
|
|
||||||
RUNNING(Instance.LifecycleState.Running),
|
|
||||||
STARTING(Instance.LifecycleState.Starting),
|
|
||||||
STOPPING(Instance.LifecycleState.Stopping),
|
|
||||||
STOPPED(Instance.LifecycleState.Stopped),
|
|
||||||
CREATING_IMAGE(Instance.LifecycleState.CreatingImage),
|
|
||||||
TERMINATING(Instance.LifecycleState.Terminating),
|
|
||||||
TERMINATED(Instance.LifecycleState.Terminated);
|
|
||||||
|
|
||||||
private final Instance.LifecycleState target;
|
|
||||||
|
|
||||||
LifecycleStateMapping(Instance.LifecycleState target) {
|
|
||||||
this.target = target;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实例状态变更事件.
|
|
||||||
* @param instance 相关实例.
|
|
||||||
* @param oldState 旧状态, 当监视器第一次查询即触发时, 该值为 {@code null}.
|
|
||||||
* @param newState 新状态.
|
|
||||||
*/
|
|
||||||
public static record InstanceStateChangeEvent(
|
|
||||||
ComputeInstance instance,
|
|
||||||
String oldState,
|
|
||||||
String newState
|
|
||||||
) {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import groovy.lang.Closure;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
@ -13,21 +13,17 @@ import java.util.concurrent.Executors;
|
|||||||
*/
|
*/
|
||||||
@TriggerName("once")
|
@TriggerName("once")
|
||||||
public class OnceTrigger implements GroovyTrigger {
|
public class OnceTrigger implements GroovyTrigger {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(OnceTrigger.class);
|
||||||
private final static ExecutorService EXECUTOR = Executors.newFixedThreadPool(
|
private final static ExecutorService EXECUTOR = Executors.newFixedThreadPool(
|
||||||
Runtime.getRuntime().availableProcessors(),
|
Runtime.getRuntime().availableProcessors(),
|
||||||
new ThreadFactoryBuilder()
|
new ThreadFactoryBuilder()
|
||||||
.setNameFormat("GroovyOnceExec-%d")
|
.setNameFormat("GroovyOnceExec-%d")
|
||||||
.setUncaughtExceptionHandler((t, e) -> LoggerFactory.getLogger(OnceTrigger.class)
|
.setUncaughtExceptionHandler((t, e) -> log.error("脚本执行时发生未捕获异常.", e))
|
||||||
.error("脚本执行时发生未捕获异常.", e))
|
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run(Closure<?> task) {
|
public void run(Runnable task) {
|
||||||
EXECUTOR.execute(task);
|
EXECUTOR.execute(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
// Nothing.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,36 +2,30 @@ package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import groovy.lang.Closure;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
import org.springframework.scheduling.support.CronTrigger;
|
import org.springframework.scheduling.support.CronTrigger;
|
||||||
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 时间触发器.
|
|
||||||
* <p> 通过设置 Cron 时间表达式, 可以达到定时触发的效果.
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@TriggerName("timer")
|
@TriggerName("timer")
|
||||||
public class TimerTrigger implements GroovyTrigger {
|
public class TimerTrigger implements GroovyTrigger {
|
||||||
|
|
||||||
|
private final static Logger log = LoggerFactory.getLogger(TimerTrigger.class);
|
||||||
|
|
||||||
|
private CronTrigger trigger;
|
||||||
private final static ThreadPoolTaskScheduler SCHEDULER = new ThreadPoolTaskScheduler();
|
private final static ThreadPoolTaskScheduler SCHEDULER = new ThreadPoolTaskScheduler();
|
||||||
static {
|
static {
|
||||||
SCHEDULER.setPoolSize(Runtime.getRuntime().availableProcessors());
|
SCHEDULER.setPoolSize(Runtime.getRuntime().availableProcessors());
|
||||||
SCHEDULER.setThreadFactory(new ThreadFactoryBuilder()
|
SCHEDULER.setThreadFactory(new ThreadFactoryBuilder()
|
||||||
.setNameFormat("Groovy-TimerTrigger-%d")
|
.setNameFormat("Groovy-TimerTrigger-%d")
|
||||||
.build());
|
.build());
|
||||||
SCHEDULER.setErrorHandler(t -> triggerLog().error("脚本执行时发生异常.", t));
|
SCHEDULER.setErrorHandler(t -> log.error("脚本执行时发生异常.", t));
|
||||||
SCHEDULER.initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private CronTrigger trigger;
|
|
||||||
private ScheduledFuture<?> future;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设定定时时间.
|
* 设定定时时间.
|
||||||
* <p> 只允许在第一次执行时设置.
|
* <p> 只允许在第一次执行时设置.
|
||||||
@ -44,56 +38,26 @@ public class TimerTrigger implements GroovyTrigger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void run(Closure<?> runnable) {
|
public void run(Runnable runnable) {
|
||||||
if (future != null) {
|
|
||||||
triggerLog().warn("脚本存在多个 run 代码块, 已忽略.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trigger == null) {
|
if (trigger == null) {
|
||||||
if (!triggerLog().isDebugEnabled()) {
|
if (!log.isDebugEnabled()) {
|
||||||
triggerLog().warn("脚本尚未设置 Cron 时间表达式, 任务将不会执行(堆栈信息请检查调试级别日志).");
|
log.warn("脚本尚未设置 Cron 时间表达式, 任务将不会执行(堆栈信息请检查调试级别日志).");
|
||||||
} else {
|
} else {
|
||||||
triggerLog().warn("{} - 脚本尚未设置 Cron 时间表达式, 任务将不会执行(堆栈信息请检查调试级别日志).", this);
|
log.warn("{} - 脚本尚未设置 Cron 时间表达式, 任务将不会执行(堆栈信息请检查调试级别日志).", this);
|
||||||
triggerLog().warn("{} - 脚本尚未设置 Cron 时间表达式, 任务将不会执行.\n{}", this, Throwables.getStackTraceAsString(new Exception()));
|
log.warn("{} - 脚本尚未设置 Cron 时间表达式, 任务将不会执行.\n{}", this, Throwables.getStackTraceAsString(new Exception()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
} else if (runnable == null) {
|
} else if (runnable == null) {
|
||||||
if (!triggerLog().isDebugEnabled()) {
|
if (!log.isDebugEnabled()) {
|
||||||
triggerLog().warn("脚本尚未设置 Cron 时间表达式, 任务将不会执行(堆栈信息请检查调试级别日志).");
|
log.warn("脚本尚未设置 Cron 时间表达式, 任务将不会执行(堆栈信息请检查调试级别日志).");
|
||||||
} else {
|
} else {
|
||||||
triggerLog().warn("{} - 脚本尚未设置任务动作, 任务将不会执行(堆栈信息请检查调试级别日志).", this);
|
log.warn("{} - 脚本尚未设置任务动作, 任务将不会执行(堆栈信息请检查调试级别日志).", this);
|
||||||
triggerLog().warn("{} - 脚本尚未设置任务动作, 任务将不会执行.\n{}", this, Throwables.getStackTraceAsString(new Exception()));
|
log.warn("{} - 脚本尚未设置任务动作, 任务将不会执行.\n{}", this, Throwables.getStackTraceAsString(new Exception()));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.future = SCHEDULER.schedule(new TimerTaskRunnable(runnable), trigger);
|
SCHEDULER.schedule(runnable, trigger);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
if (this.future != null) {
|
|
||||||
future.cancel(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Logger triggerLog() {
|
|
||||||
return LoggerFactory.getLogger(TimerTrigger.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TimerTaskRunnable implements Runnable {
|
|
||||||
|
|
||||||
private final Closure<?> closure;
|
|
||||||
|
|
||||||
private TimerTaskRunnable(Closure<?> closure) {
|
|
||||||
this.closure = closure;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
closure.call();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,6 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface TriggerName {
|
public @interface TriggerName {
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger 名称.
|
|
||||||
* <p> 需保证唯一性.
|
|
||||||
* @return 返回 Trigger 名称.
|
|
||||||
*/
|
|
||||||
String value();
|
String value();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,7 @@ import java.io.IOException;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http 访问对象.
|
*
|
||||||
* <p> 该对象可以复用.
|
|
||||||
* @author LamGC
|
* @author LamGC
|
||||||
*/
|
*/
|
||||||
public class HttpAccess {
|
public class HttpAccess {
|
||||||
@ -24,23 +23,12 @@ public class HttpAccess {
|
|||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 以 Get 方法发起 Http 请求.
|
|
||||||
* @return 返回 Http 响应对象.
|
|
||||||
* @throws IOException 当请求发送失败时抛出异常.
|
|
||||||
*/
|
|
||||||
public HttpAccessResponse get() throws IOException {
|
public HttpAccessResponse get() throws IOException {
|
||||||
HttpGet request = new HttpGet(url);
|
HttpGet request = new HttpGet(url);
|
||||||
HttpResponse response = client.execute(request);
|
HttpResponse response = client.execute(request);
|
||||||
return new HttpAccessResponse(response);
|
return new HttpAccessResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 以 Post 方法发起 Http 请求.
|
|
||||||
* @param body Post 请求体.
|
|
||||||
* @return 返回 Http 响应对象.
|
|
||||||
* @throws IOException 当请求发送失败时抛出异常.
|
|
||||||
*/
|
|
||||||
public HttpAccessResponse post(String body) throws IOException {
|
public HttpAccessResponse post(String body) throws IOException {
|
||||||
HttpPost request = new HttpPost(url);
|
HttpPost request = new HttpPost(url);
|
||||||
request.setEntity(new StringEntity(body, StandardCharsets.UTF_8));
|
request.setEntity(new StringEntity(body, StandardCharsets.UTF_8));
|
||||||
|
@ -6,6 +6,7 @@ import org.apache.http.HttpResponse;
|
|||||||
import org.apache.http.StatusLine;
|
import org.apache.http.StatusLine;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -31,45 +32,20 @@ public final class HttpAccessResponse {
|
|||||||
this.entity = response.getEntity();
|
this.entity = response.getEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取响应状态行.
|
|
||||||
* @return 返回响应状态行, 包括响应码和信息.
|
|
||||||
*/
|
|
||||||
public StatusLine getStatusLine() {
|
public StatusLine getStatusLine() {
|
||||||
return statusLine;
|
return statusLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取语言.
|
|
||||||
* @return 返回 Locale 对象.
|
|
||||||
*/
|
|
||||||
public Locale getLocale() {
|
public Locale getLocale() {
|
||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将 ResponseBody 转为字符串并返回.
|
|
||||||
* @return 返回字符串形式的响应体.
|
|
||||||
* @throws IOException 当接收失败时抛出异常.
|
|
||||||
*/
|
|
||||||
public String getContentToString() throws IOException {
|
public String getContentToString() throws IOException {
|
||||||
return EntityUtils.toString(entity);
|
return EntityUtils.toString(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取响应体实体, 可手动接收 Http Response Body.
|
|
||||||
* @return 返回 Http 实体.
|
|
||||||
*/
|
|
||||||
public HttpEntity getEntity() {
|
public HttpEntity getEntity() {
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 Header.
|
|
||||||
* @param name Header 名称.
|
|
||||||
* @return 如果存在, 返回相应值, 否则返回 {@code null}.
|
|
||||||
*/
|
|
||||||
public String getHeader(String name) {
|
|
||||||
return headers.get(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,10 @@ package net.lamgc.oracle.sentry.script.tools.http;
|
|||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建脚本使用的 HttpClient 包装对象.
|
|
||||||
* <p> 可根据脚本需要优化和简化步骤.
|
|
||||||
* @author LamGC
|
|
||||||
*/
|
|
||||||
public class ScriptHttpClient {
|
public class ScriptHttpClient {
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
private final HttpClient httpClient;
|
||||||
|
|
||||||
/**
|
|
||||||
* 包装并构造一个脚本 Http 客户端.
|
|
||||||
* @param httpClient 原始 Http 客户端.
|
|
||||||
*/
|
|
||||||
public ScriptHttpClient(HttpClient httpClient) {
|
public ScriptHttpClient(HttpClient httpClient) {
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
}
|
}
|
||||||
@ -22,7 +13,6 @@ public class ScriptHttpClient {
|
|||||||
/**
|
/**
|
||||||
* 打开一个连接.
|
* 打开一个连接.
|
||||||
* @param url 要访问的 Url.
|
* @param url 要访问的 Url.
|
||||||
* @return 返回 Http 访问对象(可重复使用).
|
|
||||||
*/
|
*/
|
||||||
public HttpAccess create(String url) {
|
public HttpAccess create(String url) {
|
||||||
return new HttpAccess(httpClient, url);
|
return new HttpAccess(httpClient, url);
|
||||||
|
@ -1 +0,0 @@
|
|||||||
net.lamgc.oracle.sentry.script.BuiltinComponentExtension
|
|
@ -1,3 +1,2 @@
|
|||||||
net.lamgc.oracle.sentry.script.groovy.trigger.OnceTrigger
|
net.lamgc.oracle.sentry.script.groovy.trigger.OnceTrigger
|
||||||
net.lamgc.oracle.sentry.script.groovy.trigger.TimerTrigger
|
net.lamgc.oracle.sentry.script.groovy.trigger.TimerTrigger
|
||||||
net.lamgc.oracle.sentry.script.groovy.trigger.InstanceStateWatchTrigger
|
|
||||||
|
@ -14,14 +14,3 @@ oracle:
|
|||||||
# 首次连接认证策略
|
# 首次连接认证策略
|
||||||
# 支持 confirm(询问) accept(接受) reject(拒绝)
|
# 支持 confirm(询问) accept(接受) reject(拒绝)
|
||||||
authenticationPolicy: 'confirm'
|
authenticationPolicy: 'confirm'
|
||||||
# SSH 转发设定
|
|
||||||
forwarding:
|
|
||||||
# X11 转发
|
|
||||||
X11:
|
|
||||||
enable: false
|
|
||||||
# SSH-Agent 转发
|
|
||||||
agent:
|
|
||||||
enable: false
|
|
||||||
# TCP 转发
|
|
||||||
tcp:
|
|
||||||
enable: true
|
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
<configuration debug="false">
|
<configuration debug="false">
|
||||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<target>System.out</target>
|
<target>System.out</target>
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.NoMarkerFilter"/>
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.LevelRangeFilter">
|
<level>INFO</level>
|
||||||
<minLevel>DEBUG</minLevel>
|
<onMatch>ACCEPT</onMatch>
|
||||||
<maxLevel>INFO</maxLevel>
|
<onMismatch>DENY</onMismatch>
|
||||||
</filter>
|
</filter>
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>[%d{HH:mm:ss.SSS} %5level][%logger][%thread]: %msg%n</pattern>
|
<pattern>[%d{HH:mm:ss.SSS} %5level][%logger][%thread]: %msg%n</pattern>
|
||||||
@ -13,7 +13,6 @@
|
|||||||
</appender>
|
</appender>
|
||||||
<appender name="stderr" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="stderr" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
<target>System.err</target>
|
<target>System.err</target>
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.NoMarkerFilter"/>
|
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
||||||
<level>WARN</level>
|
<level>WARN</level>
|
||||||
</filter>
|
</filter>
|
||||||
@ -24,7 +23,6 @@
|
|||||||
|
|
||||||
<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="logFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>./logs/latest.log</file>
|
<file>./logs/latest.log</file>
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.NoMarkerFilter"/>
|
|
||||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
<level>TRACE</level>
|
<level>TRACE</level>
|
||||||
<onMatch>DENY</onMatch>
|
<onMatch>DENY</onMatch>
|
||||||
@ -39,63 +37,11 @@
|
|||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="stdout_script" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<target>System.out</target>
|
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.MarkerFilter">
|
|
||||||
<markerName>Script</markerName>
|
|
||||||
</filter>
|
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.LevelRangeFilter">
|
|
||||||
<minLevel>DEBUG</minLevel>
|
|
||||||
<maxLevel>INFO</maxLevel>
|
|
||||||
</filter>
|
|
||||||
<encoder>
|
|
||||||
<pattern>[%d{HH:mm:ss.SSS} %5level][Script][%logger]: %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
<appender name="stderr_script" class="ch.qos.logback.core.ConsoleAppender">
|
|
||||||
<target>System.err</target>
|
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.MarkerFilter">
|
|
||||||
<markerName>Script</markerName>
|
|
||||||
</filter>
|
|
||||||
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
|
|
||||||
<level>WARN</level>
|
|
||||||
</filter>
|
|
||||||
<encoder>
|
|
||||||
<pattern>[%d{HH:mm:ss.SSS} %5level][Script][%logger]: %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<appender name="logFile_script" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
|
||||||
<file>./logs/latest-script.log</file>
|
|
||||||
<filter class="net.lamgc.oracle.sentry.common.logging.MarkerFilter">
|
|
||||||
<markerName>Script</markerName>
|
|
||||||
</filter>
|
|
||||||
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
|
||||||
<level>TRACE</level>
|
|
||||||
<onMatch>DENY</onMatch>
|
|
||||||
<onMismatch>ACCEPT</onMismatch>
|
|
||||||
</filter>
|
|
||||||
<encoder>
|
|
||||||
<pattern>[%d{HH:mm:ss.SSS} %5level][Script][%logger]: %msg%n</pattern>
|
|
||||||
</encoder>
|
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
|
||||||
<fileNamePattern>./logs/run-script-%d{yyyy-MM-dd}.log.gz</fileNamePattern>
|
|
||||||
<maxHistory>30</maxHistory>
|
|
||||||
</rollingPolicy>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<logger name="com.oracle.bmc" level="WARN"/>
|
<logger name="com.oracle.bmc" level="WARN"/>
|
||||||
<logger name="org.springframework" level="INFO"/>
|
|
||||||
<logger name="org.apache.http" level="INFO"/>
|
|
||||||
<logger name="org.apache.sshd" level="INFO"/>
|
|
||||||
<logger name="com.oracle.bmc.http.ApacheConfigurator" level="ERROR"/>
|
<logger name="com.oracle.bmc.http.ApacheConfigurator" level="ERROR"/>
|
||||||
<root level="INFO">
|
<root>
|
||||||
<appender-ref ref="stdout" />
|
<appender-ref ref="stdout" />
|
||||||
<appender-ref ref="stderr" />
|
<appender-ref ref="stderr" />
|
||||||
<appender-ref ref="logFile" />
|
<appender-ref ref="logFile" />
|
||||||
|
|
||||||
<appender-ref ref="stdout_script" />
|
|
||||||
<appender-ref ref="stderr_script" />
|
|
||||||
<appender-ref ref="logFile_script" />
|
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class LazyLoaderTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public void lazyLoadTest() throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
LazyLoader<Object> loader = new LazyLoader<>(Object::new);
|
|
||||||
|
|
||||||
Field field = LazyLoader.class.getDeclaredField("object");
|
|
||||||
field.setAccessible(true);
|
|
||||||
AtomicReference reference = (AtomicReference) field.get(loader);
|
|
||||||
assertNotNull(reference);
|
|
||||||
assertNull(reference.get());
|
|
||||||
Object instance = loader.getInstance();
|
|
||||||
assertEquals(reference.get(), instance);
|
|
||||||
assertEquals(reference.get(), loader.getInstance());
|
|
||||||
assertEquals(instance, loader.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SuppressWarnings("StatementWithEmptyBody")
|
|
||||||
public void multiThreadAccessTest() {
|
|
||||||
class Singleton {
|
|
||||||
private final static AtomicInteger constructNum = new AtomicInteger(0);
|
|
||||||
public Singleton() {
|
|
||||||
if (constructNum.incrementAndGet() > 1) {
|
|
||||||
fail("Multiple instances were generated.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final LazyLoader<Singleton> loader = new LazyLoader<>(Singleton::new);
|
|
||||||
AtomicBoolean start = new AtomicBoolean(false);
|
|
||||||
int threadNum = Runtime.getRuntime().availableProcessors();
|
|
||||||
List<Thread> threads = new ArrayList<>(threadNum);
|
|
||||||
for (int i = 0; i < threadNum; i++) {
|
|
||||||
Thread thread = new Thread(() -> {
|
|
||||||
while (!start.get()) {
|
|
||||||
}
|
|
||||||
assertNotNull(loader.getInstance());
|
|
||||||
});
|
|
||||||
threads.add(thread);
|
|
||||||
}
|
|
||||||
for (Thread thread : threads) {
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
start.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.common.retry;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
class RetryerTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void successTest() throws RetryFailedException, ExecutionException, InterruptedException {
|
|
||||||
final Object obj = new Object();
|
|
||||||
Retryer<Object> retryer = Retryer.builder(() -> obj).create();
|
|
||||||
assertEquals(obj, retryer.execute());
|
|
||||||
assertEquals(obj, retryer.executeAsync().get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void failedTest() {
|
|
||||||
assertThrows(RetryFailedException.class, () -> {
|
|
||||||
Retryer<Object> retryer = Retryer.builder(() -> {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}).create();
|
|
||||||
retryer.execute();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void retryNumberTest() {
|
|
||||||
final int retryNumber = new Random().nextInt(9) + 1;
|
|
||||||
final AtomicInteger retryCounter = new AtomicInteger(-1);
|
|
||||||
Retryer<Object> retryer = Retryer.builder(() -> {
|
|
||||||
retryCounter.incrementAndGet();
|
|
||||||
throw new RuntimeException();
|
|
||||||
}).retryNumber(retryNumber).create();
|
|
||||||
try {
|
|
||||||
retryer.execute();
|
|
||||||
} catch (RetryFailedException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
assertEquals(retryNumber, retryCounter.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void checkerTest() {
|
|
||||||
Retryer<Object> retryer = Retryer.builder(() -> null)
|
|
||||||
.retryIfReturnNull()
|
|
||||||
.create();
|
|
||||||
assertThrows(RetryFailedException.class, retryer::execute);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|
||||||
|
|
||||||
import java.security.PublicKey;
|
|
||||||
|
|
||||||
public class BadPublicKey implements PublicKey {
|
|
||||||
@Override
|
|
||||||
public String getAlgorithm() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFormat() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getEncoded() {
|
|
||||||
return new byte[0];
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,50 +3,43 @@ package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import org.apache.sshd.common.config.keys.KeyUtils;
|
import org.apache.sshd.common.config.keys.KeyUtils;
|
||||||
import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.Random;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
/**
|
|
||||||
* @see SshAuthInfoSerializer
|
|
||||||
*/
|
|
||||||
class SshAuthInfoSerializerTest {
|
class SshAuthInfoSerializerTest {
|
||||||
|
|
||||||
private final static Gson gson = new GsonBuilder()
|
private final static Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(SshAuthInfo.class, SshAuthInfoSerializer.INSTANCE)
|
.registerTypeAdapter(SshAuthInfo.class, SshAuthInfoSerializer.INSTANCE)
|
||||||
.registerTypeAdapter(PasswordAuthInfo.class, SshAuthInfoSerializer.INSTANCE)
|
|
||||||
.registerTypeAdapter(PublicKeyAuthInfo.class, SshAuthInfoSerializer.INSTANCE)
|
|
||||||
.registerTypeAdapter(UnsupportedSshAuthInfo.class, SshAuthInfoSerializer.INSTANCE)
|
|
||||||
.serializeNulls()
|
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
private JsonObject getTestsInfo(String name) {
|
private JsonObject getPasswordAuthObject() {
|
||||||
InputStream resource = this.getClass().getResourceAsStream("/ssh-auth/" + name + ".json");
|
return gson.fromJson("""
|
||||||
if (resource == null) {
|
{
|
||||||
throw new NoSuchElementException("Required resource not found: " + name);
|
"username": "opc",
|
||||||
}
|
"authType": "password",
|
||||||
return gson.fromJson(new InputStreamReader(resource, StandardCharsets.UTF_8), JsonObject.class);
|
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
||||||
|
"password": "123456"
|
||||||
|
}
|
||||||
|
""", JsonObject.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonObject getPublicKeyAuthObject() {
|
||||||
|
return gson.fromJson("""
|
||||||
|
{
|
||||||
|
"username": "opc",
|
||||||
|
"authType": "Public_Key",
|
||||||
|
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
||||||
|
"privateKeyPath": "",
|
||||||
|
"keyPassword": ""
|
||||||
|
}
|
||||||
|
""", JsonObject.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void deserializePasswordTest() {
|
public void deserializeTest() {
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("StandardPassword"), SshAuthInfo.class);
|
SshAuthInfo info = gson.fromJson(getPasswordAuthObject(), SshAuthInfo.class);
|
||||||
|
|
||||||
assertTrue(info instanceof PasswordAuthInfo);
|
assertTrue(info instanceof PasswordAuthInfo);
|
||||||
assertEquals("opc", info.getUsername());
|
assertEquals("opc", info.getUsername());
|
||||||
@ -54,206 +47,4 @@ class SshAuthInfoSerializerTest {
|
|||||||
assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey()));
|
assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializePublicKeyTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("StandardPublicKey"), SshAuthInfo.class);
|
|
||||||
assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey()));
|
|
||||||
assertEquals("opc", info.getUsername());
|
|
||||||
if (info instanceof PublicKeyAuthInfo pkInfo) {
|
|
||||||
assertEquals(new File("~/.ssh/id_rsa"), pkInfo.getPrivateKeyPath());
|
|
||||||
assertEquals("123456", pkInfo.getKeyPassword());
|
|
||||||
} else {
|
|
||||||
fail("The type of the parsing result does not match: " + info.getClass());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeBadPortNumberTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("BadPortValue-NonNumber"), SshAuthInfo.class);
|
|
||||||
|
|
||||||
assertTrue(info instanceof PasswordAuthInfo);
|
|
||||||
assertEquals("opc", info.getUsername());
|
|
||||||
assertEquals("123456", ((PasswordAuthInfo) info).getPassword());
|
|
||||||
assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey()));
|
|
||||||
assertEquals(22, info.getPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializePortNumberOutOfBoundTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("BadPortValue-OutOfBound"), SshAuthInfo.class);
|
|
||||||
|
|
||||||
assertTrue(info instanceof PasswordAuthInfo);
|
|
||||||
assertEquals("opc", info.getUsername());
|
|
||||||
assertEquals("123456", ((PasswordAuthInfo) info).getPassword());
|
|
||||||
assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey()));
|
|
||||||
assertEquals(22, info.getPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializePortNumberOutOfBoundMinusTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("BadPortValue-OutOfBound-minus"), SshAuthInfo.class);
|
|
||||||
|
|
||||||
assertTrue(info instanceof PasswordAuthInfo);
|
|
||||||
assertEquals("opc", info.getUsername());
|
|
||||||
assertEquals("123456", ((PasswordAuthInfo) info).getPassword());
|
|
||||||
assertEquals("SHA256:qBu2jRXM6Wog/jWUJJ0WLTMb3UdDGAmYEVZQNZdFZNM", KeyUtils.getFingerPrint(info.getServerKey()));
|
|
||||||
assertEquals(22, info.getPort());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeUnsupportedTest() {
|
|
||||||
assertThrows(JsonParseException.class, () ->
|
|
||||||
gson.fromJson(getTestsInfo("UnsupportedAuthType"), SshAuthInfo.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeNoExistTypeTest() {
|
|
||||||
assertThrows(JsonParseException.class, () ->
|
|
||||||
gson.fromJson(getTestsInfo("NoExistType"), SshAuthInfo.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeBadServerKeyFieldTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("BadServerKeyField"), SshAuthInfo.class);
|
|
||||||
assertNull(info.getServerKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeBadServerKeyDecodeTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("BadServerKey-decode"), SshAuthInfo.class);
|
|
||||||
assertNull(info.getServerKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeNoExistServerKeyTest() {
|
|
||||||
SshAuthInfo info = gson.fromJson(getTestsInfo("ServerKeyNoExist"), SshAuthInfo.class);
|
|
||||||
assertNull(info.getServerKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeUnsupportedJsonTypeTest() {
|
|
||||||
assertThrows(JsonParseException.class, () ->
|
|
||||||
gson.fromJson(getTestsInfo("UnsupportedJsonType"), SshAuthInfo.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void deserializeBadRequiredFieldJsonTypeTest() {
|
|
||||||
assertThrows(JsonParseException.class, () ->
|
|
||||||
gson.fromJson(getTestsInfo("BadRequiredFieldType"), SshAuthInfo.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initialSshAuthInfo(SshAuthInfo info) {
|
|
||||||
try {
|
|
||||||
KeyPair pair = KeyUtils.generateKeyPair("ssh-rsa", 3072);
|
|
||||||
info.setServerKey(pair.getPublic());
|
|
||||||
info.setPort(new Random().nextInt(65536));
|
|
||||||
info.setUsername("linux");
|
|
||||||
if (info instanceof PasswordAuthInfo psw) {
|
|
||||||
psw.setPassword(String.valueOf(new Random().nextLong()));
|
|
||||||
} else if (info instanceof PublicKeyAuthInfo pk) {
|
|
||||||
pk.setKeyPassword(String.valueOf(new Random().nextLong()));
|
|
||||||
pk.setPrivateKeyPath(new File("./" + new Random().nextLong() + "/key"));
|
|
||||||
}
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getOrFailField(JsonObject json, String field) {
|
|
||||||
if (json.has(field) && json.get(field).isJsonPrimitive()) {
|
|
||||||
return json.get(field).getAsString();
|
|
||||||
} else {
|
|
||||||
fail("The JSON field '" + field + "' does not exist or is not a primitive.");
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PublicKey decodeSshPublicKey(String publicKeyString) throws GeneralSecurityException, IOException {
|
|
||||||
String[] strings = publicKeyString.split(" ", 3);
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked") PublicKeyEntryDecoder<PublicKey, ?> decoder =
|
|
||||||
(PublicKeyEntryDecoder<PublicKey, ?>) KeyUtils.getPublicKeyEntryDecoder(strings[0]);
|
|
||||||
return decoder.decodePublicKey(null, strings[0], Base64.getDecoder().decode(strings[1]), Collections.emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serializePasswordTest() throws GeneralSecurityException, IOException {
|
|
||||||
PasswordAuthInfo info = new PasswordAuthInfo();
|
|
||||||
initialSshAuthInfo(info);
|
|
||||||
|
|
||||||
JsonObject json = gson.fromJson(gson.toJson(info), JsonObject.class);
|
|
||||||
assertEquals(SshAuthInfo.AuthType.PASSWORD.name(), getOrFailField(json, "authType"));
|
|
||||||
assertEquals(KeyUtils.getFingerPrint(info.getServerKey()),
|
|
||||||
KeyUtils.getFingerPrint(decodeSshPublicKey(getOrFailField(json, "serverKey"))));
|
|
||||||
assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port")));
|
|
||||||
assertEquals(info.getUsername(), getOrFailField(json, "username"));
|
|
||||||
|
|
||||||
assertEquals(info.getPassword(), getOrFailField(json, "password"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serializePublicKeyTest() throws GeneralSecurityException, IOException {
|
|
||||||
PublicKeyAuthInfo info = new PublicKeyAuthInfo();
|
|
||||||
initialSshAuthInfo(info);
|
|
||||||
|
|
||||||
JsonObject json = gson.fromJson(gson.toJson(info), JsonObject.class);
|
|
||||||
assertEquals(SshAuthInfo.AuthType.PUBLIC_KEY.name(), getOrFailField(json, "authType"));
|
|
||||||
assertEquals(KeyUtils.getFingerPrint(info.getServerKey()),
|
|
||||||
KeyUtils.getFingerPrint(decodeSshPublicKey(getOrFailField(json, "serverKey"))));
|
|
||||||
assertEquals(info.getUsername(), getOrFailField(json, "username"));
|
|
||||||
assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port")));
|
|
||||||
|
|
||||||
assertEquals(info.getPrivateKeyPath().getCanonicalFile(), new File(getOrFailField(json, "privateKeyPath")));
|
|
||||||
assertEquals(info.getKeyPassword(), getOrFailField(json, "keyPassword"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serializeNoExistServerKeyTest() {
|
|
||||||
PasswordAuthInfo info = new PasswordAuthInfo();
|
|
||||||
initialSshAuthInfo(info);
|
|
||||||
|
|
||||||
info.setServerKey(null);
|
|
||||||
|
|
||||||
JsonObject json = gson.fromJson(gson.toJson(info), JsonObject.class);
|
|
||||||
assertEquals(SshAuthInfo.AuthType.PASSWORD.name(), getOrFailField(json, "authType"));
|
|
||||||
assertTrue(json.get("serverKey").isJsonNull());
|
|
||||||
assertEquals(info.getUsername(), getOrFailField(json, "username"));
|
|
||||||
assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port")));
|
|
||||||
|
|
||||||
assertEquals(info.getPassword(), getOrFailField(json, "password"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serializeUnsupportedTest() {
|
|
||||||
assertThrows(JsonParseException.class, () ->
|
|
||||||
gson.toJson(new UnsupportedSshAuthInfo(false)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serializeBadPrivateKeyPathTest() {
|
|
||||||
PublicKeyAuthInfo info = new PublicKeyAuthInfo();
|
|
||||||
initialSshAuthInfo(info);
|
|
||||||
|
|
||||||
info.setPrivateKeyPath(new File("@#$*%&&96137:()*/key"));
|
|
||||||
|
|
||||||
assertThrows(JsonParseException.class, () ->
|
|
||||||
gson.toJson(info));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void serializeBadServerKeyTest() {
|
|
||||||
PasswordAuthInfo info = new PasswordAuthInfo();
|
|
||||||
initialSshAuthInfo(info);
|
|
||||||
|
|
||||||
info.setServerKey(new BadPublicKey());
|
|
||||||
|
|
||||||
JsonObject json = gson.fromJson(gson.toJson(info), JsonObject.class);
|
|
||||||
assertEquals(SshAuthInfo.AuthType.PASSWORD.name(), getOrFailField(json, "authType"));
|
|
||||||
assertTrue(json.get("serverKey").isJsonNull());
|
|
||||||
assertEquals(info.getUsername(), getOrFailField(json, "username"));
|
|
||||||
assertEquals(info.getPort(), Integer.parseInt(getOrFailField(json, "port")));
|
|
||||||
|
|
||||||
assertEquals(info.getPassword(), getOrFailField(json, "password"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,15 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.oci.compute.ssh;
|
|
||||||
|
|
||||||
public class UnsupportedSshAuthInfo extends SshAuthInfo {
|
|
||||||
|
|
||||||
private final boolean returnType;
|
|
||||||
|
|
||||||
public UnsupportedSshAuthInfo(boolean returnType) {
|
|
||||||
this.returnType = returnType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public AuthType getType() {
|
|
||||||
return returnType ? AuthType.PASSWORD : null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
package net.lamgc.oracle.sentry.script;
|
package net.lamgc.oracle.sentry.script;
|
||||||
|
|
||||||
import net.lamgc.oracle.sentry.oci.compute.ComputeInstanceManager;
|
import net.lamgc.oracle.sentry.ComputeInstanceManager;
|
||||||
import net.lamgc.oracle.sentry.oci.account.OracleAccountManager;
|
|
||||||
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
|
import net.lamgc.oracle.sentry.script.tools.http.ScriptHttpClient;
|
||||||
import org.apache.http.impl.client.HttpClientBuilder;
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -13,7 +12,8 @@ class ScriptManagerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void loadScriptTest() {
|
public void loadScriptTest() {
|
||||||
ScriptManager manager = new ScriptManager(new File("./run/scripts"),
|
ScriptManager manager = new ScriptManager(new File("./run/scripts"),
|
||||||
new ScriptComponents());
|
new ScriptComponent(new ScriptHttpClient(HttpClientBuilder.create().build()),
|
||||||
|
new ComputeInstanceManager()));
|
||||||
|
|
||||||
manager.loadScripts();
|
manager.loadScripts();
|
||||||
|
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy;
|
|
||||||
|
|
||||||
import net.lamgc.oracle.sentry.script.groovy.trigger.*;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.ServiceLoader;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see GroovyTriggerProvider
|
|
||||||
*/
|
|
||||||
class GroovyTriggerProviderTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void standardRunTest() {
|
|
||||||
GroovyTrigger trigger = GroovyTriggerProvider.INSTANCE.getTriggerByName("once");
|
|
||||||
Assertions.assertNotNull(trigger);
|
|
||||||
Assertions.assertEquals(OnceTrigger.class, trigger.getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void noAnnotationTriggerTest() throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
failIfHasTrigger(NoAnnotationTrigger.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void badTriggerNameLoadTest() throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
failIfHasTrigger(BadAnnotationTrigger.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void duplicateTriggerLoadTest() throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
Assertions.assertFalse(hasTrigger(DuplicateTriggerA.class) && hasTrigger(DuplicateTriggerB.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void tryToGetNoExistTriggerTest() {
|
|
||||||
Assertions.assertThrows(NoSuchElementException.class, () ->
|
|
||||||
GroovyTriggerProvider.INSTANCE.getTriggerByName("NoExistTrigger"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void nullTest() {
|
|
||||||
Assertions.assertThrows(NullPointerException.class, () ->
|
|
||||||
GroovyTriggerProvider.INSTANCE.getTriggerByName(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void failIfHasTrigger(Class<? extends GroovyTrigger> triggerClass)
|
|
||||||
throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
if (hasTrigger(triggerClass)) {
|
|
||||||
Assertions.fail("Trigger did not appear as expected.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private boolean hasTrigger(Class<? extends GroovyTrigger> triggerClass)
|
|
||||||
throws NoSuchFieldException, IllegalAccessException {
|
|
||||||
Field providerMapField =
|
|
||||||
GroovyTriggerProvider.class.getDeclaredField("triggerProviderMap");
|
|
||||||
providerMapField.setAccessible(true);
|
|
||||||
Map<String, ServiceLoader.Provider<GroovyTrigger>> map =
|
|
||||||
(Map<String, ServiceLoader.Provider<GroovyTrigger>>) providerMapField.get(
|
|
||||||
GroovyTriggerProvider.INSTANCE
|
|
||||||
);
|
|
||||||
providerMapField.setAccessible(false);
|
|
||||||
for (ServiceLoader.Provider<GroovyTrigger> value : map.values()) {
|
|
||||||
if (triggerClass.equals(value.type())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|
||||||
|
|
||||||
@TriggerName("")
|
|
||||||
public class BadAnnotationTrigger extends BaseTestTrigger {
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|
||||||
|
|
||||||
import groovy.lang.Closure;
|
|
||||||
|
|
||||||
public abstract class BaseTestTrigger implements GroovyTrigger {
|
|
||||||
@Override
|
|
||||||
public void run(Closure<?> task) {
|
|
||||||
throw new UnsupportedOperationException("Unavailable trigger.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void shutdown() {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|
||||||
|
|
||||||
@TriggerName("Duplicate")
|
|
||||||
public class DuplicateTriggerA extends BaseTestTrigger {
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|
||||||
|
|
||||||
@TriggerName("Duplicate")
|
|
||||||
public class DuplicateTriggerB extends BaseTestTrigger {
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package net.lamgc.oracle.sentry.script.groovy.trigger;
|
|
||||||
|
|
||||||
public class NoAnnotationTrigger extends BaseTestTrigger {
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
net.lamgc.oracle.sentry.script.groovy.trigger.NoAnnotationTrigger
|
|
||||||
net.lamgc.oracle.sentry.script.groovy.trigger.BadAnnotationTrigger
|
|
||||||
net.lamgc.oracle.sentry.script.groovy.trigger.DuplicateTriggerA
|
|
||||||
net.lamgc.oracle.sentry.script.groovy.trigger.DuplicateTriggerB
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "password",
|
|
||||||
"port": "test",
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "password",
|
|
||||||
"port": "-22",
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "password",
|
|
||||||
"port": "1000000",
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"username": {
|
|
||||||
|
|
||||||
},
|
|
||||||
"authType": "password",
|
|
||||||
"port": 22,
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "password",
|
|
||||||
"serverKey": "ssh-rsa AAAAaCADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "Public_Key",
|
|
||||||
"serverKey": {
|
|
||||||
"msg": "badServerKeyField"
|
|
||||||
},
|
|
||||||
"privateKeyPath": "~/.ssh/id_rsa",
|
|
||||||
"keyPassword": "123456"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "password",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "password",
|
|
||||||
"port": 22,
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"password": "123456"
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "opc",
|
|
||||||
"authType": "Public_Key",
|
|
||||||
"serverKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/NGFFKkchNdE8HDE9WHGIcw97ZVOP5edY7drtRQn0xSSG6uLu08T36B8IWT+XJdg45/YMmcuVSzsG1QZs/R3s0URVUhsWjwdezWDeWeBHt8/6TGl2AsgA0iXSAOeRNldhZlITFvWoBEv2wElNjCTsEGo5bBp3rVPqqZNJFUs+FR9s/uVgmFqe7HGhuKhhk7BrRThJ/NcgDRicMQ4yXU3Hl++pG54TVLH+0HmgWg312XNAWtzw2iRmKBAuu2I4pP1TRp93K/lbD7QU8k8W7QcyGSAc73nZrhyzYVMko5wQGt4/vGpchOw7ehkotSejTB1GSyhzBTZobA23For76YLzuVFOjF3lEvSh1QV30ysu0PREKLtY83ad0WHVFqVgJrFHkkXQrglN335BhGwhFzwyMpRxbD8HCDtz6VjpqwoKtd/ExQkcfaj/g10o28vRzHGyzUbCTe433V61fjSsC4Bikw15vTnQ3ZuyOzfyoCYUNpFcf1Wv+mkoWqn9xU8lGvk= Test-Server",
|
|
||||||
"privateKeyPath": "~/.ssh/id_rsa",
|
|
||||||
"keyPassword": "123456"
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"username": "linux",
|
|
||||||
"authType": "Unsupported"
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
[
|
|
||||||
"?"
|
|
||||||
]
|
|
Loading…
Reference in New Issue
Block a user