// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 package template import ( "bytes" "fmt" "strconv" "text/template" "github.com/aws/copilot-cli/internal/pkg/aws/s3" ) const ( envCFTemplatePath = "environment/cf.yml" fmtEnvCFSubTemplatePath = "environment/partials/%s.yml" envBootstrapCFTemplatePath = "environment/bootstrap-cf.yml" ) // Available env-controller managed feature names. const ( ALBFeatureName = "ALBWorkloads" EFSFeatureName = "EFSWorkloads" NATFeatureName = "NATWorkloads" InternalALBFeatureName = "InternalALBWorkloads" AliasesFeatureName = "Aliases" AppRunnerPrivateServiceFeatureName = "AppRunnerPrivateWorkloads" ) // LastForceDeployIDOutputName is the logical ID of the deployment controller output. const LastForceDeployIDOutputName = "LastForceDeployID" var friendlyEnvFeatureName = map[string]string{ ALBFeatureName: "ALB", EFSFeatureName: "EFS", NATFeatureName: "NAT Gateway", InternalALBFeatureName: "Internal ALB", AliasesFeatureName: "Aliases", AppRunnerPrivateServiceFeatureName: "App Runner Private Services", } var leastVersionForFeature = map[string]string{ ALBFeatureName: "v1.0.0", EFSFeatureName: "v1.3.0", NATFeatureName: "v1.3.0", InternalALBFeatureName: "v1.10.0", AliasesFeatureName: "v1.4.0", AppRunnerPrivateServiceFeatureName: "v1.23.0", } // AvailableEnvFeatures returns a list of the latest available feature, named after their corresponding parameter names. func AvailableEnvFeatures() []string { return []string{ALBFeatureName, EFSFeatureName, NATFeatureName, InternalALBFeatureName, AliasesFeatureName, AppRunnerPrivateServiceFeatureName} } // FriendlyEnvFeatureName returns a user-friendly feature name given a env-controller managed parameter name. // If there isn't one, it returns the parameter name that it is given. func FriendlyEnvFeatureName(feature string) string { friendly, ok := friendlyEnvFeatureName[feature] if !ok { return feature } return friendly } // LeastVersionForFeature maps each feature to the least environment template version it requires. func LeastVersionForFeature(feature string) string { return leastVersionForFeature[feature] } var ( // Template names under "environment/partials/". envCFSubTemplateNames = []string{ "cdn-resources", "cfn-execution-role", "custom-resources", "custom-resources-role", "environment-manager-role", "lambdas", "vpc-resources", "nat-gateways", "bootstrap-resources", "elb-access-logs", "mappings-regional-configs", "ar-vpc-connector", } ) var ( // Template names under "environment/partials/". bootstrapEnvSubTemplateName = []string{ "cfn-execution-role", "environment-manager-role", "bootstrap-resources", } ) // Addons holds data about an aggregated addons stack. type Addons struct { URL string ExtraParams string } // EnvOpts holds data that can be provided to enable features in an environment stack template. type EnvOpts struct { AppName string // The application name. Needed to create default value for svc discovery endpoint for upgraded environments. EnvName string LatestVersion string // Custom Resources backed by Lambda functions. CustomResources map[string]S3ObjectLocation DNSDelegationLambda string DNSCertValidatorLambda string EnableLongARNFormatLambda string CustomDomainLambda string Addons *Addons ScriptBucketName string PermissionsBoundary string ArtifactBucketARN string ArtifactBucketKeyARN string VPCConfig VPCConfig PublicHTTPConfig PublicHTTPConfig PrivateHTTPConfig PrivateHTTPConfig Telemetry *Telemetry CDNConfig *CDNConfig SerializedManifest string // Serialized manifest used to render the environment template. ForceUpdateID string DelegateDNS bool } // PublicHTTPConfig represents configuration for a public facing Load Balancer. type PublicHTTPConfig struct { HTTPConfig PublicALBSourceIPs []string CIDRPrefixListIDs []string ELBAccessLogs *ELBAccessLogs } // PrivateHTTPConfig represents configuration for an internal Load Balancer. type PrivateHTTPConfig struct { HTTPConfig CustomALBSubnets []string } // HasImportedCerts returns true if any https certificates have been // imported to the environment. func (e *EnvOpts) HasImportedCerts() bool { return len(e.PublicHTTPConfig.ImportedCertARNs) > 0 || len(e.PrivateHTTPConfig.ImportedCertARNs) > 0 || (e.CDNConfig != nil && e.CDNConfig.ImportedCertificate != nil) } // HTTPConfig represents configuration for a Load Balancer. type HTTPConfig struct { SSLPolicy *string ImportedCertARNs []string } // ELBAccessLogs represents configuration for ELB access logs S3 bucket. type ELBAccessLogs struct { BucketName string Prefix string } // ShouldCreateBucket returns true if copilot should create bucket on behalf of customer. func (elb *ELBAccessLogs) ShouldCreateBucket() bool { if elb == nil { return false } return elb.BucketName == "" } // CDNConfig represents a Content Delivery Network deployed by CloudFront. type CDNConfig struct { ImportedCertificate *string TerminateTLS bool Static *CDNStaticAssetConfig } // CDNStaticAssetConfig represents static assets config for a Content Delivery Network. type CDNStaticAssetConfig struct { Path string ImportedBucket string Alias string } // VPCConfig represents the VPC configuration. type VPCConfig struct { Imported *ImportVPC // If not-nil, use the imported VPC resources instead of the Managed VPC. Managed ManagedVPC AllowVPCIngress bool SecurityGroupConfig *SecurityGroupConfig FlowLogs *VPCFlowLogs } // ImportVPC holds the fields to import VPC resources. type ImportVPC struct { ID string PublicSubnetIDs []string PrivateSubnetIDs []string } // ManagedVPC holds the fields to configure a managed VPC. type ManagedVPC struct { CIDR string AZs []string PublicSubnetCIDRs []string PrivateSubnetCIDRs []string } // Telemetry represents optional observability and monitoring configuration. type Telemetry struct { EnableContainerInsights bool } // SecurityGroupConfig holds the fields to import security group config type SecurityGroupConfig struct { Ingress []SecurityGroupRule Egress []SecurityGroupRule } // SecurityGroupRule holds the fields to import security group rule type SecurityGroupRule struct { CidrIP string FromPort int IpProtocol string ToPort int } // VPCFlowLogs holds the fields to configure logging IP traffic using VPC flow logs. type VPCFlowLogs struct { Retention *int } // ParseEnv parses an environment's CloudFormation template with the specified data object and returns its content. func (t *Template) ParseEnv(data *EnvOpts) (*Content, error) { tpl, err := t.parse("base", envCFTemplatePath, withEnvParsingFuncs()) if err != nil { return nil, err } for _, templateName := range envCFSubTemplateNames { nestedTpl, err := t.parse(templateName, fmt.Sprintf(fmtEnvCFSubTemplatePath, templateName), withEnvParsingFuncs()) 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 environment template with data %v: %w", data, err) } return &Content{buf}, nil } // ParseEnvBootstrap parses the CloudFormation template that bootstrap IAM resources with the specified data object and returns its content. func (t *Template) ParseEnvBootstrap(data *EnvOpts, options ...ParseOption) (*Content, error) { tpl, err := t.parse("base", envBootstrapCFTemplatePath, options...) if err != nil { return nil, err } for _, templateName := range bootstrapEnvSubTemplateName { nestedTpl, err := t.parse(templateName, fmt.Sprintf(fmtEnvCFSubTemplatePath, 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 environment template with data %v: %w", data, err) } return &Content{buf}, nil } func withEnvParsingFuncs() ParseOption { return func(t *template.Template) *template.Template { return t.Funcs(map[string]interface{}{ "inc": IncFunc, "fmtSlice": FmtSliceFunc, "quote": strconv.Quote, "truncate": truncate, "bucketNameFromURL": bucketNameFromURL, "logicalIDSafe": StripNonAlphaNumFunc, }) } } func truncate(s string, maxLen int) string { if len(s) < maxLen { return s } return s[:maxLen] } func bucketNameFromURL(url string) string { bucketName, _, _ := s3.ParseURL(url) return bucketName }