Skip to content

Commit 16d7aa7

Browse files
SoManyHsallisaurus
authored andcommitted
Add top-level volumes to ecsProject
Also changed tests for ConvertToMountPoints to better reflect actual inputs for volumes in docker-compose, as well as to account for mutating top-level volumes struct TODO: set volumes on project in parseV3
1 parent dff67f2 commit 16d7aa7

File tree

8 files changed

+268
-214
lines changed

8 files changed

+268
-214
lines changed

ecs-cli/modules/cli/compose/adapter/convert_test.go

Lines changed: 77 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import (
2121
"github.com/aws/aws-sdk-go/aws"
2222
"github.com/aws/aws-sdk-go/service/ecs"
2323
"github.com/docker/libcompose/config"
24-
"github.com/docker/libcompose/project"
2524
"github.com/docker/libcompose/yaml"
2625
"github.com/stretchr/testify/assert"
2726
)
@@ -205,20 +204,22 @@ func verifyPortMapping(t *testing.T, output *ecs.PortMapping, hostPort, containe
205204
}
206205

207206
func TestConvertToMountPoints(t *testing.T) {
207+
// Valid inputs with host and container paths set:
208+
// /tmp/cache
209+
// /tmp/cache2
210+
// ./cache:/tmp/cache
211+
// /tmp/cache:ro
212+
// ./cache:/tmp/cache:ro
213+
// ./cache:/tmp/cache:rw
214+
// named_volume:/tmp/cache
208215
onlyContainerPath := yaml.Volume{Destination: containerPath}
209216
onlyContainerPath2 := yaml.Volume{Destination: containerPath2}
210-
hostAndContainerPath := yaml.Volume{Source: hostPath, Destination: containerPath} // "./cache:/tmp/cache"
217+
hostAndContainerPath := yaml.Volume{Source: hostPath, Destination: containerPath}
211218
onlyContainerPathWithRO := yaml.Volume{Destination: containerPath, AccessMode: "ro"}
212-
hostAndContainerPathWithRO := yaml.Volume{Source: hostPath, Destination: containerPath, AccessMode: "ro"} // "./cache:/tmp/cache:ro"
219+
hostAndContainerPathWithRO := yaml.Volume{Source: hostPath, Destination: containerPath, AccessMode: "ro"}
213220
hostAndContainerPathWithRW := yaml.Volume{Source: hostPath, Destination: containerPath, AccessMode: "rw"}
214221
namedVolumeAndContainerPath := yaml.Volume{Source: namedVolume, Destination: containerPath}
215222

216-
volumes := &Volumes{
217-
VolumeWithHost: make(map[string]string), // map with key:=hostSourcePath value:=VolumeName
218-
VolumeEmptyHost: []string{namedVolume}, // Declare one volume with an empty host
219-
}
220-
221-
// Valid inputs with host and container paths set
222223
mountPointsIn := yaml.Volumes{
223224
Volumes: []*yaml.Volume{
224225
&onlyContainerPath,
@@ -231,21 +232,59 @@ func TestConvertToMountPoints(t *testing.T) {
231232
},
232233
}
233234

234-
mountPointsOut, err := ConvertToMountPoints(&mountPointsIn, volumes)
235-
if err != nil {
236-
t.Fatalf("Expected to convert [%v] mountPoints without errors. But got [%v]", mountPointsIn, err)
235+
expectedVolumeWithHost := map[string]string{hostPath: "volume-3"}
236+
expectedVolumeEmptyHost := []string{namedVolume, "volume-1", "volume-2", "volume-4"}
237+
expectedMountPoints := []*ecs.MountPoint{
238+
{
239+
ContainerPath: aws.String("/tmp/cache"),
240+
ReadOnly: aws.Bool(false),
241+
SourceVolume: aws.String("volume-1"),
242+
},
243+
{
244+
ContainerPath: aws.String("/tmp/cache2"),
245+
ReadOnly: aws.Bool(false),
246+
SourceVolume: aws.String("volume-2"),
247+
},
248+
{
249+
ContainerPath: aws.String("/tmp/cache"),
250+
ReadOnly: aws.Bool(false),
251+
SourceVolume: aws.String("volume-3"),
252+
},
253+
{
254+
ContainerPath: aws.String("/tmp/cache"),
255+
ReadOnly: aws.Bool(true),
256+
SourceVolume: aws.String("volume-4"),
257+
},
258+
{
259+
ContainerPath: aws.String("/tmp/cache"),
260+
ReadOnly: aws.Bool(true),
261+
SourceVolume: aws.String("volume-3"),
262+
},
263+
{
264+
ContainerPath: aws.String("/tmp/cache"),
265+
ReadOnly: aws.Bool(false),
266+
SourceVolume: aws.String("volume-3"),
267+
},
268+
{
269+
ContainerPath: aws.String("/tmp/cache"),
270+
ReadOnly: aws.Bool(false),
271+
SourceVolume: aws.String("named_volume"),
272+
},
237273
}
238-
if len(mountPointsIn.Volumes) != len(mountPointsOut) {
239-
t.Errorf("Incorrect conversion. Input [%v] Output [%v]", mountPointsIn, mountPointsOut)
274+
275+
volumes := &Volumes{
276+
VolumeWithHost: make(map[string]string), // This field should be empty before ConvertToMountPoints is called
277+
VolumeEmptyHost: []string{namedVolume}, // We expect ConvertToVolumes to already have been called, so any named volumes should have been added to VolumeEmptyHost
240278
}
241279

242-
verifyMountPoint(t, mountPointsOut[0], volumes, "", containerPath, false, 1) // 1 is the counter for the first volume with an empty host path
243-
verifyMountPoint(t, mountPointsOut[1], volumes, "", containerPath2, false, 2) // 2 is the counter for the second volume with an empty host path
244-
verifyMountPoint(t, mountPointsOut[2], volumes, hostPath, containerPath, false, 2)
245-
verifyMountPoint(t, mountPointsOut[3], volumes, "", containerPath, true, 3) // 3 is the counter for the third volume with an empty host path
246-
verifyMountPoint(t, mountPointsOut[4], volumes, hostPath, containerPath, true, 3)
247-
verifyMountPoint(t, mountPointsOut[5], volumes, hostPath, containerPath, false, 3)
248-
verifyMountPoint(t, mountPointsOut[6], volumes, namedVolume, containerPath, false, 3)
280+
mountPointsOut, err := ConvertToMountPoints(&mountPointsIn, volumes)
281+
assert.NoError(t, err, "Unexpected error converting MountPoints")
282+
283+
// Expect top-level volumes fields to be populated
284+
assert.Equal(t, expectedVolumeWithHost, volumes.VolumeWithHost, "Expected volumeWithHost to match")
285+
assert.Equal(t, expectedVolumeEmptyHost, volumes.VolumeEmptyHost, "Expected volumeEmptyHost to match")
286+
287+
assert.ElementsMatch(t, expectedMountPoints, mountPointsOut, "Expected Mount Points to match")
249288

250289
if mountPointsOut[0].SourceVolume == mountPointsOut[1].SourceVolume {
251290
t.Errorf("Expected volume %v (onlyContainerPath) and %v (onlyContainerPath2) to be different", mountPointsOut[0].SourceVolume, mountPointsOut[1].SourceVolume)
@@ -273,10 +312,7 @@ func TestConvertToMountPointsWithInvalidAccessMode(t *testing.T) {
273312
}
274313

275314
_, err := ConvertToMountPoints(&mountPointsIn, volumes)
276-
277-
if err == nil {
278-
t.Errorf("Expected to get error for mountPoint[%s] but didn't.", hostAndContainerPathWithIncorrectAccess)
279-
}
315+
assert.Error(t, err, "Expected error converting MountPoints")
280316
}
281317

282318
func TestConvertToMountPointsNullContainerVolumes(t *testing.T) {
@@ -285,33 +321,27 @@ func TestConvertToMountPointsNullContainerVolumes(t *testing.T) {
285321
VolumeEmptyHost: []string{namedVolume},
286322
}
287323
mountPointsOut, err := ConvertToMountPoints(nil, volumes)
288-
if err != nil {
289-
t.Fatalf("Expected to convert nil mountPoints without errors. But got [%v]", err)
290-
}
291-
if len(mountPointsOut) != 0 {
292-
t.Errorf("Incorrect conversion. Input nil Output [%v]", mountPointsOut)
293-
}
324+
assert.NoError(t, err, "Unexpected error converting MountPoints")
325+
assert.Empty(t, mountPointsOut, "Expected mount points to be empty")
294326
}
295327

296-
func verifyMountPoint(t *testing.T, output *ecs.MountPoint, volumes *Volumes,
297-
source, containerPath string, readonly bool, EmptyHostCtr int) {
298-
sourceVolume := ""
299-
if containerPath != *output.ContainerPath {
300-
t.Errorf("Expected containerPath [%s] But was [%s]", containerPath, *output.ContainerPath)
301-
}
302-
if source == "" {
303-
sourceVolume = volumes.VolumeEmptyHost[EmptyHostCtr]
304-
} else if project.IsNamedVolume(source) {
305-
sourceVolume = source
306-
} else {
307-
sourceVolume = volumes.VolumeWithHost[source]
328+
func TestConvertToMountPointsWithNoCorrespondingNamedVolume(t *testing.T) {
329+
volumes := &Volumes{
330+
VolumeWithHost: make(map[string]string),
331+
VolumeEmptyHost: []string{}, // Top-level named volumes is empty
308332
}
309-
if sourceVolume != *output.SourceVolume {
310-
t.Errorf("Expected sourceVolume [%s] But was [%s]", sourceVolume, *output.SourceVolume)
333+
334+
namedVolume := yaml.Volume{
335+
Source: namedVolume,
336+
Destination: containerPath,
311337
}
312-
if readonly != *output.ReadOnly {
313-
t.Errorf("Expected readonly [%v] But was [%v]", readonly, *output.ReadOnly)
338+
339+
mountPointsIn := yaml.Volumes{
340+
Volumes: []*yaml.Volume{&namedVolume},
314341
}
342+
343+
_, err := ConvertToMountPoints(&mountPointsIn, volumes)
344+
assert.Error(t, err, "Expected error converting MountPoints")
315345
}
316346

317347
func TestConvertToExtraHosts(t *testing.T) {

ecs-cli/modules/cli/compose/project/mock/project.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,13 @@ func (_m *MockProject) Up() error {
186186
func (_mr *_MockProjectRecorder) Up() *gomock.Call {
187187
return _mr.mock.ctrl.RecordCall(_mr.mock, "Up")
188188
}
189+
190+
func (_m *MockProject) VolumeConfigs() *adapter.Volumes {
191+
ret := _m.ctrl.Call(_m, "VolumeConfigs")
192+
ret0, _ := ret[0].(*adapter.Volumes)
193+
return ret0
194+
}
195+
196+
func (_mr *_MockProjectRecorder) VolumeConfigs() *gomock.Call {
197+
return _mr.mock.ctrl.RecordCall(_mr.mock, "VolumeConfigs")
198+
}

ecs-cli/modules/cli/compose/project/project.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ type Project interface {
3838
Context() *context.ECSContext
3939
ServiceConfigs() *config.ServiceConfigs
4040
ContainerConfigs() []adapter.ContainerConfig // TODO change this to a pointer to slice?
41+
VolumeConfigs() *adapter.Volumes
4142
Entity() entity.ProjectEntity
4243

4344
// commands
@@ -57,6 +58,7 @@ type ecsProject struct {
5758

5859
// TODO: use this instead of project.Project
5960
containerConfigs []adapter.ContainerConfig
61+
volumes *adapter.Volumes
6062

6163
ecsContext *context.ECSContext
6264

@@ -100,8 +102,8 @@ func (p *ecsProject) ServiceConfigs() *config.ServiceConfigs {
100102
}
101103

102104
// VolumeConfigs returns a map of Volume Configuration loaded from compose yaml file
103-
func (p *ecsProject) VolumeConfigs() map[string]*config.VolumeConfig {
104-
return p.Project.VolumeConfigs
105+
func (p *ecsProject) VolumeConfigs() *adapter.Volumes {
106+
return p.volumes
105107
}
106108

107109
func (p *ecsProject) ContainerConfigs() []adapter.ContainerConfig {
@@ -138,7 +140,7 @@ func (p *ecsProject) Parse() error {
138140
return p.transformTaskDefinition()
139141
}
140142

141-
// parseCompose sets data from the compose files on the ecsProject
143+
// parseCompose sets data from the compose files on the ecsProject, including ContainerConfigs and VolumeConfigs
142144
func (p *ecsProject) parseCompose() error {
143145
logrus.Debug("Parsing the compose yaml...")
144146

@@ -153,6 +155,7 @@ func (p *ecsProject) parseCompose() error {
153155
if err != nil {
154156
return err
155157
}
158+
// TODO: set this in parseV1V2 itself?
156159
p.containerConfigs = *configs
157160
case "3", "3.0":
158161
configs, err := p.parseV3()
@@ -197,7 +200,7 @@ func (p *ecsProject) transformTaskDefinition() error {
197200

198201
taskDefinition, err := utils.ConvertToTaskDefinition(
199202
&ecsContext.Context,
200-
p.VolumeConfigs(),
203+
p.VolumeConfigs(), // will have already been transformed to adapter.Volumes in parseCompose
201204
p.ContainerConfigs(), // TODO Change to pointer on project?
202205
taskRoleArn,
203206
requiredCompatibilities,

ecs-cli/modules/cli/compose/project/project_parseV1V2.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ func (p *ecsProject) parseV1V2() (*[]adapter.ContainerConfig, error) {
1616
// context. It sets up the name, the composefile and the composebytes
1717
// (the composefile content). This is where libcompose ServiceConfigs
1818
// and VolumeConfigs gets loaded.
19+
20+
// TODO instantiate the libcompose project here instead of in the factory
1921
if err := p.Project.Parse(); err != nil {
2022
return nil, err
2123
}
@@ -25,6 +27,7 @@ func (p *ecsProject) parseV1V2() (*[]adapter.ContainerConfig, error) {
2527
if err != nil {
2628
return nil, err
2729
}
30+
p.volumes = volumes
2831

2932
context := &p.Context().Context
3033
serviceConfigs := p.Project.ServiceConfigs

ecs-cli/modules/cli/compose/project/project_parseV1V2_test.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -213,19 +213,29 @@ func TestParseV1V2_Version2Files(t *testing.T) {
213213
memory := int64(512)
214214
mountPoints := []*ecs.MountPoint{
215215
{
216-
ContainerPath: aws.String("/tmp/cache"),
216+
ContainerPath: aws.String("/var/lib/mysql"),
217217
ReadOnly: aws.Bool(false),
218-
SourceVolume: aws.String("banana"),
218+
SourceVolume: aws.String("volume-1"),
219219
},
220220
{
221-
ContainerPath: aws.String("/tmp/cache"),
221+
ContainerPath: aws.String("/var/lib/mysql"),
222222
ReadOnly: aws.Bool(false),
223-
SourceVolume: aws.String("volume-1"),
223+
SourceVolume: aws.String("volume-2"),
224224
},
225225
{
226226
ContainerPath: aws.String("/tmp/cache"),
227+
ReadOnly: aws.Bool(false),
228+
SourceVolume: aws.String("volume-3"),
229+
},
230+
{
231+
ContainerPath: aws.String("/etc/configs/"),
227232
ReadOnly: aws.Bool(true),
228-
SourceVolume: aws.String("volume-2"),
233+
SourceVolume: aws.String("volume-4"),
234+
},
235+
{
236+
ContainerPath: aws.String("/var/lib/mysql"),
237+
ReadOnly: aws.Bool(false),
238+
SourceVolume: aws.String("datavolume"),
229239
},
230240
}
231241
ports := []*ecs.PortMapping{
@@ -279,11 +289,13 @@ services:
279289
mysql:
280290
image: mysql
281291
volumes:
282-
- banana:/tmp/cache
283-
- :/tmp/cache
284-
- ./cache:/tmp/cache:ro
292+
- /var/lib/mysql
293+
- /opt/data:/var/lib/mysql
294+
- ./cache:/tmp/cache:rw
295+
- ~/configs:/etc/configs/:ro
296+
- datavolume:/var/lib/mysql
285297
volumes:
286-
banana:`
298+
datavolume:`
287299

288300
// Setup docker-compose file
289301
tmpfile, err := ioutil.TempFile("", "test")
@@ -326,7 +338,7 @@ volumes:
326338
assert.NoError(t, err, "Unexpected error retrieving wordpress config")
327339

328340
assert.Equal(t, mysqlImage, mysql.Image, "Expected mysql Image to match")
329-
assert.Equal(t, mountPoints, mysql.MountPoints, "Expected MountPoints to match")
341+
assert.ElementsMatch(t, mountPoints, mysql.MountPoints, "Expected MountPoints to match")
330342
}
331343

332344
func TestParseV1V2_Version1_WithEnvFile(t *testing.T) {

ecs-cli/modules/cli/compose/project/project_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,42 @@ services:
6363
assert.Equal(t, testProjectName, project.ecsContext.ProjectName, "Expected ProjectName to be overridden.")
6464
}
6565

66+
func TestParseCompose_V2_WithVolumeConfigs(t *testing.T) {
67+
// Setup docker-compose file
68+
composeFileString := `version: '2'
69+
services:
70+
wordpress:
71+
image: wordpress
72+
mysql:
73+
image: mysql
74+
volumes:
75+
- banana:/tmp/cache
76+
- :/tmp/cache
77+
- ./cache:/tmp/cache:ro
78+
volumes:
79+
banana:`
80+
81+
tmpfile, err := ioutil.TempFile("", "test")
82+
assert.NoError(t, err, "Unexpected error in creating test file")
83+
84+
defer os.Remove(tmpfile.Name())
85+
86+
_, err = tmpfile.Write([]byte(composeFileString))
87+
assert.NoError(t, err, "Unexpected error in writing to test file")
88+
89+
err = tmpfile.Close()
90+
assert.NoError(t, err, "Unexpected error closing file")
91+
92+
// Set up project
93+
project := setupTestProject(t)
94+
project.ecsContext.ComposeFiles = append(project.ecsContext.ComposeFiles, tmpfile.Name())
95+
96+
err = project.parseCompose()
97+
98+
// verify VolumeConfigs are populated
99+
assert.NotEmpty(t, project.VolumeConfigs(), "Expected volume configs to not be empty")
100+
}
101+
66102
func TestParseECSParams(t *testing.T) {
67103
ecsParamsString := `version: 1
68104
task_definition:

ecs-cli/modules/utils/compose/convert_task_definition.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ type TaskDefParams struct {
8787

8888
// ConvertToTaskDefinition transforms the yaml configs to its ecs equivalent (task definition)
8989
// TODO container config a pointer to slice?
90-
func ConvertToTaskDefinition(context *project.Context, volumeConfigs map[string]*config.VolumeConfig,
90+
func ConvertToTaskDefinition(context *project.Context, volumes *adapter.Volumes,
9191
containerConfigs []adapter.ContainerConfig, taskRoleArn string, requiredCompatibilites string, ecsParams *ECSParams) (*ecs.TaskDefinition, error) {
9292
if len(containerConfigs) == 0 {
9393
return nil, errors.New("cannot create a task definition with no containers; invalid service config")
@@ -108,12 +108,6 @@ func ConvertToTaskDefinition(context *project.Context, volumeConfigs map[string]
108108
taskRoleArn = taskDefParams.taskRoleArn
109109
}
110110

111-
// TODO: Refactor when Volumes added to top level project
112-
volumes, err := adapter.ConvertToVolumes(volumeConfigs)
113-
if err != nil {
114-
return nil, err
115-
}
116-
117111
// Create containerDefinitions
118112
containerDefinitions := []*ecs.ContainerDefinition{}
119113

0 commit comments

Comments
 (0)