view release on metacpan or search on metacpan
ENVIRONMENT=prod
secrets:
db_password:DB_PASSWORD
efs:
id: fs-abcde12355
path: /
mount_point: /mnt/my-worker
Adding new resources would normally require you to update your
policies to allow your worker to access these resource. However, the
framework automatically detects that the policy needs to be updated
when new resources are added (even secrets) and takes care of that for
you.
See `app-Fargate help configuration` for more information about
resources and options.
## Configuration as State
The framework attempts to be as transparent as possible regarding what
it is doing, how long it takes, what the result was and most
importantly _what defaults were used during resource
provisioning_. Every time the framework is run, the configuration file
is updated based on any new resources provisioned or configured. For
example, if you did not specify subnets, they are inferred by
inspecting your VPC and automatically added to the configuration file.
This gives you a single view into your Fargate application
[Back to Table of Contents](#table-of-contents)
# CLI OPTION DEFAULTS
When enabled, `App::FargateStack` automatically remembers the most recently
This framework uses a single IAM role for all tasks defined within an
application stack. The assumption is that services within the stack
share a trust boundary and operate on shared infrastructure. This
simplifies IAM management while maintaining strict isolation between
stacks.
IAM roles and policies are automatically created based on your
configuration. Only the minimum required permissions are granted.
For example, if your configuration defines an S3 bucket, the ECS task
role will be permitted to access only that specific bucket - not all
buckets in your account. The policy is updated when new resources are
added to the configuration file.
The task execution role name and role policy name are found under the
`role:` key in the configuration. The task role is found under the
`task_role:` key. Role names and role policy names are automatically
fabricated for you from the name you specified under the `app:` key.
## Task Execution Role vs. Task Role
It's important to understand that App::FargateStack provisions two
[Back to Table of Contents](#table-of-contents)
# FILESYSTEM SUPPORT
EFS volumes are defined per task and mounted according to the task
definition. This design provides fine-grained control over EFS usage,
rather than treating it as a global, stack-level resource.
Each task that requires EFS support must include both a volume and
mountPoint configuration. The ECS task role is automatically updated
to allow EFS access based on your specification.
To specify EFS support in a task:
efs:
id: fs-1234567b
mount_point: /mnt/my-stack
path: /
readonly:
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
`apply`.
## GETTING STARTED
The fastest way to get up and running with `App::FargateStack` is to
use the `create-stack` command to generate a configuration file,
inspect the deployment plan, and then apply it.
### Step 1: Create a Configuration Stub
app:
name: # required
version:
certificate_arn:
cluster:
arn:
name:
default_log_group:
domain: # required for http/https tasks
id:
last_updated:
region:
role:
arn:
name:
policy_name:
route53:
profile:
zone_id:
security_groups:
alb:
groups, cluster, target group, etc)
- ...determining if an ACM certificate exists for your domain
(if type is "https")
_Note: Discovery of these resources is only done when they are
missing from your configuration. If you have multiple VPCs for example
you can should explicitly set `vpc_id:` in the configuration to
identify the target VPC. Likewise you can explicitly set other
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_
- You have a hosted zone for your domain of the appropriate type
(private for type "http", public for type "https")
As discovery progresses, existing and required resources are logged
and your configuration file is updated. If you are **NOT** running in
dryrun mode, resources will be created immediately as they are
discovered to be missing from your environment.
## Application Load Balancer
When you provision an HTTP service, whether or not it is secure, the
service will placed behind an application load balancer. Your Fargate
service is created in private subnets, so your VPC must contain at
least two private subnets. Your load balancer can either be
_internally_ or _externally facing_.
1. It generates a default `web-acl.json` file in your project
directory. This file contains the complete definition of your Web ACL,
including the rules generated from your `managed_rules` keywords.
2. It calls `aws wafv2 create-web-acl` to create a new Web ACL.
3. It calls `aws wafv2 associate-web-acl` to link the new Web ACL to
your Application Load Balancer.
4. It updates your configuration file with the state of the new
WAF resources, including its Name, ID, ARN, LockToken, and a checksum
of the `web-acl.json` file.
5. The `waf` block in your `fargate-stack.yml` is updated to reflect
the bootstrapped state. If the `managed_rules` key was not present,
it will be added with the default value of `[default]`.
### Ongoing Management (Subsequent Runs)
After the initial creation, you take full control of the rules. To
add, remove, or modify rules, you simply edit the `web-acl.json` file
directly.
On subsequent runs of `apply`, `App::FargateStack` will:
ECS uses the exact task definition revision as registered. If the
image was specified using a tag like `:latest`, ECS resolves that tag
once -- at the time the task starts -- and stores the resolved digest
(e.g. `sha256:...`).
This means:
- Tasks launched this way will continue to run the old image, even if
the `latest` tag in ECR now points to a newer image.
- The only way to run a task with the new image is to register a new
task definition that references the updated image. You can force a new
task definition by registering the definition.
app-FargateStack register my-task
### Services: `create-service` and `update-service` use frozen images too
When you create or update a service, ECS also resolves any image tags
to their current digest and stores that in the registered task
definition.
lib/App/FargateStack.pm view on Meta::CPAN
awsvpcConfiguration => {
subnets => [$subnet_id],
securityGroups => [ $security_groups->{fargate}->{group_id} ],
assignPublicIp => $is_public ? 'ENABLED' : 'DISABLED',
}
};
# check for latest image...
$self->check_latest_image($task_name);
# this may be null if we are in dryrun mode and the config has not been updated
my $cluster_name
= $dryrun && !$cluster->{name}
? sprintf '%s-cluster', $config->{app}->{name}
: $cluster->{name};
$self->log_warn( 'run-task: cluster: [%s] launching task: [%s] in subnet: [%s]...%s',
$cluster_name, $task_name, $subnet_id, $dryrun );
$self->log_trace( sub { return Dumper( [ awsvpcConfiguration => $network_configuration ] ); } );
lib/App/FargateStack.pm view on Meta::CPAN
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));
lib/App/FargateStack.pm view on Meta::CPAN
my $result = $ecs->update_service(
cluster_name => $cluster_name,
service_name => $service_name,
force => $TRUE
);
log_die( $self, "ERROR: could not update service: [%s]\n%s", $service_name, $ecs->get_error )
if !$result;
$self->log_info( 'redeploy: successfully updated service: [%s]', $service_name );
return $SUCCESS;
}
########################################################################
sub cmd_reset_history {
########################################################################
my ( $self, @args ) = @_;
$self->fetch_option_defaults($TRUE);
lib/App/FargateStack/Builder.pm view on Meta::CPAN
########################################################################
sub update_config_id {
########################################################################
my ($self) = @_;
$Data::Dumper::Sortkeys = $TRUE;
my $config = $self->get_config;
delete $config->{id};
delete $config->{last_updated};
my $md5_hex = md5_hex( Dumper( $self->get_config ) );
$config->{id} = $md5_hex;
$config->{last_updated} = scalar localtime;
return $config;
}
########################################################################
sub update_config {
########################################################################
my ($self) = @_;
$self->log_warn( 'builder: config file %s %s be updated', $self->get_config_name, $self->get_update ? 'will' : 'will not' );
return
if !$self->get_update;
my ( $fh, $filename ) = tempfile( 'fargate-stack-XXXX', SUFFIX => '.yml' );
my $config = $self->update_config_id();
DumpFile( $filename, $config );
lib/App/FargateStack/Builder.pm view on Meta::CPAN
# NOTE: Skeletons of deleted tasks are left in the confguration file
# for single task config and for multi-task configs. --purge-config
# will allow purging of tasks in multi-task configs only...
######################################################################
if ( $self->get_purge_config ) {
delete $tasks->{$task_name};
}
if ( !$dryrun ) {
delete $config->{id};
delete $config->{last_updated};
$self->update_config;
$self->log_info(
'builder: A skeleton configuration remains that will allow you to recreate the stack. Run "app-FargateStack plan" to rehydrate your configuration.'
);
}
return $SUCCESS;
}
lib/App/FargateStack/Builder/Autoscaling.pm view on Meta::CPAN
my $current_policy = $scaling_policy->{TargetTrackingScalingPolicyConfiguration};
my $diffs = $self->display_diffs( $policy_configuration, $current_policy );
if ($diffs) {
$self->log_error( "autoscaling: scaling policy: [%s] has changed:\n%s\n", $policy_name, $diffs );
log_die( $self, 'autoscaling: aborting update...use --force to force update' )
if !$self->get_force;
$self->log_warn( 'autoscaling: scaling policy: [%s] will be updated...%s', $policy_name, $dryrun );
if ( !$dryrun ) {
my $arn = $self->create_autoscaling_policy(
app_autoscaling => $app_autoscaling,
resource_id => $resource_id,
min_capacity => $autoscaling_config->get_min_capacity,
max_capacity => $autoscaling_config->get_max_capacity,
policy_name => $policy_name,
policy_configuration => $policy_configuration,
);
lib/App/FargateStack/Builder/Autoscaling.pm view on Meta::CPAN
join "\n",
sprintf '%s min_capacity:%s max_capacity: %s', $target_action->{Schedule},
$target_action->{ScalableTargetAction}->{MinCapacity},
$target_action->{ScalableTargetAction}->{MaxCapacity}
]
);
}
else {
$self->log_warn( 'autoscaling: scheduled action: [%s] for [%s] has changed...will be updated...%s',
$action_name, $scale_type, $dryrun );
$self->inc_required_resources( 'autoscaling:scheduled-action' => $action_name );
$put_scheduled_actions{$action_name} = $action;
}
}
}
######################################################################
# Sanity check the top-level metric based capacities and scheduled actions min/max capacities
######################################################################
lib/App/FargateStack/Builder/Events.pm view on Meta::CPAN
return;
}
if ($policy) {
$self->display_diffs( $policy, $role_policy, { title => 'event policy has changed' } );
}
$self->log_warn(
'iam:role-policy: policy [%s] will be %s for [%s]...%s',
$policy_name, ( $policy ? 'updated' : 'created' ),
$role_name, $dryrun
);
$self->inc_required_resources( 'iam:policy' => [$policy_name] );
return
if $dryrun;
$iam->put_role_policy( $role_name, $policy_name, $role_policy );
$iam->check_result(
message => 'ERROR: could not %s policy [%s] for [%s]',
params => [ ( $policy ? 'update' : 'create' ), $policy_name, $role_name ]
);
$self->log_warn( 'iam:role-policy: policy [%s] %s successfully for [%s]...',
$policy_name, ( $policy ? 'updated' : 'created' ), $role_name );
return;
}
########################################################################
sub update_rule_state {
########################################################################
my ( $self, $state ) = @_;
my ( $config, $tasks, $dryrun ) = $self->common_args(qw(config tasks dryrun));
lib/App/FargateStack/Builder/Events.pm view on Meta::CPAN
my $rule_name = sprintf '%s-schedule', $task_name;
require App::Events;
my $event = App::Events->new( $self->get_global_options );
my $result = $event->describe_rule( $rule_name, '{state: State, schedule: ScheduleExpression}' );
log_die( $self, "could not describe rule: [%s]\n%s", $rule_name, $event->get_error )
if !$result;
$self->log_warn( 'events: current state: [%s] for rule [%s]...will be updated do [%s]...%s',
$result->{state}, $rule_name, ( $state ? 'ENABLED' : 'DISABLED' ), $dryrun );
return
if $dryrun;
$result = $state ? $event->enable_rule($rule_name) : $event->disable_rule($rule_name);
log_die( $self, "could not update rule: [%s]\n%s", $rule_name, $event->get_error )
if !$result && $event->get_error;
lib/App/FargateStack/Builder/IAM.pm view on Meta::CPAN
$config->{role} = $role;
$self->log_trace( sub { return Dumper( [ role => $role ] ) } );
my ( $role_name, $role_arn ) = $self->create_fargate_role();
my $policy_name = $role->{policy_name} // $self->create_default( 'policy-name', 'ecs' );
@{$role}{qw(name arn policy_name)} = ( $role_name, $role_arn, $policy_name );
######################################################################
# create policy - see if policy needs to be created or updated
######################################################################
$self->create_policy( $iam, 'ecs' );
######################################################################
# create task role
######################################################################
my $task_role = $config->{task_role} // {};
$config->{task_role} = $task_role;
$self->log_trace( sub { return Dumper( [ task_role => $task_role ] ) } );
lib/App/FargateStack/Builder/IAM.pm view on Meta::CPAN
########################################################################
my ( $self, $iam, $type ) = @_;
my ( $config, $dryrun ) = $self->common_args(qw(config dryrun));
my $role = $type eq 'ecs' ? $config->{role} : $config->{task_role};
my ( $policy_name, $role_name ) = @{$role}{qw(policy_name name)};
######################################################################
# create policy - see if policy needs to be created or updated
######################################################################
# if we turned caching off OR we don't have an ARN yet, check to see
# if the policy exists
my $policy = $iam->get_role_policy( $role_name, $policy_name );
$iam->check_result(
message => 'ERROR: could not get role policy: [%s] for role [%s]',
params => [ $policy_name, $role_name ],
regexp => qr/cannot\sbe\sfound/xsmi
lib/App/FargateStack/Builder/LogGroup.pm view on Meta::CPAN
$config->{log_group}->{name} = $name; # just in case
$config->{log_group}->{arn} = $arn;
$self->log_info( 'logs: [%s] exists...%s', $name, 'skipping' );
$self->inc_existing_resources( log_group => $arn );
if ( exists $config->{log_group}->{retention_days} && $config->{log_group}->{retention_days} ) {
if ( $retention_days != $config->{log_group}->{retention_days} ) {
$self->log_warn( 'logs: [%s] retention policy has changed...will be updated...%s', $name, $dryrun );
$self->inc_required_resources( logs => sprintf 'update retention days to %s', $config->{log_group}->{retention_days} );
if ( !$dryrun ) {
$logs->put_retention_policy( $name, $config->{log_group}->{retention_days} );
}
}
}
else {
$self->log_error('logs: missing log retention policy. Logs will be stored indefinitely!');
if ($retention_days) {
lib/App/FargateStack/Builder/TaskDefinition.pm view on Meta::CPAN
# -- port mappings --
my $portMapping = define_port_mapping( $task, $type );
if ($portMapping) {
@{$task}{qw(container_port host_port)} = @{ $portMapping->[0] }{qw(containerPort hostPort)};
}
# -- log group/stream prefix --
#
# Note: the default log group name was set earlier...and task will
# be updated when (and if) we create the log group...
my $log_group = $config->{log_group}->{name};
my $stream_prefix = $config->{app}->{name};
# -- image name --
my $image = $self->resolve_image_name( $task->{image} );
# -- environment --
my @environment
= map { { name => $_, value => $task->{environment}->{$_} } } keys %{ $task->{environment} // {} };
lib/App/FargateStack/Builder/WafV2.pm view on Meta::CPAN
$waf->check_result( message => 'ERROR: could not list web acls' );
if ( $web_acl && @{$web_acl} ) {
my $web_acl_id = $web_acl->[0]->{Id};
$web_acl = $waf->get_web_acl( name => $name, id => $web_acl_id );
$waf->check_result( message => 'ERROR: could not get web-acl: [%s]', $name );
my $web_acl_arn = $web_acl->{WebACL}->{ARN};
# check to see if rules have been updated
my $new_rules = $self->check_rules( waf => $waf, waf_config => $waf_config );
# check to see if someone has mucked with web-acl.json
my $needs_update = $self->check_web_acl_state(
name => $name,
id => $web_acl_id,
waf_config => $waf_config,
waf => $waf,
web_acl => $web_acl,
rules => $new_rules,
);
# we would have died already if there was a conflict, so now we
# have either FALSE = no update needed or TRUE = need to update web-acl
if ($needs_update) {
$self->log_warn( 'waf: web-acl: [%s] has changed...will be updated...%s', $name, $dryrun );
if ( !$dryrun ) {
$self->update_web_acl( $waf_config->{lock_token} );
$self->save_web_acl(
id => $waf_config->{id},
scope => 'REGIONAL',
name => $name,
waf_config => $waf_config,
waf => $waf,
lib/App/FargateStack/Builder/WafV2.pm view on Meta::CPAN
return $FALSE # no need to update, everything OK
if $md5_match && $lock_token_match;
return $TRUE # update OK
if !$md5_match && $lock_token_match;
if ( $md5_match && !$lock_token_match ) {
my $err_msg = <<'END_OF_ERR_MESSAGE';
WAF configuration appears to be in sync, but the remote resource has
abeen modified out-of-band. The local configuration file is being updated with
the latest LockToken. Please run 'plan' again to confirm.
END_OF_ERR_MESSAGE
$waf_config->{id} = $aws_id;
$waf_config->{arn} = $aws_arn;
$waf_config->{lock_token} = $aws_lock_token;
return $FALSE; # no need to update, just update local state
}
# if we are here, then conflict !$md5_match && !$lock_token_match
lib/App/FargateStack/Init.pm view on Meta::CPAN
# provisioned resources
if ( $self->command =~ /^(?:apply|destroy|delete-)$/xsmi ) {
$SIG{__DIE__} = sub {
my $msg = shift;
if ($EXCEPTIONS_BEING_CAUGHT) { # eval
warn $msg;
return;
}
# if config exists...we have removed last_updated and id
if ( $config && $config->{config_name} ) {
warn sprintf "Unclean shutdown - writing config file to [%s]\n", $config->{config_name};
eval { YAML::DumpFile( $config->{config_name}, $config ); }
}
die $msg;
};
}
return $TRUE;
lib/App/FargateStack/Init.pm view on Meta::CPAN
$self->set_region($region);
return $region;
}
########################################################################
sub _init_defaults {
########################################################################
my ( $self, $config ) = @_;
my $last_updated = delete $config->{last_updated};
delete $config->{id};
$config->{region} = $self->default_region( $config->{region} );
my $profile = $self->get_profile;
my $profile_source = 'command line';
if ( !$profile && $config->{profile} ) {
$profile = $config->{profile};
$profile_source = 'config';
lib/App/FargateStack/Init.pm view on Meta::CPAN
profile => $self->get_profile,
region => $self->get_region,
logger => $self->get_logger,
log_level => $self->get_log_level,
unlink => $self->get_unlink,
);
$self->set_global_options( \%global_options );
my $cache = $self->get_cache;
$self->set_cache( $cache && $last_updated ? '(cached)' : $EMPTY );
return;
}
########################################################################
sub _init_route53 {
########################################################################
my ($self) = @_;
my $command = $self->command;
lib/App/FargateStack/Pod.pm view on Meta::CPAN
ENVIRONMENT=prod
secrets:
db_password:DB_PASSWORD
efs:
id: fs-abcde12355
path: /
mount_point: /mnt/my-worker
Adding new resources would normally require you to update your
policies to allow your worker to access these resource. However, the
framework automatically detects that the policy needs to be updated
when new resources are added (even secrets) and takes care of that for
you.
See C<app-Fargate help configuration> for more information about
resources and options.
=head2 Configuration as State
The framework attempts to be as transparent as possible regarding what
it is doing, how long it takes, what the result was and most
importantly I<what defaults were used during resource
provisioning>. Every time the framework is run, the configuration file
is updated based on any new resources provisioned or configured. For
example, if you did not specify subnets, they are inferred by
inspecting your VPC and automatically added to the configuration file.
This gives you a single view into your Fargate application
=head1 CLI OPTION DEFAULTS
When enabled, C<App::FargateStack> automatically remembers the most recently
used values for several CLI options between runs. This feature helps streamline
repetitive workflows by eliminating the need to re-specify common arguments
lib/App/FargateStack/Pod.pm view on Meta::CPAN
This framework uses a single IAM role for all tasks defined within an
application stack. The assumption is that services within the stack
share a trust boundary and operate on shared infrastructure. This
simplifies IAM management while maintaining strict isolation between
stacks.
IAM roles and policies are automatically created based on your
configuration. Only the minimum required permissions are granted.
For example, if your configuration defines an S3 bucket, the ECS task
role will be permitted to access only that specific bucket - not all
buckets in your account. The policy is updated when new resources are
added to the configuration file.
The task execution role name and role policy name are found under the
C<role:> key in the configuration. The task role is found under the
C<task_role:> key. Role names and role policy names are automatically
fabricated for you from the name you specified under the C<app:> key.
=head2 Task Execution Role vs. Task Role
It's important to understand that App::FargateStack provisions two
lib/App/FargateStack/Pod.pm view on Meta::CPAN
automatically authorized for ingress to your Fargate task. This is a
rule allowing ALB-to-Fargate traffic.
=head1 FILESYSTEM SUPPORT
EFS volumes are defined per task and mounted according to the task
definition. This design provides fine-grained control over EFS usage,
rather than treating it as a global, stack-level resource.
Each task that requires EFS support must include both a volume and
mountPoint configuration. The ECS task role is automatically updated
to allow EFS access based on your specification.
To specify EFS support in a task:
efs:
id: fs-1234567b
mount_point: /mnt/my-stack
path: /
readonly:
lib/App/FargateStack/Pod.pm view on Meta::CPAN
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
C<apply>.
=head2 GETTING STARTED
The fastest way to get up and running with C<App::FargateStack> is to
use the C<create-stack> command to generate a configuration file,
inspect the deployment plan, and then apply it.
=head3 Step 1: Create a Configuration Stub
lib/App/FargateStack/Pod.pm view on Meta::CPAN
app:
name: # required
version:
certificate_arn:
cluster:
arn:
name:
default_log_group:
domain: # required for http/https tasks
id:
last_updated:
region:
role:
arn:
name:
policy_name:
route53:
profile:
zone_id:
security_groups:
alb:
lib/App/FargateStack/Pod.pm view on Meta::CPAN
(if type is "https")
=back
I<Note: Discovery of these resources is only done when they are
missing from your configuration. If you have multiple VPCs for example
you can should explicitly set C<vpc_id:> in the configuration to
identify the target VPC. Likewise you can explicitly set other
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
lib/App/FargateStack/Pod.pm view on Meta::CPAN
I<When you create a service with a load balancer, you must specify
two or more subnets in different Availability Zones. - AWS Docs>
=item * You have a hosted zone for your domain of the appropriate type
(private for type "http", public for type "https")
=back
As discovery progresses, existing and required resources are logged
and your configuration file is updated. If you are B<NOT> running in
dryrun mode, resources will be created immediately as they are
discovered to be missing from your environment.
=head2 Application Load Balancer
When you provision an HTTP service, whether or not it is secure, the
service will placed behind an application load balancer. Your Fargate
service is created in private subnets, so your VPC must contain at
least two private subnets. Your load balancer can either be
I<internally> or I<externally facing>.
lib/App/FargateStack/Pod.pm view on Meta::CPAN
your Application Load Balancer.
=item 4.
It updates your configuration file with the state of the new
WAF resources, including its Name, ID, ARN, LockToken, and a checksum
of the F<web-acl.json> file.
=item 5.
The C<waf> block in your F<fargate-stack.yml> is updated to reflect
the bootstrapped state. If the C<managed_rules> key was not present,
it will be added with the default value of C<[default]>.
=back
=head3 Ongoing Management (Subsequent Runs)
After the initial creation, you take full control of the rules. To
add, remove, or modify rules, you simply edit the F<web-acl.json> file
directly.
lib/App/FargateStack/Pod.pm view on Meta::CPAN
=over 4
=item *
Tasks launched this way will continue to run the old image, even if
the C<latest> tag in ECR now points to a newer image.
=item *
The only way to run a task with the new image is to register a new
task definition that references the updated image. You can force a new
task definition by registering the definition.
app-FargateStack register my-task
=back
=head3 Services: C<create-service> and C<update-service> use frozen images too
When you create or update a service, ECS also resolves any image tags
to their current digest and stores that in the registered task
share/ChangeLog view on Meta::CPAN
- add new options, commands
Mon Aug 11 17:40:17 2025 Rob Lauer <rlauer6@comcast.net>
[1.0.29]:
* ChangeLog
* VERSION: bump
* README.md: generated
* lib/App/FargateStack/Builder.pm.in
(delete_task_resources)
- delete id, last_updated
- log a message about skeleton config
(_delete_security_group)
- wait for ENI to release ALB security group
* lib/App/FargateStack/Builder/Cluster.pm.in
(build_fargate_cluster)
- make sure we set cluster info in config
- check_result after api call
* lib/App/FargateStack/Builder/HTTPService.pm.in
(create_alb): add tags
* lib/App/FargateStack/Init.pm.in
share/ChangeLog view on Meta::CPAN
- message about creation of new ALB if requested in config
- use create_default() for ALB name
(create_alias): refactoring
* lib/App/FargateStack/Builder/Utils.pm.in
- export confirm()
(confirm): new
(is_service_running): new
* lib/App/FargateStack/Constants.pm.in
(DEFAULT_NAMES): added more default naming subs
* lib/App/FargateStack/Pod.pm.in
- updated to reflect new commands, options
Tue Aug 5 16:04:58 2025 Rob Lauer <rlauer6@comcast.net>
[1.0.21]:
* README.md: generated
* VERSION: bump
* lib/App/FargateStack.pm.in
(help): allow help {command}
* lib/App/FargateStack/Pod.pm.in: wordsmithing
share/ChangeLog view on Meta::CPAN
* Makefile
- new target 'requires'
- add TOC to README.md
* README-FARGATE.md: minor tweaks
* VERSION: bump
* lib/App/Command.pm.in: trace message to dump command
* lib/App/ECS.pm.in
(list_tasks): new
(stop_task): new
* lib/App/FargateStack.pm.in
- updated pod
(init)
- add additional command that log @ error level
- use english $^S
(cmd_service_status): wrap events at 100 columns
(_init_config)
- use fargate-stack.yml as default
- allow enviroment variable for default configuration name
(cmd_update_service)
- add a sleep so we can get updated status
(cmd_register)
- refactoring
- update config with new arn
(help)
- show USAGE for help
(cmd_stop_task): new
(cmd_list_tasks): new
(cmd_update_policy): new
(main)
- - --force
share/ChangeLog view on Meta::CPAN
* VERSION: bump
* 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
share/README.md view on Meta::CPAN
ENVIRONMENT=prod
secrets:
db_password:DB_PASSWORD
efs:
id: fs-abcde12355
path: /
mount_point: /mnt/my-worker
Adding new resources would normally require you to update your
policies to allow your worker to access these resource. However, the
framework automatically detects that the policy needs to be updated
when new resources are added (even secrets) and takes care of that for
you.
See `app-Fargate help configuration` for more information about
resources and options.
## Configuration as State
The framework attempts to be as transparent as possible regarding what
it is doing, how long it takes, what the result was and most
importantly _what defaults were used during resource
provisioning_. Every time the framework is run, the configuration file
is updated based on any new resources provisioned or configured. For
example, if you did not specify subnets, they are inferred by
inspecting your VPC and automatically added to the configuration file.
This gives you a single view into your Fargate application
[Back to Table of Contents](#table-of-contents)
# CLI OPTION DEFAULTS
When enabled, `App::FargateStack` automatically remembers the most recently
share/README.md view on Meta::CPAN
This framework uses a single IAM role for all tasks defined within an
application stack. The assumption is that services within the stack
share a trust boundary and operate on shared infrastructure. This
simplifies IAM management while maintaining strict isolation between
stacks.
IAM roles and policies are automatically created based on your
configuration. Only the minimum required permissions are granted.
For example, if your configuration defines an S3 bucket, the ECS task
role will be permitted to access only that specific bucket - not all
buckets in your account. The policy is updated when new resources are
added to the configuration file.
The task execution role name and role policy name are found under the
`role:` key in the configuration. The task role is found under the
`task_role:` key. Role names and role policy names are automatically
fabricated for you from the name you specified under the `app:` key.
## Task Execution Role vs. Task Role
It's important to understand that App::FargateStack provisions two
share/README.md view on Meta::CPAN
[Back to Table of Contents](#table-of-contents)
# FILESYSTEM SUPPORT
EFS volumes are defined per task and mounted according to the task
definition. This design provides fine-grained control over EFS usage,
rather than treating it as a global, stack-level resource.
Each task that requires EFS support must include both a volume and
mountPoint configuration. The ECS task role is automatically updated
to allow EFS access based on your specification.
To specify EFS support in a task:
efs:
id: fs-1234567b
mount_point: /mnt/my-stack
path: /
readonly:
share/README.md view on Meta::CPAN
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
`apply`.
## GETTING STARTED
The fastest way to get up and running with `App::FargateStack` is to
use the `create-stack` command to generate a configuration file,
inspect the deployment plan, and then apply it.
### Step 1: Create a Configuration Stub
share/README.md view on Meta::CPAN
app:
name: # required
version:
certificate_arn:
cluster:
arn:
name:
default_log_group:
domain: # required for http/https tasks
id:
last_updated:
region:
role:
arn:
name:
policy_name:
route53:
profile:
zone_id:
security_groups:
alb:
share/README.md view on Meta::CPAN
groups, cluster, target group, etc)
- ...determining if an ACM certificate exists for your domain
(if type is "https")
_Note: Discovery of these resources is only done when they are
missing from your configuration. If you have multiple VPCs for example
you can should explicitly set `vpc_id:` in the configuration to
identify the target VPC. Likewise you can explicitly set other
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_
- You have a hosted zone for your domain of the appropriate type
(private for type "http", public for type "https")
As discovery progresses, existing and required resources are logged
and your configuration file is updated. If you are **NOT** running in
dryrun mode, resources will be created immediately as they are
discovered to be missing from your environment.
## Application Load Balancer
When you provision an HTTP service, whether or not it is secure, the
service will placed behind an application load balancer. Your Fargate
service is created in private subnets, so your VPC must contain at
least two private subnets. Your load balancer can either be
_internally_ or _externally facing_.
share/README.md view on Meta::CPAN
1. It generates a default `web-acl.json` file in your project
directory. This file contains the complete definition of your Web ACL,
including the rules generated from your `managed_rules` keywords.
2. It calls `aws wafv2 create-web-acl` to create a new Web ACL.
3. It calls `aws wafv2 associate-web-acl` to link the new Web ACL to
your Application Load Balancer.
4. It updates your configuration file with the state of the new
WAF resources, including its Name, ID, ARN, LockToken, and a checksum
of the `web-acl.json` file.
5. The `waf` block in your `fargate-stack.yml` is updated to reflect
the bootstrapped state. If the `managed_rules` key was not present,
it will be added with the default value of `[default]`.
### Ongoing Management (Subsequent Runs)
After the initial creation, you take full control of the rules. To
add, remove, or modify rules, you simply edit the `web-acl.json` file
directly.
On subsequent runs of `apply`, `App::FargateStack` will:
share/README.md view on Meta::CPAN
ECS uses the exact task definition revision as registered. If the
image was specified using a tag like `:latest`, ECS resolves that tag
once -- at the time the task starts -- and stores the resolved digest
(e.g. `sha256:...`).
This means:
- Tasks launched this way will continue to run the old image, even if
the `latest` tag in ECR now points to a newer image.
- The only way to run a task with the new image is to register a new
task definition that references the updated image. You can force a new
task definition by registering the definition.
app-FargateStack register my-task
### Services: `create-service` and `update-service` use frozen images too
When you create or update a service, ECS also resolves any image tags
to their current digest and stores that in the registered task
definition.