hotfix GIT subscription + local FS + script invocation feature
This commit is contained in:
parent
7337ab7021
commit
aff02e11f6
|
|
@ -59,7 +59,7 @@ public class App implements Context, Closeable {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
loadedConfig = new Config();
|
loadedConfig = new Config();
|
||||||
loadedConfig.setSubscriptions(new ArrayList<>(List.of(
|
loadedConfig.setSubscriptions(new ArrayList<>(List.of(
|
||||||
new RepositoryConfig("updates", GitSubscription.class, "https://git.kirillius.ru/kirillius/protected-resources-list.git")
|
new RepositoryConfig("updates", GitSubscription.class, "https://git.kirillius.ru/kirillius/protected-resources-list.git", "")
|
||||||
)));
|
)));
|
||||||
try {
|
try {
|
||||||
Config.store(loadedConfig, launcherConfig.getConfigFile());
|
Config.store(loadedConfig, launcherConfig.getConfigFile());
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,6 @@ package ru.kirillius.pf.sdn.External.API;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.json.JSONTokener;
|
|
||||||
import ru.kirillius.json.JSONUtility;
|
|
||||||
import ru.kirillius.pf.sdn.core.Context;
|
import ru.kirillius.pf.sdn.core.Context;
|
||||||
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
|
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
|
||||||
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
|
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
|
||||||
|
|
@ -14,14 +11,16 @@ import ru.kirillius.pf.sdn.core.Util.HashUtil;
|
||||||
import ru.kirillius.utils.logging.SystemLogger;
|
import ru.kirillius.utils.logging.SystemLogger;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscription provider that pulls JSON resource bundles from a Git repository cache.
|
* Subscription provider that pulls JSON resource bundles from a Git repository cache.
|
||||||
*/
|
*/
|
||||||
public class GitSubscription implements SubscriptionProvider {
|
public class GitSubscription implements SubscriptionProvider {
|
||||||
|
private final static String CTX = GitSubscription.class.getSimpleName();
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -31,90 +30,43 @@ public class GitSubscription implements SubscriptionProvider {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clones or updates the configured repository and loads resource bundles from the {@code resources} directory.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Map<String, NetworkResourceBundle> getResources(RepositoryConfig config) {
|
|
||||||
try {
|
|
||||||
var repoDir = new File(context.getConfig().getCacheDirectory(), "git/" + HashUtil.md5(config.getName()));
|
|
||||||
|
|
||||||
if (!repoDir.exists()) {
|
|
||||||
if (!repoDir.mkdirs()) {
|
|
||||||
throw new IOException("Unable to create directory: " + repoDir.getAbsolutePath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var existing = isGitRepository(repoDir);
|
|
||||||
var repository = existing ? openRepository(repoDir) : cloneRepository(config.getSource(), repoDir);
|
|
||||||
if (existing) {
|
|
||||||
SystemLogger.message("Fetching git repository " + config.getName() + " (" + config.getSource() + ")", CTX);
|
|
||||||
checkAndPullUpdates(repository);
|
|
||||||
}
|
|
||||||
repository.close();
|
|
||||||
|
|
||||||
var resourcesDir = new File(repoDir, "resources");
|
|
||||||
if (!resourcesDir.exists()) {
|
|
||||||
SystemLogger.error(resourcesDir + " is not exist in repo (" + config.getSource() + ")", CTX);
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
var map = new HashMap<String, NetworkResourceBundle>();
|
|
||||||
for (var file : Objects.requireNonNull(resourcesDir.listFiles())) {
|
|
||||||
try (var stream = new FileInputStream(file)) {
|
|
||||||
var name = file.getName();
|
|
||||||
if (!name.endsWith(".json")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var bundle = JSONUtility.deserializeStructure(new JSONObject(new JSONTokener(stream)), NetworkResourceBundle.class);
|
|
||||||
map.put(name.substring(0, name.length() - 5), bundle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
} catch (Exception e) {
|
|
||||||
SystemLogger.error("Error while reading git repository", CTX, e);
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runs {@code git fetch} and {@code git pull} when remote updates are available.
|
* Runs {@code git fetch} and {@code git pull} when remote updates are available.
|
||||||
*/
|
*/
|
||||||
private static void checkAndPullUpdates(Git git) throws GitAPIException {
|
private static void checkAndPullUpdates(Git git) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
var fetchResult = git.fetch()
|
||||||
|
.setCheckFetchedObjects(true)
|
||||||
|
.call();
|
||||||
|
|
||||||
var fetchResult = git.fetch()
|
if (fetchResult.getTrackingRefUpdates() != null && !fetchResult.getTrackingRefUpdates().isEmpty()) {
|
||||||
.setCheckFetchedObjects(true)
|
SystemLogger.message("Downloading updates...", CTX);
|
||||||
.call();
|
var pullResult = git.pull().call();
|
||||||
|
|
||||||
if (fetchResult.getTrackingRefUpdates() != null && !fetchResult.getTrackingRefUpdates().isEmpty()) {
|
if (pullResult.isSuccessful()) {
|
||||||
SystemLogger.message("Downloading updates...", CTX);
|
SystemLogger.message("Git pull is successful", CTX);
|
||||||
var pullResult = git.pull().call();
|
|
||||||
|
|
||||||
if (pullResult.isSuccessful()) {
|
// Проверяем были ли обновлены файлы
|
||||||
SystemLogger.message("Git pull is successful", CTX);
|
if (pullResult.getFetchResult() != null && !pullResult.getFetchResult().getTrackingRefUpdates().isEmpty()) {
|
||||||
|
var updatedFiles = new StringJoiner("\n");
|
||||||
// Проверяем были ли обновлены файлы
|
pullResult.getFetchResult().getTrackingRefUpdates().forEach(refUpdate -> updatedFiles.add(" - " + refUpdate.getLocalName() +
|
||||||
if (pullResult.getFetchResult() != null && !pullResult.getFetchResult().getTrackingRefUpdates().isEmpty()) {
|
" : " + refUpdate.getOldObjectId().abbreviate(7).name() +
|
||||||
var updatedFiles = new StringJoiner("\n");
|
" -> " + refUpdate.getNewObjectId().abbreviate(7).name()));
|
||||||
pullResult.getFetchResult().getTrackingRefUpdates().forEach(refUpdate -> updatedFiles.add(" - " + refUpdate.getLocalName() +
|
SystemLogger.message("Updated files: " + updatedFiles, CTX);
|
||||||
" : " + refUpdate.getOldObjectId().abbreviate(7).name() +
|
}
|
||||||
" -> " + refUpdate.getNewObjectId().abbreviate(7).name()));
|
} else {
|
||||||
SystemLogger.message("Updated files: " + updatedFiles, CTX);
|
SystemLogger.error("Download failed", CTX);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
SystemLogger.error("Download failed", CTX);
|
SystemLogger.message("There is no updates", CTX);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (Exception e) {
|
||||||
SystemLogger.message("There is no updates", CTX);
|
SystemLogger.error("Failed to fetch & pull repository", CTX, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static String CTX = GitSubscription.class.getSimpleName();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clones the repository into the given path.
|
* Clones the repository into the given path.
|
||||||
*/
|
*/
|
||||||
|
|
@ -150,4 +102,49 @@ public class GitSubscription implements SubscriptionProvider {
|
||||||
var gitDir = new File(directory, ".git");
|
var gitDir = new File(directory, ".git");
|
||||||
return gitDir.exists() && gitDir.isDirectory();
|
return gitDir.exists() && gitDir.isDirectory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones or updates the configured repository and loads resource bundles from the {@code resources} directory.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, NetworkResourceBundle> getResources(RepositoryConfig config) {
|
||||||
|
try {
|
||||||
|
var repoDir = new File(context.getConfig().getCacheDirectory(), "git/" + HashUtil.md5(config.getName()));
|
||||||
|
|
||||||
|
if (!repoDir.exists()) {
|
||||||
|
if (!repoDir.mkdirs()) {
|
||||||
|
throw new IOException("Unable to create directory: " + repoDir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var existing = isGitRepository(repoDir);
|
||||||
|
var repository = existing ? openRepository(repoDir) : cloneRepository(config.getSource(), repoDir);
|
||||||
|
if (existing) {
|
||||||
|
SystemLogger.message("Fetching git repository " + config.getName() + " (" + config.getSource() + ")", CTX);
|
||||||
|
checkAndPullUpdates(repository);
|
||||||
|
}
|
||||||
|
repository.close();
|
||||||
|
|
||||||
|
if(!config.getScript().isBlank()){
|
||||||
|
try (var shell = new ShellExecutor(ShellExecutor.Config.builder().useSSH(false).build())) {
|
||||||
|
var result = shell.executeCommand(new String[]{
|
||||||
|
config.getScript(),
|
||||||
|
config.getSource()
|
||||||
|
});
|
||||||
|
SystemLogger.message("Shell command execution result:"+result, CTX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resourcesDir = new File(repoDir, "resources");
|
||||||
|
if (!resourcesDir.exists()) {
|
||||||
|
SystemLogger.error(resourcesDir + " is not exist in repo (" + config.getSource() + ")", CTX);
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalFilesystemSubscription.scanLocalDirectory(resourcesDir);
|
||||||
|
} catch (Exception e) {
|
||||||
|
SystemLogger.error("Error while reading git repository", CTX, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
77
app/src/main/java/ru/kirillius/pf/sdn/External/API/LocalFilesystemSubscription.java
vendored
Normal file
77
app/src/main/java/ru/kirillius/pf/sdn/External/API/LocalFilesystemSubscription.java
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package ru.kirillius.pf.sdn.External.API;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
import ru.kirillius.json.JSONUtility;
|
||||||
|
import ru.kirillius.pf.sdn.core.Context;
|
||||||
|
import ru.kirillius.pf.sdn.core.Networking.NetworkResourceBundle;
|
||||||
|
import ru.kirillius.pf.sdn.core.Subscription.RepositoryConfig;
|
||||||
|
import ru.kirillius.pf.sdn.core.Subscription.SubscriptionProvider;
|
||||||
|
import ru.kirillius.utils.logging.SystemLogger;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription provider that pulls JSON resource bundles from a Git repository cache.
|
||||||
|
*/
|
||||||
|
public class LocalFilesystemSubscription implements SubscriptionProvider {
|
||||||
|
private final static String CTX = LocalFilesystemSubscription.class.getSimpleName();
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the provider using the shared application context.
|
||||||
|
*/
|
||||||
|
public LocalFilesystemSubscription(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones or updates the configured repository and loads resource bundles from the {@code resources} directory.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, NetworkResourceBundle> getResources(RepositoryConfig config) {
|
||||||
|
try {
|
||||||
|
var resourcesDir = new File(config.getSource());
|
||||||
|
if (!resourcesDir.exists()) {
|
||||||
|
SystemLogger.error(resourcesDir + " is not exist directory", CTX);
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
if(!config.getScript().isBlank()){
|
||||||
|
try (var shell = new ShellExecutor(ShellExecutor.Config.builder().useSSH(false).build())) {
|
||||||
|
var result = shell.executeCommand(new String[]{
|
||||||
|
config.getScript(),
|
||||||
|
config.getSource()
|
||||||
|
});
|
||||||
|
SystemLogger.message("Shell command execution result:"+result, CTX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return scanLocalDirectory(resourcesDir);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
SystemLogger.error("Error while reading local FS repository", CTX, e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, NetworkResourceBundle> scanLocalDirectory(File resourcesDir) throws IOException {
|
||||||
|
var map = new HashMap<String, NetworkResourceBundle>();
|
||||||
|
for (var file : Objects.requireNonNull(resourcesDir.listFiles())) {
|
||||||
|
try (var stream = new FileInputStream(file)) {
|
||||||
|
var name = file.getName();
|
||||||
|
if (!name.endsWith(".json")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var bundle = JSONUtility.deserializeStructure(new JSONObject(new JSONTokener(stream)), NetworkResourceBundle.class);
|
||||||
|
map.put(name.substring(0, name.length() - 5), bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ import java.net.http.HttpClient;
|
||||||
import java.net.http.HttpRequest;
|
import java.net.http.HttpRequest;
|
||||||
import java.net.http.HttpResponse;
|
import java.net.http.HttpResponse;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.Duration;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -29,7 +30,7 @@ public class TDNSAPI implements Closeable {
|
||||||
*/
|
*/
|
||||||
public TDNSAPI(String server, String authToken) {
|
public TDNSAPI(String server, String authToken) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
httpClient = HttpClient.newBuilder().build();
|
httpClient = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
|
||||||
this.authToken = authToken;
|
this.authToken = authToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import ru.kirillius.json.JSONUtility;
|
||||||
import ru.kirillius.json.rpc.Annotations.JRPCArgument;
|
import ru.kirillius.json.rpc.Annotations.JRPCArgument;
|
||||||
import ru.kirillius.json.rpc.Annotations.JRPCMethod;
|
import ru.kirillius.json.rpc.Annotations.JRPCMethod;
|
||||||
import ru.kirillius.pf.sdn.External.API.GitSubscription;
|
import ru.kirillius.pf.sdn.External.API.GitSubscription;
|
||||||
|
import ru.kirillius.pf.sdn.External.API.LocalFilesystemSubscription;
|
||||||
import ru.kirillius.pf.sdn.InMemoryLogHandler;
|
import ru.kirillius.pf.sdn.InMemoryLogHandler;
|
||||||
import ru.kirillius.pf.sdn.core.*;
|
import ru.kirillius.pf.sdn.core.*;
|
||||||
import ru.kirillius.pf.sdn.web.ProtectedMethod;
|
import ru.kirillius.pf.sdn.web.ProtectedMethod;
|
||||||
|
|
@ -124,6 +125,8 @@ public class System implements RPC {
|
||||||
public JSONArray getRepositoryTypes() {
|
public JSONArray getRepositoryTypes() {
|
||||||
var array = new JSONArray();
|
var array = new JSONArray();
|
||||||
array.put(GitSubscription.class.getName());
|
array.put(GitSubscription.class.getName());
|
||||||
|
array.put(LocalFilesystemSubscription.class.getName());
|
||||||
|
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,4 +28,9 @@ public class RepositoryConfig {
|
||||||
@Getter
|
@Getter
|
||||||
@JSONProperty
|
@JSONProperty
|
||||||
private String source;
|
private String source;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Getter
|
||||||
|
@JSONProperty(required = false)
|
||||||
|
private String script = "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ const CLASS_NAMES = {
|
||||||
subscriptionRemove: 'settings-subscription-remove',
|
subscriptionRemove: 'settings-subscription-remove',
|
||||||
subscriptionName: 'settings-subscription-name',
|
subscriptionName: 'settings-subscription-name',
|
||||||
subscriptionType: 'settings-subscription-type',
|
subscriptionType: 'settings-subscription-type',
|
||||||
subscriptionSource: 'settings-subscription-source'
|
subscriptionSource: 'settings-subscription-source',
|
||||||
|
subscriptionScript: 'settings-subscription-script'
|
||||||
};
|
};
|
||||||
|
|
||||||
let currentConfig = {};
|
let currentConfig = {};
|
||||||
|
|
@ -151,6 +152,7 @@ function createSubscriptionRow(subscription = {}, index = 0) {
|
||||||
const name = subscription.name || '';
|
const name = subscription.name || '';
|
||||||
const type = subscription.type || '';
|
const type = subscription.type || '';
|
||||||
const source = subscription.source || '';
|
const source = subscription.source || '';
|
||||||
|
const script = subscription.script || '';
|
||||||
const uid = `${Date.now()}-${index}`;
|
const uid = `${Date.now()}-${index}`;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
|
|
@ -173,6 +175,10 @@ function createSubscriptionRow(subscription = {}, index = 0) {
|
||||||
<label for="subscription-source-${uid}">Источник</label>
|
<label for="subscription-source-${uid}">Источник</label>
|
||||||
<input type="text" id="subscription-source-${uid}" class="form-control ${CLASS_NAMES.subscriptionSource}" value="${source}">
|
<input type="text" id="subscription-source-${uid}" class="form-control ${CLASS_NAMES.subscriptionSource}" value="${source}">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="subscription-script-${uid}">Скрипт</label>
|
||||||
|
<input type="text" id="subscription-script-${uid}" class="form-control ${CLASS_NAMES.subscriptionScript}" value="${script}">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
@ -342,14 +348,15 @@ function collectSubscriptions() {
|
||||||
const name = $entry.find(`.${CLASS_NAMES.subscriptionName}`).val().trim();
|
const name = $entry.find(`.${CLASS_NAMES.subscriptionName}`).val().trim();
|
||||||
const type = $entry.find(`.${CLASS_NAMES.subscriptionType}`).val();
|
const type = $entry.find(`.${CLASS_NAMES.subscriptionType}`).val();
|
||||||
const source = $entry.find(`.${CLASS_NAMES.subscriptionSource}`).val().trim();
|
const source = $entry.find(`.${CLASS_NAMES.subscriptionSource}`).val().trim();
|
||||||
|
const script = $entry.find(`.${CLASS_NAMES.subscriptionScript}`).val().trim();
|
||||||
|
|
||||||
if (!type) {
|
if (!type) {
|
||||||
hasEmptyType = true;
|
hasEmptyType = true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name || type || source) {
|
if (name || type || source || script) {
|
||||||
subscriptions.push({ name, type, source });
|
subscriptions.push({ name, type, source, script });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue