App-FuguVM
view release on metacpan or search on metacpan
lib/App/FuguVM/CLI.pm view on Meta::CPAN
# $self->_require_port($vm, $directive):
# Return the resolved port of the guest, or undef with a
# diagnostic. A stopped guest with a directive of 'auto' has no
# port, and a connection with an undef port would reach the
# default port of the protocol on the host itself.
sub _require_port ( $self, $vm, $directive )
{
my $port = $directive eq 'ssh_port' ? $vm->ssh_port : $vm->console_port;
return $port if defined $port;
$self->{log}->error( "VM '$self->{vm_name}' has no $directive now."
. " Run 'fuguvm up' first." );
return;
}
# $self->_require_running($vm):
# Return 1 while the guest runs. Log one line and return 0
# otherwise, because a clear message beats a "Failed to connect"
# from libssh2.
sub _require_running ( $self, $vm )
{
return 1 if $vm->is_running;
$self->{log}->error( "VM '$self->{vm_name}' does not run."
. " Run 'fuguvm up' first." );
return 0;
}
# $self->_require_remote($vm, $directive):
# Return the App::FuguVM::Remote object of the guest, or undef.
# The guest must run, and the port of $directive must resolve.
# _require_running and _require_port each log the reason.
sub _require_remote ( $self, $vm, $directive )
{
return if !$self->_require_running($vm);
my $port = $self->_require_port( $vm, $directive );
return if !defined $port;
# The connection uses the SSH agent for authentication. Connect
# to the IPv4 address that the forwarded port binds to. A name
# such as 'localhost' resolves to ::1 first on a dual-stack
# host, and QEMU does not listen there.
return App::FuguVM::Remote->new(
host => $vm->connect_address,
port => $port,
);
}
# Open an SSH session into the VM, or run one argument vector on it
sub cmd_ssh ( $self, $cli, @args )
{
my $vm = $self->_load_vm or return $self->{load_exit};
my $remote = $self->_require_remote( $vm, 'ssh_port' );
return EXIT_ERROR if !defined $remote;
if (@args) {
my $result = $remote->run(@args);
print $result->{stdout} if $result->{stdout};
print STDERR $result->{stderr} if $result->{stderr};
return $result->{exit_code};
}
else {
return $remote->interactive;
}
}
# Copy a local file or a local directory into the guest
sub cmd_put ( $self, $cli, @args )
{
my ( $local, $remote_path, @extra ) = @args;
if ( !defined $local || !defined $remote_path || @extra ) {
$self->{log}->error(
'Usage: fuguvm put [--mode=<octal>] <local> <remote>');
return EXIT_INVALID_ARGS;
}
if ( index( $remote_path, '/' ) != 0 ) {
$self->{log}
->error("The remote path is not absolute: $remote_path");
return EXIT_INVALID_ARGS;
}
my $mode = $cli->option('mode');
if ( defined $mode && $mode !~ /^[0-7]{3,4}$/ ) {
$self->{log}->error(
"Invalid --mode value: $mode (3 or 4 octal digits)");
return EXIT_INVALID_ARGS;
}
if ( -l $local || ( !-f $local && !-d $local ) ) {
$self->{log}
->error("Not a regular file or a directory: $local");
return EXIT_INVALID_ARGS;
}
my $vm = $self->_load_vm or return $self->{load_exit};
my $remote = $self->_require_remote( $vm, 'ssh_port' );
return EXIT_ERROR if !defined $remote;
return EXIT_ERROR
if !$remote->put( $local, $remote_path,
defined $mode ? ( mode => oct($mode) ) : () );
return EXIT_SUCCESS;
}
# Copy one guest file to the host
sub cmd_get ( $self, $cli, @args )
{
my ( $remote_path, $local, @extra ) = @args;
if ( !defined $remote_path || !defined $local || @extra ) {
$self->{log}->error('Usage: fuguvm get <remote> <local>');
return EXIT_INVALID_ARGS;
}
if ( index( $remote_path, '/' ) != 0 ) {
$self->{log}
->error("The remote path is not absolute: $remote_path");
return EXIT_INVALID_ARGS;
}
if ( -d $local ) {
lib/App/FuguVM/CLI.pm view on Meta::CPAN
{
my $vm = $self->_load_vm or return $self->{load_exit};
my $state = $self->{state};
if ( $vm->is_running ) {
$self->{log}->error("Stop the VM before restoring a snapshot");
return EXIT_VM_RUNNING;
}
my $key = $self->_current_cache_key($cache)
or return EXIT_ERROR;
my $found = $self->_snapshot_found( $cache, $key, $name )
or return EXIT_SNAPSHOT_NOT_FOUND;
# Disk::create returns early on an existing path. Without this
# removal, a restore would report success and change nothing.
my $disk_path = $state->disk_path;
if ( -f $disk_path ) {
unlink $disk_path or do {
$self->{log}->error("Cannot remove $disk_path: $!");
return EXIT_ERROR;
};
}
my $vm_config = $self->{config}->load_vm( $self->{vm_name} );
my $disk = App::FuguVM::Disk->new( $self->{config}->state_dir );
my $created =
$disk->create( $vm_config->{name}, undef, $found->{path} );
if ( !defined $created ) {
$self->{log}->error("Failed to overlay snapshot '$name'");
return EXIT_ERROR;
}
# Reseed what the disk embodies. The next 'fuguvm up' reconciles
# a checkout whose SSH key differs from the saved one.
my $meta = $found->{meta};
$state->mark_installed( $vm_config->{arch} );
$state->set_root_password( $meta->{root_password} )
if defined $meta->{root_password};
$state->mark_ssh_key_installed( $meta->{installed_ssh_pubkey} )
if defined $meta->{installed_ssh_pubkey};
$state->data->{cached_from} = "$key/$name";
$state->save;
$self->{log}->info("Restored snapshot '$name' of $key");
return EXIT_SUCCESS;
}
sub _snapshot_list ( $self, $cli, $cache, @args )
{
my $names = $cli->option('names') // 0;
my $key = $self->_current_cache_key($cache)
or return EXIT_ERROR;
my $snapshots = $cache->snapshot_list($key);
# --names writes bare names to stdout, where a shell can read
# them. The human listing goes through the logger, which writes
# to stderr and prefixes every line.
if ($names) {
say $_->{name} for @$snapshots;
return EXIT_SUCCESS;
}
if ( !@$snapshots ) {
$self->{log}->info("No snapshots for $key");
return EXIT_SUCCESS;
}
for my $snapshot (@$snapshots) {
my $created =
defined $snapshot->{created_at}
? scalar localtime $snapshot->{created_at}
: 'unknown';
$self->{log}->info(
sprintf( ' - %s %s %s',
$snapshot->{name},
_format_size( $snapshot->{size} ),
$created ) );
}
return EXIT_SUCCESS;
}
sub _snapshot_remove ( $self, $cache, $name )
{
my $key = $self->_current_cache_key($cache)
or return EXIT_ERROR;
$self->_snapshot_found( $cache, $key, $name )
or return EXIT_SNAPSHOT_NOT_FOUND;
if ( !$cache->snapshot_remove( $key, $name ) ) {
return EXIT_ERROR;
}
$self->{log}->info("Removed snapshot '$name'");
return EXIT_SUCCESS;
}
# $self->_snapshot_found($cache, $key, $name):
# Look a snapshot up. Diagnose a miss, once for every caller.
sub _snapshot_found ( $self, $cache, $key, $name )
{
my $found = $cache->snapshot_lookup( $key, $name );
$self->{log}->error("No snapshot '$name' for $key")
if !defined $found;
return $found;
}
# $self->_disk_cache_key($cache):
# Return the cache entry that backs the working disk, directly
# with its base image or through a snapshot of it.
sub _disk_cache_key ( $self, $cache )
{
my $vm_config = $self->{config}->load_vm( $self->{vm_name} );
return if !defined $vm_config;
( run in 1.246 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )