App-FargateStack

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN

will output the result of the AWS CLI commands.
- (7) Use `--skip-register` if you want to update a tasks target
rule without registering a new task definition. This is typically done
if for some reason your target rule is out of sync with your task
definition version.
- (8) To speed up processing and avoid unnecessary API calls the
framework considers the configuration file the source of truth and a
reliable representation of the state of the stack. If you want to
re-sync the configuration file set `--no-cache` and run `plan`. In
most cases this should not be necessary as the framework will
invalidate the configuration if an error occurs forcing a re-sync on
the next run of `plan` or `apply`.
- (9) `--no-update` is not permitted with `apply`. If you need a
dry plan without applying or updating the config, use `--dryrun` (and
optionally `--no-update`) with `plan`.
- (10) Set `--route53-profile` to the profile that has
permissions to manage your hosted zones. By default the script will
use the default profile.
- (11) Deleting a task, daemon, or http service will delete all of
the resources associated with that task.
    - For scheduled tasks you can disable the job from running instead of

README.md  view on Meta::CPAN

      path: /
      readonly:

Acceptable values for `readonly` are "true" and "false".

## Field Descriptions

- id:

    The ID of an existing EFS filesystem. The framework does not provision
    the EFS, but will validate its existence in the current AWS account
    and region.

- mount\_point:

    The container path to which the EFS volume will be mounted.

- path:

    The path on the EFS filesystem to map to your container's mount point.

README.md  view on Meta::CPAN

- The ECS role's policy for your task is automatically modified
to allow read/write EFS access. Set `readonly:` in your task's
`efs:` section to "true" if only want read support.
- Your EFS security group must allow access from private subnets
where the Fargate tasks are placed.
- No changes are made to the EFS security group; the framework
assumes access is already configured
- Only one EFS volume is currently supported per task configuration.
- EFS volumes are task-scoped and reused only where explicitly configured.
- The framework does not automatically provision an EFS
filesystem for you. The framework does however validate that the
filesystem exists in the current account and region.

[Back to Table of Contents](#table-of-contents)

# CONFIGURATION

The `App::FargateStack` framework defines your application stack
using a YAML configuration file. This file describes your
application's services, their resource needs, and how they should be
deployed. Then configuration is updated whenever your run `plan` or

README.md  view on Meta::CPAN


If no subnets are specified in the configuration, the framework will query all
subnets in the selected VPC and categorize them as either public or private.

The task will be placed in a private subnet by default. For this to succeed,
your VPC must have at least one private subnet with a route to a NAT Gateway,
or have appropriate VPC endpoints configured for ECR, S3, STS, CloudWatch Logs,
and any other services your task needs.

If subnets are explicitly specified in your configuration, the
framework will validate them and warn if they are not reachable or are
not usable for Fargate tasks.

### Task placement and Availability Zones

The framework places each task's ENI into exactly one subnet, which fixes
that task in a single AZ. A service can span multiple AZs by listing
subnets from at least two AZs.

What the framework does:

README.md  view on Meta::CPAN

if any required resources are unavailable. If the task type is
"https", the script looks for a public zone, public subnets and an
internet-facing ALB otherwise it looks for a private zone, private
subnets and an internal ALB.

## ACM Certificate Management

If the task type is "https" and no ACM certificate currently exists
for your domain, the framework will automatically provision one. The
certificate will be created in the same region as the ALB and issued
via AWS Certificate Manager. If the certificate is validated  via DNS
and subsequently attached to the listener on port 443.

## Port and Listener Rules

For external-facing apps, a separate listener on port 80 is
created. It forwards traffic to port 443 using a default redirect rule
(301). If you do not want a redirect rule, set the `redirect_80:` in
the `alb:` section to "false".

If you want your internal application to listen on a port other than

README.md  view on Meta::CPAN

resource configurations (subnets, ALBs, Route 53, etc)._

Resources are provisioned and your configuration file is updated
incrementally as `app-FargateStack` compares your environment to the
environment required for your stack. When either plan or
apply complete your configuration is updated giving you complete
insight into what resources were found and what resources will be
provisioned. See [CONFIGURATION](https://metacpan.org/pod/CONFIGURATION) for complete details on resource
configurations.>

Your environment will be validated against the criteria described
below.

- You have at least 2 private subnets available for deployment

    Technically you can launch a task with only 1 subnet but for services
    behind an ALB Fargate requires 2 subnets.

    _When you create a service with a load balancer, you must specify
    two or more subnets in different Availability Zones. - AWS Docs_

lib/App/EC2.pm  view on Meta::CPAN

      '--group-id' => $group_id,
      '--port'     => $port,
      '--protocol' => $protocol,
      $cidr         ? ( '--cidr'         => $cidr )         : (),
      $source_group ? ( '--source-group' => $source_group ) : (),
    ]
  );
}

########################################################################
sub validate_subnets {
########################################################################
  my ( $self, $subnets ) = @_;

  # flatten private, public subnets
  my @all_subnets = map { @{ $subnets->{$_} // [] } } keys %{$subnets};

  my @valid_subnets = map { $_->{SubnetId} } @{ $self->describe_subnets()->{Subnets} };

  foreach my $s (@all_subnets) {
    croak sprintf "ERROR: The subnet [%s] does not exist in vpc: [%s]\nvalid subnets: \n\t%s\n", $s,

lib/App/ECR.pm  view on Meta::CPAN

  ($repository_name) = split /:/xsm, $repository_name;

  my $query = 'imageDetails[?imageTags != null && contains(imageTags, `latest`)]';

  my $result = $self->describe_images( $repository_name, $query );

  return @{ $result || [] };
}

########################################################################
sub validate_images {
########################################################################
  my ( $self, @images ) = @_;

  foreach my $image (@images) {
    warn sprintf "WARN: image not found in ECR: [%s]\n", $image
      if !$self->get_latest_image($image);
  }

  return;
}

lib/App/ElbV2.pm  view on Meta::CPAN

    'create-rule' => [
      '--listener-arn' => $listener_arn,
      '--priority'     => $priority,
      '--conditions'   => $conditions,
      '--actions'      => $default_action,
    ]
  );
}

########################################################################
sub validate_alb {
########################################################################
  my ( $self, %args ) = @_;

  my ( $alb_arn, $scheme ) = @args{qw(arn scheme)};

  croak "usage: validate_alb(alb-arn, [ vpc-id ])\n"
    if !$alb_arn;

  my $query = sprintf 'LoadBalancers[?LoadBalancerArn == `%s` && Scheme == `%s`]', $alb_arn, $scheme;

  my $albs = $self->describe_load_balancers( query => $query );

  croak "ERROR: unable to find ALB: %s\n%s", $alb_arn, $self->get_error
    if !$albs;

  return $albs;

lib/App/Events.pm  view on Meta::CPAN

  return $self->command(
    'put-targets' => [
      '--rule'           => $rule,
      '--targets'        => "file://$tmpfile",
      '--event-bus-name' => $self->get_event_bus_name
    ]
  );
}

########################################################################
sub validate_schedule {
########################################################################
  my ( $self, $schedule ) = @_;

  my ( $type, $args ) = ( $schedule =~ /^(cron|at|rate)[(]([^)].+)[)]$/xsm );

  return $FALSE
    if !$type || !$args;

  my %validators = (
    rate => sub {

lib/App/Events.pm  view on Meta::CPAN

        if 6 != @cron;

      my ( $min, $hour, $dom, $month, $dow, $year ) = @cron;

      # AWS stupid cron format - both dom and dow cannot be '*', but
      # allow in our config
      if ( $dow eq q{*} && $dom eq q{*} ) {
        $dow = q{?};
      }

      if ( _validate_min($min)
        && _validate_hour($hour)
        && _validate_day($dom)
        && _validate_month($month)
        && _validate_dow($dow)
        && _validate_year($year) ) {
        return sprintf 'cron(%s %s %s %s %s %s)', $min, $hour, $dom, $month, $dow, $year;
      }

      return $FALSE;
    },
    at => sub {
      my ($args) = @_;

      my ( $this_month, $today, $this_year ) = (localtime)[ 3, 4, 5 ];
      $this_month++;

lib/App/Events.pm  view on Meta::CPAN

      # that it should blow up in there face...

      return $TRUE;
    },
  );

  return $validators{$type}->($args);
}

########################################################################
sub _validate_range {
########################################################################
  my ( $expr, $min, $max ) = @_;

  return $TRUE
    if $expr !~ /[\-]/xsm;

  return $FALSE
    if !$expr;

  # Handle simple range: "start-end"

lib/App/Events.pm  view on Meta::CPAN

    if $expr =~ m{^(\d+)-(\d+)/(\d+)$}xsm
    && $1 >= $min
    && $2 <= $max
    && $1 <= $2
    && $3 > 0;

  return $FALSE;
}

########################################################################
sub _validate_min {
########################################################################
  my ($min) = @_;

  return $TRUE if $min eq q{*};

  if ( $min =~ /^\d+$/xsm ) {
    return $TRUE if $min >= 0 && $min <= 59;
    return $FALSE;
  }

  if ( $min =~ /,/xsm ) {
    foreach my $m ( split /,/xsm, $min ) {
      return $FALSE if $m !~ /^\d+$/xsm || $m < 0 || $m > 59;
    }
    return $TRUE;
  }

  return $TRUE if _validate_range( $min, 0, 59 );

  return $FALSE;
}

########################################################################
sub _validate_hour {
########################################################################
  my ($hour) = @_;

  return $TRUE if $hour eq q{*};

  if ( $hour =~ /^\d+$/xsm ) {
    return $TRUE if $hour >= 0 && $hour <= 23;
    return $FALSE;
  }

  if ( $hour =~ /,/xsm ) {
    foreach my $h ( split /,/xsm, $hour ) {
      return $FALSE if $h !~ /^\d+$/xsm || $h < 0 || $h > 23;
    }
    return $TRUE;
  }

  return $TRUE if _validate_range( $hour, 0, 23 );

  return $FALSE;
}

########################################################################
sub _validate_day {
########################################################################
  my ($day) = @_;

  return $TRUE if $day eq q{*};

  if ( $day =~ /^\d+$/xsm ) {
    return $TRUE if $day >= 1 && $day <= 31;
    return $FALSE;
  }

  if ( $day =~ /,/xsm ) {
    foreach my $d ( split /,/xsm, $day ) {
      return $FALSE if $d !~ /^\d+$/xsm || $d < 1 || $d > 31;
    }
    return $TRUE;
  }

  return $TRUE if _validate_range( $day, 1, 31 );

  return $FALSE;
}

########################################################################
sub _validate_month {
########################################################################
  my ($month) = @_;

  return $TRUE if $month eq q{*};

  my @valid_names = qw(JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC);
  my @valid_nums  = ( 1 .. 12 );

  return $TRUE if any { $month eq $_ } @valid_names, @valid_nums;

  if ( $month =~ /,/xsm ) {
    foreach my $m ( split /,/xsm, $month ) {
      return $FALSE if none { $m eq $_ } @valid_names, @valid_nums;
    }
    return $TRUE;
  }

  return $TRUE if _validate_range( $month, 1, 12 );

  return $FALSE;
}

########################################################################
sub _validate_year {
########################################################################
  my ($year) = @_;

  return $TRUE if $year eq q{*};

  my $this_year = (localtime)[5] + 1900;

  return $TRUE if $year =~ /^\d+$/xsm && $year >= $this_year;

  if ( $year =~ /,/xsm ) {

lib/App/Events.pm  view on Meta::CPAN


  if ( $year =~ /^(\d+)-(\d+)$/xsm ) {
    my ( $start, $end ) = ( $1, $2 );
    return $TRUE if $start >= $this_year && $start <= $end;
  }

  return $FALSE;
}

########################################################################
sub _validate_dow {
########################################################################
  my ($day) = @_;

  return $TRUE if $day eq q{*};

  # allow numeric values 1..7 and short day names
  my @valid_names = qw(SUN MON TUE WED THU FRI SAT);
  my @valid_nums  = ( 1 .. 7 );

  # simple name or number

lib/App/Events.pm  view on Meta::CPAN


  # comma-separated values (e.g., "1,MON,3")
  if ( $day =~ /,/xsm ) {
    foreach my $d ( split /,/xsm, $day ) {
      return $FALSE if none { $d eq $_ } @valid_names, @valid_nums;
    }
    return $TRUE;
  }

  # numeric range
  return $TRUE if _validate_range( $day, 1, 7 );

  # named range (e.g., MON-FRI)
  return $TRUE if $day =~ /^(${\join '|', @valid_names})-(${\join '|', @valid_names})$/xsm;

  return $FALSE;
}

1;

lib/App/FargateStack/AutoscalingConfig.pm  view on Meta::CPAN

=item *

Validation: It performs a series of critical, upfront validation checks.
If any part of the configuration is missing, malformed, or logically
inconsistent (e.g., a scale-in capacity is larger than a scale-out capacity),
the subroutine will abort the entire process with a clear, descriptive error
message.

=item *

Transformation: It transforms the validated user input into two separate,
syntactically correct AWS cron expressions (cron(...)) for the scale-out and
scale-in events.

=item *

Structuring: It builds and returns a nested hash that precisely mirrors
the data structure needed for the put-scheduled-action API calls, separating
the ScaleOut and ScaleIn actions, each with their own Schedule string and
Action hash containing the corresponding capacity limits.

lib/App/FargateStack/Builder/Cluster.pm  view on Meta::CPAN

  my ( $cluster_name, $cluster_arn ) = @{$cluster}{qw(name arn)};

  if ( !$cluster_name || !$cluster_arn ) {
    $config->{cluster} //= $cluster;
    $cluster_name = $self->create_default('cluster-name');
    $cluster->{name} = $cluster_name;
  }

  ## - cluster exists? -
  if ( !$cluster_arn || !$self->get_cache ) {
    # - validate cluster arn
    $cluster_arn = $ecs->cluster_exists($cluster_name);
  }

  if ($cluster_arn) {
    $self->log_info( sprintf 'cluster: [%s] exists...%s', $cluster_name, $self->get_cache || 'skipping' );

    $self->inc_existing_resources( cluster => [$cluster_arn] );
    $cluster->{arn} = $cluster_arn;

    return;

lib/App/FargateStack/Builder/EFS.pm  view on Meta::CPAN


  if ( !$mount_point ) {
    $self->log_error('task:efs WARNING: no mount point defined...using /mnt');
  }

  my ( $arn, $readonly ) = @{$efs_config}{qw(arn readonly)};

  log_die( $self, 'ERROR: no id set for EFS volume' )
    if !$id;

  # - validate id -
  if ( !$arn || !$self->get_cache ) {
    $self->log_info( 'task: validating EFS id: [%s]...', $id );

    my $file_system = $efs->describe_file_systems( $id, 'FileSystems' );

    log_die( $self, "ERROR: no such EFS file system (%s) found\n", $id )
      if !$file_system;

    $arn = $efs_config->{arn} = $file_system->[0]->{FileSystemArn};

lib/App/FargateStack/Builder/Events.pm  view on Meta::CPAN


    my $schedule = $task->{schedule};

    if ( !$schedule ) {
      $self->log_warn( 'events: no schedule for task: [%s]...skipping', $task_name );
      return;
    }

    $self->log_info( 'events: found a schedule...validating schedule: [%s]', $schedule );

    my $valid_schedule = $event->validate_schedule($schedule);

    croak sprintf "ERROR: invalid schedule expression: %s\nSee: %s", $schedule, $EVENT_SCHEDULER_TYPE_URL
      if !$valid_schedule;

    my $rule_name = sprintf '%s-schedule', $task_name;

    if ( $valid_schedule ne $schedule ) {
      $self->log_warn( sprintf 'events: [%s] schedule modified [%s] automatically for you...', $schedule, $valid_schedule );

      $task->{schedule} = $valid_schedule;

lib/App/FargateStack/Builder/HTTPService.pm  view on Meta::CPAN

        );
      }
    );

    my $security_group_name;
    my $is_valid_alb;

    if ($alb_arn) {
      $security_group_name = $ec2->find_security_group_name($security_group_id);

      $is_valid_alb = $elb->validate_alb(
        arn    => $alb_arn,
        scheme => $self->is_https ? 'internet-facing' : 'internal'
      );
    }

    if ( $alb_arn && $is_valid_alb ) {

      # set this for later
      $self->set_alb( $elb->get_alb );

lib/App/FargateStack/Builder/Utils.pm  view on Meta::CPAN


=head2 normalize_time_range

  my ($from_ms, $to_ms) = normalize_time_range($start, $end);

Normalizes a human-friendly time range into Unix epoch timestamps in
**milliseconds**.

Given a required C<$start> and an optional C<$end>, this routine parses
each value into epoch seconds (using L<Date::Parse/str2time> for absolute
dates, or a compact “duration” syntax), validates the range, and returns
a two-element list: C<(start_ms, end_ms_or_undef)>.

Returns an empty list if C<$start> is false/undefined (useful for
“no time filter” cases).

=head3 Arguments

=over 4

=item C<$start> (required)

lib/App/FargateStack/Checker.pm  view on Meta::CPAN


=item B<--https> | B<--no-https>

Enable or disable ACM certificate checks (same region as the load balancer).
Default: B<disabled>. Turn on if you plan to deploy HTTPS.

=item B<--secrets> | B<--no-secrets>

Enable or disable Secrets Manager reachability checks. Default: B<disabled>.
When enabled, the checker verifies control-plane reachability (e.g., VPC
endpoint present or NAT available). It does not validate individual
C<GetSecretValue> permissions for task roles.

=back

=head1 OUTPUT

The main table includes rows like:

  Credentials
  Service-linked roles

lib/App/FargateStack/Init.pm  view on Meta::CPAN

  foreach (qw(private public)) {
    my $subnets = $subnets->{$_};
    $self->log_debug( 'init-ec2: %d %s subnets detected.', scalar( @{$subnets} ), $_ );
    next if $subnets && @{$subnets} > 1;

    log_die( $self, 'ERROR: you must specify at least 2 %s subnets', $_ );
  }

  $config->{vpc_id} = $ec2->get_vpc_id;

  # if we find subnets in the config...always validate in case they
  # got changed...
  if ($subnets) {
    $self->log_info('init-ec2: validating subnets...');
    $ec2->validate_subnets($subnets);  # this will croak if any are not invalid
  }
  else {
    my $subnets = $ec2->get_subnets;
    $self->set_subnets($subnets);
    $config->{subnets} = $subnets;
  }

  $self->log_debug( sub { return Dumper( [ subnets => $subnets ] ) } );

  return $ec2;

lib/App/FargateStack/Init.pm  view on Meta::CPAN


  if ( $self->get_cache ) {
    $self->log_info( 'init-tasks: skipping images validation...%s', $self->get_cache );
    return;
  }

  $self->log_info('init-tasks: validating images...');

  my $ecr = $self->fetch_ecr();

  $ecr->validate_images(@images);

  return;
}

########################################################################
sub _init_account {
########################################################################
  my ($self) = @_;

  my $config = $self->get_config;

lib/App/FargateStack/Init.pm  view on Meta::CPAN

    $route53_config->{zone_id} = $zone_id;

    return;
  }

  return
    if $self->get_cache;

  $self->log_info( 'init-route53: validating hosted zone id: [%s]...', $zone_id );

  my $zone = eval { return $route53->validate_hosted_zone( zone_id => $zone_id, domain => $domain, alb_type => $alb_type, ); };

  my $err = $EVAL_ERROR;

  if ( $zone && !$err ) {
    $self->log_info( 'init-route53: hosted zone id: [%s/%s]', $zone->{Id}, $zone->{Name} );
    return;
  }

  # output a helpful table of hosted zones for this domain
  $self->log_warn( "\n" . $self->display_hosted_zones($domain) );

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

=item (7) Use C<--skip-register> if you want to update a tasks target
rule without registering a new task definition. This is typically done
if for some reason your target rule is out of sync with your task
definition version.

=item (8) To speed up processing and avoid unnecessary API calls the
framework considers the configuration file the source of truth and a
reliable representation of the state of the stack. If you want to
re-sync the configuration file set C<--no-cache> and run C<plan>. In
most cases this should not be necessary as the framework will
invalidate the configuration if an error occurs forcing a re-sync on
the next run of C<plan> or C<apply>.

=item (9) C<--no-update> is not permitted with C<apply>. If you need a
dry plan without applying or updating the config, use C<--dryrun> (and
optionally C<--no-update>) with C<plan>.

=item (10) Set C<--route53-profile> to the profile that has
permissions to manage your hosted zones. By default the script will
use the default profile.

lib/App/FargateStack/Pod.pm  view on Meta::CPAN


Acceptable values for C<readonly> are "true" and "false".

=head2 Field Descriptions

=over 4

=item id:

The ID of an existing EFS filesystem. The framework does not provision
the EFS, but will validate its existence in the current AWS account
and region.

=item mount_point:

The container path to which the EFS volume will be mounted.

=item path:

The path on the EFS filesystem to map to your container's mount point.

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

where the Fargate tasks are placed.

=item * No changes are made to the EFS security group; the framework
assumes access is already configured

=item * Only one EFS volume is currently supported per task configuration.

=item * EFS volumes are task-scoped and reused only where explicitly configured.

=item * The framework does not automatically provision an EFS
filesystem for you. The framework does however validate that the
filesystem exists in the current account and region.

=back

=head1 CONFIGURATION

The C<App::FargateStack> framework defines your application stack
using a YAML configuration file. This file describes your
application's services, their resource needs, and how they should be
deployed. Then configuration is updated whenever your run C<plan> or

lib/App/FargateStack/Pod.pm  view on Meta::CPAN


If no subnets are specified in the configuration, the framework will query all
subnets in the selected VPC and categorize them as either public or private.

The task will be placed in a private subnet by default. For this to succeed,
your VPC must have at least one private subnet with a route to a NAT Gateway,
or have appropriate VPC endpoints configured for ECR, S3, STS, CloudWatch Logs,
and any other services your task needs.

If subnets are explicitly specified in your configuration, the
framework will validate them and warn if they are not reachable or are
not usable for Fargate tasks.

=head3 Task placement and Availability Zones

The framework places each task's ENI into exactly one subnet, which fixes
that task in a single AZ. A service can span multiple AZs by listing
subnets from at least two AZs.

What the framework does:

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

if any required resources are unavailable. If the task type is
"https", the script looks for a public zone, public subnets and an
internet-facing ALB otherwise it looks for a private zone, private
subnets and an internal ALB.

=head2 ACM Certificate Management

If the task type is "https" and no ACM certificate currently exists
for your domain, the framework will automatically provision one. The
certificate will be created in the same region as the ALB and issued
via AWS Certificate Manager. If the certificate is validated  via DNS
and subsequently attached to the listener on port 443.

=head2 Port and Listener Rules

For external-facing apps, a separate listener on port 80 is
created. It forwards traffic to port 443 using a default redirect rule
(301). If you do not want a redirect rule, set the C<redirect_80:> in
the C<alb:> section to "false".

If you want your internal application to listen on a port other than

lib/App/FargateStack/Pod.pm  view on Meta::CPAN

resource configurations (subnets, ALBs, Route 53, etc).>

Resources are provisioned and your configuration file is updated
incrementally as C<app-FargateStack> compares your environment to the
environment required for your stack. When either plan or
apply complete your configuration is updated giving you complete
insight into what resources were found and what resources will be
provisioned. See L<CONFIGURATION> for complete details on resource
configurations.>

Your environment will be validated against the criteria described
below.

=over 4

=item * You have at least 2 private subnets available for deployment

Technically you can launch a task with only 1 subnet but for services
behind an ALB Fargate requires 2 subnets.

I<When you create a service with a load balancer, you must specify

lib/App/Route53.pm  view on Meta::CPAN

sub list_hosted_zones {
########################################################################
  my ( $self, $zone_id ) = @_;

  my $query = $zone_id ? sprintf 'HostedZones[?Id == `/hostedzone/%s`]', $zone_id : $EMPTY;

  return $self->command( 'list-hosted-zones' => [ $query ? ( '--query' => $query ) : () ] );
}

########################################################################
sub validate_hosted_zone {
########################################################################
  my ( $self, %args ) = @_;

  my ( $zone_id, $domain, $alb_type ) = @args{qw(zone_id domain alb_type)};

  my $query = sprintf 'HostedZones[?Id == `/hostedzone/%s`]', $zone_id;

  my $zone = $self->list_hosted_zones($zone_id);

  die sprintf "invalid zone_id: [%s]\n", $zone_id

share/ChangeLog  view on Meta::CPAN

	* VERSION: bump
	* lib/App/FargateStack/AutoscalingConfig.pm.in: new
	* Makefile: add above to build
	* lib/App/ApplicationAutoscaling.pm.in
	(put_scheduled_action)
	- fleshed out API call
	(describe_scheduled_actions): likewise
	* lib/App/EC2.pm.in
	(describe_security_groups): named args
	* lib/App/Events.pm.in
	(_validate_range): escape -
	* lib/App/FargateStack/Builder.pm.in
	(build)
	- register-task-definitions
	- check EFS ingress state
	(is_efs_ingress_authorized): new
	(get_efs_sgs): save state
	* lib/App/FargateStack/Builder/Autoscaling.pm.in
	(build_autoscaling)
	- refactored to use App::FargateStack::AutoScalingConfig
	(build_scheduled_actions): new

share/ChangeLog  view on Meta::CPAN

	(log_die): die, so we invoke the handler
	(fetch_cli_api): escape -
	* lib/App/FargateStack/Builder/WafV2.pm.in: likewise
	* lib/App/FargateStack/Constants.pm.in
	- reorganize
	* lib/App/FargateStack/CreateStack.pm.in
	- refactored
	- respect --no-update option
	- true not JSON::true
	(cmd_create_stack)
	- validate config
	- use constants
	(parse_autoscaling_options)
	- policy_name already set\
	- true, not JSON::true
	(parse_environment_option): new
	* lib/App/FargateStack/Init.pm.in
	(init)
	- warn on caught eval errors
	(_init_route53): log message tweaks
	* lib/App/FargateStack/Logs.pm.in

share/ChangeLog  view on Meta::CPAN

	(describe_subnet)
	- refactoring
	- allow list of subnets
	(describe_subnets)
	- refactoring
	- allow hash of args
	* lib/App/FargateStack/Init.pm.in
	(_init_ec2)
	- log messages
	- check for 2 subnets
	- always validate subnets
	(_init_tasks)
	- make sure we have private subnets if http service
	- log messages
	* lib/App/FargateStack/Builder.pm.in
	(build)
	- confirm apply
	- log messages
	* lib/App/FargateStack.pm.in
	(init_logger): avoid uninitialized warning
	(show_config): tidy up output

share/ChangeLog  view on Meta::CPAN

	(delete_security_group): new
	* lib/App/ECS.pm.in
	(delete_cluster): new
	* lib/App/EFS.pm.in
	(describe_mount_targets): new
	* lib/App/ElbV2.pm.in
	(delete_target_group): new
	(describe_rules): refactoring
	(describe_load_balancers): take k/v/ pairs now
	(_find_alb_of_type): describe_load_balancers signature change
	(validate_alb): likewise
	* lib/App/FargateStack.pm.in
	(cmd_remove_service): fix usage message
	(main): +elbv2, -elb, +ecr
	* lib/App/FargateStack/Builder.pm.in
	(remove_listener_rules)
	- elbv2, don't need target arn, use domain
	- make conditional more obvious (check domain)
	(delete_task_resources)
	- refactore, debugged
	(is_our_alb): must pass alb_arn, tg_arn now

share/ChangeLog  view on Meta::CPAN

	* lib/App/ECS.pm.in
	(describe_services): new
	* lib/App/FargateStack.pm.in
	(init)
	- call _init_route53() unconditionally and let it figure it out
	- set log-level to error for commands that shouldn't log
	- command(), not get_command()
	(show_config): log_level may not be initialized yet?
	(_init_defaults):
	- save last_updated and delete last_updated and id from config
	- force cache off if config was invalidated
	(_init_route53)
	- return if not a command that needs route53
	- get_cache, not cache()
	(cmd_logs)
	- refactoring
	- order-by, descending (need most recent log stream)
	- standardize log message output
	(_init_ec2): debug message
	(cmd_service_status): new
	(_init_config): don't invalidate config here, too early
	(cmd_run_task): standardize log message output
	(check_task): new
	(cmd_stop_service): removed
	(cmd_start_service): removed
	(cmd_update_service): replaces above
	(_update_task_count): new
	(init_logger): default 'info'
	(main)
	- log-time default now true
	- added command status

share/ChangeLog  view on Meta::CPAN

	- die handler for keeping config file in sync
	- force --no-cache if config file out of sync
	(_init_route53): refactoring
	(_init_defaults): moved setting of cache here
	(cmd_stop_service): first implementation
	(cmd_list_hosted_zones): SCRIPT_NAME no SCRIPT_PATH
	(cmd_run_task): likewise
	(cmd_register): likewise
	(_init_config)
	- likewise
	- invalidate config object
	- add config_name to configuration object
	(display_hosted_zones): set route53 if not set

Mon Jul 28 15:52:08 2025  Rob Lauer  <rlauer6@comcast.net>

	[1.0.5]:
	* README.md
	* VERSION
	* lib/App/EC2.pm.in
	(new): find eligible VPC if no vpc_id

share/ChangeLog  view on Meta::CPAN

	* lib/App/FargateStack/Builder/LogGroup.pm.in
	(build_log_groups): look for group again if arn is missing

Mon Jul 28 13:24:03 2025  Rob Lauer  <rlauer6@comcast.net>

	[1.0.4]:
	* VERSION: bump
	* README.md: generated
	* lib/App/EC2.pm.in
	(new)
	- validate subnet ids against usable list
	(describe_nat_gateways): new
	(find_public_subnets): refactored
	(find_private_subnets): likewise
	(_find_subnets): likewise
	(describe_route_tables): new
	(list_route_table_associations): new
	(categorize_subnets): rename from find_all_subnets()
	* lib/App/FargateStack.pm.in
	- pod updates
	(show_config): zone_id // -

share/ChangeLog  view on Meta::CPAN

	- can't use ?, use help list
	- regex matching
	(cmd_plan): new
	(cmd_apply): new
	(cmd_update_target): new
	(main)
	- + profile_source
	- move default options to %default_options
	- move commands to %commands
	(create_rule): log debug message
	(validate_alb)
	- validate the alb by scheme
	* lib/App/FargateStack/Builder.pm.in
	(build)
	- log_info, log_warn
	- log if we update the config
	(configure_alb)
	- set ALB type based on task type (http or https)
	* lib/App/FargateStack/Builder/Certificate.pm.in
	- log_info, log_warn
	* lib/App/FargateStack/Builder/HTTPService.pm.in
	(build_http_service)

share/ChangeLog  view on Meta::CPAN

	- + unlink
	(find_hosted_zone): new
	(create_alias)
	- required ALB DNS name and ALB zone now instead of find using ALB ARN
	- fix Changes payload
	(change_resource_record_sets)
	- die instead of croak
	- use unlink
	- payload already includes Changes
	(list_hosted_zones): new
	(validate_hosted_zones)
	- die instead of croak
	(find_alias_record): new
	* my-stack-minimal.yml

Thu Jul 24 14:50:31 2025  Rob Lauer  <rlauer6@comcast.net>

	[1.0.2]:
	* README.md: generated
	* VERSION: bump
	* lib/App/AWS.pm.in

share/ChangeLog  view on Meta::CPAN

	- cache subnets
	- use log_die
	(find_public_subnets): use cached version
	(find_private_subnets): likewise
	* lib/App/FargateStack.pm.in
	- +events, iam, logs, cache
	- pod updates
	(init)
	- setup cache str
	- log cache
	- don't validate images if cached
	(_init_account)
	- use cached account
	- use log_infi, log_die
	(_init_ec2)
	- pass subnets if caching
	- do not validate subnets if caching
	(help)
	- display list of subjects in ASCII table
	- use pager
	* lib/App/FargateStack/Builder.pm.in
	(update_config_id): new
	* lib/App/FargateStack/Builder/Cluster.pm.in
	- cache cluster
	- removed create_fargate_role
	* lib/App/FargateStack/Builder/EFS.pm.in
	(add_volumes)

share/README.md  view on Meta::CPAN

will output the result of the AWS CLI commands.
- (7) Use `--skip-register` if you want to update a tasks target
rule without registering a new task definition. This is typically done
if for some reason your target rule is out of sync with your task
definition version.
- (8) To speed up processing and avoid unnecessary API calls the
framework considers the configuration file the source of truth and a
reliable representation of the state of the stack. If you want to
re-sync the configuration file set `--no-cache` and run `plan`. In
most cases this should not be necessary as the framework will
invalidate the configuration if an error occurs forcing a re-sync on
the next run of `plan` or `apply`.
- (9) `--no-update` is not permitted with `apply`. If you need a
dry plan without applying or updating the config, use `--dryrun` (and
optionally `--no-update`) with `plan`.
- (10) Set `--route53-profile` to the profile that has
permissions to manage your hosted zones. By default the script will
use the default profile.
- (11) Deleting a task, daemon, or http service will delete all of
the resources associated with that task.
    - For scheduled tasks you can disable the job from running instead of

share/README.md  view on Meta::CPAN

      path: /
      readonly:

Acceptable values for `readonly` are "true" and "false".

## Field Descriptions

- id:

    The ID of an existing EFS filesystem. The framework does not provision
    the EFS, but will validate its existence in the current AWS account
    and region.

- mount\_point:

    The container path to which the EFS volume will be mounted.

- path:

    The path on the EFS filesystem to map to your container's mount point.

share/README.md  view on Meta::CPAN

- The ECS role's policy for your task is automatically modified
to allow read/write EFS access. Set `readonly:` in your task's
`efs:` section to "true" if only want read support.
- Your EFS security group must allow access from private subnets
where the Fargate tasks are placed.
- No changes are made to the EFS security group; the framework
assumes access is already configured
- Only one EFS volume is currently supported per task configuration.
- EFS volumes are task-scoped and reused only where explicitly configured.
- The framework does not automatically provision an EFS
filesystem for you. The framework does however validate that the
filesystem exists in the current account and region.

[Back to Table of Contents](#table-of-contents)

# CONFIGURATION

The `App::FargateStack` framework defines your application stack
using a YAML configuration file. This file describes your
application's services, their resource needs, and how they should be
deployed. Then configuration is updated whenever your run `plan` or

share/README.md  view on Meta::CPAN


If no subnets are specified in the configuration, the framework will query all
subnets in the selected VPC and categorize them as either public or private.

The task will be placed in a private subnet by default. For this to succeed,
your VPC must have at least one private subnet with a route to a NAT Gateway,
or have appropriate VPC endpoints configured for ECR, S3, STS, CloudWatch Logs,
and any other services your task needs.

If subnets are explicitly specified in your configuration, the
framework will validate them and warn if they are not reachable or are
not usable for Fargate tasks.

### Task placement and Availability Zones

The framework places each task's ENI into exactly one subnet, which fixes
that task in a single AZ. A service can span multiple AZs by listing
subnets from at least two AZs.

What the framework does:

share/README.md  view on Meta::CPAN

if any required resources are unavailable. If the task type is
"https", the script looks for a public zone, public subnets and an
internet-facing ALB otherwise it looks for a private zone, private
subnets and an internal ALB.

## ACM Certificate Management

If the task type is "https" and no ACM certificate currently exists
for your domain, the framework will automatically provision one. The
certificate will be created in the same region as the ALB and issued
via AWS Certificate Manager. If the certificate is validated  via DNS
and subsequently attached to the listener on port 443.

## Port and Listener Rules

For external-facing apps, a separate listener on port 80 is
created. It forwards traffic to port 443 using a default redirect rule
(301). If you do not want a redirect rule, set the `redirect_80:` in
the `alb:` section to "false".

If you want your internal application to listen on a port other than

share/README.md  view on Meta::CPAN

resource configurations (subnets, ALBs, Route 53, etc)._

Resources are provisioned and your configuration file is updated
incrementally as `app-FargateStack` compares your environment to the
environment required for your stack. When either plan or
apply complete your configuration is updated giving you complete
insight into what resources were found and what resources will be
provisioned. See [CONFIGURATION](https://metacpan.org/pod/CONFIGURATION) for complete details on resource
configurations.>

Your environment will be validated against the criteria described
below.

- You have at least 2 private subnets available for deployment

    Technically you can launch a task with only 1 subnet but for services
    behind an ALB Fargate requires 2 subnets.

    _When you create a service with a load balancer, you must specify
    two or more subnets in different Availability Zones. - AWS Docs_



( run in 0.783 second using v1.01-cache-2.11-cpan-2e29ac893d0 )