internal/pkg/template/workload.go (768 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package template
import (
"bytes"
"crypto/sha256"
"fmt"
"slices"
"strconv"
"text/template"
"github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/dustin/go-humanize/english"
"github.com/google/uuid"
"github.com/aws/aws-sdk-go/aws"
)
// Constants for template paths.
const (
// Paths of workload cloudformation templates under templates/workloads/.
fmtWkldCFTemplatePath = "workloads/%s/%s/cf.yml"
fmtWkldPartialsCFTemplatePath = "workloads/partials/cf/%s.yml"
// Directories under templates/workloads/.
servicesDirName = "services"
jobDirName = "jobs"
// Names of workload templates.
lbWebSvcTplName = "lb-web"
rdWebSvcTplName = "rd-web"
backendSvcTplName = "backend"
workerSvcTplName = "worker"
staticSiteTplName = "static-site"
scheduledJobTplName = "scheduled-job"
)
// Constants for workload options.
const (
// AWS VPC networking configuration.
EnablePublicIP = "ENABLED"
DisablePublicIP = "DISABLED"
PublicSubnetsPlacement = "PublicSubnets"
PrivateSubnetsPlacement = "PrivateSubnets"
// RuntimePlatform configuration.
OSLinux = "LINUX"
OSWindowsServerFull = OSWindowsServer2019Full // Alias 2019 as Default WindowsSever Full platform.
OSWindowsServerCore = OSWindowsServer2019Core // Alias 2019 as Default WindowsSever Core platform.
OSWindowsServer2019Full = "WINDOWS_SERVER_2019_FULL"
OSWindowsServer2019Core = "WINDOWS_SERVER_2019_CORE"
OSWindowsServer2022Full = "WINDOWS_SERVER_2022_FULL"
OSWindowsServer2022Core = "WINDOWS_SERVER_2022_CORE"
ArchX86 = "X86_64"
ArchARM64 = "ARM64"
)
// Constants for ARN options.
const (
snsARNPattern = "arn:%s:sns:%s:%s:%s-%s-%s-%s"
)
// Constants for stack resource logical IDs
const (
LogicalIDHTTPListenerRuleWithDomain = "HTTPListenerRuleWithDomain"
)
const (
// NoExposedContainerPort indicates no port should be exposed for the service container.
NoExposedContainerPort = "-1"
)
var (
// Template names under "workloads/partials/cf/".
partialsWorkloadCFTemplateNames = []string{
"loggroup",
"envvars-container",
"envvars-common",
"secrets",
"executionrole",
"taskrole",
"workload-container",
"fargate-taskdef-base-properties",
"service-base-properties",
"servicediscovery",
"addons",
"sidecars",
"logconfig",
"autoscaling",
"eventrule",
"state-machine",
"state-machine-definition.json",
"efs-access-point",
"https-listener",
"http-listener",
"env-controller",
"mount-points",
"variables",
"volumes",
"image-overrides",
"instancerole",
"accessrole",
"publish",
"subscribe",
"nlb",
"vpc-connector",
"alb",
"rollback-alarms",
"imported-alb-resources",
}
// Operating systems to determine Fargate platform versions.
osFamiliesForPV100 = []string{
OSWindowsServer2019Full, OSWindowsServer2019Core, OSWindowsServer2022Full, OSWindowsServer2022Core,
}
)
// WorkloadNestedStackOpts holds configuration that's needed if the workload stack has a nested stack.
type WorkloadNestedStackOpts struct {
StackName string
VariableOutputs []string
SecretOutputs []string
PolicyOutputs []string
SecurityGroupOutputs []string
}
// SidecarOpts holds configuration that's needed if the service has sidecar containers.
type SidecarOpts struct {
Name string
Image *string
Essential *bool
CredsParam *string
Variables map[string]Variable
Secrets map[string]Secret
Storage SidecarStorageOpts
DockerLabels map[string]string
DependsOn map[string]string
EntryPoint []string
Command []string
HealthCheck *ContainerHealthCheck
PortMappings []*PortMapping
}
// PortMapping holds container port mapping configuration.
type PortMapping struct {
Protocol string
ContainerPort uint16
ContainerName string
}
// SidecarStorageOpts holds data structures for rendering Mount Points inside of a sidecar.
type SidecarStorageOpts struct {
MountPoints []*MountPoint
}
// StorageOpts holds data structures for rendering Volumes and Mount Points
type StorageOpts struct {
Ephemeral *int
ReadonlyRootFS *bool
Volumes []*Volume
MountPoints []*MountPoint
EFSPerms []*EFSPermission
ManagedVolumeInfo *ManagedVolumeCreationInfo // Used for delegating CreationInfo for Copilot-managed EFS.
}
// requiresEFSCreation returns true if managed volume information is specified; false otherwise.
func (s *StorageOpts) requiresEFSCreation() bool {
return s.ManagedVolumeInfo != nil
}
// EFSPermission holds information needed to render an IAM policy statement.
type EFSPermission struct {
FilesystemID FileSystemID
Write bool
AccessPointID *string
}
// MountPoint holds information needed to render a MountPoint in a containerdefinition.
type MountPoint struct {
ContainerPath *string
ReadOnly *bool
SourceVolume *string
}
// Volume contains fields that render a volume, its name, and EFSVolumeConfiguration
type Volume struct {
Name *string
EFS *EFSVolumeConfiguration
}
// ManagedVolumeCreationInfo holds information about how to create Copilot-managed access points.
type ManagedVolumeCreationInfo struct {
Name *string
DirName *string
UID *uint32
GID *uint32
}
// EFSVolumeConfiguration contains information about how to specify externally managed file systems.
type EFSVolumeConfiguration struct {
// EFSVolumeConfiguration
Filesystem FileSystemID
RootDirectory *string // "/" or empty are equivalent
// Authorization Config
AccessPointID *string
IAM *string // ENABLED or DISABLED
}
// LogConfigOpts holds configuration that's needed if the service is configured with Firelens to route
// its logs.
type LogConfigOpts struct {
Image *string
Destination map[string]string
EnableMetadata *string
SecretOptions map[string]Secret
ConfigFile *string
Variables map[string]Variable
Secrets map[string]Secret
}
// StrconvUint16 returns string converted from uint16.
func StrconvUint16(val uint16) string {
return strconv.FormatUint(uint64(val), 10)
}
// HTTPHealthCheckOpts holds configuration that's needed for HTTP Health Check.
type HTTPHealthCheckOpts struct {
// Fields with defaults always set.
HealthCheckPath string
GracePeriod int64
// Optional.
Port string
SuccessCodes string
HealthyThreshold *int64
UnhealthyThreshold *int64
Interval *int64
Timeout *int64
DeregistrationDelay *int64
}
type importable interface {
RequiresImport() bool
}
type importableValue interface {
importable
Value() string
}
// FileSystemID represnts the EFS FilesystemID.
type FileSystemID importableValue
// PlainFileSystemID returns a EFS FilesystemID that is a plain string value.
func PlainFileSystemID(value string) FileSystemID {
return plainFileSystemID(value)
}
// ImportedFileSystemID returns a EFS FilesystemID that is imported from a stack.
func ImportedFileSystemID(value string) FileSystemID {
return importedFileSystemID(value)
}
type plainFileSystemID string
// RequiresImport returns false for a plain EFS FilesystemID.
func (fs plainFileSystemID) RequiresImport() bool {
return false
}
// Value returns the plain string value of the plain EFS FilesystemID.
func (fs plainFileSystemID) Value() string {
return string(fs)
}
type importedFileSystemID string
// RequiresImport returns true for a imported EFS FilesystemID.
func (fs importedFileSystemID) RequiresImport() bool {
return true
}
// Value returns the name of the import that will be the value of the EFS Filesystem ID.
func (fs importedFileSystemID) Value() string {
return string(fs)
}
// Variable represents the value of an environment variable.
type Variable importableValue
// ImportedVariable returns a Variable that should be imported from a stack.
func ImportedVariable(name string) Variable {
return importedEnvVar(name)
}
// PlainVariable returns a Variable that is a plain string value.
func PlainVariable(value string) Variable {
return plainEnvVar(value)
}
type plainEnvVar string
// RequiresImport returns false for a plain string environment variable.
func (v plainEnvVar) RequiresImport() bool {
return false
}
// Value returns the plain string value of the environment variable.
func (v plainEnvVar) Value() string {
return string(v)
}
type importedEnvVar string
// RequiresImport returns true for an imported environment variable.
func (v importedEnvVar) RequiresImport() bool {
return true
}
// Value returns the name of the import that will be the value of the environment variable.
func (v importedEnvVar) Value() string {
return string(v)
}
type importableSubValueFrom interface {
importable
RequiresSub() bool
ValueFrom() string
}
// A Secret represents an SSM or SecretsManager secret that can be rendered in CloudFormation.
type Secret importableSubValueFrom
// plainSSMOrSecretARN is a Secret stored that can be referred by an SSM Parameter Name or a secret ARN.
type plainSSMOrSecretARN struct {
value string
}
// RequiresSub returns true if the secret should be populated in CloudFormation with !Sub.
func (s plainSSMOrSecretARN) RequiresSub() bool {
return false
}
// RequiresImport returns true if the secret should be imported from other CloudFormation stack.
func (s plainSSMOrSecretARN) RequiresImport() bool {
return false
}
// ValueFrom returns the plain string value of the secret.
func (s plainSSMOrSecretARN) ValueFrom() string {
return s.value
}
// SecretFromPlainSSMOrARN returns a Secret that refers to an SSM parameter or a secret ARN.
func SecretFromPlainSSMOrARN(value string) plainSSMOrSecretARN {
return plainSSMOrSecretARN{
value: value,
}
}
// importedSSMorSecretARN is a Secret that can be referred by the name of the import value from env addon or an arbitary CloudFormation stack.
type importedSSMorSecretARN struct {
value string
}
// RequiresSub returns true if the secret should be populated in CloudFormation with !Sub.
func (s importedSSMorSecretARN) RequiresSub() bool {
return false
}
// RequiresImport returns true if the secret should be imported from env addon or an arbitary CloudFormation stack.
func (s importedSSMorSecretARN) RequiresImport() bool {
return true
}
// ValueFrom returns the name of the import value of the Secret.
func (s importedSSMorSecretARN) ValueFrom() string {
return s.value
}
// SecretFromImportedSSMOrARN returns a Secret that refers to imported name of SSM parameter or a secret ARN.
func SecretFromImportedSSMOrARN(value string) importedSSMorSecretARN {
return importedSSMorSecretARN{
value: value,
}
}
// secretsManagerName is a Secret that can be referred by a SecretsManager secret name.
type secretsManagerName struct {
value string
}
// RequiresSub returns true if the secret should be populated in CloudFormation with !Sub.
func (s secretsManagerName) RequiresSub() bool {
return true
}
// RequiresImport returns true if the secret should be imported from other CloudFormation stack.
func (s secretsManagerName) RequiresImport() bool {
return false
}
// ValueFrom returns the resource ID of the SecretsManager secret for populating the ARN.
func (s secretsManagerName) ValueFrom() string {
return fmt.Sprintf("secret:%s", s.value)
}
// Service returns the name of the SecretsManager service for populating the ARN.
func (s secretsManagerName) Service() string {
return secretsmanager.ServiceName
}
// SecretFromSecretsManager returns a Secret that refers to SecretsManager secret name.
func SecretFromSecretsManager(value string) secretsManagerName {
return secretsManagerName{
value: value,
}
}
// NetworkLoadBalancerListener holds configuration that's need for a Network Load Balancer listener.
type NetworkLoadBalancerListener struct {
// The port and protocol that the Network Load Balancer listens to.
Port string
Protocol string
// The target container and port to which the traffic is routed to from the Network Load Balancer.
TargetContainer string
TargetPort string
SSLPolicy *string // The SSL policy applied when using TLS protocol.
Stickiness *bool
HealthCheck NLBHealthCheck
DeregistrationDelay *int64
}
// NLBHealthCheck holds configuration for Network Load Balancer health check.
type NLBHealthCheck struct {
Port string // The port to which health check requests made from Network Load Balancer are routed to.
HealthyThreshold *int64
UnhealthyThreshold *int64
Timeout *int64
Interval *int64
GracePeriod *int64
}
// NetworkLoadBalancer holds configuration that's needed for a Network Load Balancer.
type NetworkLoadBalancer struct {
Listener []NetworkLoadBalancerListener
MainContainerPort string
CertificateRequired bool
Aliases []string
}
// ALBListenerRule holds configuration that's needed for an Application Load Balancer listener rule.
type ALBListenerRule struct {
// The path that the Application Load Balancer listens to.
Path string
// The target container and port to which the traffic is routed to from the Application Load Balancer.
TargetContainer string
TargetPort string
Aliases []string
AllowedSourceIps []string
Stickiness string
HTTPHealthCheck HTTPHealthCheckOpts
HTTPVersion string
RedirectToHTTPS bool // Only relevant if HTTPSListener is true.
DeregistrationDelay *int64
}
// ALBListener holds configuration that's needed for an Application Load Balancer Listener.
type ALBListener struct {
Rules []ALBListenerRule
HostedZoneAliases AliasesForHostedZone
IsHTTPS bool // True if the listener listening on port 443.
MainContainerPort string
}
// Aliases return all the unique aliases specified across all the routing rules in ALB.
func (cfg *ALBListener) Aliases() []string {
var uniqueAliases []string
seen := make(map[string]struct{})
exists := struct{}{}
for _, rule := range cfg.Rules {
for _, entry := range rule.Aliases {
if _, value := seen[entry]; !value {
uniqueAliases = append(uniqueAliases, entry)
seen[entry] = exists
}
}
}
return uniqueAliases
}
// RulePaths returns a slice consisting of all the routing paths mentioned across multiple listener rules.
func (cfg *ALBListener) RulePaths() []string {
var rulePaths []string
for _, rule := range cfg.Rules {
rulePaths = append(rulePaths, rule.Path)
}
return rulePaths
}
// ServiceConnectOpts defines the options for service connect.
// If Client is false, logically Server must be nil.
type ServiceConnectOpts struct {
Server *ServiceConnectServer
Client bool
}
// ServiceConnectServer defines the container name and port which a service routes Service Connect through.
type ServiceConnectServer struct {
Name string
Port string
Alias string
}
// AdvancedCount holds configuration for autoscaling and capacity provider
// parameters.
type AdvancedCount struct {
Spot *int
Autoscaling *AutoscalingOpts
Cps []*CapacityProviderStrategy
}
// ContainerHealthCheck holds configuration for container health check.
type ContainerHealthCheck struct {
Command []string
Interval *int64
Retries *int64
StartPeriod *int64
Timeout *int64
}
// CapacityProviderStrategy holds the configuration needed for a
// CapacityProviderStrategyItem on a Service
type CapacityProviderStrategy struct {
Base *int
Weight *int
CapacityProvider string
}
// Cooldown holds configuration needed for autoscaling cooldown fields.
type Cooldown struct {
ScaleInCooldown *float64
ScaleOutCooldown *float64
}
// AutoscalingOpts holds configuration that's needed for Auto Scaling.
type AutoscalingOpts struct {
MinCapacity *int
MaxCapacity *int
CPU *float64
Memory *float64
Requests *float64
ResponseTime *float64
CPUCooldown Cooldown
MemCooldown Cooldown
ReqCooldown Cooldown
RespTimeCooldown Cooldown
QueueDelayCooldown Cooldown
QueueDelay *AutoscalingQueueDelayOpts
}
// AliasesForHostedZone maps hosted zone IDs to aliases that belong to it.
type AliasesForHostedZone map[string][]string
// AutoscalingQueueDelayOpts holds configuration to scale SQS queues.
type AutoscalingQueueDelayOpts struct {
AcceptableBacklogPerTask int
}
// ObservabilityOpts holds configurations for observability.
type ObservabilityOpts struct {
Tracing string // The name of the vendor used for tracing.
}
// DeploymentConfigurationOpts holds configuration for rolling deployments.
type DeploymentConfigurationOpts struct {
// The lower limit on the number of tasks that should be running during a service deployment or when a container instance is draining.
MinHealthyPercent int
// The upper limit on the number of tasks that should be running during a service deployment or when a container instance is draining.
MaxPercent int
Rollback RollingUpdateRollbackConfig
}
// RollingUpdateRollbackConfig holds config for rollback alarms.
type RollingUpdateRollbackConfig struct {
AlarmNames []string // Names of existing alarms.
// Custom alarms to create.
CPUUtilization *float64
MemoryUtilization *float64
MessagesDelayed *int
}
// HasRollbackAlarms returns true if the client is using ABR.
func (cfg RollingUpdateRollbackConfig) HasRollbackAlarms() bool {
return len(cfg.AlarmNames) > 0 || cfg.HasCustomAlarms()
}
// HasCustomAlarms returns true if the client is using Copilot-generated alarms for alarm-based rollbacks.
func (cfg RollingUpdateRollbackConfig) HasCustomAlarms() bool {
return cfg.CPUUtilization != nil || cfg.MemoryUtilization != nil || cfg.MessagesDelayed != nil
}
// TruncateAlarmName ensures that alarm names don't exceed the 255 character limit.
func (cfg RollingUpdateRollbackConfig) TruncateAlarmName(app, env, svc, alarmType string) string {
if len(app)+len(env)+len(svc)+len(alarmType) <= 255 {
return fmt.Sprintf("%s-%s-%s-%s", app, env, svc, alarmType)
}
maxSubstringLength := (255 - len(alarmType) - 3) / 3
return fmt.Sprintf("%s-%s-%s-%s", app[:maxSubstringLength], env[:maxSubstringLength], svc[:maxSubstringLength], alarmType)
}
// ExecuteCommandOpts holds configuration that's needed for ECS Execute Command.
type ExecuteCommandOpts struct{}
// StateMachineOpts holds configuration needed for State Machine retries and timeout.
type StateMachineOpts struct {
Timeout *int
Retries *int
}
// PublishOpts holds configuration needed if the service has publishers.
type PublishOpts struct {
Topics []*Topic
}
// Topic holds information needed to render a SNSTopic in a container definition.
type Topic struct {
Name *string
FIFOTopicConfig *FIFOTopicConfig
Region string
Partition string
AccountID string
App string
Env string
Svc string
}
// FIFOTopicConfig holds configuration needed if the topic is FIFO.
type FIFOTopicConfig struct {
ContentBasedDeduplication *bool
}
// SubscribeOpts holds configuration needed if the service has subscriptions.
type SubscribeOpts struct {
Topics []*TopicSubscription
Queue *SQSQueue
}
// HasTopicQueues returns true if any individual subscription has a dedicated queue.
func (s *SubscribeOpts) HasTopicQueues() bool {
for _, t := range s.Topics {
if t.Queue != nil {
return true
}
}
return false
}
// TopicSubscription holds information needed to render a SNS Topic Subscription in a container definition.
type TopicSubscription struct {
Name *string
Service *string
FilterPolicy *string
Queue *SQSQueue
}
// SQSQueue holds information needed to render a SQS Queue in a container definition.
type SQSQueue struct {
Retention *int64
Delay *int64
Timeout *int64
DeadLetter *DeadLetterQueue
FIFOQueueConfig *FIFOQueueConfig
}
// FIFOQueueConfig holds information needed to render a FIFO SQS Queue in a container definition.
type FIFOQueueConfig struct {
FIFOThroughputLimit *string
ContentBasedDeduplication *bool
DeduplicationScope *string
}
// DeadLetterQueue holds information needed to render a dead-letter SQS Queue in a container definition.
type DeadLetterQueue struct {
Tries *uint16
}
// NetworkOpts holds AWS networking configuration for the workloads.
type NetworkOpts struct {
SecurityGroups []SecurityGroup
AssignPublicIP string
// SubnetsType and SubnetIDs are mutually exclusive. They won't be set together.
SubnetsType string
SubnetIDs []string
DenyDefaultSecurityGroup bool
}
// SecurityGroup represents the ID of an additional security group associated with the tasks.
type SecurityGroup importableValue
// PlainSecurityGroup returns a SecurityGroup that is a plain string value.
func PlainSecurityGroup(value string) SecurityGroup {
return plainSecurityGroup(value)
}
// ImportedSecurityGroup returns a SecurityGroup that should be imported from a stack.
func ImportedSecurityGroup(name string) SecurityGroup {
return importedSecurityGroup(name)
}
type plainSecurityGroup string
// RequiresImport returns false for a plain string SecurityGroup.
func (sg plainSecurityGroup) RequiresImport() bool {
return false
}
// Value returns the plain string value of the SecurityGroup.
func (sg plainSecurityGroup) Value() string {
return string(sg)
}
type importedSecurityGroup string
// RequiresImport returns true for an imported SecurityGroup.
func (sg importedSecurityGroup) RequiresImport() bool {
return true
}
// Value returns the name of the import that will be the value of the SecurityGroup.
func (sg importedSecurityGroup) Value() string {
return string(sg)
}
// RuntimePlatformOpts holds configuration needed for Platform configuration.
type RuntimePlatformOpts struct {
OS string
Arch string
}
// IsDefault returns true if the platform matches the default docker image platform of "linux/amd64".
func (p RuntimePlatformOpts) IsDefault() bool {
if p.isEmpty() {
return true
}
if p.OS == OSLinux && p.Arch == ArchX86 {
return true
}
return false
}
// Version returns the Fargate platform version based on the selected os family.
func (p RuntimePlatformOpts) Version() string {
for _, os := range osFamiliesForPV100 {
if p.OS == os {
return "1.0.0"
}
}
return "LATEST"
}
func (p RuntimePlatformOpts) isEmpty() bool {
return p.OS == "" && p.Arch == ""
}
// S3ObjectLocation represents an object stored in an S3 bucket.
type S3ObjectLocation struct {
Bucket string // Name of the bucket.
Key string // Key of the object.
}
// WorkloadOpts holds optional data that can be provided to enable features in a workload stack template.
type WorkloadOpts struct {
AppName string
EnvName string
WorkloadName string
SerializedManifest string // Raw manifest file used to deploy the workload.
EnvVersion string
Version string
// Configuration for the main container.
PortMappings []*PortMapping
Variables map[string]Variable
Secrets map[string]Secret
EntryPoint []string
Command []string
ImportedALB *ImportedALB
// Additional options that are common between **all** workload templates.
Tags map[string]string // Used by App Runner workloads to tag App Runner service resources
NestedStack *WorkloadNestedStackOpts // Outputs from nested stacks such as the addons stack.
AddonsExtraParams string // Additional user defined Parameters for the addons stack.
Sidecars []*SidecarOpts
LogConfig *LogConfigOpts
Autoscaling *AutoscalingOpts
CapacityProviders []*CapacityProviderStrategy
DesiredCountOnSpot *int
Storage *StorageOpts
Network NetworkOpts
ExecuteCommand *ExecuteCommandOpts
Platform RuntimePlatformOpts
DockerLabels map[string]string
DependsOn map[string]string
Publish *PublishOpts
ServiceDiscoveryEndpoint string
ALBEnabled bool
CredentialsParameter string
PermissionsBoundary string
// Additional options for service templates.
WorkloadType string
HealthCheck *ContainerHealthCheck
GracePeriod *int64
NLB *NetworkLoadBalancer
ALBListener *ALBListener
DeploymentConfiguration DeploymentConfigurationOpts
ServiceConnectOpts ServiceConnectOpts
// Custom Resources backed by Lambda functions.
CustomResources map[string]S3ObjectLocation
// Additional options for job templates.
ScheduleExpression string
StateMachine *StateMachineOpts
// Additional options for request driven web service templates.
StartCommand *string
EnableHealthCheck bool
Observability ObservabilityOpts
Private bool
AppRunnerVPCEndpoint *string
Count *string
// Input needed for the custom resource that adds a custom domain to the service.
Alias *string
AWSSDKLayer *string
AppDNSDelegationRole *string
AppDNSName *string
// Additional options for worker service templates.
Subscribe *SubscribeOpts
// Additional options for static site template.
AssetMappingFileBucket string
AssetMappingFilePath string
StaticSiteAlias string
StaticSiteCert string
}
// HealthCheckProtocol returns the protocol for the Load Balancer health check,
// or an empty string if it shouldn't be configured, defaulting to the
// target protocol. (which is what happens, even if it isn't documented as such :))
// https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-targetgroup.html#cfn-elasticloadbalancingv2-targetgroup-healthcheckprotocol
func (lr ALBListenerRule) HealthCheckProtocol() string {
switch {
case lr.HTTPHealthCheck.Port == "443":
return "HTTPS"
case lr.TargetPort == "443" && lr.HTTPHealthCheck.Port == "":
return "HTTPS"
case lr.TargetPort == "443" && lr.HTTPHealthCheck.Port != "443":
// for backwards compatability, only set HTTP if target
// container is https but the specified health check port is not
return "HTTP"
}
return ""
}
// ImportedALB holds the fields to import an existing ALB.
type ImportedALB struct {
Name string
ARN string
DNSName string
HostedZoneID string
Listeners []LBListener
SecurityGroups []LBSecurityGroup
}
// LBListener struct represents the listener of a load balancer. // TODO(jwh): instead, reuse ALBListener
type LBListener struct {
ARN string
Port int64
Protocol string
}
// LBSecurityGroup struct represents the security group of a load balancer.
type LBSecurityGroup struct {
ID string
}
// HTTPListenerARN returns the listener ARN if the protocol is HTTP.
func (alb *ImportedALB) HTTPListenerARN() string {
for _, listener := range alb.Listeners {
if listener.Protocol == "HTTP" {
return listener.ARN
}
}
return ""
}
// HTTPSListenerARN returns the listener ARN if the protocol is HTTPS.
func (alb *ImportedALB) HTTPSListenerARN() string {
for _, listener := range alb.Listeners {
if listener.Protocol == "HTTPS" {
return listener.ARN
}
}
return ""
}
// ParseLoadBalancedWebService parses a load balanced web service's CloudFormation template
// with the specified data object and returns its content.
func (t *Template) ParseLoadBalancedWebService(data WorkloadOpts) (*Content, error) {
return t.parseSvc(lbWebSvcTplName, data, withSvcParsingFuncs())
}
// ParseRequestDrivenWebService parses a request-driven web service's CloudFormation template
// with the specified data object and returns its content.
func (t *Template) ParseRequestDrivenWebService(data WorkloadOpts) (*Content, error) {
return t.parseSvc(rdWebSvcTplName, data, withSvcParsingFuncs())
}
// ParseBackendService parses a backend service's CloudFormation template with the specified data object and returns its content.
func (t *Template) ParseBackendService(data WorkloadOpts) (*Content, error) {
return t.parseSvc(backendSvcTplName, data, withSvcParsingFuncs())
}
// ParseWorkerService parses a worker service's CloudFormation template with the specified data object and returns its content.
func (t *Template) ParseWorkerService(data WorkloadOpts) (*Content, error) {
return t.parseSvc(workerSvcTplName, data, withSvcParsingFuncs())
}
// ParseStaticSite parses a static site service's CloudFormation template with the specified data object and returns its content.
func (t *Template) ParseStaticSite(data WorkloadOpts) (*Content, error) {
return t.parseSvc(staticSiteTplName, data, withSvcParsingFuncs())
}
// ParseScheduledJob parses a scheduled job's Cloudformation Template
func (t *Template) ParseScheduledJob(data WorkloadOpts) (*Content, error) {
return t.parseJob(scheduledJobTplName, data, withSvcParsingFuncs())
}
// parseSvc parses a service's CloudFormation template with the specified data object and returns its content.
func (t *Template) parseSvc(name string, data interface{}, options ...ParseOption) (*Content, error) {
return t.parseWkld(name, servicesDirName, data, options...)
}
// parseJob parses a job's Cloudformation template with the specified data object and returns its content.
func (t *Template) parseJob(name string, data interface{}, options ...ParseOption) (*Content, error) {
return t.parseWkld(name, jobDirName, data, options...)
}
func (t *Template) parseWkld(name, wkldDirName string, data interface{}, options ...ParseOption) (*Content, error) {
tpl, err := t.parse("base", fmt.Sprintf(fmtWkldCFTemplatePath, wkldDirName, name), options...)
if err != nil {
return nil, err
}
for _, templateName := range partialsWorkloadCFTemplateNames {
nestedTpl, err := t.parse(templateName, fmt.Sprintf(fmtWkldPartialsCFTemplatePath, templateName), options...)
if err != nil {
return nil, err
}
_, err = tpl.AddParseTree(templateName, nestedTpl.Tree)
if err != nil {
return nil, fmt.Errorf("add parse tree of %s to base template: %w", templateName, err)
}
}
buf := &bytes.Buffer{}
if err := tpl.Execute(buf, data); err != nil {
return nil, fmt.Errorf("execute template %s with data %v: %w", name, data, err)
}
return &Content{buf}, nil
}
func withSvcParsingFuncs() ParseOption {
return func(t *template.Template) *template.Template {
return t.Funcs(map[string]interface{}{
"toSnakeCase": ToSnakeCaseFunc,
"hasSecrets": hasSecrets,
"fmtSlice": FmtSliceFunc,
"quoteSlice": QuoteSliceFunc,
"quote": strconv.Quote,
"randomUUID": randomUUIDFunc,
"jsonMountPoints": generateMountPointJSON,
"jsonSNSTopics": generateSNSJSON,
"jsonQueueURIs": generateQueueURIJSON,
"envControllerParams": envControllerParameters,
"logicalIDSafe": StripNonAlphaNumFunc,
"wordSeries": english.WordSeries,
"pluralWord": english.PluralWord,
"contains": slices.Contains[[]string, string],
"requiresVPCConnector": requiresVPCConnector,
"strconvUint16": StrconvUint16,
"truncateWithHashPadding": truncateWithHashPadding,
})
}
}
func truncateWithHashPadding(s string, max, paddingLength int) string {
if len(s) <= max {
return s
}
h := sha256.New()
h.Write([]byte(s))
hash := fmt.Sprintf("%x", h.Sum(nil))
return s[:max] + hash[:paddingLength]
}
func hasSecrets(opts WorkloadOpts) bool {
if len(opts.Secrets) > 0 {
return true
}
if opts.NestedStack != nil && (len(opts.NestedStack.SecretOutputs) > 0) {
return true
}
return false
}
func randomUUIDFunc() (string, error) {
id, err := uuid.NewRandom()
if err != nil {
return "", fmt.Errorf("generate random uuid: %w", err)
}
return id.String(), err
}
// envControllerParameters determines which parameters to include in the EnvController template.
func envControllerParameters(o WorkloadOpts) []string {
parameters := []string{}
if o.WorkloadType == "Load Balanced Web Service" {
if o.ImportedALB == nil {
if o.ALBEnabled {
parameters = append(parameters, "ALBWorkloads,")
}
parameters = append(parameters, "Aliases,") // YAML needs the comma separator; resolved in EnvContr.
}
}
if o.WorkloadType == "Backend Service" {
if o.ALBEnabled && o.ImportedALB == nil {
parameters = append(parameters, "InternalALBWorkloads,")
}
}
if o.WorkloadType == "Request-Driven Web Service" {
if o.Private && o.AppRunnerVPCEndpoint == nil {
parameters = append(parameters, "AppRunnerPrivateWorkloads,")
}
}
if o.Network.SubnetsType == PrivateSubnetsPlacement {
parameters = append(parameters, "NATWorkloads,")
}
if o.Storage != nil && o.Storage.requiresEFSCreation() {
parameters = append(parameters, "EFSWorkloads,")
}
return parameters
}
func requiresVPCConnector(o WorkloadOpts) bool {
if o.WorkloadType != "Request-Driven Web Service" {
return false
}
return len(o.Network.SubnetIDs) > 0 || o.Network.SubnetsType != ""
}
// ARN determines the arn for a topic using the SNSTopic name and account information
func (t Topic) ARN() string {
return fmt.Sprintf(snsARNPattern, t.Partition, t.Region, t.AccountID, t.App, t.Env, t.Svc, aws.StringValue(t.Name))
}