Skip to content

Commit 59f8c87

Browse files
authored
test(e2e): add skeleton for pipeline e2e test (#2422)
This initial e2e/pipeline pkg just creates a codecommit repository where we can push the service code. Once this PR is merged and we know that it works with CodeBuild, we'll send a follow-up PR to actually run the "copilot pipeline" commands. - [x] Tested the changes against my AWS account and local machine By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent 6b426aa commit 59f8c87

File tree

9 files changed

+344
-5
lines changed

9 files changed

+344
-5
lines changed

.release/buildspec_e2e.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,15 @@ batch:
109109
variables:
110110
TEST_SUITE: apprunner
111111
APP_REGION: ap-northeast-1
112+
- identifier: pipeline
113+
env:
114+
privileged-mode: true
115+
type: LINUX_CONTAINER
116+
compute-type: BUILD_GENERAL1_LARGE
117+
image: aws/codebuild/standard:5.0
118+
variables:
119+
TEST_SUITE: pipeline
120+
APP_REGION: eu-west-1
112121

113122

114123
phases:

Makefile

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,12 @@ package-custom-resources-clean:
9696

9797
.PHONY: run-unit-test
9898
run-unit-test:
99-
go test -race -cover -count=1 -coverprofile ${COVERAGE} ${PACKAGES}
99+
go test -race -count=1 -coverprofile=${COVERAGE} ${PACKAGES}
100100

101101
.PHONY: generate-coverage
102-
generate-coverage: ${COVERAGE}
102+
generate-coverage: test
103103
go tool cover -html=${COVERAGE}
104104

105-
${COVERAGE}: test
106-
107105
.PHONY: integ-test
108106
integ-test: packr-build run-integ-test packr-clean
109107

e2e/internal/client/aws.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import (
1313
cmd "github.com/aws/copilot-cli/e2e/internal/command"
1414
)
1515

16+
// IAM policy ARNs.
17+
const (
18+
codeCommitPowerUserPolicyARN = "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser"
19+
)
20+
1621
// AWS is a wrapper around aws commands.
1722
type AWS struct{}
1823

@@ -63,6 +68,170 @@ func (a *AWS) WaitStackCreateComplete(name string) error {
6368
return a.exec(command)
6469
}
6570

71+
// CreateCodeCommitRepo creates a repository with AWS CodeCommit and returns
72+
// the HTTP git clone url.
73+
func (a *AWS) CreateCodeCommitRepo(name string) (cloneURL string, err error) {
74+
out := new(bytes.Buffer)
75+
args := strings.Join([]string{
76+
"codecommit",
77+
"create-repository",
78+
"--repository-name",
79+
name,
80+
}, " ")
81+
if err := a.exec(args, cmd.Stdout(out)); err != nil {
82+
return "", fmt.Errorf("create commit repository %s cmd: %v", name, err)
83+
}
84+
85+
data := struct {
86+
RepositoryMetadata struct {
87+
CloneURLHTTP string `json:"cloneUrlHttp"`
88+
} `json:"repositoryMetadata"`
89+
}{}
90+
if err := json.Unmarshal(out.Bytes(), &data); err != nil {
91+
return "", fmt.Errorf("unmarshal json response from create commit repository: %v", err)
92+
}
93+
return data.RepositoryMetadata.CloneURLHTTP, nil
94+
}
95+
96+
// DeleteCodeCommitRepo delete a CodeCommit repository.
97+
func (a *AWS) DeleteCodeCommitRepo(name string) error {
98+
args := strings.Join([]string{
99+
"codecommit",
100+
"delete-repository",
101+
"--repository-name",
102+
name,
103+
}, " ")
104+
if err := a.exec(args); err != nil {
105+
return fmt.Errorf("delete repository %s: %v", name, err)
106+
}
107+
return nil
108+
}
109+
110+
// IAMServiceCreds represents service-specific IAM credentials.
111+
type IAMServiceCreds struct {
112+
UserName string `json:"ServiceUserName"` // Git username.
113+
Password string `json:"ServicePassword"` // Git password.
114+
CredentialID string `json:"ServiceSpecificCredentialId"` // ID for the creds in order to delete them.
115+
}
116+
117+
// CreateCodeCommitIAMUser creates an IAM user that can push and pull from codecommit.
118+
// Returns the credentials needed to interact with codecommit.
119+
func (a *AWS) CreateCodeCommitIAMUser(userName string) (*IAMServiceCreds, error) {
120+
if err := a.createIAMUser("/copilot/e2etests/", userName); err != nil {
121+
return nil, err
122+
}
123+
if err := a.attachUserPolicy(userName, codeCommitPowerUserPolicyARN); err != nil {
124+
return nil, err
125+
}
126+
return a.createCodeCommitCreds(userName)
127+
}
128+
129+
// DeleteCodeCommitIAMUser deletes an IAM user that can access codecommit.
130+
func (a *AWS) DeleteCodeCommitIAMUser(userName, credentialID string) error {
131+
if err := a.deleteServiceSpecificCreds(userName, credentialID); err != nil {
132+
return err
133+
}
134+
if err := a.detachUserPolicy(userName, codeCommitPowerUserPolicyARN); err != nil {
135+
return err
136+
}
137+
return a.deleteIAMUser(userName)
138+
}
139+
140+
func (a *AWS) createIAMUser(path, userName string) error {
141+
args := strings.Join([]string{
142+
"iam",
143+
"create-user",
144+
"--path",
145+
path,
146+
"--user-name",
147+
userName,
148+
}, " ")
149+
if err := a.exec(args); err != nil {
150+
return fmt.Errorf("create IAM user under path %s and name %s: %v", path, userName, err)
151+
}
152+
return nil
153+
}
154+
155+
func (a *AWS) attachUserPolicy(userName, policyARN string) error {
156+
args := strings.Join([]string{
157+
"iam",
158+
"attach-user-policy",
159+
"--user-name",
160+
userName,
161+
"--policy-arn",
162+
policyARN,
163+
}, " ")
164+
if err := a.exec(args); err != nil {
165+
return fmt.Errorf("attach policy arn %s to user %s: %v", policyARN, userName, err)
166+
}
167+
return nil
168+
}
169+
170+
func (a *AWS) createCodeCommitCreds(userName string) (*IAMServiceCreds, error) {
171+
out := new(bytes.Buffer)
172+
args := strings.Join([]string{
173+
"iam",
174+
"create-service-specific-credential",
175+
"--user-name",
176+
userName,
177+
"--service-name",
178+
"codecommit.amazonaws.com",
179+
}, " ")
180+
if err := a.exec(args, cmd.Stdout(out)); err != nil {
181+
return nil, fmt.Errorf("create commit credentials for user %s: %v", userName, err)
182+
}
183+
data := struct {
184+
Creds IAMServiceCreds `json:"ServiceSpecificCredential"`
185+
}{}
186+
if err := json.Unmarshal(out.Bytes(), &data); err != nil {
187+
return nil, fmt.Errorf("unmarshal credentials for codecommit: %v", err)
188+
}
189+
return &data.Creds, nil
190+
}
191+
192+
func (a *AWS) deleteServiceSpecificCreds(userName, credentialID string) error {
193+
args := strings.Join([]string{
194+
"iam",
195+
"delete-service-specific-credential",
196+
"--user-name",
197+
userName,
198+
"--service-specific-credential-id",
199+
credentialID,
200+
}, " ")
201+
if err := a.exec(args); err != nil {
202+
return fmt.Errorf("delete service specific creds %s for user %s: %v", credentialID, userName, err)
203+
}
204+
return nil
205+
}
206+
207+
func (a *AWS) detachUserPolicy(userName, policyARN string) error {
208+
args := strings.Join([]string{
209+
"iam",
210+
"detach-user-policy",
211+
"--user-name",
212+
userName,
213+
"--policy-arn",
214+
policyARN,
215+
}, " ")
216+
if err := a.exec(args); err != nil {
217+
return fmt.Errorf("detach user policy %s for user %s: %v", policyARN, userName, err)
218+
}
219+
return nil
220+
}
221+
222+
func (a *AWS) deleteIAMUser(userName string) error {
223+
args := strings.Join([]string{
224+
"iam",
225+
"delete-user",
226+
"--user-name",
227+
userName,
228+
}, " ")
229+
if err := a.exec(args); err != nil {
230+
return fmt.Errorf("delete iam user %s: %v", userName, err)
231+
}
232+
return nil
233+
}
234+
66235
/*VPCStackOutput runs:
67236
aws cloudformation describe-stacks --stack-name $name |
68237
jq -r .Stacks[0].Outputs

e2e/internal/client/cli.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import (
1616

1717
// CLI is a wrapper around os.execs.
1818
type CLI struct {
19-
path string
19+
path string
20+
workingDir string
2021
}
2122

2223
// AppInitRequest contains the parameters for calling copilot app init.
@@ -225,6 +226,17 @@ func NewCLI() (*CLI, error) {
225226
}, nil
226227
}
227228

229+
// NewCLIWithDir returns the Copilot CLI such that the commands are run in the specified
230+
// working directory.
231+
func NewCLIWithDir(workingDir string) (*CLI, error) {
232+
cli, err := NewCLI()
233+
if err != nil {
234+
return nil, err
235+
}
236+
cli.workingDir = workingDir
237+
return cli, nil
238+
}
239+
228240
/*Help runs
229241
copilot --help
230242
*/
@@ -850,6 +862,7 @@ func (cli *CLI) SvcPackage(opts *PackageInput) (string, error) {
850862
func (cli *CLI) exec(command *exec.Cmd) (string, error) {
851863
// Turn off colors
852864
command.Env = append(os.Environ(), "COLOR=false")
865+
command.Dir = cli.workingDir
853866
sess, err := gexec.Start(command, ginkgo.GinkgoWriter, ginkgo.GinkgoWriter)
854867
if err != nil {
855868
return "", err

e2e/pipeline/frontend/Dockerfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM nginx
2+
3+
COPY nginx.conf /etc/nginx/nginx.conf
4+
5+
RUN mkdir -p /www/data/frontend
6+
COPY index.html /www/data/frontend

e2e/pipeline/frontend/index.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7+
<title>AWS Copilot CLI</title>
8+
</head>
9+
<body style="background-color:rgb(35, 47, 62);">
10+
<div style='vertical-align: middle; margin:auto; width:50%; padding-top:200px; text-align: center;'>
11+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="141px" height="171px" viewBox="-0.5 -0.5 81 101"><defs><linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-232f3e-100-232f3e-100-s-0"><stop offset="0%" style="stop-color:#232F3E"/><stop offset="100%" style="stop-color:#232F3E"/></linearGradient></defs><g><path d="M 0 0 L 80 0 L 80 100 L 0 100 Z" fill="#ffffff" stroke="none" pointer-events="all"/><path d="M 1 1 L 79 1 L 79 79 L 1 79 Z" fill="url(#mx-gradient-232f3e-100-232f3e-100-s-0)" stroke="none" pointer-events="all"/><path d="M 38.07 13.01 C 37.9 13.01 37.73 13.06 37.57 13.15 L 16.61 25.24 C 16.28 25.43 16.08 25.79 16.08 26.17 L 16.08 52.54 C 16.09 52.92 16.29 53.26 16.61 53.45 L 39.42 66.81 C 39.75 67.01 40.16 67.01 40.49 66.81 L 61.42 54.63 C 61.75 54.44 61.95 54.09 61.95 53.71 C 61.95 53.33 61.74 52.98 61.41 52.79 L 51.36 47.12 C 51.03 46.94 50.63 46.94 50.31 47.13 L 40.07 53.14 L 28.12 46.26 L 28.12 32.5 L 38.63 26.49 C 38.96 26.3 39.16 25.95 39.16 25.57 L 39.16 14.07 C 39.16 13.78 39.04 13.51 38.84 13.31 C 38.64 13.11 38.36 13 38.07 13.01 Z M 42.08 13.05 C 41.79 13.04 41.51 13.15 41.31 13.35 C 41.1 13.55 40.98 13.82 40.98 14.11 L 40.98 25.62 C 40.99 26 41.19 26.35 41.52 26.54 L 51.84 32.55 L 51.84 44.59 C 51.84 44.96 52.04 45.31 52.37 45.5 L 62.32 51.28 C 62.65 51.47 63.05 51.47 63.38 51.28 C 63.71 51.09 63.92 50.74 63.92 50.36 L 63.92 26.19 C 63.91 25.81 63.71 25.46 63.38 25.28 L 42.58 13.2 C 42.43 13.11 42.25 13.06 42.08 13.05 Z M 37.05 15.91 L 37.05 24.96 L 26.54 30.97 C 26.21 31.16 26 31.51 26 31.89 L 26 46.87 C 26 47.25 26.2 47.6 26.54 47.79 L 39.54 55.28 C 39.87 55.47 40.28 55.47 40.6 55.28 L 50.85 49.27 L 58.76 53.73 L 39.96 64.67 L 18.2 51.92 L 18.2 26.77 Z M 43.11 15.95 L 61.8 26.8 L 61.8 48.52 L 53.96 43.97 L 53.96 31.94 C 53.96 31.56 53.76 31.21 53.43 31.02 L 43.11 25.01 Z" fill="#ffffff" stroke="none" pointer-events="all"/><g transform="translate(8.5,84.5)"><switch><foreignObject style="overflow:visible;" pointer-events="all" width="62" height="10" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"><div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 10px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; vertical-align: top; width: 63px; white-space: nowrap; overflow-wrap: normal; font-weight: bold; text-align: center;"><div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;white-space:normal;">Amazon ECS</div></div></foreignObject><text x="31" y="10" fill="#232F3E" text-anchor="middle" font-size="10px" font-family="Helvetica" font-weight="bold">Amazon ECS</text></switch></g></g></svg>
12+
</div>
13+
</body>
14+
</html>

e2e/pipeline/frontend/nginx.conf

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
events {
2+
worker_connections 768;
3+
}
4+
5+
http {
6+
server {
7+
root /www/data;
8+
listen 80;
9+
10+
location / {
11+
return 200 'healthcheck okay!';
12+
}
13+
14+
location /frontend/ {
15+
}
16+
}
17+
}

e2e/pipeline/pipeline_suite_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package pipeline_test
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
"time"
10+
11+
. "github.com/onsi/ginkgo"
12+
. "github.com/onsi/gomega"
13+
14+
"github.com/aws/copilot-cli/e2e/internal/client"
15+
)
16+
17+
// Command-line tools.
18+
var (
19+
copilot *client.CLI
20+
aws *client.AWS
21+
)
22+
23+
// Application identifiers.
24+
var (
25+
appName = fmt.Sprintf("e2e-pipeline-%d", time.Now().Unix())
26+
)
27+
28+
// CodeCommit credentials.
29+
var (
30+
repoName = appName
31+
codeCommitIAMUser = fmt.Sprintf("%s-cc", appName)
32+
codeCommitCreds *client.IAMServiceCreds
33+
)
34+
35+
func TestPipeline(t *testing.T) {
36+
RegisterFailHandler(Fail)
37+
RunSpecs(t, "Pipeline Suite")
38+
}
39+
40+
var _ = BeforeSuite(func() {
41+
cli, err := client.NewCLIWithDir(repoName)
42+
Expect(err).NotTo(HaveOccurred())
43+
copilot = cli
44+
aws = client.NewAWS()
45+
46+
creds, err := aws.CreateCodeCommitIAMUser(codeCommitIAMUser)
47+
Expect(err).NotTo(HaveOccurred())
48+
codeCommitCreds = creds
49+
})
50+
51+
var _ = AfterSuite(func() {
52+
_ = aws.DeleteCodeCommitRepo(appName)
53+
_ = aws.DeleteCodeCommitIAMUser(codeCommitIAMUser, codeCommitCreds.CredentialID)
54+
})

0 commit comments

Comments
 (0)