Skip to content

Instantly share code, notes, and snippets.

@dontlaugh
Last active October 3, 2023 23:13
Show Gist options
  • Save dontlaugh/942a75e3e848f8cfac2517312a30e614 to your computer and use it in GitHub Desktop.
Save dontlaugh/942a75e3e848f8cfac2517312a30e614 to your computer and use it in GitHub Desktop.
Annotated Raku example: wrapping SSH/LXD commands

Explanations

In Raku, Lists can be constructed several ways.

# A list of strings: "one", "two", "three"
my @x = <one two three>;
# Using << >> allows interpolating variables
my $num = "six";
my @y = << four five $six >>;

Classes get a new method automatically, where public attributes can be set in a key => "value" style.

class Person {
  has Str $.name;
}

my $p = Person.new(name => "Liz");

The object model allows overriding a TWEAK, which runs after the new method, and is convenient for setting up extra state while still taking advantage of automatic new.

TWEAK should almost always be a submethod (as opposed to a regular method) so that subclasses don't inherit it.

Examples of overidding TWEAK are in the class bodies below.

By the way: you don't need parens when calling subroutines.

sub foo($x, $y) {
  say "x: $x and y: $y";
}

foo(123, 456);
foo "blah", "blargh";

There's always more than one way to do it! Embrace and master the chaos.

You can pass blocks of code to subroutines, and they're indicated with the & sigil.

sub wrap-call(&b) {
  say "wrappin!";
  &b();
}

wrap-call({ say "nice" })
# prints:
# wrappin!
# nice

You can match lots of stuff with the ~~ smart match operator. A string against a regex literal for instance.

my $x = "Rolling Stones";
$x ~~ /Stone/;
if ?$/ {     # equivalent to $/.Bool
  say "I matched against the string";
}

The $/ is the "Match Variable", which is set to the result of the previous match. The ? prefix operator coerces it to a boolean.

#!/usr/bin/env raku
my $ssh-dir = "/home/coleman/.ssh";
my $raku-infra-key = "$ssh-dir/raku.infra";
my $envs-key = "$ssh-dir/envs";
my $personal-key = "$ssh-dir/id_ed25519";
class SSHCommand {
has Str $.host;
has Str $.user;
has Str $.private-key;
has @!base-command;
submethod TWEAK {
# build our ssh base command from args to new
my @cmd = <ssh -t>;
@cmd.append(<< -i $!private-key >>) if $!private-key;
if $!user {
@cmd.push("$!user@$!host");
} else { @cmd.push($!host) }
@!base-command = @cmd;
}
method run(@*cmd) { run(|@!base-command, |@*cmd) }
}
class LXCBase {
has Str $.remote;
has Str $.project = "default";
has Str $.instance;
has @!base-command;
submethod TWEAK {
my @cmd = <<lxc exec --project $!project "$!remote:$!instance" -- >>;
@!base-command = @cmd;
}
method exec(*@cmd) { run(|@!base-command, |@cmd) }
}
sub input($prompt = "> ", $msg = "" --> Str) {
say $msg;
prompt($prompt);
}
sub system-updates() {
my $d = SSHCommand.new(host => '10.111.0.2');
# Wrap a series of commands in a Yes/No prompt to let the user skip some commands.
YN "Update base system packages (over SSH)?", {
$d.run(<sudo xbps-install -u xbps>);
$d.run(<sudo xbps-install -Su>);
}
YN "Reboot base system?", {
$d.run(<sudo reboot>);
}
YN "Update radicale (LXD container)?", {
my $radicale = LXCBase.new(remote => "dominus", project => "default", instance => "radicale");
$radicale.exec(<<sudo xbps-install -u xbps>>);
$radicale.exec(<<sudo xbps-install -Su>>);
$radicale.exec(<<sudo sv restart radicale>>);
}
YN "Update soju?", {
my $soju = LXCBase.new(remote => "dominus", project => "default", instance => "soju");
$soju.exec(<sudo xbps-install -u xbps>);
$soju.exec(<sudo xbps-install -Su>);
$soju.exec(<sudo sv restart soju>);
}
}
sub yesno($msg --> Bool) {
my $yn = input("> ", $msg);
$yn ~~ /\h*y(es)?/;
$/.Bool; # did it match? alternative: ?$/
}
sub YN($msg, &callable) { &callable() if yesno($msg) }
# Entrypoint
sub MAIN() {
system-updates();
exit 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment