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 )