274 lines
7.4 KiB
Dart
274 lines
7.4 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:podman/podman.dart';
|
|
|
|
Future<void> main(List<String> args) async {
|
|
if (args.isEmpty || args.contains('--help') || args.contains('-h')) {
|
|
_printUsage();
|
|
return;
|
|
}
|
|
|
|
final command = args.first;
|
|
final remaining = args.sublist(1);
|
|
|
|
switch (command) {
|
|
case 'checkpoint':
|
|
await _runCheckpoint(remaining);
|
|
return;
|
|
case 'export':
|
|
await _runExport(remaining);
|
|
return;
|
|
case 'restore':
|
|
await _runRestore(remaining);
|
|
return;
|
|
case 'restore-archive':
|
|
await _runRestoreArchive(remaining);
|
|
return;
|
|
default:
|
|
stderr.writeln('Unknown command: $command');
|
|
_printUsage();
|
|
exitCode = 64;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Future<void> _runCheckpoint(List<String> args) async {
|
|
final positional = args
|
|
.where((arg) => !arg.startsWith('--'))
|
|
.toList(growable: false);
|
|
if (positional.length != 1) {
|
|
stderr.writeln('checkpoint requires exactly one <container> argument.');
|
|
_printUsage();
|
|
exitCode = 64;
|
|
return;
|
|
}
|
|
|
|
final container = positional.first;
|
|
final options = _checkpointOptionsFromArgs(args);
|
|
|
|
final client = PodmanClient();
|
|
try {
|
|
final report = await client.checkpointContainer(
|
|
container,
|
|
options: options,
|
|
);
|
|
print('Checkpoint completed for "$container"');
|
|
print(' id: ${report.id}');
|
|
print(' runtime duration: ${report.runtimeDuration}');
|
|
print(' criu stats keys: ${report.criuStatistics.keys.toList()..sort()}');
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
|
|
Future<void> _runExport(List<String> args) async {
|
|
final positional = args
|
|
.where((arg) => !arg.startsWith('--'))
|
|
.toList(growable: false);
|
|
if (positional.length != 2) {
|
|
stderr.writeln('export requires <container> <archive-path>.');
|
|
_printUsage();
|
|
exitCode = 64;
|
|
return;
|
|
}
|
|
|
|
final container = positional.first;
|
|
final archivePath = positional[1];
|
|
final options = _checkpointOptionsFromArgs(args);
|
|
|
|
final client = PodmanClient();
|
|
try {
|
|
final archive = await client.exportContainerCheckpoint(
|
|
container,
|
|
options: options,
|
|
);
|
|
await File(archivePath).writeAsBytes(archive);
|
|
print(
|
|
'Exported checkpoint for "$container" to $archivePath (${archive.length} bytes).',
|
|
);
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
|
|
Future<void> _runRestore(List<String> args) async {
|
|
final positional = args
|
|
.where((arg) => !arg.startsWith('--'))
|
|
.toList(growable: false);
|
|
if (positional.length != 1) {
|
|
stderr.writeln('restore requires exactly one <container> argument.');
|
|
_printUsage();
|
|
exitCode = 64;
|
|
return;
|
|
}
|
|
|
|
final container = positional.first;
|
|
final options = _restoreOptionsFromArgs(args);
|
|
|
|
final client = PodmanClient();
|
|
try {
|
|
final report = await client.restoreContainer(container, options: options);
|
|
print('Restore completed for "$container"');
|
|
print(' id: ${report.id}');
|
|
print(' runtime duration: ${report.runtimeDuration}');
|
|
print(' criu stats keys: ${report.criuStatistics.keys.toList()..sort()}');
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
|
|
Future<void> _runRestoreArchive(List<String> args) async {
|
|
final positional = args
|
|
.where((arg) => !arg.startsWith('--'))
|
|
.toList(growable: false);
|
|
if (positional.length != 1) {
|
|
stderr.writeln(
|
|
'restore-archive requires exactly one <archive-path> argument.',
|
|
);
|
|
_printUsage();
|
|
exitCode = 64;
|
|
return;
|
|
}
|
|
|
|
final archivePath = positional.first;
|
|
final archiveFile = File(archivePath);
|
|
if (!await archiveFile.exists()) {
|
|
stderr.writeln('Archive file not found: $archivePath');
|
|
exitCode = 66;
|
|
return;
|
|
}
|
|
|
|
final importName = _readOption(args, 'import-name') ?? 'import';
|
|
final options = _restoreOptionsFromArgs(args);
|
|
final bytes = await archiveFile.readAsBytes();
|
|
|
|
final client = PodmanClient();
|
|
try {
|
|
final report = await client.restoreContainerFromArchive(
|
|
bytes,
|
|
importName: importName,
|
|
options: options,
|
|
);
|
|
|
|
print('Archive restore completed from $archivePath');
|
|
print(' id: ${report.id}');
|
|
print(' runtime duration: ${report.runtimeDuration}');
|
|
print(' criu stats keys: ${report.criuStatistics.keys.toList()..sort()}');
|
|
} finally {
|
|
await client.close();
|
|
}
|
|
}
|
|
|
|
ContainerCheckpointOptions _checkpointOptionsFromArgs(List<String> args) {
|
|
return ContainerCheckpointOptions(
|
|
keep: args.contains('--keep'),
|
|
leaveRunning: args.contains('--leave-running'),
|
|
tcpEstablished: args.contains('--tcp-established'),
|
|
ignoreRootFs: args.contains('--ignore-rootfs'),
|
|
ignoreVolumes: args.contains('--ignore-volumes'),
|
|
printStats: args.contains('--print-stats'),
|
|
preCheckpoint: args.contains('--pre-checkpoint'),
|
|
withPrevious: args.contains('--with-previous'),
|
|
fileLocks: args.contains('--file-locks'),
|
|
createImage: _readOption(args, 'create-image'),
|
|
);
|
|
}
|
|
|
|
ContainerRestoreOptions _restoreOptionsFromArgs(List<String> args) {
|
|
final publishPorts = _readMultiOptions(args, 'publish-port');
|
|
|
|
return ContainerRestoreOptions(
|
|
name: _readOption(args, 'name'),
|
|
keep: args.contains('--keep'),
|
|
tcpEstablished: args.contains('--tcp-established'),
|
|
tcpClose: args.contains('--tcp-close'),
|
|
ignoreRootFs: args.contains('--ignore-rootfs'),
|
|
ignoreVolumes: args.contains('--ignore-volumes'),
|
|
ignoreStaticIp: args.contains('--ignore-static-ip'),
|
|
ignoreStaticMac: args.contains('--ignore-static-mac'),
|
|
printStats: args.contains('--print-stats'),
|
|
fileLocks: args.contains('--file-locks'),
|
|
publishPorts: publishPorts,
|
|
pod: _readOption(args, 'pod'),
|
|
);
|
|
}
|
|
|
|
String? _readOption(List<String> args, String name) {
|
|
final prefix = '--$name=';
|
|
for (final arg in args) {
|
|
if (arg.startsWith(prefix)) {
|
|
return arg.substring(prefix.length);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
List<String> _readMultiOptions(List<String> args, String name) {
|
|
final prefix = '--$name=';
|
|
final values = <String>[];
|
|
|
|
for (final arg in args) {
|
|
if (arg.startsWith(prefix)) {
|
|
final value = arg.substring(prefix.length);
|
|
if (value.isNotEmpty) {
|
|
values.add(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
void _printUsage() {
|
|
print('''
|
|
Checkpoint/restore example
|
|
|
|
Usage:
|
|
dart run example/checkpoint_restore_example.dart <command> [args] [options]
|
|
|
|
Commands:
|
|
checkpoint <container>
|
|
Create an in-place checkpoint and return report metadata.
|
|
|
|
export <container> <archive-path>
|
|
Export checkpoint archive bytes to the provided file path.
|
|
|
|
restore <container>
|
|
Restore a container from an existing checkpoint state/image.
|
|
|
|
restore-archive <archive-path>
|
|
Restore from a checkpoint archive tarball.
|
|
|
|
Shared options:
|
|
--keep
|
|
--tcp-established
|
|
--print-stats
|
|
--file-locks
|
|
|
|
Checkpoint/export options:
|
|
--leave-running
|
|
--ignore-rootfs
|
|
--ignore-volumes
|
|
--pre-checkpoint
|
|
--with-previous
|
|
--create-image=<name>
|
|
|
|
Restore/restore-archive options:
|
|
--name=<name>
|
|
--tcp-close
|
|
--ignore-rootfs
|
|
--ignore-volumes
|
|
--ignore-static-ip
|
|
--ignore-static-mac
|
|
--pod=<pod-name>
|
|
--publish-port=<mapping> (repeatable)
|
|
|
|
restore-archive-only options:
|
|
--import-name=<name> Defaults to "import"
|
|
|
|
Examples:
|
|
dart run example/checkpoint_restore_example.dart checkpoint gw-service --print-stats
|
|
dart run example/checkpoint_restore_example.dart export gw-service ./gw-service-checkpoint.tar
|
|
dart run example/checkpoint_restore_example.dart restore-archive ./gw-service-checkpoint.tar --name=gw-restored
|
|
''');
|
|
}
|