Промежуточный коммит
This commit is contained in:
parent
c69f90c7de
commit
ea937b12f3
|
|
@ -7,6 +7,7 @@ import org.apache.commons.compress.archivers.tar.TarConstants;
|
||||||
import ru.kirillius.utils.logging.SystemLogger;
|
import ru.kirillius.utils.logging.SystemLogger;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
@ -14,174 +15,211 @@ import java.util.logging.Level;
|
||||||
public class App {
|
public class App {
|
||||||
private static final String LOG_CONTEXT = App.class.getSimpleName();
|
private static final String LOG_CONTEXT = App.class.getSimpleName();
|
||||||
private List<ContainerMetadata> containers;
|
private List<ContainerMetadata> containers;
|
||||||
private Console console;
|
private final Console console;
|
||||||
|
private DeviceContext deviceContext;
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException, MikrotikApiException, InterruptedException {
|
public App() {
|
||||||
|
|
||||||
|
|
||||||
|
console = new Console();
|
||||||
|
try {
|
||||||
|
auth();
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
try {
|
||||||
|
if (!selectMode()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
SystemLogger.error("Unexpected error", LOG_CONTEXT, e);
|
||||||
|
console.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
console.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
SystemLogger.error("Error closing console", LOG_CONTEXT, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
SystemLogger.initializeLogging(Level.INFO, Collections.emptyList());
|
SystemLogger.initializeLogging(Level.INFO, Collections.emptyList());
|
||||||
|
|
||||||
new App();
|
new App();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContainerMetadata selectContainer() {
|
|
||||||
// System.out.println("Found containers:");
|
private boolean selectMode() throws MikrotikApiException {
|
||||||
// var i = 0;
|
var mode = console.select("What do you want to do?", List.of("Backup containers", "Restore backup", "Exit application"));
|
||||||
// for (var container : containers) {
|
if (mode == 0) {
|
||||||
// System.out.println(String.valueOf(++i) + ' ' + container);
|
selectWhatToBackup();
|
||||||
// }
|
} else if (mode == 1) {
|
||||||
// do {
|
selectWhatToRestore();
|
||||||
//
|
} else {
|
||||||
// var index = -1;
|
return false;
|
||||||
//
|
}
|
||||||
// if (containers.size() > 1) {
|
return true;
|
||||||
// do {
|
|
||||||
// index = -1;
|
|
||||||
// System.out.print("Select container you want to backup:");
|
|
||||||
// try {
|
|
||||||
// index = Integer.parseInt(cin()) - 1;
|
|
||||||
// } catch (NumberFormatException e) {
|
|
||||||
// SystemLogger.error("Invalid number", LOG_CONTEXT, e);
|
|
||||||
// }
|
|
||||||
// } while (index >= containers.size() || index < 0);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (containers.isEmpty()) {
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var container = containers.get(index);
|
|
||||||
// System.out.print("Do you really want to backup container " + container + "? [y/n]");
|
|
||||||
//
|
|
||||||
// if (cin().equals("y")) {
|
|
||||||
// return container;
|
|
||||||
// }
|
|
||||||
// } while (true);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public App() throws IOException, MikrotikApiException, InterruptedException {
|
private void selectWhatToRestore() {
|
||||||
// try {
|
//TODO
|
||||||
// console = new Console();
|
|
||||||
// auth();
|
|
||||||
// var mode = console.select("What do you want to do?", List.of("Backup containers", "Restore backup", "Exit application"));
|
|
||||||
// if(mode == 0){
|
|
||||||
// selectWhatToBackup();
|
|
||||||
// }else if(mode == 1){
|
|
||||||
// selectWhatToRestore();
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// containers = deviceContext.getContainers();
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// ContainerMetadata container;
|
|
||||||
// do {
|
|
||||||
// container = selectContainer();
|
|
||||||
// if (container == null) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!container.isRunning()) {
|
|
||||||
// SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT);
|
|
||||||
// SystemLogger.message("Starting container " + container, LOG_CONTEXT);
|
|
||||||
// deviceContext.getApiConnection().execute("/container/start .id=" + container.getId());
|
|
||||||
// try {
|
|
||||||
// Thread.sleep(10000L);
|
|
||||||
// } catch (InterruptedException e) {
|
|
||||||
// throw new RuntimeException(e);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// containers = deviceContext.getContainers();
|
|
||||||
// var id = container.getId();
|
|
||||||
// container = containers.stream().filter(containerMetadata -> containerMetadata.getId().equals(id)).findFirst().orElse(null);
|
|
||||||
// if (container == null || !container.isRunning()) {
|
|
||||||
// SystemLogger.error("Something went wrong. Unable to start container.", LOG_CONTEXT);
|
|
||||||
// pause();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } while (container == null);
|
|
||||||
//
|
|
||||||
// SystemLogger.warning("/!\\ We are going to make a backup. You have to stop all services in this container to prevent data corruption.", LOG_CONTEXT);
|
|
||||||
// System.out.print("Type yes to continue or Ctrl+C to abort: ");
|
|
||||||
// while (!"yes".equals(cin())) ;
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// var layerFile = new File(container.getGuid());
|
|
||||||
// var outputFile = new File("backup_of_" + container.getComment() + ".tar");
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// var files = findFiles(container);
|
|
||||||
// downloadFiles(container, files, layerFile);
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// SystemLogger.message("Building docker image " + outputFile.getName(), LOG_CONTEXT);
|
|
||||||
// var builder = new DockerImageBuilder(container);
|
|
||||||
// builder.build(layerFile, outputFile);
|
|
||||||
// SystemLogger.message("Backup is done ", LOG_CONTEXT);
|
|
||||||
// pause();
|
|
||||||
//
|
|
||||||
// } finally {
|
|
||||||
// if (reader != null) {
|
|
||||||
// reader.close();
|
|
||||||
// }
|
|
||||||
// if (deviceContext != null) {
|
|
||||||
// deviceContext.close();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// openSFTP();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadFiles(ContainerMetadata container, List<FileMetadata> files, File outputFile) throws IOException {
|
private void selectWhatToBackup() throws MikrotikApiException {
|
||||||
|
updateContainers();
|
||||||
|
var items = new ArrayList<>(containers.stream().map(ContainerMetadata::toString).toList());
|
||||||
|
items.add("Exit");
|
||||||
|
var selected = console.select("Select container you want to backup", items);
|
||||||
|
if (selected >= containers.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
backupContainer(containers.get(selected));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void backupContainer(ContainerMetadata container) {
|
||||||
|
startContainerIfNotRunning(container);
|
||||||
|
container = validate(container);
|
||||||
|
if (!console.confirm("/!\\ We are going to make a backup. You have to stop all services in selected container to prevent data corruption.")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var layerFile = new File(container.getGuid());
|
||||||
|
|
||||||
|
var suggestion = "backup_of_" + container.getComment();
|
||||||
|
var backupName = console.prompt("Enter name of backup [" + suggestion + "]", true);
|
||||||
|
if (backupName.isEmpty()) {
|
||||||
|
backupName = suggestion;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputFile = new File(backupName + ".tar.gz");
|
||||||
|
|
||||||
|
|
||||||
|
List<FileMetadata> files;
|
||||||
|
try {
|
||||||
|
files = loadContainerFiletree(container);
|
||||||
|
} catch (IOException | InterruptedException e) {
|
||||||
|
throw new RuntimeException("Error loading container file tree", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopContainer(container);
|
||||||
|
container = validate(container);
|
||||||
SystemLogger.message("Building image layer from remote files", LOG_CONTEXT);
|
SystemLogger.message("Building image layer from remote files", LOG_CONTEXT);
|
||||||
var sftp = deviceContext.getSftpClient();
|
createSystemLayer(container, files, layerFile);
|
||||||
try (var outputStream = new FileOutputStream(outputFile)) {
|
SystemLogger.message("Download of " + files.size() + " has been completed", LOG_CONTEXT);
|
||||||
try (var tar = new TarArchiveOutputStream(outputStream)) {
|
|
||||||
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
SystemLogger.message("Building docker image " + outputFile.getName(), LOG_CONTEXT);
|
||||||
var status = new DownloadStatus("Downloading", files.size());
|
var builder = new DockerImageBuilder(container);
|
||||||
for (var file : files) {
|
try {
|
||||||
try {
|
builder.build(layerFile, outputFile);
|
||||||
if (file.isDirectory()) {
|
} catch (IOException e) {
|
||||||
var entry = new TarArchiveEntry(file.getPath(), TarConstants.LF_DIR);
|
throw new RuntimeException(e);
|
||||||
entry.setGroupId(file.getGroupId());
|
} finally {
|
||||||
entry.setUserId(file.getUserId());
|
layerFile.delete();
|
||||||
entry.setMode(file.getMode());
|
}
|
||||||
tar.putArchiveEntry(entry);
|
SystemLogger.message("Backup is done ", LOG_CONTEXT);
|
||||||
tar.closeArchiveEntry();
|
console.pause();
|
||||||
status.addCopiedFile(0);
|
|
||||||
} else if (file.isSymlink()) {
|
}
|
||||||
var entry = new TarArchiveEntry(file.getPath(), TarConstants.LF_SYMLINK);
|
|
||||||
entry.setLinkName(file.getTarget());
|
private void stopContainer(ContainerMetadata container) {
|
||||||
tar.putArchiveEntry(entry);
|
SystemLogger.message("Stopping container " + container, LOG_CONTEXT);
|
||||||
tar.closeArchiveEntry();
|
try {
|
||||||
status.addCopiedFile(0);
|
deviceContext.getApiConnection().execute("/container/stop .id=" + container.getId());
|
||||||
} else {
|
Thread.sleep(10000L);
|
||||||
var entry = new TarArchiveEntry(file.getPath());
|
} catch (MikrotikApiException | InterruptedException e) {
|
||||||
entry.setGroupId(file.getGroupId());
|
throw new RuntimeException("Error stopping container", e);
|
||||||
entry.setUserId(file.getUserId());
|
}
|
||||||
entry.setMode(file.getMode());
|
|
||||||
var remotePath = "/" + container.getGuid() + file.getPath();
|
}
|
||||||
var size = sftp.size(remotePath);
|
|
||||||
entry.setSize(size);
|
private void startContainerIfNotRunning(ContainerMetadata container) {
|
||||||
tar.putArchiveEntry(entry);
|
try {
|
||||||
sftp.get(remotePath, new SFTPFileStream(size, tar));
|
if (!container.isRunning()) {
|
||||||
tar.closeArchiveEntry();
|
SystemLogger.warning("The selected container is not running. There is a limitation that requires the container to be running.", LOG_CONTEXT);
|
||||||
status.addCopiedFile(size);
|
console.pause();
|
||||||
}
|
SystemLogger.message("Starting container " + container, LOG_CONTEXT);
|
||||||
} catch (IOException e) {
|
deviceContext.getApiConnection().execute("/container/start .id=" + container.getId());
|
||||||
status.clear();
|
Thread.sleep(10000L);
|
||||||
SystemLogger.error("Unable to copy file " + file.getPath(), LOG_CONTEXT, e);
|
updateContainers();
|
||||||
console.pause();
|
var id = container.getId();
|
||||||
}
|
container = containers.stream().filter(containerMetadata -> containerMetadata.getId().equals(id)).findFirst().orElse(null);
|
||||||
|
if (container == null || !container.isRunning()) {
|
||||||
|
throw new RuntimeException("Something went wrong. Unable to start container.");
|
||||||
}
|
}
|
||||||
tar.finish();
|
|
||||||
status.finish();
|
|
||||||
SystemLogger.message("Download of " + files.size() + " has been completed", LOG_CONTEXT);
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<FileMetadata> findFiles(ContainerMetadata container) throws IOException, InterruptedException {
|
private ContainerMetadata validate(ContainerMetadata container) {
|
||||||
|
var found = containers.stream().filter(containerMetadata -> containerMetadata.getGuid().equals(container.getGuid())).findFirst();
|
||||||
|
if (found.isEmpty()) {
|
||||||
|
throw new RuntimeException("Failed to find container with guid: " + container.getGuid());
|
||||||
|
}
|
||||||
|
return found.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateContainers() throws MikrotikApiException {
|
||||||
|
containers = deviceContext.getContainers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSystemLayer(ContainerMetadata container, List<FileMetadata> files, File outputFile) {
|
||||||
|
try {
|
||||||
|
var sftp = deviceContext.getSftpClient();
|
||||||
|
try (var outputStream = new FileOutputStream(outputFile)) {
|
||||||
|
try (var tar = new TarArchiveOutputStream(outputStream)) {
|
||||||
|
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
||||||
|
var status = new DownloadStatus("Downloading", files.size());
|
||||||
|
for (var file : files) {
|
||||||
|
try {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
var entry = new TarArchiveEntry(file.getPath(), TarConstants.LF_DIR);
|
||||||
|
entry.setGroupId(file.getGroupId());
|
||||||
|
entry.setUserId(file.getUserId());
|
||||||
|
entry.setMode(file.getMode());
|
||||||
|
tar.putArchiveEntry(entry);
|
||||||
|
tar.closeArchiveEntry();
|
||||||
|
status.addCopiedFile(0);
|
||||||
|
} else if (file.isSymlink()) {
|
||||||
|
var entry = new TarArchiveEntry(file.getPath(), TarConstants.LF_SYMLINK);
|
||||||
|
entry.setLinkName(file.getTarget());
|
||||||
|
tar.putArchiveEntry(entry);
|
||||||
|
tar.closeArchiveEntry();
|
||||||
|
status.addCopiedFile(0);
|
||||||
|
} else {
|
||||||
|
var entry = new TarArchiveEntry(file.getPath());
|
||||||
|
entry.setGroupId(file.getGroupId());
|
||||||
|
entry.setUserId(file.getUserId());
|
||||||
|
entry.setMode(file.getMode());
|
||||||
|
var remotePath = "/" + container.getGuid() + file.getPath();
|
||||||
|
var size = sftp.size(remotePath);
|
||||||
|
entry.setSize(size);
|
||||||
|
tar.putArchiveEntry(entry);
|
||||||
|
sftp.get(remotePath, new SFTPFileStream(size, tar));
|
||||||
|
tar.closeArchiveEntry();
|
||||||
|
status.addCopiedFile(size);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
status.clear();
|
||||||
|
SystemLogger.error("Unable to copy file " + file.getPath(), LOG_CONTEXT, e);
|
||||||
|
console.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tar.finish();
|
||||||
|
status.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Error creating layer image", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FileMetadata> loadContainerFiletree(ContainerMetadata container) throws
|
||||||
|
IOException, InterruptedException {
|
||||||
SystemLogger.message("Reading container filesystem metadata...", LOG_CONTEXT);
|
SystemLogger.message("Reading container filesystem metadata...", LOG_CONTEXT);
|
||||||
var systemDirs = List.of("/sys/", "/proc/", "/dev/", "/mnt/", "/run/", "/tmp/");
|
var systemDirs = List.of("/sys/", "/proc/", "/dev/", "/mnt/", "/run/", "/tmp/");
|
||||||
var filterFiles = List.of("/.type");
|
var filterFiles = List.of("/.type");
|
||||||
|
|
@ -190,43 +228,28 @@ public class App {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private DeviceContext deviceContext;
|
|
||||||
|
|
||||||
private void auth() {
|
private void auth() {
|
||||||
do {
|
do {
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
//
|
var host = console.prompt("Enter remote host", false);
|
||||||
// System.out.print("Enter remote host:");
|
var username = console.prompt("Enter username", false);
|
||||||
// var host = cin();
|
var password = console.prompt("Enter password", false);
|
||||||
// if (host.trim().isEmpty()) {
|
|
||||||
// throw new RuntimeException("Remote host is empty");
|
console.clear();
|
||||||
// }
|
|
||||||
//
|
deviceContext = new DeviceContext(host, username, password);
|
||||||
// System.out.print("Enter username:");
|
deviceContext.getApiConnection();
|
||||||
// var username = cin();
|
} catch (Exception e) {
|
||||||
// if (username.trim().isEmpty()) {
|
SystemLogger.error("Unable to connect", LOG_CONTEXT, e);
|
||||||
// throw new RuntimeException("Username is empty");
|
if (deviceContext != null) {
|
||||||
// }
|
deviceContext.close();
|
||||||
// System.out.print("Enter password:");
|
}
|
||||||
// var password = cin();
|
deviceContext = null;
|
||||||
// if (password.trim().isEmpty()) {
|
}
|
||||||
// throw new RuntimeException("Password is empty");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// deviceContext = new DeviceContext(host, username, password);
|
|
||||||
// deviceContext.getApiConnection();
|
|
||||||
// } catch (Exception e) {
|
|
||||||
// SystemLogger.error("Unable to connect", LOG_CONTEXT, e);
|
|
||||||
// if (deviceContext != null) {
|
|
||||||
// deviceContext.close();
|
|
||||||
// }
|
|
||||||
// deviceContext = null;
|
|
||||||
// }
|
|
||||||
} while (deviceContext == null);
|
} while (deviceContext == null);
|
||||||
|
|
||||||
SystemLogger.message("Connected", LOG_CONTEXT);
|
SystemLogger.message("Connected", LOG_CONTEXT);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -206,11 +206,15 @@ public class DeviceContext implements Closeable {
|
||||||
return sshClient;
|
return sshClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiConnection getApiConnection() throws MikrotikApiException {
|
public ApiConnection getApiConnection() {
|
||||||
if (apiConnection == null) {
|
if (apiConnection == null) {
|
||||||
SystemLogger.message("Initializing api connection", LOG_CONTEXT);
|
SystemLogger.message("Initializing api connection", LOG_CONTEXT);
|
||||||
apiConnection = ApiConnection.connect(host);
|
try {
|
||||||
apiConnection.login(username, password);
|
apiConnection = ApiConnection.connect(host);
|
||||||
|
apiConnection.login(username, password);
|
||||||
|
} catch (MikrotikApiException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return apiConnection;
|
return apiConnection;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import java.nio.file.Files;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
|
||||||
public class DockerImageBuilder {
|
public class DockerImageBuilder {
|
||||||
private final MessageDigest digest;
|
private final MessageDigest digest;
|
||||||
|
|
@ -42,7 +43,7 @@ public class DockerImageBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void build(File layerFile, File outputFile) throws IOException {
|
public void build(File layerFile, File outputFile) throws IOException {
|
||||||
try (var gzip = (new FileOutputStream(outputFile))) {
|
try (var gzip = new GZIPOutputStream(new FileOutputStream(outputFile))) {
|
||||||
try (var tar = new TarArchiveOutputStream(gzip)) {
|
try (var tar = new TarArchiveOutputStream(gzip)) {
|
||||||
var layer = addLayer(layerFile, tar);
|
var layer = addLayer(layerFile, tar);
|
||||||
var repos = new JSONObject();
|
var repos = new JSONObject();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue