Repository: aws/amazon-ecs-cli-v2 (go)

Issue:
Store EnvironmentManagerRole in SSM
The `environment/cf.yml` template includes creation of the `EnvironmentManagerRole` resource. We need to capture that output on environment creation and store it in SSM.

The `app deploy` flow should be updated to utilize this EnvironmentManagerRole to support multi-account use-cases.

Pull request description:
Add the manager role ARN as a field to an environment struct
Resolves #361

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/archer/env.go b/internal/pkg/archer/env.go
index 136462f5..c55bd4f5 100644
--- a/internal/pkg/archer/env.go
+++ b/internal/pkg/archer/env.go
@@ -7,12 +7,13 @@ package archer
 // Environment represents the configuration of a particular environment in a project. It includes
 // the environment's account and region, name, as well as the project it belongs to.
 type Environment struct {
-	Project     string `json:"project"`     // Name of the project this environment belongs to.
-	Name        string `json:"name"`        // Name of the environment, must be unique within a project.
-	Region      string `json:"region"`      // Name of the region this environment is stored in.
-	AccountID   string `json:"accountID"`   // Account ID of the account this environment is stored in.
-	Prod        bool   `json:"prod"`        // Whether or not this environment is a production environment.
-	RegistryURL string `json:"registryURL"` // URL For ECR Registry for this environment.
+	Project        string `json:"project"`        // Name of the project this environment belongs to.
+	Name           string `json:"name"`           // Name of the environment, must be unique within a project.
+	Region         string `json:"region"`         // Name of the region this environment is stored in.
+	AccountID      string `json:"accountID"`      // Account ID of the account this environment is stored in.
+	Prod           bool   `json:"prod"`           // Whether or not this environment is a production environment.
+	RegistryURL    string `json:"registryURL"`    // URL For ECR Registry for this environment.
+	ManagerRoleARN string `json:"managerRoleARN"` // ARN for the manager role assumed to manipulate the environment and its applications.
 }
 
 // EnvironmentStore can List, Create and Get environments in an underlying project management store
diff --git a/internal/pkg/deploy/cloudformation/stack/env.go b/internal/pkg/deploy/cloudformation/stack/env.go
index 8f0c6d48..6981d154 100644
--- a/internal/pkg/deploy/cloudformation/stack/env.go
+++ b/internal/pkg/deploy/cloudformation/stack/env.go
@@ -21,25 +21,24 @@ type EnvStackConfig struct {
 }
 
 const (
-	// EnvParamIncludeLBKey is the CF Param Key Name for whether to include a LB
-	EnvParamIncludeLBKey = "IncludePublicLoadBalancer"
-
-	// EnvParamProjectNameKey is the CF Param Key Name for providing the project name
-	EnvParamProjectNameKey = "ProjectName"
-
-	// EnvParamEnvNameKey is the CF Param Key Name for providing the environment name
-	EnvParamEnvNameKey = "EnvironmentName"
-
-	// EnvTemplatePath is the path where the cloudformation for the environment is written
+	// EnvTemplatePath is the path where the cloudformation for the environment is written.
 	EnvTemplatePath = "environment/cf.yml"
+)
 
-	// EnvOutputECRKey is the CF Output Key Name for the ECR Repo Name
-	EnvOutputECRKey = "ECRRepositoryName"
+// Parameter keys.
+const (
+	envParamIncludeLBKey             = "IncludePublicLoadBalancer"
+	envParamProjectNameKey           = "ProjectName"
+	envParamEnvNameKey               = "EnvironmentName"
+	envParamToolsAccountPrincipalKey = "ToolsAccountPrincipalARN"
+)
 
-	envParamToolsAccountPrincipal = "ToolsAccountPrincipalARN"
+// Output keys.
+const (
+	envOutputManagerRoleKey = "EnvironmentManagerRoleARN"
 )
 
-// newEnvStackConfig sets up a struct which can provide values to CloudFormation for
+// NewEnvStackConfig sets up a struct which can provide values to CloudFormation for
 // spinning up an environment.
 func NewEnvStackConfig(input *deploy.CreateEnvironmentInput, box packd.Box) *EnvStackConfig {
 	return &EnvStackConfig{
@@ -61,19 +60,19 @@ func (e *EnvStackConfig) Template() (string, error) {
 func (e *EnvStackConfig) Parameters() []*cloudformation.Parameter {
 	return []*cloudformation.Parameter{
 		{
-			ParameterKey:   aws.String(EnvParamIncludeLBKey),
+			ParameterKey:   aws.String(envParamIncludeLBKey),
 			ParameterValue: aws.String(strconv.FormatBool(e.PublicLoadBalancer)),
 		},
 		{
-			ParameterKey:   aws.String(EnvParamProjectNameKey),
+			ParameterKey:   aws.String(envParamProjectNameKey),
 			ParameterValue: aws.String(e.Project),
 		},
 		{
-			ParameterKey:   aws.String(EnvParamEnvNameKey),
+			ParameterKey:   aws.String(envParamEnvNameKey),
 			ParameterValue: aws.String(e.Name),
 		},
 		{
-			ParameterKey:   aws.String(envParamToolsAccountPrincipal),
+			ParameterKey:   aws.String(envParamToolsAccountPrincipalKey),
 			ParameterValue: aws.String(e.ToolsAccountPrincipalARN),
 		},
 	}
@@ -110,13 +109,13 @@ func (e *EnvStackConfig) ToEnv(stack *cloudformation.Stack) (*archer.Environment
 	for _, output := range stack.Outputs {
 		stackOutputs[*output.OutputKey] = *output.OutputValue
 	}
-
 	createdEnv := archer.Environment{
-		Name:      e.Name,
-		Project:   e.Project,
-		Prod:      e.Prod,
-		Region:    stackARN.Region,
-		AccountID: stackARN.AccountID,
+		Name:           e.Name,
+		Project:        e.Project,
+		Prod:           e.Prod,
+		Region:         stackARN.Region,
+		AccountID:      stackARN.AccountID,
+		ManagerRoleARN: stackOutputs[envOutputManagerRoleKey],
 	}
 
 	return &createdEnv, nil


Test patch:
diff --git a/internal/pkg/deploy/cloudformation/stack/env_test.go b/internal/pkg/deploy/cloudformation/stack/env_test.go
index e35a3f98..7887471f 100644
--- a/internal/pkg/deploy/cloudformation/stack/env_test.go
+++ b/internal/pkg/deploy/cloudformation/stack/env_test.go
@@ -52,19 +52,19 @@ func TestEnvParameters(t *testing.T) {
 	env := NewEnvStackConfig(deploymentInput, emptyEnvBox())
 	expectedParams := []*cloudformation.Parameter{
 		{
-			ParameterKey:   aws.String(EnvParamIncludeLBKey),
+			ParameterKey:   aws.String(envParamIncludeLBKey),
 			ParameterValue: aws.String(strconv.FormatBool(deploymentInput.PublicLoadBalancer)),
 		},
 		{
-			ParameterKey:   aws.String(EnvParamProjectNameKey),
+			ParameterKey:   aws.String(envParamProjectNameKey),
 			ParameterValue: aws.String(deploymentInput.Project),
 		},
 		{
-			ParameterKey:   aws.String(EnvParamEnvNameKey),
+			ParameterKey:   aws.String(envParamEnvNameKey),
 			ParameterValue: aws.String(deploymentInput.Name),
 		},
 		{
-			ParameterKey:   aws.String(envParamToolsAccountPrincipal),
+			ParameterKey:   aws.String(envParamToolsAccountPrincipalKey),
 			ParameterValue: aws.String(deploymentInput.ToolsAccountPrincipalARN),
 		},
 	}
@@ -105,13 +105,14 @@ func TestToEnv(t *testing.T) {
 			mockStack: mockEnvironmentStack("", ""),
 		},
 		"should return a well formed environment": {
-			mockStack: mockEnvironmentStack("arn:aws:cloudformation:eu-west-3:902697171733:stack/project-env", "project/env"),
+			mockStack: mockEnvironmentStack("arn:aws:cloudformation:eu-west-3:902697171733:stack/project-env", "arn:aws:iam::902697171733:role/phonetool-test-EnvManagerRole"),
 			expectedEnv: archer.Environment{
-				Name:        mockDeployInput.Name,
-				Project:     mockDeployInput.Project,
-				Prod:        mockDeployInput.Prod,
-				AccountID:   "902697171733",
-				Region:      "eu-west-3",
+				Name:           mockDeployInput.Name,
+				Project:        mockDeployInput.Project,
+				Prod:           mockDeployInput.Prod,
+				AccountID:      "902697171733",
+				Region:         "eu-west-3",
+				ManagerRoleARN: "arn:aws:iam::902697171733:role/phonetool-test-EnvManagerRole",
 			},
 		},
 	}
@@ -130,13 +131,13 @@ func TestToEnv(t *testing.T) {
 		})
 	}
 }
-func mockEnvironmentStack(stackArn, ecrOutput string) *cloudformation.Stack {
+func mockEnvironmentStack(stackArn, managerRoleARN string) *cloudformation.Stack {
 	return &cloudformation.Stack{
 		StackId: aws.String(stackArn),
 		Outputs: []*cloudformation.Output{
-			&cloudformation.Output{
-				OutputKey:   aws.String(EnvOutputECRKey),
-				OutputValue: aws.String(ecrOutput),
+			{
+				OutputKey:   aws.String(envOutputManagerRoleKey),
+				OutputValue: aws.String(managerRoleARN),
 			},
 		},
 	}