Skip to content

Commit b57b233

Browse files
authored
test(e2e): add e2e test for customized resources (#1264)
This PR adds e2e test for customized env resources. <!-- 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.
1 parent 9fd02db commit b57b233

9 files changed

Lines changed: 501 additions & 2 deletions

File tree

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package customized_env_test
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
"time"
10+
11+
"github.com/aws/copilot-cli/e2e/internal/client"
12+
"github.com/aws/copilot-cli/e2e/internal/command"
13+
. "github.com/onsi/ginkgo"
14+
. "github.com/onsi/gomega"
15+
)
16+
17+
var cli *client.CLI
18+
var appName string
19+
var vpcStackName string
20+
var vpcStackTemplatePath string
21+
var vpcImport client.EnvInitRequestVPCImport
22+
var vpcConfig client.EnvInitRequestVPCConfig
23+
24+
type vpcStackOutput struct {
25+
OutputKey string
26+
OutputValue string
27+
ExportName string
28+
}
29+
30+
/**
31+
The Customized Env Suite creates multiple environments with customized resources,
32+
deploys a service to it, and then
33+
tears it down.
34+
*/
35+
func Test_Customized_Env(t *testing.T) {
36+
RegisterFailHandler(Fail)
37+
RunSpecs(t, "Customized Env Svc Suite")
38+
}
39+
40+
var _ = BeforeSuite(func() {
41+
vpcStackName = fmt.Sprintf("e2e-customizedenv-vpc-stack-%d", time.Now().Unix())
42+
vpcStackTemplatePath = "file://vpc.yml"
43+
ecsCli, err := client.NewCLI()
44+
Expect(err).NotTo(HaveOccurred())
45+
cli = ecsCli
46+
appName = fmt.Sprintf("e2e-customizedenv-%d", time.Now().Unix())
47+
vpcConfig = client.EnvInitRequestVPCConfig{
48+
CIDR: "10.1.0.0/16",
49+
PrivateSubnetCIDRs: "10.1.2.0/24,10.1.3.0/24",
50+
PublicSubnetCIDRs: "10.1.0.0/24,10.1.1.0/24",
51+
}
52+
})
53+
54+
var _ = AfterSuite(func() {
55+
_, err := cli.AppDelete(map[string]string{"test": "default", "prod": "default"})
56+
Expect(err).NotTo(HaveOccurred())
57+
// Delete VPC stack.
58+
err = command.Run("aws", []string{"cloudformation", "delete-stack", "--stack-name", vpcStackName})
59+
Expect(err).NotTo(HaveOccurred())
60+
})
61+
62+
func BeforeAll(fn func()) {
63+
first := true
64+
BeforeEach(func() {
65+
if first {
66+
fn()
67+
first = false
68+
}
69+
})
70+
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package customized_env_test
5+
6+
import (
7+
"bytes"
8+
"encoding/json"
9+
"errors"
10+
"fmt"
11+
"net/http"
12+
13+
. "github.com/onsi/ginkgo"
14+
. "github.com/onsi/gomega"
15+
16+
"github.com/aws/copilot-cli/e2e/internal/client"
17+
"github.com/aws/copilot-cli/e2e/internal/command"
18+
)
19+
20+
var _ = Describe("Customized Env", func() {
21+
Context("when creating a new app", func() {
22+
var appInitErr error
23+
BeforeAll(func() {
24+
_, appInitErr = cli.AppInit(&client.AppInitRequest{
25+
AppName: appName,
26+
Tags: map[string]string{
27+
"e2e-test": "customized-env",
28+
},
29+
})
30+
})
31+
32+
It("app init succeeds", func() {
33+
Expect(appInitErr).NotTo(HaveOccurred())
34+
})
35+
36+
It("app init creates a copilot directory", func() {
37+
Expect("./copilot").Should(BeADirectory())
38+
})
39+
40+
It("app ls includes new app", func() {
41+
apps, err := cli.AppList()
42+
Expect(err).NotTo(HaveOccurred())
43+
Expect(apps).To(ContainSubstring(appName))
44+
})
45+
46+
It("app show includes app name", func() {
47+
appShowOutput, err := cli.AppShow(appName)
48+
Expect(err).NotTo(HaveOccurred())
49+
Expect(appShowOutput.Name).To(Equal(appName))
50+
Expect(appShowOutput.URI).To(BeEmpty())
51+
})
52+
})
53+
54+
Context("when deploying resources to be imported", func() {
55+
BeforeAll(func() {
56+
// TODO: move "aws" commands to aws.go
57+
err := command.Run("aws", []string{"cloudformation", "create-stack", "--stack-name",
58+
vpcStackName, "--template-body", vpcStackTemplatePath})
59+
Expect(err).NotTo(HaveOccurred(), "create vpc cloudformation stack")
60+
err = command.Run("aws", []string{"cloudformation", "wait", "stack-create-complete", "--stack-name", vpcStackName})
61+
Expect(err).NotTo(HaveOccurred(), "vpc stack create complete")
62+
})
63+
It("parse vpc stack output", func() {
64+
var b bytes.Buffer
65+
err := command.Run("bash", []string{"-c", fmt.Sprintf("aws cloudformation describe-stacks --stack-name %s | jq -r .Stacks[0].Outputs", vpcStackName)}, command.Stdout(&b))
66+
Expect(err).NotTo(HaveOccurred(), "describe vpc cloudformation stack")
67+
var outputs []vpcStackOutput
68+
err = json.Unmarshal(b.Bytes(), &outputs)
69+
Expect(err).NotTo(HaveOccurred(), "unmarshal vpc stack output")
70+
for _, output := range outputs {
71+
switch output.OutputKey {
72+
case "PrivateSubnets":
73+
vpcImport.PrivateSubnetIDs = output.OutputValue
74+
case "VpcId":
75+
vpcImport.ID = output.OutputValue
76+
case "PublicSubnets":
77+
vpcImport.PublicSubnetIDs = output.OutputValue
78+
}
79+
}
80+
if !vpcImport.IsSet() {
81+
err = errors.New("vpc resources are not configured properly")
82+
}
83+
Expect(err).NotTo(HaveOccurred(), "invalid vpc stack output")
84+
})
85+
})
86+
87+
Context("when adding cross account environments", func() {
88+
var (
89+
testEnvInitErr error
90+
prodEnvInitErr error
91+
)
92+
BeforeAll(func() {
93+
_, testEnvInitErr = cli.EnvInit(&client.EnvInitRequest{
94+
AppName: appName,
95+
EnvName: "test",
96+
Profile: "default",
97+
Prod: false,
98+
VPCImport: vpcImport,
99+
CustomizedEnv: true,
100+
})
101+
_, prodEnvInitErr = cli.EnvInit(&client.EnvInitRequest{
102+
AppName: appName,
103+
EnvName: "prod",
104+
Profile: "default",
105+
Prod: true,
106+
VPCConfig: vpcConfig,
107+
CustomizedEnv: true,
108+
})
109+
})
110+
111+
It("env init should succeed for test and prod envs", func() {
112+
Expect(testEnvInitErr).NotTo(HaveOccurred())
113+
Expect(prodEnvInitErr).NotTo(HaveOccurred())
114+
})
115+
116+
It("env ls should list both envs", func() {
117+
envListOutput, err := cli.EnvList(appName)
118+
Expect(err).NotTo(HaveOccurred())
119+
Expect(len(envListOutput.Envs)).To(Equal(2))
120+
envs := map[string]client.EnvDescription{}
121+
for _, env := range envListOutput.Envs {
122+
envs[env.Name] = env
123+
Expect(env.ExecutionRole).NotTo(BeEmpty())
124+
Expect(env.ManagerRole).NotTo(BeEmpty())
125+
}
126+
127+
Expect(envs["test"]).NotTo(BeNil())
128+
Expect(envs["test"].Prod).To(BeFalse())
129+
130+
Expect(envs["prod"]).NotTo(BeNil())
131+
Expect(envs["prod"].Prod).To(BeTrue())
132+
})
133+
})
134+
135+
Context("when adding a svc", func() {
136+
var (
137+
frontEndInitErr error
138+
)
139+
BeforeAll(func() {
140+
_, frontEndInitErr = cli.SvcInit(&client.SvcInitRequest{
141+
Name: "front-end",
142+
SvcType: "Load Balanced Web Service",
143+
Dockerfile: "./front-end/Dockerfile",
144+
SvcPort: "80",
145+
})
146+
})
147+
148+
It("svc init should succeed", func() {
149+
Expect(frontEndInitErr).NotTo(HaveOccurred())
150+
})
151+
152+
It("svc init should create a svc manifest", func() {
153+
Expect("./copilot/front-end/manifest.yml").Should(BeAnExistingFile())
154+
})
155+
156+
It("svc ls should list the svc", func() {
157+
svcList, svcListError := cli.SvcList(appName)
158+
Expect(svcListError).NotTo(HaveOccurred())
159+
Expect(len(svcList.Services)).To(Equal(1))
160+
Expect(svcList.Services[0].Name).To(Equal("front-end"))
161+
})
162+
})
163+
164+
Context("when deploying a svc to test and prod envs", func() {
165+
var (
166+
testDeployErr error
167+
prodEndDeployErr error
168+
svcName string
169+
)
170+
BeforeAll(func() {
171+
svcName = "front-end"
172+
_, testDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
173+
Name: svcName,
174+
EnvName: "test",
175+
ImageTag: "gallopinggurdey",
176+
})
177+
178+
_, prodEndDeployErr = cli.SvcDeploy(&client.SvcDeployInput{
179+
Name: svcName,
180+
EnvName: "prod",
181+
ImageTag: "gallopinggurdey",
182+
})
183+
})
184+
185+
It("svc deploy should succeed to both environments", func() {
186+
Expect(testDeployErr).NotTo(HaveOccurred())
187+
Expect(prodEndDeployErr).NotTo(HaveOccurred())
188+
})
189+
190+
It("svc show should include a valid URL and description for test and prod envs", func() {
191+
svc, svcShowErr := cli.SvcShow(&client.SvcShowRequest{
192+
AppName: appName,
193+
Name: svcName,
194+
})
195+
Expect(svcShowErr).NotTo(HaveOccurred())
196+
Expect(len(svc.Routes)).To(Equal(2))
197+
// Group routes by environment
198+
envRoutes := map[string]client.SvcShowRoutes{}
199+
for _, route := range svc.Routes {
200+
envRoutes[route.Environment] = route
201+
}
202+
203+
Expect(len(svc.ServiceDiscoveries)).To(Equal(1))
204+
Expect(svc.ServiceDiscoveries[0].Environment).To(ConsistOf("test", "prod"))
205+
Expect(svc.ServiceDiscoveries[0].Namespace).To(Equal(fmt.Sprintf("%s.%s.local:80", svc.SvcName, appName)))
206+
207+
// Call each environment's endpoint and ensure it returns a 200
208+
for _, env := range []string{"test", "prod"} {
209+
route := envRoutes[env]
210+
Expect(route.Environment).To(Equal(env))
211+
Eventually(func() (int, error) {
212+
resp, fetchErr := http.Get(route.URL)
213+
return resp.StatusCode, fetchErr
214+
}, "30s", "1s").Should(Equal(200))
215+
}
216+
})
217+
218+
It("svc logs should display logs", func() {
219+
for _, envName := range []string{"test", "prod"} {
220+
var svcLogs []client.SvcLogsOutput
221+
var svcLogsErr error
222+
Eventually(func() ([]client.SvcLogsOutput, error) {
223+
svcLogs, svcLogsErr = cli.SvcLogs(&client.SvcLogsRequest{
224+
AppName: appName,
225+
Name: svcName,
226+
EnvName: envName,
227+
Since: "1h",
228+
})
229+
return svcLogs, svcLogsErr
230+
}, "60s", "10s").ShouldNot(BeEmpty())
231+
232+
for _, logLine := range svcLogs {
233+
Expect(logLine.Message).NotTo(Equal(""))
234+
Expect(logLine.LogStreamName).NotTo(Equal(""))
235+
Expect(logLine.Timestamp).NotTo(Equal(0))
236+
Expect(logLine.IngestionTime).NotTo(Equal(0))
237+
}
238+
}
239+
})
240+
241+
It("env show should display info for test and prod envs", func() {
242+
envs := map[string]client.EnvDescription{}
243+
for _, envName := range []string{"test", "prod"} {
244+
envShowOutput, envShowErr := cli.EnvShow(&client.EnvShowRequest{
245+
AppName: appName,
246+
EnvName: envName,
247+
})
248+
Expect(envShowErr).NotTo(HaveOccurred())
249+
Expect(envShowOutput.Environment.Name).To(Equal(envName))
250+
Expect(envShowOutput.Environment.App).To(Equal(appName))
251+
252+
Expect(len(envShowOutput.Services)).To(Equal(1))
253+
Expect(envShowOutput.Services[0].Name).To(Equal(svcName))
254+
Expect(envShowOutput.Services[0].Type).To(Equal("Load Balanced Web Service"))
255+
256+
Expect(len(envShowOutput.Tags)).To(Equal(3))
257+
Expect(envShowOutput.Tags["copilot-application"]).To(Equal(appName))
258+
Expect(envShowOutput.Tags["copilot-environment"]).To(Equal(envName))
259+
Expect(envShowOutput.Tags["e2e-test"]).To(Equal("customized-env"))
260+
261+
envs[envShowOutput.Environment.Name] = envShowOutput.Environment
262+
}
263+
Expect(envs["test"]).NotTo(BeNil())
264+
Expect(envs["test"].Prod).To(BeFalse())
265+
Expect(envs["prod"]).NotTo(BeNil())
266+
Expect(envs["prod"].Prod).To(BeTrue())
267+
})
268+
})
269+
})
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/front-end
6+
COPY index.html /www/data/front-end
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>
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 /front-end/ {
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)