Repository: aws/amazon-ecs-cli-v2
Pull request #427 (2019-11-20 05:12:48)

Add HTTPS to lb_fargate_app template & x-account deploys
This change updates the lb fargate app to create an https
listner if the project is configured for https.

It also enables cross account deployments and brings
the app stack name in line with the codepipeline's apps
(project-env-app).

<!-- Provide summary of changes -->

<!-- Issue number, if available. E.g. "Fixes #31", "Addresses #42, 77" -->

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.



Patch:
diff --git a/internal/pkg/cli/app_deploy.go b/internal/pkg/cli/app_deploy.go
index a4b6f66a..4e0b4c64 100644
--- a/internal/pkg/cli/app_deploy.go
+++ b/internal/pkg/cli/app_deploy.go
@@ -123,7 +123,6 @@ func (opts *appDeployOpts) init() error {
 		return fmt.Errorf("intialize workspace service: %w", err)
 	}
 	opts.workspaceService = workspaceService
-
 	return nil
 }
 
@@ -276,11 +275,16 @@ func (opts *appDeployOpts) configureClients() error {
 		return fmt.Errorf("create ECR session with region %s: %w", opts.targetEnvironment.Region, err)
 	}
 
+	envSession, err := session.FromRole(opts.targetEnvironment.ManagerRoleARN, opts.targetEnvironment.Region)
+	if err != nil {
+		return fmt.Errorf("assuming environment manager role: %w", err)
+	}
+
 	// ECR client against tools account profile AND target environment region
 	opts.ecrService = ecr.New(defaultSessEnvRegion)
 
-	// app deploy CF client against tools account profile AND target environment region
-	opts.appDeployCfClient = cloudformation.New(defaultSessEnvRegion)
+	// app deploy CF client against env account profile AND target environment region
+	opts.appDeployCfClient = cloudformation.New(envSession)
 
 	// app package CF client against tools account
 	appPackageCfSess, err := session.Default()
@@ -346,7 +350,8 @@ func (opts appDeployOpts) deployApp() error {
 
 	template, err := opts.getAppDeployTemplate()
 
-	stackName := fmt.Sprintf("%s-%s", opts.app, opts.targetEnvironment.Name)
+	// TODO move stack
+	stackName := fmt.Sprintf("%s-%s-%s", opts.ProjectName(), opts.targetEnvironment.Name, opts.app)
 	changeSetName := fmt.Sprintf("%s-%s", stackName, opts.imageTag)
 
 	opts.spinner.Start(
@@ -360,7 +365,7 @@ func (opts appDeployOpts) deployApp() error {
 		stack.EnvTagKey:     opts.targetEnvironment.Name,
 		stack.AppTagKey:     opts.app,
 	}
-	if err := opts.applyAppDeployTemplate(template, stackName, changeSetName, tags); err != nil {
+	if err := opts.applyAppDeployTemplate(template, stackName, changeSetName, opts.targetEnvironment.ExecutionRoleARN, tags); err != nil {
 		opts.spinner.Stop("Error!")
 		return err
 	}
@@ -395,8 +400,8 @@ func (opts appDeployOpts) getAppDeployTemplate() (string, error) {
 	return buffer.String(), nil
 }
 
-func (opts appDeployOpts) applyAppDeployTemplate(template, stackName, changeSetName string, tags map[string]string) error {
-	if err := opts.appDeployCfClient.DeployApp(template, stackName, changeSetName, tags); err != nil {
+func (opts appDeployOpts) applyAppDeployTemplate(template, stackName, changeSetName, cfExecutionRole string, tags map[string]string) error {
+	if err := opts.appDeployCfClient.DeployApp(template, stackName, changeSetName, cfExecutionRole, tags); err != nil {
 		return fmt.Errorf("deploy application: %w", err)
 	}
 
diff --git a/internal/pkg/cli/app_package.go b/internal/pkg/cli/app_package.go
index 58b43b01..a0f1cc00 100644
--- a/internal/pkg/cli/app_package.go
+++ b/internal/pkg/cli/app_package.go
@@ -198,12 +198,21 @@ func (opts *PackageAppOpts) getTemplates(env *archer.Environment) (*cfnTemplates
 
 	switch t := mft.(type) {
 	case *manifest.LBFargateManifest:
-		appStack := stack.NewLBFargateStack(&deploy.CreateLBFargateAppInput{
+		createLBAppInput := &deploy.CreateLBFargateAppInput{
 			App:          mft.(*manifest.LBFargateManifest),
 			Env:          env,
 			ImageRepoURL: repoURL,
 			ImageTag:     opts.Tag,
-		})
+		}
+		var appStack *stack.LBFargateStackConfig
+		// If the project supports DNS Delegation, we'll also
+		// make sure the app supports HTTPS
+		if proj.RequiresDNSDelegation() {
+			appStack = stack.NewHTTPSLBFargateStack(createLBAppInput)
+		} else {
+			appStack = stack.NewLBFargateStack(createLBAppInput)
+		}
+
 		tpl, err := appStack.Template()
 		if err != nil {
 			return nil, err
diff --git a/internal/pkg/deploy/cloudformation/app.go b/internal/pkg/deploy/cloudformation/app.go
index 45514977..efcfd8ce 100644
--- a/internal/pkg/deploy/cloudformation/app.go
+++ b/internal/pkg/deploy/cloudformation/app.go
@@ -14,7 +14,7 @@ import (
 
 // DeployApp wraps the application deployment flow and handles orchestration of
 // creating a stack versus updating a stack.
-func (cf CloudFormation) DeployApp(template, stackName, changeSetName string, tags map[string]string) error {
+func (cf CloudFormation) DeployApp(template, stackName, changeSetName, cfExecutionRole string, tags map[string]string) error {
 	var cfnTags []*cloudformation.Tag
 	for k, v := range tags {
 		cfnTags = append(cfnTags, &cloudformation.Tag{
@@ -28,6 +28,7 @@ func (cf CloudFormation) DeployApp(template, stackName, changeSetName string, ta
 		TemplateBody: aws.String(template),
 		Capabilities: aws.StringSlice([]string{cloudformation.CapabilityCapabilityIam}),
 		Tags:         cfnTags,
+		RoleARN:      aws.String(cfExecutionRole),
 	})
 
 	// If CreateStack does not return an error we'll wait for StackCreateComplete status.
@@ -61,6 +62,7 @@ func (cf CloudFormation) DeployApp(template, stackName, changeSetName string, ta
 		Capabilities:  aws.StringSlice([]string{cloudformation.CapabilityCapabilityIam}),
 		ChangeSetType: aws.String(cloudformation.ChangeSetTypeUpdate),
 		Tags:          cfnTags,
+		RoleARN:       aws.String(cfExecutionRole),
 	})
 
 	if err != nil {
diff --git a/internal/pkg/deploy/cloudformation/stack/lb_fargate_app.go b/internal/pkg/deploy/cloudformation/stack/lb_fargate_app.go
index b721b938..4a1aa6cc 100644
--- a/internal/pkg/deploy/cloudformation/stack/lb_fargate_app.go
+++ b/internal/pkg/deploy/cloudformation/stack/lb_fargate_app.go
@@ -24,6 +24,7 @@ const (
 
 const (
 	lbFargateParamProjectNameKey    = "ProjectName"
+	lbFargatePramHTTPSKey           = "HTTPSEnabled"
 	lbFargateParamEnvNameKey        = "EnvName"
 	lbFargateParamAppNameKey        = "AppName"
 	lbFargateParamContainerImageKey = "ContainerImage"
@@ -39,14 +40,26 @@ const (
 // load balanced Fargate application.
 type LBFargateStackConfig struct {
 	*deploy.CreateLBFargateAppInput
-
-	box packd.Box
+	httpsEnabled bool
+	box          packd.Box
 }
 
 // NewLBFargateStack creates a new LBFargateStackConfig from a load-balanced AWS Fargate application.
 func NewLBFargateStack(in *deploy.CreateLBFargateAppInput) *LBFargateStackConfig {
 	return &LBFargateStackConfig{
 		CreateLBFargateAppInput: in,
+		httpsEnabled:            false,
+		box:                     templates.Box(),
+	}
+}
+
+// NewHTTPSLBFargateStack creates a new LBFargateStackConfig from a load-balanced AWS Fargate application. It
+// creates an HTTPS listener and assumes that the environment it's being deployed into has an HTTPS configured
+// listener.
+func NewHTTPSLBFargateStack(in *deploy.CreateLBFargateAppInput) *LBFargateStackConfig {
+	return &LBFargateStackConfig{
+		CreateLBFargateAppInput: in,
+		httpsEnabled:            true,
 		box:                     templates.Box(),
 	}
 }
@@ -123,6 +136,10 @@ func (c *LBFargateStackConfig) Parameters() []*cloudformation.Parameter {
 			ParameterKey:   aws.String(lbFargateTaskCountKey),
 			ParameterValue: aws.String(strconv.Itoa(templateParams.App.Count)),
 		},
+		{
+			ParameterKey:   aws.String(lbFargatePramHTTPSKey),
+			ParameterValue: aws.String(strconv.FormatBool(c.httpsEnabled)),
+		},
 	}
 }
 
@@ -169,6 +186,7 @@ type lbFargateTemplateParams struct {
 	// Additional fields needed to render the CloudFormation stack.
 	Priority int // Listener's rule priority https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-listeners.html#listener-rules
 
+	HTTPSEnabled string
 	// Field types to override.
 	Image struct {
 		URL  string
@@ -186,7 +204,8 @@ func (c *LBFargateStackConfig) toTemplateParams() *lbFargateTemplateParams {
 			},
 			Env: c.Env,
 		},
-		Priority: 1, // TODO assign a unique path priority given a path.
+		HTTPSEnabled: strconv.FormatBool(c.httpsEnabled),
+		Priority:     1, // TODO assign a unique path priority given a path.
 		Image: struct {
 			URL  string
 			Port int
diff --git a/templates/cicd/pipeline_cfn.yml b/templates/cicd/pipeline_cfn.yml
index c55f4864..6ce6f8c2 100644
--- a/templates/cicd/pipeline_cfn.yml
+++ b/templates/cicd/pipeline_cfn.yml
@@ -325,9 +325,9 @@ Resources:
                 Provider: CloudFormation
               Configuration:
                 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-action-reference.html
-                ChangeSetName: {{$.ProjectName}}-{{$app}}-{{$stage.Name}}
+                ChangeSetName: {{$.ProjectName}}-{{$stage.Name}}-{{$app}}
                 ActionMode: CREATE_UPDATE
-                StackName: {{$.ProjectName}}-{{$app}}-{{$stage.Name}}
+                StackName: {{$.ProjectName}}-{{$stage.Name}}-{{$app}}
                 Capabilities: CAPABILITY_NAMED_IAM
                 TemplatePath: BuildOutput::infrastructure/{{$stage.AppTemplatePath $app}}
                 TemplateConfiguration: BuildOutput::infrastructure/{{$stage.AppTemplateConfigurationPath $app}}
diff --git a/templates/lb-fargate-service/cf.yml b/templates/lb-fargate-service/cf.yml
index b3891cbc..3f6581d1 100644
--- a/templates/lb-fargate-service/cf.yml
+++ b/templates/lb-fargate-service/cf.yml
@@ -33,6 +33,16 @@ Parameters:
   TaskCount:
     Type: Number
     Default: {{.App.Count}}
+  HTTPSEnabled:
+    Type: String
+    AllowedValues: [true, false]
+    Default: '{{.HTTPSEnabled}}'
+Conditions:
+  HTTPLoadBalancer:
+    !Not
+      - !Condition HTTPSLoadBalancer
+  HTTPSLoadBalancer:
+    !Equals [!Ref HTTPSEnabled, true]
 Resources:
   LogGroup:
     Type: AWS::Logs::LogGroup
@@ -117,8 +127,6 @@ Resources:
               !Sub "${ProjectName}-${EnvName}-PublicLoadBalancerSecurityGroupId"
   Service:
     Type: AWS::ECS::Service
-    DependsOn:
-      - ListenerRule
     Properties:
       Cluster:
         Fn::ImportValue:
@@ -150,7 +158,6 @@ Resources:
         - ContainerName: !Ref AppName
           ContainerPort: !Ref ContainerPort
           TargetGroupArn: !Ref TargetGroup
-
   TargetGroup:
     Type: AWS::ElasticLoadBalancingV2::TargetGroup
     Properties:
@@ -167,9 +174,33 @@ Resources:
       VpcId:
         Fn::ImportValue:
           !Sub "${ProjectName}-${EnvName}-VpcId"
-
-  ListenerRule:
+  LoadBalancerDNSAlias:
+    Type: AWS::Route53::RecordSetGroup
+    Condition: HTTPSLoadBalancer
+    Properties:
+      HostedZoneId:
+        Fn::ImportValue:
+          !Sub "${ProjectName}-${EnvName}-HostedZone"
+      Comment: !Sub "LoadBalancer alias for app ${AppName}"
+      RecordSets:
+      - Name:
+          !Join
+            - '.'
+            - - !Ref AppName
+              - Fn::ImportValue:
+                  !Sub "${ProjectName}-${EnvName}-SubDomain"
+              - ""
+        Type: A
+        AliasTarget:
+          HostedZoneId:
+            Fn::ImportValue:
+              !Sub "${ProjectName}-${EnvName}-CanonicalHostedZoneID"
+          DNSName:
+            Fn::ImportValue:
+              !Sub "${ProjectName}-${EnvName}-PublicLoadBalancerDNS"
+  HTTPListenerRule:
     Type: AWS::ElasticLoadBalancingV2::ListenerRule
+    Condition: HTTPLoadBalancer
     Properties:
       Actions:
         - TargetGroupArn: !Ref TargetGroup
@@ -183,3 +214,23 @@ Resources:
         Fn::ImportValue:
           !Sub "${ProjectName}-${EnvName}-HTTPListenerArn"
       Priority: !Ref RulePriority
+  HTTPSListenerRule:
+    Type: AWS::ElasticLoadBalancingV2::ListenerRule
+    Condition: HTTPSLoadBalancer
+    Properties:
+      Actions:
+        - TargetGroupArn: !Ref TargetGroup
+          Type: forward
+      Conditions:
+        - Field: 'host-header'
+          HostHeaderConfig:
+            Values:
+              - Fn::Join:
+                - '.'
+                - - !Ref AppName
+                  - Fn::ImportValue:
+                      !Sub "${ProjectName}-${EnvName}-SubDomain"
+      ListenerArn:
+        Fn::ImportValue:
+          !Sub "${ProjectName}-${EnvName}-HTTPSListenerArn"
+      Priority: !Ref RulePriority
diff --git a/templates/lb-fargate-service/params.json b/templates/lb-fargate-service/params.json
index 4cc54f7e..34283e48 100644
--- a/templates/lb-fargate-service/params.json
+++ b/templates/lb-fargate-service/params.json
@@ -9,7 +9,8 @@
     "RulePath": "{{.App.Path}}",
     "TaskCPU": "{{.App.CPU}}",
     "TaskMemory": "{{.App.Memory}}",
-    "TaskCount": "{{.App.Count}}"
+    "TaskCount": "{{.App.Count}}",
+    "HTTPSEnabled": "{{.HTTPSEnabled}}"
   },
   "Tags": {
     "ecs-project": "{{.Env.Project}}",


Test patch:
diff --git a/internal/pkg/cli/app_package_test.go b/internal/pkg/cli/app_package_test.go
index 07ca139e..a8b01eac 100644
--- a/internal/pkg/cli/app_package_test.go
+++ b/internal/pkg/cli/app_package_test.go
@@ -520,6 +520,46 @@ http:
   path: '*'
 cpu: 256
 memory: 512
+count: 1`), nil)
+			},
+			expectDeployer: func(m *climocks.MockprojectResourcesGetter) {
+				m.EXPECT().GetProjectResourcesByRegion(gomock.Any(), gomock.Any()).Return(&archer.ProjectRegionalResources{
+					RepositoryURLs: map[string]string{
+						"frontend": "some url",
+					},
+				}, nil)
+			},
+		},
+		"print CFN template with HTTPS": {
+			inProjectName: "phonetool",
+			inEnvName:     "test",
+			inAppName:     "frontend",
+			inTagName:     "latest",
+
+			expectStore: func(m *climocks.MockprojectService) {
+				m.EXPECT().GetEnvironment("phonetool", "test").Return(&archer.Environment{
+					Project:   "phonetool",
+					Name:      "test",
+					AccountID: "1111",
+					Region:    "us-west-2",
+				}, nil)
+				m.EXPECT().GetProject("phonetool").Return(&archer.Project{
+					Name:      "phonetool",
+					AccountID: "1234",
+					Domain:    "ecs.aws",
+				}, nil)
+			},
+			expectWorkspace: func(m *mocks.MockWorkspace) {
+				m.EXPECT().AppManifestFileName("frontend").Return("frontend-app.yml")
+				m.EXPECT().ReadFile("frontend-app.yml").Return([]byte(`name: frontend
+type: Load Balanced Web App
+image:
+  build: frontend/Dockerfile
+  port: 80
+http:
+  path: '*'
+cpu: 256
+memory: 512
 count: 1`), nil)
 			},
 			expectDeployer: func(m *climocks.MockprojectResourcesGetter) {
diff --git a/internal/pkg/deploy/cloudformation/app_test.go b/internal/pkg/deploy/cloudformation/app_test.go
index 43bb7a9a..d86241f7 100644
--- a/internal/pkg/deploy/cloudformation/app_test.go
+++ b/internal/pkg/deploy/cloudformation/app_test.go
@@ -17,6 +17,7 @@ func TestDeployApp(t *testing.T) {
 	mockTemplate := "mockTemplate"
 	mockStackName := "mockStackName"
 	mockChangeSetName := "mockChangeSetName"
+	mockExecutionRole := "mockExecutionRole"
 	mockError := errors.New("mockError")
 
 	testCases := map[string]struct {
@@ -37,6 +38,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockStackName, *in.StackName)
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return &cloudformation.CreateStackOutput{}, nil
 			},
@@ -55,6 +57,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockStackName, *in.StackName)
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return nil, awserr.New(cloudformation.ErrCodeAlreadyExistsException, "", nil)
 			},
@@ -66,6 +69,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
 				require.Equal(t, cloudformation.ChangeSetTypeUpdate, *in.ChangeSetType)
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return &cloudformation.CreateChangeSetOutput{}, nil
 			},
@@ -100,6 +104,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockStackName, *in.StackName)
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return nil, awserr.New(cloudformation.ErrCodeAlreadyExistsException, "", nil)
 			},
@@ -111,6 +116,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
 				require.Equal(t, cloudformation.ChangeSetTypeUpdate, *in.ChangeSetType)
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return &cloudformation.CreateChangeSetOutput{}, nil
 			},
@@ -140,6 +146,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockStackName, *in.StackName)
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return nil, awserr.New(cloudformation.ErrCodeAlreadyExistsException, "", nil)
 			},
@@ -151,6 +158,7 @@ func TestDeployApp(t *testing.T) {
 				require.Equal(t, mockTemplate, *in.TemplateBody)
 				require.Equal(t, cloudformation.CapabilityCapabilityIam, *in.Capabilities[0])
 				require.Equal(t, cloudformation.ChangeSetTypeUpdate, *in.ChangeSetType)
+				require.Equal(t, mockExecutionRole, *in.RoleARN)
 
 				return &cloudformation.CreateChangeSetOutput{}, nil
 			},
@@ -190,7 +198,7 @@ func TestDeployApp(t *testing.T) {
 				},
 			}
 
-			gotErr := cf.DeployApp(mockTemplate, mockStackName, mockChangeSetName, nil)
+			gotErr := cf.DeployApp(mockTemplate, mockStackName, mockChangeSetName, mockExecutionRole, nil)
 
 			require.Equal(t, tc.wantErr, gotErr)
 		})
diff --git a/internal/pkg/deploy/cloudformation/stack/lb_fargate_app_test.go b/internal/pkg/deploy/cloudformation/stack/lb_fargate_app_test.go
index 303aeb0c..b343d5a9 100644
--- a/internal/pkg/deploy/cloudformation/stack/lb_fargate_app_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/lb_fargate_app_test.go
@@ -148,74 +148,96 @@ func TestLBFargateStackConfig_Template(t *testing.T) {
 }
 
 func TestLBFargateStackConfig_Parameters(t *testing.T) {
-	// GIVEN
-	conf := &LBFargateStackConfig{
-		CreateLBFargateAppInput: &deploy.CreateLBFargateAppInput{
-			App: manifest.NewLoadBalancedFargateManifest("frontend", "frontend/Dockerfile"),
-			Env: &archer.Environment{
-				Project:   "phonetool",
-				Name:      "test",
-				Region:    "us-west-2",
-				AccountID: "12345",
-				Prod:      false,
-			},
-			ImageRepoURL: "12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend",
-			ImageTag:     "manual-bf3678c",
+	testCases := map[string]struct {
+		httpsEnabled bool
+		expectedHTTP string
+	}{
+		"HTTPS Enabled": {
+			httpsEnabled: true,
+			expectedHTTP: "true",
+		},
+		"HTTPS Not Enabled": {
+			httpsEnabled: false,
+			expectedHTTP: "false",
 		},
 	}
+	for name, tc := range testCases {
+		t.Run(name, func(t *testing.T) {
 
-	// WHEN
-	params := conf.Parameters()
+			// GIVEN
+			conf := &LBFargateStackConfig{
+				CreateLBFargateAppInput: &deploy.CreateLBFargateAppInput{
+					App: manifest.NewLoadBalancedFargateManifest("frontend", "frontend/Dockerfile"),
+					Env: &archer.Environment{
+						Project:   "phonetool",
+						Name:      "test",
+						Region:    "us-west-2",
+						AccountID: "12345",
+						Prod:      false,
+					},
+					ImageRepoURL: "12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend",
+					ImageTag:     "manual-bf3678c",
+				},
+				httpsEnabled: tc.httpsEnabled,
+			}
 
-	// THEN
-	require.Equal(t, []*cloudformation.Parameter{
-		{
-			ParameterKey:   aws.String(lbFargateParamProjectNameKey),
-			ParameterValue: aws.String("phonetool"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateParamEnvNameKey),
-			ParameterValue: aws.String("test"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateParamAppNameKey),
-			ParameterValue: aws.String("frontend"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateParamContainerImageKey),
-			ParameterValue: aws.String("12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend:manual-bf3678c"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateParamContainerPortKey),
-			ParameterValue: aws.String("80"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateRulePriorityKey),
-			ParameterValue: aws.String("1"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateRulePathKey),
-			ParameterValue: aws.String("*"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateTaskCPUKey),
-			ParameterValue: aws.String("256"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateTaskMemoryKey),
-			ParameterValue: aws.String("512"),
-		},
-		{
-			ParameterKey:   aws.String(lbFargateTaskCountKey),
-			ParameterValue: aws.String("1"),
-		},
-	}, params)
+			// WHEN
+			params := conf.Parameters()
 
+			// THEN
+			require.Equal(t, []*cloudformation.Parameter{
+				{
+					ParameterKey:   aws.String(lbFargateParamProjectNameKey),
+					ParameterValue: aws.String("phonetool"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateParamEnvNameKey),
+					ParameterValue: aws.String("test"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateParamAppNameKey),
+					ParameterValue: aws.String("frontend"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateParamContainerImageKey),
+					ParameterValue: aws.String("12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend:manual-bf3678c"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateParamContainerPortKey),
+					ParameterValue: aws.String("80"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateRulePriorityKey),
+					ParameterValue: aws.String("1"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateRulePathKey),
+					ParameterValue: aws.String("*"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateTaskCPUKey),
+					ParameterValue: aws.String("256"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateTaskMemoryKey),
+					ParameterValue: aws.String("512"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargateTaskCountKey),
+					ParameterValue: aws.String("1"),
+				},
+				{
+					ParameterKey:   aws.String(lbFargatePramHTTPSKey),
+					ParameterValue: aws.String(tc.expectedHTTP),
+				},
+			}, params)
+		})
+	}
 }
 
 func TestLBFargateStackConfig_SerializedParameters(t *testing.T) {
 	testCases := map[string]struct {
-		in *deploy.CreateLBFargateAppInput
+		in *LBFargateStackConfig
 
 		mockBox func(box *packd.MemoryBox)
 
@@ -223,8 +245,8 @@ func TestLBFargateStackConfig_SerializedParameters(t *testing.T) {
 		wantedError  error
 	}{
 		"unavailable template": {
-			mockBox: func(box *packd.MemoryBox) {}, // empty box where template does not exist
-
+			mockBox:      func(box *packd.MemoryBox) {}, // empty box where template does not exist
+			in:           &LBFargateStackConfig{},
 			wantedParams: "",
 			wantedError: &ErrTemplateNotFound{
 				templateLocation: lbFargateAppParamsPath,
@@ -232,17 +254,70 @@ func TestLBFargateStackConfig_SerializedParameters(t *testing.T) {
 			},
 		},
 		"render params template": {
-			in: &deploy.CreateLBFargateAppInput{
-				App: manifest.NewLoadBalancedFargateManifest("frontend", "frontend/Dockerfile"),
-				Env: &archer.Environment{
-					Project:   "phonetool",
-					Name:      "test",
-					Region:    "us-west-2",
-					AccountID: "12345",
-					Prod:      false,
+			in: &LBFargateStackConfig{
+				CreateLBFargateAppInput: &deploy.CreateLBFargateAppInput{
+					App: manifest.NewLoadBalancedFargateManifest("frontend", "frontend/Dockerfile"),
+					Env: &archer.Environment{
+						Project:   "phonetool",
+						Name:      "test",
+						Region:    "us-west-2",
+						AccountID: "12345",
+						Prod:      false,
+					},
+					ImageRepoURL: "12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend",
+					ImageTag:     "manual-bf3678c",
 				},
-				ImageRepoURL: "12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend",
-				ImageTag:     "manual-bf3678c",
+				httpsEnabled: false,
+			},
+
+			mockBox: func(box *packd.MemoryBox) {
+				box.AddString(lbFargateAppParamsPath, `{
+  "Parameters" : {
+    "ProjectName" : "{{.Env.Project}}",
+    "EnvName": "{{.Env.Name}}",
+    "AppName": "{{.App.Name}}",
+    "ContainerImage": "{{.Image.URL}}",
+    "ContainerPort": "{{.Image.Port}}",
+    "RulePriority": "{{.Priority}}",
+    "RulePath": "{{.App.Path}}",
+    "TaskCPU": "{{.App.CPU}}",
+    "TaskMemory": "{{.App.Memory}}",
+    "TaskCount": "{{.App.Count}}",
+    "HTTPSEnabled": "{{.HTTPSEnabled}}"
+  }
+}`)
+			},
+			wantedParams: `{
+  "Parameters" : {
+    "ProjectName" : "phonetool",
+    "EnvName": "test",
+    "AppName": "frontend",
+    "ContainerImage": "12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend:manual-bf3678c",
+    "ContainerPort": "80",
+    "RulePriority": "1",
+    "RulePath": "*",
+    "TaskCPU": "256",
+    "TaskMemory": "512",
+    "TaskCount": "1",
+    "HTTPSEnabled": "false"
+  }
+}`,
+		},
+		"render params template for https": {
+			in: &LBFargateStackConfig{
+				CreateLBFargateAppInput: &deploy.CreateLBFargateAppInput{
+					App: manifest.NewLoadBalancedFargateManifest("frontend", "frontend/Dockerfile"),
+					Env: &archer.Environment{
+						Project:   "phonetool",
+						Name:      "test",
+						Region:    "us-west-2",
+						AccountID: "12345",
+						Prod:      false,
+					},
+					ImageRepoURL: "12345.dkr.ecr.us-west-2.amazonaws.com/phonetool/frontend",
+					ImageTag:     "manual-bf3678c",
+				},
+				httpsEnabled: true,
 			},
 			mockBox: func(box *packd.MemoryBox) {
 				box.AddString(lbFargateAppParamsPath, `{
@@ -256,7 +331,8 @@ func TestLBFargateStackConfig_SerializedParameters(t *testing.T) {
     "RulePath": "{{.App.Path}}",
     "TaskCPU": "{{.App.CPU}}",
     "TaskMemory": "{{.App.Memory}}",
-    "TaskCount": "{{.App.Count}}"
+    "TaskCount": "{{.App.Count}}",
+    "HTTPSEnabled": "{{.HTTPSEnabled}}"
   }
 }`)
 			},
@@ -271,7 +347,8 @@ func TestLBFargateStackConfig_SerializedParameters(t *testing.T) {
     "RulePath": "*",
     "TaskCPU": "256",
     "TaskMemory": "512",
-    "TaskCount": "1"
+    "TaskCount": "1",
+    "HTTPSEnabled": "true"
   }
 }`,
 		},
@@ -282,14 +359,10 @@ func TestLBFargateStackConfig_SerializedParameters(t *testing.T) {
 			// GIVEN
 			box := packd.NewMemoryBox()
 			tc.mockBox(box)
-
-			conf := &LBFargateStackConfig{
-				CreateLBFargateAppInput: tc.in,
-				box:                     box,
-			}
+			tc.in.box = box
 
 			// WHEN
-			params, err := conf.SerializedParameters()
+			params, err := tc.in.SerializedParameters()
 
 			// THEN
 			require.True(t, errors.Is(err, tc.wantedError), "expected: %v, got: %v", tc.wantedError, err)
diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/rendered_pipeline_cfn_template.yml b/internal/pkg/deploy/cloudformation/stack/testdata/rendered_pipeline_cfn_template.yml
index f08016d9..081cc64e 100755
--- a/internal/pkg/deploy/cloudformation/stack/testdata/rendered_pipeline_cfn_template.yml
+++ b/internal/pkg/deploy/cloudformation/stack/testdata/rendered_pipeline_cfn_template.yml
@@ -372,9 +372,9 @@ Resources:
                 Provider: CloudFormation
               Configuration:
                 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-action-reference.html
-                ChangeSetName: chickenProject-frontend-test-chicken
+                ChangeSetName: chickenProject-test-chicken-frontend
                 ActionMode: CREATE_UPDATE
-                StackName: chickenProject-frontend-test-chicken
+                StackName: chickenProject-test-chicken-frontend
                 Capabilities: CAPABILITY_NAMED_IAM
                 TemplatePath: BuildOutput::infrastructure/frontend.stack.yml
                 TemplateConfiguration: BuildOutput::infrastructure/frontend-test-chicken.params.json
@@ -398,9 +398,9 @@ Resources:
                 Provider: CloudFormation
               Configuration:
                 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-action-reference.html
-                ChangeSetName: chickenProject-backend-test-chicken
+                ChangeSetName: chickenProject-test-chicken-backend
                 ActionMode: CREATE_UPDATE
-                StackName: chickenProject-backend-test-chicken
+                StackName: chickenProject-test-chicken-backend
                 Capabilities: CAPABILITY_NAMED_IAM
                 TemplatePath: BuildOutput::infrastructure/backend.stack.yml
                 TemplateConfiguration: BuildOutput::infrastructure/backend-test-chicken.params.json
@@ -444,9 +444,9 @@ Resources:
                 Provider: CloudFormation
               Configuration:
                 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-action-reference.html
-                ChangeSetName: chickenProject-frontend-prod-can-fly
+                ChangeSetName: chickenProject-prod-can-fly-frontend
                 ActionMode: CREATE_UPDATE
-                StackName: chickenProject-frontend-prod-can-fly
+                StackName: chickenProject-prod-can-fly-frontend
                 Capabilities: CAPABILITY_NAMED_IAM
                 TemplatePath: BuildOutput::infrastructure/frontend.stack.yml
                 TemplateConfiguration: BuildOutput::infrastructure/frontend-prod-can-fly.params.json
@@ -470,9 +470,9 @@ Resources:
                 Provider: CloudFormation
               Configuration:
                 # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/continuous-delivery-codepipeline-action-reference.html
-                ChangeSetName: chickenProject-backend-prod-can-fly
+                ChangeSetName: chickenProject-prod-can-fly-backend
                 ActionMode: CREATE_UPDATE
-                StackName: chickenProject-backend-prod-can-fly
+                StackName: chickenProject-prod-can-fly-backend
                 Capabilities: CAPABILITY_NAMED_IAM
                 TemplatePath: BuildOutput::infrastructure/backend.stack.yml
                 TemplateConfiguration: BuildOutput::infrastructure/backend-prod-can-fly.params.json