podman/example/checkpoint_restore_example.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
''');
}