App-FargateStack
view release on metacpan or search on metacpan
lib/App/FargateStack.pm view on Meta::CPAN
package App::FargateStack;
########################################################################
# Copyright (C) 2025, TBC Development Group, LLC All rights reserved. #
# This is free software and may be modified or redistributed under the #
# same terms as Perl itself. #
# #
# Repository: https://github.com/rlauer6/App-Fargate #
########################################################################
use strict;
use warnings;
use App::FargateStack::Builder::Utils qw(jmespath_mapping toCamelCase dmp choose confirm);
use App::FargateStack::Constants;
use App::FargateStack::Pod;
use Carp;
use CLI::Simple;
use CLI::Simple::Constants qw(:booleans :chars %LOG_LEVELS);
use Cwd qw(realpath);
use Data::Dumper;
use Date::Parse qw(str2time);
use English qw(no_match_vars);
use File::Basename qw(basename fileparse);
use List::Util qw(none any);
use Log::Log4perl;
use Pod::Usage;
use Scalar::Util qw(reftype looks_like_number);
use Text::ASCIITable::EasyTable;
use Term::ANSIColor;
use YAML qw(LoadFile);
use Role::Tiny::With;
# command methods
with 'App::FargateStack::Init';
with 'App::FargateStack::Logs';
with 'App::FargateStack::Route53';
with 'App::FargateStack::CloudTrail';
with 'App::FargateStack::CreateStack';
with 'App::FargateStack::Autoscaling';
with 'App::BenchmarkRole';
# builder methods
with 'App::FargateStack::Builder';
with 'App::FargateStack::Builder::Autoscaling';
with 'App::FargateStack::Builder::IAM';
with 'App::FargateStack::Builder::Certificate';
with 'App::FargateStack::Builder::Events';
with 'App::FargateStack::Builder::EFS';
with 'App::FargateStack::Builder::HTTPService';
with 'App::FargateStack::Builder::Cluster';
with 'App::FargateStack::Builder::LogGroup';
with 'App::FargateStack::Builder::SecurityGroup';
with 'App::FargateStack::Builder::Secrets';
with 'App::FargateStack::Builder::Service';
with 'App::FargateStack::Builder::S3Bucket';
with 'App::FargateStack::Builder::SQSQueue';
with 'App::FargateStack::Builder::TaskDefinition';
with 'App::FargateStack::Builder::Utils';
with 'App::FargateStack::Builder::WafV2';
our $VERSION = '1.0.50';
use parent qw(CLI::Simple);
__PACKAGE__->use_log4perl( config => $LOG4PERL_CONF );
caller or __PACKAGE__->main;
########################################################################
sub init_logger {
########################################################################
my ($self) = @_;
my $log4perl_conf = $self->get_log4perl_conf;
if ( !$self->get_color ) {
$log4perl_conf =~ s/ColoredLevels//xsm;
if ( $self->get_log_level && $self->get_log_level =~ /debug|trace/xsm ) {
$log4perl_conf =~ s/ConversionPattern(.*?)$/ConversionPattern = [%d] (%M:%L) %m%n/xsm;
}
}
$self->set_log4perl_conf($log4perl_conf);
lib/App/FargateStack.pm view on Meta::CPAN
my $latest_digest = $latest_image->{imageDigest};
my $current_digest = $tasks->{$task_name}->{image_digest} // $EMPTY;
if ( $current_digest && $current_digest ne $latest_digest ) {
$self->log_error('run-task: You are not running the latest image!');
$self->log_error( 'run-task: [%s] != [%s]', $latest_digest, $current_digest );
$self->log_error('run-task: run "app-FargateStack register-task" to align the latest image with your task');
log_die( $self, 'run-task: use --force to force service creation or align your task with new image' )
if !$self->get_force;
}
return $TRUE;
}
########################################################################
sub get_task_status {
########################################################################
my ( $self, $cluster_name, $task_arn ) = @_;
my @elems = qw(last_status stopped_reason containers);
my $query = jmespath_mapping 'tasks[0]' => \@elems;
my $ecs = $self->fetch_ecs;
my $result = $ecs->describe_tasks( $cluster_name, $task_arn, $query );
$ecs->check_result( message => 'ERROR: unable to describe task: [%s]', $task_arn );
my ( $status, $stopped_reason, $containers ) = @{$result}{@elems};
return ( $status, $stopped_reason, $containers->[0]->{exitCode} );
}
########################################################################
sub get_default_service_name {
########################################################################
my ( $self, $skip_arg ) = @_;
# skip retrieving the arg (used when we want to allow a count as the first arg)
if ( !$skip_arg ) {
my ($service_name) = $self->get_args;
return $service_name
if $service_name;
}
my $tasks = $self->get_config->{tasks};
return grep { $tasks->{$_}->{type} =~ /^(https?|daemon)/xsm } keys %{$tasks};
}
########################################################################
sub cmd_deploy_service {
########################################################################
my ($self) = @_;
my ( $config, $tasks ) = $self->common_args(qw(config tasks));
my ( $task_name, $desired_count ) = $self->get_args;
if ( $task_name && looks_like_number $task_name ) {
$desired_count = $task_name;
$task_name = $EMPTY;
}
if ( !$task_name || !exists $tasks->{$task_name} ) {
( $task_name, my $err ) = $self->get_default_service_name($TRUE); # skip arg
if ( $err || !$task_name ) {
if ($err) {
$self->log_error('ERROR: multiple services in configuration.');
}
elsif ( !$task_name ) {
$self->log_error( 'ERROR: %s',
$task_name ? "service: [$task_name] not found in configuration" : 'no service types in configuration' );
}
die sprintf "usage: %s deploy-service service-name\n", $ENV{SCRIPT_NAME};
}
}
if ( !$desired_count || !looks_like_number $desired_count ) {
$desired_count = $tasks->{$task_name}->{desired_count} // 1;
}
$self->log_info('service: checking to see if task and latest image are aligned...');
$self->check_latest_image($task_name);
return $self->build_service( $task_name, $desired_count );
}
########################################################################
sub check_task {
########################################################################
my ( $self, $task_name, $warn ) = @_;
my $level = $warn ? 'warn' : 'die';
my $config = $self->get_config;
return $TRUE
if $task_name && $config->{tasks}->{$task_name};
log_die( $self, 'ERROR: no such task [%s] defined in config', $task_name )
if $level eq 'die';
$self->get_logger->warn( 'WARNING: no such task [%s] defined in config...trying anyway ¯\_(ã)_/¯', $task_name );
return;
}
########################################################################
sub cmd_remove_service {
########################################################################
my ($self) = @_;
my ( $task_name, $err ) = $self->get_default_service_name();
die "usage: %s remove-service task-name\n", $ENV{SCRIPT_NAME}
if !$task_name || $err;
my ( $config, $cluster, $dryrun ) = $self->common_args(qw(config cluster dryrun));
my $cluster_name = $cluster->{name};
$self->verify_service($task_name);
$self->check_task( $task_name, 'warn' );
$self->log_warn( 'remove-service: task [%s] will be deleted...%s', $task_name, $dryrun );
return $SUCCESS
if $dryrun;
my $ecs = $self->fetch_ecs;
my $result = $ecs->delete_service( $cluster->{name}, $task_name );
$ecs->check_result( message => 'ERROR: could not stop service %s', $task_name );
return $SUCCESS;
}
lib/App/FargateStack.pm view on Meta::CPAN
my @data = {
'Status' => $self->maybe_color( bright_white => $result->{status} ),
'Running Count' => $self->maybe_color( green => $result->{running_count} ),
'Desired Count' => $self->maybe_color( bright_white => $result->{desired_count} ),
'Pending Count' => $self->maybe_color( yellow => $result->{pending_count} ),
};
print {*STDOUT} easy_table(
table_options => {
allowANSI => $TRUE,
headingText => sprintf "Service Status\nTask Definition: %s",
$result->{task_definition}
},
data => \@data,
columns => [ 'Status', 'Running Count', 'Pending Count', 'Desired Count' ]
);
print {*STDOUT} <<'END_OF_NOTE';
* Note that this command will not force redeployment of your services! To force redeployment of your servicce:
app-FargateStack redeploy [service-name]
END_OF_NOTE
return;
}
########################################################################
sub update_task_count {
########################################################################
my ( $self, $task_name, $desired_count ) = @_;
my ( $config, $cluster ) = $self->common_args(qw(config cluster));
my $cluster_name = $cluster->{name};
$self->verify_service($task_name);
my $ecs = $self->get_ecs;
my $result = $ecs->update_service(
cluster_name => $cluster_name,
desired_count => $desired_count,
service_name => $task_name,
);
log_die( $self, "ERROR: could not update service: [%s]\n%s", $task_name, $ecs->get_error )
if !$result;
return $result;
}
########################################################################
sub cmd_start_stop_service {
########################################################################
my ($self) = @_;
my ( $task_name, $count ) = $self->get_args;
if ( looks_like_number $task_name ) {
$count = $task_name;
$task_name = $EMPTY;
}
$task_name = $self->check_service_name($task_name);
my $command = $self->command;
if ( $command eq 'start-service' ) {
$count ||= 1;
}
elsif ( $command eq 'update-service' ) {
}
else {
$count = 0;
}
if ( !$task_name ) {
if ( $count == 0 ) {
die sprintf "usage: %s -c config-name stop-service task-name\n", $ENV{SCRIPT_NAME};
}
die sprintf "usage: %s -c config-name start-service task-name [count]\n", $ENV{SCRIPT_NAME};
}
my $result = $self->update_task_count( $task_name, $count );
sleep 2; # wait a few seconds for status to be updated
return $self->cmd_service_status($task_name);
}
########################################################################
sub cmd_register_task_definition {
########################################################################
my ($self) = @_;
my ( $config, $tasks, $dryrun ) = $self->common_args(qw(config tasks dryrun));
my $task_name = $self->get_default_task_name;
my $action = $self->get_skip_register ? 'update-target' : 'register';
log_die( $self, 'usage: %s %s task-name', $action, $ENV{SCRIPT_NAME} )
if !$task_name;
my $task_definition_file = sprintf 'taskdef-%s.json', $task_name;
$self->check_task($task_name);
log_die( $self, "ERROR: no task definition file found for %s\n", $task_name )
if !-s $task_definition_file;
my $task_definition_arn;
my $ecs = $self->fetch_ecs;
if ( !$self->get_skip_register ) {
$self->log_warn( 'register: registering task definition for: [%s]...%s', $task_name, $dryrun );
if ( !$dryrun ) {
( run in 2.195 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )