mirror of https://github.com/docker/compose.git
Merge pull request #783 from docker/efs_opts
Let user pass EFS create option by driver_opts
This commit is contained in:
commit
82db90646f
|
@ -19,15 +19,17 @@ package ecs
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/api/compose"
|
"github.com/docker/compose-cli/api/compose"
|
||||||
|
|
||||||
"github.com/docker/compose-cli/errdefs"
|
"github.com/docker/compose-cli/errdefs"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation"
|
"github.com/awslabs/goformation/v4/cloudformation"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/ec2"
|
"github.com/awslabs/goformation/v4/cloudformation/ec2"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
"github.com/awslabs/goformation/v4/cloudformation/ecs"
|
||||||
|
"github.com/awslabs/goformation/v4/cloudformation/efs"
|
||||||
"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
|
"github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -199,24 +201,23 @@ func (b *ecsAPIService) parseExternalVolumes(ctx context.Context, project *types
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if id == "" {
|
if id != "" {
|
||||||
tags["Name"] = fmt.Sprintf("%s_%s", project.Name, vol.Name)
|
|
||||||
logrus.Debug("no EFS filesystem found, create a fresh new one")
|
|
||||||
id, err = b.aws.CreateFileSystem(ctx, tags)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
filesystems[name] = id
|
filesystems[name] = id
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return filesystems, nil
|
return filesystems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensureResources create required resources in template if not yet defined
|
// ensureResources create required resources in template if not yet defined
|
||||||
func (b *ecsAPIService) ensureResources(resources *awsResources, project *types.Project, template *cloudformation.Template) {
|
func (b *ecsAPIService) ensureResources(resources *awsResources, project *types.Project, template *cloudformation.Template) error {
|
||||||
b.ensureCluster(resources, project, template)
|
b.ensureCluster(resources, project, template)
|
||||||
b.ensureNetworks(resources, project, template)
|
b.ensureNetworks(resources, project, template)
|
||||||
|
err := b.ensureVolumes(resources, project, template)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
b.ensureLoadBalancer(resources, project, template)
|
b.ensureLoadBalancer(resources, project, template)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
func (b *ecsAPIService) ensureCluster(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
||||||
|
@ -257,6 +258,70 @@ func (b *ecsAPIService) ensureNetworks(r *awsResources, project *types.Project,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *ecsAPIService) ensureVolumes(r *awsResources, project *types.Project, template *cloudformation.Template) error {
|
||||||
|
for name, volume := range project.Volumes {
|
||||||
|
if _, ok := r.filesystems[name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var backupPolicy *efs.FileSystem_BackupPolicy
|
||||||
|
if backup, ok := volume.DriverOpts["backup_policy"]; ok {
|
||||||
|
backupPolicy = &efs.FileSystem_BackupPolicy{
|
||||||
|
Status: backup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lifecyclePolicies []efs.FileSystem_LifecyclePolicy
|
||||||
|
if policy, ok := volume.DriverOpts["lifecycle_policy"]; ok {
|
||||||
|
lifecyclePolicies = append(lifecyclePolicies, efs.FileSystem_LifecyclePolicy{
|
||||||
|
TransitionToIA: strings.TrimSpace(policy),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var provisionedThroughputInMibps float64
|
||||||
|
if t, ok := volume.DriverOpts["provisioned_throughput"]; ok {
|
||||||
|
v, err := strconv.ParseFloat(t, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
provisionedThroughputInMibps = v
|
||||||
|
}
|
||||||
|
|
||||||
|
var performanceMode = volume.DriverOpts["performance_mode"]
|
||||||
|
var throughputMode = volume.DriverOpts["throughput_mode"]
|
||||||
|
var kmsKeyID = volume.DriverOpts["kms_key_id"]
|
||||||
|
|
||||||
|
n := volumeResourceName(name)
|
||||||
|
template.Resources[n] = &efs.FileSystem{
|
||||||
|
BackupPolicy: backupPolicy,
|
||||||
|
Encrypted: true,
|
||||||
|
FileSystemPolicy: nil,
|
||||||
|
FileSystemTags: []efs.FileSystem_ElasticFileSystemTag{
|
||||||
|
{
|
||||||
|
Key: compose.ProjectTag,
|
||||||
|
Value: project.Name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: compose.VolumeTag,
|
||||||
|
Value: name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "Name",
|
||||||
|
Value: fmt.Sprintf("%s_%s", project.Name, name),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
KmsKeyId: kmsKeyID,
|
||||||
|
LifecyclePolicies: lifecyclePolicies,
|
||||||
|
PerformanceMode: performanceMode,
|
||||||
|
ProvisionedThroughputInMibps: provisionedThroughputInMibps,
|
||||||
|
ThroughputMode: throughputMode,
|
||||||
|
AWSCloudFormationDeletionPolicy: "Retain",
|
||||||
|
}
|
||||||
|
r.filesystems[name] = cloudformation.Ref(n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
func (b *ecsAPIService) ensureLoadBalancer(r *awsResources, project *types.Project, template *cloudformation.Template) {
|
||||||
if r.loadBalancer != "" {
|
if r.loadBalancer != "" {
|
||||||
return
|
return
|
||||||
|
|
|
@ -58,7 +58,10 @@ func (b *ecsAPIService) convert(ctx context.Context, project *types.Project) (*c
|
||||||
}
|
}
|
||||||
|
|
||||||
template := cloudformation.NewTemplate()
|
template := cloudformation.NewTemplate()
|
||||||
b.ensureResources(&resources, project, template)
|
err = b.ensureResources(&resources, project, template)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for name, secret := range project.Secrets {
|
for name, secret := range project.Secrets {
|
||||||
err := b.createSecret(project, name, secret, template)
|
err := b.createSecret(project, name, secret, template)
|
||||||
|
@ -441,6 +444,10 @@ func serviceResourceName(service string) string {
|
||||||
return fmt.Sprintf("%sService", normalizeResourceName(service))
|
return fmt.Sprintf("%sService", normalizeResourceName(service))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func volumeResourceName(service string) string {
|
||||||
|
return fmt.Sprintf("%sFilesystem", normalizeResourceName(service))
|
||||||
|
}
|
||||||
|
|
||||||
func normalizeResourceName(s string) string {
|
func normalizeResourceName(s string) string {
|
||||||
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
|
return strings.Title(regexp.MustCompile("[^a-zA-Z0-9]+").ReplaceAllString(s, ""))
|
||||||
}
|
}
|
||||||
|
|
|
@ -357,20 +357,8 @@ networks:
|
||||||
assert.Check(t, s.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups[0] == "sg-123abc") //nolint:staticcheck
|
assert.Check(t, s.NetworkConfiguration.AwsvpcConfiguration.SecurityGroups[0] == "sg-123abc") //nolint:staticcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func testVolume(t *testing.T, yaml string, fn ...func(m *MockAPIMockRecorder)) {
|
|
||||||
template := convertYaml(t, yaml, fn...)
|
|
||||||
|
|
||||||
s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget)
|
|
||||||
assert.Check(t, s != nil)
|
|
||||||
assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck
|
|
||||||
|
|
||||||
s = template.Resources["DbdataNFSMountTargetOnSubnet2"].(*efs.MountTarget)
|
|
||||||
assert.Check(t, s != nil)
|
|
||||||
assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUseExternalVolume(t *testing.T) {
|
func TestUseExternalVolume(t *testing.T) {
|
||||||
testVolume(t, `
|
template := convertYaml(t, `
|
||||||
services:
|
services:
|
||||||
test:
|
test:
|
||||||
image: nginx
|
image: nginx
|
||||||
|
@ -381,30 +369,50 @@ volumes:
|
||||||
`, useDefaultVPC, func(m *MockAPIMockRecorder) {
|
`, useDefaultVPC, func(m *MockAPIMockRecorder) {
|
||||||
m.FileSystemExists(gomock.Any(), "fs-123abc").Return(true, nil)
|
m.FileSystemExists(gomock.Any(), "fs-123abc").Return(true, nil)
|
||||||
})
|
})
|
||||||
|
s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget)
|
||||||
|
assert.Check(t, s != nil)
|
||||||
|
assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck
|
||||||
|
|
||||||
|
s = template.Resources["DbdataNFSMountTargetOnSubnet2"].(*efs.MountTarget)
|
||||||
|
assert.Check(t, s != nil)
|
||||||
|
assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateVolume(t *testing.T) {
|
func TestCreateVolume(t *testing.T) {
|
||||||
testVolume(t, `
|
template := convertYaml(t, `
|
||||||
services:
|
services:
|
||||||
test:
|
test:
|
||||||
image: nginx
|
image: nginx
|
||||||
volumes:
|
volumes:
|
||||||
db-data: {}
|
db-data:
|
||||||
|
driver_opts:
|
||||||
|
backup_policy: ENABLED
|
||||||
|
lifecycle_policy: AFTER_30_DAYS
|
||||||
|
performance_mode: maxIO
|
||||||
|
throughput_mode: provisioned
|
||||||
|
provisioned_throughput: 1024
|
||||||
`, useDefaultVPC, func(m *MockAPIMockRecorder) {
|
`, useDefaultVPC, func(m *MockAPIMockRecorder) {
|
||||||
m.FindFileSystem(gomock.Any(), map[string]string{
|
m.FindFileSystem(gomock.Any(), map[string]string{
|
||||||
compose.ProjectTag: t.Name(),
|
compose.ProjectTag: t.Name(),
|
||||||
compose.VolumeTag: "db-data",
|
compose.VolumeTag: "db-data",
|
||||||
}).Return("", nil)
|
}).Return("", nil)
|
||||||
m.CreateFileSystem(gomock.Any(), map[string]string{
|
|
||||||
compose.ProjectTag: t.Name(),
|
|
||||||
compose.VolumeTag: "db-data",
|
|
||||||
"Name": fmt.Sprintf("%s_%s", t.Name(), "db-data"),
|
|
||||||
}).Return("fs-123abc", nil)
|
|
||||||
})
|
})
|
||||||
|
n := volumeResourceName("db-data")
|
||||||
|
f := template.Resources[n].(*efs.FileSystem)
|
||||||
|
assert.Check(t, f != nil)
|
||||||
|
assert.Equal(t, f.BackupPolicy.Status, "ENABLED") //nolint:staticcheck
|
||||||
|
assert.Equal(t, f.LifecyclePolicies[0].TransitionToIA, "AFTER_30_DAYS") //nolint:staticcheck
|
||||||
|
assert.Equal(t, f.PerformanceMode, "maxIO") //nolint:staticcheck
|
||||||
|
assert.Equal(t, f.ThroughputMode, "provisioned") //nolint:staticcheck
|
||||||
|
assert.Equal(t, f.ProvisionedThroughputInMibps, float64(1024)) //nolint:staticcheck
|
||||||
|
|
||||||
|
s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget)
|
||||||
|
assert.Check(t, s != nil)
|
||||||
|
assert.Equal(t, s.FileSystemId, cloudformation.Ref(n)) //nolint:staticcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReusePreviousVolume(t *testing.T) {
|
func TestReusePreviousVolume(t *testing.T) {
|
||||||
testVolume(t, `
|
template := convertYaml(t, `
|
||||||
services:
|
services:
|
||||||
test:
|
test:
|
||||||
image: nginx
|
image: nginx
|
||||||
|
@ -416,6 +424,9 @@ volumes:
|
||||||
compose.VolumeTag: "db-data",
|
compose.VolumeTag: "db-data",
|
||||||
}).Return("fs-123abc", nil)
|
}).Return("fs-123abc", nil)
|
||||||
})
|
})
|
||||||
|
s := template.Resources["DbdataNFSMountTargetOnSubnet1"].(*efs.MountTarget)
|
||||||
|
assert.Check(t, s != nil)
|
||||||
|
assert.Equal(t, s.FileSystemId, "fs-123abc") //nolint:staticcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServiceMapping(t *testing.T) {
|
func TestServiceMapping(t *testing.T) {
|
||||||
|
|
|
@ -98,6 +98,7 @@ var compatibleComposeAttributes = []string{
|
||||||
"volumes",
|
"volumes",
|
||||||
"volumes.external",
|
"volumes.external",
|
||||||
"volumes.name",
|
"volumes.name",
|
||||||
|
"volumes.driver_opts",
|
||||||
"networks.external",
|
"networks.external",
|
||||||
"networks.name",
|
"networks.name",
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue