Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion lib/Dobby/Boxmate/App/Command.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sub boxman ($self) {
$self->app->boxman;
}

sub droplet_from_prefix ($self, $boxprefix) {
sub maybe_droplet_from_prefix ($self, $boxprefix) {
length $boxprefix
|| $self->usage->die({ pre_text => "No box prefix provided.\n\n" });

Expand All @@ -21,11 +21,90 @@ sub droplet_from_prefix ($self, $boxprefix) {
my $want_name = join q{.}, $boxprefix, $username, $boxman->box_domain;
my ($droplet) = grep {; $_->{name} eq $want_name } @$droplets;

return $droplet;
}

sub droplet_from_prefix ($self, $boxprefix) {
my $droplet = $self->maybe_droplet_from_prefix($boxprefix);

unless ($droplet) {
die "Couldn't find box for $boxprefix\n";
}

return $droplet;
}

sub print_droplet_list ($self, $droplets, $username = undef) {
require DateTime::Format::RFC3339;
require Term::ANSIColor;
require Text::Table;
require Time::Duration;

my $parser = DateTime::Format::RFC3339->new;
my $boxman = $self->boxman;

# Ugh, should sort out the ME:: table formatter for general use.
my $table = Text::Table->new(
'', # Status
'region',
' ', # Type
' ', # Default
'name',
'ip',
{ title => 'age', align => 'right' },
{ title => 'cost', align => 'right' },
{ title => 'img age', align => 'right', align_title => 'right' },
);

my $default;
if ($username) {
my ($rec) = grep {; $_->{type} eq 'CNAME' && $_->{name} eq $username }
$boxman->dobby->get_all_domain_records_for_domain($boxman->box_domain)->get;

$default = $rec->{data};
}

for my $droplet (@$droplets) {
my $name = $droplet->{name};
my $status = $droplet->{status};
my $ip = $boxman->_ip_address_for_droplet($droplet); # XXX _method
my $image = $droplet->{image};

my $created = $parser->parse_datetime($droplet->{created_at});
my $age_secs = time - $created->epoch;

my $img_created = $parser->parse_datetime($image->{created_at});
my $img_age_secs = time - $img_created->epoch;

my $cost = sprintf '%4s',
'$' . builtin::ceil($droplet->{size}{price_hourly} * $age_secs / 3600);

my $icon = ($image->{slug} && $image->{slug} =~ /^debian/) ? "\N{CYCLONE}"
: (($image->{description}//'') =~ /^Debian/) ? "\N{CYCLONE}" # Deb 11
: ($image->{name} =~ /\Afminabox/ ) ? "\N{PACKAGE}"
: "\N{BLACK QUESTION MARK ORNAMENT}";

my $default = $default && $default eq $name
? "\N{SPARKLES}"
: "\N{IDEOGRAPHIC SPACE}";

$table->add(
($status eq 'active' ? "\N{LARGE GREEN CIRCLE}" : "\N{HEAVY MINUS SIGN}"),
$droplet->{region}{slug},
"$icon\N{INVISIBLE SEPARATOR}",
"$default\N{INVISIBLE SEPARATOR}",
$name,
$ip,
Time::Duration::concise(Time::Duration::duration($age_secs, 1)),
$cost,
Time::Duration::concise(Time::Duration::duration($img_age_secs, 1)),
);
}

# This leading space is *bananas* and is here because Text::Table will think
# about LARGE GREEN CIRCLE as being one wide, but it's two.
print Term::ANSIColor::colored(['bold', 'bright_white'], qq{ $_}) for $table->title;
print qq{$_} for $table->body;
}

1;
54 changes: 52 additions & 2 deletions lib/Dobby/Boxmate/App/Command/destroy.pm
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ use utf8;

sub abstract { 'destroy a box' }

sub opt_spec {
return (
[ finder => hidden => {
default => 'by_prefix',
one_of => [
[ 'by-prefix' => 'find box by prefix of default domain' ],
[ 'by-id|I' => 'find box by Droplet id' ],
[ 'by-name|N' => 'find box by Droplet name' ],
],
} ],
[],
[ 'ip=s', 'only destroy if the public IP is this' ],
);
}

sub usage_desc {
'%c destroy %o BOXPREFIX',
}
Expand All @@ -18,9 +33,44 @@ sub validate_args ($self, $opt, $args) {

sub execute ($self, $opt, $args) {
my $boxman = $self->boxman;
my $droplet = $self->droplet_from_prefix($args->[0]);
my $locator = $args->[0];

my $method = "_find_" . $opt->finder;

my @droplets = $self->$method($opt, $locator);

if ($opt->ip) {
@droplets = grep {
$opt->ip eq $self->boxman->_ip_address_for_droplet($_) # XXX _method
} @droplets;
}

unless (@droplets) {
die "I couldn't find the box you want to destroy.\n";
}

if (@droplets > 1) {
$self->print_droplet_list(\@droplets, undef);
say "";

die "More than one box matched your criteria.\n";
}

$boxman->destroy_droplet($droplets[0], { force => 1 })->get;
}

sub _find_by_prefix ($self, $opt, $locator) {
return $self->maybe_droplet_from_prefix($locator);
}

sub _find_by_id ($self, $opt, $locator) {
$opt->ip || die "You can't destroy a box by id without --ip for safety.\n";
return $self->boxman->dobby->get_droplet_by_id($locator)->get;
}

$boxman->destroy_droplet($droplet, { force => 1 })->get;
sub _find_by_name ($self, $opt, $locator) {
my @droplets = $self->boxman->dobby->get_droplets_by_name($locator)->get;
return @droplets;
}

1;
71 changes: 2 additions & 69 deletions lib/Dobby/Boxmate/App/Command/list.pm
Original file line number Diff line number Diff line change
Expand Up @@ -58,75 +58,8 @@ sub execute ($self, $opt, $args) {
return;
}

require DateTime::Format::RFC3339;
require Term::ANSIColor;
require Text::Table;
require Time::Duration;

my $parser = DateTime::Format::RFC3339->new;

# Ugh, should sort out the ME:: table formatter for general use.
my $table = Text::Table->new(
'', # Status
'region',
' ', # Type
' ', # Default
'name',
'ip',
{ title => 'age', align => 'right' },
{ title => 'cost', align => 'right' },
{ title => 'img age', align => 'right', align_title => 'right' },
);

my $default;
unless ($opt->everything) {
my ($rec) = grep {; $_->{type} eq 'CNAME' && $_->{name} eq $username }
$boxman->dobby->get_all_domain_records_for_domain($boxman->box_domain)->get;

$default = $rec->{data};
}

for my $droplet (@$droplets) {
my $name = $droplet->{name};
my $status = $droplet->{status};
my $ip = $self->boxman->_ip_address_for_droplet($droplet); # XXX _method
my $image = $droplet->{image};

my $created = $parser->parse_datetime($droplet->{created_at});
my $age_secs = time - $created->epoch;

my $img_created = $parser->parse_datetime($image->{created_at});
my $img_age_secs = time - $img_created->epoch;

my $cost = sprintf '%4s',
'$' . builtin::ceil($droplet->{size}{price_hourly} * $age_secs / 3600);

my $icon = ($image->{slug} && $image->{slug} =~ /^debian/) ? "\N{CYCLONE}"
: (($image->{description}//'') =~ /^Debian/) ? "\N{CYCLONE}" # Deb 11
: ($image->{name} =~ /\Afminabox/ ) ? "\N{PACKAGE}"
: "\N{BLACK QUESTION MARK ORNAMENT}";

my $default = $default && $default eq $name
? "\N{SPARKLES}"
: "\N{IDEOGRAPHIC SPACE}";

$table->add(
($status eq 'active' ? "\N{LARGE GREEN CIRCLE}" : "\N{HEAVY MINUS SIGN}"),
$droplet->{region}{slug},
"$icon\N{INVISIBLE SEPARATOR}",
"$default\N{INVISIBLE SEPARATOR}",
$name,
$ip,
Time::Duration::concise(Time::Duration::duration($age_secs, 1)),
$cost,
Time::Duration::concise(Time::Duration::duration($img_age_secs, 1)),
);
}

# This leading space is *bananas* and is here because Text::Table will think
# about LARGE GREEN CIRCLE as being one wide, but it's two.
print Term::ANSIColor::colored(['bold', 'bright_white'], qq{ $_}) for $table->title;
print qq{$_} for $table->body;
$self->print_droplet_list($droplets, $username);
return;
}

1;
28 changes: 27 additions & 1 deletion lib/Dobby/Client.pm
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ sub http ($self) {
};
}

async sub json_get ($self, $path) {
async sub json_get ($self, $path, $arg=undef) {
my $undef_if_404 = $arg && $arg->{undef_if_404};

my $res = await $self->http->do_request(
method => 'GET',
uri => $self->api_base . $path,
Expand All @@ -60,6 +62,10 @@ async sub json_get ($self, $path) {
);

unless ($res->is_success) {
if ($undef_if_404 && $res->code == 404) {
return undef;
}

die "error getting $path at DigitalOcean: " . $res->as_string;
}

Expand Down Expand Up @@ -247,6 +253,7 @@ async sub _do_action_status_f ($self, $action_url) {
async sub _get_droplets ($self, $arg = {}) {
my $path = '/droplets?per_page=200';
$path .= "&tag_name=$arg->{tag}" if $arg->{tag};
$path .= "&name=$arg->{name}" if $arg->{name};

my $droplets_data = await $self->json_get($path);

Expand All @@ -272,6 +279,25 @@ async sub get_droplets_with_tag ($self, $tag) {
await $self->_get_droplets({ tag => $tag });
}

async sub get_droplet_by_id ($self, $id) {
$id =~ /\A[0-9]+\z/
|| Carp::croak("bogus id given to get_droplet_by_id; should be a string of digits");

my $path = "/droplets/$id";

my $droplet = await $self->json_get($path, { undef_if_404 => 1 });

return $droplet;
}

async sub get_droplets_by_name ($self, $name) {
length $name
|| Carp::croak("get_droplet_by_name without a name passed in");

my @droplets = await $self->_get_droplets({ name => $name });
return @droplets;
}

async sub add_droplet_to_project ($self, $droplet_id, $project_id) {
my $path = "/projects/$project_id/resources";

Expand Down
Loading