Skip to content

Commit c10e3c1

Browse files
authored
Merge pull request #2 from rjbs/bak-1170-destroy-more-ways
add new ways to destroy droplets
2 parents 61abf2e + 7e26443 commit c10e3c1

File tree

4 files changed

+161
-73
lines changed

4 files changed

+161
-73
lines changed

lib/Dobby/Boxmate/App/Command.pm

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ sub boxman ($self) {
99
$self->app->boxman;
1010
}
1111

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

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

24+
return $droplet;
25+
}
26+
27+
sub droplet_from_prefix ($self, $boxprefix) {
28+
my $droplet = $self->maybe_droplet_from_prefix($boxprefix);
29+
2430
unless ($droplet) {
2531
die "Couldn't find box for $boxprefix\n";
2632
}
2733

2834
return $droplet;
2935
}
3036

37+
sub print_droplet_list ($self, $droplets, $username = undef) {
38+
require DateTime::Format::RFC3339;
39+
require Term::ANSIColor;
40+
require Text::Table;
41+
require Time::Duration;
42+
43+
my $parser = DateTime::Format::RFC3339->new;
44+
my $boxman = $self->boxman;
45+
46+
# Ugh, should sort out the ME:: table formatter for general use.
47+
my $table = Text::Table->new(
48+
'', # Status
49+
'region',
50+
' ', # Type
51+
' ', # Default
52+
'name',
53+
'ip',
54+
{ title => 'age', align => 'right' },
55+
{ title => 'cost', align => 'right' },
56+
{ title => 'img age', align => 'right', align_title => 'right' },
57+
);
58+
59+
my $default;
60+
if ($username) {
61+
my ($rec) = grep {; $_->{type} eq 'CNAME' && $_->{name} eq $username }
62+
$boxman->dobby->get_all_domain_records_for_domain($boxman->box_domain)->get;
63+
64+
$default = $rec->{data};
65+
}
66+
67+
for my $droplet (@$droplets) {
68+
my $name = $droplet->{name};
69+
my $status = $droplet->{status};
70+
my $ip = $boxman->_ip_address_for_droplet($droplet); # XXX _method
71+
my $image = $droplet->{image};
72+
73+
my $created = $parser->parse_datetime($droplet->{created_at});
74+
my $age_secs = time - $created->epoch;
75+
76+
my $img_created = $parser->parse_datetime($image->{created_at});
77+
my $img_age_secs = time - $img_created->epoch;
78+
79+
my $cost = sprintf '%4s',
80+
'$' . builtin::ceil($droplet->{size}{price_hourly} * $age_secs / 3600);
81+
82+
my $icon = ($image->{slug} && $image->{slug} =~ /^debian/) ? "\N{CYCLONE}"
83+
: (($image->{description}//'') =~ /^Debian/) ? "\N{CYCLONE}" # Deb 11
84+
: ($image->{name} =~ /\Afminabox/ ) ? "\N{PACKAGE}"
85+
: "\N{BLACK QUESTION MARK ORNAMENT}";
86+
87+
my $default = $default && $default eq $name
88+
? "\N{SPARKLES}"
89+
: "\N{IDEOGRAPHIC SPACE}";
90+
91+
$table->add(
92+
($status eq 'active' ? "\N{LARGE GREEN CIRCLE}" : "\N{HEAVY MINUS SIGN}"),
93+
$droplet->{region}{slug},
94+
"$icon\N{INVISIBLE SEPARATOR}",
95+
"$default\N{INVISIBLE SEPARATOR}",
96+
$name,
97+
$ip,
98+
Time::Duration::concise(Time::Duration::duration($age_secs, 1)),
99+
$cost,
100+
Time::Duration::concise(Time::Duration::duration($img_age_secs, 1)),
101+
);
102+
}
103+
104+
# This leading space is *bananas* and is here because Text::Table will think
105+
# about LARGE GREEN CIRCLE as being one wide, but it's two.
106+
print Term::ANSIColor::colored(['bold', 'bright_white'], qq{ $_}) for $table->title;
107+
print qq{$_} for $table->body;
108+
}
109+
31110
1;

lib/Dobby/Boxmate/App/Command/destroy.pm

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ use utf8;
88

99
sub abstract { 'destroy a box' }
1010

11+
sub opt_spec {
12+
return (
13+
[ finder => hidden => {
14+
default => 'by_prefix',
15+
one_of => [
16+
[ 'by-prefix' => 'find box by prefix of default domain' ],
17+
[ 'by-id|I' => 'find box by Droplet id' ],
18+
[ 'by-name|N' => 'find box by Droplet name' ],
19+
],
20+
} ],
21+
[],
22+
[ 'ip=s', 'only destroy if the public IP is this' ],
23+
);
24+
}
25+
1126
sub usage_desc {
1227
'%c destroy %o BOXPREFIX',
1328
}
@@ -18,9 +33,44 @@ sub validate_args ($self, $opt, $args) {
1833

1934
sub execute ($self, $opt, $args) {
2035
my $boxman = $self->boxman;
21-
my $droplet = $self->droplet_from_prefix($args->[0]);
36+
my $locator = $args->[0];
37+
38+
my $method = "_find_" . $opt->finder;
39+
40+
my @droplets = $self->$method($opt, $locator);
41+
42+
if ($opt->ip) {
43+
@droplets = grep {
44+
$opt->ip eq $self->boxman->_ip_address_for_droplet($_) # XXX _method
45+
} @droplets;
46+
}
47+
48+
unless (@droplets) {
49+
die "I couldn't find the box you want to destroy.\n";
50+
}
51+
52+
if (@droplets > 1) {
53+
$self->print_droplet_list(\@droplets, undef);
54+
say "";
55+
56+
die "More than one box matched your criteria.\n";
57+
}
58+
59+
$boxman->destroy_droplet($droplets[0], { force => 1 })->get;
60+
}
61+
62+
sub _find_by_prefix ($self, $opt, $locator) {
63+
return $self->maybe_droplet_from_prefix($locator);
64+
}
65+
66+
sub _find_by_id ($self, $opt, $locator) {
67+
$opt->ip || die "You can't destroy a box by id without --ip for safety.\n";
68+
return $self->boxman->dobby->get_droplet_by_id($locator)->get;
69+
}
2270

23-
$boxman->destroy_droplet($droplet, { force => 1 })->get;
71+
sub _find_by_name ($self, $opt, $locator) {
72+
my @droplets = $self->boxman->dobby->get_droplets_by_name($locator)->get;
73+
return @droplets;
2474
}
2575

2676
1;

lib/Dobby/Boxmate/App/Command/list.pm

Lines changed: 2 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -58,75 +58,8 @@ sub execute ($self, $opt, $args) {
5858
return;
5959
}
6060

61-
require DateTime::Format::RFC3339;
62-
require Term::ANSIColor;
63-
require Text::Table;
64-
require Time::Duration;
65-
66-
my $parser = DateTime::Format::RFC3339->new;
67-
68-
# Ugh, should sort out the ME:: table formatter for general use.
69-
my $table = Text::Table->new(
70-
'', # Status
71-
'region',
72-
' ', # Type
73-
' ', # Default
74-
'name',
75-
'ip',
76-
{ title => 'age', align => 'right' },
77-
{ title => 'cost', align => 'right' },
78-
{ title => 'img age', align => 'right', align_title => 'right' },
79-
);
80-
81-
my $default;
82-
unless ($opt->everything) {
83-
my ($rec) = grep {; $_->{type} eq 'CNAME' && $_->{name} eq $username }
84-
$boxman->dobby->get_all_domain_records_for_domain($boxman->box_domain)->get;
85-
86-
$default = $rec->{data};
87-
}
88-
89-
for my $droplet (@$droplets) {
90-
my $name = $droplet->{name};
91-
my $status = $droplet->{status};
92-
my $ip = $self->boxman->_ip_address_for_droplet($droplet); # XXX _method
93-
my $image = $droplet->{image};
94-
95-
my $created = $parser->parse_datetime($droplet->{created_at});
96-
my $age_secs = time - $created->epoch;
97-
98-
my $img_created = $parser->parse_datetime($image->{created_at});
99-
my $img_age_secs = time - $img_created->epoch;
100-
101-
my $cost = sprintf '%4s',
102-
'$' . builtin::ceil($droplet->{size}{price_hourly} * $age_secs / 3600);
103-
104-
my $icon = ($image->{slug} && $image->{slug} =~ /^debian/) ? "\N{CYCLONE}"
105-
: (($image->{description}//'') =~ /^Debian/) ? "\N{CYCLONE}" # Deb 11
106-
: ($image->{name} =~ /\Afminabox/ ) ? "\N{PACKAGE}"
107-
: "\N{BLACK QUESTION MARK ORNAMENT}";
108-
109-
my $default = $default && $default eq $name
110-
? "\N{SPARKLES}"
111-
: "\N{IDEOGRAPHIC SPACE}";
112-
113-
$table->add(
114-
($status eq 'active' ? "\N{LARGE GREEN CIRCLE}" : "\N{HEAVY MINUS SIGN}"),
115-
$droplet->{region}{slug},
116-
"$icon\N{INVISIBLE SEPARATOR}",
117-
"$default\N{INVISIBLE SEPARATOR}",
118-
$name,
119-
$ip,
120-
Time::Duration::concise(Time::Duration::duration($age_secs, 1)),
121-
$cost,
122-
Time::Duration::concise(Time::Duration::duration($img_age_secs, 1)),
123-
);
124-
}
125-
126-
# This leading space is *bananas* and is here because Text::Table will think
127-
# about LARGE GREEN CIRCLE as being one wide, but it's two.
128-
print Term::ANSIColor::colored(['bold', 'bright_white'], qq{ $_}) for $table->title;
129-
print qq{$_} for $table->body;
61+
$self->print_droplet_list($droplets, $username);
62+
return;
13063
}
13164

13265
1;

lib/Dobby/Client.pm

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ sub http ($self) {
5050
};
5151
}
5252

53-
async sub json_get ($self, $path) {
53+
async sub json_get ($self, $path, $arg=undef) {
54+
my $undef_if_404 = $arg && $arg->{undef_if_404};
55+
5456
my $res = await $self->http->do_request(
5557
method => 'GET',
5658
uri => $self->api_base . $path,
@@ -60,6 +62,10 @@ async sub json_get ($self, $path) {
6062
);
6163

6264
unless ($res->is_success) {
65+
if ($undef_if_404 && $res->code == 404) {
66+
return undef;
67+
}
68+
6369
die "error getting $path at DigitalOcean: " . $res->as_string;
6470
}
6571

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

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

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

282+
async sub get_droplet_by_id ($self, $id) {
283+
$id =~ /\A[0-9]+\z/
284+
|| Carp::croak("bogus id given to get_droplet_by_id; should be a string of digits");
285+
286+
my $path = "/droplets/$id";
287+
288+
my $droplet = await $self->json_get($path, { undef_if_404 => 1 });
289+
290+
return $droplet;
291+
}
292+
293+
async sub get_droplets_by_name ($self, $name) {
294+
length $name
295+
|| Carp::croak("get_droplet_by_name without a name passed in");
296+
297+
my @droplets = await $self->_get_droplets({ name => $name });
298+
return @droplets;
299+
}
300+
275301
async sub add_droplet_to_project ($self, $droplet_id, $project_id) {
276302
my $path = "/projects/$project_id/resources";
277303

0 commit comments

Comments
 (0)