Skip to content

Commit 9e21374

Browse files
committed
Tying everything together:
- read all ECS Params and flags input for SD creation - merge all inputs together and validate - find existing namespace ID if possible
1 parent d0e329b commit 9e21374

File tree

10 files changed

+843
-146
lines changed

10 files changed

+843
-146
lines changed

ecs-cli/modules/cli/compose/entity/service/service.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const (
5353
)
5454

5555
// make servicediscovery.Create easily mockable in tests
56-
var servicediscoveryCreate = servicediscovery.Create
56+
var servicediscoveryCreate servicediscovery.CreateFunc = servicediscovery.Create
5757

5858
// NewService creates an instance of a Service and also sets up a cache for task definition
5959
func NewService(ecsContext *context.ECSContext) entity.ProjectEntity {
@@ -188,21 +188,11 @@ func (s *Service) Create() error {
188188
// TODO: Instead of always setting count=1, if the containers were Stopped before,
189189
// Start should fetch the previously set desired-count from the cache and start x count of containers
190190
func (s *Service) Start() error {
191-
ecsService, err := s.describeService()
192-
if err != nil {
193-
// Describe API returns the failures for resources in the response (instead of returning an error)
194-
// Read the custom error returned from describeService to see if the resource was missing
195-
if strings.Contains(err.Error(), ecsMissingResourceCode) {
196-
return fmt.Errorf("Please use '%s' command to create the service '%s' first",
197-
flags.CreateServiceCommandName, entity.GetServiceName(s))
198-
}
199-
return err
200-
}
201-
err = entity.OptionallyCreateLogs(s)
191+
err := entity.OptionallyCreateLogs(s)
202192
if err != nil {
203193
return err
204194
}
205-
return s.startService(ecsService)
195+
return s.startService()
206196
}
207197

208198
// Up creates the task definition and service and starts the containers if necessary.
@@ -244,7 +234,11 @@ func (s *Service) Up() error {
244234
if err != nil {
245235
return err
246236
}
247-
return s.Start()
237+
return s.startService()
238+
}
239+
240+
if s.Context().CLIContext.Bool(flags.EnableServiceDiscoveryFlag) {
241+
return fmt.Errorf("Service Discovery can not be enabled on an existing ECS Service")
248242
}
249243

250244
ecsServiceName := aws.StringValue(ecsService.ServiceName)
@@ -467,7 +461,7 @@ func (s *Service) createService() error {
467461
return err
468462
}
469463

470-
serviceRegistryARN, err := servicediscoveryCreate(s.Context().CLIContext, networkMode, serviceName, s.Context().CommandConfig.Cluster)
464+
serviceRegistryARN, err := servicediscoveryCreate(networkMode, serviceName, s.Context())
471465
if err != nil {
472466
return err
473467
}
@@ -516,7 +510,17 @@ func (s *Service) describeService() (*ecs.Service, error) {
516510
}
517511

518512
// startService checks if the service has a zero desired count and updates the count to 1 (of each container)
519-
func (s *Service) startService(ecsService *ecs.Service) error {
513+
func (s *Service) startService() error {
514+
ecsService, err := s.describeService()
515+
if err != nil {
516+
// Describe API returns the failures for resources in the response (instead of returning an error)
517+
// Read the custom error returned from describeService to see if the resource was missing
518+
if strings.Contains(err.Error(), ecsMissingResourceCode) {
519+
return fmt.Errorf("Please use '%s' command to create the service '%s' first",
520+
flags.CreateServiceCommandName, entity.GetServiceName(s))
521+
}
522+
return err
523+
}
520524
desiredCount := aws.Int64Value(ecsService.DesiredCount)
521525
forceDeployment := s.Context().CLIContext.Bool(flags.ForceDeploymentFlag)
522526
if desiredCount != 0 {

ecs-cli/modules/cli/compose/entity/service/service_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ func TestCreateWithServiceDiscovery(t *testing.T) {
669669
nonMockedServicediscoveryCreate := servicediscoveryCreate
670670
defer func() { servicediscoveryCreate = nonMockedServicediscoveryCreate }()
671671

672-
servicediscoveryCreate = func(c *cli.Context, networkMode, serviceName, clusterName string) (*string, error) {
672+
servicediscoveryCreate = func(networkMode, serviceName string, c *context.ECSContext) (*string, error) {
673673
return aws.String(sdsARN), nil
674674
}
675675

@@ -699,7 +699,7 @@ func TestCreateWithServiceDiscoveryWithContainerNameAndPort(t *testing.T) {
699699
nonMockedServicediscoveryCreate := servicediscoveryCreate
700700
defer func() { servicediscoveryCreate = nonMockedServicediscoveryCreate }()
701701

702-
servicediscoveryCreate = func(c *cli.Context, networkMode, serviceName, clusterName string) (*string, error) {
702+
servicediscoveryCreate = func(networkMode, serviceName string, c *context.ECSContext) (*string, error) {
703703
return aws.String(sdsARN), nil
704704
}
705705

ecs-cli/modules/cli/servicediscovery/servicediscovery_app.go

Lines changed: 109 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ package servicediscovery
1616
import (
1717
"fmt"
1818

19+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/cli/compose/context"
1920
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/cloudformation"
2021
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/commands/flags"
2122
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/config"
23+
utils "github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils/compose"
2224
"github.com/aws/aws-sdk-go/aws"
23-
"github.com/aws/aws-sdk-go/service/ecs"
2425
"github.com/sirupsen/logrus"
2526
"github.com/urfave/cli"
2627
)
@@ -30,138 +31,160 @@ const (
3031
serviceDiscoveryServiceStackNameFormat = "amazon-ecs-cli-setup-%s-%s-service-discovery-service"
3132
)
3233

33-
// CloudFormation template parameters
3434
const (
35-
parameterKeyNamespaceDescription = "NamespaceDescription"
36-
parameterKeyVPCID = "VPCID"
37-
parameterKeyNamespaceName = "NamespaceName"
35+
cfnTemplateOutputPrivateNamespaceID = "PrivateDNSNamespaceID"
36+
cfnTemplateOutputSDSARN = "ServiceDiscoveryServiceARN"
3837
)
3938

40-
const (
41-
parameterKeySDSDescription = "SDSDescription"
42-
parameterKeySDSName = "SDSName"
43-
parameterKeyNamespaceID = "NamespaceID"
44-
parameterKeyDNSType = "DNSType"
45-
parameterKeyDNSTTL = "DNSTTL"
46-
parameterKeyHealthCheckCustomConfigFailureThreshold = "FailureThreshold"
47-
)
39+
// Create creates a DNS namespace (or uses an existing one) and creates a Service Discovery Service
40+
// The Service Discovery ARN is returned so that it can be used to enable ECS Service Discovery
41+
func Create(networkMode, serviceName string, c *context.ECSContext) (*string, error) {
42+
cfnClient := cloudformation.NewCloudformationClient(c.CommandConfig)
4843

49-
const (
50-
CFNTemplateOutputPrivateNamespaceID = "PrivateDNSNamespaceID"
51-
CFNTemplateOutputSDSARN = "ServiceDiscoveryServiceARN"
52-
)
53-
54-
const (
55-
dnsRecordTypeA = "A"
56-
dnsRecordTypeSRV = "SRV"
57-
)
44+
var ecsParamsSD *utils.ServiceDiscovery
45+
if c.ECSParams != nil {
46+
ecsParamsSD = &c.ECSParams.RunParams.ServiceDiscovery
47+
} else {
48+
ecsParamsSD = &utils.ServiceDiscovery{}
49+
}
5850

59-
var requiredParamsSDS = []string{parameterKeyNamespaceID, parameterKeySDSName, parameterKeyDNSType}
60-
var requiredParamsNamespace = []string{parameterKeyVPCID, parameterKeyNamespaceName}
51+
return create(c.CLIContext, networkMode, serviceName, cfnClient, ecsParamsSD, c.CommandConfig)
52+
}
6153

62-
// Create creates resources for service discovery and returns the ID of the Service Discovery Service
63-
func Create(c *cli.Context, networkMode, serviceName, clusterName string) (*string, error) {
64-
rdwr, err := config.NewReadWriter()
54+
func create(c *cli.Context, networkMode, serviceName string, cfnClient cloudformation.CloudformationClient, ecsParamsSD *utils.ServiceDiscovery, config *config.CommandConfig) (*string, error) {
55+
err := validateNameAndIdExclusive(c, ecsParamsSD)
6556
if err != nil {
6657
return nil, err
6758
}
68-
69-
commandConfig, err := config.NewCommandConfig(c, rdwr)
59+
input, err := mergeSDFlagsAndInput(c, ecsParamsSD)
7060
if err != nil {
7161
return nil, err
7262
}
73-
74-
cfnClient := cloudformation.NewCloudformationClient(commandConfig)
75-
76-
return create(c, networkMode, serviceName, clusterName, cfnClient)
77-
}
78-
79-
func create(c *cli.Context, networkMode, serviceName, clusterName string, cfnClient cloudformation.CloudformationClient) (*string, error) {
80-
// create namespace
81-
namespaceParams := namespaceCFNParams(c)
82-
if err := namespaceParams.Validate(); err != nil {
83-
return nil, err
84-
}
85-
86-
namespaceStackName := cfnStackName(privateDNSNamespaceStackNameFormat, clusterName, serviceName)
87-
if _, err := cfnClient.CreateStack(cloudformation.GetPrivateNamespaceTemplate(), namespaceStackName, false, namespaceParams); err != nil {
63+
err = validateMergedSDInputFields(input, networkMode)
64+
if err != nil {
8865
return nil, err
8966
}
67+
namespaceWarningsWhenIDSpecified(input)
9068

91-
logrus.Info("Waiting for the private DNS namespace to be created...")
92-
// Wait for stack creation
93-
cfnClient.WaitUntilCreateComplete(namespaceStackName)
94-
95-
// Get the ID of the namespace we just created
96-
namespaceID, err := getOutputIDFromStack(cfnClient, namespaceStackName, CFNTemplateOutputPrivateNamespaceID)
69+
namespaceID, err := getOrCreateNamespace(c, networkMode, serviceName, cfnClient, input, config)
9770
if err != nil {
9871
return nil, err
9972
}
10073

10174
// create SDS
102-
sdsParams := sdsCFNParams(aws.StringValue(namespaceID), serviceName, networkMode)
75+
sdsParams := getSDSCFNParams(aws.StringValue(namespaceID), serviceName, networkMode, input)
10376
if err := sdsParams.Validate(); err != nil {
10477
return nil, err
10578
}
10679

107-
sdsStackName := cfnStackName(serviceDiscoveryServiceStackNameFormat, clusterName, serviceName)
80+
sdsStackName := cfnStackName(serviceDiscoveryServiceStackNameFormat, config.Cluster, serviceName)
10881
if _, err := cfnClient.CreateStack(cloudformation.GetSDSTemplate(), sdsStackName, false, sdsParams); err != nil {
10982
return nil, err
11083
}
11184

11285
logrus.Info("Waiting for the Service Discovery Service to be created...")
113-
// Wait for stack creation
11486
cfnClient.WaitUntilCreateComplete(sdsStackName)
11587

11688
// Return the ID of the SDS we just created
117-
return getOutputIDFromStack(cfnClient, sdsStackName, CFNTemplateOutputSDSARN)
89+
return getOutputIDFromStack(cfnClient, sdsStackName, cfnTemplateOutputSDSARN)
11890
}
11991

120-
func getOutputIDFromStack(cfnClient cloudformation.CloudformationClient, stackName, outputKey string) (*string, error) {
121-
response, err := cfnClient.DescribeStacks(stackName)
122-
if err != nil {
92+
func createNamespace(c *cli.Context, networkMode, serviceName, clusterName string, cfnClient cloudformation.CloudformationClient, input *utils.ServiceDiscovery) (*string, error) {
93+
namespaceParams := getNamespaceCFNParams(input)
94+
if err := namespaceParams.Validate(); err != nil {
12395
return nil, err
12496
}
125-
if len(response.Stacks) == 0 {
126-
return nil, fmt.Errorf("Could not find CloudFormation stack: %s", stackName)
127-
}
12897

129-
for _, output := range response.Stacks[0].Outputs {
130-
if aws.StringValue(output.OutputKey) == outputKey {
131-
return output.OutputValue, nil
132-
}
98+
namespaceStackName := cfnStackName(privateDNSNamespaceStackNameFormat, clusterName, serviceName)
99+
if _, err := cfnClient.CreateStack(cloudformation.GetPrivateNamespaceTemplate(), namespaceStackName, false, namespaceParams); err != nil {
100+
return nil, err
133101
}
134-
return nil, fmt.Errorf("Failed to find output %s in stack %s", outputKey, stackName)
135102

136-
}
103+
logrus.Info("Waiting for the private DNS namespace to be created...")
104+
cfnClient.WaitUntilCreateComplete(namespaceStackName)
137105

138-
func cfnStackName(stackName, cluster, service string) string {
139-
return fmt.Sprintf(stackName, cluster, service)
106+
// Get the ID of the namespace we just created
107+
return getOutputIDFromStack(cfnClient, namespaceStackName, cfnTemplateOutputPrivateNamespaceID)
140108
}
141109

142-
func sdsCFNParams(namespaceID, sdsName, networkMode string) *cloudformation.CfnStackParams {
143-
cfnParams := cloudformation.NewCfnStackParams(requiredParamsSDS)
144-
145-
cfnParams.Add(parameterKeyNamespaceID, namespaceID)
146-
cfnParams.Add(parameterKeySDSName, sdsName)
147-
148-
dnsType := dnsRecordTypeSRV
149-
if networkMode == ecs.NetworkModeAwsvpc {
150-
dnsType = dnsRecordTypeA
110+
func getOrCreateNamespace(c *cli.Context, networkMode, serviceName string, cfnClient cloudformation.CloudformationClient, input *utils.ServiceDiscovery, config *config.CommandConfig) (*string, error) {
111+
namespace, err := getExistingNamespace(input, config)
112+
if err != nil {
113+
return nil, err
151114
}
152-
cfnParams.Add(parameterKeyDNSType, dnsType)
115+
if namespace == nil {
116+
namespace, err = createNamespace(c, networkMode, serviceName, config.Cluster, cfnClient, input)
117+
} else {
118+
logrus.Infof("Using existing namespace %s", *namespace)
119+
}
120+
return namespace, err
121+
}
153122

154-
return cfnParams
123+
func getExistingNamespace(input *utils.ServiceDiscovery, config *config.CommandConfig) (*string, error) {
124+
switch {
125+
case input.PrivateDNSNamespace.ID != "":
126+
return aws.String(input.PrivateDNSNamespace.ID), nil
127+
case input.PublicDNSNamespace.ID != "":
128+
return aws.String(input.PublicDNSNamespace.ID), nil
129+
case input.PrivateDNSNamespace.Name != "":
130+
return findPrivateNamespace(input.PrivateDNSNamespace.Name, input.PrivateDNSNamespace.VPC, config)
131+
case input.PublicDNSNamespace.Name != "":
132+
return getPublicNamespaceSpecifiedByName(input.PublicDNSNamespace.Name, config)
133+
default:
134+
return nil, nil
135+
}
155136
}
156137

157-
func namespaceCFNParams(context *cli.Context) *cloudformation.CfnStackParams {
158-
cfnParams := cloudformation.NewCfnStackParams(requiredParamsNamespace)
138+
func getPublicNamespaceSpecifiedByName(name string, config *config.CommandConfig) (*string, error) {
139+
namespace, err := findPublicNamespace(name, config)
140+
if err != nil {
141+
return nil, err
142+
}
143+
if namespace == nil {
144+
// we do not create public namespaces, so failing to find it is in an error case
145+
return nil, fmt.Errorf("Failed to find public namespace %s", name)
146+
}
147+
return namespace, err
148+
}
159149

160-
namespaceName := context.String(flags.PrivateDNSNamespaceNameFlag)
161-
cfnParams.Add(parameterKeyNamespaceName, namespaceName)
150+
// Flags override the values specified in ECS Params
151+
// Merges fields for Namespace and SDS
152+
// This function just merges fields; it doesn't validate them
153+
func mergeSDFlagsAndInput(c *cli.Context, ecsParamsSD *utils.ServiceDiscovery) (*utils.ServiceDiscovery, error) {
154+
// Private DNS Namespace fields
155+
privNamespace := ecsParamsSD.PrivateDNSNamespace
156+
privNamespace.Namespace = resolveNamespaceOverride(c.String(flags.PrivateDNSNamespaceNameFlag), c.String(flags.PrivateDNSNamespaceIDFlag), "private", privNamespace.Namespace)
157+
privNamespace.VPC = resolveStringFieldOverride(c, flags.VpcIdFlag, privNamespace.VPC, "private_dns_namespace.vpc")
158+
ecsParamsSD.PrivateDNSNamespace = privNamespace
159+
160+
// Public DNS Namespace fields
161+
pubNamespace := ecsParamsSD.PublicDNSNamespace
162+
pubNamespace.Namespace = resolveNamespaceOverride(c.String(flags.PublicDNSNamespaceNameFlag), c.String(flags.PublicDNSNamespaceIDFlag), "public", pubNamespace.Namespace)
163+
ecsParamsSD.PublicDNSNamespace = pubNamespace
164+
165+
// SDS fields
166+
sds := ecsParamsSD.ServiceDiscoveryService
167+
sds.DNSConfig.Type = resolveStringFieldOverride(c, flags.DNSTypeFlag, sds.DNSConfig.Type, "dns_config.type")
168+
ttl, err := resolveIntPointerFieldOverride(c, flags.DNSTTLFlag, sds.DNSConfig.TTL, "dns_config.ttl")
169+
if err != nil {
170+
return nil, err
171+
}
172+
sds.DNSConfig.TTL = ttl
173+
threshold, err := resolveIntPointerFieldOverride(c, flags.HealthcheckCustomConfigFailureThresholdFlag, sds.HealthCheckCustomConfig.FailureThreshold, "failure_threshold")
174+
if err != nil {
175+
return nil, err
176+
}
177+
sds.HealthCheckCustomConfig.FailureThreshold = threshold
178+
ecsParamsSD.ServiceDiscoveryService = sds
162179

163-
vpcID := context.String(flags.VpcIdFlag)
164-
cfnParams.Add(parameterKeyVPCID, vpcID)
180+
// top level fields
181+
// these container fields aren't used when creating Route53 resources in this package, but we aggregate them here so that we can do error checking- SRV records require these.
182+
ecsParamsSD.ContainerName = resolveStringFieldOverride(c, flags.ServiceDiscoveryContainerNameFlag, ecsParamsSD.ContainerName, "container_name")
183+
port, err := resolveIntPointerFieldOverride(c, flags.ServiceDiscoveryContainerPortFlag, ecsParamsSD.ContainerPort, "container_port")
184+
if err != nil {
185+
return nil, err
186+
}
187+
ecsParamsSD.ContainerPort = port
165188

166-
return cfnParams
189+
return ecsParamsSD, nil
167190
}

0 commit comments

Comments
 (0)