Skip to content

Commit 8d3a17f

Browse files
committed
Add output file generation to 'registry-creds up'
1 parent 79a185a commit 8d3a17f

11 files changed

+326
-97
lines changed

ecs-cli/modules/cli/regcreds/create_task_execution_role.go

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919

2020
iamClient "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/iam"
2121
kmsClient "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/kms"
22-
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils"
22+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/regcreds"
2323
"github.com/aws/aws-sdk-go/aws"
2424
"github.com/aws/aws-sdk-go/service/iam"
2525
log "github.com/sirupsen/logrus"
@@ -31,44 +31,47 @@ const (
3131
)
3232

3333
type executionRoleParams struct {
34-
CredEntries map[string]CredsOutputEntry
34+
CredEntries map[string]readers.CredsOutputEntry
3535
RoleName string
3636
Region string
3737
}
3838

39-
func createTaskExecutionRole(params executionRoleParams, iamClient iamClient.Client, kmsClient kmsClient.Client) error {
39+
func createTaskExecutionRole(params executionRoleParams, iamClient iamClient.Client, kmsClient kmsClient.Client) (*time.Time, error) {
4040
log.Infof("Creating resources for task execution role %s...", params.RoleName)
4141

4242
// create role
4343
roleName, err := createOrFindRole(params.RoleName, iamClient)
4444
if err != nil {
45-
return err
45+
return nil, err
4646
}
4747

4848
// generate policy document
4949
policyDoc, err := generateSecretsPolicy(params.CredEntries, kmsClient)
5050
if err != nil {
51-
return err
51+
return nil, err
5252
}
5353

54+
// create datetime for policy & output
55+
createTime := time.Now().UTC()
56+
5457
// create the new policy
55-
newPolicy, err := createRegistryCredentialsPolicy(params.RoleName, policyDoc, iamClient)
58+
newPolicy, err := createRegistryCredentialsPolicy(params.RoleName, policyDoc, createTime, iamClient)
5659
if err != nil {
57-
return err
60+
return nil, err
5861
}
5962
log.Infof("Created new task execution role policy %s", aws.StringValue(newPolicy.Arn))
6063

6164
// attach managed execution role policy & new credentials policy to role
6265
err = attachRolePolicies(*newPolicy.Arn, roleName, params.Region, iamClient)
6366
if err != nil {
64-
return err
67+
return nil, err
6568
}
6669

67-
return nil
70+
return &createTime, nil
6871
}
6972

70-
func createRegistryCredentialsPolicy(roleName, policyDoc string, client iamClient.Client) (*iam.Policy, error) {
71-
newPolicyName := generateECSResourceName(roleName + "-policy")
73+
func createRegistryCredentialsPolicy(roleName, policyDoc string, createTime time.Time, client iamClient.Client) (*iam.Policy, error) {
74+
newPolicyName := generateECSResourceName(roleName + "-policy-" + createTime.Format(readers.ECSCredFileTimeFmt))
7275
policyDescriptionFmtString := "Policy generated by the ecs-cli for role: %s"
7376

7477
createPolicyRequest := iam.CreatePolicyInput{
@@ -78,20 +81,6 @@ func createRegistryCredentialsPolicy(roleName, policyDoc string, client iamClien
7881
}
7982

8083
policyResult, err := client.CreatePolicy(createPolicyRequest)
81-
// if policy already exists, create new one with timestamp
82-
// TODO: consider instead updating existing policy with CreatePolicyVersion()
83-
if utils.EntityAlreadyExists(err) {
84-
createPolicyRequest := iam.CreatePolicyInput{
85-
PolicyName: generateECSResourceName(roleName + "-policy-" + time.Now().UTC().Format("20060102150405")),
86-
PolicyDocument: aws.String(policyDoc),
87-
Description: aws.String(fmt.Sprintf(policyDescriptionFmtString, roleName)),
88-
}
89-
policyResult, err := client.CreatePolicy(createPolicyRequest)
90-
if err != nil {
91-
return nil, err
92-
}
93-
return policyResult.Policy, nil
94-
}
9584
if err != nil {
9685
return nil, err
9786
}

ecs-cli/modules/cli/regcreds/create_task_execution_role_test.go

Lines changed: 19 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package regcreds
1616
import (
1717
"testing"
1818

19+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/regcreds"
1920
"github.com/aws/aws-sdk-go/aws"
2021
"github.com/aws/aws-sdk-go/aws/awserr"
2122
"github.com/aws/aws-sdk-go/service/iam"
@@ -28,8 +29,8 @@ func TestCreateTaskExecutionRole(t *testing.T) {
2829
testRegistry := "myreg.test.io"
2930
testRegCredARN := "arn:aws:secret/some-test-arn"
3031
testRegKMSKey := "arn:aws:kms:key/67yt-756yth"
31-
testCreds := make(map[string]CredsOutputEntry)
32-
testCreds[testRegistry] = CredsOutputEntry{
32+
testCreds := make(map[string]readers.CredsOutputEntry)
33+
testCreds[testRegistry] = readers.CredsOutputEntry{
3334
CredentialARN: testRegCredARN,
3435
KMSKeyID: testRegKMSKey,
3536
}
@@ -57,15 +58,16 @@ func TestCreateTaskExecutionRole(t *testing.T) {
5758
Region: "us-west-2",
5859
}
5960

60-
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
61+
policyCreateTime, err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
6162
assert.NoError(t, err, "Unexpected error when creating task execution role")
63+
assert.NotNil(t, policyCreateTime, "Expected policy create time to be non-nil")
6264
}
6365

6466
func TestCreateTaskExecutionRole_NoKMSKey(t *testing.T) {
6567
testRegistry := "myreg.test.io"
6668
testRegCredARN := "arn:aws:secret/some-test-arn"
67-
testCreds := make(map[string]CredsOutputEntry)
68-
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
69+
testCreds := make(map[string]readers.CredsOutputEntry)
70+
testCreds[testRegistry] = readers.CredsOutputEntry{CredentialARN: testRegCredARN}
6971
testRoleName := "myNginxProjectRole"
7072

7173
testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
@@ -88,15 +90,16 @@ func TestCreateTaskExecutionRole_NoKMSKey(t *testing.T) {
8890
Region: "us-west-2",
8991
}
9092

91-
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
93+
policyCreateTime, err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
9294
assert.NoError(t, err, "Unexpected error when creating task execution role")
95+
assert.NotNil(t, policyCreateTime, "Expected policy create time to be non-nil")
9396
}
9497

9598
func TestCreateTaskExecutionRole_RoleExists(t *testing.T) {
9699
testRegistry := "myreg.test.io"
97100
testRegCredARN := "arn:aws:secret/some-test-arn"
98-
testCreds := make(map[string]CredsOutputEntry)
99-
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
101+
testCreds := make(map[string]readers.CredsOutputEntry)
102+
testCreds[testRegistry] = readers.CredsOutputEntry{CredentialARN: testRegCredARN}
100103
testRoleName := "myNginxProjectRole"
101104

102105
testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
@@ -120,49 +123,16 @@ func TestCreateTaskExecutionRole_RoleExists(t *testing.T) {
120123
Region: "us-west-2",
121124
}
122125

123-
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
124-
assert.NoError(t, err, "Unexpected error when creating task execution role")
125-
}
126-
127-
func TestCreateTaskExecutionRole_RoleAndPolicyAlreadyExists(t *testing.T) {
128-
testRegistry := "myreg.test.io"
129-
testRegCredARN := "arn:aws:secret/some-test-arn"
130-
testCreds := make(map[string]CredsOutputEntry)
131-
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
132-
testRoleName := "myNginxProjectRole"
133-
134-
testPolicySecondArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy-20181010222222")
135-
entityExistsError := awserr.New("EntityAlreadyExists", "Didn't you see the error code? This resource already exists.", errors.New("something went wrong"))
136-
137-
mocks := setupTestController(t)
138-
gomock.InOrder(
139-
// CreateOrFindRole should return nil if given role already exists
140-
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return("", nil),
141-
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(nil, entityExistsError),
142-
)
143-
gomock.InOrder(
144-
// CreatePolicy should be called again if the generated policy name already exists
145-
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(nil, entityExistsError),
146-
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(&iam.CreatePolicyOutput{Policy: &iam.Policy{Arn: testPolicySecondArn}}, nil),
147-
mocks.MockIAM.EXPECT().AttachRolePolicy(getExecutionRolePolicyARN("us-west-2"), testRoleName).Return(nil, nil),
148-
mocks.MockIAM.EXPECT().AttachRolePolicy(*testPolicySecondArn, testRoleName).Return(nil, nil),
149-
)
150-
151-
testParams := executionRoleParams{
152-
CredEntries: testCreds,
153-
RoleName: testRoleName,
154-
Region: "us-west-2",
155-
}
156-
157-
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
126+
policyCreateTime, err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
158127
assert.NoError(t, err, "Unexpected error when creating task execution role")
128+
assert.NotNil(t, policyCreateTime, "Expected policy create time to be non-nil")
159129
}
160130

161131
func TestCreateTaskExecutionRole_ErrorOnCreateRoleFails(t *testing.T) {
162132
testRegistry := "myreg.test.io"
163133
testRegCredARN := "arn:aws:secret/some-test-arn"
164-
testCreds := make(map[string]CredsOutputEntry)
165-
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
134+
testCreds := make(map[string]readers.CredsOutputEntry)
135+
testCreds[testRegistry] = readers.CredsOutputEntry{CredentialARN: testRegCredARN}
166136
testRoleName := "myNginxProjectRole"
167137

168138
mocks := setupTestController(t)
@@ -177,18 +147,17 @@ func TestCreateTaskExecutionRole_ErrorOnCreateRoleFails(t *testing.T) {
177147
Region: "us-west-2",
178148
}
179149

180-
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
150+
_, err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
181151
assert.Error(t, err, "Expected error when CreateRole fails")
182152
}
183153

184154
func TestCreateTaskExecutionRole_ErrorOnCreatePolicyFails(t *testing.T) {
185155
testRegistry := "myreg.test.io"
186156
testRegCredARN := "arn:aws:secret/some-test-arn"
187-
testCreds := make(map[string]CredsOutputEntry)
188-
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
157+
testCreds := make(map[string]readers.CredsOutputEntry)
158+
testCreds[testRegistry] = readers.CredsOutputEntry{CredentialARN: testRegCredARN}
189159
testRoleName := "myNginxProjectRole"
190160

191-
//testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
192161
testRoleArn := aws.String("arn:aws:iam::role/" + testRoleName)
193162

194163
mocks := setupTestController(t)
@@ -206,6 +175,6 @@ func TestCreateTaskExecutionRole_ErrorOnCreatePolicyFails(t *testing.T) {
206175
Region: "us-west-2",
207176
}
208177

209-
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
178+
_, err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
210179
assert.Error(t, err, "Expected error when CreatePolicy fails")
211180
}

ecs-cli/modules/cli/regcreds/generate_policy.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"encoding/json"
1818

1919
kmsClient "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/kms"
20+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/regcreds"
2021
)
2122

2223
const (
@@ -36,7 +37,7 @@ type StatementEntry struct {
3637
Resource []string
3738
}
3839

39-
func generateSecretsPolicy(credEntries map[string]CredsOutputEntry, kmsClient kmsClient.Client) (string, error) {
40+
func generateSecretsPolicy(credEntries map[string]readers.CredsOutputEntry, kmsClient kmsClient.Client) (string, error) {
4041
policyStatements := make([]StatementEntry, 0, len(credEntries))
4142

4243
for _, entry := range credEntries {

ecs-cli/modules/cli/regcreds/regcreds_app.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package regcreds
1616
import (
1717
"fmt"
1818
"strings"
19+
"time"
1920

2021
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/iam"
2122
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/kms"
@@ -30,14 +31,6 @@ import (
3031
"github.com/urfave/cli"
3132
)
3233

33-
// CredsOutputEntry contains the credential ARN and associated container names
34-
// TODO: use & move to output_reader once implemented?
35-
type CredsOutputEntry struct {
36-
CredentialARN string
37-
KMSKeyID string
38-
ContainerNames []string
39-
}
40-
4134
// Up creates or updates registry credential secrets and an ECS task execution role needed to use them in a task def
4235
func Up(c *cli.Context) {
4336
args := c.Args()
@@ -72,6 +65,14 @@ func Up(c *cli.Context) {
7265
log.Fatal("Error executing 'up': ", err)
7366
}
7467

68+
outputDir := c.String(flags.OutputDirFlag)
69+
skipOutput := c.Bool(flags.NoOutputFileFlag)
70+
71+
err = validateOutputOptions(outputDir, skipOutput)
72+
if err != nil {
73+
log.Fatal("Error executing 'up': ", err)
74+
}
75+
7576
// find or create secrets, role
7677
updateAllowed := c.Bool(flags.UpdateExistingSecretsFlag)
7778

@@ -80,6 +81,7 @@ func Up(c *cli.Context) {
8081
log.Fatal("Error executing 'up': ", err)
8182
}
8283

84+
var policyCreateTime *time.Time
8385
if !skipRole {
8486
region := commandConfig.Session.Config.Region
8587

@@ -89,17 +91,24 @@ func Up(c *cli.Context) {
8991
Region: *region,
9092
}
9193

92-
err = createTaskExecutionRole(roleParams, iamClient, kmsClient)
94+
policyCreateTime, err = createTaskExecutionRole(roleParams, iamClient, kmsClient)
9395
if err != nil {
9496
log.Fatal("Error executing 'up': ", err)
9597
}
98+
} else {
99+
log.Info("Skipping role creation.")
96100
}
97101

98-
//TODO: produce output file
102+
// produce output file
103+
if !skipOutput {
104+
readers.GenerateCredsOutput(credentialOutput, roleName, outputDir, policyCreateTime)
105+
} else {
106+
log.Info("Skipping output file generation.")
107+
}
99108
}
100109

101-
func getOrCreateRegistryCredentials(entryMap readers.RegistryCreds, smClient secretsClient.SMClient, updateAllowed bool) (map[string]CredsOutputEntry, error) {
102-
registryResults := make(map[string]CredsOutputEntry)
110+
func getOrCreateRegistryCredentials(entryMap readers.RegistryCreds, smClient secretsClient.SMClient, updateAllowed bool) (map[string]readers.CredsOutputEntry, error) {
111+
registryResults := make(map[string]readers.CredsOutputEntry)
103112

104113
for registryName, credentialEntry := range entryMap {
105114
log.Infof("Processing credentials for registry %s...", registryName)
@@ -124,7 +133,7 @@ func getOrCreateRegistryCredentials(entryMap readers.RegistryCreds, smClient sec
124133
if keyForSecret == nil {
125134
keyForSecret = &credentialEntry.KmsKeyID
126135
}
127-
registryResults[registryName] = buildOutputEntry(arn, *keyForSecret, credentialEntry.ContainerNames)
136+
registryResults[registryName] = readers.BuildOutputEntry(arn, *keyForSecret, credentialEntry.ContainerNames)
128137
}
129138

130139
return registryResults, nil
@@ -278,3 +287,10 @@ func getNewCommandConfig(c *cli.Context) *config.CommandConfig {
278287

279288
return commandConfig
280289
}
290+
291+
func validateOutputOptions(outputDir string, skipOutput bool) error {
292+
if outputDir != "" && skipOutput {
293+
return fmt.Errorf("both output directory ('%s') and '--"+flags.NoOutputFileFlag+"' specified", outputDir)
294+
}
295+
return nil
296+
}

ecs-cli/modules/cli/regcreds/regcreds_app_helpers.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@ import (
2222
"github.com/aws/aws-sdk-go/aws/arn"
2323
)
2424

25-
func buildOutputEntry(arn string, key string, containers []string) CredsOutputEntry {
26-
return CredsOutputEntry{
27-
CredentialARN: arn,
28-
KMSKeyID: key,
29-
ContainerNames: containers,
30-
}
31-
}
32-
3325
// returns the provided value with the ecs-cli resource prefix added
3426
func generateECSResourceName(providedName string) *string {
3527
return aws.String(utils.ECSCLIResourcePrefix + providedName)

ecs-cli/modules/commands/flags/flags.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,8 @@ const (
132132
UpdateExistingSecretsFlag = "update-existing-secrets"
133133
RoleNameFlag = "role-name"
134134
NoRoleFlag = "no-role"
135+
NoOutputFileFlag = "no-output-file"
136+
OutputDirFlag = "output-dir"
135137
)
136138

137139
// OptionalRegionAndProfileFlags provides these flags:

ecs-cli/modules/commands/regcreds/regcreds_command.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,13 @@ func regcredsUpFlags() []cli.Flag {
5757
Name: flags.NoRoleFlag,
5858
Usage: "[Optional] If specified, no task execution role will be created.",
5959
},
60+
cli.BoolFlag{
61+
Name: flags.NoOutputFileFlag,
62+
Usage: "[Optional] If specified, no output file for use with 'compose' will be created.",
63+
},
64+
cli.StringFlag{
65+
Name: flags.OutputDirFlag,
66+
Usage: "[Optional] The directory where the output file should be created. If none specified, file will be created in the current working directory.",
67+
},
6068
}
6169
}

0 commit comments

Comments
 (0)