view release on metacpan or search on metacpan
# App-Ordo
**Official Command Line Interface for Ordo â Hierarchical Job Scheduler**
A terminal client for managing jobs, clusters, calendars, and servers in Ordo.
## Purpose
Ordo is a modern job scheduler designed for complex, hierarchical workloads (DAGs of jobs and clusters, Quartz-style calendars, real-time monitoring, multi-server execution).
The `ordo` CLI gives you full control from the terminal â with both one-shot commands and a rich interactive shell.
## Features
- Interactive shell with command history
- Hierarchical navigation (`ls`, `cd`)
- Full CRUD for jobs, clusters, calendars, and worker servers
- Real-time status, logs, and zombie detection (`sync`)
- Beautiful colored tables and output
- Built-in pager for long results
- Scriptable for automation
## Installation
### From CPAN (recommended)
```
lib/App/Ordo/Command/Cal/Attach.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Attach;
use Moo;
use Term::ANSIColor qw(colored);
use feature qw(say);
extends 'App::Ordo::Command::Base';
sub name { "cal attach" }
sub summary { "Attach a calendar to a cluster" }
sub usage { "<calendar> <cluster-path>" }
sub option_spec { {} }
sub execute {
my ($self, $opt, $cal_name, $cluster_path) = @_;
unless ($cal_name && $cluster_path) {
say colored(["bold red"], "Usage: cal attach <calendar> <cluster-path>");
return;
}
my $res = $self->api->call('update_cluster', {
name => $cluster_path,
cal => $cal_name, # server accepts 'cal' => name
});
if ($res->{success}) {
say colored(["bold green"], "Calendar '$cal_name' attached to cluster '$cluster_path'");
lib/App/Ordo/Command/Cal/Create.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Create;
use Moo;
use feature qw(say);
extends 'App::Ordo::Command::Base';
use Term::ANSIColor qw(colored);
sub name { "cal create" }
sub summary { "Create a new calendar" }
sub usage { "<name> [--desc \"description\"]" }
sub option_spec {
return {
'description=s' => 'Description for the calendar',
# No positional options in spec â name is taken from @args
};
}
sub execute {
my ($self, $opt, $name) = @_;
unless ($name) {
say colored(["bold red"], "Error: calendar name is required");
$self->show_help;
return;
}
my $res = $self->api->call('create_cal', {
name => $name,
tz => $self->api->tz,
description => $opt->{description},
});
if ($res->{success}) {
say colored(["bold green"], "Calendar '$name' created successfully");
} else {
say colored(["bold red"], "Failed to create calendar: " . ($res->{message} || 'unknown error'));
}
}
1;
lib/App/Ordo/Command/Cal/Cron/Add.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Cron::Add;
use Moo;
use feature qw(say);
extends 'App::Ordo::Command::Base';
use Term::ANSIColor qw(colored);
sub name { "cal cron add" }
sub summary { "Add a cron expression to a calendar" }
sub usage { "<calendar> \"<cron expression\" [--description \"text\"]" }
sub option_spec {
return {
'description=s' => 'Description for this cron',
};
}
sub execute {
my ($self, $opt, $cal_name, $cron_expr) = @_;
unless ($cal_name && $cron_expr) {
say colored(["bold red"], "Usage: cal cron add <calendar> \"<cron>\" [--description \"...\"]");
return;
}
my $res = $self->api->call('create_cron', {
cal => $cal_name,
name => $cron_expr,
description => $opt->{description},
});
if ($res->{success}) {
say colored(["bold green"], "Cron '$cron_expr' added to calendar '$cal_name'");
} else {
say colored(["bold red"], "Failed: " . ($res->{message} || 'calendar not found'));
}
}
1;
lib/App/Ordo/Command/Cal/Cron/Delete.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Cron::Delete;
use Moo;
use feature qw(say);
extends 'App::Ordo::Command::Base';
use Term::ANSIColor qw(colored);
sub name { "cal cron delete" }
sub summary { "Delete a cron expression from a calendar" }
sub usage { "<cron-id>" }
sub aliases { ['cal cron rm'] }
sub option_spec {
return {};
}
sub execute {
my ( $self, $opt, $cron_id ) = @_;
lib/App/Ordo/Command/Cal/Cron/Delete.pm view on Meta::CPAN
# cal => $cal_name,
id => $cron_id,
};
$payload->{force} = 1 if $opt->{force};
my $res = $self->api->call( 'delete_cron', $payload );
if ( $res->{success} ) {
my $name = $res->{cal};
my $message = "Cron ID $cron_id removed from calendar";
$message .= " $res->{cal}" if $res->{cal};
say colored( ["bold green"], $message );
}
else {
say colored( ["bold red"], "Failed to delete cron: " . ( $res->{message} || 'unknown error' ) );
}
}
1;
lib/App/Ordo/Command/Cal/Delete.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Delete;
use Moo;
use feature qw(say);
extends 'App::Ordo::Command::Base';
use Term::ANSIColor qw(colored);
sub name { "cal delete" }
sub summary { "Delete a calendar and all its cron expressions" }
sub usage { "<calendar-name>" }
sub aliases { ['cal rm'] }
sub option_spec {
return {
'force' => 'Force delete even if attached to clusters',
};
}
sub execute {
my ($self, $opt, $cal_name) = @_;
unless ($cal_name) {
say colored(["bold red"], "Usage: cal delete <calendar-name> [--force]");
say "Use --force if the calendar is attached to clusters";
return;
}
my $payload = { name => $cal_name };
$payload->{force} = 1 if $opt->{force};
my $res = $self->api->call('delete_cal', $payload);
if ($res->{success}) {
say colored(["bold green"], "Calendar '$cal_name' deleted");
say "All associated cron expressions removed (cascade delete)";
} else {
say colored(["bold red"], "Failed to delete calendar: " . ($res->{message} || 'unknown error'));
}
}
1;
lib/App/Ordo/Command/Cal/Detach.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Detach;
use Moo;
use Term::ANSIColor qw(colored);
use feature qw(say);
extends 'App::Ordo::Command::Base';
sub name { "cal detach" }
sub summary { "Detach a calendar from a cluster" }
sub usage { "<calendar> <cluster-path>" }
sub option_spec { {} }
sub execute {
my ($self, $opt, $cal_name, $cluster_path) = @_;
unless ($cal_name && $cluster_path) {
say colored(["bold red"], "Usage: cal detach <calendar> <cluster-path>");
return;
}
my $res = $self->api->call('update_cluster', {
name => $cluster_path,
cal_id => '', # empty string = detach
});
if ($res->{success}) {
say colored(["bold green"], "Calendar '$cal_name' detached from cluster '$cluster_path'");
lib/App/Ordo/Command/Cal/List.pm view on Meta::CPAN
use feature qw(say);
use utf8;
use open ':std', ':utf8'; # Set STDOUT and STDERR to UTF-8
extends 'App::Ordo::Command::Base';
use POSIX qw(strftime);
use Term::ANSIColor qw(colored);
use Text::Table::Tiny 1.02 qw(generate_table);
sub name { "cal list" }
sub summary { "List all calendars and their cron expressions" }
sub usage { "[calendar-name]" }
sub aliases { ['cal ls'] }
sub option_spec { {} }
sub execute {
my ($self, $opt, $filter_name) = @_;
my $res = $self->api->call('find_cal', {});
unless ($res->{success} && scalar @{ $res->{cals} }) {
say colored(["bold yellow"], "No calendars found");
return;
}
my @cals = @{$res->{cals} || []};
# Optional: filter by name
if ($filter_name) {
@cals = grep { ($_->{name} || '') eq $filter_name } @cals;
unless (@cals) {
say colored(["bold yellow"], "No calendar found with name: $filter_name");
return;
}
}
say colored(["bold cyan"], "Calendars (" . scalar(@cals) . " found):\n");
for my $cal (@cals) {
my $name = $cal->{name} || colored(["yellow"], "(unnamed id:$cal->{id})");
my $desc = $cal->{description} ? " - $cal->{description}" : "";
my $attached = $cal->{cluster_ids} && @{$cal->{cluster_ids}}
lib/App/Ordo/Command/Cal/Show.pm view on Meta::CPAN
package App::Ordo::Command::Cal::Show;
use Moo;
extends 'App::Ordo::Command::Base';
use feature qw(say);
use POSIX qw(strftime);
use Term::ANSIColor qw(colored);
use Text::Table::Tiny 1.02 qw(generate_table);
sub name { "cal show" }
sub summary { "Show detailed information about a calendar" }
sub usage { "<calendar-name>" }
sub option_spec { {} }
sub execute {
my ($self, $opt, $cal_name) = @_;
unless ($cal_name) {
say colored(["bold red"], "Usage: cal show <calendar-name>");
return;
}
my $res = $self->api->call('find_cal', { name => $cal_name });
unless ($res->{success}) {
say colored(["bold red"], "Calendar not found: $cal_name");
say " " . ($res->{message} || '');
return;
}
lib/App/Ordo/Command/Cluster/Create.pm view on Meta::CPAN
sub name { "cluster create" }
sub summary { "Create a new cluster (folder/DAG)" }
sub usage { "<path> [options]" }
sub option_spec {
return {
#'desc|description|d=s' => 'Cluster description',
'desc' => 'Cluster description',
'needs=s@' => 'Clusters this depends on (AND logic)',
'needs_any=s@' => 'Clusters this depends on (OR logic)',
'cal=s' => 'Attach calendar by name',
'loops=i' => 'Number of loops',
'clonable=i' => 'Allow cloning (0/1)',
'on_fail=s' => 'Cluster to run on failure',
};
}
sub execute {
my ($self, $opt, $path) = @_;
unless ($path) {
lib/App/Ordo/Command/Cluster/Update.pm view on Meta::CPAN
sub name { "cluster update" }
sub summary { "Update an existing cluster" }
sub usage { "<path> [options]" }
sub option_spec {
return {
'description=s' => 'Change description',
'needs=s@' => 'Replace AND dependencies',
'needs_any=s@' => 'Replace OR dependencies',
'remove_needs' => 'Remove all dependencies (boolean)',
'cal=s' => 'Attach/detach calendar (name or empty)',
'loops=i' => 'Change loop count',
'clonable=i' => 'Change clonable flag',
'on_fail=s' => 'Change fail alarm cluster',
};
}
sub execute {
my ($self, $opt, $path) = @_;
unless ($path) {